forked from 1b-agent/opencadstudio-upstream
Merge main into Select-Color-Redesign
Resolve conflicts with the new gradient and graphic-attributes colour targets: keep main's targets and menu handling, routed through the new ColorPicker state; adapt dock/view.rs to `color_picker`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013rGmDUNr1hUf4zo1X3dak8
This commit is contained in:
220 files changed
+37313
-5826
No files matched your search
+394
-21
@@ -14,6 +14,7 @@ pub(crate) mod commands;
|
||||
pub(crate) mod dim_viewport;
|
||||
#[cfg(test)]
|
||||
mod dimension_preview_tests;
|
||||
mod dock;
|
||||
mod document;
|
||||
mod drafting_settings;
|
||||
pub(crate) mod expr_eval;
|
||||
@@ -814,6 +815,11 @@ pub(super) struct OpenCADStudio {
|
||||
render_mode_preview: Option<codec::entities::ViewportRenderMode>,
|
||||
/// Whether the Properties panel is shown on the left (PROPERTIES).
|
||||
show_properties: bool,
|
||||
/// The Layer Manager pallet (LAYERS) is open.
|
||||
pub(crate) show_layers: bool,
|
||||
/// Whether the Graphic Attributes palette is shown (GRAPHICATTRIBUTES).
|
||||
show_graphic_attributes: bool,
|
||||
graphic_attributes: crate::ui::window::graphic_attributes::GraphicAttributesState,
|
||||
/// Docked Insert Block panel visibility.
|
||||
pub(crate) show_block_palette: bool,
|
||||
/// Node graph overlay over the viewport.
|
||||
@@ -840,17 +846,33 @@ pub(super) struct OpenCADStudio {
|
||||
pub(crate) sheet_set_import_parent: Option<String>,
|
||||
/// General edge-stack dock layout for the side panels.
|
||||
pub(crate) dock: crate::ui::dock::DockState,
|
||||
/// Which panel is currently floated at full height (hovered, or a pinned
|
||||
/// panel on top).
|
||||
pub(crate) dock_expanded: Option<crate::ui::dock::PanelId>,
|
||||
/// Panel currently being dragged between sides / reordered.
|
||||
pub(crate) dock_dragging: Option<crate::ui::dock::PanelId>,
|
||||
/// Panel currently being width-resized.
|
||||
pub(crate) dock_resizing: Option<crate::ui::dock::PanelId>,
|
||||
/// Last pointer position during a drag / resize.
|
||||
/// Pallet revealed although it auto-hides (its edge's group, or its
|
||||
/// floating frame): hovered, being dragged, or just opened. Cleared when
|
||||
/// the pointer leaves.
|
||||
pub(crate) dock_peek: Option<crate::ui::dock::PanelId>,
|
||||
/// Reveal or hide waiting out the hover delay, with the generation its
|
||||
/// timer carries; any newer hover supersedes it.
|
||||
pub(crate) dock_hover_pending: Option<crate::ui::dock::HoverPending>,
|
||||
pub(crate) dock_hover_gen: u64,
|
||||
/// What the pointer is dragging in the dock (pallet, group, width,
|
||||
/// splitter, floating size, Layer Manager column).
|
||||
pub(crate) dock_drag: Option<crate::ui::dock::DockDrag>,
|
||||
/// Pallet whose strip icon (or floating frame) the pointer is over.
|
||||
pub(crate) dock_icon_hover: Option<crate::ui::dock::PanelId>,
|
||||
/// Last pointer position during a dock drag.
|
||||
pub(crate) dock_drag_last: Option<iced::Point>,
|
||||
/// Live drag target (side + index), shown as a highlight while dragging.
|
||||
pub(crate) dock_drag_target: Option<(crate::app::config::DockSide, usize)>,
|
||||
/// Docked panel whose title bar the pointer is over (shows pin / close).
|
||||
pub(crate) dock_title_hover: Option<crate::ui::dock::PanelId>,
|
||||
/// Edge whose icon-strip pallet menu (+) is open.
|
||||
pub(crate) dock_edge_menu: Option<crate::app::config::DockSide>,
|
||||
/// Floating pallet whose title-bar right-click menu is open.
|
||||
pub(crate) dock_float_menu: Option<crate::ui::dock::PanelId>,
|
||||
/// Pointer position over a floating title bar (bar-local), and where
|
||||
/// its open menu was asked for.
|
||||
pub(crate) dock_float_pointer: iced::Point,
|
||||
pub(crate) dock_float_menu_at: iced::Point,
|
||||
/// Group whose edge band (group grip) the pointer is over.
|
||||
pub(crate) dock_grip_hover: Option<(crate::app::config::DockSide, usize)>,
|
||||
/// Reference-table column currently being width-resized (column index),
|
||||
/// with the last pointer position. Mirrors the Layers Name-column drag.
|
||||
pub(crate) xref_col_drag: Option<usize>,
|
||||
@@ -890,6 +912,56 @@ pub(super) struct OpenCADStudio {
|
||||
pending_native_thumbnail_save: Option<PendingNativeThumbnailSave>,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pending_web_thumbnail_save: Option<PendingWebThumbnailSave>,
|
||||
/// Web: whether the save in flight came from a plain Save
|
||||
/// (Save/QSAVE or the save-before-close flow), so the tab's retained
|
||||
/// File System Access handle (see io::fs_access) may be written
|
||||
/// through directly; Save As opens the dialog modal and must re-pick.
|
||||
/// Set true by `save_with_default_format`, false by
|
||||
/// `open_save_dialog_window`.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
save_plain_reuse_fs_handle: bool,
|
||||
/// One web autosave round is running; ticks that fire while it lasts are
|
||||
/// dropped instead of queued (the next interval retries).
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_autosave_in_flight: bool,
|
||||
/// Web autosave storage already reported a failure — stay quiet until a
|
||||
/// round succeeds again, so a full quota does not spam every tick.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_autosave_warned: bool,
|
||||
/// Crash-recovery snapshots found at boot (newest first) that the restore
|
||||
/// prompt still has to offer, one at a time.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_autosave_pending: Vec<crate::io::web_autosave::WebAutosaveEntry>,
|
||||
/// Snapshots a "Restore all" click still expects to settle (opened,
|
||||
/// failed, cancelled, unreadable, or rejected by the open queue). Each
|
||||
/// settle funnels through `queue_next_web_autosave_prompt`, which counts
|
||||
/// this down; reaching zero arms `web_session_focus_pending`.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_autosave_restore_all_remaining: usize,
|
||||
/// A Restore-all batch finished settling — apply the remembered
|
||||
/// session's active tab at the next message boundary, after the last
|
||||
/// restored open has actually inserted its tab (`web_autosave_opened`
|
||||
/// runs before that insertion, so applying there would pick against a
|
||||
/// tab list still missing the last restore).
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_session_focus_pending: bool,
|
||||
/// The tab list the previous session remembered (localStorage), loaded
|
||||
/// after the boot autosave scan. Advisory only: it feeds the restore
|
||||
/// modal's session line and the post-restore tab selection — nothing is
|
||||
/// ever auto-opened from it.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_session: Option<crate::io::web_session::WebSession>,
|
||||
/// The snapshot whose recovered open is in flight: `(autosave key,
|
||||
/// open id)`. The OPFS copy is the only one, so it is deleted only
|
||||
/// once the open with exactly that id succeeds — identity, never the
|
||||
/// display name (same-named files share one stored copy on web).
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_autosave_consumed: Option<(String, u64)>,
|
||||
/// Autosave keys a web save wanted to remove while a snapshot round was
|
||||
/// still writing (the round's late write would resurrect the snapshot
|
||||
/// with a fresh mtime); the removes run when the round finishes.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_autosave_clear_after: Vec<String>,
|
||||
// ── Floating panel windows ────────────────────────────────────────────
|
||||
/// The open Select Color panel, if any.
|
||||
color_picker: Option<crate::ui::color_select::ColorPicker>,
|
||||
@@ -937,9 +1009,6 @@ pub(super) struct OpenCADStudio {
|
||||
modal_drag_last: Option<Point>,
|
||||
/// True while the modal title bar is held (a drag is in progress).
|
||||
modal_dragging: bool,
|
||||
/// Layer Manager: dragging the Name-column divider (width follows the
|
||||
/// shared ModalDragMove flow).
|
||||
layer_col_dragging: bool,
|
||||
/// Layer Manager Name column width in px, adjusted by the divider drag.
|
||||
layer_name_col_w: f32,
|
||||
/// How far the user has dragged the modal's corner resize grip from the
|
||||
@@ -1560,6 +1629,25 @@ pub(super) struct PendingExternalChange {
|
||||
set_current_path: bool,
|
||||
}
|
||||
|
||||
/// One tab's slot in a web SAVEALL round (`saveall_web`): the tab identity,
|
||||
/// download name and race-guard markers snapshot when serialization started,
|
||||
/// plus the writer result — the future fills `result` in before the
|
||||
/// completion message is built. The web counterpart of native's
|
||||
/// `SaveOutcome`, minus the path/continuation bookkeeping SAVEALL never had.
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub struct WebSaveallEntry {
|
||||
tab_id: u64,
|
||||
path: PathBuf,
|
||||
filename: String,
|
||||
ext: String,
|
||||
version: codec::DxfVersion,
|
||||
geometry_epoch: u64,
|
||||
edit_revision: u64,
|
||||
camera_generation: u64,
|
||||
result: Result<Vec<u8>, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub struct SaveOutcome {
|
||||
@@ -1593,6 +1681,9 @@ pub enum UiColor {
|
||||
/// Where a colour chosen in the standalone palette window should be applied.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ColorPickTarget {
|
||||
Gradient(u8),
|
||||
GraphicAttributesLine,
|
||||
GraphicAttributesSolid,
|
||||
DimStyle(DsField),
|
||||
MLeader(&'static str),
|
||||
Table(u8, &'static str),
|
||||
@@ -1946,7 +2037,6 @@ pub enum ModalKind {
|
||||
PluginManager,
|
||||
UpdateNotice,
|
||||
DonationPrompt,
|
||||
Layers,
|
||||
LayerStateManager,
|
||||
LayerTranslator,
|
||||
DrawingUnits,
|
||||
@@ -2014,6 +2104,11 @@ pub enum ModalKind {
|
||||
XrefHelp,
|
||||
/// Sheet set wizard, properties and sheet / subset forms.
|
||||
SheetSet,
|
||||
/// Web: a crash-recovery autosave snapshot newer than the drawing's last
|
||||
/// save awaits a restore/discard decision. Distinct from the corrupt-file
|
||||
/// `RecoveryPrompt` — nothing is wrong with the drawing itself.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
AutosaveRestore,
|
||||
}
|
||||
|
||||
/// A property group controlled by a layer state's restore mask.
|
||||
@@ -2135,6 +2230,7 @@ pub enum PasteFocus {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Message {
|
||||
GraphicAttributes(crate::ui::window::graphic_attributes::GraphicAttributesMsg),
|
||||
SpaceMouseWake,
|
||||
SpaceMouseFrame(iced::time::Instant),
|
||||
SpaceMouseFocus(iced::window::Id, bool),
|
||||
@@ -2153,6 +2249,10 @@ pub enum Message {
|
||||
ControlStep(String, Box<Message>),
|
||||
ControlTaskDone(String),
|
||||
ControlScreenshot(String, Option<iced::window::Screenshot>),
|
||||
/// A frame has been drawn (or the anti-hang timer expired) for a parked
|
||||
/// `capture` request: take the screenshot now, so it shows the latest
|
||||
/// state instead of the previous frame (C11).
|
||||
ControlCaptureFrame { redrew: bool },
|
||||
ControlToggle,
|
||||
/// Node graph overlay interaction.
|
||||
Graph(crate::ui::node_graph::GraphMsg),
|
||||
@@ -2180,6 +2280,12 @@ pub enum Message {
|
||||
PasteShortcutResolved(PasteFocus),
|
||||
/// Completion of a system text clipboard read requested by PASTECLIP.
|
||||
SystemClipboardPaste(SystemClipboardText),
|
||||
/// Web: completion of the clipboard-image read the paste fallback starts
|
||||
/// when no text was found — `Some((name, bytes))` attaches the image,
|
||||
/// `None` reports the usual unsupported-content error. Native never
|
||||
/// constructs it.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
SystemClipboardPasteImage(Option<(String, Vec<u8>)>),
|
||||
/// Ctrl/Cmd+A — select all layer rows when the Layer Manager is open, or all
|
||||
/// drawing objects otherwise (#236).
|
||||
SelectAllShortcut,
|
||||
@@ -2200,6 +2306,12 @@ pub enum Message {
|
||||
OpenPathPicked(Option<(PathBuf, u64)>),
|
||||
/// A file was dragged from the desktop and dropped on the window (#344).
|
||||
FileDropped(PathBuf),
|
||||
/// Web only: a drawing dragged onto the page — the DOM drop listener has
|
||||
/// already read the bytes; this opens through the web-picker flow (#344).
|
||||
/// The third field tags an autosave-restore file with its snapshot key so
|
||||
/// the snapshot is deleted only when this exact open succeeds.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
WebFileDropped(String, Vec<u8>, Option<String>),
|
||||
/// Web only: Ctrl+V captured by a focused text field, whose iced-internal
|
||||
/// paste is inert there — read the async clipboard instead (#346).
|
||||
WebFieldPaste,
|
||||
@@ -2262,6 +2374,39 @@ pub enum Message {
|
||||
/// Web: an asynchronous OPFS copy written after Save is ready for recents.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
WebRecentStored(Result<PathBuf, String>),
|
||||
/// Web: the `?file=` boot deep link settled — its bytes entered the
|
||||
/// guarded open queue, or a notice/error text for the command line
|
||||
/// (recent copy missing, refused or failed fetch).
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
WebLinkSettled(crate::io::web_link::LinkOutcome),
|
||||
/// Web: the boot scan finished — these autosave snapshots are newer than
|
||||
/// the drawings' last saved copies (newest first).
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
WebAutosavesScanned(Result<Vec<crate::io::web_autosave::WebAutosaveEntry>, String>),
|
||||
/// Web: restore the offered autosave snapshot into a recovered tab.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
WebAutosaveRestore,
|
||||
/// Web: restore EVERY pending autosave snapshot — each is read and
|
||||
/// enqueued through the guarded open queue with its own key (the poll
|
||||
/// opens them sequentially); a snapshot is still consumed only after its
|
||||
/// own open succeeds.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
WebAutosaveRestoreAll,
|
||||
/// Web: delete the offered autosave snapshot (and offer the next, if any).
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
WebAutosaveDiscard,
|
||||
/// Web: close the restore prompt WITHOUT deleting the offered snapshot
|
||||
/// (Escape/✕): it is simply re-offered on the next boot. Only the
|
||||
/// Discard button deletes.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
WebAutosaveDefer,
|
||||
/// Web: the offered snapshot's bytes are in; queue them for the guarded
|
||||
/// open poll. The OPFS copy is consumed only once that open succeeds.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
WebAutosaveBytes(String, String, Result<Vec<u8>, String>),
|
||||
/// Web: one autosave round finished for all dirty tabs (serially).
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
WebAutosaveSnapshotFinished(Result<(), String>),
|
||||
SaveFile,
|
||||
SaveAs,
|
||||
// ── Custom Save-As dialog ─────────────────────────────────────────────
|
||||
@@ -2439,6 +2584,19 @@ pub enum Message {
|
||||
LayerTranslatorLoad,
|
||||
/// A target set was read from `path`.
|
||||
LayerTranslatorLoaded(std::path::PathBuf),
|
||||
/// Web: a standards drawing picked and parsed in the browser, with the
|
||||
/// target layers already extracted — a name-only path has nothing behind
|
||||
/// it for `load_targets` to read. `Ok(None)` = cancelled.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
LayerTranslatorLoadedWeb(
|
||||
Result<
|
||||
Option<(
|
||||
String,
|
||||
Vec<crate::modules::draw::layers::laytrans::TargetLayer>,
|
||||
)>,
|
||||
String,
|
||||
>,
|
||||
),
|
||||
LayerTranslatorSelectFrom(String),
|
||||
LayerTranslatorSelectTo(String),
|
||||
/// Pair the two selected layers.
|
||||
@@ -2544,6 +2702,89 @@ pub enum Message {
|
||||
bounds: Option<iced::Rectangle>,
|
||||
screenshot: Option<iced::window::Screenshot>,
|
||||
},
|
||||
/// Web SAVE: the serializer worker (or the inline fallback) produced the
|
||||
/// drawing bytes; download them and finish the save bookkeeping. The
|
||||
/// epoch/revision/camera markers snapshot the tab when serialization
|
||||
/// started — the web counterpart of native's `snapshot_is_current`, so a
|
||||
/// save that raced user edits does not wipe the newer dirty flag.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
WebSaveSerialized {
|
||||
tab_id: u64,
|
||||
filename: String,
|
||||
autosave_key: String,
|
||||
version: codec::DxfVersion,
|
||||
geometry_epoch: u64,
|
||||
edit_revision: u64,
|
||||
camera_generation: u64,
|
||||
result: Result<Vec<u8>, String>,
|
||||
},
|
||||
/// Web SAVE with the File System Access API: the showSaveFilePicker
|
||||
/// promise settled. The picker ran in the save-confirm handler — the
|
||||
/// user-gesture moment — because the serialization that follows may
|
||||
/// outlast the transient-activation window; the retained handle (kept
|
||||
/// in io::fs_access's tab-keyed registry, since a JsValue handle cannot
|
||||
/// ride in a Send Message) is written to later without needing a
|
||||
/// gesture. `Ok(Some(name))` = destination chosen (`name` is its real,
|
||||
/// possibly renamed name); `Ok(None)` = the user dismissed the picker
|
||||
/// (cancel the save, like desktop); `Err` = the picker itself failed
|
||||
/// (fall back to the legacy download).
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
WebSaveHandlePicked {
|
||||
tab_id: u64,
|
||||
filename: String,
|
||||
ext: String,
|
||||
version: codec::DxfVersion,
|
||||
picked: Result<Option<String>, String>,
|
||||
},
|
||||
/// Web SAVE in-place completion: the retained FS Access handle's
|
||||
/// createWritable write settled (on failure the task already fell back
|
||||
/// to a download); run the shared save bookkeeping, including the OPFS
|
||||
/// recent-copy store the download path performs. Same race-guard
|
||||
/// markers as `WebSaveSerialized`.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
WebSaveWritten {
|
||||
tab_id: u64,
|
||||
filename: String,
|
||||
autosave_key: String,
|
||||
version: codec::DxfVersion,
|
||||
geometry_epoch: u64,
|
||||
edit_revision: u64,
|
||||
camera_generation: u64,
|
||||
result: Result<(), String>,
|
||||
bytes: Vec<u8>,
|
||||
},
|
||||
/// Web WBLOCK: the serializer worker (or the inline fallback) produced
|
||||
/// the block document bytes; download them under `filename`, then run
|
||||
/// the same command-line bookkeeping as `WblockWriteFinished`.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
WebWblockSerialized {
|
||||
block_name: String,
|
||||
path: std::path::PathBuf,
|
||||
filename: String,
|
||||
result: Result<Vec<u8>, String>,
|
||||
},
|
||||
/// Web SAVEALL round completion: one entry per tab serialized on the
|
||||
/// worker (or the inline fallback). Downloads fire per entry; dirty
|
||||
/// flags clear only for tabs whose serialized snapshot is still current
|
||||
/// (the same race guard a single web Save uses).
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
WebSaveallSerialized {
|
||||
skipped: usize,
|
||||
results: Vec<WebSaveallEntry>,
|
||||
},
|
||||
/// Web ARCHIVE/ETRANSMIT completion: the serializer worker (or the
|
||||
/// inline fallback) produced the drawing bytes; assemble the transmittal
|
||||
/// ZIP from the dependencies collected at command time, download it and
|
||||
/// report — including the skipped-dependency lines.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
WebArchiveSerialized {
|
||||
cmd: String,
|
||||
zip_name: String,
|
||||
filename: String,
|
||||
deps: Vec<(String, Vec<u8>)>,
|
||||
skipped: Vec<String>,
|
||||
result: Result<Vec<u8>, String>,
|
||||
},
|
||||
/// Retry the failed save after the other application releases the file.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
SaveFileInUseRetry,
|
||||
@@ -2573,6 +2814,19 @@ pub enum Message {
|
||||
/// dispatch, this accepts an interactive verb and all of its arguments on
|
||||
/// the same line (`BOX 0,0,0 10,10,0 10`).
|
||||
ScriptLine(String),
|
||||
/// Web SCRIPT: the picked .scr file's name and text. `None` = the
|
||||
/// browser dialog was cancelled.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
ScriptFilePicked(Option<(String, String)>),
|
||||
/// Web CENTERLTYPEFILE: the picked .lin/.dnl file's name and text.
|
||||
/// `None` = cancelled.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
CenterlineLinetypeFilePicked(Option<(String, String)>),
|
||||
/// Web DATALINK: the picked .csv file's name and raw bytes — registered
|
||||
/// in the picked-source registry, then fed to the same table-insert path
|
||||
/// the native typed-path command uses. `None` = cancelled.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
DataLinkCsvPicked(Option<(String, Vec<u8>)>),
|
||||
/// Append one typed character to the command-line input from the
|
||||
/// global key-press subscription. Used when the text-input widget
|
||||
/// itself isn't focused (focus parked on viewport / button / etc.)
|
||||
@@ -2603,6 +2857,12 @@ pub enum Message {
|
||||
shortcut: String,
|
||||
extend_selection: bool,
|
||||
},
|
||||
/// A text field captured SPACE while it is rebound away from
|
||||
/// COMMANDSPACE; run the binding only if the command input has focus.
|
||||
CommandLineSpaceProbe,
|
||||
/// The command input has focus: drop the space it just typed and run the
|
||||
/// SPACE binding.
|
||||
CommandLineSpaceResolved,
|
||||
/// A widget captured Up/Down; resolve it only if the command input owns
|
||||
/// keyboard focus.
|
||||
CommandLineArrowProbe {
|
||||
@@ -2856,6 +3116,8 @@ pub enum Message {
|
||||
ToggleViewCube,
|
||||
/// Toggle the Properties panel visibility (PROPERTIES).
|
||||
ToggleProperties,
|
||||
/// Toggle the Graphic Attributes panel visibility (GRAPHICATTRIBUTES).
|
||||
ToggleGraphicAttributes,
|
||||
/// Toggle the document file tabs at the top (FILETAB).
|
||||
ToggleFileTabs,
|
||||
/// Toggle the layout tabs at the bottom (LAYOUTTAB).
|
||||
@@ -3667,6 +3929,8 @@ pub enum Message {
|
||||
/// Send current layout to the system printer (via lp / lpr).
|
||||
PrintToPrinter,
|
||||
/// Callback from the async printer job.
|
||||
/// Web never constructs it — printing opens the PDF preview instead.
|
||||
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
|
||||
PrintResult(Result<String, String>),
|
||||
/// Open the full Plot / Print dialog (seeds state from the layout).
|
||||
PlotDialogOpen,
|
||||
@@ -4001,7 +4265,9 @@ pub enum Message {
|
||||
DataExtractionBrowseSettings,
|
||||
DataExtractionBrowseSettingsResult(Option<std::path::PathBuf>),
|
||||
DataExtractionAddDrawings,
|
||||
DataExtractionAddDrawingsResult(Vec<std::path::PathBuf>),
|
||||
/// Picked source drawings — `Err` carries a pick/parse failure (web
|
||||
/// parses each file on the worker at pick time).
|
||||
DataExtractionAddDrawingsResult(Result<Vec<std::path::PathBuf>, String>),
|
||||
DataExtractionAddFolder,
|
||||
DataExtractionAddFolderResult(Option<std::path::PathBuf>),
|
||||
DataExtractionClearSources,
|
||||
@@ -4028,6 +4294,8 @@ pub enum Message {
|
||||
/// Trigger OBJ import: show open-file dialog.
|
||||
ObjImport,
|
||||
/// Callback after the user picks (or cancels) the OBJ file path.
|
||||
/// Web never constructs it — the browser handle's bytes are read directly.
|
||||
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
|
||||
ObjImportPath(Option<std::path::PathBuf>),
|
||||
ObjImportFinished(
|
||||
u64,
|
||||
@@ -4270,6 +4538,9 @@ impl OpenCADStudio {
|
||||
render_mode_menu_open: false,
|
||||
render_mode_preview: None,
|
||||
show_properties: true,
|
||||
show_layers: false,
|
||||
show_graphic_attributes: false,
|
||||
graphic_attributes: Default::default(),
|
||||
show_block_palette: false,
|
||||
show_node_graph: false,
|
||||
property_target_override: None,
|
||||
@@ -4284,11 +4555,18 @@ impl OpenCADStudio {
|
||||
block_palette: Default::default(),
|
||||
xref_manager: Default::default(),
|
||||
dock: Default::default(),
|
||||
dock_expanded: None,
|
||||
dock_dragging: None,
|
||||
dock_resizing: None,
|
||||
dock_peek: None,
|
||||
dock_hover_pending: None,
|
||||
dock_hover_gen: 0,
|
||||
dock_float_menu: None,
|
||||
dock_float_pointer: iced::Point::ORIGIN,
|
||||
dock_float_menu_at: iced::Point::ORIGIN,
|
||||
dock_drag: None,
|
||||
dock_drag_last: None,
|
||||
dock_drag_target: None,
|
||||
dock_title_hover: None,
|
||||
dock_edge_menu: None,
|
||||
dock_grip_hover: None,
|
||||
dock_icon_hover: None,
|
||||
xref_col_drag: None,
|
||||
xref_col_last: None,
|
||||
xref_split_drag: false,
|
||||
@@ -4304,6 +4582,24 @@ impl OpenCADStudio {
|
||||
pending_native_thumbnail_save: None,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pending_web_thumbnail_save: None,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
save_plain_reuse_fs_handle: false,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_autosave_in_flight: false,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_autosave_warned: false,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_autosave_pending: Vec::new(),
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_autosave_restore_all_remaining: 0,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_session_focus_pending: false,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_session: None,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_autosave_consumed: None,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_autosave_clear_after: Vec::new(),
|
||||
color_picker: None,
|
||||
recent_colors: Vec::new(),
|
||||
active_modal: None,
|
||||
@@ -4324,7 +4620,6 @@ impl OpenCADStudio {
|
||||
modal_offset: iced::Vector::ZERO,
|
||||
modal_drag_last: None,
|
||||
modal_dragging: false,
|
||||
layer_col_dragging: false,
|
||||
layer_name_col_w: 130.0,
|
||||
modal_resize: iced::Vector::ZERO,
|
||||
modal_content_size: None,
|
||||
@@ -4861,6 +5156,16 @@ impl OpenCADStudio {
|
||||
fn boot_web() -> (Self, Task<Message>) {
|
||||
#[cfg_attr(target_arch = "wasm32", allow(unused_mut))]
|
||||
let mut s = Self::new();
|
||||
// A web host that intends to automate can opt in before the first
|
||||
// control request by setting `opencadstudio.automation` to "1" in
|
||||
// localStorage (the same pre-load mechanism the settings use): the
|
||||
// startup donation prompt then never queues (#1349).
|
||||
if web_sys::window().and_then(|window| window.local_storage().ok().flatten()).and_then(
|
||||
|storage| storage.get_item("opencadstudio.automation").ok().flatten(),
|
||||
) == Some("1".to_string())
|
||||
{
|
||||
s.control.automation_seen = true;
|
||||
}
|
||||
s.queue_startup_prompts();
|
||||
let focus = s.focus_cmd_input();
|
||||
let primary_font =
|
||||
@@ -4880,9 +5185,64 @@ impl OpenCADStudio {
|
||||
Message::DiscussionsFetched,
|
||||
);
|
||||
let thumbs_fetch = s.refresh_recent_thumbs();
|
||||
// Crash-recovery scan: OPFS snapshots newer than a drawing's last
|
||||
// saved copy come back as a restore prompt (stale ones are deleted).
|
||||
let autosave_scan = Task::perform(
|
||||
crate::io::web_autosave::scan(),
|
||||
Message::WebAutosavesScanned,
|
||||
);
|
||||
// URL deep link (`?file=…`): a shareable/embeddable link that opens a
|
||||
// drawing at boot. An http(s) URL is fetched with the same capped
|
||||
// streaming reader the remote-image fetch uses and handed to the
|
||||
// guarded drop-open queue; a bare name reads the OPFS recent copy the
|
||||
// same way `open_recent_web` does. Everything else (other schemes,
|
||||
// non-.dwg/.dxf URL tails) is refused with a console warning — the
|
||||
// same scheme gate the image fetch applies. Pure parsing/validation
|
||||
// lives in `io::web_link` and is unit-tested there.
|
||||
let deep_link: Task<Message> = {
|
||||
let search = web_sys::window()
|
||||
.and_then(|window| window.location().search().ok())
|
||||
.unwrap_or_default();
|
||||
match crate::io::web_link::parse_link(&search) {
|
||||
crate::io::web_link::ParsedLink::Target(target) => {
|
||||
// Opening a URL from the query string is an implicit trust
|
||||
// decision: name the origin before the fetch starts (info
|
||||
// only — the bytes still go through the parse + recovery
|
||||
// machinery like any other open). Same-origin links stay
|
||||
// quiet: that drawing already comes from this site.
|
||||
if let crate::io::web_link::LinkTarget::Url { url, name } = &target {
|
||||
if !crate::io::web_link::same_origin(url) {
|
||||
let origin = crate::io::web_link::display_origin(url)
|
||||
.unwrap_or_else(|| url.clone());
|
||||
s.command_line
|
||||
.push_info(&format!("Opening {name} from {origin}…"));
|
||||
}
|
||||
}
|
||||
Task::perform(crate::io::web_link::open_link(target), |outcome| {
|
||||
Message::WebLinkSettled(outcome)
|
||||
})
|
||||
}
|
||||
crate::io::web_link::ParsedLink::Ignored(refusal, value) => {
|
||||
web_sys::console::warn_1(&wasm_bindgen::JsValue::from_str(
|
||||
&crate::io::web_link::refusal_warning(refusal, &value),
|
||||
));
|
||||
Task::none()
|
||||
}
|
||||
crate::io::web_link::ParsedLink::Absent => Task::none(),
|
||||
}
|
||||
};
|
||||
(
|
||||
s,
|
||||
Task::batch([focus, fonts, patrons, videos, discussions, thumbs_fetch]),
|
||||
Task::batch([
|
||||
focus,
|
||||
fonts,
|
||||
patrons,
|
||||
videos,
|
||||
discussions,
|
||||
thumbs_fetch,
|
||||
autosave_scan,
|
||||
deep_link,
|
||||
]),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -4940,6 +5300,19 @@ impl Drop for OpenCADStudio {
|
||||
/// unavailable for now (see issue #45). Native keeps the multi-window `run`.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn run_web() -> iced::Result {
|
||||
// winit never emits DroppedFile on the web, so the document drag & drop
|
||||
// listeners are installed here and feed the control poll (#344).
|
||||
crate::io::web_drop::install();
|
||||
// Best-effort, fire-and-forget: ask the browser not to evict the origin's
|
||||
// storage under pressure — settings (localStorage) and the OPFS recent
|
||||
// copies / autosave snapshots live there. A denial changes nothing.
|
||||
wasm_bindgen_futures::spawn_local(async {
|
||||
if let Some(window) = web_sys::window() {
|
||||
if let Ok(promise) = window.navigator().storage().persist() {
|
||||
let _ = wasm_bindgen_futures::JsFuture::from(promise).await;
|
||||
}
|
||||
}
|
||||
});
|
||||
iced::application(
|
||||
OpenCADStudio::boot_web,
|
||||
OpenCADStudio::update,
|
||||
|
||||
Reference in new issue
Block a user