Skip to content

Commit

Permalink
Add online users support
Browse files Browse the repository at this point in the history
  • Loading branch information
ed-asriyan committed Apr 8, 2024
1 parent d10198d commit d638a69
Show file tree
Hide file tree
Showing 28 changed files with 611 additions and 315 deletions.
3 changes: 3 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ VITE_FIREBASE_MEASUREMENT_ID=
# base URL where the website is hosted (e.g. https://watchtogether.online)
VITE_URL=

# usernames separated by comma to be randomly picked for users
VITE_USERNAMES=🐵,🐒,🦍,🦧,🐶,🐕,🦮,🐕‍🦺,🐩,🐺,🦊,🦝,🐱,🐈,🐈‍⬛,🦁,🐯,🐅,🐆,🐴,🫎,🫏,🐎,🦄,🦓,🦌,🦬,🐮,🐂,🐃,🐄,🐷,🐖,🐗,🐽,🐏,🐑,🐐,🐪,🐫,🦙,🦒,🐘,🦣,🦏,🦛,🐭,🐁,🐀,🐹,🐰,🐇,🐿️,🦫,🦔,🦇,🐻,🐻‍❄️,🐨,🐼,🦥,🦦,🦨,🦘,🦡,🦃,🐔,🐓,🐣,🐤,🐥,🐦,🐧,🕊️,🦅,🦆,🦢,🦉,🦤,🪶,🦩,🦚,🦜,🪽,🐦‍⬛,🪿,🐸,🐊,🐢,🦎,🐍,🐲,🐉,🦕,🦖,🐳,🐋,🐬,🦭,🐟,🐠,🐡,🦈,🐙,🪼,🦀,🦞,🦐,🦑,🐌,🦋,🐛,🐜,🐝,🪲,🐞,🦗,🪳,🕷️,🦂,🦟,🪰,🪱,💐,🌸,🪷,🏵️,🌹,🌺,🌻,🌼,🌷,⛄,🧸,🎃,🍇,🍈,🍉,🍊,🍋,🍌,🍍,🥭,🍎,🍏,🍐,🍑,🍒,🍓,🫐,🥝,🍅,🫒

