This commit is contained in:
2026-04-04 15:29:23 -04:00
parent 4eec1e0e09
commit 2f1cdca594
36 changed files with 6732 additions and 2161 deletions
+6
View File
@@ -45,6 +45,12 @@ spacetimedb/dist/
# SpacetimeDB generated module bindings
src/module_bindings/**
# Tauri & Rust
src-tauri/target/
src-tauri/gen/
# Rust's Cargo.lock is often committed in binary/app projects,
# but target/ is always ignored.
# Ignore this file
.gitignore
+3 -3
View File
@@ -4,11 +4,11 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Svelte + TS</title>
<title>Ditchcord</title>
</head>
<body>
<div id="root"></div>
<script src="/config.js"></script>
<script type="module" src="/src/main.ts"></script>
<script src="config.js"></script>
<script type="module" src="src/main.ts"></script>
</body>
</html>
+2 -1
View File
@@ -1,5 +1,5 @@
{
"name": "my-spacetime-app",
"name": "ditchcord",
"private": true,
"version": "0.0.1",
"type": "module",
@@ -27,6 +27,7 @@
},
"devDependencies": {
"@eslint/js": "^9.17.0",
"@tauri-apps/cli": "^2.10.1",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/user-event": "^14.6.1",
"@types/node": "^25.5.0",
+1208 -2111
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -3,6 +3,6 @@
"run": "pnpm run dev"
},
"server": "maincloud",
"database": "my-spacetime-app-jdhdg",
"database": "ditchcord",
"module-path": "./spacetimedb"
}
+1 -1
View File
@@ -1,3 +1,3 @@
{
"database": "my-spacetime-app-jdhdg"
"database": "ditchcord"
}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "my-spacetime-app",
"name": "ditchcord",
"license": "ISC",
"type": "module",
"scripts": {
+5274
View File
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
[package]
name = "ditchcord"
version = "0.1.0"
description = "ditchcord - self-hosted communication"
authors = ["Adam Lamers"]
license = ""
repository = ""
edition = "2021"
rust-version = "1.77.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "ditchcord_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2.5.6", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
log = "0.4"
tauri = { version = "2.10.3", features = [] }
tauri-plugin-log = "2"
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSMicrophoneUsageDescription</key>
<string>Request microphone access for voice chat</string>
<key>NSCameraUsageDescription</key>
<string>Request camera access for video chat</string>
<key>NSScreenCaptureUsageDescription</key>
<string>Request screen capture access for screen sharing</string>
<key>NSLocalNetworkUsageDescription</key>
<string>Request local network access for WebRTC peer-to-peer connectivity</string>
</dict>
</plist>
+3
View File
@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}
+11
View File
@@ -0,0 +1,11 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "enables the default permissions",
"windows": [
"main"
],
"permissions": [
"core:default"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

+16
View File
@@ -0,0 +1,16 @@
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.setup(|app| {
if cfg!(debug_assertions) {
app.handle().plugin(
tauri_plugin_log::Builder::default()
.level(log::LevelFilter::Info)
.build(),
)?;
}
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
+6
View File
@@ -0,0 +1,6 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
ditchcord_lib::run();
}
+37
View File
@@ -0,0 +1,37 @@
{
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "ditchcord",
"version": "0.1.0",
"identifier": "com.ditchcord.chat",
"build": {
"frontendDist": "../dist",
"devUrl": "http://localhost:5173",
"beforeDevCommand": "pnpm dev",
"beforeBuildCommand": "pnpm build"
},
"app": {
"windows": [
{
"title": "Ditchcord",
"width": 800,
"height": 600,
"resizable": true,
"fullscreen": false
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
}
}
+1 -1
View File
@@ -145,7 +145,7 @@
id="stdb-db"
type="text"
bind:value={stdbDbName}
placeholder="my-spacetime-app-jdhdg"
placeholder="ditchcord"
style="background-color: var(--background-tertiary); color: var(--text-normal); border: 1px solid var(--background-modifier-accent); border-radius: 4px; padding: 10px;"
/>
</div>
+106 -38
View File
@@ -77,19 +77,38 @@
{ id: "voice", name: "Voice & Video", icon: "fas fa-microphone" },
];
async function handleCustomEmojiUpload(e: Event) {
const file = (e.target as HTMLInputElement).files?.[0];
if (!file) return;
let newEmojiName = $state("");
let newEmojiFile = $state<File | null>(null);
let isEmojiUploading = $state(false);
let emojiError = $state<string | null>(null);
const name = prompt("Enter emoji name:", file.name.split('.')[0]) || "custom";
const category = "custom";
async function handleEmojiSubmit(e: SubmitEvent) {
e.preventDefault();
if (!newEmojiFile || !newEmojiName.trim()) return;
isEmojiUploading = true;
emojiError = null;
try {
const { data } = await optimizeEmoji(file);
await chat.uploadCustomEmoji(name, category, data);
const { data } = await optimizeEmoji(newEmojiFile);
await chat.uploadCustomEmoji(newEmojiName.trim(), "custom", data);
newEmojiName = "";
newEmojiFile = null;
} catch (err) {
console.error("Failed to upload custom emoji:", err);
alert("Failed to process emoji image.");
emojiError = "Failed to process emoji image.";
} finally {
isEmojiUploading = false;
}
}
function onEmojiFileChange(e: Event) {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) {
newEmojiFile = file;
if (!newEmojiName) {
newEmojiName = file.name.split('.')[0];
}
}
}
@@ -189,16 +208,49 @@
<p>Personalize your experience by uploading unique emojis. They'll be automatically optimized for the best performance.</p>
</div>
<div class="emoji-upload-form-box">
<form class="emoji-upload-form" onsubmit={handleEmojiSubmit}>
<div class="form-row">
<div class="form-group" style="flex: 1; margin-bottom: 0;">
<label for="emoji-name">Emoji Name</label>
<div class="input-wrapper">
<span class="emoji-prefix">:</span>
<input
id="emoji-name"
type="text"
bind:value={newEmojiName}
placeholder="emoji-name"
/>
<span class="emoji-suffix">:</span>
</div>
</div>
<div class="form-group" style="margin-bottom: 0;">
<label>Emoji Image</label>
<label class="custom-file-upload">
<i class="fas fa-image"></i>
<span class="file-name-text">{newEmojiFile ? newEmojiFile.name : "Select Image"}</span>
<input type="file" accept="image/*" onchange={onEmojiFileChange} style="display: none;" />
</label>
</div>
<button
type="submit"
class="btn-success"
style="align-self: flex-end; height: 38px; padding: 0 20px;"
disabled={!newEmojiFile || !newEmojiName.trim() || isEmojiUploading}
>
{isEmojiUploading ? "Uploading..." : "Upload"}
</button>
</div>
{#if emojiError}
<div class="form-error" style="color: var(--status-danger); font-size: 0.75rem; margin-top: 8px;">
<i class="fas fa-exclamation-circle"></i> {emojiError}
</div>
{/if}
</form>
</div>
<div class="emoji-management-container">
<div class="emoji-management-grid">
<label class="emoji-upload-card">
<div class="emoji-upload-icon">
<i class="fas fa-plus"></i>
</div>
<span>Upload</span>
<input type="file" accept="image/*" onchange={handleCustomEmojiUpload} style="display: none;" />
</label>
{#each chat.customEmojis as emoji}
<div class="emoji-item-card" title=":{emoji.name}:">
<div class="emoji-preview">
@@ -625,37 +677,53 @@
gap: 12px;
}
.emoji-upload-card {
.emoji-upload-form-box {
background-color: var(--background-secondary);
padding: 16px;
border-radius: 8px;
margin-bottom: 16px;
border: 1px solid var(--background-modifier-accent);
}
.form-row {
display: flex;
gap: 16px;
align-items: flex-start;
}
.emoji-prefix, .emoji-suffix {
padding: 0 8px;
color: var(--text-muted);
font-weight: bold;
}
.custom-file-upload {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
background-color: var(--background-tertiary);
border: 1px dashed var(--background-modifier-accent);
gap: 8px;
background-color: var(--background-primary);
color: var(--text-normal);
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
aspect-ratio: 1;
font-size: 0.9rem;
border: 1px solid var(--background-modifier-accent);
transition: background-color 0.1s;
height: 38px;
box-sizing: border-box;
max-width: 200px;
}
.emoji-upload-card:hover {
border-color: var(--brand);
.file-name-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.custom-file-upload:hover {
background-color: var(--background-modifier-hover);
}
.emoji-upload-icon {
font-size: 1.2rem;
color: var(--text-muted);
}
.emoji-upload-card span {
font-size: 0.65rem;
font-weight: 700;
color: var(--text-muted);
text-transform: uppercase;
}
.emoji-item-card {
display: flex;
flex-direction: column;
+1 -1
View File
@@ -41,7 +41,7 @@ export const TokenStore = {
};
export const getStdbHost = () => localStorage.getItem(HOST_KEY) || getEnv("VITE_SPACETIMEDB_HOST", "wss://maincloud.spacetimedb.com");
export const getStdbDbName = () => localStorage.getItem(DB_NAME_KEY) || getEnv("VITE_SPACETIMEDB_DB_NAME", "my-spacetime-app-jdhdg");
export const getStdbDbName = () => localStorage.getItem(DB_NAME_KEY) || getEnv("VITE_SPACETIMEDB_DB_NAME", "ditchcord");
let _connection: DbConnection | null = null;
export const getConnection = () => _connection;
+2
View File
@@ -4,6 +4,8 @@ import "@fortawesome/fontawesome-free/css/all.min.css";
import "./index.css";
import App from "./App.svelte";
console.log("Ditchcord: Starting app...");
const app = mount(App, {
target: document.getElementById("root")!,
});
+14 -3
View File
@@ -1,9 +1,20 @@
import { defineConfig } from "vite";
import { svelte } from "@sveltejs/vite-plugin-svelte";
import basicSsl from "@vitejs/plugin-basic-ssl";
import basicSsl from '@vitejs/plugin-basic-ssl';
// https://vite.dev/config/
export default defineConfig({
plugins: [svelte(), basicSsl()],
server: {},
plugins: [
// basicSsl(),
svelte(),
],
// Prevent vite from obscuring rust errors
clearScreen: false,
base: "./",
// Tauri expects a fixed port, fail if that port is not available
server: {
strictPort: true,
port: 5173,
host: "0.0.0.0",
},
});