Refactoring misc

This commit is contained in:
WardPearce
2025-01-24 16:08:49 +13:00
parent 9f6aa292c6
commit 4121872c18
22 changed files with 282 additions and 279 deletions
+1 -1
View File
@@ -106,7 +106,7 @@ export class ElectronCapacitorApp {
defaultHeight: 800,
});
// Setup preload script path and construct our main window.
const preloadPath = join(app.getAppPath(), 'build', 'src', 'preload.js');
const preloadPath = join(app.getAppPath(), 'build', 'src', 'preload);
this.MainWindow = new BrowserWindow({
icon,
show: false,
@@ -1,9 +1,11 @@
<script lang="ts">
import { getBestThumbnail, proxyGoogleImage } from '$lib/images';
import { cleanNumber } from '$lib/time';
import { onMount } from 'svelte';
import { _ } from 'svelte-i18n';
import { get } from 'svelte/store';
import type { Channel } from '../api/model';
import { cleanNumber, getBestThumbnail, proxyGoogleImage, truncate } from '../misc';
import { truncate } from '../misc';
import { interfaceLowBandwidthMode } from '../store';
export let channel: Channel;
@@ -1,8 +1,9 @@
<script lang="ts">
import { getComments } from '$lib/api';
import { type Comment, type Comments } from '$lib/api/model';
import { getBestThumbnail, numberWithCommas, proxyGoogleImage } from '$lib/misc';
import { getBestThumbnail, proxyGoogleImage } from '$lib/images';
import { interfaceLowBandwidthMode } from '$lib/store';
import { numberWithCommas } from '$lib/time';
import { _ } from 'svelte-i18n';
export let comment: Comment;
@@ -1,7 +1,8 @@
<script lang="ts">
import { cleanNumber } from '$lib/time';
import { _ } from 'svelte-i18n';
import type { HashTag } from '../api/model';
import { cleanNumber, truncate } from '../misc';
import { truncate } from '../misc';
export let hashtag: HashTag;
@@ -2,6 +2,8 @@
import 'vidstack/bundle';
import { page } from '$app/stores';
import { getBestThumbnail, proxyGoogleImage } from '$lib/images';
import { padTime, videoLength } from '$lib/time';
import { Capacitor } from '@capacitor/core';
import { ScreenOrientation, type ScreenOrientationResult } from '@capacitor/screen-orientation';
import { StatusBar, Style } from '@capacitor/status-bar';
@@ -16,14 +18,7 @@
import type { MediaPlayerElement } from 'vidstack/elements';
import { deleteVideoProgress, getVideoProgress, saveVideoProgress } from '../api';
import type { VideoPlay } from '../api/model';
import {
getBestThumbnail,
padTime,
proxyVideoUrl,
pullBitratePreference,
videoLength,
type PhasedDescription
} from '../misc';
import { proxyVideoUrl, pullBitratePreference, type PhasedDescription } from '../misc';
import {
authStore,
instanceStore,
@@ -610,7 +605,9 @@
>
<media-provider>
{#if !audioMode}
<media-poster class="vds-poster" src={getBestThumbnail(data.video.videoThumbnails, 1251, 781)}
<media-poster
class="vds-poster"
src={proxyGoogleImage(getBestThumbnail(data.video.videoThumbnails, 1251, 781))}
></media-poster>
{/if}
</media-provider>
@@ -1,9 +1,11 @@
<script lang="ts">
import { getBestThumbnail } from '$lib/images';
import { letterCase } from '$lib/letterCasing';
import { onMount } from 'svelte';
import { _ } from 'svelte-i18n';
import { get } from 'svelte/store';
import type { Playlist } from '../api/model';
import { getBestThumbnail, letterCase, truncate } from '../misc';
import { truncate } from '../misc';
import { interfaceLowBandwidthMode } from '../store';
export let playlist: Playlist;
@@ -1,6 +1,7 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { bookmarkletSaveToUrl } from '$lib/externalSettings';
import { letterCase, titleCase, titleCases } from '$lib/letterCasing';
import { Clipboard } from '@capacitor/clipboard';
import { Capacitor } from '@capacitor/core';
import ui from 'beercss';
@@ -9,7 +10,7 @@
import { onMount } from 'svelte';
import { _ } from 'svelte-i18n';
import { get } from 'svelte/store';
import { ensureNoTrailingSlash, letterCase, titleCase, titleCases } from '../misc';
import { ensureNoTrailingSlash } from '../misc';
import { getPages } from '../navPages';
import {
authStore,
@@ -1,18 +1,13 @@
<script lang="ts">
import { getBestThumbnail, proxyGoogleImage } from '$lib/images';
import { letterCase } from '$lib/letterCasing';
import { cleanNumber, videoLength } from '$lib/time';
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
import { _ } from 'svelte-i18n';
import { get } from 'svelte/store';
import { getChannel, getDeArrow, getThumbnail, getVideo, getVideoProgress } from '../api';
import type { Notification, PlaylistPageVideo, Video, VideoBase, VideoPlay } from '../api/model';
import {
cleanNumber,
getBestThumbnail,
letterCase,
proxyGoogleImage,
proxyVideoUrl,
truncate,
videoLength
} from '../misc';
import { proxyVideoUrl, truncate } from '../misc';
import type { PlayerEvents } from '../player';
import {
authStore,
@@ -1,4 +1,5 @@
<script lang="ts">
import { videoLength } from '$lib/time';
import Fuse from 'fuse.js';
import { VTTCue, parseText, type ParsedCaptionsResult } from 'media-captions';
import { _ } from 'svelte-i18n';
@@ -6,7 +7,7 @@
import type { MediaTimeUpdateEvent } from 'vidstack';
import type { MediaPlayerElement } from 'vidstack/elements';
import type { VideoPlay } from '../api/model';
import { decodeHtmlCharCodes, videoLength } from '../misc';
import { decodeHtmlCharCodes } from '../misc';
import { instanceStore } from '../store';
export let video: VideoPlay;
+41
View File
@@ -0,0 +1,41 @@
import { get } from "svelte/store";
import type { Image } from "./api/model";
import { instanceStore } from "./store";
export function getBestThumbnail(
images: Image[] | null,
maxWidthDimension: number = 480,
maxHeightDimension = 360
): string {
if (images && images.length > 0) {
const imagesFiltered = images.filter(
(image) => image.width < maxWidthDimension && image.height < maxHeightDimension
);
if (imagesFiltered.length === 0) {
return images[0].url;
}
imagesFiltered.sort((a, b) => {
return b.width * b.height - a.width * a.height;
});
return imagesFiltered[0].url;
} else {
return '';
}
}
export function proxyGoogleImage(source: string): string {
if (source.startsWith('//')) source = `https:${source}`;
let path: string | undefined;
try {
path = new URL(source).pathname;
} catch { }
if (typeof path === 'undefined') return '';
return `${get(instanceStore)}/ggpht${path}`;
}
+61
View File
@@ -0,0 +1,61 @@
import { get } from "svelte/store";
import { interfaceForceCase } from "./store";
export type TitleCase = 'uppercase' | 'lowercase' | 'sentence case' | 'title case' | null;
export const titleCases: TitleCase[] = [
'lowercase',
'uppercase',
'title case',
'sentence case'
];
export function letterCase(text: string, caseTypeOverwrite?: TitleCase): string {
const casing = caseTypeOverwrite ? caseTypeOverwrite : get(interfaceForceCase);
if (!casing) return text;
switch (casing) {
case 'lowercase':
return text.toLowerCase();
case 'uppercase':
return text.toUpperCase();
case 'sentence case':
return sentenceCase(text);
default:
return titleCase(text);
}
}
export function sentenceCase(text: string): string {
let sentences: string[] = text.match(/[^.!?]*[.!?]*/g) || [];
let casedSentences: string[] = sentences.map(sentence => {
sentence = sentence.trim();
if (sentence.length === 0) return '';
return sentence.charAt(0).toUpperCase() + sentence.slice(1).toLowerCase();
});
return casedSentences.join(' ');
}
export function titleCase(text: string): string {
if (!text) return '';
let words: string[] = text.split(/(\s+)/).filter(word => word.trim().length > 0);
let titleCasedWords: string[] = words.map(word => {
if (word.length === 0) return '';
const firstChar = word.charAt(0);
const restOfWord = word.slice(1).toLowerCase();
// Capitalize if the first character is a letter
if (/[a-zA-Z]/.test(firstChar)) {
return firstChar.toUpperCase() + restOfWord;
}
return word;
});
return titleCasedWords.join(' ');
}
+1 -233
View File
@@ -1,220 +1,24 @@
import { pushState } from '$app/navigation';
import { page } from '$app/stores';
import he from 'he';
import humanNumber from 'human-number';
import type Peer from 'peerjs';
import { get } from 'svelte/store';
import type { Image } from './api/model';
import { instanceStore, interfaceForceCase } from './store';
export type TitleCase = 'uppercase' | 'lowercase' | 'sentence case' | 'title case' | null;
export const titleCases: TitleCase[] = [
'lowercase',
'uppercase',
'title case',
'sentence case'
];
export function letterCase(text: string, caseTypeOverwrite?: TitleCase): string {
const casing = caseTypeOverwrite ? caseTypeOverwrite : get(interfaceForceCase);
if (!casing) return text;
switch (casing) {
case 'lowercase':
return text.toLowerCase();
case 'uppercase':
return text.toUpperCase();
case 'sentence case':
return sentenceCase(text);
default:
return titleCase(text);
}
}
export function sentenceCase(text: string): string {
let sentences: string[] = text.match(/[^.!?]*[.!?]*/g) || [];
let casedSentences: string[] = sentences.map(sentence => {
sentence = sentence.trim();
if (sentence.length === 0) return '';
return sentence.charAt(0).toUpperCase() + sentence.slice(1).toLowerCase();
});
return casedSentences.join(' ');
}
export function titleCase(text: string): string {
if (!text) return '';
let words: string[] = text.split(/(\s+)/).filter(word => word.trim().length > 0);
let titleCasedWords: string[] = words.map(word => {
if (word.length === 0) return '';
const firstChar = word.charAt(0);
const restOfWord = word.slice(1).toLowerCase();
// Capitalize if the first character is a letter
if (/[a-zA-Z]/.test(firstChar)) {
return firstChar.toUpperCase() + restOfWord;
}
return word;
});
return titleCasedWords.join(' ');
}
import { instanceStore } from './store';
export function truncate(value: string, maxLength: number = 50): string {
return value.length > maxLength ? `${value.substring(0, maxLength)}...` : value;
}
export function numberWithCommas(number: number) {
if (typeof number === 'undefined') return;
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
export function cleanNumber(number: number): string {
return humanNumber(number, (number: number) =>
Number.parseFloat(number.toString()).toFixed(1)
).replace('.0', '');
}
export function videoLength(lengthSeconds: number): string {
const hours = Math.floor(lengthSeconds / 3600);
let minutes: number | string = Math.floor((lengthSeconds % 3600) / 60);
let seconds: number | string = Math.round(lengthSeconds % 60);
if (minutes < 10) {
minutes = `0${minutes}`;
}
if (seconds < 10) {
seconds = `0${seconds}`;
}
if (hours !== 0) {
return `${hours}:${minutes}:${seconds}`;
} else {
return `${minutes}:${seconds}`;
}
}
export interface PhasedDescription {
description: string;
timestamps: { title: string; time: number; timePretty: string; }[];
}
export function padTime(time: string): string {
let timeParts = time.split(':');
if (timeParts.length < 3) {
timeParts = ('00:' + time).split(':');
}
const hours = (timeParts[0] || '0').padStart(2, '0');
const minutes = (timeParts[1] || '0').padStart(2, '0');
let seconds = timeParts[2] || '0';
let milliseconds = '';
if (seconds.includes('.')) {
const [sec, ms] = seconds.split('.');
seconds = sec.padStart(2, '0');
milliseconds = `.${ms.padStart(3, '0')}`;
} else {
seconds = seconds.padStart(2, '0');
}
return `${hours}:${minutes}:${seconds}${milliseconds}`;
}
export function decodeHtmlCharCodes(str: string): string {
const { decode } = he;
return decode(str);
}
export function phaseDescription(content: string, usingYoutubeJs: boolean = false): PhasedDescription {
const timestamps: { title: string; time: number; timePretty: string; }[] = [];
const lines = content.split('\n');
// Regular expressions for different timestamp formats
const urlRegex = /<a href="([^"]+)"/;
const timestampRegexInvidious = /<a href="([^"]+)" data-onclick="jump_to_time" data-jump-time="(\d+)">(\d+:\d+(?::\d+)?)<\/a>\s*(.+)/;
const timestampRegexYtJs = /&(?:\S*?&)?t=(\d+)\s*s.*?<span[^>]*>([^<]*)<\/span>.*?>(.*?)<\/span>/;
let filteredLines: string[] = [];
lines.forEach((line) => {
const urlMatch = urlRegex.exec(line);
// Use appropriate regex based on the `usingYoutubeJs` flag
const timestampMatch = (usingYoutubeJs ? timestampRegexYtJs : timestampRegexInvidious).exec(usingYoutubeJs ? line + '</span>' : line);
if (urlMatch !== null && timestampMatch === null) {
// If line contains a URL but not a timestamp, modify the URL
const modifiedLine = line.replace(
/<a href="([^"]+)"/,
'<a href="$1" target="_blank" rel="noopener noreferrer" class="link"'
);
filteredLines.push(modifiedLine);
} else if (timestampMatch !== null) {
// If line contains a timestamp, extract details and push into timestamps array
const time = usingYoutubeJs ? timestampMatch[1] : timestampMatch[2];
const timestamp = usingYoutubeJs ? timestampMatch[2] : timestampMatch[3];
const title = usingYoutubeJs ? timestampMatch[3] || '' : timestampMatch[4] || '';
timestamps.push({
time: convertToSeconds(time),
// Remove any HTML in the timestamp title.
title: decodeHtmlCharCodes(title.replace(/<[^>]+>/g, '').replace(/\n/g, '').trim()),
timePretty: timestamp
});
} else {
filteredLines.push(line);
}
});
const filteredContent = filteredLines.join('\n');
return { description: filteredContent, timestamps: timestamps };
}
function convertToSeconds(time: string): number {
const parts = time.split(':').map((part) => parseInt(part));
let seconds = 0;
if (parts.length === 3) {
// hh:mm:ss
seconds = parts[0] * 3600 + parts[1] * 60 + parts[2];
} else if (parts.length === 2) {
// hh:ss or m:ss
seconds = parts[0] * 60 + parts[1];
} else if (parts.length === 1) {
// s
seconds = parts[0];
}
return seconds;
}
export function humanizeSeconds(totalSeconds: number): string {
const secondsInMinute = 60;
const secondsInHour = 3600;
const hours = Math.floor(totalSeconds / secondsInHour);
const minutes = Math.floor((totalSeconds % secondsInHour) / secondsInMinute);
const parts: string[] = [];
if (hours > 0) {
parts.push(`${hours} hour${hours > 1 ? 's' : ''}`);
}
if (minutes > 0) {
parts.push(`${minutes} minute${minutes > 1 ? 's' : ''}`);
}
return parts.join(', ');
}
export function proxyVideoUrl(source: string): string {
const rawSrc = new URL(source);
rawSrc.host = get(instanceStore).replace('http://', '').replace(
@@ -238,18 +42,6 @@ export function pullBitratePreference(): number {
return -1;
}
export function proxyGoogleImage(source: string): string {
if (source.startsWith('//')) source = `https:${source}`;
let path: string | undefined;
try {
path = new URL(source).pathname;
} catch { }
if (typeof path === 'undefined') return '';
return `${get(instanceStore)}/ggpht${path}`;
}
export function unsafeRandomItem(array: any[]): any {
return array[Math.floor(Math.random() * array.length)];
@@ -283,30 +75,6 @@ export async function peerJs(peerId: string): Promise<Peer> {
);
}
export function getBestThumbnail(
images: Image[] | null,
maxWidthDimension: number = 480,
maxHeightDimension = 360
): string {
if (images && images.length > 0) {
const imagesFiltered = images.filter(
(image) => image.width < maxWidthDimension && image.height < maxHeightDimension
);
if (imagesFiltered.length === 0) {
return images[0].url;
}
imagesFiltered.sort((a, b) => {
return b.width * b.height - a.width * a.height;
});
return imagesFiltered[0].url;
} else {
return '';
}
}
export function ensureNoTrailingSlash(url: any): string {
if (typeof url !== 'string') return '';
+1 -1
View File
@@ -1,6 +1,6 @@
import type { AdaptiveFormats, Captions, Image, StoryBoard, Thumbnail, VideoBase, VideoPlay } from '$lib/api/model';
import { numberWithCommas } from '$lib/misc';
import { interfaceRegionStore, poTokenCacheStore } from '$lib/store';
import { numberWithCommas } from '$lib/time';
import { Capacitor } from '@capacitor/core';
import { BG } from 'bgutils-js';
import { get } from 'svelte/store';
+1 -1
View File
@@ -4,7 +4,7 @@ import type Peer from 'peerjs';
import type { DataConnection } from 'peerjs';
import { persisted } from 'svelte-persisted-store';
import { writable, type Writable } from 'svelte/store';
import type { TitleCase } from './misc';
import type { TitleCase } from './letterCasing';
import type { PoTokens } from './patches/youtubejs';
function platformDependentDefault(givenValue: any, defaultValue: any): any {
+92
View File
@@ -0,0 +1,92 @@
import humanNumber from 'human-number';
export function numberWithCommas(number: number) {
if (typeof number === 'undefined') return;
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
export function cleanNumber(number: number): string {
return humanNumber(number, (number: number) =>
Number.parseFloat(number.toString()).toFixed(1)
).replace('.0', '');
}
export function videoLength(lengthSeconds: number): string {
const hours = Math.floor(lengthSeconds / 3600);
let minutes: number | string = Math.floor((lengthSeconds % 3600) / 60);
let seconds: number | string = Math.round(lengthSeconds % 60);
if (minutes < 10) {
minutes = `0${minutes}`;
}
if (seconds < 10) {
seconds = `0${seconds}`;
}
if (hours !== 0) {
return `${hours}:${minutes}:${seconds}`;
} else {
return `${minutes}:${seconds}`;
}
}
export function padTime(time: string): string {
let timeParts = time.split(':');
if (timeParts.length < 3) {
timeParts = ('00:' + time).split(':');
}
const hours = (timeParts[0] || '0').padStart(2, '0');
const minutes = (timeParts[1] || '0').padStart(2, '0');
let seconds = timeParts[2] || '0';
let milliseconds = '';
if (seconds.includes('.')) {
const [sec, ms] = seconds.split('.');
seconds = sec.padStart(2, '0');
milliseconds = `.${ms.padStart(3, '0')}`;
} else {
seconds = seconds.padStart(2, '0');
}
return `${hours}:${minutes}:${seconds}${milliseconds}`;
}
export function convertToSeconds(time: string): number {
const parts = time.split(':').map((part) => parseInt(part));
let seconds = 0;
if (parts.length === 3) {
// hh:mm:ss
seconds = parts[0] * 3600 + parts[1] * 60 + parts[2];
} else if (parts.length === 2) {
// hh:ss or m:ss
seconds = parts[0] * 60 + parts[1];
} else if (parts.length === 1) {
// s
seconds = parts[0];
}
return seconds;
}
export function humanizeSeconds(totalSeconds: number): string {
const secondsInMinute = 60;
const secondsInHour = 3600;
const hours = Math.floor(totalSeconds / secondsInHour);
const minutes = Math.floor((totalSeconds % secondsInHour) / secondsInMinute);
const parts: string[] = [];
if (hours > 0) {
parts.push(`${hours} hour${hours > 1 ? 's' : ''}`);
}
if (minutes > 0) {
parts.push(`${minutes} minute${minutes > 1 ? 's' : ''}`);
}
return parts.join(', ');
}
+45
View File
@@ -0,0 +1,45 @@
import { type PhasedDescription, decodeHtmlCharCodes } from "./misc";
import { convertToSeconds } from "./time";
export function phaseDescription(content: string, usingYoutubeJs: boolean = false): PhasedDescription {
const timestamps: { title: string; time: number; timePretty: string; }[] = [];
const lines = content.split('\n');
// Regular expressions for different timestamp formats
const urlRegex = /<a href="([^"]+)"/;
const timestampRegexInvidious = /<a href="([^"]+)" data-onclick="jump_to_time" data-jump-time="(\d+)">(\d+:\d+(?::\d+)?)<\/a>\s*(.+)/;
const timestampRegexYtJs = /&(?:\S*?&)?t=(\d+)\s*s.*?<span[^>]*>([^<]*)<\/span>.*?>(.*?)<\/span>/;
let filteredLines: string[] = [];
lines.forEach((line) => {
const urlMatch = urlRegex.exec(line);
// Use appropriate regex based on the `usingYoutubeJs` flag
const timestampMatch = (usingYoutubeJs ? timestampRegexYtJs : timestampRegexInvidious).exec(usingYoutubeJs ? line + '</span>' : line);
if (urlMatch !== null && timestampMatch === null) {
// If line contains a URL but not a timestamp, modify the URL
const modifiedLine = line.replace(
/<a href="([^"]+)"/,
'<a href="$1" target="_blank" rel="noopener noreferrer" class="link"'
);
filteredLines.push(modifiedLine);
} else if (timestampMatch !== null) {
// If line contains a timestamp, extract details and push into timestamps array
const time = usingYoutubeJs ? timestampMatch[1] : timestampMatch[2];
const timestamp = usingYoutubeJs ? timestampMatch[2] : timestampMatch[3];
const title = usingYoutubeJs ? timestampMatch[3] || '' : timestampMatch[4] || '';
timestamps.push({
time: convertToSeconds(time),
// Remove any HTML in the timestamp title.
title: decodeHtmlCharCodes(title.replace(/<[^>]+>/g, '').replace(/\n/g, '').trim()),
timePretty: timestamp
});
} else {
filteredLines.push(line);
}
});
const filteredContent = filteredLines.join('\n');
return { description: filteredContent, timestamps: timestamps };
}
@@ -5,8 +5,9 @@
import PageLoading from '$lib/components/PageLoading.svelte';
import PlaylistThumbnail from '$lib/components/PlaylistThumbnail.svelte';
import VideoList from '$lib/components/VideoList.svelte';
import { cleanNumber, getBestThumbnail, proxyGoogleImage } from '$lib/misc';
import { getBestThumbnail, proxyGoogleImage } from '$lib/images';
import { activePageStore, authStore, interfaceLowBandwidthMode } from '$lib/store';
import { cleanNumber } from '$lib/time';
import { Clipboard } from '@capacitor/clipboard';
import { Capacitor } from '@capacitor/core';
import { onMount } from 'svelte';
@@ -1,7 +1,7 @@
<script lang="ts">
import { getHashtag } from '$lib/api/index.js';
import { getHashtag } from '$lib/api';
import VideoList from '$lib/components/VideoList.svelte';
import { activePageStore } from '$lib/store.js';
import { activePageStore } from '$lib/store';
import InfiniteLoading, { type InfiniteEvent } from 'svelte-infinite-loading';
export let data;
@@ -1,9 +1,10 @@
<script lang="ts">
import { getPlaylist } from '$lib/api/index';
import type { PlaylistPageVideo } from '$lib/api/model.js';
import type { PlaylistPageVideo } from '$lib/api/model';
import VideoList from '$lib/components/VideoList.svelte';
import { cleanNumber, unsafeRandomItem } from '$lib/misc.js';
import { unsafeRandomItem } from '$lib/misc';
import { activePageStore, playlistSettingsStore } from '$lib/store';
import { cleanNumber } from '$lib/time';
import { Clipboard } from '@capacitor/clipboard';
import { Capacitor } from '@capacitor/core';
import { onMount } from 'svelte';
@@ -9,22 +9,15 @@
postSubscribe,
removePlaylistVideo
} from '$lib/api/index';
import type { Comments, PlaylistPage, PlaylistPageVideo } from '$lib/api/model.js';
import type { Comments, PlaylistPage, PlaylistPageVideo } from '$lib/api/model';
import Comment from '$lib/components/Comment.svelte';
import Player from '$lib/components/Player.svelte';
import ShareVideo from '$lib/components/ShareVideo.svelte';
import Thumbnail from '$lib/components/Thumbnail.svelte';
import Transcript from '$lib/components/Transcript.svelte';
import {
cleanNumber,
getBestThumbnail,
humanizeSeconds,
letterCase,
numberWithCommas,
proxyGoogleImage,
truncate,
unsafeRandomItem
} from '$lib/misc';
import { getBestThumbnail, proxyGoogleImage } from '$lib/images';
import { letterCase } from '$lib/letterCasing';
import { truncate, unsafeRandomItem } from '$lib/misc';
import type { PlayerEvents } from '$lib/player';
import {
activePageStore,
@@ -41,6 +34,7 @@
syncPartyConnectionsStore,
syncPartyPeerStore
} from '$lib/store';
import { cleanNumber, humanizeSeconds, numberWithCommas } from '$lib/time';
import ui from 'beercss';
import type { DataConnection } from 'peerjs';
import { type Segment } from 'sponsorblock-api';
@@ -6,13 +6,13 @@ import {
getVideo,
postHistory
} from '$lib/api/index';
import { phaseDescription } from '$lib/misc';
import {
authStore,
playerProxyVideosStore,
returnYTDislikesInstanceStore,
returnYtDislikesStore
} from '$lib/store';
import { phaseDescription } from '$lib/timestamps';
import { error } from '@sveltejs/kit';
import { get } from 'svelte/store';
@@ -1,8 +1,8 @@
import { getVideo } from '$lib/api/index';
import type { PhasedDescription } from '$lib/misc';
import { playerProxyVideosStore } from '$lib/store';
import { error } from '@sveltejs/kit';
import { get } from 'svelte/store';
import type { PhasedDescription } from '../../../../lib/misc.js';
import { playerProxyVideosStore } from '../../../../lib/store.js';
export async function load({ params }) {
let video;