Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for persistent superchats #79

Merged
merged 47 commits into from
Jun 17, 2022
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
2a69054
rework css for pinned message to allow sticky
KentoNishi May 24, 2022
90b978d
add a sticky bar with chips
KentoNishi May 27, 2022
3260a63
parse ticker actions instead
KentoNishi May 29, 2022
fa4a341
progress so far
KentoNishi May 30, 2022
547b5bf
timed superchat appearance
KentoNishi May 30, 2022
bfcce23
superchat ticking progress
KentoNishi May 30, 2022
b1bce68
expand tickers
KentoNishi May 30, 2022
419622e
changes to types
KentoNishi May 30, 2022
5ecec4c
disallow duplicates
KentoNishi May 30, 2022
9db9f09
removed some extra css
KentoNishi May 30, 2022
703a4bf
fix: use smelte dark instead of location dark for stickybar
r2dev2 May 31, 2022
c2b5799
start showing supas immediately on load
KentoNishi Jun 2, 2022
1e8fc0e
Merge branch 'sticky-superchats' of github.com:LiveTL/HyperChat into …
KentoNishi Jun 2, 2022
add3ef2
use fullDurationSec
KentoNishi Jun 2, 2022
1fb0181
add touch and horizontal scrollwheel support
KentoNishi Jun 2, 2022
43dc929
clearer close button
KentoNishi Jun 2, 2022
ea164e3
only update stickySuperchats if items in discard
KentoNishi Jun 2, 2022
7fa40f3
Merge branch 'master' into sticky-superchats
KentoNishi Jun 4, 2022
8a2a0f0
fix small forceTLColor bug
KentoNishi Jun 7, 2022
7f4ee1b
fix disappearing bar
KentoNishi Jun 8, 2022
45503b7
safeguard possibly null runs prop
KentoNishi Jun 8, 2022
a1d6d62
remove messageId prop
KentoNishi Jun 8, 2022
3834d2d
truncate long names
KentoNishi Jun 8, 2022
9757f74
enableStickySuperchatBar setting
KentoNishi Jun 10, 2022
fc8c551
WIP liveChatTickerSponsorItemRenderer
KentoNishi Jun 10, 2022
a2d8b90
parse membership items in parseTickerAction
KentoNishi Jun 10, 2022
e8c1b35
partially implemented sticky bar for members
KentoNishi Jun 11, 2022
f354919
display member items on the bar
KentoNishi Jun 11, 2022
e870f44
expand membership chips on click
KentoNishi Jun 11, 2022
95137bd
thin scrollbars everywhere
KentoNishi Jun 11, 2022
7297a34
5-second leniency for sticky bar
KentoNishi Jun 11, 2022
7b6a084
pixel-perfect vertical centering cuz im a nerd
KentoNishi Jun 11, 2022
adad8e8
evenly padded superchat dialog
KentoNishi Jun 11, 2022
e0759c2
fixed some padding
KentoNishi Jun 11, 2022
1d863b1
fixed bugs + overlapped absolute items
KentoNishi Jun 14, 2022
7759590
workaround for ff
KentoNishi Jun 14, 2022
b6d7852
run superchat bar on 500ms tick
KentoNishi Jun 14, 2022
45efba1
some visual stuff
KentoNishi Jun 14, 2022
1d4a4fc
take ur uncustomizable scrollbar, ff users
KentoNishi Jun 14, 2022
9780564
w-full
KentoNishi Jun 14, 2022
5687d21
remove transition
KentoNishi Jun 14, 2022
77ff141
open, dispatch('resize')
KentoNishi Jun 15, 2022
cd9d63d
changelog
KentoNishi Jun 15, 2022
bae8f36
slightly shorter changelog
KentoNishi Jun 15, 2022
7048db6
forgot to SET stickySuperchats
KentoNishi Jun 15, 2022
ba7bc8d
Un-absolute sticky bar
ChrRubin Jun 16, 2022
ddfeacf
update changelog
KentoNishi Jun 17, 2022
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
72 changes: 50 additions & 22 deletions src/components/Hyperchat.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import PaidMessage from './PaidMessage.svelte';
import MembershipItem from './MembershipItem.svelte';
import ReportBanDialog from './ReportBanDialog.svelte';
import SuperchatViewDialog from './SuperchatViewDialog.svelte';
import StickyBar from './StickyBar.svelte';
import {
paramsTabId,
paramsFrameId,
Expand All @@ -31,7 +33,9 @@
hoveredItem,
port,
selfChannelId,
alertDialog
alertDialog,
stickySuperchats,
currentProgress
} from '../ts/storage';

