Clean up camelCase and snake_case in frontend code

This commit is contained in:
Keavon Chambers 2022-08-23 13:36:27 -07:00
parent 71f12db1e6
commit 1a90a4db86
30 changed files with 317 additions and 162 deletions

File diff suppressed because one or more lines are too long

View File

@ -63,9 +63,6 @@ pub const SCROLLBAR_SPACING: f64 = 0.1;
pub const ASYMPTOTIC_EFFECT: f64 = 0.5; pub const ASYMPTOTIC_EFFECT: f64 = 0.5;
pub const SCALE_EFFECT: f64 = 0.5; pub const SCALE_EFFECT: f64 = 0.5;
pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
pub const FILE_SAVE_SUFFIX: &str = ".graphite";
// Colors // Colors
pub const COLOR_ACCENT: Color = Color::from_unsafe(0x00 as f32 / 255., 0xA8 as f32 / 255., 0xFF as f32 / 255.); pub const COLOR_ACCENT: Color = Color::from_unsafe(0x00 as f32 / 255., 0xA8 as f32 / 255., 0xFF as f32 / 255.);
@ -74,5 +71,8 @@ pub const DEFAULT_FONT_FAMILY: &str = "Merriweather";
pub const DEFAULT_FONT_STYLE: &str = "Normal (400)"; pub const DEFAULT_FONT_STYLE: &str = "Normal (400)";
// Document // Document
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.11"; // Remember to save a simple document and replace the test file `graphite-test-document.graphite` pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.12"; // Remember to save a simple document and replace the test file `graphite-test-document.graphite`
pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
pub const FILE_SAVE_SUFFIX: &str = ".graphite";
pub const VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR: f32 = 1.05; pub const VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR: f32 = 1.05;

View File

@ -16,50 +16,166 @@ use serde::{Deserialize, Serialize};
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] #[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub enum FrontendMessage { pub enum FrontendMessage {
// Display prefix: make the frontend show something, like a dialog // Display prefix: make the frontend show something, like a dialog
DisplayDialog { icon: String }, DisplayDialog {
icon: String,
},
DisplayDialogDismiss, DisplayDialogDismiss,
DisplayDialogPanic { panic_info: String, header: String, description: String }, DisplayDialogPanic {
DisplayEditableTextbox { text: String, line_width: Option<f64>, font_size: f64, color: Color }, #[serde(rename = "panicInfo")]
panic_info: String,
header: String,
description: String,
},
DisplayEditableTextbox {
text: String,
#[serde(rename = "lineWidth")]
line_width: Option<f64>,
#[serde(rename = "fontSize")]
font_size: f64,
color: Color,
},
DisplayRemoveEditableTextbox, DisplayRemoveEditableTextbox,
// Trigger prefix: cause a browser API to do something // Trigger prefix: cause a browser API to do something
TriggerAboutGraphiteLocalizedCommitDate { commit_date: String }, TriggerAboutGraphiteLocalizedCommitDate {
TriggerFileDownload { document: String, name: String }, #[serde(rename = "commitDate")]
TriggerFontLoad { font: Font, is_default: bool }, commit_date: String,
},
TriggerFileDownload {
document: String,
name: String,
},
TriggerFontLoad {
font: Font,
#[serde(rename = "isDefault")]
is_default: bool,
},
TriggerImport, TriggerImport,
TriggerIndexedDbRemoveDocument { document_id: u64 }, TriggerIndexedDbRemoveDocument {
TriggerIndexedDbWriteDocument { document: String, details: FrontendDocumentDetails, version: String }, #[serde(rename = "documentId")]
document_id: u64,
},
TriggerIndexedDbWriteDocument {
document: String,
details: FrontendDocumentDetails,
version: String,
},
TriggerOpenDocument, TriggerOpenDocument,
TriggerPaste, TriggerPaste,
TriggerRasterDownload { document: String, name: String, mime: String, size: (f64, f64) }, TriggerRasterDownload {
document: String,
name: String,
mime: String,
size: (f64, f64),
},
TriggerRefreshBoundsOfViewports, TriggerRefreshBoundsOfViewports,
TriggerTextCommit, TriggerTextCommit,
TriggerTextCopy { copy_text: String }, TriggerTextCopy {
#[serde(rename = "copyText")]
copy_text: String,
},
TriggerViewportResize, TriggerViewportResize,
TriggerVisitLink { url: String }, TriggerVisitLink {
url: String,
},
// Update prefix: give the frontend a new value or state for it to use // Update prefix: give the frontend a new value or state for it to use
UpdateActiveDocument { document_id: u64 }, UpdateActiveDocument {
UpdateDialogDetails { layout_target: LayoutTarget, layout: SubLayout }, #[serde(rename = "documentId")]
UpdateDocumentArtboards { svg: String }, document_id: u64,
UpdateDocumentArtwork { svg: String }, },
UpdateDocumentBarLayout { layout_target: LayoutTarget, layout: SubLayout }, UpdateDialogDetails {
UpdateDocumentLayerDetails { data: LayerPanelEntry }, #[serde(rename = "layoutTarget")]
UpdateDocumentLayerTreeStructure { data_buffer: RawBuffer }, layout_target: LayoutTarget,
UpdateDocumentModeLayout { layout_target: LayoutTarget, layout: SubLayout }, layout: SubLayout,
UpdateDocumentOverlays { svg: String }, },
UpdateDocumentRulers { origin: (f64, f64), spacing: f64, interval: f64 }, UpdateDocumentArtboards {
UpdateDocumentScrollbars { position: (f64, f64), size: (f64, f64), multiplier: (f64, f64) }, svg: String,
UpdateImageData { image_data: Vec<FrontendImageData> }, },
UpdateInputHints { hint_data: HintData }, UpdateDocumentArtwork {
UpdateLayerTreeOptionsLayout { layout_target: LayoutTarget, layout: SubLayout }, svg: String,
UpdateMenuBarLayout { layout_target: LayoutTarget, layout: Vec<MenuColumn> }, },
UpdateMouseCursor { cursor: MouseCursorIcon }, UpdateDocumentBarLayout {
UpdateNodeGraphVisibility { visible: bool }, #[serde(rename = "layoutTarget")]
UpdateOpenDocumentsList { open_documents: Vec<FrontendDocumentDetails> }, layout_target: LayoutTarget,
UpdatePropertyPanelOptionsLayout { layout_target: LayoutTarget, layout: SubLayout }, layout: SubLayout,
UpdatePropertyPanelSectionsLayout { layout_target: LayoutTarget, layout: SubLayout }, },
UpdateToolOptionsLayout { layout_target: LayoutTarget, layout: SubLayout }, UpdateDocumentLayerDetails {
UpdateToolShelfLayout { layout_target: LayoutTarget, layout: SubLayout }, data: LayerPanelEntry,
UpdateWorkingColorsLayout { layout_target: LayoutTarget, layout: SubLayout }, },
UpdateDocumentLayerTreeStructure {
#[serde(rename = "dataBuffer")]
data_buffer: RawBuffer,
},
UpdateDocumentModeLayout {
#[serde(rename = "layoutTarget")]
layout_target: LayoutTarget,
layout: SubLayout,
},
UpdateDocumentOverlays {
svg: String,
},
UpdateDocumentRulers {
origin: (f64, f64),
spacing: f64,
interval: f64,
},
UpdateDocumentScrollbars {
position: (f64, f64),
size: (f64, f64),
multiplier: (f64, f64),
},
UpdateImageData {
#[serde(rename = "imageData")]
image_data: Vec<FrontendImageData>,
},
UpdateInputHints {
#[serde(rename = "hintData")]
hint_data: HintData,
},
UpdateLayerTreeOptionsLayout {
#[serde(rename = "layoutTarget")]
layout_target: LayoutTarget,
layout: SubLayout,
},
UpdateMenuBarLayout {
#[serde(rename = "layoutTarget")]
layout_target: LayoutTarget,
layout: Vec<MenuColumn>,
},
UpdateMouseCursor {
cursor: MouseCursorIcon,
},
UpdateNodeGraphVisibility {
visible: bool,
},
UpdateOpenDocumentsList {
#[serde(rename = "openDocuments")]
open_documents: Vec<FrontendDocumentDetails>,
},
UpdatePropertyPanelOptionsLayout {
#[serde(rename = "layoutTarget")]
layout_target: LayoutTarget,
layout: SubLayout,
},
UpdatePropertyPanelSectionsLayout {
#[serde(rename = "layoutTarget")]
layout_target: LayoutTarget,
layout: SubLayout,
},
UpdateToolOptionsLayout {
#[serde(rename = "layoutTarget")]
layout_target: LayoutTarget,
layout: SubLayout,
},
UpdateToolShelfLayout {
#[serde(rename = "layoutTarget")]
layout_target: LayoutTarget,
layout: SubLayout,
},
UpdateWorkingColorsLayout {
#[serde(rename = "layoutTarget")]
layout_target: LayoutTarget,
layout: SubLayout,
},
} }

View File

