Refactor the TypeScript data flow for full type safety and auto-generation of Rust types (#3865)

* Migrate Specta to Tsify to auto-generate messages.ts, working except colors and widgets

* Adopt the generated FillColor/Color/GradientStops

* Fix widget typing

* Separate WidgetGroup enum variants into wrapper structs

* Small rename

* Simplify widgets further

* Clean up message type references

* Switch type imports to the auto-generated file

* Remove lowercase serde rename

* Fix FillChoice deserialization

* Fix small regression from #3837

* Improve type safety

* Make WidgetSpan type-safe

* More cleanup and type safety

* More type safety

* More type safety

* Get the rest to type-check without errors; improve widget builder macro to have optional icons; improve Svelte 5 configs

* Cargo fmt

* Fix imports

* Update outdated readme info

* Fix lint command rename references

* Fix typos

* One more typos fix

* Remove unnecessary dep: prefix from the edited Cargo.toml files

* Remove excess parts from Cargo.toml

* Fix compiling on desktop

* Revert "Remove excess parts from Cargo.toml"

This reverts commit 6b711117b3a5d5d8a3ee20f36a43bc74930b7c82.

* Update dev docs with simpler, more accurate instructions
This commit is contained in:
Keavon Chambers
2026-03-09 16:35:04 -07:00
parent fbd2658148
commit 52d2b38a82
199 changed files with 2265 additions and 2811 deletions
@@ -1,15 +1,16 @@
use super::IconName;
use super::utility_types::{DocumentDetails, MouseCursorIcon, OpenDocument};
use crate::messages::app_window::app_window_message_handler::AppWindowPlatform;
use crate::messages::frontend::utility_types::EyedropperPreviewImage;
use crate::messages::input_mapper::utility_types::misc::ActionShortcut;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::utility_types::{
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, NodeGraphErrorDiagnostic, Transform,
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, NodeGraphErrorDiagnostic,
};
use crate::messages::portfolio::document::utility_types::nodes::{LayerPanelEntry, LayerStructureEntry};
use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate};
use crate::messages::prelude::*;
use glam::IVec2;
use crate::messages::tool::tool_messages::eyedropper_tool::PrimarySecondary;
use graph_craft::document::NodeId;
use graphene_std::raster::Image;
use graphene_std::raster::color::Color;
@@ -20,13 +21,14 @@ use std::path::PathBuf;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
#[impl_message(Message, Frontend)]
#[derive(derivative::Derivative, Clone, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(derivative::Derivative, Clone, serde::Serialize, serde::Deserialize)]
#[derivative(Debug, PartialEq)]
pub enum FrontendMessage {
// Display prefix: make the frontend show something, like a dialog
DisplayDialog {
title: String,
icon: String,
icon: IconName,
},
DialogClose,
DisplayDialogPanic {
@@ -41,7 +43,7 @@ pub enum FrontendMessage {
font_size: f64,
color: String,
#[serde(rename = "fontData")]
font_data: Vec<u8>,
font_data: serde_bytes::ByteBuf,
transform: [f64; 6],
#[serde(rename = "maxWidth")]
max_width: Option<f64>,
@@ -51,7 +53,7 @@ pub enum FrontendMessage {
},
DisplayEditableTextboxUpdateFontData {
#[serde(rename = "fontData")]
font_data: Vec<u8>,
font_data: serde_bytes::ByteBuf,
},
DisplayEditableTextboxTransform {
transform: [f64; 6],
@@ -87,11 +89,11 @@ pub enum FrontendMessage {
document_id: DocumentId,
name: String,
path: Option<PathBuf>,
content: Vec<u8>,
content: serde_bytes::ByteBuf,
},
TriggerSaveFile {
name: String,
content: Vec<u8>,
content: serde_bytes::ByteBuf,
},
TriggerExportImage {
svg: String,
@@ -125,6 +127,7 @@ pub enum FrontendMessage {
TriggerOpen,
TriggerImport,
TriggerSavePreferences {
#[tsify(type = "unknown")]
preferences: PreferencesMessageHandler,
},
TriggerSaveActiveDocument {
@@ -152,9 +155,8 @@ pub enum FrontendMessage {
document_id: DocumentId,
},
UpdateGradientStopColorPickerPosition {
color: Color,
x: f64,
y: f64,
color: Color, // TODO: Color (without `none`) -> Color (with `none`)
position: (f64, f64),
},
UpdateImportsExports {
/// If the primary import is not visible, then it is None.
@@ -163,10 +165,10 @@ pub enum FrontendMessage {
exports: Vec<Option<FrontendGraphInput>>,
/// The primary import location.
#[serde(rename = "importPosition")]
import_position: IVec2,
import_position: (i32, i32),
/// The primary export location.
#[serde(rename = "exportPosition")]
export_position: IVec2,
export_position: (i32, i32),
/// The document network does not have an add import or export button.
#[serde(rename = "addImportExport")]
add_import_export: bool,
@@ -202,7 +204,7 @@ pub enum FrontendMessage {
UpdateLayout {
#[serde(rename = "layoutTarget")]
layout_target: LayoutTarget,
diff: Vec<WidgetDiff>,
diff: Vec<WidgetDiff>, // TODO: Align this with what's generated
},
UpdateImportReorderIndex {
#[serde(rename = "importIndex")]
@@ -223,6 +225,8 @@ pub enum FrontendMessage {
UpdateDocumentArtwork {
svg: String,
},
// This message is intercepted before being sent to the frontend
#[serde(skip)]
UpdateImageData {
image_data: Vec<(u64, Image<Color>)>,
},
@@ -253,7 +257,7 @@ pub enum FrontendMessage {
#[serde(rename = "secondaryColor")]
secondary_color: String,
#[serde(rename = "setColorChoice")]
set_color_choice: Option<String>,
set_color_choice: Option<PrimarySecondary>,
},
UpdateGraphFadeArtwork {
percentage: f64,
@@ -278,7 +282,8 @@ pub enum FrontendMessage {
selected: Vec<NodeId>,
},
UpdateNodeGraphTransform {
transform: Transform,
translation: (f64, f64),
scale: f64,
},
UpdateNodeThumbnail {
id: NodeId,
@@ -304,6 +309,7 @@ pub enum FrontendMessage {
UpdateViewportHolePunch {
active: bool,
},
#[cfg(not(target_family = "wasm"))]
UpdateViewportPhysicalBounds {
x: f64,
y: f64,
@@ -322,18 +328,26 @@ pub enum FrontendMessage {
},
// Window prefix: cause the application window to do something
#[cfg(not(target_family = "wasm"))]
WindowPointerLock,
WindowPointerLockMove {
x: f64,
y: f64,
position: (f64, f64),
},
#[cfg(not(target_family = "wasm"))]
WindowClose,
#[cfg(not(target_family = "wasm"))]
WindowMinimize,
#[cfg(not(target_family = "wasm"))]
WindowMaximize,
WindowFullscreen,
#[cfg(not(target_family = "wasm"))]
WindowDrag,
#[cfg(not(target_family = "wasm"))]
WindowHide,
#[cfg(not(target_family = "wasm"))]
WindowHideOthers,
#[cfg(not(target_family = "wasm"))]
WindowShowAll,
#[cfg(not(target_family = "wasm"))]
WindowRestart,
}
+4
View File
@@ -4,3 +4,7 @@ pub mod utility_types;
#[doc(inline)]
pub use frontend_message::{FrontendMessage, FrontendMessageDiscriminant};
// TODO: Make this an enum with the actual icon names, somehow derived from or tied to the frontend icon set.
// TODO: Then remove `#[widget_builder(string)]` from all icon fields.
pub type IconName = String;
+13 -7
View File
@@ -3,13 +3,15 @@ use std::path::PathBuf;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::prelude::*;
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct OpenDocument {
pub id: DocumentId,
pub details: DocumentDetails,
}
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct DocumentDetails {
pub name: String,
pub path: Option<PathBuf>,
@@ -19,7 +21,8 @@ pub struct DocumentDetails {
pub is_auto_saved: bool,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum MouseCursorIcon {
#[default]
Default,
@@ -37,7 +40,8 @@ pub enum MouseCursorIcon {
Rotate,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum FileType {
#[default]
Png,
@@ -55,7 +59,8 @@ impl FileType {
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum ExportBounds {
#[default]
AllArtwork,
@@ -63,9 +68,10 @@ pub enum ExportBounds {
Artboard(LayerNodeIdentifier),
}
#[derive(Clone, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(large_number_types_as_bigints))]
#[derive(Clone, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct EyedropperPreviewImage {
pub data: Vec<u8>,
pub data: serde_bytes::ByteBuf,
pub width: u32,
pub height: u32,
}