init
Some checks failed
release / release (push) Has been cancelled

This commit is contained in:
2025-06-11 16:21:08 +08:00
commit 2efae7c0f0
36 changed files with 63396 additions and 0 deletions

51
src/App.svelte Normal file
View File

@@ -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
src/app.css Normal file
View File

@@ -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;
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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>

12
src/lib/chats.svelte.ts Normal file
View File

@@ -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
src/main.ts Normal file
View File

@@ -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;

View File

@@ -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
src/preview/chats.json Normal file

File diff suppressed because it is too large Load Diff

9
src/preview/main.ts Normal file
View File

@@ -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;

View File

@@ -0,0 +1,6 @@
export const state = $state({
giftOnly: false,
commentLimit: 30,
skip: 0,
showJson: false
});

7
src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />
import { OneSDK } from '@onecomme.com/onesdk/OneSDK';
declare global {
var OneSDK: OneSDK;
}