mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-04 01:28:12 +08:00
Port frontend from Vue to Svelte (WIP in separate folder, many bugs) (#964)
* Make `tauri build` just work * Move folder: frontend/wasm -> wasm * Create SvelteKit project with 'npx create-svelte' * Move wasm-communication into seperate npm package * Use wasm-pack directly and pack package.json * Got it to work * Add primitive build script for wasm npm module * Fix wasm build script (python) * Clean up glue code * Rewrite wasm build script in node.js * Add serde-reflection to trace types * Add traced types * Generate typescript (.d.ts) from Rust types * Update .d.ts * Finalize TS types * Add script to update .d.ts * Add watch command to build wasm-bindgen * Make wasm work again * Add sass; fix build script for windows * Describe requirement for wasm-pack * Add license * Copy and reorganize vue components * translate LayoutCol.vue * Split app.scss into pieces * Translate LayoutRow.svelte * Rename scss files * Fix compile issues on Windows * WIP port TitleBar * Support classes for LayoutCol/Row * Restructure based on Vue codebase * Port all components in window folder * Port FloatingMenu * Port Document panel component * Update readme after folder move * Update typegen: print discriminant by default * Update typegen: Merge from branch 'tailwind' 4f14fedb Fixes bigint & bytes * Made Vue/webpack/eslint to accept wasm package at new location This is quite a hack. Those two packages are both named the same. Yes, it's an npm package inside another npm package. - frontend/src/wasm-communication/ - frontend-svelte/glue/ 'wasm/pkg/index.js' imports the correct one registered when linking. * Port LayerTree * Port NodeGraph * Port Properties * Port components in /floating-menus * Finish porting all Vue -> Svelte components * Change import prefix * Revert type generation * Revert moved wasm folder * Revert all of @locriacyber's work on this branch - Remove Vite and restore Webpack - Remove SvelteKit - Remove everything except the components I ported to Svelte - Restore all frontend files from Vue code, now altered for Svelte * Convert Vue's 'reactive' and 'provide' to Svelte's stores and contexts * Fix event emitting and bi-di data flow * Undo removal of 'update:' events * Fix 'update:' event dispatching * Fix usage in parent of bi-di component props * Fix component typing, more progress towards no errors * The page builds and opens! * Add loading spinner and remove postcss dependency * Make the basics of document editing work * Fix rebase history Co-authored-by: Locria Cyber <74560659+locriacyber@users.noreply.github.com>
This commit is contained in:
co-authored by
Locria Cyber
parent
01853fe4b7
commit
5619d44300
@@ -0,0 +1,170 @@
|
||||
<script lang="ts">
|
||||
import DialogModal from "@/components/floating-menus/DialogModal.svelte";
|
||||
import LayoutCol from "@/components/layout/LayoutCol.svelte";
|
||||
import LayoutRow from "@/components/layout/LayoutRow.svelte";
|
||||
import Panel from "@/components/window/workspace/Panel.svelte";
|
||||
import { getContext } from "svelte";
|
||||
import { type Editor } from "@/wasm-communication/editor";
|
||||
import { type WorkspaceState } from "@/state-providers/workspace";
|
||||
import { type PortfolioState } from "@/state-providers/portfolio";
|
||||
import { type DialogState } from "@/state-providers/dialog";
|
||||
import { FrontendDocumentDetails } from "@/wasm-communication/messages";
|
||||
|
||||
const MIN_PANEL_SIZE = 100;
|
||||
const PANEL_SIZES = {
|
||||
/**/ root: 100,
|
||||
/* ├── */ content: 80,
|
||||
/* │ ├── */ document: 60,
|
||||
/* │ └── */ graph: 40,
|
||||
/* └── */ details: 20,
|
||||
/* ├── */ properties: 45,
|
||||
/* └── */ layers: 55,
|
||||
};
|
||||
|
||||
let panelSizes = PANEL_SIZES;
|
||||
let documentPanel: Panel;
|
||||
|
||||
$: activeDocumentIndex = $portfolio.activeDocumentIndex;
|
||||
$: nodeGraphVisible = $workspace.nodeGraphVisible;
|
||||
$: documentTabLabels = $portfolio.documents.map((doc: FrontendDocumentDetails) => {
|
||||
const name = doc.displayName;
|
||||
|
||||
if (!editor.instance.inDevelopmentMode()) return { name };
|
||||
|
||||
const tooltip = `Document ID ${doc.id}`;
|
||||
return { name, tooltip };
|
||||
});
|
||||
$: {
|
||||
scrollIntoView(activeDocumentIndex);
|
||||
}
|
||||
function scrollIntoView(activeDocumentIndex: number) {
|
||||
documentPanel?.scrollTabIntoView(activeDocumentIndex);
|
||||
}
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
const workspace = getContext<WorkspaceState>("workspace");
|
||||
const portfolio = getContext<PortfolioState>("portfolio");
|
||||
const dialog = getContext<DialogState>("dialog");
|
||||
|
||||
function resizePanel(e: PointerEvent) {
|
||||
const gutter = (e.target || undefined) as HTMLDivElement | undefined;
|
||||
const nextSibling = (gutter?.nextElementSibling || undefined) as HTMLDivElement | undefined;
|
||||
const prevSibling = (gutter?.previousElementSibling || undefined) as HTMLDivElement | undefined;
|
||||
const parentElement = (gutter?.parentElement || undefined) as HTMLDivElement | undefined;
|
||||
|
||||
const nextSiblingName = (nextSibling?.getAttribute("data-subdivision-name") || undefined) as keyof typeof PANEL_SIZES;
|
||||
const prevSiblingName = (prevSibling?.getAttribute("data-subdivision-name") || undefined) as keyof typeof PANEL_SIZES;
|
||||
|
||||
if (!gutter || !nextSibling || !prevSibling || !parentElement || !nextSiblingName || !prevSiblingName) return;
|
||||
|
||||
// Are we resizing horizontally?
|
||||
const isHorizontal = gutter.getAttribute("data-gutter-horizontal") !== null;
|
||||
|
||||
// Get the current size in px of the panels being resized and the gutter
|
||||
const gutterSize = isHorizontal ? gutter.getBoundingClientRect().width : gutter.getBoundingClientRect().height;
|
||||
const nextSiblingSize = isHorizontal ? nextSibling.getBoundingClientRect().width : nextSibling.getBoundingClientRect().height;
|
||||
const prevSiblingSize = isHorizontal ? prevSibling.getBoundingClientRect().width : prevSibling.getBoundingClientRect().height;
|
||||
const parentElementSize = isHorizontal ? parentElement.getBoundingClientRect().width : parentElement.getBoundingClientRect().height;
|
||||
|
||||
// Measure the resizing panels as a percentage of all sibling panels
|
||||
const totalResizingSpaceOccupied = gutterSize + nextSiblingSize + prevSiblingSize;
|
||||
const proportionBeingResized = totalResizingSpaceOccupied / parentElementSize;
|
||||
|
||||
// Prevent cursor flicker as mouse temporarily leaves the gutter
|
||||
gutter.setPointerCapture(e.pointerId);
|
||||
|
||||
const mouseStart = isHorizontal ? e.clientX : e.clientY;
|
||||
|
||||
const updatePosition = (e: PointerEvent): void => {
|
||||
const mouseCurrent = isHorizontal ? e.clientX : e.clientY;
|
||||
let mouseDelta = mouseStart - mouseCurrent;
|
||||
|
||||
mouseDelta = Math.max(nextSiblingSize + mouseDelta, MIN_PANEL_SIZE) - nextSiblingSize;
|
||||
mouseDelta = prevSiblingSize - Math.max(prevSiblingSize - mouseDelta, MIN_PANEL_SIZE);
|
||||
|
||||
panelSizes[nextSiblingName] = ((nextSiblingSize + mouseDelta) / totalResizingSpaceOccupied) * proportionBeingResized * 100;
|
||||
panelSizes[prevSiblingName] = ((prevSiblingSize - mouseDelta) / totalResizingSpaceOccupied) * proportionBeingResized * 100;
|
||||
|
||||
window.dispatchEvent(new CustomEvent("resize"));
|
||||
};
|
||||
|
||||
const cleanup = (e: PointerEvent): void => {
|
||||
gutter.releasePointerCapture(e.pointerId);
|
||||
|
||||
document.removeEventListener("pointermove", updatePosition);
|
||||
document.removeEventListener("pointerleave", cleanup);
|
||||
document.removeEventListener("pointerup", cleanup);
|
||||
};
|
||||
|
||||
document.addEventListener("pointermove", updatePosition);
|
||||
document.addEventListener("pointerleave", cleanup);
|
||||
document.addEventListener("pointerup", cleanup);
|
||||
}
|
||||
</script>
|
||||
|
||||
<LayoutRow class="workspace" data-workspace>
|
||||
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["root"] }} data-subdivision-name="root">
|
||||
<LayoutCol class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["content"] }} data-subdivision-name="content">
|
||||
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["document"] }} data-subdivision-name="document">
|
||||
<Panel
|
||||
panelType={$portfolio.documents.length > 0 ? "Document" : undefined}
|
||||
tabCloseButtons={true}
|
||||
tabMinWidths={true}
|
||||
tabLabels={documentTabLabels}
|
||||
clickAction={(tabIndex) => editor.instance.selectDocument($portfolio.documents[tabIndex].id)}
|
||||
closeAction={(tabIndex) => editor.instance.closeDocumentWithConfirmation($portfolio.documents[tabIndex].id)}
|
||||
tabActiveIndex={$portfolio.activeDocumentIndex}
|
||||
bind:this={documentPanel}
|
||||
/>
|
||||
</LayoutRow>
|
||||
{#if nodeGraphVisible}
|
||||
<LayoutRow class="workspace-grid-resize-gutter" data-gutter-vertical on:pointerdown={resizePanel} />
|
||||
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["graph"] }} data-subdivision-name="graph">
|
||||
<Panel panelType="NodeGraph" tabLabels={[{ name: "Node Graph" }]} tabActiveIndex={0} />
|
||||
</LayoutRow>
|
||||
{/if}
|
||||
</LayoutCol>
|
||||
<LayoutCol class="workspace-grid-resize-gutter" data-gutter-horizontal on:pointerdown={(e) => resizePanel(e)} />
|
||||
<LayoutCol class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["details"] }} data-subdivision-name="details">
|
||||
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["properties"] }} data-subdivision-name="properties">
|
||||
<Panel panelType="Properties" tabLabels={[{ name: "Properties" }]} tabActiveIndex={0} />
|
||||
</LayoutRow>
|
||||
<LayoutRow class="workspace-grid-resize-gutter" data-gutter-vertical on:pointerdown={(e) => resizePanel(e)} />
|
||||
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["layers"] }} data-subdivision-name="layers">
|
||||
<Panel panelType="LayerTree" tabLabels={[{ name: "Layer Tree" }]} tabActiveIndex={0} />
|
||||
</LayoutRow>
|
||||
</LayoutCol>
|
||||
</LayoutRow>
|
||||
{#if $dialog.visible}
|
||||
<DialogModal />
|
||||
{/if}
|
||||
</LayoutRow>
|
||||
|
||||
<style lang="scss" global>
|
||||
.workspace {
|
||||
position: relative;
|
||||
flex: 1 1 100%;
|
||||
|
||||
.workspace-grid-subdivision {
|
||||
min-height: 28px;
|
||||
flex: 1 1 0;
|
||||
|
||||
&.folded {
|
||||
flex-grow: 0;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.workspace-grid-resize-gutter {
|
||||
flex: 0 0 4px;
|
||||
|
||||
&.layout-row {
|
||||
cursor: ns-resize;
|
||||
}
|
||||
|
||||
&.layout-col {
|
||||
cursor: ew-resize;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user