diff --git a/Cargo.lock b/Cargo.lock index ad2d843..2b54f02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1458,7 +1458,6 @@ dependencies = [ "chat", "chat_ui", "common", - "core-text", "device", "futures", "gpui", @@ -1467,7 +1466,6 @@ dependencies = [ "gpui_platform", "gpui_tokio", "gpui_windows", - "indexset", "itertools 0.13.0", "log", "nostr-connect", @@ -1486,6 +1484,7 @@ dependencies = [ "tracing-subscriber", "ui", "webbrowser", + "workspace", ] [[package]] @@ -1523,6 +1522,7 @@ dependencies = [ "ui", "wasm-bindgen", "webbrowser", + "workspace", ] [[package]] @@ -9088,6 +9088,38 @@ version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +[[package]] +name = "workspace" +version = "1.0.0-beta5" +dependencies = [ + "anyhow", + "auto_update", + "chat", + "chat_ui", + "common", + "device", + "futures", + "gpui", + "indexset", + "itertools 0.13.0", + "log", + "nostr-connect", + "nostr-sdk", + "oneshot", + "person", + "relay_auth", + "serde", + "serde_json", + "settings", + "smallvec", + "smol", + "state", + "theme", + "tracing-subscriber", + "ui", + "webbrowser", +] + [[package]] name = "writeable" version = "0.6.3" diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml new file mode 100644 index 0000000..668a930 --- /dev/null +++ b/crates/workspace/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "workspace" +version.workspace = true +edition.workspace = true +publish.workspace = true + +[dependencies] +ui = { path = "../ui" } +theme = { path = "../theme" } +common = { path = "../common" } +state = { path = "../state" } +device = { path = "../device" } +chat = { path = "../chat" } +chat_ui = { path = "../chat_ui" } +settings = { path = "../settings" } +auto_update = { path = "../auto_update" } +person = { path = "../person" } +relay_auth = { path = "../relay_auth" } + +gpui.workspace = true +nostr-connect.workspace = true +nostr-sdk.workspace = true +anyhow.workspace = true +serde.workspace = true +serde_json.workspace = true +itertools.workspace = true +log.workspace = true +smallvec.workspace = true +smol.workspace = true +futures.workspace = true +oneshot.workspace = true +webbrowser.workspace = true +tracing-subscriber.workspace = true + +indexset = "0.12.3" diff --git a/desktop/src/dialogs/import.rs b/crates/workspace/src/dialogs/import.rs similarity index 100% rename from desktop/src/dialogs/import.rs rename to crates/workspace/src/dialogs/import.rs diff --git a/desktop/src/dialogs/mod.rs b/crates/workspace/src/dialogs/mod.rs similarity index 100% rename from desktop/src/dialogs/mod.rs rename to crates/workspace/src/dialogs/mod.rs diff --git a/desktop/src/dialogs/restore.rs b/crates/workspace/src/dialogs/restore.rs similarity index 100% rename from desktop/src/dialogs/restore.rs rename to crates/workspace/src/dialogs/restore.rs diff --git a/desktop/src/dialogs/screening.rs b/crates/workspace/src/dialogs/screening.rs similarity index 100% rename from desktop/src/dialogs/screening.rs rename to crates/workspace/src/dialogs/screening.rs diff --git a/desktop/src/dialogs/settings.rs b/crates/workspace/src/dialogs/settings.rs similarity index 100% rename from desktop/src/dialogs/settings.rs rename to crates/workspace/src/dialogs/settings.rs diff --git a/desktop/src/workspace.rs b/crates/workspace/src/lib.rs similarity index 99% rename from desktop/src/workspace.rs rename to crates/workspace/src/lib.rs index d33eefa..fa2934f 100644 --- a/desktop/src/workspace.rs +++ b/crates/workspace/src/lib.rs @@ -27,7 +27,10 @@ use crate::dialogs::import::ImportIdentity; use crate::dialogs::restore::RestoreEncryption; use crate::dialogs::settings; use crate::panels::{backup, contact_list, greeter, messaging_relays, profile, relay_list, trash}; -use crate::sidebar; + +mod dialogs; +mod panels; +mod sidebar; pub fn init(window: &mut Window, cx: &mut App) -> Entity { cx.new(|cx| Workspace::new(window, cx)) diff --git a/desktop/src/panels/backup.rs b/crates/workspace/src/panels/backup.rs similarity index 100% rename from desktop/src/panels/backup.rs rename to crates/workspace/src/panels/backup.rs diff --git a/desktop/src/panels/contact_list.rs b/crates/workspace/src/panels/contact_list.rs similarity index 100% rename from desktop/src/panels/contact_list.rs rename to crates/workspace/src/panels/contact_list.rs diff --git a/desktop/src/panels/greeter.rs b/crates/workspace/src/panels/greeter.rs similarity index 99% rename from desktop/src/panels/greeter.rs rename to crates/workspace/src/panels/greeter.rs index 07e973c..c56e8d1 100644 --- a/desktop/src/panels/greeter.rs +++ b/crates/workspace/src/panels/greeter.rs @@ -8,8 +8,8 @@ use ui::button::{Button, ButtonVariants}; use ui::dock::{DockPlacement, Panel, PanelEvent}; use ui::{Icon, IconName, Sizable, StyledExt, h_flex, v_flex}; +use crate::Workspace; use crate::panels::profile; -use crate::workspace::Workspace; pub fn init(window: &mut Window, cx: &mut App) -> Entity { cx.new(|cx| GreeterPanel::new(window, cx)) diff --git a/desktop/src/panels/messaging_relays.rs b/crates/workspace/src/panels/messaging_relays.rs similarity index 100% rename from desktop/src/panels/messaging_relays.rs rename to crates/workspace/src/panels/messaging_relays.rs diff --git a/desktop/src/panels/mod.rs b/crates/workspace/src/panels/mod.rs similarity index 100% rename from desktop/src/panels/mod.rs rename to crates/workspace/src/panels/mod.rs diff --git a/desktop/src/panels/profile.rs b/crates/workspace/src/panels/profile.rs similarity index 100% rename from desktop/src/panels/profile.rs rename to crates/workspace/src/panels/profile.rs diff --git a/desktop/src/panels/relay_list.rs b/crates/workspace/src/panels/relay_list.rs similarity index 100% rename from desktop/src/panels/relay_list.rs rename to crates/workspace/src/panels/relay_list.rs diff --git a/desktop/src/panels/trash.rs b/crates/workspace/src/panels/trash.rs similarity index 100% rename from desktop/src/panels/trash.rs rename to crates/workspace/src/panels/trash.rs diff --git a/desktop/src/sidebar/entry.rs b/crates/workspace/src/sidebar/entry.rs similarity index 100% rename from desktop/src/sidebar/entry.rs rename to crates/workspace/src/sidebar/entry.rs diff --git a/desktop/src/sidebar/mod.rs b/crates/workspace/src/sidebar/mod.rs similarity index 100% rename from desktop/src/sidebar/mod.rs rename to crates/workspace/src/sidebar/mod.rs diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 5b1384c..bf12d2a 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -28,6 +28,7 @@ icons = [ [dependencies] assets = { path = "../crates/assets" } +workspace = { path = "../crates/workspace" } ui = { path = "../crates/ui" } theme = { path = "../crates/theme" } common = { path = "../crates/common" } @@ -62,9 +63,3 @@ futures.workspace = true oneshot.workspace = true webbrowser.workspace = true tracing-subscriber.workspace = true - -indexset = "0.12.3" - -[target.'cfg(target_os = "macos")'.dependencies] -# Temporary workaround https://github.com/zed-industries/zed/issues/47168 -core-text = "=21.0.0" diff --git a/desktop/src/main.rs b/desktop/src/main.rs index b7d1edd..6fe518d 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -10,11 +10,6 @@ use gpui_platform::application; use state::{APP_ID, CLIENT_NAME}; use ui::Root; -mod dialogs; -mod panels; -mod sidebar; -mod workspace; - actions!(coop, [Quit]); fn main() { diff --git a/web/Cargo.toml b/web/Cargo.toml index 6ebc020..5d45141 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true publish.workspace = true [dependencies] +workspace = { path = "../crates/workspace" } assets = { path = "../crates/assets" } ui = { path = "../crates/ui" } theme = { path = "../crates/theme" } diff --git a/web/Makefile b/web/Makefile new file mode 100644 index 0000000..0b2a314 --- /dev/null +++ b/web/Makefile @@ -0,0 +1,46 @@ +.PHONY: help dev build-wasm build-web build clean install + +help: ## Show help information + @echo "Coop - Available commands:" + @echo "" + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}' + +install: ## Install all dependencies + @echo "Checking Rust WASM target..." + @rustup target add wasm32-unknown-unknown || true + @echo "Checking wasm-bindgen-cli..." + @cargo install wasm-bindgen-cli || true + @echo "Installing frontend dependencies..." + @cd www && deno install + +build-wasm: ## Build WASM (release mode) + @./script/build-wasm.sh --release + +build-wasm-dev: ## Build WASM (debug mode) + @./script/build-wasm.sh + +build-web: ## Build frontend + @cd www && deno task build + +build-web-prod: ## Build frontend for production (GitHub Pages) + @cd www && deno install && NODE_ENV=production deno task build + +build: build-wasm build-web ## Build complete project (WASM + frontend) + +build-prod: build-wasm build-web-prod ## Build complete project for production + +dev: build-wasm-dev ## Start development server + @cd www && deno install && deno task dev + +preview: ## Preview production build + @cd www && deno task preview + +clean: ## Clean build artifacts + @echo "Cleaning build artifacts..." + @rm -rf www/dist + @rm -rf www/src/wasm/*.js www/src/wasm/*.wasm + @cargo clean + +watch-wasm: ## Watch Rust code changes and auto-rebuild WASM + @echo "Watching WASM changes..." + @cargo watch -x 'build --target wasm32-unknown-unknown' -s './script/build-wasm.sh' diff --git a/web/script/build-wasm.sh b/web/script/build-wasm.sh new file mode 100755 index 0000000..7ef3e53 --- /dev/null +++ b/web/script/build-wasm.sh @@ -0,0 +1,56 @@ +#!/bin/bash +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo -e "${GREEN}Building Coop Web...${NC}" + +# Get the script directory +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +PROJECT_ROOT="$SCRIPT_DIR/.." + +# Parse arguments +RELEASE_FLAG="" +if [[ "$1" == "--release" ]]; then + RELEASE_FLAG="--release" + echo -e "${YELLOW}Building in release mode${NC}" +fi + +# Step 1: Build WASM +echo -e "${GREEN}Step 1: Building WASM...${NC}" +cd "$PROJECT_ROOT" +cargo build --target wasm32-unknown-unknown $RELEASE_FLAG + +# Determine the build directory +if [[ "$RELEASE_FLAG" == "--release" ]]; then + BUILD_MODE="release" +else + BUILD_MODE="debug" +fi + +# WASM file is in workspace target directory +WORKSPACE_ROOT="$PROJECT_ROOT/../.." +WASM_PATH="$WORKSPACE_ROOT/target/wasm32-unknown-unknown/$BUILD_MODE/coop_web.wasm" + +# Check if WASM file exists +if [[ ! -f "$WASM_PATH" ]]; then + echo -e "${RED}Error: WASM file not found at: $WASM_PATH${NC}" + exit 1 +fi + +# Step 2: Generate JavaScript bindings +echo -e "${GREEN}Step 2: Generating JavaScript bindings...${NC}" +wasm-bindgen "$WASM_PATH" \ + --out-dir "$PROJECT_ROOT/www/src/wasm" \ + --target web \ + --no-typescript + +echo -e "${GREEN}✓ Build completed successfully!${NC}" +echo -e "${YELLOW}Next steps:${NC}" +echo -e " cd www" +echo -e " deno install" +echo -e " deno run dev" diff --git a/web/src/lib.rs b/web/src/lib.rs index 8c36aa8..1eb55a8 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -1,4 +1,5 @@ use gpui::*; +use ui::Root; use wasm_bindgen::prelude::*; #[wasm_bindgen] @@ -20,7 +21,47 @@ pub fn run() -> Result<(), JsValue> { #[cfg(target_family = "wasm")] let app = gpui_platform::single_threaded_web(); - app.run(|_cx| {}); + app.run(|cx| { + cx.open_window(WindowOptions::default(), |window, cx| { + // Bring the app to the foreground + cx.activate(true); + + cx.new(|cx| { + // Initialize the tokio runtime + gpui_tokio::init(cx); + + // Initialize components + ui::init(cx); + + // Initialize theme registry + theme::init(cx); + + // Initialize settings + settings::init(window, cx); + + // Initialize the nostr client + state::init(window, cx); + + // Initialize person registry + person::init(window, cx); + + // Initialize relay auth registry + relay_auth::init(window, cx); + + // Initialize device signer + // + // NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md + device::init(window, cx); + + // Initialize app registry + chat::init(window, cx); + + // Root Entity + Root::new(workspace::init(window, cx).into(), window, cx) + }) + }) + .expect("Failed to open window. Please restart the application."); + }); Ok(()) } diff --git a/web/src/www/index.html b/web/src/www/index.html index 40ab364..ac03b2d 100644 --- a/web/src/www/index.html +++ b/web/src/www/index.html @@ -72,6 +72,6 @@

Loading Coop...

- + diff --git a/web/src/www/main.js b/web/src/www/main.js new file mode 100644 index 0000000..48d644c --- /dev/null +++ b/web/src/www/main.js @@ -0,0 +1,35 @@ +async function init() { + const loadingEl = document.getElementById('loading'); + const appEl = document.getElementById('app'); + + try { + // Import the WASM module + const wasm = await import('./wasm/coop_web.js'); + await wasm.default(); + + // Initialize the story gallery + await wasm.run(); + + // Hide loading indicator + if (appEl) { + appEl.remove(); + } + } catch (error) { + console.error('Failed to initialize:', error); + + // Show error message + if (loadingEl) { + loadingEl.innerHTML = ` +
+

Failed to load the application

+

${error.message || error}

+

+ Please check the console for more details. +

+
+ `; + } + } +} + +init(); diff --git a/web/src/www/package.json b/web/src/www/package.json new file mode 100644 index 0000000..197cb94 --- /dev/null +++ b/web/src/www/package.json @@ -0,0 +1,17 @@ +{ + "name": "coop-web", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "wasm": "cargo build --target wasm32-unknown-unknown --release && wasm-bindgen ../../../target/wasm32-unknown-unknown/release/gpui_component_story_web.wasm --out-dir ./src/wasm --target web --no-typescript" + }, + "devDependencies": { + "vite": "^8", + "vite-plugin-static-copy": "^3.2.0", + "vite-plugin-wasm": "^3.3.0" + } +} diff --git a/web/src/www/vite.config.js b/web/src/www/vite.config.js new file mode 100644 index 0000000..34f3075 --- /dev/null +++ b/web/src/www/vite.config.js @@ -0,0 +1,75 @@ +import { defineConfig } from "vite"; +import wasm from "vite-plugin-wasm"; +import { viteStaticCopy } from "vite-plugin-static-copy"; +import path from "path"; +import { fileURLToPath } from "url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +export default defineConfig({ + plugins: [ + wasm(), + viteStaticCopy({ + targets: [ + { + src: path.resolve(__dirname, "../../../assets/icons"), + dest: "assets", + }, + ], + }), + { + name: "serve-assets", + configureServer(server) { + server.middlewares.use( + "/coop/assets", + (req, res, next) => { + const assetsPath = path.resolve(__dirname, "../../../assets"); + const filePath = path.join( + assetsPath, + req.url.replace("/assets", ""), + ); + + // Try to serve the file + import("fs").then(({ default: fs }) => { + if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) { + res.setHeader("Access-Control-Allow-Origin", "*"); + if (filePath.endsWith(".svg")) { + res.setHeader("Content-Type", "image/svg+xml"); + } + fs.createReadStream(filePath).pipe(res); + } else { + next(); + } + }); + }, + ); + }, + }, + ], + build: { + target: "esnext", + minify: true, + sourcemap: false, + rollupOptions: { + output: { + manualChunks: undefined, + }, + }, + }, + server: { + port: 3000, + open: true, + fs: { + strict: false, + allow: [".."], + }, + headers: { + "Cross-Origin-Embedder-Policy": "require-corp", + "Cross-Origin-Opener-Policy": "same-origin", + }, + }, + optimizeDeps: { + exclude: ["./src/wasm"], + }, + base: "/", +});