@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize};
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
pub struct FrontendDocumentDetails { pub struct FrontendDocumentDetails {
#[serde(rename = "isSaved")]
pub is_saved: bool, pub is_saved: bool,
pub name: String, pub name: String,
pub id: u64, pub id: u64,

View File

@ -48,7 +48,9 @@ impl LayerMetadata {
pub struct LayerPanelEntry { pub struct LayerPanelEntry {
pub name: String, pub name: String,
pub visible: bool, pub visible: bool,
#[serde(rename = "layerType")]
pub layer_type: LayerDataTypeDiscriminant, pub layer_type: LayerDataTypeDiscriminant,
#[serde(rename = "layerMetadata")]
pub layer_metadata: LayerMetadata, pub layer_metadata: LayerMetadata,
pub path: Vec<LayerId>, pub path: Vec<LayerId>,
pub thumbnail: String, pub thumbnail: String,

View File

@ -295,7 +295,7 @@ export default defineComponent({
// Initialize certain setup tasks required by the editor backend to be ready for the user now that the frontend is ready // Initialize certain setup tasks required by the editor backend to be ready for the user now that the frontend is ready
const platform = operatingSystem(); const platform = operatingSystem();
this.editor.instance.init_after_frontend_ready(platform); this.editor.instance.initAfterFrontendReady(platform);
}, },
beforeUnmount() { beforeUnmount() {
// Call the destructor for each manager // Call the destructor for each manager

View File

@ -256,27 +256,27 @@ export default defineComponent({
const buffer = await file.arrayBuffer(); const buffer = await file.arrayBuffer();
const u8Array = new Uint8Array(buffer); const u8Array = new Uint8Array(buffer);
this.editor.instance.paste_image(file.type, u8Array, e.clientX, e.clientY); this.editor.instance.pasteImage(file.type, u8Array, e.clientX, e.clientY);
} }
}); });
}, },
translateCanvasX(newValue: number) { translateCanvasX(newValue: number) {
const delta = newValue - this.scrollbarPos.x; const delta = newValue - this.scrollbarPos.x;
this.scrollbarPos.x = newValue; this.scrollbarPos.x = newValue;
this.editor.instance.translate_canvas(-delta * this.scrollbarMultiplier.x, 0); this.editor.instance.translateCanvas(-delta * this.scrollbarMultiplier.x, 0);
}, },
translateCanvasY(newValue: number) { translateCanvasY(newValue: number) {
const delta = newValue - this.scrollbarPos.y; const delta = newValue - this.scrollbarPos.y;
this.scrollbarPos.y = newValue; this.scrollbarPos.y = newValue;
this.editor.instance.translate_canvas(0, -delta * this.scrollbarMultiplier.y); this.editor.instance.translateCanvas(0, -delta * this.scrollbarMultiplier.y);
}, },
pageX(delta: number) { pageX(delta: number) {
const move = delta < 0 ? 1 : -1; const move = delta < 0 ? 1 : -1;
this.editor.instance.translate_canvas_by_fraction(move, 0); this.editor.instance.translateCanvasByFraction(move, 0);
}, },
pageY(delta: number) { pageY(delta: number) {
const move = delta < 0 ? 1 : -1; const move = delta < 0 ? 1 : -1;
this.editor.instance.translate_canvas_by_fraction(0, move); this.editor.instance.translateCanvasByFraction(0, move);
}, },
canvasPointerDown(e: PointerEvent) { canvasPointerDown(e: PointerEvent) {
const onEditbox = e.target instanceof HTMLDivElement && e.target.contentEditable; const onEditbox = e.target instanceof HTMLDivElement && e.target.contentEditable;
@ -341,7 +341,7 @@ export default defineComponent({
triggerTextCommit() { triggerTextCommit() {
if (!this.textInput) return; if (!this.textInput) return;
const textCleaned = textInputCleanup(this.textInput.innerText); const textCleaned = textInputCleanup(this.textInput.innerText);
this.editor.instance.on_change_text(textCleaned); this.editor.instance.onChangeText(textCleaned);
}, },
displayEditableTextbox(displayEditableTextbox: DisplayEditableTextbox) { displayEditableTextbox(displayEditableTextbox: DisplayEditableTextbox) {
this.textInput = document.createElement("DIV") as HTMLDivElement; this.textInput = document.createElement("DIV") as HTMLDivElement;
@ -350,14 +350,14 @@ export default defineComponent({
else this.textInput.textContent = `${displayEditableTextbox.text}\n`; else this.textInput.textContent = `${displayEditableTextbox.text}\n`;
this.textInput.contentEditable = "true"; this.textInput.contentEditable = "true";
this.textInput.style.width = displayEditableTextbox.line_width ? `${displayEditableTextbox.line_width}px` : "max-content"; this.textInput.style.width = displayEditableTextbox.lineWidth ? `${displayEditableTextbox.lineWidth}px` : "max-content";
this.textInput.style.height = "auto"; this.textInput.style.height = "auto";
this.textInput.style.fontSize = `${displayEditableTextbox.font_size}px`; this.textInput.style.fontSize = `${displayEditableTextbox.fontSize}px`;
this.textInput.style.color = displayEditableTextbox.color.toRgbaCSS(); this.textInput.style.color = displayEditableTextbox.color.toRgbaCSS();
this.textInput.oninput = (): void => { this.textInput.oninput = (): void => {
if (!this.textInput) return; if (!this.textInput) return;
this.editor.instance.update_bounds(textInputCleanup(this.textInput.innerText)); this.editor.instance.updateBounds(textInputCleanup(this.textInput.innerText));
}; };
}, },
displayRemoveEditableTextbox() { displayRemoveEditableTextbox() {

View File

@ -23,16 +23,16 @@
<div class="indent" :style="{ marginLeft: layerIndent(listing.entry) }"></div> <div class="indent" :style="{ marginLeft: layerIndent(listing.entry) }"></div>
<button <button
v-if="listing.entry.layer_type === 'Folder'" v-if="listing.entry.layerType === 'Folder'"
class="expand-arrow" class="expand-arrow"
:class="{ expanded: listing.entry.layer_metadata.expanded }" :class="{ expanded: listing.entry.layerMetadata.expanded }"
@click.stop="handleExpandArrowClick(listing.entry.path)" @click.stop="handleExpandArrowClick(listing.entry.path)"
></button> ></button>
<LayoutRow <LayoutRow
class="layer" class="layer"
:class="{ selected: listing.entry.layer_metadata.selected }" :class="{ selected: listing.entry.layerMetadata.selected }"
:data-index="index" :data-index="index"
:title="`${listing.entry.name}${devMode ? '\nLayer Path: ' + listing.entry.path.join(' / ') : ''}` || null" :title="listing.entry.tooltip"
:draggable="draggable" :draggable="draggable"
@dragstart="(e) => draggable && dragStart(e, listing)" @dragstart="(e) => draggable && dragStart(e, listing)"
@click.exact="(e) => selectLayer(false, false, false, listing, e)" @click.exact="(e) => selectLayer(false, false, false, listing, e)"
@ -45,17 +45,17 @@
@click.alt="(e) => e.stopPropagation()" @click.alt="(e) => e.stopPropagation()"
> >
<LayoutRow class="layer-type-icon"> <LayoutRow class="layer-type-icon">
<IconLabel v-if="listing.entry.layer_type === 'Folder'" :icon="'NodeFolder'" :iconStyle="'Node'" title="Folder" /> <IconLabel v-if="listing.entry.layerType === 'Folder'" :icon="'NodeFolder'" :iconStyle="'Node'" title="Folder" />
<IconLabel v-else-if="listing.entry.layer_type === 'Image'" :icon="'NodeImage'" :iconStyle="'Node'" title="Image" /> <IconLabel v-else-if="listing.entry.layerType === 'Image'" :icon="'NodeImage'" :iconStyle="'Node'" title="Image" />
<IconLabel v-else-if="listing.entry.layer_type === 'Shape'" :icon="'NodeShape'" :iconStyle="'Node'" title="Shape" /> <IconLabel v-else-if="listing.entry.layerType === 'Shape'" :icon="'NodeShape'" :iconStyle="'Node'" title="Shape" />
<IconLabel v-else-if="listing.entry.layer_type === 'Text'" :icon="'NodeText'" :iconStyle="'Node'" title="Path" /> <IconLabel v-else-if="listing.entry.layerType === 'Text'" :icon="'NodeText'" :iconStyle="'Node'" title="Path" />
</LayoutRow> </LayoutRow>
<LayoutRow class="layer-name" @dblclick="() => onEditLayerName(listing)"> <LayoutRow class="layer-name" @dblclick="() => onEditLayerName(listing)">
<input <input
data-text-input data-text-input
type="text" type="text"
:value="listing.entry.name" :value="listing.entry.name"
:placeholder="listing.entry.layer_type" :placeholder="listing.entry.layerType"
:disabled="!listing.editingName" :disabled="!listing.editingName"
@blur="() => onEditLayerNameDeselect(listing)" @blur="() => onEditLayerNameDeselect(listing)"
@keydown.esc="onEditLayerNameDeselect(listing)" @keydown.esc="onEditLayerNameDeselect(listing)"
@ -292,7 +292,6 @@ export default defineComponent({
// Layer data // Layer data
layerCache: new Map() as Map<string, LayerPanelEntry>, // TODO: replace with BigUint64Array as index layerCache: new Map() as Map<string, LayerPanelEntry>, // TODO: replace with BigUint64Array as index
layers: [] as LayerListingInfo[], layers: [] as LayerListingInfo[],
devMode: process.env.NODE_ENV === "development",
// Interactive dragging // Interactive dragging
draggable: true, draggable: true,
@ -313,10 +312,10 @@ export default defineComponent({
return `${height}px`; return `${height}px`;
}, },
toggleLayerVisibility(path: BigUint64Array) { toggleLayerVisibility(path: BigUint64Array) {
this.editor.instance.toggle_layer_visibility(path); this.editor.instance.toggleLayerVisibility(path);
}, },
handleExpandArrowClick(path: BigUint64Array) { handleExpandArrowClick(path: BigUint64Array) {
this.editor.instance.toggle_layer_expansion(path); this.editor.instance.toggleLayerExpansion(path);
}, },
async onEditLayerName(listing: LayerListingInfo) { async onEditLayerName(listing: LayerListingInfo) {
if (listing.editingName) return; if (listing.editingName) return;
@ -337,7 +336,7 @@ export default defineComponent({
const name = (inputElement as HTMLInputElement).value; const name = (inputElement as HTMLInputElement).value;
listing.editingName = false; listing.editingName = false;
this.editor.instance.set_layer_name(listing.entry.path, name); this.editor.instance.setLayerName(listing.entry.path, name);
}, },
async onEditLayerNameDeselect(listing: LayerListingInfo) { async onEditLayerNameDeselect(listing: LayerListingInfo) {
this.draggable = true; this.draggable = true;
@ -354,14 +353,14 @@ export default defineComponent({
// Pressing the Ctrl key on a Mac, or the Cmd key on another platform, is a violation of the `.exact` qualifier so we filter it out here // Pressing the Ctrl key on a Mac, or the Cmd key on another platform, is a violation of the `.exact` qualifier so we filter it out here
const opposite = platformIsMac() ? ctrl : cmd; const opposite = platformIsMac() ? ctrl : cmd;
if (!opposite) this.editor.instance.select_layer(listing.entry.path, ctrlOrCmd, shift); if (!opposite) this.editor.instance.selectLayer(listing.entry.path, ctrlOrCmd, shift);
// We always want to stop propagation so the click event doesn't pass through the layer and cause a deselection by clicking the layer panel background // We always want to stop propagation so the click event doesn't pass through the layer and cause a deselection by clicking the layer panel background
// This is also why we cover the remaining cases not considered by the `.exact` qualifier, in the last two bindings on the layer element, with a `stopPropagation()` call // This is also why we cover the remaining cases not considered by the `.exact` qualifier, in the last two bindings on the layer element, with a `stopPropagation()` call
event.stopPropagation(); event.stopPropagation();
}, },
async deselectAllLayers() { async deselectAllLayers() {
this.editor.instance.deselect_all_layers(); this.editor.instance.deselectAllLayers();
}, },
calculateDragIndex(tree: HTMLElement, clientY: number): DraggingData { calculateDragIndex(tree: HTMLElement, clientY: number): DraggingData {
const treeChildren = tree.children; const treeChildren = tree.children;
@ -405,9 +404,9 @@ export default defineComponent({
} }
// Inserting below current row // Inserting below current row
else if (distance > -closest && distance > -RANGE_TO_INSERT_WITHIN_BOTTOM_FOLDER_NOT_ROOT && distance < 0) { else if (distance > -closest && distance > -RANGE_TO_INSERT_WITHIN_BOTTOM_FOLDER_NOT_ROOT && distance < 0) {
insertFolder = layer.layer_type === "Folder" ? layer.path : layer.path.slice(0, layer.path.length - 1); insertFolder = layer.layerType === "Folder" ? layer.path : layer.path.slice(0, layer.path.length - 1);
insertIndex = layer.layer_type === "Folder" ? 0 : folderIndex + 1; insertIndex = layer.layerType === "Folder" ? 0 : folderIndex + 1;
highlightFolder = layer.layer_type === "Folder"; highlightFolder = layer.layerType === "Folder";
closest = -distance; closest = -distance;
markerHeight = index === treeChildren.length - 1 ? rect.bottom - INSERT_MARK_OFFSET : rect.bottom; markerHeight = index === treeChildren.length - 1 ? rect.bottom - INSERT_MARK_OFFSET : rect.bottom;
} }
@ -426,7 +425,7 @@ export default defineComponent({
}, },
async dragStart(event: DragEvent, listing: LayerListingInfo) { async dragStart(event: DragEvent, listing: LayerListingInfo) {
const layer = listing.entry; const layer = listing.entry;
if (!layer.layer_metadata.selected) this.selectLayer(event.ctrlKey, event.metaKey, event.shiftKey, listing, event); if (!layer.layerMetadata.selected) this.selectLayer(event.ctrlKey, event.metaKey, event.shiftKey, listing, event);
// Set style of cursor for drag // Set style of cursor for drag
if (event.dataTransfer) { if (event.dataTransfer) {
@ -448,7 +447,7 @@ export default defineComponent({
if (this.draggingData) { if (this.draggingData) {
const { insertFolder, insertIndex } = this.draggingData; const { insertFolder, insertIndex } = this.draggingData;
this.editor.instance.move_layer_in_tree(insertFolder, insertIndex); this.editor.instance.moveLayerInTree(insertFolder, insertIndex);
this.draggingData = undefined; this.draggingData = undefined;
} }

View File

@ -2,7 +2,7 @@
<template> <template>
<div class="widget-layout"> <div class="widget-layout">
<component :is="LayoutGroupType(layoutRow)" :widgetData="layoutRow" :layoutTarget="layout.layout_target" v-for="(layoutRow, index) in layout.layout" :key="index" /> <component :is="LayoutGroupType(layoutRow)" :widgetData="layoutRow" :layoutTarget="layout.layoutTarget" v-for="(layoutRow, index) in layout.layout" :key="index" />
</div> </div>
</template> </template>

View File

@ -122,7 +122,7 @@ export default defineComponent({
}, },
methods: { methods: {
updateLayout(widgetId: bigint, value: unknown) { updateLayout(widgetId: bigint, value: unknown) {
this.editor.instance.update_layout(this.layoutTarget, widgetId, value); this.editor.instance.updateLayout(this.layoutTarget, widgetId, value);
}, },
withoutValue(props: Record<string, unknown>): Record<string, unknown> { withoutValue(props: Record<string, unknown>): Record<string, unknown> {
const { value: _, ...rest } = props; const { value: _, ...rest } = props;

View File

@ -94,7 +94,7 @@ const WidgetSection = defineComponent({
}), }),
methods: { methods: {
updateLayout(widgetId: bigint, value: unknown) { updateLayout(widgetId: bigint, value: unknown) {
this.editor.instance.update_layout(this.layoutTarget, widgetId, value); this.editor.instance.updateLayout(this.layoutTarget, widgetId, value);
}, },
layoutGroupType(layoutGroup: LayoutGroup): unknown { layoutGroupType(layoutGroup: LayoutGroup): unknown {
if (isWidgetRow(layoutGroup)) return WidgetRow; if (isWidgetRow(layoutGroup)) return WidgetRow;

View File

@ -24,7 +24,7 @@
:direction="'Bottom'" :direction="'Bottom'"
:minWidth="240" :minWidth="240"
:drawIcon="true" :drawIcon="true"
:defaultAction="() => editor.instance.request_coming_soon_dialog()" :defaultAction="() => editor.instance.requestComingSoonDialog()"
:ref="(ref: typeof MenuList) => ref && (entry.ref = ref)" :ref="(ref: typeof MenuList) => ref && (entry.ref = ref)"
/> />
</div> </div>
@ -111,7 +111,7 @@ export default defineComponent({
group.map((entry) => ({ group.map((entry) => ({
...entry, ...entry,
children: entry.children ? menuEntryToFrontendMenuEntry(entry.children) : undefined, children: entry.children ? menuEntryToFrontendMenuEntry(entry.children) : undefined,
action: (): void => this.editor.instance.update_layout(updateMenuBarLayout.layout_target, entry.action.widgetId, undefined), action: (): void => this.editor.instance.updateLayout(updateMenuBarLayout.layoutTarget, entry.action.widgetId, undefined),
shortcutRequiresLock: entry.shortcut ? shortcutRequiresLock(entry.shortcut.keys) : undefined, shortcutRequiresLock: entry.shortcut ? shortcutRequiresLock(entry.shortcut.keys) : undefined,
})) }))
); );

View File

@ -105,11 +105,11 @@ export default defineComponent({
}, },
primaryColorChanged(color: RGBA) { primaryColorChanged(color: RGBA) {
const newColor = rgbaToDecimalRgba(color); const newColor = rgbaToDecimalRgba(color);
this.editor.instance.update_primary_color(newColor.r, newColor.g, newColor.b, newColor.a); this.editor.instance.updatePrimaryColor(newColor.r, newColor.g, newColor.b, newColor.a);
}, },
secondaryColorChanged(color: RGBA) { secondaryColorChanged(color: RGBA) {
const newColor = rgbaToDecimalRgba(color); const newColor = rgbaToDecimalRgba(color);
this.editor.instance.update_secondary_color(newColor.r, newColor.g, newColor.b, newColor.a); this.editor.instance.updateSecondaryColor(newColor.r, newColor.g, newColor.b, newColor.a);
}, },
}, },
}); });

View File

@ -70,7 +70,7 @@ export default defineComponent({
}, },
mounted() { mounted() {
this.editor.subscriptions.subscribeJsMessage(UpdateInputHints, (updateInputHints) => { this.editor.subscriptions.subscribeJsMessage(UpdateInputHints, (updateInputHints) => {
this.hintData = updateInputHints.hint_data; this.hintData = updateInputHints.hintData;
}); });
}, },
components: { components: {

View File

@ -252,10 +252,10 @@ export default defineComponent({
}, },
methods: { methods: {
newDocument() { newDocument() {
this.editor.instance.new_document_dialog(); this.editor.instance.newDocumentDialog();
}, },
openDocument() { openDocument() {
this.editor.instance.document_open(); this.editor.instance.documentOpen();
}, },
platformModifiers(reservedKey: boolean): KeysGroup { platformModifiers(reservedKey: boolean): KeysGroup {
// TODO: Remove this by properly feeding these keys from a layout provided by the backend // TODO: Remove this by properly feeding these keys from a layout provided by the backend

View File

@ -8,8 +8,8 @@
:tabCloseButtons="true" :tabCloseButtons="true"
:tabMinWidths="true" :tabMinWidths="true"
:tabLabels="portfolio.state.documents.map((doc) => doc.displayName)" :tabLabels="portfolio.state.documents.map((doc) => doc.displayName)"
:clickAction="(tabIndex) => editor.instance.select_document(portfolio.state.documents[tabIndex].id)" :clickAction="(tabIndex) => editor.instance.selectDocument(portfolio.state.documents[tabIndex].id)"
:closeAction="(tabIndex) => editor.instance.close_document_with_confirmation(portfolio.state.documents[tabIndex].id)" :closeAction="(tabIndex) => editor.instance.closeDocumentWithConfirmation(portfolio.state.documents[tabIndex].id)"
:tabActiveIndex="portfolio.state.activeDocumentIndex" :tabActiveIndex="portfolio.state.activeDocumentIndex"
ref="documentsPanel" ref="documentsPanel"
/> />

View File

@ -4,15 +4,15 @@ import { UpdateImageData } from "@/wasm-communication/messages";
export function createBlobManager(editor: Editor): void { export function createBlobManager(editor: Editor): void {
// Subscribe to process backend event // Subscribe to process backend event
editor.subscriptions.subscribeJsMessage(UpdateImageData, (updateImageData) => { editor.subscriptions.subscribeJsMessage(UpdateImageData, (updateImageData) => {
updateImageData.image_data.forEach(async (element) => { updateImageData.imageData.forEach(async (element) => {
// Using updateImageData.image_data.buffer returns undefined for some reason? // Using updateImageData.imageData.buffer returns undefined for some reason?
const blob = new Blob([new Uint8Array(element.image_data.values()).buffer], { type: element.mime }); const blob = new Blob([new Uint8Array(element.imageData.values()).buffer], { type: element.mime });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const image = await createImageBitmap(blob); const image = await createImageBitmap(blob);
editor.instance.set_image_blob_url(element.path, url, image.width, image.height); editor.instance.setImageBlobUrl(element.path, url, image.width, image.height);
}); });
}); });
} }

View File

@ -5,6 +5,6 @@ export function createClipboardManager(editor: Editor): void {
// Subscribe to process backend event // Subscribe to process backend event
editor.subscriptions.subscribeJsMessage(TriggerTextCopy, (triggerTextCopy) => { editor.subscriptions.subscribeJsMessage(TriggerTextCopy, (triggerTextCopy) => {
// If the Clipboard API is supported in the browser, copy text to the clipboard // If the Clipboard API is supported in the browser, copy text to the clipboard
navigator.clipboard?.writeText?.(triggerTextCopy.copy_text); navigator.clipboard?.writeText?.(triggerTextCopy.copyText);
}); });
} }

View File

@ -106,7 +106,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
if (await shouldRedirectKeyboardEventToBackend(e)) { if (await shouldRedirectKeyboardEventToBackend(e)) {
e.preventDefault(); e.preventDefault();
const modifiers = makeKeyboardModifiersBitfield(e); const modifiers = makeKeyboardModifiersBitfield(e);
editor.instance.on_key_down(key, modifiers); editor.instance.onKeyDown(key, modifiers);
return; return;
} }
@ -121,7 +121,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
if (await shouldRedirectKeyboardEventToBackend(e)) { if (await shouldRedirectKeyboardEventToBackend(e)) {
e.preventDefault(); e.preventDefault();
const modifiers = makeKeyboardModifiersBitfield(e); const modifiers = makeKeyboardModifiersBitfield(e);
editor.instance.on_key_up(key, modifiers); editor.instance.onKeyUp(key, modifiers);
} }
} }
@ -146,7 +146,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
} }
const modifiers = makeKeyboardModifiersBitfield(e); const modifiers = makeKeyboardModifiersBitfield(e);
editor.instance.on_mouse_move(e.clientX, e.clientY, e.buttons, modifiers); editor.instance.onMouseMove(e.clientX, e.clientY, e.buttons, modifiers);
} }
function onPointerDown(e: PointerEvent): void { function onPointerDown(e: PointerEvent): void {
@ -162,13 +162,13 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
} }
if (!inTextInput) { if (!inTextInput) {
if (textInput) editor.instance.on_change_text(textInputCleanup(textInput.innerText)); if (textInput) editor.instance.onChangeText(textInputCleanup(textInput.innerText));
else viewportPointerInteractionOngoing = isTargetingCanvas instanceof Element; else viewportPointerInteractionOngoing = isTargetingCanvas instanceof Element;
} }
if (viewportPointerInteractionOngoing) { if (viewportPointerInteractionOngoing) {
const modifiers = makeKeyboardModifiersBitfield(e); const modifiers = makeKeyboardModifiersBitfield(e);
editor.instance.on_mouse_down(e.clientX, e.clientY, e.buttons, modifiers); editor.instance.onMouseDown(e.clientX, e.clientY, e.buttons, modifiers);
} }
} }
@ -177,7 +177,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
if (!textInput) { if (!textInput) {
const modifiers = makeKeyboardModifiersBitfield(e); const modifiers = makeKeyboardModifiersBitfield(e);
editor.instance.on_mouse_up(e.clientX, e.clientY, e.buttons, modifiers); editor.instance.onMouseUp(e.clientX, e.clientY, e.buttons, modifiers);
} }
} }
@ -186,7 +186,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
if (!textInput) { if (!textInput) {
const modifiers = makeKeyboardModifiersBitfield(e); const modifiers = makeKeyboardModifiersBitfield(e);
editor.instance.on_double_click(e.clientX, e.clientY, e.buttons, modifiers); editor.instance.onDoubleClick(e.clientX, e.clientY, e.buttons, modifiers);
} }
} }
@ -213,7 +213,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
if (isTargetingCanvas) { if (isTargetingCanvas) {
e.preventDefault(); e.preventDefault();
const modifiers = makeKeyboardModifiersBitfield(e); const modifiers = makeKeyboardModifiersBitfield(e);
editor.instance.on_wheel_scroll(e.clientX, e.clientY, e.buttons, e.deltaX, e.deltaY, e.deltaZ, modifiers); editor.instance.onWheelScroll(e.clientX, e.clientY, e.buttons, e.deltaX, e.deltaY, e.deltaZ, modifiers);
} }
} }
@ -233,20 +233,20 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
const flattened = boundsOfViewports.flat(); const flattened = boundsOfViewports.flat();
const data = Float64Array.from(flattened); const data = Float64Array.from(flattened);
if (boundsOfViewports.length > 0) editor.instance.bounds_of_viewports(data); if (boundsOfViewports.length > 0) editor.instance.boundsOfViewports(data);
} }
function onBeforeUnload(e: BeforeUnloadEvent): void { function onBeforeUnload(e: BeforeUnloadEvent): void {
const activeDocument = document.state.documents[document.state.activeDocumentIndex]; const activeDocument = document.state.documents[document.state.activeDocumentIndex];
if (!activeDocument.is_saved) editor.instance.trigger_auto_save(activeDocument.id); if (!activeDocument.isSaved) editor.instance.triggerAutoSave(activeDocument.id);
// Skip the message if the editor crashed, since work is already lost // Skip the message if the editor crashed, since work is already lost
if (editor.instance.has_crashed()) return; if (editor.instance.hasCrashed()) return;
// Skip the message during development, since it's annoying when testing // Skip the message during development, since it's annoying when testing
if (process.env.NODE_ENV === "development") return; if (process.env.NODE_ENV === "development") return;
const allDocumentsSaved = document.state.documents.reduce((acc, doc) => acc && doc.is_saved, true); const allDocumentsSaved = document.state.documents.reduce((acc, doc) => acc && doc.isSaved, true);
if (!allDocumentsSaved) { if (!allDocumentsSaved) {
e.returnValue = "Unsaved work will be lost if the web browser tab is closed. Close anyway?"; e.returnValue = "Unsaved work will be lost if the web browser tab is closed. Close anyway?";
e.preventDefault(); e.preventDefault();
@ -262,7 +262,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
if (item.type === "text/plain") { if (item.type === "text/plain") {
item.getAsString((text) => { item.getAsString((text) => {
if (text.startsWith("graphite/layer: ")) { if (text.startsWith("graphite/layer: ")) {
editor.instance.paste_serialized_data(text.substring(16, text.length)); editor.instance.pasteSerializedData(text.substring(16, text.length));
} }
}); });
} }
@ -272,7 +272,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
file.arrayBuffer().then((buffer): void => { file.arrayBuffer().then((buffer): void => {
const u8Array = new Uint8Array(buffer); const u8Array = new Uint8Array(buffer);
editor.instance.paste_image(file.type, u8Array); editor.instance.pasteImage(file.type, u8Array);
}); });
} }
}); });
@ -305,7 +305,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
const text = reader.result as string; const text = reader.result as string;
if (text.startsWith("graphite/layer: ")) { if (text.startsWith("graphite/layer: ")) {
editor.instance.paste_serialized_data(text.substring(16, text.length)); editor.instance.pasteSerializedData(text.substring(16, text.length));
} }
}; };
reader.readAsText(blob); reader.readAsText(blob);
@ -319,7 +319,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
reader.onload = (): void => { reader.onload = (): void => {
const u8Array = new Uint8Array(reader.result as ArrayBuffer); const u8Array = new Uint8Array(reader.result as ArrayBuffer);
editor.instance.paste_image(imageType, u8Array); editor.instance.pasteImage(imageType, u8Array);
}; };
reader.readAsArrayBuffer(blob); reader.readAsArrayBuffer(blob);
} }
@ -343,7 +343,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
}; };
const message = Object.entries(matchMessage).find(([key]) => String(err).includes(key))?.[1] || String(err); const message = Object.entries(matchMessage).find(([key]) => String(err).includes(key))?.[1] || String(err);
editor.instance.error_dialog("Cannot access clipboard", message); editor.instance.errorDialog("Cannot access clipboard", message);
} }
}); });

