Restyle and refactor shortcut labels to send hints bar and welcome screen layouts from Rust (#3447)

* Restyle UserInputLabel and refactor its usages to have all input its data sent from Rust

* Replace the welcome screen quick buttons with ones sent by backend

* Add the ShortcutLabel widget to the backend

* Replace hints bar with a backend-controlled layout; show mouse icons in place of mouse labels
This commit is contained in:
Keavon Chambers
2025-12-04 01:04:14 -08:00
committed by GitHub
parent 6ed42d06bb
commit 810ce40e9b
78 changed files with 981 additions and 997 deletions
@@ -3,8 +3,10 @@
import Document from "@graphite/components/panels/Document.svelte";
import Layers from "@graphite/components/panels/Layers.svelte";
import Properties from "@graphite/components/panels/Properties.svelte";
import Welcome from "@graphite/components/panels/Welcome.svelte";
const PANEL_COMPONENTS = {
Welcome,
Document,
Layers,
Properties,
@@ -17,23 +19,16 @@
import { getContext, tick } from "svelte";
import type { Editor } from "@graphite/editor";
import { type LayoutKeysGroup, type Key } from "@graphite/messages";
import type { AppWindowState } from "@graphite/state-providers/app-window";
import { operatingSystem, isEventSupported } from "@graphite/utility-functions/platform";
import { extractPixelData } from "@graphite/utility-functions/rasterization";
import { isEventSupported } from "@graphite/utility-functions/platform";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
import TextButton from "@graphite/components/widgets/buttons/TextButton.svelte";
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
import UserInputLabel from "@graphite/components/widgets/labels/UserInputLabel.svelte";
const BUTTON_LEFT = 0;
const BUTTON_MIDDLE = 1;
const appWindow = getContext<AppWindowState>("appWindow");
const editor = getContext<Editor>("editor");
export let tabMinWidths = false;
@@ -54,52 +49,6 @@
let tabElements: (LayoutRow | undefined)[] = [];
function platformModifiedAccelKey(browserReservedKey: boolean): LayoutKeysGroup {
// TODO: Remove this by properly feeding these keys from a layout provided by the backend
const ALT: Key = { key: "Alt", label: "Alt" };
const COMMAND: Key = { key: "Command", label: "Command" };
const CONTROL: Key = { key: "Control", label: "Ctrl" };
// Only consider the browser reserved key on web platforms
const reservedKey = $appWindow.platform === "Web" ? browserReservedKey : false;
// Return either Command (Mac) or Control (Windows/Linux), with Alt added if the browser reserves the shortcut
if (operatingSystem() === "Mac") return reservedKey ? [ALT, COMMAND] : [COMMAND];
return reservedKey ? [CONTROL, ALT] : [CONTROL];
}
function dropFile(e: DragEvent) {
if (!e.dataTransfer) return;
e.preventDefault();
Array.from(e.dataTransfer.items).forEach(async (item) => {
const file = item.getAsFile();
if (!file) return;
if (file.type.includes("svg")) {
const svgData = await file.text();
editor.handle.pasteSvg(file.name, svgData);
return;
}
if (file.type.startsWith("image")) {
const imageData = await extractPixelData(file);
editor.handle.pasteImage(file.name, new Uint8Array(imageData.data), imageData.width, imageData.height);
return;
}
const graphiteFileSuffix = "." + editor.handle.fileExtension();
if (file.name.endsWith(graphiteFileSuffix)) {
const content = await file.text();
const documentName = file.name.slice(0, -graphiteFileSuffix.length);
editor.handle.openDocumentFile(documentName, content);
return;
}
});
}
function onEmptySpaceAction(e: MouseEvent) {
if (e.target !== e.currentTarget) return;
if (e.button === BUTTON_MIDDLE || (e.button === BUTTON_LEFT && e.detail === 2)) emptySpaceAction?.();
@@ -162,66 +111,10 @@
</LayoutRow>
{/each}
</LayoutRow>
<!-- <PopoverButton style="VerticalEllipsis">
<TextLabel bold={true}>Panel Options</TextLabel>
<TextLabel multiline={true}>Coming soon</TextLabel>
</PopoverButton> -->
</LayoutRow>
<LayoutCol class="panel-body">
{#if panelType}
<svelte:component this={PANEL_COMPONENTS[panelType]} />
{:else}
<LayoutCol class="empty-panel" on:dragover={(e) => e.preventDefault()} on:drop={dropFile}>
<LayoutCol class="top-spacer"></LayoutCol>
<LayoutCol class="content-container">
<LayoutCol class="content">
<LayoutRow class="logotype">
<IconLabel icon="GraphiteLogotypeSolid" />
</LayoutRow>
<LayoutRow class="actions">
<table>
<tbody>
<tr>
<td>
<TextButton label="New Document" icon="File" flush={true} action={() => editor.handle.newDocumentDialog()} />
</td>
<td>
<UserInputLabel keysWithLabelsGroups={[[...platformModifiedAccelKey(true), { key: "KeyN", label: "N" }]]} />
</td>
</tr>
<tr>
<td>
<TextButton label="Open Document" icon="Folder" flush={true} action={() => editor.handle.openDocument()} />
</td>
<td>
<UserInputLabel keysWithLabelsGroups={[[...platformModifiedAccelKey(false), { key: "KeyO", label: "O" }]]} />
</td>
</tr>
<tr>
<td colspan="2">
<TextButton label="Open Demo Artwork" icon="Image" flush={true} action={() => editor.handle.demoArtworkDialog()} />
</td>
</tr>
<tr>
<td colspan="2">
<TextButton label="Support the Development Fund" icon="Heart" flush={true} action={() => editor.handle.visitUrl("https://graphite.rs/donate/")} />
</td>
</tr>
</tbody>
</table>
</LayoutRow>
</LayoutCol>
</LayoutCol>
<LayoutCol class="bottom-message">
{#if new Date().getFullYear() === 2025}
<TextLabel italic={true} disabled={true}>
September 2025 release — <a href="https://youtube.com/watch?v=Vl5BA4g3QXM" target="_blank">What's new? (video)</a>
— Note: some older documents may render differently and require manual fixes.
<a href="https://ec6796b4.graphite-editor.pages.dev/" target="_blank">Need the old version?</a>
</TextLabel>
{/if}
</LayoutCol>
</LayoutCol>
{/if}
</LayoutCol>
</LayoutCol>
@@ -323,7 +216,7 @@
left: -1px;
width: 1px;
height: 16px;
background: var(--color-4-dimgray);
background: var(--color-5-dullgray);
}
&:last-of-type {
@@ -335,15 +228,11 @@
right: -1px;
width: 1px;
height: 16px;
background: var(--color-4-dimgray);
background: var(--color-5-dullgray);
}
}
}
}
// .popover-button {
// margin: 2px 4px;
// }
}
.panel-body {
@@ -354,66 +243,11 @@
> div {
padding-bottom: 4px;
}
.empty-panel {
background: var(--color-2-mildblack);
margin: 4px;
border-radius: 2px;
justify-content: space-between;
.content-container {
flex: 0 0 auto;
justify-content: center;
.content {
flex: 0 0 auto;
align-items: center;
.logotype {
margin-top: 8px;
margin-bottom: 40px;
svg {
width: auto;
height: 120px;
}
}
.actions {
margin-bottom: 8px;
table {
border-spacing: 8px;
margin: -8px;
td {
padding: 0;
}
}
}
}
}
.top-spacer {
flex: 0 1 48px;
}
.bottom-message {
flex: 0 0 48px;
align-items: center;
justify-content: end;
.text-label {
white-space: wrap;
margin: 0 1em;
}
}
}
}
// Needed for the viewport hole punch on desktop
.viewport-hole-punch &.document-panel,
.viewport-hole-punch &.document-panel .panel-body:not(:has(.empty-panel)) {
.viewport-hole-punch &.document-panel .panel-body:not(:has(.welcome-panel)) {
background: none;
}
}
@@ -135,7 +135,7 @@
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["document"] }} data-subdivision-name="document">
<Panel
class="document-panel"
panelType={$portfolio.documents.length > 0 ? "Document" : undefined}
panelType={$portfolio.documents.length > 0 ? "Document" : "Welcome"}
tabCloseButtons={true}
tabMinWidths={true}
tabLabels={documentTabLabels}