Skip to content
Merged
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
31 changes: 28 additions & 3 deletions src/components/Hyperchat.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import PinnedMessage from './PinnedMessage.svelte';
import ChatSummary from './ChatSummary.svelte';
import RedirectBanner from './RedirectBanner.svelte';
import PollResults from './PollResults.svelte';
import PaidMessage from './PaidMessage.svelte';
import MembershipItem from './MembershipItem.svelte';
import ReportBanDialog from './ReportBanDialog.svelte';
Expand Down Expand Up @@ -64,6 +65,7 @@
const TRUNCATE_SIZE = 20;
let messageActions: Array<Chat.MessageAction | Welcome> = [];
const messageKeys = new Set<string>();
let poll: Ytc.ParsedPoll | null;
let pinned: Ytc.ParsedPinned | null;
let summary: Ytc.ParsedSummary | null;
let redirect: Ytc.ParsedRedirect | null;
Expand Down Expand Up @@ -92,7 +94,7 @@
// },
// showtime: 5000,
// };
$: hasBanner = Boolean(pinned ?? redirect ?? (summary != null && $showChatSummary));
$: hasBanner = Boolean(poll ?? pinned ?? redirect ?? (summary != null && $showChatSummary));
let div: HTMLElement;
let isAtBottom = true;
let truncateInterval: number | undefined;
Expand Down Expand Up @@ -223,6 +225,9 @@
case 'delete':
onDelete(action.deletion);
break;
case 'poll':
poll = action;
break;
case 'summary':
summary = action;
break;
Expand All @@ -233,7 +238,22 @@
pinned = action;
break;
case 'unpin':
pinned = null;
if (action.targetActionId) {
if (action.targetActionId === pinned?.actionId) {
pinned = null;
}
if (action.targetActionId === summary?.actionId) {
summary = null;
}
if (action.targetActionId === poll?.actionId) {
poll = null;
}
if (action.targetActionId === redirect?.actionId) {
redirect = null;
}
} else {
pinned = null;
}
break;
case 'playerProgress':
$currentProgress = action.playerProgress;
Expand Down Expand Up @@ -409,7 +429,7 @@
}
}, 350);
};
$: $enableStickySuperchatBar, pinned, topBarResized();
$: $enableStickySuperchatBar, hasBanner, topBarResized();