View File

@ -19,7 +19,7 @@ export function createLocalizationManager(editor: Editor): void {
// Subscribe to process backend event // Subscribe to process backend event
editor.subscriptions.subscribeJsMessage(TriggerAboutGraphiteLocalizedCommitDate, (triggerAboutGraphiteLocalizedCommitDate) => { editor.subscriptions.subscribeJsMessage(TriggerAboutGraphiteLocalizedCommitDate, (triggerAboutGraphiteLocalizedCommitDate) => {
const localized = localizeTimestamp(triggerAboutGraphiteLocalizedCommitDate.commit_date); const localized = localizeTimestamp(triggerAboutGraphiteLocalizedCommitDate.commitDate);
editor.instance.request_about_graphite_dialog_with_localized_commit_date(localized); editor.instance.requestAboutGraphiteDialogWithLocalizedCommitDate(localized);
}); });
} }

View File

@ -13,7 +13,7 @@ export function createPanicManager(editor: Editor, dialogState: DialogState): vo
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
(Error as any).stackTraceLimit = Infinity; (Error as any).stackTraceLimit = Infinity;
const stackTrace = new Error().stack || ""; const stackTrace = new Error().stack || "";
const panicDetails = `${displayDialogPanic.panic_info}${stackTrace ? `\n\n${stackTrace}` : ""}`; const panicDetails = `${displayDialogPanic.panicInfo}${stackTrace ? `\n\n${stackTrace}` : ""}`;
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error(panicDetails); console.error(panicDetails);
@ -29,8 +29,7 @@ function preparePanicDialog(header: string, details: string, panicDetails: strin
{ rowWidgets: [new Widget({ kind: "TextLabel", value: header, bold: true, italic: false, tableAlign: false, multiline: false }, 0n)] }, { rowWidgets: [new Widget({ kind: "TextLabel", value: header, bold: true, italic: false, tableAlign: false, multiline: false }, 0n)] },
{ rowWidgets: [new Widget({ kind: "TextLabel", value: details, bold: false, italic: false, tableAlign: false, multiline: true }, 1n)] }, { rowWidgets: [new Widget({ kind: "TextLabel", value: details, bold: false, italic: false, tableAlign: false, multiline: true }, 1n)] },
], ],
// eslint-disable-next-line camelcase layoutTarget: null,
layout_target: null,
}; };
const reloadButton: TextButtonWidget = { const reloadButton: TextButtonWidget = {

View File

@ -36,7 +36,7 @@ export async function createPersistenceManager(editor: Editor, portfolio: Portfo
storeDocumentOrder(); storeDocumentOrder();
}); });
editor.subscriptions.subscribeJsMessage(TriggerIndexedDbRemoveDocument, async (removeAutoSaveDocument) => { editor.subscriptions.subscribeJsMessage(TriggerIndexedDbRemoveDocument, async (removeAutoSaveDocument) => {
removeDocument(removeAutoSaveDocument.document_id); removeDocument(removeAutoSaveDocument.documentId);
}); });
// Open the IndexedDB database connection and save it to this variable, which is a promise that resolves once the connection is open // Open the IndexedDB database connection and save it to this variable, which is a promise that resolves once the connection is open
@ -61,7 +61,7 @@ export async function createPersistenceManager(editor: Editor, portfolio: Portfo
Error on opening IndexDB: Error on opening IndexDB:
${dbOpenRequest.error} ${dbOpenRequest.error}
`; `;
editor.instance.error_dialog("Document auto-save doesn't work in this browser", errorText); editor.instance.errorDialog("Document auto-save doesn't work in this browser", errorText);
}; };
dbOpenRequest.onsuccess = (): void => { dbOpenRequest.onsuccess = (): void => {
@ -82,10 +82,10 @@ export async function createPersistenceManager(editor: Editor, portfolio: Portfo
.map((id) => previouslySavedDocuments.find((autoSave) => autoSave.details.id === id)) .map((id) => previouslySavedDocuments.find((autoSave) => autoSave.details.id === id))
.filter((x) => x !== undefined) as TriggerIndexedDbWriteDocument[]; .filter((x) => x !== undefined) as TriggerIndexedDbWriteDocument[];
const currentDocumentVersion = editor.instance.graphite_document_version(); const currentDocumentVersion = editor.instance.graphiteDocumentVersion();
orderedSavedDocuments.forEach((doc: TriggerIndexedDbWriteDocument) => { orderedSavedDocuments.forEach((doc: TriggerIndexedDbWriteDocument) => {
if (doc.version === currentDocumentVersion) { if (doc.version === currentDocumentVersion) {
editor.instance.open_auto_saved_document(BigInt(doc.details.id), doc.details.name, doc.details.is_saved, doc.document); editor.instance.openAutoSavedDocument(BigInt(doc.details.id), doc.details.name, doc.details.isSaved, doc.document);
} else { } else {
removeDocument(doc.details.id); removeDocument(doc.details.id);
} }

View File

@ -48,12 +48,12 @@ export function createFontsState(editor: Editor) {
// Subscribe to process backend events // Subscribe to process backend events
editor.subscriptions.subscribeJsMessage(TriggerFontLoad, async (triggerFontLoad) => { editor.subscriptions.subscribeJsMessage(TriggerFontLoad, async (triggerFontLoad) => {
const url = await getFontFileUrl(triggerFontLoad.font.font_family, triggerFontLoad.font.font_style); const url = await getFontFileUrl(triggerFontLoad.font.fontFamily, triggerFontLoad.font.fontStyle);
if (url) { if (url) {
const response = await (await fetch(url)).arrayBuffer(); const response = await (await fetch(url)).arrayBuffer();
editor.instance.on_font_load(triggerFontLoad.font.font_family, triggerFontLoad.font.font_style, url, new Uint8Array(response), triggerFontLoad.is_default); editor.instance.onFontLoad(triggerFontLoad.font.fontFamily, triggerFontLoad.font.fontStyle, url, new Uint8Array(response), triggerFontLoad.isDefault);
} else { } else {
editor.instance.error_dialog("Failed to load font", `The font ${triggerFontLoad.font.font_family} with style ${triggerFontLoad.font.font_style} does not exist`); editor.instance.errorDialog("Failed to load font", `The font ${triggerFontLoad.font.fontFamily} with style ${triggerFontLoad.font.fontStyle} does not exist`);
} }
}); });

View File

@ -15,21 +15,21 @@ export function createPortfolioState(editor: Editor) {
// Set up message subscriptions on creation // Set up message subscriptions on creation
editor.subscriptions.subscribeJsMessage(UpdateOpenDocumentsList, (updateOpenDocumentList) => { editor.subscriptions.subscribeJsMessage(UpdateOpenDocumentsList, (updateOpenDocumentList) => {
state.documents = updateOpenDocumentList.open_documents; state.documents = updateOpenDocumentList.openDocuments;
}); });
editor.subscriptions.subscribeJsMessage(UpdateActiveDocument, (updateActiveDocument) => { editor.subscriptions.subscribeJsMessage(UpdateActiveDocument, (updateActiveDocument) => {
// Assume we receive a correct document id // Assume we receive a correct document id
const activeId = state.documents.findIndex((doc) => doc.id === updateActiveDocument.document_id); const activeId = state.documents.findIndex((doc) => doc.id === updateActiveDocument.documentId);
state.activeDocumentIndex = activeId; state.activeDocumentIndex = activeId;
}); });
editor.subscriptions.subscribeJsMessage(TriggerOpenDocument, async () => { editor.subscriptions.subscribeJsMessage(TriggerOpenDocument, async () => {
const extension = editor.instance.file_save_suffix(); const extension = editor.instance.fileSaveSuffix();
const data = await upload(extension, "text"); const data = await upload(extension, "text");
editor.instance.open_document_file(data.filename, data.content); editor.instance.openDocumentFile(data.filename, data.content);
}); });
editor.subscriptions.subscribeJsMessage(TriggerImport, async () => { editor.subscriptions.subscribeJsMessage(TriggerImport, async () => {
const data = await upload("image/*", "data"); const data = await upload("image/*", "data");
editor.instance.paste_image(data.type, Uint8Array.from(data.content)); editor.instance.pasteImage(data.type, Uint8Array.from(data.content));
}); });
editor.subscriptions.subscribeJsMessage(TriggerFileDownload, (triggerFileDownload) => { editor.subscriptions.subscribeJsMessage(TriggerFileDownload, (triggerFileDownload) => {
download(triggerFileDownload.name, triggerFileDownload.document); download(triggerFileDownload.name, triggerFileDownload.document);

View File

@ -19,7 +19,7 @@ export async function initWasm(): Promise<void> {
// Provide a random starter seed which must occur after initializing the WASM module, since WASM can't generate its own random numbers // Provide a random starter seed which must occur after initializing the WASM module, since WASM can't generate its own random numbers
const randomSeed = BigInt(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)); const randomSeed = BigInt(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER));
wasmImport?.set_random_seed(randomSeed); wasmImport?.setRandomSeed(randomSeed);
} }
// Should be called after running `initWasm()` and its promise resolving // Should be called after running `initWasm()` and its promise resolving

View File

@ -1,4 +1,3 @@
/* eslint-disable camelcase */
/* eslint-disable max-classes-per-file */ /* eslint-disable max-classes-per-file */
import { Transform, Type, plainToClass } from "class-transformer"; import { Transform, Type, plainToClass } from "class-transformer";
@ -26,7 +25,7 @@ export class UpdateNodeGraphVisibility extends JsMessage {
export class UpdateOpenDocumentsList extends JsMessage { export class UpdateOpenDocumentsList extends JsMessage {
@Type(() => FrontendDocumentDetails) @Type(() => FrontendDocumentDetails)
readonly open_documents!: FrontendDocumentDetails[]; readonly openDocuments!: FrontendDocumentDetails[];
} }
// Allows the auto save system to use a string for the id rather than a BigInt. // Allows the auto save system to use a string for the id rather than a BigInt.
@ -36,12 +35,12 @@ export class UpdateOpenDocumentsList extends JsMessage {
export abstract class DocumentDetails { export abstract class DocumentDetails {
readonly name!: string; readonly name!: string;
readonly is_saved!: boolean; readonly isSaved!: boolean;
readonly id!: bigint | string; readonly id!: bigint | string;
get displayName(): string { get displayName(): string {
return `${this.name}${this.is_saved ? "" : "*"}`; return `${this.name}${this.isSaved ? "" : "*"}`;
} }
} }
@ -66,12 +65,12 @@ export class IndexedDbDocumentDetails extends DocumentDetails {
export class TriggerIndexedDbRemoveDocument extends JsMessage { export class TriggerIndexedDbRemoveDocument extends JsMessage {
// Use a string since IndexedDB can not use BigInts for keys // Use a string since IndexedDB can not use BigInts for keys
@Transform(({ value }: { value: bigint }) => value.toString()) @Transform(({ value }: { value: bigint }) => value.toString())
document_id!: string; documentId!: string;
} }
export class UpdateInputHints extends JsMessage { export class UpdateInputHints extends JsMessage {
@Type(() => HintInfo) @Type(() => HintInfo)
readonly hint_data!: HintData; readonly hintData!: HintData;
} }
export type HintData = HintGroup[]; export type HintData = HintGroup[];
@ -137,11 +136,11 @@ export class Color {
} }
export class UpdateActiveDocument extends JsMessage { export class UpdateActiveDocument extends JsMessage {
readonly document_id!: bigint; readonly documentId!: bigint;
} }
export class DisplayDialogPanic extends JsMessage { export class DisplayDialogPanic extends JsMessage {
readonly panic_info!: string; readonly panicInfo!: string;
readonly header!: string; readonly header!: string;
@ -245,11 +244,11 @@ interface DataBuffer {
length: bigint; length: bigint;
} }
export function newUpdateDocumentLayerTreeStructure(input: { data_buffer: DataBuffer }, wasm: WasmRawInstance): UpdateDocumentLayerTreeStructure { export function newUpdateDocumentLayerTreeStructure(input: { dataBuffer: DataBuffer }, wasm: WasmRawInstance): UpdateDocumentLayerTreeStructure {
const pointerNum = Number(input.data_buffer.pointer); const pointerNum = Number(input.dataBuffer.pointer);
const lengthNum = Number(input.data_buffer.length); const lengthNum = Number(input.dataBuffer.length);
const wasmMemoryBuffer = wasm.wasm_memory().buffer; const wasmMemoryBuffer = wasm.wasmMemory().buffer;
// Decode the folder structure encoding // Decode the folder structure encoding
const encoding = new DataView(wasmMemoryBuffer, pointerNum, lengthNum); const encoding = new DataView(wasmMemoryBuffer, pointerNum, lengthNum);
@ -302,9 +301,9 @@ export function newUpdateDocumentLayerTreeStructure(input: { data_buffer: DataBu
export class DisplayEditableTextbox extends JsMessage { export class DisplayEditableTextbox extends JsMessage {
readonly text!: string; readonly text!: string;
readonly line_width!: undefined | number; readonly lineWidth!: undefined | number;
readonly font_size!: number; readonly fontSize!: number;
@Type(() => Color) @Type(() => Color)
readonly color!: Color; readonly color!: Color;
@ -312,7 +311,7 @@ export class DisplayEditableTextbox extends JsMessage {
export class UpdateImageData extends JsMessage { export class UpdateImageData extends JsMessage {
@Type(() => ImageData) @Type(() => ImageData)
readonly image_data!: ImageData[]; readonly imageData!: ImageData[];
} }
export class DisplayRemoveEditableTextbox extends JsMessage {} export class DisplayRemoveEditableTextbox extends JsMessage {}
@ -327,13 +326,13 @@ export class LayerPanelEntry {
visible!: boolean; visible!: boolean;
layer_type!: LayerType; layerType!: LayerType;
@Transform(({ value }: { value: bigint[] }) => new BigUint64Array(value)) @Transform(({ value }: { value: bigint[] }) => new BigUint64Array(value))
path!: BigUint64Array; path!: BigUint64Array;
@Type(() => LayerMetadata) @Type(() => LayerMetadata)
layer_metadata!: LayerMetadata; layerMetadata!: LayerMetadata;
thumbnail!: string; thumbnail!: string;
} }
@ -351,22 +350,22 @@ export class ImageData {
readonly mime!: string; readonly mime!: string;
readonly image_data!: Uint8Array; readonly imageData!: Uint8Array;
} }
export class DisplayDialogDismiss extends JsMessage {} export class DisplayDialogDismiss extends JsMessage {}
export class Font { export class Font {
font_family!: string; fontFamily!: string;
font_style!: string; fontStyle!: string;
} }
export class TriggerFontLoad extends JsMessage { export class TriggerFontLoad extends JsMessage {
@Type(() => Font) @Type(() => Font)
font!: Font; font!: Font;
is_default!: boolean; isDefault!: boolean;
} }
export class TriggerVisitLink extends JsMessage { export class TriggerVisitLink extends JsMessage {
@ -376,11 +375,11 @@ export class TriggerVisitLink extends JsMessage {
export class TriggerTextCommit extends JsMessage {} export class TriggerTextCommit extends JsMessage {}
export class TriggerTextCopy extends JsMessage { export class TriggerTextCopy extends JsMessage {
readonly copy_text!: string; readonly copyText!: string;
} }
export class TriggerAboutGraphiteLocalizedCommitDate extends JsMessage { export class TriggerAboutGraphiteLocalizedCommitDate extends JsMessage {
readonly commit_date!: string; readonly commitDate!: string;
} }
export class TriggerViewportResize extends JsMessage {} export class TriggerViewportResize extends JsMessage {}
@ -647,13 +646,13 @@ function hoistWidgetHolders(widgetHolders: any[]): Widget[] {
// WIDGET LAYOUT // WIDGET LAYOUT
export interface WidgetLayout { export interface WidgetLayout {
layout_target: unknown; layoutTarget: unknown;
layout: LayoutGroup[]; layout: LayoutGroup[];
} }
export function defaultWidgetLayout(): WidgetLayout { export function defaultWidgetLayout(): WidgetLayout {
return { return {
layout_target: null, layoutTarget: null,
layout: [], layout: [],
}; };
} }
@ -708,7 +707,7 @@ function createWidgetLayout(widgetLayout: any[]): LayoutGroup[] {
// WIDGET LAYOUTS // WIDGET LAYOUTS
export class UpdateDialogDetails extends JsMessage implements WidgetLayout { export class UpdateDialogDetails extends JsMessage implements WidgetLayout {
layout_target!: unknown; layoutTarget!: unknown;
// TODO: Replace `any` with correct typing // TODO: Replace `any` with correct typing
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -717,7 +716,7 @@ export class UpdateDialogDetails extends JsMessage implements WidgetLayout {
} }
export class UpdateDocumentModeLayout extends JsMessage implements WidgetLayout { export class UpdateDocumentModeLayout extends JsMessage implements WidgetLayout {
layout_target!: unknown; layoutTarget!: unknown;
// TODO: Replace `any` with correct typing // TODO: Replace `any` with correct typing
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -726,7 +725,7 @@ export class UpdateDocumentModeLayout extends JsMessage implements WidgetLayout
} }
export class UpdateToolOptionsLayout extends JsMessage implements WidgetLayout { export class UpdateToolOptionsLayout extends JsMessage implements WidgetLayout {
layout_target!: unknown; layoutTarget!: unknown;
// TODO: Replace `any` with correct typing // TODO: Replace `any` with correct typing
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -735,7 +734,7 @@ export class UpdateToolOptionsLayout extends JsMessage implements WidgetLayout {
} }
export class UpdateDocumentBarLayout extends JsMessage implements WidgetLayout { export class UpdateDocumentBarLayout extends JsMessage implements WidgetLayout {
layout_target!: unknown; layoutTarget!: unknown;
// TODO: Replace `any` with correct typing // TODO: Replace `any` with correct typing
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -744,7 +743,7 @@ export class UpdateDocumentBarLayout extends JsMessage implements WidgetLayout {
} }
export class UpdateToolShelfLayout extends JsMessage implements WidgetLayout { export class UpdateToolShelfLayout extends JsMessage implements WidgetLayout {
layout_target!: unknown; layoutTarget!: unknown;
// TODO: Replace `any` with correct typing // TODO: Replace `any` with correct typing
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -753,7 +752,7 @@ export class UpdateToolShelfLayout extends JsMessage implements WidgetLayout {
} }
export class UpdateWorkingColorsLayout extends JsMessage implements WidgetLayout { export class UpdateWorkingColorsLayout extends JsMessage implements WidgetLayout {
layout_target!: unknown; layoutTarget!: unknown;
// TODO: Replace `any` with correct typing // TODO: Replace `any` with correct typing
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -762,7 +761,7 @@ export class UpdateWorkingColorsLayout extends JsMessage implements WidgetLayout
} }
export class UpdatePropertyPanelOptionsLayout extends JsMessage implements WidgetLayout { export class UpdatePropertyPanelOptionsLayout extends JsMessage implements WidgetLayout {
layout_target!: unknown; layoutTarget!: unknown;
// TODO: Replace `any` with correct typing // TODO: Replace `any` with correct typing
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -771,7 +770,7 @@ export class UpdatePropertyPanelOptionsLayout extends JsMessage implements Widge
} }
export class UpdatePropertyPanelSectionsLayout extends JsMessage implements WidgetLayout { export class UpdatePropertyPanelSectionsLayout extends JsMessage implements WidgetLayout {
layout_target!: unknown; layoutTarget!: unknown;
// TODO: Replace `any` with correct typing // TODO: Replace `any` with correct typing
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -780,7 +779,7 @@ export class UpdatePropertyPanelSectionsLayout extends JsMessage implements Widg
} }
export class UpdateLayerTreeOptionsLayout extends JsMessage implements WidgetLayout { export class UpdateLayerTreeOptionsLayout extends JsMessage implements WidgetLayout {
layout_target!: unknown; layoutTarget!: unknown;
// TODO: Replace `any` with correct typing // TODO: Replace `any` with correct typing
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -789,7 +788,7 @@ export class UpdateLayerTreeOptionsLayout extends JsMessage implements WidgetLay
} }
export class UpdateMenuBarLayout extends JsMessage { export class UpdateMenuBarLayout extends JsMessage {
layout_target!: unknown; layoutTarget!: unknown;
// TODO: Replace `any` with correct typing // TODO: Replace `any` with correct typing
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any

View File

@ -11,7 +11,7 @@ module.exports = {
chainWebpack: (config) => { chainWebpack: (config) => {
// WASM Pack Plugin integrates compiled Rust code (.wasm) and generated wasm-bindgen code (.js) with the webpack bundle // WASM Pack Plugin integrates compiled Rust code (.wasm) and generated wasm-bindgen code (.js) with the webpack bundle
// Use this JS to import the bundled Rust entry points: const wasm = import("@/../wasm/pkg").then(panicProxy); // Use this JS to import the bundled Rust entry points: const wasm = import("@/../wasm/pkg").then(panicProxy);
// Then call WASM functions with: (await wasm).function_name() // Then call WASM functions with: (await wasm).functionName()
// https://github.com/wasm-tool/wasm-pack-plugin // https://github.com/wasm-tool/wasm-pack-plugin
config config
// https://cli.vuejs.org/guide/webpack.html#modifying-options-of-a-plugin // https://cli.vuejs.org/guide/webpack.html#modifying-options-of-a-plugin

View File

@ -23,13 +23,13 @@ use wasm_bindgen::prelude::*;
/// Set the random seed used by the editor by calling this from JS upon initialization. /// Set the random seed used by the editor by calling this from JS upon initialization.
/// This is necessary because WASM doesn't have a random number generator. /// This is necessary because WASM doesn't have a random number generator.
#[wasm_bindgen] #[wasm_bindgen(js_name = setRandomSeed)]
pub fn set_random_seed(seed: u64) { pub fn set_random_seed(seed: u64) {
editor::application::set_uuid_seed(seed); editor::application::set_uuid_seed(seed);
} }
/// Provides a handle to access the raw WASM memory /// Provides a handle to access the raw WASM memory
#[wasm_bindgen] #[wasm_bindgen(js_name = wasmMemory)]
pub fn wasm_memory() -> JsValue { pub fn wasm_memory() -> JsValue {
wasm_bindgen::memory() wasm_bindgen::memory()
} }
@ -111,6 +111,7 @@ impl JsEditorHandle {
// the backend from the web frontend. // the backend from the web frontend.
// ======================================================================== // ========================================================================
#[wasm_bindgen(js_name = initAfterFrontendReady)]
pub fn init_after_frontend_ready(&self, platform: String) { pub fn init_after_frontend_ready(&self, platform: String) {
let platform = match platform.as_str() { let platform = match platform.as_str() {
"Windows" => Platform::Windows, "Windows" => Platform::Windows,
@ -124,29 +125,32 @@ impl JsEditorHandle {
} }
/// Displays a dialog with an error message /// Displays a dialog with an error message
#[wasm_bindgen(js_name = errorDialog)]
pub fn error_dialog(&self, title: String, description: String) { pub fn error_dialog(&self, title: String, description: String) {
let message = DialogMessage::DisplayDialogError { title, description }; let message = DialogMessage::DisplayDialogError { title, description };
self.dispatch(message); self.dispatch(message);
} }
/// Answer whether or not the editor has crashed /// Answer whether or not the editor has crashed
#[wasm_bindgen(js_name = hasCrashed)]
pub fn has_crashed(&self) -> bool { pub fn has_crashed(&self) -> bool {
EDITOR_HAS_CRASHED.load(Ordering::SeqCst) EDITOR_HAS_CRASHED.load(Ordering::SeqCst)
} }
/// Get the constant `FILE_SAVE_SUFFIX` /// Get the constant `FILE_SAVE_SUFFIX`
#[wasm_bindgen] #[wasm_bindgen(js_name = fileSaveSuffix)]
pub fn file_save_suffix(&self) -> String { pub fn file_save_suffix(&self) -> String {
FILE_SAVE_SUFFIX.into() FILE_SAVE_SUFFIX.into()
} }
/// Get the constant `GRAPHITE_DOCUMENT_VERSION` /// Get the constant `GRAPHITE_DOCUMENT_VERSION`
#[wasm_bindgen] #[wasm_bindgen(js_name = graphiteDocumentVersion)]
pub fn graphite_document_version(&self) -> String { pub fn graphite_document_version(&self) -> String {
GRAPHITE_DOCUMENT_VERSION.to_string() GRAPHITE_DOCUMENT_VERSION.to_string()
} }
/// Update layout of a given UI /// Update layout of a given UI
#[wasm_bindgen(js_name = updateLayout)]
pub fn update_layout(&self, layout_target: JsValue, widget_id: u64, value: JsValue) -> Result<(), JsValue> { pub fn update_layout(&self, layout_target: JsValue, widget_id: u64, value: JsValue) -> Result<(), JsValue> {
match (from_value(layout_target), from_value(value)) { match (from_value(layout_target), from_value(value)) {
(Ok(layout_target), Ok(value)) => { (Ok(layout_target), Ok(value)) => {
@ -158,21 +162,25 @@ impl JsEditorHandle {
} }
} }
#[wasm_bindgen(js_name = selectDocument)]
pub fn select_document(&self, document_id: u64) { pub fn select_document(&self, document_id: u64) {
let message = PortfolioMessage::SelectDocument { document_id }; let message = PortfolioMessage::SelectDocument { document_id };
self.dispatch(message); self.dispatch(message);
} }
#[wasm_bindgen(js_name = newDocumentDialog)]
pub fn new_document_dialog(&self) { pub fn new_document_dialog(&self) {
let message = DialogMessage::RequestNewDocumentDialog; let message = DialogMessage::RequestNewDocumentDialog;
self.dispatch(message); self.dispatch(message);
} }
#[wasm_bindgen(js_name = documentOpen)]
pub fn document_open(&self) { pub fn document_open(&self) {
let message = PortfolioMessage::OpenDocument; let message = PortfolioMessage::OpenDocument;
self.dispatch(message); self.dispatch(message);
} }
#[wasm_bindgen(js_name = openDocumentFile)]
pub fn open_document_file(&self, document_name: String, document_serialized_content: String) { pub fn open_document_file(&self, document_name: String, document_serialized_content: String) {
let message = PortfolioMessage::OpenDocumentFile { let message = PortfolioMessage::OpenDocumentFile {
document_name, document_name,
@ -181,6 +189,7 @@ impl JsEditorHandle {
self.dispatch(message); self.dispatch(message);
} }
#[wasm_bindgen(js_name = openAutoSavedDocument)]
pub fn open_auto_saved_document(&self, document_id: u64, document_name: String, document_is_saved: bool, document_serialized_content: String) { pub fn open_auto_saved_document(&self, document_id: u64, document_name: String, document_is_saved: bool, document_serialized_content: String) {
let message = PortfolioMessage::OpenDocumentFileWithId { let message = PortfolioMessage::OpenDocumentFileWithId {
document_id, document_id,
@ -191,21 +200,25 @@ impl JsEditorHandle {
self.dispatch(message); self.dispatch(message);
} }
#[wasm_bindgen(js_name = triggerAutoSave)]
pub fn trigger_auto_save(&self, document_id: u64) { pub fn trigger_auto_save(&self, document_id: u64) {
let message = PortfolioMessage::AutoSaveDocument { document_id }; let message = PortfolioMessage::AutoSaveDocument { document_id };
self.dispatch(message); self.dispatch(message);
} }
#[wasm_bindgen(js_name = closeDocumentWithConfirmation)]
pub fn close_document_with_confirmation(&self, document_id: u64) { pub fn close_document_with_confirmation(&self, document_id: u64) {
let message = PortfolioMessage::CloseDocumentWithConfirmation { document_id }; let message = PortfolioMessage::CloseDocumentWithConfirmation { document_id };
self.dispatch(message); self.dispatch(message);
} }
#[wasm_bindgen(js_name = requestAboutGraphiteDialogWithLocalizedCommitDate)]
pub fn request_about_graphite_dialog_with_localized_commit_date(&self, localized_commit_date: String) { pub fn request_about_graphite_dialog_with_localized_commit_date(&self, localized_commit_date: String) {
let message = DialogMessage::RequestAboutGraphiteDialogWithLocalizedCommitDate { localized_commit_date }; let message = DialogMessage::RequestAboutGraphiteDialogWithLocalizedCommitDate { localized_commit_date };
self.dispatch(message); self.dispatch(message);
} }
#[wasm_bindgen(js_name = requestComingSoonDialog)]
pub fn request_coming_soon_dialog(&self, issue: Option<i32>) { pub fn request_coming_soon_dialog(&self, issue: Option<i32>) {
let message = DialogMessage::RequestComingSoonDialog { issue }; let message = DialogMessage::RequestComingSoonDialog { issue };
self.dispatch(message); self.dispatch(message);
@ -213,6 +226,7 @@ impl JsEditorHandle {
/// Send new bounds when document panel viewports get resized or moved within the editor /// Send new bounds when document panel viewports get resized or moved within the editor
/// [left, top, right, bottom]... /// [left, top, right, bottom]...
#[wasm_bindgen(js_name = boundsOfViewports)]
pub fn bounds_of_viewports(&self, bounds_of_viewports: &[f64]) { pub fn bounds_of_viewports(&self, bounds_of_viewports: &[f64]) {
let chunked: Vec<_> = bounds_of_viewports.chunks(4).map(ViewportBounds::from_slice).collect(); let chunked: Vec<_> = bounds_of_viewports.chunks(4).map(ViewportBounds::from_slice).collect();
@ -221,6 +235,7 @@ impl JsEditorHandle {
} }
/// Mouse movement within the screenspace bounds of the viewport /// Mouse movement within the screenspace bounds of the viewport
#[wasm_bindgen(js_name = onMouseMove)]
pub fn on_mouse_move(&self, x: f64, y: f64, mouse_keys: u8, modifiers: u8) { pub fn on_mouse_move(&self, x: f64, y: f64, mouse_keys: u8, modifiers: u8) {
let editor_mouse_state = EditorMouseState::from_keys_and_editor_position(mouse_keys, (x, y).into()); let editor_mouse_state = EditorMouseState::from_keys_and_editor_position(mouse_keys, (x, y).into());
@ -231,6 +246,7 @@ impl JsEditorHandle {
} }
/// Mouse scrolling within the screenspace bounds of the viewport /// Mouse scrolling within the screenspace bounds of the viewport
#[wasm_bindgen(js_name = onWheelScroll)]
pub fn on_wheel_scroll(&self, x: f64, y: f64, mouse_keys: u8, wheel_delta_x: i32, wheel_delta_y: i32, wheel_delta_z: i32, modifiers: u8) { pub fn on_wheel_scroll(&self, x: f64, y: f64, mouse_keys: u8, wheel_delta_x: i32, wheel_delta_y: i32, wheel_delta_z: i32, modifiers: u8) {
let mut editor_mouse_state = EditorMouseState::from_keys_and_editor_position(mouse_keys, (x, y).into()); let mut editor_mouse_state = EditorMouseState::from_keys_and_editor_position(mouse_keys, (x, y).into());
editor_mouse_state.scroll_delta = ScrollDelta::new(wheel_delta_x, wheel_delta_y, wheel_delta_z); editor_mouse_state.scroll_delta = ScrollDelta::new(wheel_delta_x, wheel_delta_y, wheel_delta_z);
@ -242,6 +258,7 @@ impl JsEditorHandle {
} }
/// A mouse button depressed within screenspace the bounds of the viewport /// A mouse button depressed within screenspace the bounds of the viewport
#[wasm_bindgen(js_name = onMouseDown)]
pub fn on_mouse_down(&self, x: f64, y: f64, mouse_keys: u8, modifiers: u8) { pub fn on_mouse_down(&self, x: f64, y: f64, mouse_keys: u8, modifiers: u8) {
let editor_mouse_state = EditorMouseState::from_keys_and_editor_position(mouse_keys, (x, y).into()); let editor_mouse_state = EditorMouseState::from_keys_and_editor_position(mouse_keys, (x, y).into());
@ -252,6 +269,7 @@ impl JsEditorHandle {
} }
/// A mouse button released /// A mouse button released
#[wasm_bindgen(js_name = onMouseUp)]
pub fn on_mouse_up(&self, x: f64, y: f64, mouse_keys: u8, modifiers: u8) { pub fn on_mouse_up(&self, x: f64, y: f64, mouse_keys: u8, modifiers: u8) {
let editor_mouse_state = EditorMouseState::from_keys_and_editor_position(mouse_keys, (x, y).into()); let editor_mouse_state = EditorMouseState::from_keys_and_editor_position(mouse_keys, (x, y).into());
@ -262,6 +280,7 @@ impl JsEditorHandle {
} }
/// Mouse double clicked /// Mouse double clicked
#[wasm_bindgen(js_name = onDoubleClick)]
pub fn on_double_click(&self, x: f64, y: f64, mouse_keys: u8, modifiers: u8) { pub fn on_double_click(&self, x: f64, y: f64, mouse_keys: u8, modifiers: u8) {
let editor_mouse_state = EditorMouseState::from_keys_and_editor_position(mouse_keys, (x, y).into()); let editor_mouse_state = EditorMouseState::from_keys_and_editor_position(mouse_keys, (x, y).into());
let modifier_keys = ModifierKeys::from_bits(modifiers).expect("Invalid modifier keys"); let modifier_keys = ModifierKeys::from_bits(modifiers).expect("Invalid modifier keys");
@ -271,6 +290,7 @@ impl JsEditorHandle {
} }
/// A keyboard button depressed within screenspace the bounds of the viewport /// A keyboard button depressed within screenspace the bounds of the viewport
#[wasm_bindgen(js_name = onKeyDown)]
pub fn on_key_down(&self, name: String, modifiers: u8) { pub fn on_key_down(&self, name: String, modifiers: u8) {
let key = translate_key(&name); let key = translate_key(&name);
let modifier_keys = ModifierKeys::from_bits(modifiers).expect("Invalid modifier keys"); let modifier_keys = ModifierKeys::from_bits(modifiers).expect("Invalid modifier keys");
@ -282,6 +302,7 @@ impl JsEditorHandle {
} }
/// A keyboard button released /// A keyboard button released
#[wasm_bindgen(js_name = onKeyUp)]
pub fn on_key_up(&self, name: String, modifiers: u8) { pub fn on_key_up(&self, name: String, modifiers: u8) {
let key = translate_key(&name); let key = translate_key(&name);
let modifier_keys = ModifierKeys::from_bits(modifiers).expect("Invalid modifier keys"); let modifier_keys = ModifierKeys::from_bits(modifiers).expect("Invalid modifier keys");
@ -293,6 +314,7 @@ impl JsEditorHandle {
} }
/// A text box was committed /// A text box was committed
#[wasm_bindgen(js_name = onChangeText)]
pub fn on_change_text(&self, new_text: String) -> Result<(), JsValue> { pub fn on_change_text(&self, new_text: String) -> Result<(), JsValue> {
let message = TextToolMessage::TextChange { new_text }; let message = TextToolMessage::TextChange { new_text };
self.dispatch(message); self.dispatch(message);
@ -301,6 +323,7 @@ impl JsEditorHandle {
} }
/// A font has been downloaded /// A font has been downloaded
#[wasm_bindgen(js_name = onFontLoad)]
pub fn on_font_load(&self, font_family: String, font_style: String, preview_url: String, data: Vec<u8>, is_default: bool) -> Result<(), JsValue> { pub fn on_font_load(&self, font_family: String, font_style: String, preview_url: String, data: Vec<u8>, is_default: bool) -> Result<(), JsValue> {
let message = PortfolioMessage::FontLoaded { let message = PortfolioMessage::FontLoaded {
font_family, font_family,
@ -315,6 +338,7 @@ impl JsEditorHandle {
} }
/// A text box was changed /// A text box was changed
#[wasm_bindgen(js_name = updateBounds)]
pub fn update_bounds(&self, new_text: String) -> Result<(), JsValue> { pub fn update_bounds(&self, new_text: String) -> Result<(), JsValue> {
let message = TextToolMessage::UpdateBounds { new_text }; let message = TextToolMessage::UpdateBounds { new_text };
self.dispatch(message); self.dispatch(message);
@ -323,6 +347,7 @@ impl JsEditorHandle {
} }
/// Update primary color /// Update primary color
#[wasm_bindgen(js_name = updatePrimaryColor)]
pub fn update_primary_color(&self, red: f32, green: f32, blue: f32, alpha: f32) -> Result<(), JsValue> { pub fn update_primary_color(&self, red: f32, green: f32, blue: f32, alpha: f32) -> Result<(), JsValue> {
let primary_color = match Color::from_rgbaf32(red, green, blue, alpha) { let primary_color = match Color::from_rgbaf32(red, green, blue, alpha) {
Some(color) => color, Some(color) => color,
@ -336,6 +361,7 @@ impl JsEditorHandle {
} }
/// Update secondary color /// Update secondary color
#[wasm_bindgen(js_name = updateSecondaryColor)]
pub fn update_secondary_color(&self, red: f32, green: f32, blue: f32, alpha: f32) -> Result<(), JsValue> { pub fn update_secondary_color(&self, red: f32, green: f32, blue: f32, alpha: f32) -> Result<(), JsValue> {
let secondary_color = match Color::from_rgbaf32(red, green, blue, alpha) { let secondary_color = match Color::from_rgbaf32(red, green, blue, alpha) {
Some(color) => color, Some(color) => color,
@ -349,24 +375,28 @@ impl JsEditorHandle {
} }
/// Paste layers from a serialized json representation /// Paste layers from a serialized json representation
#[wasm_bindgen(js_name = pasteSerializedData)]
pub fn paste_serialized_data(&self, data: String) { pub fn paste_serialized_data(&self, data: String) {
let message = PortfolioMessage::PasteSerializedData { data }; let message = PortfolioMessage::PasteSerializedData { data };
self.dispatch(message); self.dispatch(message);
} }
/// Modify the layer selection based on the layer which is clicked while holding down the <kbd>Ctrl</kbd> and/or <kbd>Shift</kbd> modifier keys used for range selection behavior /// Modify the layer selection based on the layer which is clicked while holding down the <kbd>Ctrl</kbd> and/or <kbd>Shift</kbd> modifier keys used for range selection behavior
#[wasm_bindgen(js_name = selectLayer)]
pub fn select_layer(&self, layer_path: Vec<LayerId>, ctrl: bool, shift: bool) { pub fn select_layer(&self, layer_path: Vec<LayerId>, ctrl: bool, shift: bool) {
let message = DocumentMessage::SelectLayer { layer_path, ctrl, shift }; let message = DocumentMessage::SelectLayer { layer_path, ctrl, shift };
self.dispatch(message); self.dispatch(message);
} }
/// Deselect all layers /// Deselect all layers
#[wasm_bindgen(js_name = deselectAllLayers)]
pub fn deselect_all_layers(&self) { pub fn deselect_all_layers(&self) {
let message = DocumentMessage::DeselectAllLayers; let message = DocumentMessage::DeselectAllLayers;
self.dispatch(message); self.dispatch(message);
} }
/// Move a layer to be next to the specified neighbor /// Move a layer to be next to the specified neighbor
#[wasm_bindgen(js_name = moveLayerInTree)]
pub fn move_layer_in_tree(&self, folder_path: Vec<LayerId>, insert_index: isize) { pub fn move_layer_in_tree(&self, folder_path: Vec<LayerId>, insert_index: isize) {
let message = DocumentMessage::MoveSelectedLayersTo { let message = DocumentMessage::MoveSelectedLayersTo {
folder_path, folder_path,
@ -377,24 +407,28 @@ impl JsEditorHandle {
} }
/// Set the name for the layer /// Set the name for the layer
#[wasm_bindgen(js_name = setLayerName)]
pub fn set_layer_name(&self, layer_path: Vec<LayerId>, name: String) { pub fn set_layer_name(&self, layer_path: Vec<LayerId>, name: String) {
let message = DocumentMessage::SetLayerName { layer_path, name }; let message = DocumentMessage::SetLayerName { layer_path, name };
self.dispatch(message); self.dispatch(message);
} }
/// Translates document (in viewport coords) /// Translates document (in viewport coords)
#[wasm_bindgen(js_name = translateCanvas)]
pub fn translate_canvas(&self, delta_x: f64, delta_y: f64) { pub fn translate_canvas(&self, delta_x: f64, delta_y: f64) {
let message = NavigationMessage::TranslateCanvas { delta: (delta_x, delta_y).into() }; let message = NavigationMessage::TranslateCanvas { delta: (delta_x, delta_y).into() };
self.dispatch(message); self.dispatch(message);
} }
/// Translates document (in viewport coords) /// Translates document (in viewport coords)
#[wasm_bindgen(js_name = translateCanvasByFraction)]
pub fn translate_canvas_by_fraction(&self, delta_x: f64, delta_y: f64) { pub fn translate_canvas_by_fraction(&self, delta_x: f64, delta_y: f64) {
let message = NavigationMessage::TranslateCanvasByViewportFraction { delta: (delta_x, delta_y).into() }; let message = NavigationMessage::TranslateCanvasByViewportFraction { delta: (delta_x, delta_y).into() };
self.dispatch(message); self.dispatch(message);
} }
/// Sends the blob url generated by js /// Sends the blob url generated by js
#[wasm_bindgen(js_name = setImageBlobUrl)]
pub fn set_image_blob_url(&self, path: Vec<LayerId>, blob_url: String, width: f64, height: f64) { pub fn set_image_blob_url(&self, path: Vec<LayerId>, blob_url: String, width: f64, height: f64) {
let dimensions = (width, height); let dimensions = (width, height);
let message = Operation::SetImageBlobUrl { path, blob_url, dimensions }; let message = Operation::SetImageBlobUrl { path, blob_url, dimensions };
@ -402,6 +436,7 @@ impl JsEditorHandle {
} }
/// Pastes an image /// Pastes an image
#[wasm_bindgen(js_name = pasteImage)]
pub fn paste_image(&self, mime: String, image_data: Vec<u8>, mouse_x: Option<f64>, mouse_y: Option<f64>) { pub fn paste_image(&self, mime: String, image_data: Vec<u8>, mouse_x: Option<f64>, mouse_y: Option<f64>) {
let mouse = mouse_x.and_then(|x| mouse_y.map(|y| (x, y))); let mouse = mouse_x.and_then(|x| mouse_y.map(|y| (x, y)));
let message = DocumentMessage::PasteImage { mime, image_data, mouse }; let message = DocumentMessage::PasteImage { mime, image_data, mouse };
@ -409,12 +444,14 @@ impl JsEditorHandle {
} }
/// Toggle visibility of a layer from the layer list /// Toggle visibility of a layer from the layer list
#[wasm_bindgen(js_name = toggleLayerVisibility)]
pub fn toggle_layer_visibility(&self, layer_path: Vec<LayerId>) { pub fn toggle_layer_visibility(&self, layer_path: Vec<LayerId>) {
let message = DocumentMessage::ToggleLayerVisibility { layer_path }; let message = DocumentMessage::ToggleLayerVisibility { layer_path };
self.dispatch(message); self.dispatch(message);
} }
/// Toggle expansions state of a layer from the layer list /// Toggle expansions state of a layer from the layer list
#[wasm_bindgen(js_name = toggleLayerExpansion)]
pub fn toggle_layer_expansion(&self, layer_path: Vec<LayerId>) { pub fn toggle_layer_expansion(&self, layer_path: Vec<LayerId>) {
let message = DocumentMessage::ToggleLayerExpansion { layer_path }; let message = DocumentMessage::ToggleLayerExpansion { layer_path };
self.dispatch(message); self.dispatch(message);

View File

@ -4,7 +4,9 @@ use std::collections::HashMap;
/// A font type (storing font family and font style and an optional preview URL) /// A font type (storing font family and font style and an optional preview URL)
#[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)]
pub struct Font { pub struct Font {
#[serde(rename = "fontFamily")]
pub font_family: String, pub font_family: String,
#[serde(rename = "fontStyle")]
pub font_style: String, pub font_style: String,
} }
impl Font { impl Font {

View File

@ -3,7 +3,7 @@ title = "Features and roadmap"
template = "page.html" template = "page.html"
+++ +++
This page is a work in progress. Below is an incomplete list of planned features in no particular order. Many are long-term aspirations. In the coming days, this will be sequenced into a roadmap and expanded with further details. This page is a work in progress. Below is an incomplete list of planned features in no particular order. Many are long-term aspirations. Soon, this will be sequenced into a roadmap and expanded with further details.
Short-term feature development at a more granular level is tracked in the [Task Board](https://github.com/GraphiteEditor/Graphite/projects/1) on GitHub. Check that out to see what's coming down the pipeline during monthly sprints. Short-term feature development at a more granular level is tracked in the [Task Board](https://github.com/GraphiteEditor/Graphite/projects/1) on GitHub. Check that out to see what's coming down the pipeline during monthly sprints.