Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
238 changes: 238 additions & 0 deletions src/lib/components/tweet.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
<script lang="ts">
import Like from '$lib/icons/like.svelte'
import Reply from '$lib/icons/reply.svelte'
import Retweet from '$lib/icons/retweet.svelte'
import RetweetSmall from '$lib/icons/retweet_small.svelte'
import ThreeDot from '$lib/icons/three_dot.svelte'
import UpArrow from '$lib/icons/up_arrow.svelte'
import { Tweet } from '$lib/tweet'
import type { components } from 'twitter-api-sdk/dist/types'
import { _ } from 'svelte-i18n'

export let tweet_data: components['schemas']['Tweet']
export let user_data_map: Map<string, components['schemas']['User']>
export let referenced_tweets_data_map: Map<string, components['schemas']['Tweet']>
export let media_data_map: Map<string, components['schemas']['Media']>

let tweet: Tweet = new Tweet(
tweet_data,
user_data_map,
referenced_tweets_data_map,
media_data_map
)

function on_click_tweet(tweet: Tweet): void {
const selection = window.getSelection()

if (selection?.isCollapsed) {
location.href = tweet.status_url
}
}
</script>

<div class="flex_column tweet_container" on:click={() => on_click_tweet(tweet)} on:keypress>
{#if tweet.is_retweet}
<div class="flex_row align_items_center retweet_row">
<div class="flex_row align_items_center avatar_above">
<div class="retweet_icon"><RetweetSmall /></div>
</div>
{$_('name_retweeted', {
values: { name: tweet.retweet_user_name },
})}
</div>
{/if}
<div class="flex_row tweet_element">
<div class="avatar_container">
<a href={tweet.profile_url}>
<img class="avatar_icon" src={tweet.profile_image_url} alt="avatar" />
</a>
</div>
<div class="flex_column tweet_body">
<div class="flex_column text_column">
<div class="flex_row username_row">
<div class="name overflow_ellipsis">
<a href={tweet.profile_url}>{tweet.name}</a>
</div>
<div class="username overflow_ellipsis">
<a href={tweet.profile_url}>@{tweet.username}</a>
</div>
<div>·</div>
<div class="time">
<a href={tweet.status_url}>
<time datetime={tweet.created_at}>{tweet.elapsed_time}</time>
</a>
</div>
<div class="flex_auto" />
<div class="action_icon tap_area_container">
<div class="tap_area" />
<ThreeDot />
</div>
</div>
<div dir="auto">
{@html tweet.html_text}
</div>
</div>

{#if tweet.media_count === 1}
<div class="media_frame">
<img alt="画像" src={tweet.media_url_0} class="media" />
</div>
{:else if tweet.media_count === 2}
<div class="media_frame flex_column media_column media_frame_tile">
<div class="flex_row media_row">
<div class="media_cell">
<img alt="画像" src={tweet.media_url_0} class="media" />
</div>
<div class="media_cell">
<img alt="画像" src={tweet.media_url_1} class="media" />
</div>
</div>
</div>
{:else if tweet.media_count === 3}
<div class="media_frame media_row media_frame_tile">
<div class="media_cell">
<img alt="画像" src={tweet.media_url_0} class="media" />
</div>
<div class="flex_column media_column">
<div class="media_cell">
<img alt="画像" src={tweet.media_url_1} class="media" />
</div>
<div class="media_cell">
<img alt="画像" src={tweet.media_url_2} class="media" />
</div>
</div>
</div>
{:else if tweet.media_count === 4}
<div class="media_frame flex_column media_column media_frame_tile">
<div class="flex_row media_row">
<div class="media_cell">
<img alt="画像" src={tweet.media_url_0} class="media" />
</div>
<div class="media_cell">
<img alt="画像" src={tweet.media_url_1} class="media" />
</div>
</div>
<div class="flex_row media_row">
<div class="media_cell">
<img alt="画像" src={tweet.media_url_2} class="media" />
</div>
<div class="media_cell">
<img alt="画像" src={tweet.media_url_3} class="media" />
</div>
</div>
</div>
{/if}

<div class="flex_row align_items_center action_row">
<div class="flex_row align_items_center action">
<div class="action_icon tap_area_container">
<div class="tap_area" />
<Reply />
</div>
<div class="icon_text overflow_ellipsis">{tweet.reply_count}</div>
</div>
<div class="flex_row align_items_center action">
<div class="action_icon tap_area_container">
<div class="tap_area" />
<Retweet />
</div>
<div class="icon_text overflow_ellipsis">{tweet.retweet_count}</div>
</div>
<div class="flex_row align_items_center action">
<div class="action_icon tap_area_container">
<div class="tap_area" />
<Like />
</div>
<div class="icon_text overflow_ellipsis">{tweet.like_count}</div>
</div>
<div class="flex_row align_items_center action_up_arrow">
<div class="action_icon tap_area_container">
<div class="tap_area" />
<UpArrow />
</div>
</div>
</div>
</div>
</div>
</div>

<style>
.tweet_container {
padding: 16px;
gap: 6px;
cursor: pointer;
}

.tweet_container:hover {
transition: 0.2s;
background-color: rgb(245, 248, 250);
}

.avatar_above {
min-width: 48px;
justify-content: flex-end;
}

.tweet_element {
gap: 12px;
}

.tweet_body {
gap: 12px;
min-width: 0;
flex: auto;

font-size: 16px;
line-height: 20px;
font-weight: 400;
}

.text_column {
gap: 8px;
min-width: 0;
overflow-wrap: break-word;
}

.username_row {
gap: 4px;

font-weight: 400;
font-size: 15px;
line-height: 20px;
}

.time {
white-space: nowrap;
}

.time a {
color: var(--gray-color);
}

.action_row {
gap: 10px;

color: var(--gray-color);
font-size: 13px;
line-height: 16px;
}

.action {
gap: 10px;
flex: 1;
}

.action_up_arrow {
flex: 0;
}

.icon_text {
font-size: 14px;
}

.action_icon {
width: 17.5px;
height: 17.5px;
fill: var(--gray-color);
}
</style>
74 changes: 74 additions & 0 deletions src/lib/components/tweet_list.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<script lang="ts">
import TweetComponent from '$lib/components/tweet.svelte'
import Loading from '$lib/icons/loading.svelte'
import { TweetUtil } from '$lib/tweet_util'
import { Util } from '$lib/util'
import { onMount } from 'svelte'
import type { components } from 'twitter-api-sdk/dist/types'
import '../../routes/app.css'

let tweets_data: components['schemas']['Tweet'][] = []
let users_data: components['schemas']['User'][] = []
let referenced_tweets_data: components['schemas']['Tweet'][] = []
let media_data: components['schemas']['Media'][] = []

export let api_path: string

let user_data_map: Map<string, components['schemas']['User']>
let referenced_tweets_data_map: Map<string, components['schemas']['Tweet']>
let media_data_map: Map<string, components['schemas']['Media']>

let is_loading = false

async function fetch_data(api_path: string): Promise<void> {
const tweets = await fetch(api_path)

if (tweets.status !== 200) {
console.log(tweets.body)
return
}

const home_data = await tweets.json()

tweets_data = home_data.data
users_data = home_data.includes.users ?? []
referenced_tweets_data = home_data.includes.tweets ?? []
media_data = home_data.includes.media ?? []

console.log(tweets_data.length)

user_data_map = Util.to_map_by_id(users_data)
referenced_tweets_data_map = Util.to_map_by_id(referenced_tweets_data)

const missing_media_data = await TweetUtil.get_missing_media_data(
tweets_data,
user_data_map,
referenced_tweets_data_map,
media_data
)

media_data = media_data.concat(missing_media_data)

media_data_map = Util.to_map_by_media_key(media_data)
}

onMount(async () => {
if (tweets_data.length === 0) {
is_loading = true
await fetch_data(api_path)
is_loading = false
}
})
</script>

{#if is_loading}
<div style="padding: 16px; display: flex; justify-content: center">
<Loading />
</div>
{:else}
<div class="flex_column gap_border">
{#each tweets_data as tweet_data}
<TweetComponent {tweet_data} {user_data_map} {referenced_tweets_data_map} {media_data_map} />
{/each}
</div>
{/if}
7 changes: 7 additions & 0 deletions src/lib/icons/calendar.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<svg viewBox="0 0 24 24" aria-hidden="true"
><g
><path
d="M7 4V3h2v1h6V3h2v1h1.5C19.89 4 21 5.12 21 6.5v12c0 1.38-1.11 2.5-2.5 2.5h-13C4.12 21 3 19.88 3 18.5v-12C3 5.12 4.12 4 5.5 4H7zm0 2H5.5c-.27 0-.5.22-.5.5v12c0 .28.23.5.5.5h13c.28 0 .5-.22.5-.5v-12c0-.28-.22-.5-.5-.5H17v1h-2V6H9v1H7V6zm0 6h2v-2H7v2zm0 4h2v-2H7v2zm4-4h2v-2h-2v2zm0 4h2v-2h-2v2zm4-4h2v-2h-2v2z"
/></g
></svg
>
7 changes: 7 additions & 0 deletions src/lib/icons/link.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<svg viewBox="0 0 24 24" aria-hidden="true"
><g
><path
d="M18.36 5.64c-1.95-1.96-5.11-1.96-7.07 0L9.88 7.05 8.46 5.64l1.42-1.42c2.73-2.73 7.16-2.73 9.9 0 2.73 2.74 2.73 7.17 0 9.9l-1.42 1.42-1.41-1.42 1.41-1.41c1.96-1.96 1.96-5.12 0-7.07zm-2.12 3.53l-7.07 7.07-1.41-1.41 7.07-7.07 1.41 1.41zm-12.02.71l1.42-1.42 1.41 1.42-1.41 1.41c-1.96 1.96-1.96 5.12 0 7.07 1.95 1.96 5.11 1.96 7.07 0l1.41-1.41 1.42 1.41-1.42 1.42c-2.73 2.73-7.16 2.73-9.9 0-2.73-2.74-2.73-7.17 0-9.9z"
/></g
></svg
>
7 changes: 7 additions & 0 deletions src/lib/icons/location_pin.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<svg viewBox="0 0 24 24" aria-hidden="true">
<g>
<path
d="M12 7c-1.93 0-3.5 1.57-3.5 3.5S10.07 14 12 14s3.5-1.57 3.5-3.5S13.93 7 12 7zm0 5c-.827 0-1.5-.673-1.5-1.5S11.173 9 12 9s1.5.673 1.5 1.5S12.827 12 12 12zm0-10c-4.687 0-8.5 3.813-8.5 8.5 0 5.967 7.621 11.116 7.945 11.332l.555.37.555-.37c.324-.216 7.945-5.365 7.945-11.332C20.5 5.813 16.687 2 12 2zm0 17.77c-1.665-1.241-6.5-5.196-6.5-9.27C5.5 6.916 8.416 4 12 4s6.5 2.916 6.5 6.5c0 4.073-4.835 8.028-6.5 9.27z"
/></g
>
</svg>
Loading