mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-13 01:11:11 +00:00
A file put on a server without the server's key stays its owner's after its tab closes or the app quits. The app records it with the preferences (server, id, name, owner token, the owner's links, what others join with; at most 200; validated when read back) and never shows the token: not in command results, ui.inspect, logs or debug output. - Home > Online lists your online files: open one as its owner (a new tab, or its tab comes to the front), start and stop sharing, copy the code or link, rename on the server, remove from the server (confirmed), forget here. One request per server when the section opens; offline and gone states. - collab.files, collab.openFile, collab.forgetFile; collab.manageFile takes its server and token from the record and gains share / unshare. - Server: Admin owned (a compatible addition within protocol version 2) says what became of the files a list of owner tokens owns. Older servers leave the list as recorded. - Tabs mark files in a live session (shared, or open only to you); toasts about a file that isn't in front name it; after a crash a recorded file reopens from its server as its owner. - Draft catalogs (de, fr, ja, ko, pt-BR, zh-Hans) translate the 71 strings and 5 command labels they lacked and the new ones; es and es-419 follow. - Docs: collab-protocol, server, mcp, control-protocol, parity pages, gaps, ROADMAP.
131 lines
3.8 KiB
Rust
131 lines
3.8 KiB
Rust
//! The command registry. Each area module exposes `specs()`; [`all_commands`] collects them once.
|
|
|
|
pub mod arrange;
|
|
pub mod comment;
|
|
pub mod component;
|
|
pub mod create;
|
|
pub mod edit;
|
|
pub mod file;
|
|
pub mod image;
|
|
pub mod layout;
|
|
pub mod library;
|
|
pub mod lists;
|
|
pub mod page;
|
|
pub mod props;
|
|
pub mod proto;
|
|
pub mod proto_actions;
|
|
pub mod section;
|
|
pub mod structure;
|
|
pub mod styles;
|
|
pub mod text;
|
|
pub mod variables;
|
|
pub mod variables_io;
|
|
pub mod view;
|
|
|
|
use std::collections::HashMap;
|
|
use std::sync::OnceLock;
|
|
|
|
use serde_json::Value;
|
|
|
|
use crate::{Result, Session};
|
|
|
|
pub type RunFn = fn(&mut Session, &Value) -> Result<Value>;
|
|
|
|
/// A registered command.
|
|
pub struct CommandSpec {
|
|
pub id: &'static str,
|
|
pub label: &'static str,
|
|
/// Parameter documentation for agents (`list_commands`).
|
|
pub params: &'static str,
|
|
/// Whether the command edits the document (an undo step).
|
|
pub history: bool,
|
|
pub run: RunFn,
|
|
}
|
|
|
|
impl CommandSpec {
|
|
pub fn menu(&self) -> Option<&'static str> {
|
|
crate::catalog::MENU.iter().find(|m| m.id == self.id).map(|m| m.menu)
|
|
}
|
|
pub fn shortcut(&self) -> Option<&'static str> {
|
|
crate::catalog::shortcut(self.id)
|
|
}
|
|
}
|
|
|
|
/// Define a command spec: `spec!(id, label, params_doc, history, run)`.
|
|
#[macro_export]
|
|
macro_rules! spec {
|
|
($id:expr, $label:expr, $params:expr, $hist:expr, $run:expr) => {
|
|
$crate::cmd::CommandSpec { id: $id, label: $label, params: $params, history: $hist, run: $run }
|
|
};
|
|
}
|
|
|
|
struct Registry {
|
|
list: Vec<CommandSpec>,
|
|
index: HashMap<&'static str, usize>,
|
|
}
|
|
|
|
fn registry() -> &'static Registry {
|
|
static R: OnceLock<Registry> = OnceLock::new();
|
|
R.get_or_init(|| {
|
|
let mut list = Vec::new();
|
|
list.extend(create::specs());
|
|
list.extend(edit::specs());
|
|
list.extend(lists::specs());
|
|
list.extend(arrange::specs());
|
|
list.extend(structure::specs());
|
|
list.extend(section::specs());
|
|
list.extend(layout::specs());
|
|
list.extend(file::specs());
|
|
list.extend(page::specs());
|
|
list.extend(view::specs());
|
|
list.extend(text::specs());
|
|
list.extend(styles::specs());
|
|
list.extend(image::specs());
|
|
list.extend(variables::specs());
|
|
list.extend(variables_io::specs());
|
|
list.extend(component::specs());
|
|
list.extend(library::specs());
|
|
list.extend(proto::specs());
|
|
list.extend(proto_actions::specs());
|
|
list.extend(comment::specs());
|
|
list.extend(crate::vectoredit::specs());
|
|
list.extend(crate::shapebuilder::specs());
|
|
list.extend(crate::codegen::specs());
|
|
list.extend(crate::devinspect::specs());
|
|
list.extend(crate::component_codegen::specs());
|
|
list.extend(crate::code_connect::specs());
|
|
list.extend(crate::annotations::specs());
|
|
list.extend(crate::dev_resources::specs());
|
|
list.extend(crate::distance::specs());
|
|
list.extend(crate::collab::specs());
|
|
list.extend(crate::branches::specs());
|
|
list.extend(crate::plugins::specs());
|
|
list.extend(crate::plugin_directory::specs());
|
|
list.extend(crate::widgets::specs());
|
|
list.extend(crate::live::specs());
|
|
list.extend(crate::online::specs());
|
|
list.extend(crate::invite_links::specs());
|
|
let mut index = HashMap::new();
|
|
for (i, s) in list.iter().enumerate() {
|
|
if index.insert(s.id, i).is_some() {
|
|
log::warn!("duplicate command id {}", s.id);
|
|
}
|
|
}
|
|
Registry { list, index }
|
|
})
|
|
}
|
|
|
|
pub fn all_commands() -> &'static [CommandSpec] {
|
|
®istry().list
|
|
}
|
|
|
|
pub fn find_command(id: &str) -> Option<&'static CommandSpec> {
|
|
let r = registry();
|
|
r.index.get(id).and_then(|i| r.list.get(*i))
|
|
}
|
|
|
|
/// `{"ok": true}` shorthand.
|
|
pub fn ok() -> Result<Value> {
|
|
Ok(serde_json::json!({"ok": true}))
|
|
}
|