const welcome = { welcome: true, message: { messageId: 'welcome' } };
Expand Down Expand Up @@ -91,11 +95,11 @@
if (!isAtBottom) return;
// On replays' initial data, only show messages with negative timestamp
if (isInitial && isReplay) {
messageActions.push(...messagesAction.messages.filter(
messageActions.push(...filterTickers(messagesAction.messages).filter(
(a) => a.message.timestamp.startsWith('-') && shouldShowMessage(a)
));
} else {
messageActions.push(...messagesAction.messages.filter(shouldShowMessage));
messageActions.push(...filterTickers(messagesAction.messages).filter(shouldShowMessage));
}
if (!isInitial) checkTruncateMessages();
};
Expand All @@ -109,6 +113,23 @@
});
};

const filterTickers = (items: Chat.MessageAction[]): Chat.MessageAction[] => {
const keep: Chat.MessageAction[] = [];
const discard: Ytc.ParsedTicker[] = [];
items.forEach(item => {
if (
'tickerDuration' in item.message &&
!$stickySuperchats.some(sc => sc.messageId === item.message.messageId)
) discard.push(item.message);
else keep.push(item);
});
$stickySuperchats = [
...discard,
...$stickySuperchats
];
return keep;
};

const onDelete = (deletion: Ytc.ParsedDeleted) => {
messageActions.some((action) => {
if (isWelcome(action)) return false;
Expand Down Expand Up @@ -137,6 +158,9 @@
case 'unpin':
pinned = null;
break;
case 'playerProgress':
$currentProgress = action.playerProgress;
break;
case 'forceUpdate':
messageActions = [...action.messages].filter(shouldShowMessage);
if (action.showWelcome) {
Expand Down Expand Up @@ -251,7 +275,6 @@
);

const containerClass = 'h-screen w-screen text-black dark:text-white dark:bg-black dark:bg-opacity-25';
const pinnedClass = 'absolute top-2 inset-x-2';

const isSuperchat = (action: Chat.MessageAction) => (action.message.superChat || action.message.superSticker);
const isMembership = (action: Chat.MessageAction) => (action.message.membership);
Expand All @@ -265,6 +288,7 @@
</script>

<ReportBanDialog />
<SuperchatViewDialog />

<svelte:window on:resize={scrollToBottom} on:load={onLoad} />

Expand Down Expand Up @@ -296,11 +320,14 @@
{/each}
</div>
</div>
{#if pinned}
<div class={pinnedClass}>
<PinnedMessage pinned={pinned} />
</div>
{/if}
<div class="absolute top-0 w-full">
<StickyBar />
{#if pinned}
<div class="mx-2 mt-2">
<PinnedMessage pinned={pinned} />
</div>
{/if}
</div>
{#if !isAtBottom}
<div
class="absolute left-1/2 transform -translate-x-1/2 bottom-0 pb-1"
Expand All @@ -312,27 +339,28 @@
</div>

<style>
.content {
scrollbar-width: thin;
scrollbar-color: #888 transparent;
.hover-highlight {
/* transition: 0.1s; */
background-color: transparent;
}
.content::-webkit-scrollbar {
.hover-highlight:hover {
background-color: #80808040;
}
* :global(::-webkit-scrollbar) {
width: 4px;
height: 4px;
}
.content::-webkit-scrollbar-track {
* :global(::-webkit-scrollbar-track) {
background: transparent;
}
.content::-webkit-scrollbar-thumb {
* :global(::-webkit-scrollbar-thumb) {
background: #888;
}
.content::-webkit-scrollbar-thumb:hover {
* :global(::-webkit-scrollbar-thumb:hover) {
background: #555;
}
.hover-highlight {
/* transition: 0.1s; */
background-color: transparent;
}
.hover-highlight:hover {
background-color: #80808040;
* {
scrollbar-width: thin;
scrollbar-color: #888 transparent;
}
</style>
2 changes: 1 addition & 1 deletion src/components/Message.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

export let message: Ytc.ParsedMessage;
export let deleted: Chat.MessageDeletedObj | null = null;
export let messageId: Chat.MessageAction['message']['messageId'];
export let messageId: Chat.MessageAction['message']['messageId'] = '';
export let forceDark = false;
export let hideName = false;

Expand Down
25 changes: 20 additions & 5 deletions src/components/PaidMessage.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
import Message from './Message.svelte';
import isDarkColor from 'is-dark-color';
import { Theme } from '../ts/chat-constants';
import { focusedSuperchat } from '../ts/storage';

export let message: Ytc.ParsedMessage;
export let message: Ytc.ParsedTimedItem;
export let chip = false;
export let fillPortion = 1;

let headerStyle = '';

Expand All @@ -20,16 +23,28 @@
headerStyle = '';
}

const classes = 'inline-flex flex-col rounded break-words overflow-hidden w-full';
const classes = `inline-flex flex-col rounded overflow-hidden ${chip ? 'w-fit whitespace-pre' : 'w-full break-words'}`;

$: if (!paid) {
console.error('Not a paid message', { message });
}
</script>

{#if paid}
<div class={classes} style={backgroundColor + textColor}>
<div class="p-2" style={headerStyle}>
<div class={classes} style={(chip ? '' : backgroundColor) + textColor}>
<div
class="relative overflow-hidden p-2 {chip ? 'rounded-full cursor-pointer' : ''}"
style={headerStyle}
on:click={() => {
if (chip) $focusedSuperchat = message;
}}
>
{#if chip}
<div class="absolute top-0 right-0 h-full" style="
background-color: rgba(0, 0, 0, 0.1);
width: {Math.round(fillPortion * 100)}%;
" />
{/if}
<span class="mr-1 underline font-bold">{amount}</span>
<span class="font-bold tracking-wide" style={nameColor}>
{message.author.name}
Expand All @@ -41,7 +56,7 @@
alt={message.superSticker.alt} />
{/if}
</div>
{#if message.message.length > 0}
{#if !chip && message.message.length > 0}
<div class="p-2">
<Message message={message} hideName forceTLColor={
isDarkColor(`#${message.superChat?.headerTextColor}`) ? Theme.LIGHT : Theme.DARK
Expand Down
14 changes: 4 additions & 10 deletions src/components/Settings.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,11 @@
</div>

<style>
:global(::-webkit-scrollbar) {
width: 4px;
height: 4px;
}
:global(::-webkit-scrollbar-track) {
* :global(::-webkit-scrollbar-track) {
background: transparent;
}
:global(::-webkit-scrollbar-thumb) {
background: #888;
}
:global(::-webkit-scrollbar-thumb:hover) {
background: #555;
* {
scrollbar-width: thin;
scrollbar-color: #888 transparent;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

webkit-scrollbar-track probably isn't needed if thin scrollbars are removed. Also if we're removing thin scrollbars here on Chrome, we should also remove it on Firefox.

}
</style>
35 changes: 35 additions & 0 deletions src/components/StickyBar.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<script lang="ts">
import { isDark, stickySuperchats, currentProgress } from '../ts/storage';
import PaidMessage from './PaidMessage.svelte';
let scrollableElem: HTMLDivElement;
$: if (scrollableElem) {
scrollableElem.addEventListener('wheel', (e) => {
e.preventDefault();
e.stopPropagation();
scrollableElem.scrollBy(e.deltaY, 0);
});
}
$: $stickySuperchats = $stickySuperchats.filter(sc => {
return (sc.showtime / 1000 <= $currentProgress) && (sc.showtime / 1000 + sc.tickerDuration) >= $currentProgress;
});
</script>

{#if $stickySuperchats.length}
<div class="w-full overflow-y-hidden" style="overflow-x: overlay;" bind:this={scrollableElem}>
<div
class="flex items-center"
style="
height: calc(2.5rem + 4px);
width: fit-content;
min-width: 100%;
background-color: #{$isDark ? '202020' : 'ffffff'}
"
>
{#each $stickySuperchats as sc}
<span class="mx-0.5">
<PaidMessage message={sc} chip fillPortion={($currentProgress - sc.showtime / 1000) / sc.tickerDuration} />
</span>
{/each}
</div>
</div>
{/if}
18 changes: 18 additions & 0 deletions src/components/SuperchatViewDialog.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<script lang="ts">
import {
focusedSuperchat
} from '../ts/storage';
import Dialog from './common/Dialog.svelte';
import PaidMessage from './PaidMessage.svelte';
import Button from 'smelte/src/components/Button';
$: sc = $focusedSuperchat as Ytc.ParsedTimedItem;
</script>

<Dialog active={Boolean(sc)} noCloseButton>
<PaidMessage message={sc} />
<div slot="actions">
<Button on:click={() => {
$focusedSuperchat = null;
}} color="primary">Close</Button>
</div>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The close button feels unnecessary.

</Dialog>
20 changes: 4 additions & 16 deletions src/components/settings/InterfaceSettings.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
showUserBadges,
emojiRenderMode,
autoLiveChat,
useSystemEmojis
useSystemEmojis,
isDark
} from '../../ts/storage';
import { Theme, themeItems, emojiRenderItems } from '../../ts/chat-constants';
import { themeItems, emojiRenderItems } from '../../ts/chat-constants';
import Card from '../common/Card.svelte';
import Radio from '../common/RadioGroupStore.svelte';
import Checkbox from '../common/CheckboxStore.svelte';
Expand All @@ -23,20 +24,7 @@
);

const darkStore = dark();
$: switch ($theme) {
case Theme.DARK:
darkStore.set(true);
break;
case Theme.LIGHT:
darkStore.set(false);
break;
case Theme.YOUTUBE:
if (window.location.search.includes('dark')) darkStore.set(true);
else darkStore.set(false);
break;
default:
break;
}
$: darkStore.set($isDark);

$: console.debug({
theme: $theme,
Expand Down
30 changes: 26 additions & 4 deletions src/ts/chat-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,13 +181,35 @@ const parsePinnedMessageAction = (action: Ytc.AddPinnedAction): Ytc.ParsedPinned
};
};

const processCommonAction = (action: Ytc.ReplayAction, isReplay: boolean, liveTimeoutOrReplayMs: number): Ytc.ParsedMessage | Ytc.ParsedMisc | undefined => {
const parseTickerAction = (action: Ytc.AddTickerAction, isReplay: boolean, liveTimeoutOrReplayMs: number): Ytc.ParsedTicker | undefined => {
const baseRenderer = action.item.liveChatTickerPaidMessageItemRenderer;
if (!baseRenderer) return;
const parsedMessage = parseAddChatItemAction({
item: {
liveChatPaidMessageRenderer: baseRenderer.showItemEndpoint.showLiveChatItemEndpoint.renderer.liveChatPaidMessageRenderer
}
}, isReplay, liveTimeoutOrReplayMs);
if (!parsedMessage) return;
return {
type: 'ticker',
...parsedMessage,
tickerDuration: baseRenderer.durationSec
};
};

const processCommonAction = (
action: Ytc.ReplayAction,
isReplay: boolean,
liveTimeoutOrReplayMs: number
): Ytc.ParsedTimedItem | Ytc.ParsedMisc | undefined => {
if (action.addChatItemAction) {
return parseAddChatItemAction(action.addChatItemAction, isReplay, liveTimeoutOrReplayMs);
} else if (action.addBannerToLiveChatCommand) {
return parsePinnedMessageAction(action.addBannerToLiveChatCommand);
} else if (action.removeBannerForLiveChatCommand) {
return { type: 'unpin' } as const;
} else if (action.addLiveChatTickerItemAction) {
return parseTickerAction(action.addLiveChatTickerItemAction, isReplay, liveTimeoutOrReplayMs);
}
};

Expand All @@ -202,8 +224,8 @@ const processLiveAction = (action: Ytc.Action, isReplay: boolean, liveTimeoutMs:
}
};

const sortAction = (action: Ytc.ParsedAction, messageArray: Ytc.ParsedMessage[], bonkArray: Ytc.ParsedBonk[], deleteArray: Ytc.ParsedDeleted[], miscArray: Ytc.ParsedMisc[]): void => {
if ('message' in action) {
const sortAction = (action: Ytc.ParsedAction, messageArray: Ytc.ParsedTimedItem[], bonkArray: Ytc.ParsedBonk[], deleteArray: Ytc.ParsedDeleted[], miscArray: Ytc.ParsedMisc[]): void => {
if ('message' in action || 'tickerDuration' in action) {
messageArray.push(action);
} else if ('replacedMessage' in action && 'authorId' in action) {
bonkArray.push(action);
Expand Down Expand Up @@ -233,7 +255,7 @@ export const parseChatResponse = (response: string, isReplay: boolean): Ytc.Pars
return;
}

const messageArray: Ytc.ParsedMessage[] = [];
const messageArray: Ytc.ParsedTimedItem[] = [];
const bonkArray: Ytc.ParsedBonk[] = [];
const deleteArray: Ytc.ParsedDeleted[] = [];
const miscArray: Ytc.ParsedMisc[] = [];
Expand Down
4 changes: 3 additions & 1 deletion src/ts/queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,9 @@ export function ytcQueue(isReplay = false): YtcQueue {
* Normally called by the live polling interval that runs every 250 ms.
*/
const updateLiveProgress = (): void => {
onVideoProgress(Date.now() / 1000);
const t = Date.now() / 1000;
onVideoProgress(t);
updatePlayerProgress(t);
};

/**
Expand Down
Loading