Documents by default, cloud project rename, multi-select drag, new settings
This commit is contained in:
Generated
+185
-15
@@ -722,6 +722,19 @@ version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "core-graphics"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
|
||||
dependencies = [
|
||||
"bitflags 2.13.1",
|
||||
"core-foundation",
|
||||
"core-graphics-types",
|
||||
"foreign-types 0.5.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-graphics"
|
||||
version = "0.25.0"
|
||||
@@ -1074,6 +1087,28 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "drag"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e90b4a25ace5ce0561534b073943594cbcd21af936e64d09aec444568411f8c"
|
||||
dependencies = [
|
||||
"core-graphics 0.24.0",
|
||||
"dunce",
|
||||
"gdk",
|
||||
"gdkx11",
|
||||
"gtk",
|
||||
"log",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"raw-window-handle",
|
||||
"serde",
|
||||
"thiserror 2.0.18",
|
||||
"windows 0.52.0",
|
||||
"windows-core 0.58.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dtoa"
|
||||
version = "1.0.11"
|
||||
@@ -3044,9 +3079,17 @@ checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
|
||||
dependencies = [
|
||||
"bitflags 2.13.1",
|
||||
"block2",
|
||||
"libc",
|
||||
"objc2",
|
||||
"objc2-cloud-kit",
|
||||
"objc2-core-data",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-core-image",
|
||||
"objc2-core-text",
|
||||
"objc2-core-video",
|
||||
"objc2-foundation",
|
||||
"objc2-quartz-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3066,6 +3109,7 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa"
|
||||
dependencies = [
|
||||
"bitflags 2.13.1",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
@@ -3126,6 +3170,19 @@ dependencies = [
|
||||
"objc2-core-graphics",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-video"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6"
|
||||
dependencies = [
|
||||
"bitflags 2.13.1",
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-io-surface",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-encode"
|
||||
version = "4.1.0"
|
||||
@@ -4783,7 +4840,7 @@ dependencies = [
|
||||
"bitflags 2.13.1",
|
||||
"block2",
|
||||
"core-foundation",
|
||||
"core-graphics",
|
||||
"core-graphics 0.25.0",
|
||||
"crossbeam-channel",
|
||||
"dbus",
|
||||
"dispatch2",
|
||||
@@ -4808,7 +4865,7 @@ dependencies = [
|
||||
"tao-macros",
|
||||
"unicode-segmentation",
|
||||
"url",
|
||||
"windows",
|
||||
"windows 0.61.3",
|
||||
"windows-core 0.61.2",
|
||||
"windows-version",
|
||||
"x11-dl",
|
||||
@@ -4890,7 +4947,7 @@ dependencies = [
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
"window-vibrancy",
|
||||
"windows",
|
||||
"windows 0.61.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4989,6 +5046,21 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-drag"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "729ca0ce4b1169869d3405216d3c09a524f41ea5e2eec89f917cd6623f8a70ca"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"drag",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-fs"
|
||||
version = "2.5.1"
|
||||
@@ -5031,7 +5103,7 @@ dependencies = [
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.18",
|
||||
"url",
|
||||
"windows",
|
||||
"windows 0.61.3",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
@@ -5057,7 +5129,7 @@ dependencies = [
|
||||
"url",
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
"windows",
|
||||
"windows 0.61.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5082,7 +5154,7 @@ dependencies = [
|
||||
"url",
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
"windows",
|
||||
"windows 0.61.3",
|
||||
"wry",
|
||||
]
|
||||
|
||||
@@ -5616,6 +5688,7 @@ dependencies = [
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-drag",
|
||||
"tauri-plugin-opener",
|
||||
"typst",
|
||||
"typst-assets",
|
||||
@@ -6533,10 +6606,10 @@ checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a"
|
||||
dependencies = [
|
||||
"webview2-com-macros",
|
||||
"webview2-com-sys",
|
||||
"windows",
|
||||
"windows 0.61.3",
|
||||
"windows-core 0.61.2",
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-implement 0.60.2",
|
||||
"windows-interface 0.59.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6557,7 +6630,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c"
|
||||
dependencies = [
|
||||
"thiserror 2.0.18",
|
||||
"windows",
|
||||
"windows 0.61.3",
|
||||
"windows-core 0.61.2",
|
||||
]
|
||||
|
||||
@@ -6613,6 +6686,18 @@ dependencies = [
|
||||
"windows-version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
|
||||
dependencies = [
|
||||
"windows-core 0.52.0",
|
||||
"windows-implement 0.52.0",
|
||||
"windows-interface 0.52.0",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.61.3"
|
||||
@@ -6635,14 +6720,36 @@ dependencies = [
|
||||
"windows-core 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
|
||||
dependencies = [
|
||||
"windows-implement 0.58.0",
|
||||
"windows-interface 0.58.0",
|
||||
"windows-result 0.2.0",
|
||||
"windows-strings 0.1.0",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-implement 0.60.2",
|
||||
"windows-interface 0.59.3",
|
||||
"windows-link 0.1.3",
|
||||
"windows-result 0.3.4",
|
||||
"windows-strings 0.4.2",
|
||||
@@ -6654,8 +6761,8 @@ version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-implement 0.60.2",
|
||||
"windows-interface 0.59.3",
|
||||
"windows-link 0.2.1",
|
||||
"windows-result 0.4.1",
|
||||
"windows-strings 0.5.1",
|
||||
@@ -6672,6 +6779,28 @@ dependencies = [
|
||||
"windows-threading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12168c33176773b86799be25e2a2ba07c7aab9968b37541f1094dbd7a60c8946"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.2"
|
||||
@@ -6683,6 +6812,28 @@ dependencies = [
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d8dc32e0095a7eeccebd0e3f09e9509365ecb3fc6ac4d6f5f14a3f6392942d1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.3"
|
||||
@@ -6716,6 +6867,15 @@ dependencies = [
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
@@ -6734,6 +6894,16 @@ dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
|
||||
dependencies = [
|
||||
"windows-result 0.2.0",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.2"
|
||||
@@ -7104,7 +7274,7 @@ dependencies = [
|
||||
"webkit2gtk",
|
||||
"webkit2gtk-sys",
|
||||
"webview2-com",
|
||||
"windows",
|
||||
"windows 0.61.3",
|
||||
"windows-core 0.61.2",
|
||||
"windows-version",
|
||||
"x11-dl",
|
||||
|
||||
@@ -22,6 +22,7 @@ tauri-build = { version = "2", features = [] }
|
||||
tauri = { version = "2", features = [] }
|
||||
tauri-plugin-opener = "2"
|
||||
tauri-plugin-dialog = "2"
|
||||
tauri-plugin-drag = "2.1.1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
rusqlite = { version = "0.32", features = ["bundled"] }
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"opener:allow-reveal-item-in-dir",
|
||||
"dialog:default",
|
||||
"dialog:allow-open",
|
||||
"dialog:allow-save"
|
||||
"dialog:allow-save",
|
||||
"drag:default"
|
||||
]
|
||||
}
|
||||
|
||||
+14
-12
@@ -27,7 +27,7 @@ const SCHEMA: [&str; 5] = [
|
||||
"CREATE TABLE IF NOT EXISTS projects (
|
||||
path TEXT PRIMARY KEY,
|
||||
entrypoint TEXT NOT NULL DEFAULT 'main.typ',
|
||||
space_id TEXT,
|
||||
cloud_project_id TEXT,
|
||||
last_synced_at TEXT
|
||||
)",
|
||||
"CREATE TABLE IF NOT EXISTS base_files (
|
||||
@@ -53,8 +53,10 @@ const SCHEMA: [&str; 5] = [
|
||||
)",
|
||||
];
|
||||
|
||||
const MIGRATIONS: [&str; 1] =
|
||||
["ALTER TABLE document_links ADD COLUMN synced_at TEXT"];
|
||||
const MIGRATIONS: [&str; 2] = [
|
||||
"ALTER TABLE document_links ADD COLUMN synced_at TEXT",
|
||||
"ALTER TABLE projects RENAME COLUMN space_id TO cloud_project_id",
|
||||
];
|
||||
|
||||
impl Store {
|
||||
pub fn open(app: &AppHandle) -> Result<Self, String> {
|
||||
@@ -130,14 +132,14 @@ impl Store {
|
||||
let row: Option<(String, Option<String>, Option<String>)> = self.with(|connection| {
|
||||
connection
|
||||
.query_row(
|
||||
"SELECT entrypoint, space_id, last_synced_at FROM projects WHERE path = ?1",
|
||||
"SELECT entrypoint, cloud_project_id, last_synced_at FROM projects WHERE path = ?1",
|
||||
params![project],
|
||||
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
|
||||
)
|
||||
.optional()
|
||||
})?;
|
||||
|
||||
let Some((entrypoint, space_id, last_synced_at)) = row else {
|
||||
let Some((entrypoint, cloud_project_id, last_synced_at)) = row else {
|
||||
return Ok(ProjectMeta::default());
|
||||
};
|
||||
|
||||
@@ -158,7 +160,7 @@ impl Store {
|
||||
|
||||
Ok(ProjectMeta {
|
||||
entrypoint,
|
||||
space_id,
|
||||
cloud_project_id,
|
||||
last_synced_at,
|
||||
base_hashes,
|
||||
})
|
||||
@@ -180,16 +182,16 @@ impl Store {
|
||||
pub fn save_meta(&self, project: &str, meta: &ProjectMeta) -> Result<(), String> {
|
||||
self.with(|connection| {
|
||||
connection.execute(
|
||||
"INSERT INTO projects (path, entrypoint, space_id, last_synced_at)
|
||||
"INSERT INTO projects (path, entrypoint, cloud_project_id, last_synced_at)
|
||||
VALUES (?1, ?2, ?3, ?4)
|
||||
ON CONFLICT(path) DO UPDATE SET
|
||||
entrypoint = excluded.entrypoint,
|
||||
space_id = excluded.space_id,
|
||||
cloud_project_id = excluded.cloud_project_id,
|
||||
last_synced_at = excluded.last_synced_at",
|
||||
params![
|
||||
project,
|
||||
meta.entrypoint,
|
||||
meta.space_id,
|
||||
meta.cloud_project_id,
|
||||
meta.last_synced_at
|
||||
],
|
||||
)?;
|
||||
@@ -335,11 +337,11 @@ impl Store {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn all_space_links(&self) -> Result<Vec<(String, String, Option<String>)>, String> {
|
||||
pub fn all_cloud_project_links(&self) -> Result<Vec<(String, String, Option<String>)>, String> {
|
||||
self.with(|connection| {
|
||||
let mut statement = connection.prepare(
|
||||
"SELECT path, space_id, last_synced_at FROM projects
|
||||
WHERE space_id IS NOT NULL",
|
||||
"SELECT path, cloud_project_id, last_synced_at FROM projects
|
||||
WHERE cloud_project_id IS NOT NULL",
|
||||
)?;
|
||||
let rows = statement.query_map([], |row| {
|
||||
Ok((
|
||||
|
||||
+64
-30
@@ -16,7 +16,7 @@ use assets::Asset;
|
||||
use db::Store;
|
||||
use compiler::{CompileResult, Diagnostic};
|
||||
use lsp::{LspHandle, LspState};
|
||||
use sync::{Account, SpaceSummary, SyncReport};
|
||||
use sync::{Account, ProjectSummary, SyncReport};
|
||||
use workspace::{
|
||||
browse, is_project_dir, is_text_file, list_files, load_settings, project_file_path,
|
||||
read_target_files, resolve_target, save_settings, workspace_path, BrowseEntry,
|
||||
@@ -351,7 +351,7 @@ pub struct TargetInfo {
|
||||
pub entrypoint: String,
|
||||
pub standalone: bool,
|
||||
pub is_project: bool,
|
||||
pub space_id: Option<String>,
|
||||
pub cloud_project_id: Option<String>,
|
||||
pub files: Vec<FileEntry>,
|
||||
}
|
||||
|
||||
@@ -368,7 +368,7 @@ fn target_info(app: AppHandle, store: State<'_, Store>, path: String) -> Result<
|
||||
entrypoint: target.entrypoint.clone(),
|
||||
standalone: true,
|
||||
is_project: false,
|
||||
space_id: None,
|
||||
cloud_project_id: None,
|
||||
files: vec![FileEntry {
|
||||
path: target.entrypoint.clone(),
|
||||
name: target.entrypoint,
|
||||
@@ -385,7 +385,7 @@ fn target_info(app: AppHandle, store: State<'_, Store>, path: String) -> Result<
|
||||
entrypoint: target.entrypoint,
|
||||
standalone: false,
|
||||
is_project: is_project_dir(&target.root),
|
||||
space_id: meta.space_id,
|
||||
cloud_project_id: meta.cloud_project_id,
|
||||
files: list_files(&target.root)?,
|
||||
})
|
||||
}
|
||||
@@ -725,9 +725,9 @@ fn cloud_account(app: AppHandle, store: State<'_, Store>) -> Result<Option<Accou
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn cloud_list_spaces(app: AppHandle, store: State<'_, Store>) -> Result<Vec<SpaceSummary>, String> {
|
||||
fn cloud_list_projects(app: AppHandle, store: State<'_, Store>) -> Result<Vec<ProjectSummary>, String> {
|
||||
let (server_url, token) = cloud_credentials(&app, &store)?;
|
||||
sync::list_spaces(&server_url, &token)
|
||||
sync::list_cloud_projects(&server_url, &token)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -864,6 +864,30 @@ fn cloud_unlink_document(store: State<'_, Store>, path: String) -> Result<(), St
|
||||
store.forget_document_link(&path)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn cloud_create_document(
|
||||
app: AppHandle,
|
||||
store: State<'_, Store>,
|
||||
path: String,
|
||||
title: String,
|
||||
) -> Result<String, String> {
|
||||
let (server_url, token) = cloud_credentials(&app, &store)?;
|
||||
let full = workspace_path(&app, &store, &path)?;
|
||||
let content = std::fs::read_to_string(&full).map_err(|e| e.to_string())?;
|
||||
|
||||
let document = sync::create_document(&server_url, &token, title.trim(), &content)?;
|
||||
|
||||
store.save_document_link(
|
||||
&path,
|
||||
&document.id,
|
||||
&document.hash,
|
||||
&document.role,
|
||||
&document.content,
|
||||
)?;
|
||||
|
||||
Ok(document.id)
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct LinkedDocument {
|
||||
pub path: String,
|
||||
@@ -899,21 +923,21 @@ fn cloud_linked_documents(
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct LinkedSpace {
|
||||
pub struct LinkedProject {
|
||||
pub path: String,
|
||||
pub space_id: String,
|
||||
pub cloud_project_id: String,
|
||||
pub synced_at: Option<String>,
|
||||
pub sync_state: Option<String>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn cloud_linked_spaces(
|
||||
fn cloud_linked_projects(
|
||||
app: AppHandle,
|
||||
store: State<'_, Store>,
|
||||
) -> Result<Vec<LinkedSpace>, String> {
|
||||
) -> Result<Vec<LinkedProject>, String> {
|
||||
let mut linked = Vec::new();
|
||||
|
||||
for (path, space_id, synced_at) in store.all_space_links()? {
|
||||
for (path, cloud_project_id, synced_at) in store.all_cloud_project_links()? {
|
||||
let Ok(full) = workspace_path(&app, &store, &path) else {
|
||||
continue;
|
||||
};
|
||||
@@ -921,10 +945,10 @@ fn cloud_linked_spaces(
|
||||
continue;
|
||||
}
|
||||
|
||||
linked.push(LinkedSpace {
|
||||
linked.push(LinkedProject {
|
||||
sync_state: workspace::project_sync_state(&full, synced_at.as_deref()),
|
||||
path,
|
||||
space_id,
|
||||
cloud_project_id,
|
||||
synced_at,
|
||||
});
|
||||
}
|
||||
@@ -941,22 +965,22 @@ fn cloud_document_link(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn cloud_create_space(app: AppHandle, store: State<'_, Store>, name: String) -> Result<SpaceSummary, String> {
|
||||
fn cloud_create_project(app: AppHandle, store: State<'_, Store>, name: String) -> Result<ProjectSummary, String> {
|
||||
let (server_url, token) = cloud_credentials(&app, &store)?;
|
||||
sync::create_space(&server_url, &token, name.trim())
|
||||
sync::create_cloud_project(&server_url, &token, name.trim())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn cloud_delete_space(app: AppHandle, store: State<'_, Store>, space_id: String) -> Result<(), String> {
|
||||
fn cloud_delete_project(app: AppHandle, store: State<'_, Store>, cloud_project_id: String) -> Result<(), String> {
|
||||
let (server_url, token) = cloud_credentials(&app, &store)?;
|
||||
sync::delete_space(&server_url, &token, &space_id)
|
||||
sync::delete_cloud_project(&server_url, &token, &cloud_project_id)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn cloud_clone_space(
|
||||
fn cloud_clone_project(
|
||||
app: AppHandle,
|
||||
store: State<'_, Store>,
|
||||
space_id: String,
|
||||
cloud_project_id: String,
|
||||
project_name: String,
|
||||
) -> Result<SyncReport, String> {
|
||||
let (server_url, token) = cloud_credentials(&app, &store)?;
|
||||
@@ -965,7 +989,15 @@ fn cloud_clone_space(
|
||||
if dir.exists() {
|
||||
return Err(format!("A project named '{}' already exists", project_name));
|
||||
}
|
||||
sync::clone_space(&server_url, &token, &app, &store, &project, &dir, &space_id)
|
||||
sync::clone_cloud_project(
|
||||
&server_url,
|
||||
&token,
|
||||
&app,
|
||||
&store,
|
||||
&project,
|
||||
&dir,
|
||||
&cloud_project_id,
|
||||
)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -973,17 +1005,17 @@ fn cloud_link_project(
|
||||
app: AppHandle,
|
||||
store: State<'_, Store>,
|
||||
project: String,
|
||||
space_id: Option<String>,
|
||||
cloud_project_id: Option<String>,
|
||||
) -> Result<SyncReport, String> {
|
||||
let (server_url, token) = cloud_credentials(&app, &store)?;
|
||||
let (dir, mut meta) = load_project(&app, &store, &project)?;
|
||||
|
||||
let space_id = match space_id {
|
||||
let cloud_project_id = match cloud_project_id {
|
||||
Some(id) if !id.trim().is_empty() => id,
|
||||
_ => sync::create_space(&server_url, &token, &project)?.id,
|
||||
_ => sync::create_cloud_project(&server_url, &token, &project)?.id,
|
||||
};
|
||||
|
||||
meta.space_id = Some(space_id);
|
||||
meta.cloud_project_id = Some(cloud_project_id);
|
||||
meta.base_hashes.clear();
|
||||
store.save_meta(&project, &meta)?;
|
||||
|
||||
@@ -993,7 +1025,7 @@ fn cloud_link_project(
|
||||
#[tauri::command]
|
||||
fn cloud_unlink_project(app: AppHandle, store: State<'_, Store>, project: String) -> Result<(), String> {
|
||||
let (dir, mut meta) = load_project(&app, &store, &project)?;
|
||||
meta.space_id = None;
|
||||
meta.cloud_project_id = None;
|
||||
meta.base_hashes.clear();
|
||||
meta.last_synced_at = None;
|
||||
let _ = dir;
|
||||
@@ -1069,6 +1101,7 @@ pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_drag::init())
|
||||
.setup(|app| {
|
||||
let store = Store::open(&app.handle())?;
|
||||
app.manage(store);
|
||||
@@ -1119,7 +1152,7 @@ pub fn run() {
|
||||
cloud_login,
|
||||
cloud_logout,
|
||||
cloud_account,
|
||||
cloud_list_spaces,
|
||||
cloud_list_projects,
|
||||
cloud_list_folders,
|
||||
cloud_list_documents,
|
||||
cloud_list_shared,
|
||||
@@ -1130,11 +1163,12 @@ pub fn run() {
|
||||
cloud_resolve_document,
|
||||
cloud_document_link,
|
||||
cloud_linked_documents,
|
||||
cloud_linked_spaces,
|
||||
cloud_linked_projects,
|
||||
cloud_unlink_document,
|
||||
cloud_create_space,
|
||||
cloud_delete_space,
|
||||
cloud_clone_space,
|
||||
cloud_create_document,
|
||||
cloud_create_project,
|
||||
cloud_delete_project,
|
||||
cloud_clone_project,
|
||||
cloud_link_project,
|
||||
cloud_unlink_project,
|
||||
cloud_push,
|
||||
|
||||
+82
-37
@@ -87,7 +87,7 @@ pub fn me(server_url: &str, token: &str) -> Result<Account, String> {
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct SpaceSummary {
|
||||
pub struct ProjectSummary {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub entrypoint: String,
|
||||
@@ -95,29 +95,37 @@ pub struct SpaceSummary {
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
pub fn list_spaces(server_url: &str, token: &str) -> Result<Vec<SpaceSummary>, String> {
|
||||
pub fn list_cloud_projects(server_url: &str, token: &str) -> Result<Vec<ProjectSummary>, String> {
|
||||
agent()
|
||||
.get(&endpoint(server_url, "/spaces"))
|
||||
.get(&endpoint(server_url, "/projects"))
|
||||
.set("Authorization", &format!("Bearer {}", token))
|
||||
.call()
|
||||
.map_err(describe)?
|
||||
.into_json::<Vec<SpaceSummary>>()
|
||||
.into_json::<Vec<ProjectSummary>>()
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
pub fn create_space(server_url: &str, token: &str, name: &str) -> Result<SpaceSummary, String> {
|
||||
pub fn create_cloud_project(
|
||||
server_url: &str,
|
||||
token: &str,
|
||||
name: &str,
|
||||
) -> Result<ProjectSummary, String> {
|
||||
agent()
|
||||
.post(&endpoint(server_url, "/spaces"))
|
||||
.post(&endpoint(server_url, "/projects"))
|
||||
.set("Authorization", &format!("Bearer {}", token))
|
||||
.send_json(ureq::json!({ "name": name }))
|
||||
.map_err(describe)?
|
||||
.into_json::<SpaceSummary>()
|
||||
.into_json::<ProjectSummary>()
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
pub fn delete_space(server_url: &str, token: &str, space_id: &str) -> Result<(), String> {
|
||||
pub fn delete_cloud_project(
|
||||
server_url: &str,
|
||||
token: &str,
|
||||
cloud_project_id: &str,
|
||||
) -> Result<(), String> {
|
||||
agent()
|
||||
.delete(&endpoint(server_url, &format!("/spaces/{}", space_id)))
|
||||
.delete(&endpoint(server_url, &format!("/projects/{}", cloud_project_id)))
|
||||
.set("Authorization", &format!("Bearer {}", token))
|
||||
.call()
|
||||
.map_err(describe)?;
|
||||
@@ -131,7 +139,9 @@ pub struct ManifestEntry {
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SpaceManifest {
|
||||
pub struct ProjectManifest {
|
||||
pub project_id: String,
|
||||
pub name: String,
|
||||
pub entrypoint: String,
|
||||
pub files: Vec<ManifestEntry>,
|
||||
}
|
||||
@@ -139,17 +149,17 @@ pub struct SpaceManifest {
|
||||
pub fn get_manifest(
|
||||
server_url: &str,
|
||||
token: &str,
|
||||
space_id: &str,
|
||||
) -> Result<SpaceManifest, String> {
|
||||
cloud_project_id: &str,
|
||||
) -> Result<ProjectManifest, String> {
|
||||
agent()
|
||||
.get(&endpoint(
|
||||
server_url,
|
||||
&format!("/spaces/{}/manifest", space_id),
|
||||
&format!("/projects/{}/manifest", cloud_project_id),
|
||||
))
|
||||
.set("Authorization", &format!("Bearer {}", token))
|
||||
.call()
|
||||
.map_err(describe)?
|
||||
.into_json::<SpaceManifest>()
|
||||
.into_json::<ProjectManifest>()
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
@@ -176,11 +186,14 @@ impl FileContent {
|
||||
pub fn pull_file(
|
||||
server_url: &str,
|
||||
token: &str,
|
||||
space_id: &str,
|
||||
cloud_project_id: &str,
|
||||
path: &str,
|
||||
) -> Result<FileContent, String> {
|
||||
agent()
|
||||
.get(&endpoint(server_url, &format!("/spaces/{}/file", space_id)))
|
||||
.get(&endpoint(
|
||||
server_url,
|
||||
&format!("/projects/{}/file", cloud_project_id),
|
||||
))
|
||||
.query("path", path)
|
||||
.set("Authorization", &format!("Bearer {}", token))
|
||||
.call()
|
||||
@@ -204,7 +217,7 @@ pub enum PushResult {
|
||||
pub fn push_file(
|
||||
server_url: &str,
|
||||
token: &str,
|
||||
space_id: &str,
|
||||
cloud_project_id: &str,
|
||||
path: &str,
|
||||
bytes: &[u8],
|
||||
base_hash: Option<&str>,
|
||||
@@ -216,7 +229,10 @@ pub fn push_file(
|
||||
};
|
||||
|
||||
let response = agent()
|
||||
.put(&endpoint(server_url, &format!("/spaces/{}/file", space_id)))
|
||||
.put(&endpoint(
|
||||
server_url,
|
||||
&format!("/projects/{}/file", cloud_project_id),
|
||||
))
|
||||
.set("Authorization", &format!("Bearer {}", token))
|
||||
.send_json(ureq::json!({
|
||||
"path": path,
|
||||
@@ -248,11 +264,14 @@ pub fn push_file(
|
||||
pub fn delete_remote_file(
|
||||
server_url: &str,
|
||||
token: &str,
|
||||
space_id: &str,
|
||||
cloud_project_id: &str,
|
||||
path: &str,
|
||||
) -> Result<(), String> {
|
||||
let response = agent()
|
||||
.delete(&endpoint(server_url, &format!("/spaces/{}/file", space_id)))
|
||||
.delete(&endpoint(
|
||||
server_url,
|
||||
&format!("/projects/{}/file", cloud_project_id),
|
||||
))
|
||||
.query("path", path)
|
||||
.set("Authorization", &format!("Bearer {}", token))
|
||||
.call();
|
||||
@@ -306,12 +325,12 @@ pub fn pull_project(
|
||||
project_dir: &Path,
|
||||
meta: &mut ProjectMeta,
|
||||
) -> Result<SyncReport, String> {
|
||||
let space_id = meta
|
||||
.space_id
|
||||
let cloud_project_id = meta
|
||||
.cloud_project_id
|
||||
.clone()
|
||||
.ok_or("Project is not linked to a cloud space")?;
|
||||
.ok_or("Project is not linked to a cloud project")?;
|
||||
|
||||
let manifest = get_manifest(server_url, token, &space_id)?;
|
||||
let manifest = get_manifest(server_url, token, &cloud_project_id)?;
|
||||
let mut report = SyncReport::default();
|
||||
|
||||
let local_files: HashSet<String> = collect_files(project_dir)?.into_iter().collect();
|
||||
@@ -327,7 +346,7 @@ pub fn pull_project(
|
||||
if base.is_some() {
|
||||
continue;
|
||||
}
|
||||
let remote = pull_file(server_url, token, &space_id, &entry.path)?;
|
||||
let remote = pull_file(server_url, token, &cloud_project_id, &entry.path)?;
|
||||
write_local(project_dir, &entry.path, &remote.bytes()?)?;
|
||||
meta.base_hashes.insert(entry.path.clone(), remote.hash);
|
||||
report.pulled.push(entry.path.clone());
|
||||
@@ -346,7 +365,7 @@ pub fn pull_project(
|
||||
continue;
|
||||
}
|
||||
|
||||
let remote = pull_file(server_url, token, &space_id, &entry.path)?;
|
||||
let remote = pull_file(server_url, token, &cloud_project_id, &entry.path)?;
|
||||
let remote_bytes = remote.bytes()?;
|
||||
|
||||
if base.as_deref() == Some(local_hash.as_str()) {
|
||||
@@ -428,10 +447,10 @@ pub fn push_project(
|
||||
project_dir: &Path,
|
||||
meta: &mut ProjectMeta,
|
||||
) -> Result<SyncReport, String> {
|
||||
let space_id = meta
|
||||
.space_id
|
||||
let cloud_project_id = meta
|
||||
.cloud_project_id
|
||||
.clone()
|
||||
.ok_or("Project is not linked to a cloud space")?;
|
||||
.ok_or("Project is not linked to a cloud project")?;
|
||||
|
||||
let mut report = SyncReport::default();
|
||||
let local_files = collect_files(project_dir)?;
|
||||
@@ -446,7 +465,14 @@ pub fn push_project(
|
||||
continue;
|
||||
}
|
||||
|
||||
match push_file(server_url, token, &space_id, path, &bytes, base.as_deref())? {
|
||||
match push_file(
|
||||
server_url,
|
||||
token,
|
||||
&cloud_project_id,
|
||||
path,
|
||||
&bytes,
|
||||
base.as_deref(),
|
||||
)? {
|
||||
PushResult::Applied => {
|
||||
meta.base_hashes.insert(path.clone(), hash);
|
||||
report.pushed.push(path.clone());
|
||||
@@ -481,7 +507,7 @@ pub fn push_project(
|
||||
.collect();
|
||||
|
||||
for path in removed {
|
||||
delete_remote_file(server_url, token, &space_id, &path)?;
|
||||
delete_remote_file(server_url, token, &cloud_project_id, &path)?;
|
||||
meta.base_hashes.remove(&path);
|
||||
report.deleted_remote.push(path);
|
||||
}
|
||||
@@ -522,20 +548,20 @@ pub fn report_progress(
|
||||
);
|
||||
}
|
||||
|
||||
pub fn clone_space(
|
||||
pub fn clone_cloud_project(
|
||||
server_url: &str,
|
||||
token: &str,
|
||||
app: &tauri::AppHandle,
|
||||
store: &Store,
|
||||
project: &str,
|
||||
project_dir: &Path,
|
||||
space_id: &str,
|
||||
cloud_project_id: &str,
|
||||
) -> Result<SyncReport, String> {
|
||||
std::fs::create_dir_all(project_dir).map_err(|e| e.to_string())?;
|
||||
|
||||
let manifest = get_manifest(server_url, token, space_id)?;
|
||||
let manifest = get_manifest(server_url, token, cloud_project_id)?;
|
||||
let mut meta = store.meta(project)?;
|
||||
meta.space_id = Some(space_id.to_string());
|
||||
meta.cloud_project_id = Some(cloud_project_id.to_string());
|
||||
meta.entrypoint = manifest.entrypoint.clone();
|
||||
|
||||
let mut report = SyncReport::default();
|
||||
@@ -544,7 +570,7 @@ pub fn clone_space(
|
||||
for (index, entry) in manifest.files.iter().enumerate() {
|
||||
report_progress(app, project, index, total, false);
|
||||
|
||||
let remote = pull_file(server_url, token, space_id, &entry.path)?;
|
||||
let remote = pull_file(server_url, token, cloud_project_id, &entry.path)?;
|
||||
write_local(project_dir, &entry.path, &remote.bytes()?)?;
|
||||
meta.base_hashes.insert(entry.path.clone(), remote.hash);
|
||||
report.pulled.push(entry.path.clone());
|
||||
@@ -621,7 +647,7 @@ pub struct CloudDocument {
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct SharedItems {
|
||||
pub documents: Vec<CloudDocument>,
|
||||
pub spaces: Vec<SpaceSummary>,
|
||||
pub projects: Vec<ProjectSummary>,
|
||||
}
|
||||
|
||||
pub fn list_folders(server_url: &str, token: &str) -> Result<Vec<CloudFolder>, String> {
|
||||
@@ -687,6 +713,25 @@ pub fn pull_document(
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
pub fn create_document(
|
||||
server_url: &str,
|
||||
token: &str,
|
||||
title: &str,
|
||||
content: &str,
|
||||
) -> Result<DocumentContent, String> {
|
||||
agent()
|
||||
.post(&endpoint(server_url, "/documents"))
|
||||
.set("Authorization", &format!("Bearer {}", token))
|
||||
.send_json(ureq::json!({
|
||||
"title": title,
|
||||
"content": content,
|
||||
"folder_id": null,
|
||||
}))
|
||||
.map_err(describe)?
|
||||
.into_json::<DocumentContent>()
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
pub fn sync_document(
|
||||
server_url: &str,
|
||||
token: &str,
|
||||
|
||||
@@ -115,7 +115,7 @@ pub fn workspace_root(app: &AppHandle, store: &Store) -> Result<PathBuf, String>
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct ProjectMeta {
|
||||
pub entrypoint: String,
|
||||
pub space_id: Option<String>,
|
||||
pub cloud_project_id: Option<String>,
|
||||
pub last_synced_at: Option<String>,
|
||||
pub base_hashes: HashMap<String, String>,
|
||||
}
|
||||
@@ -124,7 +124,7 @@ impl Default for ProjectMeta {
|
||||
fn default() -> Self {
|
||||
ProjectMeta {
|
||||
entrypoint: "main.typ".to_string(),
|
||||
space_id: None,
|
||||
cloud_project_id: None,
|
||||
last_synced_at: None,
|
||||
base_hashes: HashMap::new(),
|
||||
}
|
||||
@@ -154,7 +154,7 @@ pub struct BrowseEntry {
|
||||
pub kind: String,
|
||||
pub size: u64,
|
||||
pub modified: Option<String>,
|
||||
pub space_id: Option<String>,
|
||||
pub cloud_project_id: Option<String>,
|
||||
pub last_synced_at: Option<String>,
|
||||
pub child_count: usize,
|
||||
pub cloud_linked: bool,
|
||||
@@ -259,9 +259,9 @@ pub fn browse(app: &AppHandle, store: &Store, relative: &str) -> Result<Vec<Brow
|
||||
})
|
||||
.unwrap_or(0);
|
||||
|
||||
let space_id = meta.as_ref().and_then(|m| m.space_id.clone());
|
||||
let cloud_project_id = meta.as_ref().and_then(|m| m.cloud_project_id.clone());
|
||||
let last_synced_at = meta.as_ref().and_then(|m| m.last_synced_at.clone());
|
||||
let sync_state = if space_id.is_some() {
|
||||
let sync_state = if cloud_project_id.is_some() {
|
||||
sync_state_for(newest_change(&full), last_synced_at.as_deref())
|
||||
} else {
|
||||
None
|
||||
@@ -273,8 +273,8 @@ pub fn browse(app: &AppHandle, store: &Store, relative: &str) -> Result<Vec<Brow
|
||||
kind: if project { "project" } else { "folder" }.to_string(),
|
||||
size: 0,
|
||||
modified: modified_at(&full),
|
||||
cloud_linked: space_id.is_some(),
|
||||
space_id,
|
||||
cloud_linked: cloud_project_id.is_some(),
|
||||
cloud_project_id,
|
||||
last_synced_at,
|
||||
sync_state,
|
||||
child_count,
|
||||
@@ -299,7 +299,7 @@ pub fn browse(app: &AppHandle, store: &Store, relative: &str) -> Result<Vec<Brow
|
||||
kind: kind.to_string(),
|
||||
size: std::fs::metadata(&full).map(|m| m.len()).unwrap_or(0),
|
||||
modified: modified_at(&full),
|
||||
space_id: None,
|
||||
cloud_project_id: None,
|
||||
last_synced_at: link.as_ref().and_then(|link| link.synced_at.clone()),
|
||||
child_count: 0,
|
||||
cloud_linked: link.is_some(),
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"resources": ["icons/32x32.png"],
|
||||
"linux": {
|
||||
"deb": {
|
||||
"depends": [
|
||||
|
||||
Reference in New Issue
Block a user