const isMention = (msg: Ytc.ParsedMessage) => {
return $selfChannelName && msg.message.map(run => {
Expand Down Expand Up @@ -465,6 +485,11 @@
</div>
{#if hasBanner}
<div class="absolute top-0 w-full" bind:this={topBar}>
{#if poll}
<div class="mx-1.5 mt-1.5">
<PollResults poll={poll} on:resize={topBarResized} />
</div>
{/if}
{#if summary && $showChatSummary}
<div class="mx-1.5 mt-1.5">
<ChatSummary summary={summary} on:resize={topBarResized} />
Expand Down
90 changes: 90 additions & 0 deletions src/components/PollResults.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<script lang="ts">
import { slide, fade } from 'svelte/transition';
import MessageRun from './MessageRuns.svelte';
import Tooltip from './common/Tooltip.svelte';
import Icon from 'smelte/src/components/Icon';
import { Theme } from '../ts/chat-constants';
import { createEventDispatcher } from 'svelte';
import { showProfileIcons } from '../ts/storage';
import ProgressLinear from 'smelte/src/components/ProgressLinear';

export let poll: Ytc.ParsedPoll;

let dismissed = false;
let shorten = false;
let prevId: string | null = null;
const classes = 'rounded inline-flex flex-col overflow-visible ' +
'bg-secondary-900 p-2 w-full text-white z-10 shadow';

const onShorten = () => {
shorten = !shorten;
};

$: if (poll.actionId !== prevId) {
dismissed = false;
shorten = false;
prevId = poll.actionId;
}

const dispatch = createEventDispatcher();
$: dismissed, shorten, dispatch('resize');
</script>

{#if !dismissed}
<div
class={classes}
transition:fade={{ duration: 250 }}
>
<div class="flex flex-row items-center cursor-pointer" on:click={onShorten}>
<div class="font-medium tracking-wide text-white flex-1">
<span class="mr-1 inline-block" style="transform: translateY(3px);">
<Icon small>
{#if shorten}
expand_more
{:else}
expand_less
{/if}
</Icon>
</span>
{#if $showProfileIcons}
<img
class="h-5 w-5 inline align-middle rounded-full flex-none"
src={poll.item.profileIcon.src}
alt={poll.item.profileIcon.alt}
/>
{/if}
{#each poll.item.header as run}
{#if run.type === 'text'}
<span class="align-middle">{run.text}</span>
{/if}
{/each}
</div>
<div class="flex-none self-end" style="transform: translateY(3px);">
<Tooltip offsetY={0} small>
<Icon
slot="activator"
class="cursor-pointer text-lg"
on:click={() => { dismissed = true; }}
>
close
</Icon>
Dismiss
</Tooltip>
</div>
</div>
{#if !shorten && !dismissed}
<div class="mt-1 inline-flex flex-row gap-2 break-words w-full overflow-visible" transition:slide|local={{ duration: 300 }}>
<MessageRun runs={poll.item.question} forceDark forceTLColor={Theme.DARK}/>
</div>
{#each poll.item.choices as choice}
<div class="mt-1 w-full whitespace-pre-line flex justify-start items-end" transition:slide|global={{ duration: 300 }}>
<MessageRun runs={choice.text} forceDark forceTLColor={Theme.DARK} />
<span class="ml-auto" transition:slide|global={{ duration: 300 }}>
{choice.percentage}
</span>
</div>
<ProgressLinear progress={(choice.ratio || 0.001) * 100} color="gray"/>
{/each}
{/if}
</div>
{/if}
46 changes: 38 additions & 8 deletions src/ts/typings/ytc.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,17 @@ declare namespace Ytc {
icon?: string;
accessibility?: AccessibilityObj;
isDisabled?: boolean;
text?: RunsObj;
text?: RunsObj; // | SimpleTextObj;
command: {
commandMetadata?: {
webCommandMetadata?: {
apiUrl?: string;
sendPost?: boolean;
}
}
liveChatActionEndpoint?: {
params: string;
}
urlEndpoint?: {
url: string;
target: string;
Expand All @@ -280,6 +289,19 @@ declare namespace Ytc {
}
}

interface EngagementMessageRenderer {
message: RunsObj[];
id: string;
timestampUsec?: IntString;
icon?: {
/** Unlocalized string */
iconType: string;
};
actionButton?: {
buttonRenderer: ButtonRenderer;
}
}

interface PlaceholderRenderer { // No idea what the purpose of this is
id: string;
timestampUsec: IntString;
Expand All @@ -292,14 +314,19 @@ declare namespace Ytc {
pollQuestion: RunsObj;
metadataText: RunsObj;
thumbnail?: Thumbnails;
liveChatPollType?: string;
};
};
choices: Array<{
text: RunsObj;
selected: boolean;
voteRatio?: number;
votePercentage?: SimpleTextObj;
}>;
choices: PollChoice[];
displayVoteResults?: boolean;
button?: ButtonRenderer;
}

interface PollChoice {
text: RunsObj;
selected: boolean;
voteRatio?: number;
votePercentage?: SimpleTextObj;
}

type Renderers = TextMessageRenderer | PaidMessageRenderer |
Expand All @@ -324,8 +351,10 @@ declare namespace Ytc {
liveChatBannerRedirectRenderer?: RedirectRenderer;
/** ??? */
liveChatPlaceholderItemRenderer?: PlaceholderRenderer;
/** Poll */
/** Poll start */
pollRenderer?: PollRenderer;
/** Poll end + other in-chat announcements TODO */
liveChatViewerEngagementMessageRenderer?: EngagementMessageRenderer;
}

interface TickerRenderer { // Doesn't have a timestamp but ID is always a paid message id
Expand Down Expand Up @@ -491,6 +520,7 @@ declare namespace Ytc {
percentage?: string;
}>;
};
// TODO add 'action' for ending poll button
}

interface ParsedRemoveBanner {
Expand Down
Loading