lint fixes
This commit is contained in:
@@ -54,3 +54,9 @@ src-tauri/gen/
|
|||||||
# Ignore this file
|
# Ignore this file
|
||||||
.gitignore
|
.gitignore
|
||||||
|
|
||||||
|
|
||||||
|
# wrangler files
|
||||||
|
.wrangler
|
||||||
|
.dev.vars*
|
||||||
|
!.dev.vars.example
|
||||||
|
!.env.example
|
||||||
|
|||||||
@@ -1,2 +1,10 @@
|
|||||||
dist/
|
dist/
|
||||||
node_modules/
|
node_modules/
|
||||||
|
.git/
|
||||||
|
.cursor/
|
||||||
|
coverage/
|
||||||
|
spacetimedb/dist/
|
||||||
|
src/module_bindings/
|
||||||
|
src-tauri/target/
|
||||||
|
src-tauri/gen/
|
||||||
|
pnpm-lock.yaml
|
||||||
|
|||||||
+4
-3
@@ -3,7 +3,7 @@ import js from "@eslint/js";
|
|||||||
import tseslint from "typescript-eslint";
|
import tseslint from "typescript-eslint";
|
||||||
import svelte from "eslint-plugin-svelte";
|
import svelte from "eslint-plugin-svelte";
|
||||||
import svelteParser from "svelte-eslint-parser";
|
import svelteParser from "svelte-eslint-parser";
|
||||||
import { defineConfig } from 'eslint/config';
|
import { defineConfig } from "eslint/config";
|
||||||
|
|
||||||
export default defineConfig([
|
export default defineConfig([
|
||||||
tseslint.configs.recommended,
|
tseslint.configs.recommended,
|
||||||
@@ -32,7 +32,7 @@ export default defineConfig([
|
|||||||
"README.md",
|
"README.md",
|
||||||
".github/",
|
".github/",
|
||||||
".cursor/",
|
".cursor/",
|
||||||
"spacetimedb/dist",
|
"spacetimedb/",
|
||||||
"src/module_bindings/",
|
"src/module_bindings/",
|
||||||
"src-tauri/**",
|
"src-tauri/**",
|
||||||
],
|
],
|
||||||
@@ -64,13 +64,14 @@ export default defineConfig([
|
|||||||
projectService: true,
|
projectService: true,
|
||||||
extraFileExtensions: [".svelte"],
|
extraFileExtensions: [".svelte"],
|
||||||
svelteFeatures: {
|
svelteFeatures: {
|
||||||
runes: true
|
runes: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rules: {
|
rules: {
|
||||||
|
"no-unused-vars": "off",
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
"@/no-trailing-spaces": "warn",
|
"@/no-trailing-spaces": "warn",
|
||||||
"@typescript-eslint/no-unused-vars": [
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
|||||||
+7
-3
@@ -9,13 +9,15 @@
|
|||||||
"build": "tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
"format": "prettier . --write --ignore-path ../../.prettierignore",
|
"format": "prettier . --write --ignore-path ../../.prettierignore",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview",
|
"preview": "pnpm run build && wrangler dev",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"spacetime:generate": "spacetime generate --lang typescript --out-dir src/module_bindings --module-path spacetimedb",
|
"spacetime:generate": "spacetime generate --lang typescript --out-dir src/module_bindings --module-path spacetimedb",
|
||||||
"spacetime:publish:local": "spacetime publish --module-path spacetimedb --server local",
|
"spacetime:publish:local": "spacetime publish --module-path spacetimedb --server local",
|
||||||
"spacetime:publish": "spacetime publish --module-path spacetimedb --server maincloud",
|
"spacetime:publish": "spacetime publish --module-path spacetimedb --server maincloud",
|
||||||
"deploy:local": "docker compose -f docker-compose.local.yml up --build",
|
"deploy:local": "docker compose -f docker-compose.local.yml up --build",
|
||||||
"deploy:maincloud": "docker compose -f docker-compose.maincloud.yml up --build"
|
"deploy:maincloud": "docker compose -f docker-compose.maincloud.yml up --build",
|
||||||
|
"deploy:cloudflare": "wrangler deploy",
|
||||||
|
"deploy": "pnpm run build && wrangler deploy"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^7.2.0",
|
"@fortawesome/fontawesome-free": "^7.2.0",
|
||||||
@@ -27,6 +29,7 @@
|
|||||||
"svelte-check": "^4.4.6"
|
"svelte-check": "^4.4.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@cloudflare/vite-plugin": "^1.31.0",
|
||||||
"@eslint/js": "^9.17.0",
|
"@eslint/js": "^9.17.0",
|
||||||
"@tauri-apps/cli": "^2.10.1",
|
"@tauri-apps/cli": "^2.10.1",
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
@@ -44,6 +47,7 @@
|
|||||||
"typescript": "~5.6.2",
|
"typescript": "~5.6.2",
|
||||||
"typescript-eslint": "^8.18.2",
|
"typescript-eslint": "^8.18.2",
|
||||||
"vite": "^8.0.3",
|
"vite": "^8.0.3",
|
||||||
"vitest": "3.2.4"
|
"vitest": "3.2.4",
|
||||||
|
"wrangler": "^4.80.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+826
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,9 @@ const user = table(
|
|||||||
{
|
{
|
||||||
name: "user",
|
name: "user",
|
||||||
public: true,
|
public: true,
|
||||||
indexes: [{ accessor: "by_online", algorithm: "btree", columns: ["online"] }],
|
indexes: [
|
||||||
|
{ accessor: "by_online", algorithm: "btree", columns: ["online"] },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identity: t.identity().primaryKey(),
|
identity: t.identity().primaryKey(),
|
||||||
@@ -467,7 +469,12 @@ export const upload_avatar = spacetimedb.reducer(
|
|||||||
if (data.length > 1024 * 1024) {
|
if (data.length > 1024 * 1024) {
|
||||||
throw new SenderError("Avatar exceeds 1MB limit");
|
throw new SenderError("Avatar exceeds 1MB limit");
|
||||||
}
|
}
|
||||||
const img = ctx.db.image.insert({ id: 0n, data, mime_type: mimeType, name: "avatar" });
|
const img = ctx.db.image.insert({
|
||||||
|
id: 0n,
|
||||||
|
data,
|
||||||
|
mime_type: mimeType,
|
||||||
|
name: "avatar",
|
||||||
|
});
|
||||||
const user = ctx.db.user.identity.find(ctx.sender);
|
const user = ctx.db.user.identity.find(ctx.sender);
|
||||||
if (!user) throw new SenderError("User not found");
|
if (!user) throw new SenderError("User not found");
|
||||||
ctx.db.user.identity.update({ ...user, avatar_id: img.id });
|
ctx.db.user.identity.update({ ...user, avatar_id: img.id });
|
||||||
@@ -1049,7 +1056,7 @@ export const send_message = spacetimedb.reducer(
|
|||||||
|
|
||||||
export const init = spacetimedb.init((ctx) => {
|
export const init = spacetimedb.init((ctx) => {
|
||||||
let hasServers = false;
|
let hasServers = false;
|
||||||
for (const _ of ctx.db.server.iter()) {
|
for (const _server of ctx.db.server.iter()) {
|
||||||
hasServers = true;
|
hasServers = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
"$schema": "../gen/schemas/desktop-schema.json",
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
"identifier": "default",
|
"identifier": "default",
|
||||||
"description": "enables the default permissions",
|
"description": "enables the default permissions",
|
||||||
"windows": [
|
"windows": ["main"],
|
||||||
"main"
|
"permissions": ["core:default"]
|
||||||
],
|
|
||||||
"permissions": [
|
|
||||||
"core:default"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { auth } from "./auth.svelte";
|
import { auth } from "./auth.svelte";
|
||||||
import { HOST_KEY, DB_NAME_KEY, getEnv } from "../env";
|
import { HOST_KEY, DB_NAME_KEY } from "../env";
|
||||||
import { TokenStore, getStdbHost, getStdbDbName } from "../config";
|
import { TokenStore, getStdbHost, getStdbDbName } from "../config";
|
||||||
import SpacetimeProvider from "../SpacetimeProvider.svelte";
|
import SpacetimeProvider from "../SpacetimeProvider.svelte";
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
children: any,
|
children: any,
|
||||||
reconnect: () => void,
|
reconnect: () => void,
|
||||||
showServerSettings: boolean,
|
showServerSettings: boolean,
|
||||||
onToggleServerSettings: (val: boolean) => void,
|
onToggleServerSettings: (_val: boolean) => void,
|
||||||
reconnectKey: number
|
reconnectKey: number
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
@@ -22,7 +22,6 @@
|
|||||||
const MAINCLOUD_URI = "https://maincloud.spacetimedb.com";
|
const MAINCLOUD_URI = "https://maincloud.spacetimedb.com";
|
||||||
let isMaincloud = $state(true);
|
let isMaincloud = $state(true);
|
||||||
|
|
||||||
let userWantsToConnect = $state(false);
|
|
||||||
let isSettingsExpanded = $state(false);
|
let isSettingsExpanded = $state(false);
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
@@ -48,10 +47,6 @@
|
|||||||
if (stdbDbName) localStorage.setItem(DB_NAME_KEY, stdbDbName);
|
if (stdbDbName) localStorage.setItem(DB_NAME_KEY, stdbDbName);
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleAuthError(error: string | null) {
|
|
||||||
authError = error;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isBypassEnabled =
|
const isBypassEnabled =
|
||||||
import.meta.env.VITE_BYPASS_AUTH === "true" ||
|
import.meta.env.VITE_BYPASS_AUTH === "true" ||
|
||||||
new URLSearchParams(window.location.search).has("bypass_auth");
|
new URLSearchParams(window.location.search).has("bypass_auth");
|
||||||
@@ -77,7 +72,6 @@
|
|||||||
<SpacetimeProvider
|
<SpacetimeProvider
|
||||||
onReconnectTrigger={reconnect}
|
onReconnectTrigger={reconnect}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
userWantsToConnect = false;
|
|
||||||
isGuest = false;
|
isGuest = false;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -106,7 +100,6 @@
|
|||||||
<div style="display: flex; flex-direction: column; gap: 12px; width: 100%">
|
<div style="display: flex; flex-direction: column; gap: 12px; width: 100%">
|
||||||
<button
|
<button
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
userWantsToConnect = true;
|
|
||||||
auth.signinRedirect();
|
auth.signinRedirect();
|
||||||
}}
|
}}
|
||||||
disabled={auth.isLoading}
|
disabled={auth.isLoading}
|
||||||
@@ -119,7 +112,6 @@
|
|||||||
<button
|
<button
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
isGuest = true;
|
isGuest = true;
|
||||||
userWantsToConnect = true;
|
|
||||||
}}
|
}}
|
||||||
class="btn-secondary"
|
class="btn-secondary"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ class AuthStore {
|
|||||||
keysToRemove.push(key);
|
keysToRemove.push(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
keysToRemove.forEach(key => localStorage.removeItem(key));
|
keysToRemove.forEach((key) => localStorage.removeItem(key));
|
||||||
|
|
||||||
if (this.#user) {
|
if (this.#user) {
|
||||||
await this.#userManager.signoutRedirect();
|
await this.#userManager.signoutRedirect();
|
||||||
|
|||||||
@@ -4,9 +4,7 @@
|
|||||||
import { portal } from "../../portal";
|
import { portal } from "../../portal";
|
||||||
import type { ChatService } from "../services/chat.svelte";
|
import type { ChatService } from "../services/chat.svelte";
|
||||||
import type { WebRTCService } from "../services/webrtc/webrtc.svelte";
|
import type { WebRTCService } from "../services/webrtc/webrtc.svelte";
|
||||||
import type { WebRTCStats } from "../services/webrtc/types";
|
|
||||||
import Avatar from "./Avatar.svelte";
|
import Avatar from "./Avatar.svelte";
|
||||||
import type * as Types from "../../module_bindings/types";
|
|
||||||
|
|
||||||
const chat = getContext<ChatService>("chat");
|
const chat = getContext<ChatService>("chat");
|
||||||
const webrtc = getContext<WebRTCService>("webrtc");
|
const webrtc = getContext<WebRTCService>("webrtc");
|
||||||
@@ -183,7 +181,6 @@
|
|||||||
{@const isWatchingMe = chat.watching.some(w => w.watcher.isEqual(vs.identity) && w.watchee.isEqual(chat.identity))}
|
{@const isWatchingMe = chat.watching.some(w => w.watcher.isEqual(vs.identity) && w.watchee.isEqual(chat.identity))}
|
||||||
{@const amISharing = chat.currentVoiceState?.isSharingScreen}
|
{@const amISharing = chat.currentVoiceState?.isSharingScreen}
|
||||||
{@const voiceStatusColor = isMe ? "green" : getStatusColor(status)}
|
{@const voiceStatusColor = isMe ? "green" : getStatusColor(status)}
|
||||||
{@const videoStatusColor = isMe ? (isSharing ? "green" : undefined) : (isSharing ? getStatusColor(status) : undefined)}
|
|
||||||
{@const isLocalUserInThisChannel = chat.connectedVoiceChannel?.id === channel.id}
|
{@const isLocalUserInThisChannel = chat.connectedVoiceChannel?.id === channel.id}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext, onMount } from "svelte";
|
import { getContext, onMount } from "svelte";
|
||||||
import { EMOJIS, type EmojiInfo } from "../emojis";
|
import { EMOJIS } from "../emojis";
|
||||||
import type { ChatService } from "../services/chat.svelte";
|
import type { ChatService } from "../services/chat.svelte";
|
||||||
import type * as Types from "../../module_bindings/types";
|
|
||||||
import { optimizeEmoji } from "../utils";
|
import { optimizeEmoji } from "../utils";
|
||||||
|
|
||||||
let { onSelect, onClose } = $props<{
|
let { onSelect } = $props<{
|
||||||
onSelect: (emoji?: string, customEmojiId?: bigint) => void;
|
onSelect: (_emoji?: string, _customEmojiId?: bigint) => void;
|
||||||
onClose: () => void;
|
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const chat = getContext<ChatService>("chat");
|
const chat = getContext<ChatService>("chat");
|
||||||
@@ -22,7 +20,7 @@
|
|||||||
recentEmojis = JSON.parse(saved).map((id: any) =>
|
recentEmojis = JSON.parse(saved).map((id: any) =>
|
||||||
typeof id === 'string' && id.startsWith('custom:') ? BigInt(id.split(':')[1]) : id
|
typeof id === 'string' && id.startsWith('custom:') ? BigInt(id.split(':')[1]) : id
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch {
|
||||||
recentEmojis = [];
|
recentEmojis = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,8 +63,8 @@
|
|||||||
{ id: 'custom', name: 'Custom', icon: '✨' },
|
{ id: 'custom', name: 'Custom', icon: '✨' },
|
||||||
];
|
];
|
||||||
|
|
||||||
async function handleCustomUpload(e: Event) {
|
async function handleCustomUpload(_e: Event) {
|
||||||
const file = (e.target as HTMLInputElement).files?.[0];
|
const file = (_e.target as HTMLInputElement).files?.[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
// We'll assume the user provides a name or we use the filename
|
// We'll assume the user provides a name or we use the filename
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
|
||||||
import type * as Types from "../../module_bindings/types";
|
import type * as Types from "../../module_bindings/types";
|
||||||
|
|
||||||
let { image, onClose }: { image: Types.Image, onClose: () => void } = $props();
|
let { image, onClose }: { image: Types.Image, onClose: () => void } = $props();
|
||||||
@@ -20,7 +19,6 @@
|
|||||||
let hasMoved = false;
|
let hasMoved = false;
|
||||||
|
|
||||||
const isZoomed = $derived(zoomLevel > baseScale + 0.001);
|
const isZoomed = $derived(zoomLevel > baseScale + 0.001);
|
||||||
const currentActualScale = $derived(zoomLevel);
|
|
||||||
|
|
||||||
function handleKeydown(e: KeyboardEvent) {
|
function handleKeydown(e: KeyboardEvent) {
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
@@ -130,7 +128,7 @@
|
|||||||
isDragging = false;
|
isDragging = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleOverlayClick(e: MouseEvent) {
|
function handleOverlayClick() {
|
||||||
const clickDuration = Date.now() - mousedownTime;
|
const clickDuration = Date.now() - mousedownTime;
|
||||||
if (hasMoved || clickDuration > 300) return;
|
if (hasMoved || clickDuration > 300) return;
|
||||||
onClose();
|
onClose();
|
||||||
@@ -424,6 +422,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.full-image.animate {
|
.full-image.animate {
|
||||||
transition: transform 0.15s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: transform 0.05s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
if (!title && !description && !image) {
|
if (!title && !description && !image) {
|
||||||
throw new Error("No useful metadata found");
|
throw new Error("No useful metadata found");
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch {
|
||||||
if (!isCancelled) {
|
if (!isCancelled) {
|
||||||
error = true;
|
error = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
let {
|
let {
|
||||||
activeChannelId,
|
activeChannelId,
|
||||||
activeThreadId,
|
activeThreadId: _activeThreadId,
|
||||||
isFullyAuthenticated
|
isFullyAuthenticated
|
||||||
} = $props<{
|
} = $props<{
|
||||||
activeChannelId: bigint | null,
|
activeChannelId: bigint | null,
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
let {
|
let {
|
||||||
threadMessages,
|
threadMessages,
|
||||||
users,
|
users,
|
||||||
identity,
|
identity: _identity,
|
||||||
images
|
images: _images
|
||||||
}: {
|
}: {
|
||||||
threadMessages: readonly Types.Message[],
|
threadMessages: readonly Types.Message[],
|
||||||
users: readonly Types.User[],
|
users: readonly Types.User[],
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Identity } from "spacetimedb";
|
import { Identity } from "spacetimedb";
|
||||||
import type * as Types from "../../module_bindings/types";
|
import type * as Types from "../../module_bindings/types";
|
||||||
import { useReducer } from "spacetimedb/svelte";
|
|
||||||
import { reducers } from "../../module_bindings";
|
|
||||||
import ThreadMessageList from "./ThreadMessageList.svelte";
|
import ThreadMessageList from "./ThreadMessageList.svelte";
|
||||||
import ThreadMessageInput from "./ThreadMessageInput.svelte";
|
import ThreadMessageInput from "./ThreadMessageInput.svelte";
|
||||||
import RichText from "./RichText.svelte";
|
import RichText from "./RichText.svelte";
|
||||||
@@ -15,7 +13,7 @@
|
|||||||
pendingThreadParentMessageId,
|
pendingThreadParentMessageId,
|
||||||
setPendingThreadParentMessageId,
|
setPendingThreadParentMessageId,
|
||||||
activeChannelId,
|
activeChannelId,
|
||||||
activeServer,
|
activeServer: _activeServer,
|
||||||
isFullyAuthenticated,
|
isFullyAuthenticated,
|
||||||
users,
|
users,
|
||||||
identity,
|
identity,
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Identity } from "spacetimedb";
|
import { Identity } from "spacetimedb";
|
||||||
import { getContext, onMount, onDestroy } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import Avatar from "./Avatar.svelte";
|
import Avatar from "./Avatar.svelte";
|
||||||
import type { WebRTCService } from "../services/webrtc/webrtc.svelte";
|
|
||||||
import type * as Types from "../../module_bindings/types";
|
import type * as Types from "../../module_bindings/types";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@@ -27,8 +26,6 @@
|
|||||||
users: readonly Types.User[];
|
users: readonly Types.User[];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const webrtc = getContext<WebRTCService>("webrtc");
|
|
||||||
|
|
||||||
let videoRef = $state<HTMLVideoElement | null>(null);
|
let videoRef = $state<HTMLVideoElement | null>(null);
|
||||||
let containerRef = $state<HTMLDivElement | null>(null);
|
let containerRef = $state<HTMLDivElement | null>(null);
|
||||||
let isMuted = $state(false);
|
let isMuted = $state(false);
|
||||||
|
|||||||
+15
-3
@@ -1024,7 +1024,11 @@ export const EMOJIS: EmojiInfo[] = [
|
|||||||
{ char: "🈁", name: "Japanese “here” button", category: "symbols" },
|
{ char: "🈁", name: "Japanese “here” button", category: "symbols" },
|
||||||
{ char: "🈂️", name: "Japanese “service charge” button", category: "symbols" },
|
{ char: "🈂️", name: "Japanese “service charge” button", category: "symbols" },
|
||||||
{ char: "🈷️", name: "Japanese “monthly amount” button", category: "symbols" },
|
{ char: "🈷️", name: "Japanese “monthly amount” button", category: "symbols" },
|
||||||
{ char: "🈶", name: "Japanese “not free of charge” button", category: "symbols" },
|
{
|
||||||
|
char: "🈶",
|
||||||
|
name: "Japanese “not free of charge” button",
|
||||||
|
category: "symbols",
|
||||||
|
},
|
||||||
{ char: "🈯", name: "Japanese “reserved” button", category: "symbols" },
|
{ char: "🈯", name: "Japanese “reserved” button", category: "symbols" },
|
||||||
{ char: "🉐", name: "Japanese “bargain” button", category: "symbols" },
|
{ char: "🉐", name: "Japanese “bargain” button", category: "symbols" },
|
||||||
{ char: "🈹", name: "Japanese “discount” button", category: "symbols" },
|
{ char: "🈹", name: "Japanese “discount” button", category: "symbols" },
|
||||||
@@ -1034,9 +1038,17 @@ export const EMOJIS: EmojiInfo[] = [
|
|||||||
{ char: "🈸", name: "Japanese “application” button", category: "symbols" },
|
{ char: "🈸", name: "Japanese “application” button", category: "symbols" },
|
||||||
{ char: "🈴", name: "Japanese “passing grade” button", category: "symbols" },
|
{ char: "🈴", name: "Japanese “passing grade” button", category: "symbols" },
|
||||||
{ char: "🈳", name: "Japanese “vacancy” button", category: "symbols" },
|
{ char: "🈳", name: "Japanese “vacancy” button", category: "symbols" },
|
||||||
{ char: "㊗️", name: "Japanese “congratulations” button", category: "symbols" },
|
{
|
||||||
|
char: "㊗️",
|
||||||
|
name: "Japanese “congratulations” button",
|
||||||
|
category: "symbols",
|
||||||
|
},
|
||||||
{ char: "㊙️", name: "Japanese “secret” button", category: "symbols" },
|
{ char: "㊙️", name: "Japanese “secret” button", category: "symbols" },
|
||||||
{ char: "🈺", name: "Japanese “open for business” button", category: "symbols" },
|
{
|
||||||
|
char: "🈺",
|
||||||
|
name: "Japanese “open for business” button",
|
||||||
|
category: "symbols",
|
||||||
|
},
|
||||||
{ char: "🈵", name: "Japanese “no vacancy” button", category: "symbols" },
|
{ char: "🈵", name: "Japanese “no vacancy” button", category: "symbols" },
|
||||||
{ char: "🔴", name: "red circle", category: "symbols" },
|
{ char: "🔴", name: "red circle", category: "symbols" },
|
||||||
{ char: "🟠", name: "orange circle", category: "symbols" },
|
{ char: "🟠", name: "orange circle", category: "symbols" },
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export class ChatService {
|
|||||||
|
|
||||||
// Background effect to populate avatar cache
|
// Background effect to populate avatar cache
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const usersWithAvatars = this.users.filter(u => u.avatarId);
|
const usersWithAvatars = this.users.filter((u) => u.avatarId);
|
||||||
for (const user of usersWithAvatars) {
|
for (const user of usersWithAvatars) {
|
||||||
const avatarId = user.avatarId!;
|
const avatarId = user.avatarId!;
|
||||||
const idStr = avatarId.toString();
|
const idStr = avatarId.toString();
|
||||||
@@ -64,7 +64,7 @@ export class ChatService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. If not in cache, check if we have the image data in SpacetimeDB sync
|
// 2. If not in cache, check if we have the image data in SpacetimeDB sync
|
||||||
const img = this.images.find(i => i.id === id);
|
const img = this.images.find((i) => i.id === id);
|
||||||
if (img) {
|
if (img) {
|
||||||
const url = await imageCache.set(id, img.data, img.mimeType);
|
const url = await imageCache.set(id, img.data, img.mimeType);
|
||||||
this.#avatarUrls.set(idStr, url);
|
this.#avatarUrls.set(idStr, url);
|
||||||
@@ -356,7 +356,11 @@ export class ChatService {
|
|||||||
this.#msg.handleLoadMoreMessages();
|
this.#msg.handleLoadMoreMessages();
|
||||||
};
|
};
|
||||||
|
|
||||||
handleToggleReaction = (messageId: bigint, emoji?: string, customEmojiId?: bigint) => {
|
handleToggleReaction = (
|
||||||
|
messageId: bigint,
|
||||||
|
emoji?: string,
|
||||||
|
customEmojiId?: bigint,
|
||||||
|
) => {
|
||||||
this.#msg.toggleReaction(messageId, emoji, customEmojiId);
|
this.#msg.toggleReaction(messageId, emoji, customEmojiId);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -377,7 +381,11 @@ export class ChatService {
|
|||||||
this.#msg.uploadImage(data, mimeType, name);
|
this.#msg.uploadImage(data, mimeType, name);
|
||||||
};
|
};
|
||||||
|
|
||||||
uploadCustomEmoji = async (name: string, category: string, data: Uint8Array) => {
|
uploadCustomEmoji = async (
|
||||||
|
name: string,
|
||||||
|
category: string,
|
||||||
|
data: Uint8Array,
|
||||||
|
) => {
|
||||||
this.#msg.uploadCustomEmoji(name, category, data);
|
this.#msg.uploadCustomEmoji(name, category, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -420,7 +428,12 @@ export class ChatService {
|
|||||||
})
|
})
|
||||||
.map((ta) => {
|
.map((ta) => {
|
||||||
const user = this.users.find((u) => u.identity.isEqual(ta.identity));
|
const user = this.users.find((u) => u.identity.isEqual(ta.identity));
|
||||||
return user || { name: `User ${ta.identity.toHexString().substring(0, 8)}` } as any as Types.User;
|
return (
|
||||||
|
user ||
|
||||||
|
({
|
||||||
|
name: `User ${ta.identity.toHexString().substring(0, 8)}`,
|
||||||
|
} as any as Types.User)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,12 +26,18 @@ export class MessagingService {
|
|||||||
messageLimit = $state(50);
|
messageLimit = $state(50);
|
||||||
isLoadingMore = $state(false);
|
isLoadingMore = $state(false);
|
||||||
|
|
||||||
constructor(db: DatabaseService, nav: NavigationService, identity: () => Identity | null) {
|
constructor(
|
||||||
|
db: DatabaseService,
|
||||||
|
nav: NavigationService,
|
||||||
|
identity: () => Identity | null,
|
||||||
|
) {
|
||||||
this.#db = db;
|
this.#db = db;
|
||||||
this.#nav = nav;
|
this.#nav = nav;
|
||||||
this.#identity = identity;
|
this.#identity = identity;
|
||||||
|
|
||||||
this.#createThreadWithMessageReducer = useReducer(reducers.createThreadWithMessage);
|
this.#createThreadWithMessageReducer = useReducer(
|
||||||
|
reducers.createThreadWithMessage,
|
||||||
|
);
|
||||||
this.#sendMessageReducer = useReducer(reducers.sendMessage);
|
this.#sendMessageReducer = useReducer(reducers.sendMessage);
|
||||||
this.#uploadImageReducer = useReducer(reducers.uploadImage);
|
this.#uploadImageReducer = useReducer(reducers.uploadImage);
|
||||||
this.#uploadCustomEmojiReducer = useReducer(reducers.uploadCustomEmoji);
|
this.#uploadCustomEmojiReducer = useReducer(reducers.uploadCustomEmoji);
|
||||||
@@ -42,9 +48,15 @@ export class MessagingService {
|
|||||||
const [messageImagesStore] = useTable(tables.message_image);
|
const [messageImagesStore] = useTable(tables.message_image);
|
||||||
const [messageReactionsStore] = useTable(tables.message_reaction);
|
const [messageReactionsStore] = useTable(tables.message_reaction);
|
||||||
|
|
||||||
messagesStore.subscribe((v: readonly Types.Message[]) => (this.#allMessages = v));
|
messagesStore.subscribe(
|
||||||
messageImagesStore.subscribe((v: readonly Types.MessageImage[]) => (this.#messageImages = v));
|
(v: readonly Types.Message[]) => (this.#allMessages = v),
|
||||||
messageReactionsStore.subscribe((v: readonly Types.MessageReaction[]) => (this.#messageReactions = v));
|
);
|
||||||
|
messageImagesStore.subscribe(
|
||||||
|
(v: readonly Types.MessageImage[]) => (this.#messageImages = v),
|
||||||
|
);
|
||||||
|
messageReactionsStore.subscribe(
|
||||||
|
(v: readonly Types.MessageReaction[]) => (this.#messageReactions = v),
|
||||||
|
);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const channelId = this.#nav.activeChannelId;
|
const channelId = this.#nav.activeChannelId;
|
||||||
@@ -57,38 +69,53 @@ export class MessagingService {
|
|||||||
if (!conn) return;
|
if (!conn) return;
|
||||||
|
|
||||||
untrack(() => {
|
untrack(() => {
|
||||||
const queries = [
|
const queries = ["SELECT * FROM server", "SELECT * FROM custom_emoji"];
|
||||||
"SELECT * FROM server",
|
|
||||||
"SELECT * FROM custom_emoji",
|
|
||||||
];
|
|
||||||
|
|
||||||
// 1. Surgical Membership & Identity Pruning
|
// 1. Surgical Membership & Identity Pruning
|
||||||
// Only load our own memberships and those of the active server
|
// Only load our own memberships and those of the active server
|
||||||
if (identity && serverId) {
|
if (identity && serverId) {
|
||||||
queries.push(`SELECT * FROM server_member WHERE identity = 0x${identity.toHexString()} OR server_id = ${serverId}`);
|
queries.push(
|
||||||
|
`SELECT * FROM server_member WHERE identity = 0x${identity.toHexString()} OR server_id = ${serverId}`,
|
||||||
|
);
|
||||||
} else if (identity) {
|
} else if (identity) {
|
||||||
queries.push(`SELECT * FROM server_member WHERE identity = 0x${identity.toHexString()}`);
|
queries.push(
|
||||||
|
`SELECT * FROM server_member WHERE identity = 0x${identity.toHexString()}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serverId) {
|
if (serverId) {
|
||||||
// 2. Metadata for the Active Server
|
// 2. Metadata for the Active Server
|
||||||
queries.push(`SELECT * FROM channel WHERE server_id = ${serverId}`);
|
queries.push(`SELECT * FROM channel WHERE server_id = ${serverId}`);
|
||||||
queries.push(`SELECT * FROM thread WHERE channel_id IN (SELECT id FROM channel WHERE server_id = ${serverId})`);
|
queries.push(
|
||||||
|
`SELECT * FROM thread WHERE channel_id IN (SELECT id FROM channel WHERE server_id = ${serverId})`,
|
||||||
|
);
|
||||||
|
|
||||||
// Voice states and activity are lightweight and indexed by channel_id
|
// Voice states and activity are lightweight and indexed by channel_id
|
||||||
queries.push(`SELECT * FROM voice_state WHERE channel_id IN (SELECT id FROM channel WHERE server_id = ${serverId})`);
|
queries.push(
|
||||||
queries.push(`SELECT * FROM voice_activity WHERE channel_id IN (SELECT id FROM channel WHERE server_id = ${serverId})`);
|
`SELECT * FROM voice_state WHERE channel_id IN (SELECT id FROM channel WHERE server_id = ${serverId})`,
|
||||||
queries.push(`SELECT * FROM typing_activity WHERE channel_id IN (SELECT id FROM channel WHERE server_id = ${serverId})`);
|
);
|
||||||
queries.push(`SELECT * FROM watching WHERE channel_id IN (SELECT id FROM channel WHERE server_id = ${serverId})`);
|
queries.push(
|
||||||
|
`SELECT * FROM voice_activity WHERE channel_id IN (SELECT id FROM channel WHERE server_id = ${serverId})`,
|
||||||
|
);
|
||||||
|
queries.push(
|
||||||
|
`SELECT * FROM typing_activity WHERE channel_id IN (SELECT id FROM channel WHERE server_id = ${serverId})`,
|
||||||
|
);
|
||||||
|
queries.push(
|
||||||
|
`SELECT * FROM watching WHERE channel_id IN (SELECT id FROM channel WHERE server_id = ${serverId})`,
|
||||||
|
);
|
||||||
|
|
||||||
// 3. Load the actual messages for the active channel (with pagination)
|
// 3. Load the actual messages for the active channel (with pagination)
|
||||||
if (channelId) {
|
if (channelId) {
|
||||||
queries.push(`SELECT * FROM message WHERE channel_id = ${channelId} AND thread_id IS NULL ORDER BY sent DESC LIMIT ${limit}`);
|
queries.push(
|
||||||
|
`SELECT * FROM message WHERE channel_id = ${channelId} AND thread_id IS NULL ORDER BY sent DESC LIMIT ${limit}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If viewing a thread, pull those specific messages too
|
// If viewing a thread, pull those specific messages too
|
||||||
if (threadId) {
|
if (threadId) {
|
||||||
queries.push(`SELECT * FROM message WHERE thread_id = ${threadId} OR id = (SELECT parent_message_id FROM thread WHERE id = ${threadId})`);
|
queries.push(
|
||||||
|
`SELECT * FROM message WHERE thread_id = ${threadId} OR id = (SELECT parent_message_id FROM thread WHERE id = ${threadId})`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Surgical Image & Reaction Sync
|
// 4. Surgical Image & Reaction Sync
|
||||||
@@ -98,8 +125,12 @@ export class MessagingService {
|
|||||||
? `(SELECT id FROM message WHERE thread_id = ${threadId} OR id = (SELECT parent_message_id FROM thread WHERE id = ${threadId}))`
|
? `(SELECT id FROM message WHERE thread_id = ${threadId} OR id = (SELECT parent_message_id FROM thread WHERE id = ${threadId}))`
|
||||||
: `(SELECT id FROM message WHERE channel_id = ${channelId} AND thread_id IS NULL ORDER BY sent DESC LIMIT ${limit})`;
|
: `(SELECT id FROM message WHERE channel_id = ${channelId} AND thread_id IS NULL ORDER BY sent DESC LIMIT ${limit})`;
|
||||||
|
|
||||||
queries.push(`SELECT * FROM message_image WHERE message_id IN ${visibleMsgSubquery}`);
|
queries.push(
|
||||||
queries.push(`SELECT * FROM message_reaction WHERE message_id IN ${visibleMsgSubquery}`);
|
`SELECT * FROM message_image WHERE message_id IN ${visibleMsgSubquery}`,
|
||||||
|
);
|
||||||
|
queries.push(
|
||||||
|
`SELECT * FROM message_reaction WHERE message_id IN ${visibleMsgSubquery}`,
|
||||||
|
);
|
||||||
|
|
||||||
// Image Sync: Message Images + User Avatars
|
// Image Sync: Message Images + User Avatars
|
||||||
const userSubquery = `(SELECT identity FROM user WHERE online = true OR identity IN (SELECT identity FROM server_member WHERE server_id = ${serverId}) OR identity IN (SELECT sender FROM message WHERE id IN ${visibleMsgSubquery}))`;
|
const userSubquery = `(SELECT identity FROM user WHERE online = true OR identity IN (SELECT identity FROM server_member WHERE server_id = ${serverId}) OR identity IN (SELECT sender FROM message WHERE id IN ${visibleMsgSubquery}))`;
|
||||||
@@ -130,7 +161,7 @@ export class MessagingService {
|
|||||||
});
|
});
|
||||||
// Reset limit on channel switch
|
// Reset limit on channel switch
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const _ = this.#nav.activeChannelId;
|
void this.#nav.activeChannelId;
|
||||||
untrack(() => {
|
untrack(() => {
|
||||||
this.messageLimit = 50;
|
this.messageLimit = 50;
|
||||||
});
|
});
|
||||||
@@ -216,11 +247,19 @@ export class MessagingService {
|
|||||||
this.#uploadImageReducer({ data, mimeType, name });
|
this.#uploadImageReducer({ data, mimeType, name });
|
||||||
};
|
};
|
||||||
|
|
||||||
uploadCustomEmoji = async (name: string, category: string, data: Uint8Array) => {
|
uploadCustomEmoji = async (
|
||||||
|
name: string,
|
||||||
|
category: string,
|
||||||
|
data: Uint8Array,
|
||||||
|
) => {
|
||||||
this.#uploadCustomEmojiReducer({ name, category, data });
|
this.#uploadCustomEmojiReducer({ name, category, data });
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleReaction = (messageId: bigint, emoji?: string, customEmojiId?: bigint) => {
|
toggleReaction = (
|
||||||
|
messageId: bigint,
|
||||||
|
emoji?: string,
|
||||||
|
customEmojiId?: bigint,
|
||||||
|
) => {
|
||||||
this.#toggleReactionReducer({ messageId, emoji, customEmojiId });
|
this.#toggleReactionReducer({ messageId, emoji, customEmojiId });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export class ChannelAudioWebRTCService {
|
|||||||
$effect(() => {
|
$effect(() => {
|
||||||
const audioTrack = this.localStream?.getAudioTracks()[0] || null;
|
const audioTrack = this.localStream?.getAudioTracks()[0] || null;
|
||||||
// Accessing peers and peerStatuses to trigger effect on changes
|
// Accessing peers and peerStatuses to trigger effect on changes
|
||||||
const _statuses = this.peerManager.peerStatuses;
|
void this.peerManager.peerStatuses;
|
||||||
this.peerManager.peers.forEach(async (peer, peerIdHex) => {
|
this.peerManager.peers.forEach(async (peer, peerIdHex) => {
|
||||||
const transceivers = peer.pc.getTransceivers();
|
const transceivers = peer.pc.getTransceivers();
|
||||||
if (transceivers[0] && transceivers[0].sender.track !== audioTrack) {
|
if (transceivers[0] && transceivers[0].sender.track !== audioTrack) {
|
||||||
|
|||||||
@@ -224,7 +224,10 @@ export class LocalMediaService {
|
|||||||
|
|
||||||
// Persist screen share settings
|
// Persist screen share settings
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
localStorage.setItem("screen_share_resolution", this.screenShareResolution);
|
localStorage.setItem(
|
||||||
|
"screen_share_resolution",
|
||||||
|
this.screenShareResolution,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
localStorage.setItem("screen_share_framerate", this.screenShareFramerate);
|
localStorage.setItem("screen_share_framerate", this.screenShareFramerate);
|
||||||
@@ -331,7 +334,8 @@ export class LocalMediaService {
|
|||||||
try {
|
try {
|
||||||
console.log("[local-media] Requesting screen share...");
|
console.log("[local-media] Requesting screen share...");
|
||||||
|
|
||||||
const res = RESOLUTIONS[this.screenShareResolution] || RESOLUTIONS["1080"];
|
const res =
|
||||||
|
RESOLUTIONS[this.screenShareResolution] || RESOLUTIONS["1080"];
|
||||||
const { width, height } = res;
|
const { width, height } = res;
|
||||||
const frameRate = parseInt(this.screenShareFramerate);
|
const frameRate = parseInt(this.screenShareFramerate);
|
||||||
|
|
||||||
@@ -363,10 +367,10 @@ export class LocalMediaService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
stopScreenShare = (
|
stopScreenShare = (
|
||||||
onTrackCleared: (track: MediaStreamTrack | null) => void,
|
onTrackCleared: (_track: MediaStreamTrack | null) => void,
|
||||||
) => {
|
) => {
|
||||||
if (this.localScreenStream) {
|
if (this.localScreenStream) {
|
||||||
this.localScreenStream.getTracks().forEach((track) => track.stop());
|
this.localScreenStream.getTracks().forEach((t) => t.stop());
|
||||||
this.localScreenStream = null;
|
this.localScreenStream = null;
|
||||||
this.#setSharingScreen({ sharing: false });
|
this.#setSharingScreen({ sharing: false });
|
||||||
onTrackCleared(null);
|
onTrackCleared(null);
|
||||||
|
|||||||
@@ -17,15 +17,15 @@ export class PeerManagerService {
|
|||||||
mediaType = $state<"voice" | "screen">("voice");
|
mediaType = $state<"voice" | "screen">("voice");
|
||||||
isDeafened = $state(false);
|
isDeafened = $state(false);
|
||||||
targetBitrate = $state<number>(5000000);
|
targetBitrate = $state<number>(5000000);
|
||||||
onNegotiationNeeded: (peerIdHex: string, pc: RTCPeerConnection) => void;
|
onNegotiationNeeded: (_peerIdHex: string, _pc: RTCPeerConnection) => void;
|
||||||
onIceCandidate: (peerIdHex: string, candidate: RTCIceCandidate) => void;
|
onIceCandidate: (_peerIdHex: string, _candidate: RTCIceCandidate) => void;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
identity: Identity | null,
|
identity: Identity | null,
|
||||||
mediaType: "voice" | "screen",
|
mediaType: "voice" | "screen",
|
||||||
isDeafened: boolean,
|
isDeafened: boolean,
|
||||||
onNegotiationNeeded: (peerIdHex: string, pc: RTCPeerConnection) => void,
|
onNegotiationNeeded: (_peerIdHex: string, _pc: RTCPeerConnection) => void,
|
||||||
onIceCandidate: (peerIdHex: string, candidate: RTCIceCandidate) => void,
|
onIceCandidate: (_peerIdHex: string, _candidate: RTCIceCandidate) => void,
|
||||||
) {
|
) {
|
||||||
this.identity = identity;
|
this.identity = identity;
|
||||||
this.mediaType = mediaType;
|
this.mediaType = mediaType;
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export class ScreenSharingWebRTCService {
|
|||||||
const videoTrack = this.localScreenStream?.getVideoTracks()[0] || null;
|
const videoTrack = this.localScreenStream?.getVideoTracks()[0] || null;
|
||||||
const audioTrack = this.localScreenStream?.getAudioTracks()[0] || null;
|
const audioTrack = this.localScreenStream?.getAudioTracks()[0] || null;
|
||||||
// Accessing peers and peerStatuses to trigger effect on changes
|
// Accessing peers and peerStatuses to trigger effect on changes
|
||||||
const _statuses = this.peerManager.peerStatuses;
|
void this.peerManager.peerStatuses;
|
||||||
this.peerManager.peers.forEach(async (peer, peerIdHex) => {
|
this.peerManager.peers.forEach(async (peer, peerIdHex) => {
|
||||||
const transceivers = peer.pc.getTransceivers();
|
const transceivers = peer.pc.getTransceivers();
|
||||||
let changed = false;
|
let changed = false;
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { Identity } from "spacetimedb";
|
|
||||||
|
|
||||||
export interface Peer {
|
export interface Peer {
|
||||||
pc: RTCPeerConnection;
|
pc: RTCPeerConnection;
|
||||||
audio?: HTMLAudioElement;
|
audio?: HTMLAudioElement;
|
||||||
@@ -33,8 +31,12 @@ export interface LocalMediaState {
|
|||||||
isSharingScreen: boolean;
|
isSharingScreen: boolean;
|
||||||
toggleMute: () => void;
|
toggleMute: () => void;
|
||||||
toggleDeafen: () => void;
|
toggleDeafen: () => void;
|
||||||
startScreenShare: (peerManager: any) => Promise<void>;
|
startScreenShare: (
|
||||||
stopScreenShare: (peerManager: any) => void;
|
onTrackReady: (_track: MediaStreamTrack) => void,
|
||||||
|
) => Promise<void>;
|
||||||
|
stopScreenShare: (
|
||||||
|
onTrackCleared: (_track: MediaStreamTrack | null) => void,
|
||||||
|
) => void;
|
||||||
requestMic: () => Promise<void>;
|
requestMic: () => Promise<void>;
|
||||||
releaseMic: () => void;
|
releaseMic: () => void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,8 @@ export class WebRTCService {
|
|||||||
this.screen.identity = this.identity;
|
this.screen.identity = this.identity;
|
||||||
this.screen.connectedChannelId = this.connectedChannelId;
|
this.screen.connectedChannelId = this.connectedChannelId;
|
||||||
this.screen.localScreenStream = this.localMedia.localScreenStream;
|
this.screen.localScreenStream = this.localMedia.localScreenStream;
|
||||||
this.screen.peerManager.targetBitrate = parseFloat(this.localMedia.screenShareBitrate) * 1000000;
|
this.screen.peerManager.targetBitrate =
|
||||||
|
parseFloat(this.localMedia.screenShareBitrate) * 1000000;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sync mute/deafen to DB
|
// Sync mute/deafen to DB
|
||||||
|
|||||||
+2
-2
@@ -113,8 +113,8 @@ export const optimizeEmoji = async (
|
|||||||
|
|
||||||
// Draw image centered and cropped to square
|
// Draw image centered and cropped to square
|
||||||
const scale = Math.max(DIM / img.width, DIM / img.height);
|
const scale = Math.max(DIM / img.width, DIM / img.height);
|
||||||
const x = (DIM / 2) - (img.width / 2) * scale;
|
const x = DIM / 2 - (img.width / 2) * scale;
|
||||||
const y = (DIM / 2) - (img.height / 2) * scale;
|
const y = DIM / 2 - (img.height / 2) * scale;
|
||||||
ctx.drawImage(img, x, y, img.width * scale, img.height * scale);
|
ctx.drawImage(img, x, y, img.width * scale, img.height * scale);
|
||||||
|
|
||||||
canvas.toBlob(
|
canvas.toBlob(
|
||||||
|
|||||||
+13
-6
@@ -7,8 +7,8 @@ export { HOST_KEY, DB_NAME_KEY, getEnv };
|
|||||||
const normalizeHost = (host: string) => {
|
const normalizeHost = (host: string) => {
|
||||||
try {
|
try {
|
||||||
const url = new URL(host.includes("://") ? host : `https://${host}`);
|
const url = new URL(host.includes("://") ? host : `https://${host}`);
|
||||||
return url.origin.replace(/^http/, 'ws'); // Ensure ws/wss
|
return url.origin.replace(/^http/, "ws"); // Ensure ws/wss
|
||||||
} catch (e) {
|
} catch {
|
||||||
return host.trim().replace(/\/+$/, ""); // Fallback
|
return host.trim().replace(/\/+$/, ""); // Fallback
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -25,11 +25,15 @@ export const TokenStore = {
|
|||||||
clear: (host: string, dbName: string) => {
|
clear: (host: string, dbName: string) => {
|
||||||
const key = `${normalizeHost(host)}/${dbName}/auth_token`;
|
const key = `${normalizeHost(host)}/${dbName}/auth_token`;
|
||||||
localStorage.removeItem(key);
|
localStorage.removeItem(key);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getStdbHost = () => localStorage.getItem(HOST_KEY) || getEnv("VITE_SPACETIMEDB_HOST", "https://maincloud.spacetimedb.com");
|
export const getStdbHost = () =>
|
||||||
export const getStdbDbName = () => localStorage.getItem(DB_NAME_KEY) || getEnv("VITE_SPACETIMEDB_DB_NAME", "ditchcord");
|
localStorage.getItem(HOST_KEY) ||
|
||||||
|
getEnv("VITE_SPACETIMEDB_HOST", "https://maincloud.spacetimedb.com");
|
||||||
|
export const getStdbDbName = () =>
|
||||||
|
localStorage.getItem(DB_NAME_KEY) ||
|
||||||
|
getEnv("VITE_SPACETIMEDB_DB_NAME", "ditchcord");
|
||||||
|
|
||||||
let _connection: DbConnection | null = null;
|
let _connection: DbConnection | null = null;
|
||||||
export const getConnection = () => _connection;
|
export const getConnection = () => _connection;
|
||||||
@@ -89,7 +93,10 @@ class ConnectionManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const connectionBuilder = (onReconnectTrigger: () => void, oidcToken?: string) => {
|
export const connectionBuilder = (
|
||||||
|
onReconnectTrigger: () => void,
|
||||||
|
oidcToken?: string,
|
||||||
|
) => {
|
||||||
const host = getStdbHost();
|
const host = getStdbHost();
|
||||||
const dbName = getStdbDbName();
|
const dbName = getStdbDbName();
|
||||||
|
|
||||||
|
|||||||
+7
-4
@@ -1,12 +1,15 @@
|
|||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||||
import basicSsl from '@vitejs/plugin-basic-ssl';
|
import basicSsl from "@vitejs/plugin-basic-ssl";
|
||||||
|
|
||||||
|
import { cloudflare } from "@cloudflare/vite-plugin";
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
process.env.VITE_USE_SSL === 'true' ? basicSsl() : [],
|
process.env.VITE_USE_SSL === "true" ? basicSsl() : [],
|
||||||
svelte(),
|
svelte(),
|
||||||
|
cloudflare()
|
||||||
],
|
],
|
||||||
// Prevent vite from obscuring rust errors
|
// Prevent vite from obscuring rust errors
|
||||||
clearScreen: false,
|
clearScreen: false,
|
||||||
@@ -14,7 +17,7 @@ export default defineConfig({
|
|||||||
// Tauri expects a fixed port, fail if that port is not available
|
// Tauri expects a fixed port, fail if that port is not available
|
||||||
server: {
|
server: {
|
||||||
strictPort: true,
|
strictPort: true,
|
||||||
port: process.env.VITE_USE_SSL === 'true' ? 5174 : 5173,
|
port: process.env.VITE_USE_SSL === "true" ? 5174 : 5173,
|
||||||
host: "0.0.0.0",
|
host: "0.0.0.0",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user