mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-23 21: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:
@@ -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>
|
||||
Reference in New Issue
Block a user