#
# [ all parameters below this line are optional ]
#
Expand Down
29 changes: 14 additions & 15 deletions src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,34 @@
import 'uikit/dist/js/uikit';
import { _ } from 'svelte-i18n';
import { randomStr } from './utils';
import Analytics from './components/analytics.svelte';
import Analytics from './analytics.svelte';
import Page from './components/index.svelte';
import { environment, isProduction } from './settings';
import './app.scss';
let header;
let roomId: string;
const getRoomId = function () {
return document.location.hash?.slice(1).toLowerCase();
const testPrefix = 'test_';
const syncHashAndRoomId = function () {
let hash = document.location.hash?.slice(1).toLowerCase() || localStorage.getItem('roomId') || randomStr(6);
if (!isProduction && !hash?.startsWith(testPrefix)) {
hash = `${testPrefix}${hash}`;
}
localStorage.setItem('roomId', hash);
roomId = hash;
document.location.hash = `#${hash}`;
};
let roomId = getRoomId();
if (!roomId) {
document.location.hash = `#${localStorage.getItem('roomId') || randomStr(6)}`;
}
$: roomId && localStorage.setItem('roomId', roomId);
const onHashChanged = function () {
roomId = getRoomId();
};
syncHashAndRoomId();
</script>

<svelte:window on:hashchange={onHashChanged}></svelte:window>
<svelte:window on:hashchange={syncHashAndRoomId}></svelte:window>

<Analytics/>

{#if !isProduction}
<span class="uk-position-absolute uk-text-warning" style:top="0" style:left="0" style:z-index="10">
<span class="uk-position-fixed uk-text-warning" style:top="0" style:left="0" style:z-index="10">
Running in non-production environment: environment="{ environment }", isProd={ isProduction }
</span>
{/if}
Expand Down
9 changes: 6 additions & 3 deletions src/components/analytics.svelte → src/analytics.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts" context="module">
import type { SourceType } from '../normalize-link';
import { analytics, isProduction } from '../settings';
import type { SourceType } from './normalize-link';
import { analytics, isProduction } from './settings';
const trackRaw = function (...args: any[]) {
if (isProduction) {
Expand All @@ -26,6 +26,10 @@
}
};
export const getUserId = function (): string {
return globalThis.gaGlobal?.vid.replace('.', '-');
};
export class ClickEvent extends Event<{
target: string;
}> {
Expand All @@ -36,7 +40,6 @@
roomId: string;
sourceType: SourceType;
url: string;
isExample: boolean;
}> {
readonly name: string = 'watch_minute';
}
Expand Down
6 changes: 3 additions & 3 deletions src/components/1-video-selector.svelte
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import { _ } from 'svelte-i18n';
import { track, ClickEvent, UrlPasteEvent } from './analytics.svelte';
import { track, ClickEvent, UrlPasteEvent } from '../analytics.svelte';
import Interpolator from './interpolator.svelte';
import { getExampleVideo, isExample, haveExamples } from '../stores/video-example';
import VideoSelectorBtn from './video-selector-btn.svelte';
import { LocalRoom } from '../stores/local-room';
import { Room } from '../stores/room';
import { SourceType } from '../normalize-link';
export let room: LocalRoom;
export let room: Room;
$: url = room.url;
$: isLocalMode = room.isLocalMode;
Expand Down
2 changes: 1 addition & 1 deletion src/components/2-copy-url.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts">
import { _ } from 'svelte-i18n';
import Interpolator from './interpolator.svelte';
import { track, ClickEvent } from './analytics.svelte';
import { track, ClickEvent } from '../analytics.svelte';
export let roomId: string;
Expand Down
75 changes: 35 additions & 40 deletions src/components/index.svelte
Original file line number Diff line number Diff line change
@@ -1,45 +1,53 @@
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { tick } from 'svelte';
import { onDestroy} from 'svelte';
import { fade } from 'svelte/transition';
import { _, locale } from 'svelte-i18n';
import { locales } from '../i18n/index';
import Loader from './loader.svelte';
import { RemoteRoom } from '../stores/remote-room';
import { LocalRoom } from '../stores/local-room';
import { Room } from '../stores/room';
import { type Link } from '../normalize-link'
import VideoSelector from './1-video-selector.svelte';
import CopyUrl from './2-copy-url.svelte';
import VideoPlayer from './video-player/index.svelte';
import Users from './users/index.svelte';
import { randomStr } from '../utils';
import { syncTime } from '../stores/clock';
import { track, ClickEvent, WatchedMinuteEvent, LocaleChangedEvent } from './analytics.svelte';
import { track, ClickEvent, LocaleChangedEvent } from '../analytics.svelte';
import { isExample } from '../stores/video-example';
export let roomId: string;
let remoteRoom: RemoteRoom;
let localRoom: LocalRoom;
$: remoteRoom = new RemoteRoom(roomId);
$: localRoom = new LocalRoom(remoteRoom);
$: initRoom(remoteRoom);
$: url = localRoom?.url;
$: play = localRoom?.play;
$: paused = localRoom?.paused;
$: minutesWatched = localRoom?.minutesWatched;
$: currentTime = localRoom?.currentTime;
let isRoomLoading = true;
let isTimeLoading = true;
const initRoom = async function (room: RemoteRoom) {
let room: Room;
$: (async (roomId) => {
isRoomLoading = true;
if (room) {
room.destruct();
}
room = new Room(roomId);
try {
await remoteRoom.load();
await room.init();
} finally {
isRoomLoading = false;
}
})(roomId);
$: url = room?.url;
$: play = room?.play;
$: paused = room?.paused;
$: minutesWatched = room?.minutesWatched;
$: currentTime = room?.currentTime;
$: users = room?.users;
onDestroy(() => {
if (room) {
room.destruct();
}
});
let isRoomLoading = true;
let isTimeLoading = true;
const initRoom = async function (room: Room) {
};
const initTime = async function () {
Expand Down Expand Up @@ -95,22 +103,8 @@
track(new LocaleChangedEvent({ locale: $locale }));
};
let timeSpentInterval;
onMount(() => {
timeSpentInterval = setInterval(() => {
if ($play && !$paused) {
$minutesWatched += 1;
track(new WatchedMinuteEvent({ roomId, sourceType: $play.type, isExample: isExample($url) }));
}
}, 60000);
});
onDestroy(() => {
clearInterval(timeSpentInterval);
});
$: isLoading = !localRoom || isRoomLoading || isTimeLoading;
</script>
$: isLoading = !room || isRoomLoading || isTimeLoading;
</script>

<div class="uk-flex-1 uk-flex uk-flex-center" class:uk-flex-middle={isLoading}>
{#if isLoading}
Expand All @@ -124,11 +118,12 @@
<div uk-grid class="uk-grid-small">
<div class="tile uk-flex uk-flex-column">
<h2 class="uk-card-title">🍿 { $_('selectVideo.title') }</h2>
<VideoSelector room={localRoom} />
<VideoSelector room={room} />
</div>
<div class="tile uk-flex uk-flex-column">
<h2 class="uk-card-title">👥 { $_('invite.title') }</h2>
<CopyUrl roomId={roomId}/>
<CopyUrl roomId={roomId} />
<Users users={$users} />
</div>

<div class="buttons uk-flex uk-margin-top uk-flex-column">
Expand Down
26 changes: 26 additions & 0 deletions src/components/users/index.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<script lang="ts">
import { flip } from 'svelte/animate';
import { fade, blur, fly, slide, scale, draw } from 'svelte/transition';
import { _ } from 'svelte-i18n';
import User from './user.svelte';
import type { User } from '../../stores/remote-room';
import { myNameStore } from '../../stores/my-name';
import { myId } from '../../stores/my-id';
export let users: User[];
</script>

<div class="users uk-flex-center uk-flex uk-margin-top uk-text-center uk-flex-middle">
<User user={({ name: $myNameStore, id: myId })} me={true} bind:myName={$myNameStore} />
{#each users as user (user.id)}
{#if $myNameStore !== user}
<User user={user} me={false} />
{/if}
{/each}
</div>

<style lang="scss">
.users {
overflow-y: scroll;
}
</style>
66 changes: 66 additions & 0 deletions src/components/users/user.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<script lang="ts">
import { _ } from 'svelte-i18n';
import { XORWow } from 'random-seedable';
import type { User } from '../../stores/bound-user';
export let user: User;
export let myName: string;
export let me: boolean;
const maxLength = 10;
const updateName = function () {
myName = prompt($_('users.nameEditPromt', { values: { maxLength }}), myName);
};
const code = function (str: string): number {
return [...str].reduce((a, x) => a + x.charCodeAt(0), 0);
};
function pseudoRandom(seed) {
return function() {
seed = seed * 16807 % 2147483647;
return seed;
}
}
const stringToColor = function (str: string): string {
const rand = pseudoRandom(code(str + user.id.slice(1)));
const randColor = () => (rand() % 100 + 64).toString(16).padStart(2, '0');
return `#${randColor()}${randColor()}${randColor()}70`;
};
const emojiRegex = /^\p{Emoji}$/u;
const isEmoji = function (text: string): boolean {
return emojiRegex.test(text);
};
</script>

<div
class="user uk-text-center uk-flex uk-flex-column uk-flex-center uk-flex-middle uk-flex-middle uk-margin-right"
style:background-color={stringToColor(user.name)}
>
<div class="uk-text-emphasis uk-margin-small-top" class:uk-text-large={user.name.length === 1 || isEmoji(user.name)}>
{ user.name.slice(0, maxLength) }
</div>
<span class="uk-text-muted uk-text-small">
{#if me}
<u on:click={updateName} class="pointer">{ $_('users.nameEdit') }</u>
{:else}
online
{/if}
</span>
</div>

<style lang="scss">
.user {
height: 4rem;
width: 4rem;
min-height: 4rem;
min-width: 4rem;
border-radius: 1.5rem;
border: 1px solid #7d7d7d;
text-wrap: wrap;
padding: 0.5rem;
}
</style>
2 changes: 1 addition & 1 deletion src/components/video-player/index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
} catch {
let url = link.url;
if (isHls) {
url = `${proxies.hlsUrl}/${btoa(link.url) }.m3u8`;
url = `${proxies.hlsUrl}/${btoa(link.url)}.m3u8`;
} else {
url = `${proxies.regularUrl}?url=${link.url}`;
}
Expand Down
6 changes: 3 additions & 3 deletions src/components/video-selector-btn.svelte
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<script lang="ts">
import { _ } from 'svelte-i18n';
import { track, ClickEvent } from './analytics.svelte';
import type { LocalRoom } from '../stores/local-room';
import { track, ClickEvent } from '../analytics.svelte';
import type { Room } from '../stores/room';
export let room: LocalRoom;
export let room: Room;
$: blobUrl = room.blobUrl;
$: fileName = room.fileName;
Expand Down
20 changes: 20 additions & 0 deletions src/destructable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
type Destructor = () => void;

export class Destructable {
private readonly destructors: Destructor[] = [];

protected onDestruct (destructor: Destructor) {
this.destructors.push(destructor);
}

protected registerDependency (destructable: Destructable) {
this.onDestruct(() => destructable.destruct());
}

destruct () {
while (this.destructors.length) {
const destructor = this.destructors.pop();
destructor && destructor();
}
}
}
4 changes: 4 additions & 0 deletions src/i18n/_.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ const def = {
},
poweredBy: 'Powered by',
or: 'or',
users: {
nameEdit: 'edit',
nameEditPromt: 'Enter your name (maximum {maxLength} characters) or leave the input blank to pick random name',
},
};

export type TranslatedText = typeof def;
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ const fr: TranslatedText = {
},
poweredBy: 'Alimenté par',
or: 'ou',
users: {
nameEdit: 'éditer',
nameEditPromt: 'Entrez votre nom (maximum {maxLength} caractères) ou laissez le champ vide pour choisir un nom au hasard',
},
};

export default fr;
Loading

0 comments on commit d638a69

Please sign in to comment.