mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 07:18:04 +08:00
Remove all use of document indices (#406)
* removed all use of document indicies * -add u64 support for wasm bridge * fixed rust formating * Cleaned up FrontendDocumentState in js-messages * Tiny tweaks from code review * - moved more of closeDocumentWithConfirmation to rust - updated serde_wasm_bindgen to add feature flag * changed to upsteam version of serde_wasm_bindgen * cargo fmt * -fix event propigation on delete - Js message change class extention to typedef * changed another typedef * cargo fmt Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<button class="icon-button" :class="`size-${String(size)}`" @click="action">
|
||||
<button class="icon-button" :class="`size-${String(size)}`" @click="(e) => action(e)">
|
||||
<IconLabel :icon="icon" />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
@@ -7,26 +7,11 @@
|
||||
:class="{ active: tabIndex === tabActiveIndex }"
|
||||
v-for="(tabLabel, tabIndex) in tabLabels"
|
||||
:key="tabIndex"
|
||||
@click.middle="
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
documents.closeDocumentWithConfirmation(tabIndex);
|
||||
}
|
||||
"
|
||||
@click="panelType === 'Document' && documents.selectDocument(tabIndex)"
|
||||
@click="(e) => e.stopPropagation() || (clickAction && clickAction(tabIndex))"
|
||||
@click.middle="(e) => e.stopPropagation() || (closeAction && closeAction(tabIndex))"
|
||||
>
|
||||
<span>{{ tabLabel }}</span>
|
||||
<IconButton
|
||||
:action="
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
documents.closeDocumentWithConfirmation(tabIndex);
|
||||
}
|
||||
"
|
||||
:icon="'CloseX'"
|
||||
:size="16"
|
||||
v-if="tabCloseButtons"
|
||||
/>
|
||||
<IconButton :action="(e) => e.stopPropagation() || (closeAction && closeAction(tabIndex))" :icon="'CloseX'" :size="16" v-if="tabCloseButtons" />
|
||||
</div>
|
||||
</div>
|
||||
<PopoverButton :icon="PopoverButtonIcon.VerticalEllipsis">
|
||||
@@ -193,6 +178,8 @@ export default defineComponent({
|
||||
tabLabels: { type: Array as PropType<string[]>, required: true },
|
||||
tabActiveIndex: { type: Number, required: true },
|
||||
panelType: { type: String, required: true },
|
||||
clickAction: { type: Function as PropType<(index: number) => void>, required: false },
|
||||
closeAction: { type: Function as PropType<(index: number) => void>, required: false },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -6,6 +6,18 @@
|
||||
:tabCloseButtons="true"
|
||||
:tabMinWidths="true"
|
||||
:tabLabels="documents.state.documents.map((doc) => doc.displayName)"
|
||||
:clickAction="
|
||||
(tabIndex) => {
|
||||
const targetId = documents.state.documents[tabIndex].id;
|
||||
editor.instance.select_document(targetId);
|
||||
}
|
||||
"
|
||||
:closeAction="
|
||||
(tabIndex) => {
|
||||
const targetId = documents.state.documents[tabIndex].id;
|
||||
editor.instance.close_document_with_confirmation(targetId);
|
||||
}
|
||||
"
|
||||
:tabActiveIndex="documents.state.activeDocumentIndex"
|
||||
ref="documentsPanel"
|
||||
/>
|
||||
@@ -61,16 +73,13 @@ import LayoutCol from "@/components/layout/LayoutCol.vue";
|
||||
import DialogModal from "@/components/widgets/floating-menus/DialogModal.vue";
|
||||
|
||||
export default defineComponent({
|
||||
inject: ["documents", "dialog"],
|
||||
inject: ["documents", "dialog", "editor"],
|
||||
components: {
|
||||
LayoutRow,
|
||||
LayoutCol,
|
||||
Panel,
|
||||
DialogModal,
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
activeDocumentIndex() {
|
||||
return this.documents.state.activeDocumentIndex;
|
||||
|
||||
@@ -19,19 +19,31 @@ export class JsMessage {
|
||||
// for details about how to transform the JSON from wasm-bindgen into classes.
|
||||
// ============================================================================
|
||||
|
||||
export class UpdateOpenDocumentsList extends JsMessage {
|
||||
@Transform(({ value }) => value.map((tuple: [string, boolean]) => ({ name: tuple[0], isSaved: tuple[1] })))
|
||||
readonly open_documents!: { name: string; isSaved: boolean }[];
|
||||
export class FrontendDocumentDetails {
|
||||
readonly name!: string;
|
||||
|
||||
readonly is_saved!: boolean;
|
||||
|
||||
readonly id!: BigInt;
|
||||
|
||||
get displayName() {
|
||||
return `${this.name}${this.is_saved ? "" : "*"}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class UpdateOpenDocumentsList extends JsMessage {
|
||||
@Type(() => FrontendDocumentDetails)
|
||||
readonly open_documents!: FrontendDocumentDetails[];
|
||||
}
|
||||
|
||||
export type HintData = HintInfo[][];
|
||||
|
||||
export class UpdateInputHints extends JsMessage {
|
||||
@Type(() => HintInfo)
|
||||
readonly hint_data!: HintData;
|
||||
}
|
||||
|
||||
export class HintGroup extends Array<HintInfo> {}
|
||||
|
||||
export class HintData extends Array<HintGroup> {}
|
||||
export type KeysGroup = string[];
|
||||
|
||||
export class HintInfo {
|
||||
readonly keys!: string[];
|
||||
@@ -43,8 +55,6 @@ export class HintInfo {
|
||||
readonly plus!: boolean;
|
||||
}
|
||||
|
||||
export class KeysGroup extends Array<string> {}
|
||||
|
||||
const To255Scale = Transform(({ value }) => value * 255);
|
||||
export class Color {
|
||||
@To255Scale
|
||||
@@ -83,7 +93,7 @@ export class SetActiveTool extends JsMessage {
|
||||
}
|
||||
|
||||
export class SetActiveDocument extends JsMessage {
|
||||
readonly document_index!: number;
|
||||
readonly document_id!: BigInt;
|
||||
}
|
||||
|
||||
export class DisplayError extends JsMessage {
|
||||
@@ -101,7 +111,7 @@ export class DisplayPanic extends JsMessage {
|
||||
}
|
||||
|
||||
export class DisplayConfirmationToCloseDocument extends JsMessage {
|
||||
readonly document_index!: number;
|
||||
readonly document_id!: BigInt;
|
||||
}
|
||||
|
||||
export class DisplayConfirmationToCloseAllDocuments extends JsMessage {}
|
||||
@@ -157,23 +167,25 @@ export class DisplayFolderTreeStructure extends JsMessage {
|
||||
}
|
||||
|
||||
interface DataBuffer {
|
||||
pointer: number;
|
||||
length: number;
|
||||
pointer: BigInt;
|
||||
length: BigInt;
|
||||
}
|
||||
|
||||
export function newDisplayFolderTreeStructure(input: { data_buffer: DataBuffer }, wasm: WasmInstance): DisplayFolderTreeStructure {
|
||||
const { pointer, length } = input.data_buffer;
|
||||
const pointerNum = Number(pointer);
|
||||
const lengthNum = Number(length);
|
||||
const wasmMemoryBuffer = wasm.wasm_memory().buffer;
|
||||
|
||||
// Decode the folder structure encoding
|
||||
const encoding = new DataView(wasmMemoryBuffer, pointer, length);
|
||||
const encoding = new DataView(wasmMemoryBuffer, pointerNum, lengthNum);
|
||||
|
||||
// The structure section indicates how to read through the upcoming layer list and assign depths to each layer
|
||||
const structureSectionLength = Number(encoding.getBigUint64(0, true));
|
||||
const structureSectionMsbSigned = new DataView(wasmMemoryBuffer, pointer + 8, structureSectionLength * 8);
|
||||
const structureSectionMsbSigned = new DataView(wasmMemoryBuffer, pointerNum + 8, structureSectionLength * 8);
|
||||
|
||||
// The layer IDs section lists each layer ID sequentially in the tree, as it will show up in the panel
|
||||
const layerIdsSection = new DataView(wasmMemoryBuffer, pointer + 8 + structureSectionLength * 8);
|
||||
const layerIdsSection = new DataView(wasmMemoryBuffer, pointerNum + 8 + structureSectionLength * 8);
|
||||
|
||||
let layersEncountered = 0;
|
||||
let currentFolder = new DisplayFolderTreeStructure(BigInt(-1), []);
|
||||
@@ -226,12 +238,6 @@ export class SetCanvasRotation extends JsMessage {
|
||||
readonly new_radians!: number;
|
||||
}
|
||||
|
||||
function newPath(input: number[][]): BigUint64Array {
|
||||
// eslint-disable-next-line
|
||||
const u32CombinedPairs = input.map((n: number[]) => BigInt((BigInt(n[0]) << BigInt(32)) | BigInt(n[1])));
|
||||
return new BigUint64Array(u32CombinedPairs);
|
||||
}
|
||||
|
||||
export type BlendMode =
|
||||
| "Normal"
|
||||
| "Multiply"
|
||||
@@ -263,7 +269,7 @@ export class LayerPanelEntry {
|
||||
|
||||
layer_type!: LayerType;
|
||||
|
||||
@Transform(({ value }) => newPath(value))
|
||||
@Transform(({ value }) => new BigUint64Array(value))
|
||||
path!: BigUint64Array;
|
||||
|
||||
@Type(() => LayerData)
|
||||
|
||||
@@ -175,7 +175,7 @@ export function createInputManager(editor: EditorState, container: HTMLElement,
|
||||
// Skip the message during development, since it's annoying when testing
|
||||
if (process.env.NODE_ENV === "development") return;
|
||||
|
||||
const allDocumentsSaved = document.state.documents.reduce((acc, doc) => acc && doc.isSaved, true);
|
||||
const allDocumentsSaved = document.state.documents.reduce((acc, doc) => acc && doc.is_saved, true);
|
||||
if (!allDocumentsSaved) {
|
||||
e.returnValue = "Unsaved work will be lost if the web browser tab is closed. Close anyway?";
|
||||
e.preventDefault();
|
||||
|
||||
@@ -8,47 +8,30 @@ import {
|
||||
DisplayConfirmationToCloseAllDocuments,
|
||||
DisplayConfirmationToCloseDocument,
|
||||
ExportDocument,
|
||||
FrontendDocumentDetails,
|
||||
OpenDocumentBrowse,
|
||||
SaveDocument,
|
||||
SetActiveDocument,
|
||||
UpdateOpenDocumentsList,
|
||||
} from "@/dispatcher/js-messages";
|
||||
|
||||
class DocumentSaveState {
|
||||
readonly displayName: string;
|
||||
|
||||
constructor(readonly name: string, readonly isSaved: boolean) {
|
||||
this.displayName = `${name}${isSaved ? "" : "*"}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function createDocumentsState(editor: EditorState, dialogState: DialogState) {
|
||||
const state = reactive({
|
||||
unsaved: false,
|
||||
documents: [] as DocumentSaveState[],
|
||||
documents: [] as FrontendDocumentDetails[],
|
||||
activeDocumentIndex: 0,
|
||||
});
|
||||
|
||||
const selectDocument = (tabIndex: number) => {
|
||||
editor.instance.select_document(tabIndex);
|
||||
};
|
||||
|
||||
const closeDocumentWithConfirmation = (tabIndex: number) => {
|
||||
// Close automatically if it's already saved, no confirmation is needed
|
||||
const targetDocument = state.documents[tabIndex];
|
||||
if (targetDocument.isSaved) {
|
||||
editor.instance.close_document(tabIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
// Switch to the document that's being prompted to close
|
||||
selectDocument(tabIndex);
|
||||
const closeDocumentWithConfirmation = async (documentId: BigInt) => {
|
||||
// Assume we receive a correct document_id
|
||||
const targetDocument = state.documents.find((doc) => doc.id === documentId) as FrontendDocumentDetails;
|
||||
const tabLabel = targetDocument.displayName;
|
||||
|
||||
// Show the close confirmation prompt
|
||||
dialogState.createDialog("File", "Save changes before closing?", targetDocument.displayName, [
|
||||
dialogState.createDialog("File", "Save changes before closing?", tabLabel, [
|
||||
{
|
||||
kind: "TextButton",
|
||||
callback: () => {
|
||||
callback: async () => {
|
||||
editor.instance.save_document();
|
||||
dialogState.dismissDialog();
|
||||
},
|
||||
@@ -56,15 +39,15 @@ export function createDocumentsState(editor: EditorState, dialogState: DialogSta
|
||||
},
|
||||
{
|
||||
kind: "TextButton",
|
||||
callback: () => {
|
||||
editor.instance.close_document(tabIndex);
|
||||
callback: async () => {
|
||||
editor.instance.close_document(targetDocument.id);
|
||||
dialogState.dismissDialog();
|
||||
},
|
||||
props: { label: "Discard", minWidth: 96 },
|
||||
},
|
||||
{
|
||||
kind: "TextButton",
|
||||
callback: () => {
|
||||
callback: async () => {
|
||||
dialogState.dismissDialog();
|
||||
},
|
||||
props: { label: "Cancel", minWidth: 96 },
|
||||
@@ -94,15 +77,17 @@ export function createDocumentsState(editor: EditorState, dialogState: DialogSta
|
||||
|
||||
// Set up message subscriptions on creation
|
||||
editor.dispatcher.subscribeJsMessage(UpdateOpenDocumentsList, (updateOpenDocumentList) => {
|
||||
state.documents = updateOpenDocumentList.open_documents.map(({ name, isSaved }) => new DocumentSaveState(name, isSaved));
|
||||
state.documents = updateOpenDocumentList.open_documents;
|
||||
});
|
||||
|
||||
editor.dispatcher.subscribeJsMessage(SetActiveDocument, (setActiveDocument) => {
|
||||
state.activeDocumentIndex = setActiveDocument.document_index;
|
||||
// Assume we receive a correct document id
|
||||
const activeId = state.documents.findIndex((doc) => doc.id === setActiveDocument.document_id);
|
||||
state.activeDocumentIndex = activeId;
|
||||
});
|
||||
|
||||
editor.dispatcher.subscribeJsMessage(DisplayConfirmationToCloseDocument, (displayConfirmationToCloseDocument) => {
|
||||
closeDocumentWithConfirmation(displayConfirmationToCloseDocument.document_index);
|
||||
closeDocumentWithConfirmation(displayConfirmationToCloseDocument.document_id);
|
||||
});
|
||||
|
||||
editor.dispatcher.subscribeJsMessage(DisplayConfirmationToCloseAllDocuments, () => {
|
||||
@@ -128,8 +113,6 @@ export function createDocumentsState(editor: EditorState, dialogState: DialogSta
|
||||
|
||||
return {
|
||||
state: readonly(state),
|
||||
selectDocument,
|
||||
closeDocumentWithConfirmation,
|
||||
closeAllDocumentsWithConfirmation,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user