tauri
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
"run": "pnpm run dev"
|
||||
},
|
||||
"server": "maincloud",
|
||||
"database": "my-spacetime-app-jdhdg",
|
||||
"database": "ditchcord",
|
||||
"module-path": "./spacetimedb"
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"database": "my-spacetime-app-jdhdg"
|
||||
"database": "ditchcord"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "my-spacetime-app",
|
||||
"name": "ditchcord",
|
||||
"license": "ISC",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -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"
|
||||
@@ -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>
|
||||
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "enables the default permissions",
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 49 KiB |
@@ -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");
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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")!,
|
||||
});
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
});
|
||||
|
||||