mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-23 03:08:12 +08:00
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:
@@ -12,8 +12,8 @@
|
||||
import TextInput from "@graphite/components/widgets/inputs/TextInput.svelte";
|
||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||
import Separator from "@graphite/components/widgets/labels/Separator.svelte";
|
||||
import ShortcutLabel from "@graphite/components/widgets/labels/ShortcutLabel.svelte";
|
||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||
import UserInputLabel from "@graphite/components/widgets/labels/UserInputLabel.svelte";
|
||||
|
||||
let self: FloatingMenu | undefined;
|
||||
let scroller: LayoutCol | undefined;
|
||||
@@ -447,8 +447,8 @@
|
||||
|
||||
<TextLabel class="entry-label" styles={{ "font-family": `${!entry.font ? "inherit" : entry.value}` }}>{entry.label}</TextLabel>
|
||||
|
||||
{#if entry.shortcutKeys?.keys.length}
|
||||
<UserInputLabel keysWithLabelsGroups={[entry.shortcutKeys.keys]} requiresLock={entry.shortcutRequiresLock} textOnly={true} />
|
||||
{#if entry.tooltipShortcut?.shortcut.length}
|
||||
<ShortcutLabel shortcut={entry.tooltipShortcut} />
|
||||
{/if}
|
||||
|
||||
{#if entry.children?.length}
|
||||
@@ -499,7 +499,7 @@
|
||||
margin: 4px 0;
|
||||
|
||||
div {
|
||||
background: var(--color-4-dimgray);
|
||||
background: var(--color-3-darkgray);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,7 +535,7 @@
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.user-input-label {
|
||||
.shortcut-label {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
import { getContext } from "svelte";
|
||||
|
||||
import type { Editor } from "@graphite/editor";
|
||||
import type { LabeledShortcut } from "@graphite/messages";
|
||||
import type { TooltipState } from "@graphite/state-providers/tooltip";
|
||||
|
||||
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import ShortcutLabel from "@graphite/components/widgets/labels/ShortcutLabel.svelte";
|
||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||
|
||||
const tooltip = getContext<TooltipState>("tooltip");
|
||||
@@ -15,7 +17,15 @@
|
||||
|
||||
$: label = filterTodo($tooltip.element?.getAttribute("data-tooltip-label")?.trim());
|
||||
$: description = filterTodo($tooltip.element?.getAttribute("data-tooltip-description")?.trim());
|
||||
$: shortcut = filterTodo($tooltip.element?.getAttribute("data-tooltip-shortcut")?.trim());
|
||||
$: shortcutJSON = $tooltip.element?.getAttribute("data-tooltip-shortcut")?.trim();
|
||||
$: shortcut = ((shortcutJSON) => {
|
||||
if (!shortcutJSON) return undefined;
|
||||
try {
|
||||
return JSON.parse(shortcutJSON) as LabeledShortcut;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
})(shortcutJSON);
|
||||
|
||||
// TODO: Once all TODOs are replaced with real text, remove this function
|
||||
function filterTodo(text: string | undefined): string | undefined {
|
||||
@@ -24,8 +34,8 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="tooltip" style:top={`${$tooltip.position.y}px`} style:left={`${$tooltip.position.x}px`}>
|
||||
{#if label || description}
|
||||
{#if label || description}
|
||||
<div class="tooltip" style:top={`${$tooltip.position.y}px`} style:left={`${$tooltip.position.x}px`}>
|
||||
<FloatingMenu open={true} type="Tooltip" direction="Bottom" bind:this={self}>
|
||||
{#if label || shortcut}
|
||||
<LayoutRow class="tooltip-header">
|
||||
@@ -33,7 +43,7 @@
|
||||
<TextLabel class="tooltip-label">{label}</TextLabel>
|
||||
{/if}
|
||||
{#if shortcut}
|
||||
<TextLabel class="tooltip-shortcut">{shortcut}</TextLabel>
|
||||
<ShortcutLabel shortcut={{ shortcut }} />
|
||||
{/if}
|
||||
</LayoutRow>
|
||||
{/if}
|
||||
@@ -41,8 +51,8 @@
|
||||
<TextLabel class="tooltip-description">{description}</TextLabel>
|
||||
{/if}
|
||||
</FloatingMenu>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss" global>
|
||||
.tooltip {
|
||||
@@ -62,17 +72,10 @@
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.text-label + .tooltip-shortcut {
|
||||
.text-label + .shortcut-label {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.tooltip-shortcut {
|
||||
color: var(--color-b-lightgray);
|
||||
background: var(--color-3-darkgray);
|
||||
padding: 0 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.tooltip-description {
|
||||
color: var(--color-b-lightgray);
|
||||
}
|
||||
|
||||
@@ -519,7 +519,7 @@
|
||||
.floating-menu-content {
|
||||
background: var(--color-2-mildblack);
|
||||
box-shadow: rgba(var(--color-0-black-rgb), 0.5) 0 2px 4px;
|
||||
border: 1px solid var(--color-4-dimgray);
|
||||
border: 1px solid var(--color-3-darkgray);
|
||||
border-radius: 4px;
|
||||
color: var(--color-e-nearwhite);
|
||||
font-size: inherit;
|
||||
@@ -615,7 +615,7 @@
|
||||
&.top .tail,
|
||||
&.topleft .tail,
|
||||
&.topright .tail {
|
||||
border-color: var(--color-4-dimgray) transparent transparent transparent;
|
||||
border-color: var(--color-3-darkgray) transparent transparent transparent;
|
||||
|
||||
&::before {
|
||||
border-color: var(--color-2-mildblack) transparent transparent transparent;
|
||||
@@ -633,7 +633,7 @@
|
||||
&.bottom .tail,
|
||||
&.bottomleft .tail,
|
||||
&.bottomright .tail {
|
||||
border-color: transparent transparent var(--color-4-dimgray) transparent;
|
||||
border-color: transparent transparent var(--color-3-darkgray) transparent;
|
||||
|
||||
&::before {
|
||||
border-color: transparent transparent var(--color-2-mildblack) transparent;
|
||||
@@ -649,7 +649,7 @@
|
||||
}
|
||||
|
||||
&.left .tail {
|
||||
border-color: transparent transparent transparent var(--color-4-dimgray);
|
||||
border-color: transparent transparent transparent var(--color-3-darkgray);
|
||||
|
||||
&::before {
|
||||
border-color: transparent transparent transparent var(--color-2-mildblack);
|
||||
@@ -665,7 +665,7 @@
|
||||
}
|
||||
|
||||
&.right .tail {
|
||||
border-color: transparent var(--color-4-dimgray) transparent transparent;
|
||||
border-color: transparent var(--color-3-darkgray) transparent transparent;
|
||||
|
||||
&::before {
|
||||
border-color: transparent var(--color-2-mildblack) transparent transparent;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { ActionShortcut } from "@graphite/messages";
|
||||
|
||||
let className = "";
|
||||
export { className as class };
|
||||
export let classes: Record<string, boolean> = {};
|
||||
@@ -7,7 +9,7 @@
|
||||
export let styles: Record<string, string | number | undefined> = {};
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let tooltipShortcut: ActionShortcut | undefined = undefined;
|
||||
// TODO: Add middle-click drag scrolling
|
||||
export let scrollableX = false;
|
||||
export let scrollableY = false;
|
||||
@@ -30,7 +32,7 @@
|
||||
<div
|
||||
data-tooltip-label={tooltipLabel}
|
||||
data-tooltip-description={tooltipDescription}
|
||||
data-tooltip-shortcut={tooltipShortcut}
|
||||
data-tooltip-shortcut={tooltipShortcut?.shortcut ? JSON.stringify(tooltipShortcut.shortcut) : undefined}
|
||||
data-scrollable-x={scrollableX ? "" : undefined}
|
||||
data-scrollable-y={scrollableY ? "" : undefined}
|
||||
class={`layout-col ${className} ${extraClasses}`.trim()}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { ActionShortcut } from "@graphite/messages";
|
||||
|
||||
let className = "";
|
||||
export { className as class };
|
||||
export let classes: Record<string, boolean> = {};
|
||||
@@ -7,7 +9,7 @@
|
||||
export let styles: Record<string, string | number | undefined> = {};
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let tooltipShortcut: ActionShortcut | undefined = undefined;
|
||||
// TODO: Add middle-click drag scrolling
|
||||
export let scrollableX = false;
|
||||
export let scrollableY = false;
|
||||
@@ -30,7 +32,7 @@
|
||||
<div
|
||||
data-tooltip-label={tooltipLabel}
|
||||
data-tooltip-description={tooltipDescription}
|
||||
data-tooltip-shortcut={tooltipShortcut}
|
||||
data-tooltip-shortcut={tooltipShortcut?.shortcut ? JSON.stringify(tooltipShortcut.shortcut) : undefined}
|
||||
data-scrollable-x={scrollableX ? "" : undefined}
|
||||
data-scrollable-y={scrollableY ? "" : undefined}
|
||||
class={`layout-row ${className} ${extraClasses}`.trim()}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { getContext, onMount, onDestroy, tick } from "svelte";
|
||||
|
||||
import { shortcutAltClick } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||
import type { Editor } from "@graphite/editor";
|
||||
import {
|
||||
defaultWidgetLayout,
|
||||
@@ -11,7 +12,7 @@
|
||||
UpdateLayersPanelControlBarRightLayout,
|
||||
UpdateLayersPanelBottomBarLayout,
|
||||
} from "@graphite/messages";
|
||||
import type { DataBuffer, LayerPanelEntry } from "@graphite/messages";
|
||||
import type { ActionShortcut, DataBuffer, LayerPanelEntry } from "@graphite/messages";
|
||||
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
|
||||
import { operatingSystem } from "@graphite/utility-functions/platform";
|
||||
import { extractPixelData } from "@graphite/utility-functions/rasterization";
|
||||
@@ -73,6 +74,8 @@
|
||||
let layersPanelControlBarRightLayout = defaultWidgetLayout();
|
||||
let layersPanelBottomBarLayout = defaultWidgetLayout();
|
||||
|
||||
const altClickKeys: ActionShortcut = shortcutAltClick();
|
||||
|
||||
onMount(() => {
|
||||
editor.subscriptions.subscribeJsMessage(UpdateLayersPanelControlBarLeftLayout, (updateLayersPanelControlBarLeftLayout) => {
|
||||
patchWidgetLayout(layersPanelControlBarLeftLayout, updateLayersPanelControlBarLeftLayout);
|
||||
@@ -622,7 +625,7 @@
|
||||
? "Hide the layers nested within. (To affect all open descendants, perform the shortcut shown.)"
|
||||
: "Show the layers nested within. (To affect all closed descendants, perform the shortcut shown.)") +
|
||||
(listing.entry.ancestorOfSelected && !listing.entry.expanded ? "\n\nNote: a selected layer is currently contained within.\n" : "")}
|
||||
data-tooltip-shortcut="Alt Click"
|
||||
data-tooltip-shortcut={altClickKeys?.shortcut ? JSON.stringify(altClickKeys.shortcut) : undefined}
|
||||
on:click={(e) => handleExpandArrowClickWithModifiers(e, listing.entry.id)}
|
||||
tabindex="0"
|
||||
></button>
|
||||
@@ -634,7 +637,7 @@
|
||||
icon="Clipped"
|
||||
class="clipped-arrow"
|
||||
tooltipDescription="Clipping mask is active. To release it, perform the shortcut on the layer border."
|
||||
tooltipShortcut="Alt Click"
|
||||
tooltipShortcut={altClickKeys}
|
||||
/>
|
||||
{/if}
|
||||
<div class="thumbnail">
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
<script lang="ts">
|
||||
import { getContext, onMount, onDestroy } from "svelte";
|
||||
|
||||
import type { Editor } from "@graphite/editor";
|
||||
import { defaultWidgetLayout, patchWidgetLayout, UpdateWelcomeScreenButtonsLayout } from "@graphite/messages";
|
||||
import { extractPixelData } from "@graphite/utility-functions/rasterization";
|
||||
|
||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
|
||||
let welcomePanelButtonsLayout = defaultWidgetLayout();
|
||||
|
||||
onMount(() => {
|
||||
editor.subscriptions.subscribeJsMessage(UpdateWelcomeScreenButtonsLayout, (updateWelcomeScreenButtonsLayout) => {
|
||||
patchWidgetLayout(welcomePanelButtonsLayout, updateWelcomeScreenButtonsLayout);
|
||||
welcomePanelButtonsLayout = welcomePanelButtonsLayout;
|
||||
});
|
||||
|
||||
editor.handle.requestWelcomeScreenButtonsLayout();
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
editor.subscriptions.unsubscribeJsMessage(UpdateWelcomeScreenButtonsLayout);
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<LayoutCol class="welcome-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">
|
||||
<WidgetLayout layout={welcomePanelButtonsLayout} />
|
||||
</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>
|
||||
|
||||
<style lang="scss" global>
|
||||
.welcome-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;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -588,10 +588,11 @@
|
||||
data-visibility-button
|
||||
size={24}
|
||||
icon={node.visible ? "EyeVisible" : "EyeHidden"}
|
||||
hoverIcon={node.visible ? "EyeHide" : "EyeShow"}
|
||||
action={() => {
|
||||
/* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */
|
||||
}}
|
||||
tooltipLabel={node.visible ? "Visible" : "Hidden"}
|
||||
tooltipLabel={node.visible ? "Hide" : "Show"}
|
||||
/>
|
||||
|
||||
<svg class="border-mask" width="0" height="0">
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
{:else if isWidgetSection(layoutGroup)}
|
||||
<WidgetSection widgetData={layoutGroup} layoutTarget={layout.layoutTarget} class={className} {classes} />
|
||||
{:else if isWidgetTable(layoutGroup)}
|
||||
<WidgetTable widgetData={layoutGroup} layoutTarget={layout.layoutTarget} />
|
||||
<WidgetTable widgetData={layoutGroup} unstyled={layoutGroup.unstyled} layoutTarget={layout.layoutTarget} />
|
||||
{:else}
|
||||
<TextLabel styles={{ color: "#d6536e" }}>Error: The widget layout that belongs here has an invalid layout group type</TextLabel>
|
||||
{/if}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||
import ImageLabel from "@graphite/components/widgets/labels/ImageLabel.svelte";
|
||||
import Separator from "@graphite/components/widgets/labels/Separator.svelte";
|
||||
import ShortcutLabel from "@graphite/components/widgets/labels/ShortcutLabel.svelte";
|
||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||
|
||||
@@ -127,6 +128,11 @@
|
||||
{#if iconLabel}
|
||||
<IconLabel {...exclude(iconLabel)} />
|
||||
{/if}
|
||||
{@const shortcutLabel = narrowWidgetProps(component.props, "ShortcutLabel")}
|
||||
{@const shortcutLabelShortcut = shortcutLabel?.shortcut ? { ...shortcutLabel, shortcut: shortcutLabel.shortcut } : undefined}
|
||||
{#if shortcutLabel && shortcutLabelShortcut}
|
||||
<ShortcutLabel {...exclude(shortcutLabelShortcut)} />
|
||||
{/if}
|
||||
{@const imageLabel = narrowWidgetProps(component.props, "ImageLabel")}
|
||||
{#if imageLabel}
|
||||
<ImageLabel {...exclude(imageLabel)} />
|
||||
|
||||
@@ -5,15 +5,18 @@
|
||||
|
||||
export let widgetData: WidgetTableFromJsMessages;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export let layoutTarget: any; // TODO: Give this a real type
|
||||
export let layoutTarget: any;
|
||||
export let unstyled = false;
|
||||
|
||||
$: columns = widgetData.tableWidgets.length > 0 ? widgetData.tableWidgets[0].length : 0;
|
||||
</script>
|
||||
|
||||
<table>
|
||||
<table class:unstyled>
|
||||
<tbody>
|
||||
{#each widgetData.tableWidgets as row}
|
||||
<tr>
|
||||
{#each row as cell}
|
||||
<td>
|
||||
<td colspan={row.length < columns ? columns - row.length + 1 : undefined}>
|
||||
<WidgetSpan widgetData={{ rowWidgets: [cell] }} {layoutTarget} narrow={true} />
|
||||
</td>
|
||||
{/each}
|
||||
@@ -23,7 +26,7 @@
|
||||
</table>
|
||||
|
||||
<style lang="scss">
|
||||
table {
|
||||
table:not(.unstyled) {
|
||||
background: var(--color-3-darkgray);
|
||||
border: none;
|
||||
border-spacing: 4px;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { ActionShortcut } from "@graphite/messages";
|
||||
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import TextButton from "@graphite/components/widgets/buttons/TextButton.svelte";
|
||||
|
||||
@@ -6,7 +8,7 @@
|
||||
export let disabled = false;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let tooltipShortcut: ActionShortcut | undefined = undefined;
|
||||
// Callbacks
|
||||
export let action: (index: number) => void;
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { type IconName, type IconSize } from "@graphite/icons";
|
||||
import type { ActionShortcut } from "@graphite/messages";
|
||||
|
||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||
|
||||
@@ -10,7 +11,7 @@
|
||||
export let active = false;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let tooltipShortcut: ActionShortcut | undefined = undefined;
|
||||
// Callbacks
|
||||
export let action: (e?: MouseEvent) => void;
|
||||
|
||||
@@ -32,7 +33,7 @@
|
||||
{disabled}
|
||||
data-tooltip-label={tooltipLabel}
|
||||
data-tooltip-description={tooltipDescription}
|
||||
data-tooltip-shortcut={tooltipShortcut}
|
||||
data-tooltip-shortcut={tooltipShortcut?.shortcut ? JSON.stringify(tooltipShortcut.shortcut) : undefined}
|
||||
tabindex={active ? -1 : 0}
|
||||
{...$$restProps}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { ActionShortcut } from "@graphite/messages";
|
||||
import { IMAGE_BASE64_STRINGS } from "@graphite/utility-functions/images";
|
||||
|
||||
let className = "";
|
||||
@@ -10,7 +11,7 @@
|
||||
export let height: string | undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let tooltipShortcut: ActionShortcut | undefined = undefined;
|
||||
// Callbacks
|
||||
export let action: (e?: MouseEvent) => void;
|
||||
|
||||
@@ -26,7 +27,7 @@
|
||||
class={`image-button ${className} ${extraClasses}`.trim()}
|
||||
data-tooltip-label={tooltipLabel}
|
||||
data-tooltip-description={tooltipDescription}
|
||||
data-tooltip-shortcut={tooltipShortcut}
|
||||
data-tooltip-shortcut={tooltipShortcut?.shortcut ? JSON.stringify(tooltipShortcut.shortcut) : undefined}
|
||||
alt=""
|
||||
on:click={action}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { FrontendGraphDataType } from "@graphite/messages";
|
||||
import type { FrontendGraphDataType, ActionShortcut } from "@graphite/messages";
|
||||
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
export let dataType: FrontendGraphDataType;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let tooltipShortcut: ActionShortcut | undefined = undefined;
|
||||
// Callbacks
|
||||
export let action: (e?: MouseEvent) => void;
|
||||
</script>
|
||||
@@ -20,7 +20,7 @@
|
||||
on:click={action}
|
||||
data-tooltip-label={tooltipLabel}
|
||||
data-tooltip-description={tooltipDescription}
|
||||
data-tooltip-shortcut={tooltipShortcut}
|
||||
data-tooltip-shortcut={tooltipShortcut?.shortcut ? JSON.stringify(tooltipShortcut.shortcut) : undefined}
|
||||
tabindex="-1"
|
||||
>
|
||||
{#if !exposed}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { type IconName, type PopoverButtonStyle } from "@graphite/icons";
|
||||
|
||||
import type { MenuDirection } from "@graphite/messages";
|
||||
import type { MenuDirection, ActionShortcut } from "@graphite/messages";
|
||||
|
||||
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
@@ -13,7 +13,7 @@
|
||||
export let icon: IconName | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let tooltipShortcut: ActionShortcut | undefined = undefined;
|
||||
export let disabled = false;
|
||||
export let popoverMinWidth = 1;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
import type { IconName } from "@graphite/icons";
|
||||
import type { MenuListEntry } from "@graphite/messages";
|
||||
import type { MenuListEntry, ActionShortcut } from "@graphite/messages";
|
||||
|
||||
import MenuList from "@graphite/components/floating-menus/MenuList.svelte";
|
||||
import ConditionalWrapper from "@graphite/components/layout/ConditionalWrapper.svelte";
|
||||
@@ -25,7 +25,7 @@
|
||||
export let narrow = false;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let tooltipShortcut: ActionShortcut | undefined = undefined;
|
||||
export let menuListChildren: MenuListEntry[][] | undefined = undefined;
|
||||
|
||||
// Callbacks
|
||||
@@ -66,7 +66,7 @@
|
||||
style:min-width={minWidth > 0 ? `${minWidth}px` : undefined}
|
||||
data-tooltip-label={tooltipLabel}
|
||||
data-tooltip-description={tooltipDescription}
|
||||
data-tooltip-shortcut={tooltipShortcut}
|
||||
data-tooltip-shortcut={tooltipShortcut?.shortcut ? JSON.stringify(tooltipShortcut.shortcut) : undefined}
|
||||
data-emphasized={emphasized || undefined}
|
||||
data-disabled={disabled || undefined}
|
||||
data-text-button
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
import type { IconName } from "@graphite/icons";
|
||||
import type { ActionShortcut } from "@graphite/messages";
|
||||
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||
@@ -13,7 +14,7 @@
|
||||
export let icon: IconName = "Checkmark";
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let tooltipShortcut: ActionShortcut | undefined = undefined;
|
||||
export let forLabel: bigint | undefined = undefined;
|
||||
|
||||
let inputElement: HTMLInputElement | undefined;
|
||||
@@ -55,7 +56,7 @@
|
||||
on:keydown={(e) => e.key === "Enter" && toggleCheckboxFromLabel(e)}
|
||||
data-tooltip-label={tooltipLabel}
|
||||
data-tooltip-description={tooltipDescription}
|
||||
data-tooltip-shortcut={tooltipShortcut}
|
||||
data-tooltip-shortcut={tooltipShortcut?.shortcut ? JSON.stringify(tooltipShortcut.shortcut) : undefined}
|
||||
>
|
||||
<LayoutRow class="checkbox-box">
|
||||
<IconLabel icon={displayIcon} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
import type { FillChoice, MenuDirection } from "@graphite/messages";
|
||||
import type { FillChoice, MenuDirection, ActionShortcut } from "@graphite/messages";
|
||||
import { Color, contrastingOutlineFactor, Gradient } from "@graphite/messages";
|
||||
|
||||
import ColorPicker from "@graphite/components/floating-menus/ColorPicker.svelte";
|
||||
@@ -19,7 +19,7 @@
|
||||
// export let allowTransparency = false; // TODO: Implement
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let tooltipShortcut: ActionShortcut | undefined = undefined;
|
||||
|
||||
$: outlineFactor = contrastingOutlineFactor(value, ["--color-1-nearblack", "--color-3-darkgray"], 0.01);
|
||||
$: outlined = outlineFactor > 0.0001;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
import type { Curve, CurveManipulatorGroup } from "@graphite/messages";
|
||||
import type { Curve, CurveManipulatorGroup, ActionShortcut } from "@graphite/messages";
|
||||
import { clamp } from "@graphite/utility-functions/math";
|
||||
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
@@ -18,7 +18,7 @@
|
||||
export let disabled = false;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let tooltipShortcut: ActionShortcut | undefined = undefined;
|
||||
|
||||
const GRID_SIZE = 4;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
import type { MenuListEntry } from "@graphite/messages";
|
||||
import type { MenuListEntry, ActionShortcut } from "@graphite/messages";
|
||||
|
||||
import MenuList from "@graphite/components/floating-menus/MenuList.svelte";
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
@@ -23,7 +23,7 @@
|
||||
export let narrow = false;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let tooltipShortcut: ActionShortcut | undefined = undefined;
|
||||
export let minWidth = 0;
|
||||
export let maxWidth = 0;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
import type { ActionShortcut } from "@graphite/messages";
|
||||
import { operatingSystem } from "@graphite/utility-functions/platform";
|
||||
|
||||
import { preventEscapeClosingParentFloatingMenu } from "@graphite/components/layout/FloatingMenu.svelte";
|
||||
@@ -27,7 +28,7 @@
|
||||
export let textarea = false;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let tooltipShortcut: ActionShortcut | undefined = undefined;
|
||||
export let placeholder: string | undefined = undefined;
|
||||
export let hideContextMenu = false;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, getContext, onMount, tick } from "svelte";
|
||||
|
||||
import type { MenuListEntry } from "@graphite/messages";
|
||||
import type { MenuListEntry, ActionShortcut } from "@graphite/messages";
|
||||
import type { FontsState } from "@graphite/state-providers/fonts";
|
||||
|
||||
import MenuList from "@graphite/components/floating-menus/MenuList.svelte";
|
||||
@@ -25,7 +25,7 @@
|
||||
export let disabled = false;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let tooltipShortcut: ActionShortcut | undefined = undefined;
|
||||
|
||||
let open = false;
|
||||
let entries: MenuListEntry[] = [];
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, onMount, onDestroy } from "svelte";
|
||||
|
||||
import { evaluateMathExpression } from "@graphite/../wasm/pkg/graphite_wasm.js";
|
||||
import { evaluateMathExpression } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||
import { PRESS_REPEAT_DELAY_MS, PRESS_REPEAT_INTERVAL_MS } from "@graphite/io-managers/input";
|
||||
import { type NumberInputMode, type NumberInputIncrementBehavior } from "@graphite/messages";
|
||||
import type { NumberInputMode, NumberInputIncrementBehavior, ActionShortcut } from "@graphite/messages";
|
||||
import { browserVersion, isDesktop } from "@graphite/utility-functions/platform";
|
||||
|
||||
import { preventEscapeClosingParentFloatingMenu } from "@graphite/components/layout/FloatingMenu.svelte";
|
||||
@@ -20,7 +20,7 @@
|
||||
export let label: string | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let tooltipShortcut: ActionShortcut | undefined = undefined;
|
||||
|
||||
// Disabled
|
||||
export let disabled = false;
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
on:click={() => handleEntryClick(entry)}
|
||||
data-tooltip-label={entry.tooltipLabel}
|
||||
data-tooltip-description={entry.tooltipDescription}
|
||||
data-tooltip-shortcut={entry.tooltipShortcut}
|
||||
data-tooltip-shortcut={entry.tooltipShortcut?.shortcut ? JSON.stringify(entry.tooltipShortcut.shortcut) : undefined}
|
||||
tabindex={index === selectedIndex ? -1 : 0}
|
||||
{disabled}
|
||||
>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
import type { ReferencePoint } from "@graphite/messages";
|
||||
import type { ReferencePoint, ActionShortcut } from "@graphite/messages";
|
||||
|
||||
const dispatch = createEventDispatcher<{ value: ReferencePoint }>();
|
||||
|
||||
@@ -9,14 +9,20 @@
|
||||
export let disabled = false;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let tooltipShortcut: ActionShortcut | undefined = undefined;
|
||||
|
||||
function setValue(newValue: ReferencePoint) {
|
||||
dispatch("value", newValue);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="reference-point-input" class:disabled data-tooltip-label={tooltipLabel} data-tooltip-description={tooltipDescription} data-tooltip-shortcut={tooltipShortcut}>
|
||||
<div
|
||||
class="reference-point-input"
|
||||
class:disabled
|
||||
data-tooltip-label={tooltipLabel}
|
||||
data-tooltip-description={tooltipDescription}
|
||||
data-tooltip-shortcut={tooltipShortcut?.shortcut ? JSON.stringify(tooltipShortcut.shortcut) : undefined}
|
||||
>
|
||||
<button on:click={() => setValue("TopLeft")} class="row-1 col-1" class:active={value === "TopLeft"} tabindex="-1" {disabled}><div /></button>
|
||||
<button on:click={() => setValue("TopCenter")} class="row-1 col-2" class:active={value === "TopCenter"} tabindex="-1" {disabled}><div /></button>
|
||||
<button on:click={() => setValue("TopRight")} class="row-1 col-3" class:active={value === "TopRight"} tabindex="-1" {disabled}><div /></button>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
// export let disabled = false;
|
||||
// export let tooltipLabel: string | undefined = undefined;
|
||||
// export let tooltipDescription: string | undefined = undefined;
|
||||
// export let tooltipShortcut: string | undefined = undefined;
|
||||
// export let tooltipShortcut: ActionShortcut | undefined = undefined;
|
||||
|
||||
let markerTrack: LayoutRow | undefined = undefined;
|
||||
let positionRestore: number | undefined = undefined;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
import type { ActionShortcut } from "@graphite/messages";
|
||||
|
||||
import FieldInput from "@graphite/components/widgets/inputs/FieldInput.svelte";
|
||||
|
||||
const dispatch = createEventDispatcher<{ commitText: string }>();
|
||||
@@ -9,7 +11,7 @@
|
||||
export let label: string | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let tooltipShortcut: ActionShortcut | undefined = undefined;
|
||||
export let disabled = false;
|
||||
|
||||
let self: FieldInput | undefined;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
import type { ActionShortcut } from "@graphite/messages";
|
||||
|
||||
import FieldInput from "@graphite/components/widgets/inputs/FieldInput.svelte";
|
||||
|
||||
const dispatch = createEventDispatcher<{ commitText: string }>();
|
||||
@@ -9,7 +11,7 @@
|
||||
export let label: string | undefined = undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let tooltipShortcut: ActionShortcut | undefined = undefined;
|
||||
export let placeholder: string | undefined = undefined;
|
||||
// Disabled
|
||||
export let disabled = false;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { type IconName, ICONS, ICON_SVG_STRINGS } from "@graphite/icons";
|
||||
import type { ActionShortcut } from "@graphite/messages";
|
||||
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
|
||||
@@ -11,7 +12,7 @@
|
||||
export let disabled = false;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let tooltipShortcut: ActionShortcut | undefined = undefined;
|
||||
|
||||
$: iconSizeClass = ((icon: IconName) => {
|
||||
const iconData = ICONS[icon];
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { ActionShortcut } from "@graphite/messages";
|
||||
|
||||
let className = "";
|
||||
export { className as class };
|
||||
export let classes: Record<string, boolean> = {};
|
||||
@@ -8,7 +10,7 @@
|
||||
export let height: string | undefined;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let tooltipShortcut: ActionShortcut | undefined = undefined;
|
||||
|
||||
$: extraClasses = Object.entries(classes)
|
||||
.flatMap(([className, stateName]) => (stateName ? [className] : []))
|
||||
@@ -22,7 +24,7 @@
|
||||
class={`image-label ${className} ${extraClasses}`.trim()}
|
||||
data-tooltip-label={tooltipLabel}
|
||||
data-tooltip-description={tooltipDescription}
|
||||
data-tooltip-shortcut={tooltipShortcut}
|
||||
data-tooltip-shortcut={tooltipShortcut?.shortcut ? JSON.stringify(tooltipShortcut.shortcut) : undefined}
|
||||
alt=""
|
||||
/>
|
||||
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
<script lang="ts">
|
||||
import type { IconName } from "@graphite/icons";
|
||||
import type { ActionShortcut, KeyRaw, LabeledShortcut, MouseMotion } from "@graphite/messages";
|
||||
import { operatingSystem } from "@graphite/utility-functions/platform";
|
||||
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||
|
||||
export let shortcut: ActionShortcut;
|
||||
|
||||
function keyTextOrIconList(keyGroup: LabeledShortcut): { label?: string; icon?: IconName; mouseMotion?: MouseMotion }[] {
|
||||
const list = keyGroup.map((labeledKeyOrMouseMotion) => {
|
||||
// Use a mouse icon if it's a mouse motion instead of a key
|
||||
if (typeof labeledKeyOrMouseMotion === "string") return { mouseMotion: labeledKeyOrMouseMotion };
|
||||
|
||||
// `key` is the name of the `Key` enum in Rust, while `label` is the localized string to display (if it doesn't become an icon)
|
||||
let key = labeledKeyOrMouseMotion.key;
|
||||
const label = labeledKeyOrMouseMotion.label;
|
||||
|
||||
// Replace Alt and Accel keys with their Mac-specific equivalents
|
||||
if (operatingSystem() === "Mac") {
|
||||
if (key === "Alt") key = "Option";
|
||||
if (key === "Accel") key = "Command";
|
||||
}
|
||||
|
||||
// Either display an icon...
|
||||
const icon = keyboardHintIcon(key);
|
||||
if (icon) return { icon };
|
||||
|
||||
// ...or display text
|
||||
return { label };
|
||||
});
|
||||
|
||||
// Consolidate consecutive labels into a concatenated single label
|
||||
const consolidatedList: typeof list = [];
|
||||
list.forEach((item) => {
|
||||
const lastItem = consolidatedList[consolidatedList.length - 1];
|
||||
if (item.label && lastItem?.label) lastItem.label += " " + item.label;
|
||||
else consolidatedList.push(item);
|
||||
});
|
||||
return consolidatedList;
|
||||
}
|
||||
|
||||
function keyboardHintIcon(input: KeyRaw): IconName | undefined {
|
||||
switch (input) {
|
||||
case "ArrowDown":
|
||||
return "KeyboardArrowDown";
|
||||
case "ArrowLeft":
|
||||
return "KeyboardArrowLeft";
|
||||
case "ArrowRight":
|
||||
return "KeyboardArrowRight";
|
||||
case "ArrowUp":
|
||||
return "KeyboardArrowUp";
|
||||
case "Backspace":
|
||||
return "KeyboardBackspace";
|
||||
case "Enter":
|
||||
return "KeyboardEnter";
|
||||
case "Space":
|
||||
return "KeyboardSpace";
|
||||
case "Tab":
|
||||
return "KeyboardTab";
|
||||
case "Command":
|
||||
return operatingSystem() === "Mac" ? "KeyboardCommand" : undefined;
|
||||
case "Control":
|
||||
return operatingSystem() === "Mac" ? "KeyboardControl" : undefined;
|
||||
case "Option":
|
||||
return operatingSystem() === "Mac" ? "KeyboardOption" : undefined;
|
||||
case "Shift":
|
||||
return operatingSystem() === "Mac" ? "KeyboardShift" : undefined;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function mouseHintIcon(input?: MouseMotion): IconName {
|
||||
return `MouseHint${input}` as IconName;
|
||||
}
|
||||
</script>
|
||||
|
||||
<LayoutRow class="shortcut-label">
|
||||
{#each keyTextOrIconList(shortcut.shortcut) as { label, icon, mouseMotion }}
|
||||
{#if label}
|
||||
<div class="key-label">
|
||||
<TextLabel>{label}</TextLabel>
|
||||
</div>
|
||||
{:else if icon}
|
||||
<div class="key-icon">
|
||||
<IconLabel {icon} />
|
||||
</div>
|
||||
{:else if mouseMotion}
|
||||
<div class="mouse-icon">
|
||||
<IconLabel icon={mouseHintIcon(mouseMotion)} />
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</LayoutRow>
|
||||
|
||||
<style lang="scss" global>
|
||||
.shortcut-label {
|
||||
.key-icon,
|
||||
.key-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 16px;
|
||||
padding: 0 4px;
|
||||
border-radius: 4px;
|
||||
background: var(--color-3-darkgray);
|
||||
color: var(--color-b-lightgray);
|
||||
fill: var(--color-b-lightgray);
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: var(--color-b-lightgray);
|
||||
|
||||
.dim {
|
||||
fill: var(--color-8-uppergray);
|
||||
}
|
||||
}
|
||||
|
||||
.floating-menu-content .row > & {
|
||||
.key-label,
|
||||
.key-icon,
|
||||
.mouse-icon {
|
||||
color: var(--color-8-uppergray);
|
||||
background: none;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.key-icon svg {
|
||||
fill: var(--color-8-uppergray);
|
||||
}
|
||||
|
||||
.mouse-icon svg {
|
||||
// 3 shades brighter than the 8-uppergray of key labels/icons
|
||||
fill: var(--color-b-lightgray);
|
||||
|
||||
.dim {
|
||||
// 3 shades darker than the 8-uppergray of key labels/icons
|
||||
fill: var(--color-5-dullgray);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { ActionShortcut } from "@graphite/messages";
|
||||
|
||||
let className = "";
|
||||
export { className as class };
|
||||
export let classes: Record<string, boolean> = {};
|
||||
@@ -16,7 +18,7 @@
|
||||
export let multiline = false;
|
||||
export let tooltipLabel: string | undefined = undefined;
|
||||
export let tooltipDescription: string | undefined = undefined;
|
||||
export let tooltipShortcut: string | undefined = undefined;
|
||||
export let tooltipShortcut: ActionShortcut | undefined = undefined;
|
||||
export let forCheckbox: bigint | undefined = undefined;
|
||||
|
||||
$: extraClasses = Object.entries(classes)
|
||||
@@ -41,7 +43,7 @@
|
||||
style={`${styleName} ${extraStyles}`.trim() || undefined}
|
||||
data-tooltip-label={tooltipLabel}
|
||||
data-tooltip-description={tooltipDescription}
|
||||
data-tooltip-shortcut={tooltipShortcut}
|
||||
data-tooltip-shortcut={tooltipShortcut?.shortcut ? JSON.stringify(tooltipShortcut.shortcut) : undefined}
|
||||
for={forCheckbox !== undefined ? `checkbox-input-${forCheckbox}` : undefined}
|
||||
>
|
||||
<slot />
|
||||
|
||||
@@ -1,273 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from "svelte";
|
||||
|
||||
import type { IconName } from "@graphite/icons";
|
||||
|
||||
import { type KeyRaw, type LayoutKeysGroup, type Key, type MouseMotion } from "@graphite/messages";
|
||||
import type { FullscreenState } from "@graphite/state-providers/fullscreen";
|
||||
import { operatingSystem } from "@graphite/utility-functions/platform";
|
||||
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||
import Separator from "@graphite/components/widgets/labels/Separator.svelte";
|
||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||
|
||||
type LabelData = { label?: string; icon?: IconName; width: string };
|
||||
|
||||
// Keys that become icons if they are listed here with their units of width
|
||||
const ICON_WIDTHS_MAC = {
|
||||
Shift: 2,
|
||||
Control: 2,
|
||||
Option: 2,
|
||||
Command: 2,
|
||||
};
|
||||
const ICON_WIDTHS = {
|
||||
ArrowUp: 1,
|
||||
ArrowRight: 1,
|
||||
ArrowDown: 1,
|
||||
ArrowLeft: 1,
|
||||
Backspace: 2,
|
||||
Enter: 2,
|
||||
Tab: 2,
|
||||
Space: 3,
|
||||
...(operatingSystem() === "Mac" ? ICON_WIDTHS_MAC : {}),
|
||||
};
|
||||
|
||||
const fullscreen = getContext<FullscreenState>("fullscreen");
|
||||
|
||||
export let keysWithLabelsGroups: LayoutKeysGroup[] = [];
|
||||
export let mouseMotion: MouseMotion | undefined = undefined;
|
||||
export let requiresLock = false;
|
||||
export let textOnly = false;
|
||||
|
||||
$: keyboardLockInfoMessage = watchKeyboardLockInfoMessage($fullscreen.keyboardLockApiSupported);
|
||||
|
||||
$: displayKeyboardLockNotice = requiresLock && !$fullscreen.keyboardLocked;
|
||||
|
||||
function watchKeyboardLockInfoMessage(keyboardLockApiSupported: boolean): string {
|
||||
const RESERVED = "This keyboard shortcut is reserved by the browser.";
|
||||
const USE_FULLSCREEN = "It is made available in fullscreen mode.";
|
||||
const USE_SECURE_CTX = "It is made available in fullscreen mode when this website is served from a secure context (https or localhost).";
|
||||
const SWITCH_BROWSER = "Use a Chromium-based browser (like Chrome or Edge) in fullscreen mode to directly use the shortcut.";
|
||||
|
||||
if (keyboardLockApiSupported) return `${RESERVED} ${USE_FULLSCREEN}`;
|
||||
if (!("chrome" in window)) return `${RESERVED} ${SWITCH_BROWSER}`;
|
||||
if (!window.isSecureContext) return `${RESERVED} ${USE_SECURE_CTX}`;
|
||||
return RESERVED;
|
||||
}
|
||||
|
||||
function keyTextOrIconList(keyGroup: LayoutKeysGroup): LabelData[] {
|
||||
return keyGroup.map((key) => keyTextOrIcon(key));
|
||||
}
|
||||
|
||||
function keyTextOrIcon(keyWithLabel: Key): LabelData {
|
||||
// `key` is the name of the `Key` enum in Rust, while `label` is the localized string to display (if it doesn't become an icon)
|
||||
let key = keyWithLabel.key;
|
||||
const label = keyWithLabel.label;
|
||||
|
||||
// Replace Alt and Accel keys with their Mac-specific equivalents
|
||||
if (operatingSystem() === "Mac") {
|
||||
if (key === "Alt") key = "Option";
|
||||
if (key === "Accel") key = "Command";
|
||||
}
|
||||
|
||||
// Either display an icon...
|
||||
// @ts-expect-error We want undefined if it isn't in the object
|
||||
const iconWidth: number | undefined = ICON_WIDTHS[key];
|
||||
const icon = iconWidth !== undefined && iconWidth > 0 && (keyboardHintIcon(key) || false);
|
||||
if (icon) return { icon, width: `width-${iconWidth}` };
|
||||
|
||||
// ...or display text
|
||||
return { label, width: `width-${label.length}` };
|
||||
}
|
||||
|
||||
function mouseHintIcon(input?: MouseMotion): IconName {
|
||||
return `MouseHint${input}` as IconName;
|
||||
}
|
||||
|
||||
function keyboardHintIcon(input: KeyRaw): IconName | undefined {
|
||||
switch (input) {
|
||||
case "ArrowDown":
|
||||
return "KeyboardArrowDown";
|
||||
case "ArrowLeft":
|
||||
return "KeyboardArrowLeft";
|
||||
case "ArrowRight":
|
||||
return "KeyboardArrowRight";
|
||||
case "ArrowUp":
|
||||
return "KeyboardArrowUp";
|
||||
case "Backspace":
|
||||
return "KeyboardBackspace";
|
||||
case "Command":
|
||||
return "KeyboardCommand";
|
||||
case "Control":
|
||||
return "KeyboardControl";
|
||||
case "Enter":
|
||||
return "KeyboardEnter";
|
||||
case "Option":
|
||||
return "KeyboardOption";
|
||||
case "Shift":
|
||||
return "KeyboardShift";
|
||||
case "Space":
|
||||
return "KeyboardSpace";
|
||||
case "Tab":
|
||||
return "KeyboardTab";
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if displayKeyboardLockNotice}
|
||||
<IconLabel class="user-input-label keyboard-lock-notice" icon="Info" tooltipDescription={keyboardLockInfoMessage} />
|
||||
{:else}
|
||||
<LayoutRow class="user-input-label" classes={{ "text-only": textOnly }}>
|
||||
{#each keysWithLabelsGroups as keysWithLabels, groupIndex}
|
||||
{#if groupIndex > 0}
|
||||
<Separator type="Related" />
|
||||
{/if}
|
||||
{#each keyTextOrIconList(keysWithLabels) as keyInfo}
|
||||
<div class={`input-key ${keyInfo.width}`}>
|
||||
{#if keyInfo.icon}
|
||||
<IconLabel icon={keyInfo.icon} />
|
||||
{:else if keyInfo.label !== undefined}
|
||||
<TextLabel>{keyInfo.label}</TextLabel>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{/each}
|
||||
{#if mouseMotion}
|
||||
<div class="input-mouse">
|
||||
<IconLabel icon={mouseHintIcon(mouseMotion)} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $$slots.default}
|
||||
<div class="hint-text">
|
||||
<slot />
|
||||
</div>
|
||||
{/if}
|
||||
</LayoutRow>
|
||||
{/if}
|
||||
|
||||
<style lang="scss" global>
|
||||
.user-input-label {
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
|
||||
&.text-only {
|
||||
display: flex;
|
||||
|
||||
.input-key {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.icon-label {
|
||||
margin: calc(calc(18px - 12px) / 2) 0;
|
||||
}
|
||||
|
||||
& + .input-key {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.text-only) {
|
||||
.input-key,
|
||||
.input-mouse {
|
||||
& + .input-key,
|
||||
& + .input-mouse {
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.input-key {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-family: "Source Code Pro", monospace;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
height: 16px;
|
||||
border-radius: 4px;
|
||||
background: var(--color-3-darkgray);
|
||||
color: var(--color-b-lightgray);
|
||||
|
||||
.icon-label {
|
||||
fill: var(--color-b-lightgray);
|
||||
}
|
||||
|
||||
.text-label {
|
||||
// Firefox renders the text 1px lower than Chrome (tested on Windows) with 16px line-height,
|
||||
// so moving it up 1 pixel by using 15px makes them agree.
|
||||
line-height: 15px;
|
||||
}
|
||||
|
||||
&.width-1 {
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
&.width-2 {
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
&.width-3 {
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
&.width-4 {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
&.width-5 {
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
.icon-label {
|
||||
margin: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input-mouse {
|
||||
.bright {
|
||||
fill: var(--color-b-lightgray);
|
||||
}
|
||||
|
||||
.dim {
|
||||
fill: var(--color-8-uppergray);
|
||||
}
|
||||
}
|
||||
|
||||
.hint-text:not(:empty) {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.floating-menu-content .row > & {
|
||||
.input-key {
|
||||
border-color: var(--color-3-darkgray);
|
||||
color: var(--color-8-uppergray);
|
||||
}
|
||||
|
||||
.input-key .icon-label svg,
|
||||
&.keyboard-lock-notice.keyboard-lock-notice svg,
|
||||
.input-mouse .bright {
|
||||
fill: var(--color-8-uppergray);
|
||||
}
|
||||
|
||||
.input-mouse .dim {
|
||||
fill: var(--color-3-darkgray);
|
||||
}
|
||||
}
|
||||
|
||||
.floating-menu-content .row:hover > & {
|
||||
.input-key {
|
||||
border-color: var(--color-8-uppergray);
|
||||
}
|
||||
|
||||
.input-mouse .dim {
|
||||
fill: var(--color-8-uppergray);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -2,45 +2,25 @@
|
||||
import { getContext, onMount } from "svelte";
|
||||
|
||||
import type { Editor } from "@graphite/editor";
|
||||
import { type HintData, type HintInfo, type LayoutKeysGroup, UpdateInputHints } from "@graphite/messages";
|
||||
import { operatingSystem } from "@graphite/utility-functions/platform";
|
||||
import { defaultWidgetLayout, patchWidgetLayout, UpdateStatusBarHintsLayout } from "@graphite/messages";
|
||||
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import Separator from "@graphite/components/widgets/labels/Separator.svelte";
|
||||
import UserInputLabel from "@graphite/components/widgets/labels/UserInputLabel.svelte";
|
||||
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
|
||||
let hintData: HintData = [];
|
||||
|
||||
function inputKeysForPlatform(hint: HintInfo): LayoutKeysGroup[] {
|
||||
return operatingSystem() === "Mac" && hint.keyGroupsMac ? hint.keyGroupsMac : hint.keyGroups;
|
||||
}
|
||||
let statusBarHintsLayout = defaultWidgetLayout();
|
||||
|
||||
onMount(() => {
|
||||
editor.subscriptions.subscribeJsMessage(UpdateInputHints, (data) => {
|
||||
hintData = data.hintData;
|
||||
editor.subscriptions.subscribeJsMessage(UpdateStatusBarHintsLayout, (updateStatusBarHintsLayout) => {
|
||||
patchWidgetLayout(statusBarHintsLayout, updateStatusBarHintsLayout);
|
||||
statusBarHintsLayout = statusBarHintsLayout;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<LayoutRow class="status-bar">
|
||||
<LayoutRow class="hint-groups">
|
||||
{#each hintData as hintGroup, index (hintGroup)}
|
||||
{#if index !== 0}
|
||||
<Separator type="Section" />
|
||||
{/if}
|
||||
{#each hintGroup as hint (hint)}
|
||||
{#if hint.plus}
|
||||
<LayoutRow class="plus">+</LayoutRow>
|
||||
{/if}
|
||||
{#if hint.slash}
|
||||
<LayoutRow class="slash">/</LayoutRow>
|
||||
{/if}
|
||||
<UserInputLabel mouseMotion={hint.mouse} keysWithLabelsGroups={inputKeysForPlatform(hint)}>{hint.label}</UserInputLabel>
|
||||
{/each}
|
||||
{/each}
|
||||
</LayoutRow>
|
||||
<WidgetLayout class="hints" layout={statusBarHintsLayout} />
|
||||
</LayoutRow>
|
||||
|
||||
<style lang="scss" global>
|
||||
@@ -49,30 +29,29 @@
|
||||
width: 100%;
|
||||
flex: 0 0 auto;
|
||||
|
||||
.hint-groups {
|
||||
flex: 0 0 auto;
|
||||
max-width: 100%;
|
||||
margin: 0 -4px;
|
||||
.hints {
|
||||
overflow: hidden;
|
||||
--row-height: 24px;
|
||||
margin: 0 4px;
|
||||
max-width: calc(100% - 2 * 4px);
|
||||
|
||||
.separator.section {
|
||||
// Width of section separator (12px) minus the margin of the surrounding user input labels (8px)
|
||||
margin: 0 calc(12px - 8px);
|
||||
}
|
||||
|
||||
.plus,
|
||||
.slash {
|
||||
flex: 0 0 auto;
|
||||
.text-label,
|
||||
.shortcut-label {
|
||||
align-items: center;
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
|
||||
+ .text-label,
|
||||
+ .shortcut-label {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-input-label {
|
||||
margin: 0 8px;
|
||||
.text-label:not(.bold) + .shortcut-label {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
& + .user-input-label {
|
||||
margin-left: 0;
|
||||
}
|
||||
.text-label.bold {
|
||||
padding: 0 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from "svelte";
|
||||
|
||||
import { shortcutF11 } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||
import type { ActionShortcut } from "@graphite/messages";
|
||||
import type { FullscreenState } from "@graphite/state-providers/fullscreen";
|
||||
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
@@ -8,6 +10,8 @@
|
||||
|
||||
const fullscreen = getContext<FullscreenState>("fullscreen");
|
||||
|
||||
const f11Keys: ActionShortcut = shortcutF11();
|
||||
|
||||
async function handleClick() {
|
||||
if ($fullscreen.windowFullscreen) fullscreen.exitFullscreen();
|
||||
else fullscreen.enterFullscreen();
|
||||
@@ -19,7 +23,7 @@
|
||||
on:click={handleClick}
|
||||
tooltipLabel={$fullscreen.windowFullscreen ? "Exit Fullscreen" : "Enter Fullscreen"}
|
||||
tooltipDescription={$fullscreen.keyboardLockApiSupported ? "While fullscreen, keyboard shortcuts normally reserved by the browser become available." : ""}
|
||||
tooltipShortcut="F11"
|
||||
tooltipShortcut={f11Keys}
|
||||
>
|
||||
<IconLabel icon={$fullscreen.windowFullscreen ? "FullscreenExit" : "FullscreenEnter"} />
|
||||
</LayoutRow>
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user