Skip to content

Commit a32f41f

Browse files
authored
Vue initialization and FloatingMenu codebase refactoring and cleanup (#649)
* Clean up Vue initialization-related code * Rename folder: dispatcher -> interop * Rename folder: state -> providers * Comments and clarification * Rename JS dispatcher to subscription router * Assorted cleanup and renaming * Rename: js-messages.ts -> messages.ts * Comments * Remove unused Vue component injects * Clean up coming soon and add warning about freezing the app * Further cleanup * Dangerous changes * Simplify App.vue code * Move more disparate init code from components into managers * Rename folder: providers -> state-providers * Other * Move Document panel options bar separator to backend * Add destructors to managers to fix HMR * Comments and code style * Rename variable: font -> font_file_url * Fix async font loading; refactor janky floating menu openness and min-width measurement; fix Vetur errors * Fix misaligned canvas in viewport until panning on page (re)load * Add Vue bidirectional props documentation * More folder renaming for better terminology; add some documentation
1 parent df675a7 commit a32f41f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+1570
-1460
lines changed

editor/src/document/document_message.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ pub enum DocumentMessage {
7373
affected_folder_path: Vec<LayerId>,
7474
},
7575
FontLoaded {
76-
font: String,
76+
font_file_url: String,
7777
data: Vec<u8>,
7878
is_default: bool,
7979
},
@@ -82,7 +82,7 @@ pub enum DocumentMessage {
8282
affected_layer_path: Vec<LayerId>,
8383
},
8484
LoadFont {
85-
font: String,
85+
font_file_url: String,
8686
},
8787
MoveSelectedLayersTo {
8888
folder_path: Vec<LayerId>,

editor/src/document/document_message_handler.rs

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,7 @@ impl DocumentMessageHandler {
523523
}
524524
}
525525

526+
// TODO: Loading the default font should happen on a per-application basis, not a per-document basis
526527
pub fn load_default_font(&self, responses: &mut VecDeque<Message>) {
527528
if !self.graphene_document.font_cache.has_default() {
528529
responses.push_back(FrontendMessage::TriggerFontLoadDefault.into())
@@ -669,30 +670,36 @@ impl DocumentMessageHandler {
669670
}]);
670671

671672
let document_mode_layout = WidgetLayout::new(vec![LayoutRow::Row {
672-
widgets: vec![WidgetHolder::new(Widget::DropdownInput(DropdownInput {
673-
entries: vec![vec![
674-
DropdownEntryData {
675-
label: DocumentMode::DesignMode.to_string(),
676-
icon: DocumentMode::DesignMode.icon_name(),
677-
..DropdownEntryData::default()
678-
},
679-
DropdownEntryData {
680-
label: DocumentMode::SelectMode.to_string(),
681-
icon: DocumentMode::SelectMode.icon_name(),
682-
on_update: WidgetCallback::new(|_| DialogMessage::RequestComingSoonDialog { issue: Some(330) }.into()),
683-
..DropdownEntryData::default()
684-
},
685-
DropdownEntryData {
686-
label: DocumentMode::GuideMode.to_string(),
687-
icon: DocumentMode::GuideMode.icon_name(),
688-
on_update: WidgetCallback::new(|_| DialogMessage::RequestComingSoonDialog { issue: Some(331) }.into()),
689-
..DropdownEntryData::default()
690-
},
691-
]],
692-
selected_index: Some(self.document_mode as u32),
693-
draw_icon: true,
694-
..Default::default()
695-
}))],
673+
widgets: vec![
674+
WidgetHolder::new(Widget::DropdownInput(DropdownInput {
675+
entries: vec![vec![
676+
DropdownEntryData {
677+
label: DocumentMode::DesignMode.to_string(),
678+
icon: DocumentMode::DesignMode.icon_name(),
679+
..DropdownEntryData::default()
680+
},
681+
DropdownEntryData {
682+
label: DocumentMode::SelectMode.to_string(),
683+
icon: DocumentMode::SelectMode.icon_name(),
684+
on_update: WidgetCallback::new(|_| DialogMessage::RequestComingSoonDialog { issue: Some(330) }.into()),
685+
..DropdownEntryData::default()
686+
},
687+
DropdownEntryData {
688+
label: DocumentMode::GuideMode.to_string(),
689+
icon: DocumentMode::GuideMode.icon_name(),
690+
on_update: WidgetCallback::new(|_| DialogMessage::RequestComingSoonDialog { issue: Some(331) }.into()),
691+
..DropdownEntryData::default()
692+
},
693+
]],
694+
selected_index: Some(self.document_mode as u32),
695+
draw_icon: true,
696+
..Default::default()
697+
})),
698+
WidgetHolder::new(Widget::Separator(Separator {
699+
separator_type: SeparatorType::Section,
700+
direction: SeparatorDirection::Horizontal,
701+
})),
702+
],
696703
}]);
697704

698705
responses.push_back(
@@ -1107,8 +1114,8 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
11071114
let affected_layer_path = affected_folder_path;
11081115
responses.extend([LayerChanged { affected_layer_path }.into(), DocumentStructureChanged.into()]);
11091116
}
1110-
FontLoaded { font, data, is_default } => {
1111-
self.graphene_document.font_cache.insert(font, data, is_default);
1117+
FontLoaded { font_file_url, data, is_default } => {
1118+
self.graphene_document.font_cache.insert(font_file_url, data, is_default);
11121119
responses.push_back(DocumentMessage::DirtyRenderDocument.into());
11131120
}
11141121
GroupSelectedLayers => {
@@ -1147,9 +1154,9 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
11471154
responses.push_back(PropertiesPanelMessage::CheckSelectedWasUpdated { path: affected_layer_path }.into());
11481155
self.update_layer_tree_options_bar_widgets(responses);
11491156
}
1150-
LoadFont { font } => {
1151-
if !self.graphene_document.font_cache.loaded_font(&font) {
1152-
responses.push_front(FrontendMessage::TriggerFontLoad { font }.into());
1157+
LoadFont { font_file_url } => {
1158+
if !self.graphene_document.font_cache.loaded_font(&font_file_url) {
1159+
responses.push_front(FrontendMessage::TriggerFontLoad { font_file_url }.into());
11531160
}
11541161
}
11551162
MoveSelectedLayersTo {

editor/src/document/portfolio_message_handler.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ impl PortfolioMessageHandler {
7979
new_document.update_layer_tree_options_bar_widgets(responses);
8080

8181
new_document.load_image_data(responses, &new_document.graphene_document.root.data, Vec::new());
82+
// TODO: Loading the default font should happen on a per-application basis, not a per-document basis
8283
new_document.load_default_font(responses);
8384

8485
self.documents.insert(document_id, new_document);

editor/src/document/properties_panel_message_handler.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -714,12 +714,12 @@ fn node_section_font(layer: &TextLayer) -> LayoutRow {
714714
is_style_picker: false,
715715
font_family: layer.font_family.clone(),
716716
font_style: layer.font_style.clone(),
717-
font_file: String::new(),
717+
font_file_url: String::new(),
718718
on_update: WidgetCallback::new(move |font_input: &FontInput| {
719719
PropertiesPanelMessage::ModifyFont {
720720
font_family: font_input.font_family.clone(),
721721
font_style: font_input.font_style.clone(),
722-
font_file: Some(font_input.font_file.clone()),
722+
font_file: Some(font_input.font_file_url.clone()),
723723
size,
724724
}
725725
.into()
@@ -741,12 +741,12 @@ fn node_section_font(layer: &TextLayer) -> LayoutRow {
741741
is_style_picker: true,
742742
font_family: layer.font_family.clone(),
743743
font_style: layer.font_style.clone(),
744-
font_file: String::new(),
744+
font_file_url: String::new(),
745745
on_update: WidgetCallback::new(move |font_input: &FontInput| {
746746
PropertiesPanelMessage::ModifyFont {
747747
font_family: font_input.font_family.clone(),
748748
font_style: font_input.font_style.clone(),
749-
font_file: Some(font_input.font_file.clone()),
749+
font_file: Some(font_input.font_file_url.clone()),
750750
size,
751751
}
752752
.into()

editor/src/frontend/frontend_message.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pub enum FrontendMessage {
2222
// Trigger prefix: cause a browser API to do something
2323
TriggerFileDownload { document: String, name: String },
2424
TriggerFileUpload,
25-
TriggerFontLoad { font: String },
25+
TriggerFontLoad { font_file_url: String },
2626
TriggerFontLoadDefault,
2727
TriggerIndexedDbRemoveDocument { document_id: u64 },
2828
TriggerIndexedDbWriteDocument { document: String, details: FrontendDocumentDetails, version: String },

editor/src/layout/layout_message_handler.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,17 +95,17 @@ impl MessageHandler<LayoutMessage, ()> for LayoutMessageHandler {
9595
let update_value = value.as_object().expect("FontInput update was not of type: object");
9696
let font_family_value = update_value.get("fontFamily").expect("FontInput update does not have a fontFamily");
9797
let font_style_value = update_value.get("fontStyle").expect("FontInput update does not have a fontStyle");
98-
let font_file_value = update_value.get("fontFile").expect("FontInput update does not have a fontFile");
98+
let font_file_url_value = update_value.get("fontFileUrl").expect("FontInput update does not have a fontFileUrl");
9999

100100
let font_family = font_family_value.as_str().expect("FontInput update fontFamily was not of type: string");
101101
let font_style = font_style_value.as_str().expect("FontInput update fontStyle was not of type: string");
102-
let font_file = font_file_value.as_str().expect("FontInput update fontFile was not of type: string");
102+
let font_file_url = font_file_url_value.as_str().expect("FontInput update fontFileUrl was not of type: string");
103103

104104
font_input.font_family = font_family.into();
105105
font_input.font_style = font_style.into();
106-
font_input.font_file = font_file.into();
106+
font_input.font_file_url = font_file_url.into();
107107

108-
responses.push_back(DocumentMessage::LoadFont { font: font_file.into() }.into());
108+
responses.push_back(DocumentMessage::LoadFont { font_file_url: font_file_url.into() }.into());
109109
let callback_message = (font_input.on_update.callback)(font_input);
110110
responses.push_back(callback_message);
111111
}

editor/src/layout/widgets.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ impl WidgetLayout {
4949

5050
pub type SubLayout = Vec<LayoutRow>;
5151

52+
// TODO: Rename LayoutRow to something more generic
5253
#[remain::sorted]
5354
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
5455
pub enum LayoutRow {
@@ -254,8 +255,8 @@ pub struct FontInput {
254255
pub font_family: String,
255256
#[serde(rename = "fontStyle")]
256257
pub font_style: String,
257-
#[serde(rename = "fontFile")]
258-
pub font_file: String,
258+
#[serde(rename = "fontFileUrl")]
259+
pub font_file_url: String,
259260
#[serde(skip)]
260261
#[derivative(Debug = "ignore", PartialEq = "ignore")]
261262
pub on_update: WidgetCallback<FontInput>,

editor/src/viewport_tools/tools/text_tool.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ impl PropertyHolder for TextTool {
8484
TextMessage::UpdateOptions(TextOptionsUpdate::Font {
8585
family: font_input.font_family.clone(),
8686
style: font_input.font_style.clone(),
87-
file: font_input.font_file.clone(),
87+
file: font_input.font_file_url.clone(),
8888
})
8989
.into()
9090
}),
@@ -102,7 +102,7 @@ impl PropertyHolder for TextTool {
102102
TextMessage::UpdateOptions(TextOptionsUpdate::Font {
103103
family: font_input.font_family.clone(),
104104
style: font_input.font_style.clone(),
105-
file: font_input.font_file.clone(),
105+
file: font_input.font_file_url.clone(),
106106
})
107107
.into()
108108
}),

frontend/src/App.vue

Lines changed: 65 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<template>
22
<MainWindow />
33

4-
<div class="unsupported-modal-backdrop" v-if="showUnsupportedModal">
4+
<div class="unsupported-modal-backdrop" v-if="apiUnsupported" ref="unsupported">
55
<LayoutCol class="unsupported-modal">
66
<h2>Your browser currently doesn't support Graphite</h2>
77
<p>Unfortunately, some features won't work properly. Please upgrade to a modern browser such as Firefox, Chrome, Edge, or Safari version 15 or later.</p>
@@ -11,7 +11,7 @@
1111
API which is required for using the editor. However, you can still explore the user interface.
1212
</p>
1313
<LayoutRow>
14-
<button class="unsupported-modal-button" @click="() => closeModal()">I understand, let's just see the interface</button>
14+
<button class="unsupported-modal-button" @click="() => closeUnsupportedWarning()">I understand, let's just see the interface</button>
1515
</LayoutRow>
1616
</LayoutCol>
1717
</div>
@@ -258,78 +258,96 @@ img {
258258
<script lang="ts">
259259
import { defineComponent } from "vue";
260260
261-
import { createAutoSaveManager } from "@/lifetime/auto-save";
262-
import { initErrorHandling } from "@/lifetime/errors";
263-
import { createInputManager, InputManager } from "@/lifetime/input";
264-
import { createDialogState, DialogState } from "@/state/dialog";
265-
import { createFullscreenState, FullscreenState } from "@/state/fullscreen";
266-
import { createPortfolioState, PortfolioState } from "@/state/portfolio";
267-
import { createEditorState, EditorState } from "@/state/wasm-loader";
268-
import { createWorkspaceState, WorkspaceState } from "@/state/workspace";
261+
import { createBuildMetadataManager } from "@/io-managers/build-metadata";
262+
import { createClipboardManager } from "@/io-managers/clipboard";
263+
import { createHyperlinkManager } from "@/io-managers/hyperlinks";
264+
import { createInputManager } from "@/io-managers/input";
265+
import { createPanicManager } from "@/io-managers/panic";
266+
import { createPersistenceManager } from "@/io-managers/persistence";
267+
import { createDialogState, DialogState } from "@/state-providers/dialog";
268+
import { createFontsState, FontsState } from "@/state-providers/fonts";
269+
import { createFullscreenState, FullscreenState } from "@/state-providers/fullscreen";
270+
import { createPortfolioState, PortfolioState } from "@/state-providers/portfolio";
271+
import { createWorkspaceState, WorkspaceState } from "@/state-providers/workspace";
272+
import { createEditor, Editor } from "@/wasm-communication/editor";
269273
270274
import LayoutCol from "@/components/layout/LayoutCol.vue";
271275
import LayoutRow from "@/components/layout/LayoutRow.vue";
272276
import MainWindow from "@/components/window/MainWindow.vue";
273277
274-
// Vue injects don't play well with TypeScript, and all injects will show up as `any`. As a workaround, we can define these types.
278+
const managerDestructors: {
279+
createBuildMetadataManager?: () => void;
280+
createClipboardManager?: () => void;
281+
createHyperlinkManager?: () => void;
282+
createInputManager?: () => void;
283+
createPanicManager?: () => void;
284+
createPersistenceManager?: () => void;
285+
} = {};
286+
287+
// Vue injects don't play well with TypeScript (all injects will show up as `any`) but we can define these types as a solution
275288
declare module "@vue/runtime-core" {
289+
// Systems `provide`d by the root App to be `inject`ed into descendant components and used for reactive bindings
276290
interface ComponentCustomProperties {
291+
// Graphite WASM editor instance
292+
editor: Editor;
293+
294+
// State provider systems
277295
dialog: DialogState;
296+
fonts: FontsState;
297+
fullscreen: FullscreenState;
278298
portfolio: PortfolioState;
279299
workspace: WorkspaceState;
280-
fullscreen: FullscreenState;
281-
editor: EditorState;
282-
// This must be set to optional because there is a time in the lifecycle of the component where inputManager is undefined.
283-
// That's because we initialize inputManager in `mounted()` rather than `data()` since the div hasn't been created yet.
284-
inputManager?: InputManager;
285300
}
286301
}
287302
288303
export default defineComponent({
289304
provide() {
290-
return {
291-
editor: this.editor,
292-
dialog: this.dialog,
293-
portfolio: this.portfolio,
294-
workspace: this.workspace,
295-
fullscreen: this.fullscreen,
296-
inputManager: this.inputManager,
297-
};
305+
return { ...this.$data };
298306
},
299307
data() {
300-
// Initialize the Graphite WASM editor instance
301-
const editor = createEditorState();
302-
303-
// Initialize other stateful Vue systems
304-
const dialog = createDialogState(editor);
305-
const portfolio = createPortfolioState(editor);
306-
const workspace = createWorkspaceState(editor);
307-
const fullscreen = createFullscreenState();
308-
initErrorHandling(editor, dialog);
309-
createAutoSaveManager(editor, portfolio);
310-
308+
const editor = createEditor();
311309
return {
310+
// Graphite WASM editor instance
312311
editor,
313-
dialog,
314-
portfolio,
315-
workspace,
316-
fullscreen,
317-
showUnsupportedModal: !("BigInt64Array" in window),
318-
inputManager: undefined as undefined | InputManager,
312+
313+
// State provider systems
314+
dialog: createDialogState(editor),
315+
fonts: createFontsState(editor),
316+
fullscreen: createFullscreenState(),
317+
portfolio: createPortfolioState(editor),
318+
workspace: createWorkspaceState(editor),
319319
};
320320
},
321+
computed: {
322+
apiUnsupported() {
323+
return !("BigInt64Array" in window);
324+
},
325+
},
321326
methods: {
322-
closeModal() {
323-
this.showUnsupportedModal = false;
327+
closeUnsupportedWarning() {
328+
const element = this.$refs.unsupported as HTMLElement;
329+
element.parentElement?.removeChild(element);
324330
},
325331
},
326-
mounted() {
327-
this.inputManager = createInputManager(this.editor, this.$el.parentElement, this.dialog, this.portfolio, this.fullscreen);
328-
332+
async mounted() {
333+
// Initialize managers, which are isolated systems that subscribe to backend messages to link them to browser API functionality (like JS events, IndexedDB, etc.)
334+
Object.assign(managerDestructors, {
335+
createBuildMetadataManager: createBuildMetadataManager(this.editor),
336+
createClipboardManager: createClipboardManager(this.editor),
337+
createHyperlinkManager: createHyperlinkManager(this.editor),
338+
createInputManager: createInputManager(this.editor, this.$el.parentElement, this.dialog, this.portfolio, this.fullscreen),
339+
createPanicManager: createPanicManager(this.editor, this.dialog),
340+
createPersistenceManager: await createPersistenceManager(this.editor, this.portfolio),
341+
});
342+
343+
// Initialize certain setup tasks required by the editor backend to be ready for the user now that the frontend is ready
329344
this.editor.instance.init_app();
330345
},
331346
beforeUnmount() {
332-
this.inputManager?.removeListeners();
347+
// Call the destructor for each manager
348+
Object.values(managerDestructors).forEach((destructor) => destructor?.());
349+
350+
// Destroy the WASM editor instance
333351
this.editor.instance.free();
334352
},
335353
components: {

0 commit comments

Comments
 (0)