@@ -0,0 +1,51 @@
|
||||
<script lang="ts">
|
||||
import type { Comment } from '@onecomme.com/onesdk/types/Comment';
|
||||
import { SendHorizontalIcon } from '@lucide/svelte';
|
||||
import RenderComment from '@/components/RenderComment.svelte';
|
||||
let { chats }: { chats: Comment[] } = $props();
|
||||
|
||||
let date = $state(new Date());
|
||||
|
||||
let hour = $derived(date.getHours());
|
||||
let minute = $derived(date.getMinutes());
|
||||
|
||||
let render_chats = $derived([...chats].reverse());
|
||||
|
||||
function pad(str: string | { toString(): string }, char: number): string {
|
||||
const s = str.toString();
|
||||
const diff = char - s.length;
|
||||
if (diff <= 0) return s;
|
||||
return s + '0'.repeat(diff);
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
const interval = setInterval(() => {
|
||||
date = new Date();
|
||||
}, 1000);
|
||||
return () => clearInterval(interval);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="bg-frame px-30 pt-30 pb-15 rounded-30 h-full w-full flex flex-col gap-15 font-naikai">
|
||||
<div class="grow flex flex-col rounded-15 overflow-hidden bg-background">
|
||||
<div class="bg-status flex flex-row justify-center p-5 text-16 font-bold text-status-text">
|
||||
<span>{pad(hour, 2)}:{pad(minute, 2)}</span>
|
||||
</div>
|
||||
<div class="grow flex flex-col-reverse px-15 h-0 overflow-hidden">
|
||||
{#each render_chats as chat (chat.id ?? chat)}
|
||||
<RenderComment {chat} />
|
||||
{/each}
|
||||
</div>
|
||||
<div class="px-30 py-15">
|
||||
<div
|
||||
class="rounded-full text-20 text-input-text bg-input px-20 py-10 flex flex-row items-center justify-between gap-10"
|
||||
>
|
||||
<span>回覆...</span>
|
||||
<SendHorizontalIcon class="size-20 text-input-icon" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row justify-center">
|
||||
<div class="size-50 rounded-full bg-background"></div>
|
||||
</div>
|
||||
</div>
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
@theme {
|
||||
--spacing: calc(var(--render-scale, 1) * 1px);
|
||||
|
||||
--color-frame: var(--chat-frame, #ffafb5);
|
||||
--color-status: var(--chat-status, #f4f4f4);
|
||||
--color-status-text: var(--chat-status-text, #000000);
|
||||
--color-background: var(--chat-background, #ffffff);
|
||||
|
||||
--color-name: var(--chat-name, #000000);
|
||||
--color-name-moderator: var(--chat-name-moderator, #193cb8);
|
||||
--color-name-owner: var(--chat-name-owner, #a65f00);
|
||||
--color-name-member: var(--chat-name-member, #006045);
|
||||
|
||||
--color-chat-bubble: var(--chat-bubble, #cad1f9);
|
||||
--color-chat-bubble-owner: var(--chat-bubble-owner, #fdebc0);
|
||||
--color-chat-text: var(--chat-text, #000000);
|
||||
|
||||
--color-hint: var(--chat-hint, #8f8f8f);
|
||||
|
||||
--color-input: var(--chat-input, #f4f4f4);
|
||||
--color-input-text: var(--chat-input-text, #8f8f8f);
|
||||
|
||||
--font-naikai: 'NaikaiFont', var(--font-sans);
|
||||
}
|
||||
|
||||
@utility rounded-* {
|
||||
border-radius: calc(--value(number) * var(--spacing));
|
||||
}
|
||||
|
||||
@utility text-* {
|
||||
font-size: calc(--value(number) * var(--spacing));
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'NaikaiFont';
|
||||
src:
|
||||
url('./assets/naikai/NaikaiFont-ExtraLight.woff') format('woff'),
|
||||
url('./assets/naikai/NaikaiFont-ExtraLight.woff2') format('woff2');
|
||||
font-weight: 200;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'NaikaiFont';
|
||||
src:
|
||||
url('./assets/naikai/NaikaiFont-Light.woff') format('woff'),
|
||||
url('./assets/naikai/NaikaiFont-Light.woff2') format('woff2');
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'NaikaiFont';
|
||||
src:
|
||||
url('./assets/naikai/NaikaiFont-Regular.woff') format('woff'),
|
||||
url('./assets/naikai/NaikaiFont-Regular.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'NaikaiFont';
|
||||
src:
|
||||
url('./assets/naikai/NaikaiFont-SemiBold.woff') format('woff'),
|
||||
url('./assets/naikai/NaikaiFont-SemiBold.woff2') format('woff2');
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'NaikaiFont';
|
||||
src:
|
||||
url('./assets/naikai/NaikaiFont-Bold.woff') format('woff'),
|
||||
url('./assets/naikai/NaikaiFont-Bold.woff2') format('woff2');
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
#app {
|
||||
@apply h-screen w-screen;
|
||||
}
|
||||
:root,
|
||||
body {
|
||||
@apply bg-transparent;
|
||||
}
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -0,0 +1,98 @@
|
||||
<script lang="ts">
|
||||
import { WrenchIcon } from '@lucide/svelte';
|
||||
import type { Comment } from '@onecomme.com/onesdk/types/Comment';
|
||||
|
||||
let { chat }: { chat: Comment } = $props();
|
||||
|
||||
let author_color = $derived.by(() => {
|
||||
if (chat.data.isOwner) return 'text-name-owner';
|
||||
|
||||
if (chat.service === 'youtube') {
|
||||
if (chat.data.isModerator) return 'text-name-moderator';
|
||||
if (chat.data.isMember) return 'text-name-member';
|
||||
}
|
||||
return 'text-name';
|
||||
});
|
||||
|
||||
let layout = $derived(
|
||||
chat.data.isOwner
|
||||
? ['flex-row-reverse', 'pl-30', 'origin-bottom-right']
|
||||
: ['flex-row', 'pr-30', 'origin-bottom-left']
|
||||
);
|
||||
|
||||
let is_youtube_gift = $derived(chat.service === 'youtube' && chat.data.hasGift);
|
||||
let gift_type = $derived(
|
||||
chat.service === 'youtube' && chat.data.hasGift ? chat.data.giftType : undefined
|
||||
);
|
||||
|
||||
let author_name = $derived(chat.data.displayName || chat.data.name);
|
||||
</script>
|
||||
|
||||
{#if gift_type === 'giftreceived'}
|
||||
<div class="text-14 text-hint text-center py-5 origin-bottom scale-enter">
|
||||
{chat.data.comment}
|
||||
</div>
|
||||
{:else}
|
||||
<div class={[layout, 'flex gap-10 scale-enter py-10']}>
|
||||
<img src={chat.data.originalProfileImage} class="size-40 rounded-full" alt={author_name} />
|
||||
<div
|
||||
class={[
|
||||
'flex flex-col gap-5 grow w-0',
|
||||
chat.data.isOwner ? 'items-end' : 'items-start'
|
||||
]}
|
||||
>
|
||||
<div
|
||||
class={[
|
||||
'text-ellipsis whitespace-nowrap px-5 gap-5 flex flex-row items-center font-semibold text-12 max-w-full',
|
||||
author_color,
|
||||
chat.data.isOwner ? 'justify-end' : 'justify-start'
|
||||
]}
|
||||
>
|
||||
<span class="text-ellipsis whitespace-nowrap">{author_name}</span>
|
||||
{#if chat.service === 'youtube' && chat.data.isModerator}
|
||||
<WrenchIcon class="size-12 min-w-14 min-h-14" />
|
||||
{/if}
|
||||
</div>
|
||||
{#if is_youtube_gift && gift_type === 'supersticker'}
|
||||
<div class="supersticker">
|
||||
{@html chat.data.comment}
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class={[
|
||||
'px-15 py-10 text-chat-text bubble rounded-15 text-16',
|
||||
chat.data.isOwner ? 'bg-chat-bubble-owner' : 'bg-chat-bubble'
|
||||
]}
|
||||
>
|
||||
{@html chat.data.comment}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
@keyframes scale {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.scale-enter {
|
||||
animation: scale 0.2s ease-out;
|
||||
}
|
||||
|
||||
.bubble :global(img) {
|
||||
display: inline-block;
|
||||
width: calc(16 * var(--spacing));
|
||||
height: calc(16 * var(--spacing));
|
||||
}
|
||||
|
||||
.supersticker :global(img) {
|
||||
width: calc(72 * var(--spacing));
|
||||
height: calc(72 * var(--spacing));
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { Comment } from '@onecomme.com/onesdk/types/Comment';
|
||||
|
||||
let value = $state.raw<Comment[]>([]);
|
||||
|
||||
export const chats = {
|
||||
get value() {
|
||||
return value;
|
||||
},
|
||||
set value(newValue: Comment[]) {
|
||||
value = newValue;
|
||||
}
|
||||
};
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import './app.css';
|
||||
import App from './App.svelte';
|
||||
import type { Comment } from '@onecomme.com/onesdk/types/Comment';
|
||||
import { chats } from './lib/chats.svelte';
|
||||
import { mount } from 'svelte';
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
globalThis.OneSDK = (await import('@onecomme.com/onesdk')).default;
|
||||
}
|
||||
|
||||
OneSDK.subscribe({
|
||||
action: 'comments',
|
||||
callback: (res: Comment[]) => {
|
||||
chats.value = res;
|
||||
}
|
||||
});
|
||||
OneSDK.setup({
|
||||
commentLimit: 30
|
||||
});
|
||||
OneSDK.connect();
|
||||
|
||||
const app = mount(App, {
|
||||
target: document.getElementById('app')!,
|
||||
props: {
|
||||
get chats() {
|
||||
return chats.value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default app;
|
||||
@@ -0,0 +1,43 @@
|
||||
<script lang="ts">
|
||||
import type { Comment } from '@onecomme.com/onesdk/types/Comment';
|
||||
import data from '@/preview/chats.json';
|
||||
import { state } from '@/preview/states.svelte';
|
||||
import App from '@/App.svelte';
|
||||
|
||||
let filtered = $derived(
|
||||
state.giftOnly ? data.filter((c) => c.data.hasGift) : data
|
||||
) as Comment[];
|
||||
let chats = $derived(filtered.slice(state.skip, state.skip + state.commentLimit));
|
||||
</script>
|
||||
|
||||
<div class="p-8 w-screen h-screen flex flex-col gap-16">
|
||||
<div class="bg-white sticky top-0 z-50 flex flex-col gap-2 py-2">
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<label>
|
||||
<input type="checkbox" bind:checked={state.giftOnly} />
|
||||
Gift Only
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" bind:checked={state.showJson} />
|
||||
Show JSON
|
||||
</label>
|
||||
</div>
|
||||
<label class="flex flex-row items-center gap-1">
|
||||
Limit ({state.commentLimit})
|
||||
<input type="range" bind:value={state.commentLimit} class="flex-grow" min="10" />
|
||||
</label>
|
||||
<label class="flex flex-row items-center gap-1">
|
||||
Skip ({state.skip})
|
||||
<input
|
||||
type="range"
|
||||
bind:value={state.skip}
|
||||
class="flex-grow"
|
||||
min={0}
|
||||
max={filtered.length - state.commentLimit}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="grow">
|
||||
<App {chats} />
|
||||
</div>
|
||||
</div>
|
||||
+62765
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
||||
import '../app.css';
|
||||
import { mount } from 'svelte';
|
||||
import Preview from './Preview.svelte';
|
||||
|
||||
const app = mount(Preview, {
|
||||
target: document.getElementById('app')!
|
||||
});
|
||||
|
||||
export default app;
|
||||
@@ -0,0 +1,6 @@
|
||||
export const state = $state({
|
||||
giftOnly: false,
|
||||
commentLimit: 30,
|
||||
skip: 0,
|
||||
showJson: false
|
||||
});
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
/// <reference types="svelte" />
|
||||
/// <reference types="vite/client" />
|
||||
import { OneSDK } from '@onecomme.com/onesdk/OneSDK';
|
||||
|
||||
declare global {
|
||||
var OneSDK: OneSDK;
|
||||
}
|
||||
Reference in New Issue
Block a user