Improve tooltip docs with Markdown styling and refined math node explanations (#3488)

This commit is contained in:
Keavon Chambers
2025-12-20 01:05:15 -08:00
committed by GitHub
parent 2c21e1a90b
commit f1e8ebefc5
19 changed files with 276 additions and 185 deletions

View File

@@ -27,7 +27,7 @@
// State provider systems
let dialog = createDialogState(editor);
setContext("dialog", dialog);
let tooltip = createTooltipState();
let tooltip = createTooltipState(editor);
setContext("tooltip", tooltip);
let document = createDocumentState(editor);
setContext("document", document);

View File

@@ -1,9 +1,9 @@
<script lang="ts">
import { onDestroy, createEventDispatcher } from "svelte";
import { getContext, onDestroy, createEventDispatcher } from "svelte";
import type { HSV, RGB, FillChoice } from "@graphite/messages";
import type { MenuDirection } from "@graphite/messages";
import type { HSV, RGB, FillChoice, MenuDirection } from "@graphite/messages";
import { Color, contrastingOutlineFactor, Gradient } from "@graphite/messages";
import type { TooltipState } from "@graphite/state-providers/tooltip";
import { clamp } from "@graphite/utility-functions/math";
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
@@ -40,6 +40,7 @@
];
const dispatch = createEventDispatcher<{ colorOrGradient: FillChoice; startHistoryTransaction: undefined }>();
const tooltip = getContext<TooltipState>("tooltip");
export let colorOrGradient: FillChoice;
export let allowNone = false;
@@ -424,12 +425,16 @@
"--opaque-color-contrasting": (newColor.opaque() || new Color(0, 0, 0, 1)).contrastingColor(),
}}
>
{@const hueDescription = "The shade along the spectrum of the rainbow."}
{@const saturationDescription = "The vividness from grayscale to full color."}
{@const valueDescription = "The brightness from black to full color."}
<LayoutCol class="pickers-and-gradient">
<LayoutRow class="pickers">
<LayoutCol
class="saturation-value-picker"
data-tooltip-label="Saturation and Value"
data-tooltip-description={disabled ? "Disabled (read-only)." : ""}
data-tooltip-description={`To move only along the saturation (X) or value (Y) axis, perform the shortcut shown.${disabled ? "\n\nDisabled (read-only)." : ""}`}
data-tooltip-shortcut={$tooltip.shiftClickShortcut?.shortcut ? JSON.stringify($tooltip.shiftClickShortcut.shortcut) : undefined}
on:pointerdown={onPointerDown}
data-saturation-value-picker
>
@@ -449,7 +454,7 @@
<LayoutCol
class="hue-picker"
data-tooltip-label="Hue"
data-tooltip-description={`The shade along the spectrum of the rainbow.${disabled ? "\n\nDisabled (read-only)." : ""}`}
data-tooltip-description={`${hueDescription}${disabled ? "\n\nDisabled (read-only)." : ""}`}
on:pointerdown={onPointerDown}
data-hue-picker
>
@@ -522,10 +527,8 @@
</LayoutRow>
<!-- <DropdownInput entries={[[{ label: "sRGB" }]]} selectedIndex={0} disabled={true} tooltipDescription="Color model, color space, and HDR (coming soon)." /> -->
<LayoutRow>
<TextLabel
tooltipLabel="Hex Color Code"
tooltipDescription="Color code in hexadecimal format. 6 digits if opaque, 8 with alpha.\nAccepts input of CSS color values including named colors.">Hex</TextLabel
>
{@const hexDescription = "Color code in hexadecimal format. 6 digits if opaque, 8 with alpha. Accepts input of CSS color values including named colors."}
<TextLabel tooltipLabel="Hex Color Code" tooltipDescription={hexDescription}>Hex</TextLabel>
<Separator type="Related" />
<LayoutRow>
<TextInput
@@ -537,7 +540,7 @@
}}
centered={true}
tooltipLabel="Hex Color Code"
tooltipDescription="Color code in hexadecimal format. 6 digits if opaque, 8 with alpha.\nAccepts input of CSS color values including named colors."
tooltipDescription={hexDescription}
bind:this={hexCodeInputWidget}
/>
</LayoutRow>
@@ -601,16 +604,17 @@
v: "Value Component",
}[channel]}
tooltipDescription={{
h: "The shade along the spectrum of the rainbow.",
s: "The vividness from grayscale to full color.",
v: "The brightness from black to full color.",
h: hueDescription,
s: saturationDescription,
v: valueDescription,
}[channel]}
/>
{/each}
</LayoutRow>
</LayoutRow>
<LayoutRow>
<TextLabel tooltipLabel="Alpha" tooltipDescription="The level of translucency, from transparent (0%) to opaque (100%).">Alpha</TextLabel>
{@const alphaDescription = "The level of translucency, from transparent (0%) to opaque (100%)."}
<TextLabel tooltipLabel="Alpha" tooltipDescription={alphaDescription}>Alpha</TextLabel>
<Separator type="Related" />
<NumberInput
value={!isNone ? alpha * 100 : undefined}
@@ -630,7 +634,7 @@
mode="Range"
displayDecimalPlaces={1}
tooltipLabel="Alpha"
tooltipDescription="The level of translucency, from transparent (0%) to opaque (100%)."
tooltipDescription={alphaDescription}
/>
</LayoutRow>
<LayoutRow class="leftover-space" />
@@ -670,7 +674,7 @@
data-pure-tile={name.toLowerCase()}
style:--pure-color={color}
style:--pure-color-gray={gray}
data-tooltip-label="Set to Red"
data-tooltip-label={`Set to ${name}`}
data-tooltip-description={disabled ? "Disabled (read-only)." : ""}
/>
{/each}

View File

@@ -15,8 +15,8 @@
let self: FloatingMenu | undefined;
$: label = filterTodo($tooltip.element?.getAttribute("data-tooltip-label")?.trim());
$: description = filterTodo($tooltip.element?.getAttribute("data-tooltip-description")?.trim());
$: label = parseMarkdown(filterTodo($tooltip.element?.getAttribute("data-tooltip-label")?.trim()));
$: description = parseMarkdown(filterTodo($tooltip.element?.getAttribute("data-tooltip-description")?.trim()));
$: shortcutJSON = $tooltip.element?.getAttribute("data-tooltip-shortcut")?.trim();
$: shortcut = ((shortcutJSON) => {
if (!shortcutJSON) return undefined;
@@ -32,6 +32,26 @@
if (text?.trim().toUpperCase() === "TODO" && !editor.handle.inDevelopmentMode()) return "";
return text;
}
function parseMarkdown(markdown: string | undefined): string | undefined {
if (!markdown) return undefined;
return (
markdown
// .split("\n")
// .map((line) => line.trim())
// .join("\n")
// .split("\n\n")
// .map((paragraph) => paragraph.replaceAll("\n", " "))
// .join("\n\n")
// Bold
.replace(/\*\*((?:(?!\*\*).)+)\*\*/g, "<strong>$1</strong>")
// Italic
.replace(/\*([^*]+)\*/g, "<em>$1</em>")
// Backticks
.replace(/`([^`]+)`/g, "<code>$1</code>")
);
}
</script>
{#if label || description}
@@ -40,7 +60,7 @@
{#if label || shortcut}
<LayoutRow class="tooltip-header">
{#if label}
<TextLabel class="tooltip-label">{label}</TextLabel>
<TextLabel class="tooltip-label">{@html label}</TextLabel>
{/if}
{#if shortcut}
<ShortcutLabel shortcut={{ shortcut }} />
@@ -48,7 +68,7 @@
</LayoutRow>
{/if}
{#if description}
<TextLabel class="tooltip-description">{description}</TextLabel>
<TextLabel class="tooltip-description">{@html description}</TextLabel>
{/if}
</FloatingMenu>
</div>

View File

@@ -9,10 +9,10 @@
UpdateLayersPanelControlBarLeftLayout,
UpdateLayersPanelControlBarRightLayout,
UpdateLayersPanelBottomBarLayout,
SendShortcutAltClick,
} from "@graphite/messages";
import type { ActionShortcut, DataBuffer, LayerPanelEntry, Layout } from "@graphite/messages";
import type { DataBuffer, LayerPanelEntry, Layout } from "@graphite/messages";
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
import type { TooltipState } from "@graphite/state-providers/tooltip";
import { operatingSystem } from "@graphite/utility-functions/platform";
import { extractPixelData } from "@graphite/utility-functions/rasterization";
@@ -49,6 +49,7 @@
const editor = getContext<Editor>("editor");
const nodeGraph = getContext<NodeGraphState>("nodeGraph");
const tooltip = getContext<TooltipState>("tooltip");
let list: LayoutCol | undefined;
@@ -73,13 +74,7 @@
let layersPanelControlBarRightLayout: Layout = [];
let layersPanelBottomBarLayout: Layout = [];
let altClickShortcut: ActionShortcut | undefined;
onMount(() => {
editor.subscriptions.subscribeJsMessage(SendShortcutAltClick, async (data) => {
altClickShortcut = data.shortcut;
});
editor.subscriptions.subscribeJsMessage(UpdateLayersPanelControlBarLeftLayout, (updateLayersPanelControlBarLeftLayout) => {
patchLayout(layersPanelControlBarLeftLayout, updateLayersPanelControlBarLeftLayout);
layersPanelControlBarLeftLayout = layersPanelControlBarLeftLayout;
@@ -628,7 +623,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\nA selected layer is currently contained within.\n" : "")}
data-tooltip-shortcut={altClickShortcut?.shortcut ? JSON.stringify(altClickShortcut.shortcut) : undefined}
data-tooltip-shortcut={$tooltip.altClickShortcut?.shortcut ? JSON.stringify($tooltip.altClickShortcut.shortcut) : undefined}
on:click={(e) => handleExpandArrowClickWithModifiers(e, listing.entry.id)}
tabindex="0"
></button>
@@ -639,8 +634,9 @@
<IconLabel
icon="Clipped"
class="clipped-arrow"
tooltipDescription="Clipping mask is active. To release it, perform the shortcut on the layer border."
tooltipShortcut={altClickShortcut}
tooltipLabel="Layer Clipped"
tooltipDescription="Clipping mask is active. To release it, target the bottom border of the layer and perform the shortcut shown."
tooltipShortcut={$tooltip.altClickShortcut}
/>
{/if}
<div class="thumbnail">

View File

@@ -501,7 +501,7 @@
style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
style:--layer-area-width={layerAreaWidth}
style:--node-chain-area-left-extension={layerChainWidth !== 0 ? layerChainWidth + 0.5 : 0}
data-tooltip-label={node.displayName === node.reference ? node.displayName : `${node.displayName} (${node.reference})`}
data-tooltip-label={node.displayName === node.reference || !node.reference ? node.displayName : `${node.displayName} (${node.reference})`}
data-tooltip-description={`
${(description || "").trim()}${editor.handle.inDevelopmentMode() ? `\n\nID: ${node.id}. Position: (${node.position.x}, ${node.position.y}).` : ""}
`.trim()}
@@ -651,7 +651,7 @@
style:--clip-path-id={`url(#${clipPathId})`}
style:--data-color={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
data-tooltip-label={node.displayName === node.reference ? node.displayName : `${node.displayName} (${node.reference})`}
data-tooltip-label={node.displayName === node.reference || !node.reference ? node.displayName : `${node.displayName} (${node.reference})`}
data-tooltip-description={`
${(description || "").trim()}${editor.handle.inDevelopmentMode() ? `\n\nID: ${node.id}. Position: (${node.position.x}, ${node.position.y}).` : ""}
`.trim()}

View File

@@ -72,7 +72,8 @@
font-style: italic;
}
&.monospace {
&.monospace,
code {
font-family: "Source Code Pro", monospace;
font-size: 12px;
}
@@ -94,5 +95,10 @@
a {
color: inherit;
}
code {
background: var(--color-3-darkgray);
padding: 0 2px;
}
}
</style>

View File

@@ -1,24 +1,15 @@
<script lang="ts">
import { getContext, onMount } from "svelte";
import { getContext } from "svelte";
import type { Editor } from "@graphite/editor";
import type { ActionShortcut } from "@graphite/messages";
import { SendShortcutF11 } from "@graphite/messages";
import type { FullscreenState } from "@graphite/state-providers/fullscreen";
import type { TooltipState } from "@graphite/state-providers/tooltip";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
const fullscreen = getContext<FullscreenState>("fullscreen");
const editor = getContext<Editor>("editor");
let f11Shortcut: ActionShortcut | undefined = undefined;
onMount(() => {
editor.subscriptions.subscribeJsMessage(SendShortcutF11, async (data) => {
f11Shortcut = data.shortcut;
});
});
const tooltip = getContext<TooltipState>("tooltip");
async function handleClick() {
if ($fullscreen.windowFullscreen) fullscreen.exitFullscreen();
@@ -31,7 +22,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={f11Shortcut}
tooltipShortcut={$tooltip.f11Shortcut}
>
<IconLabel icon={$fullscreen.windowFullscreen ? "FullscreenExit" : "FullscreenEnter"} />
</LayoutRow>

View File

@@ -481,11 +481,11 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
The browser's clipboard permission has been denied.
Open the browser's website settings (usually accessible
just left of the URL) to allow this permission.
just left of the URL bar) to allow this permission.
`;
const nothing = stripIndents`
No valid clipboard data was found. You may have better
luck pasting with the standard keyboard shortcut instead.
success pasting with the standard keyboard shortcut instead.
`;
const matchMessage = {

View File

@@ -119,6 +119,11 @@ export class SendShortcutAltClick extends JsMessage {
readonly shortcut!: ActionShortcut | undefined;
}
export class SendShortcutShiftClick extends JsMessage {
@Transform(({ value }: { value: ActionShortcut }) => value || undefined)
readonly shortcut!: ActionShortcut | undefined;
}
export class UpdateNodeThumbnail extends JsMessage {
readonly id!: bigint;
@@ -1696,6 +1701,7 @@ export const messageMakers: Record<string, MessageMaker> = {
SendUIMetadata,
SendShortcutF11,
SendShortcutAltClick,
SendShortcutShiftClick,
TriggerAboutGraphiteLocalizedCommitDate,
TriggerDisplayThirdPartyLicensesDialog,
TriggerExportImage,

View File

@@ -9,7 +9,7 @@ export function createAppWindowState(editor: Editor) {
maximized: false,
fullscreen: false,
viewportHolePunch: false,
uiScale: 1.0,
uiScale: 1,
});
// Set up message subscriptions on creation

View File

@@ -1,12 +1,18 @@
import { writable } from "svelte/store";
import { type Editor } from "@graphite/editor";
import { SendShortcutAltClick, SendShortcutF11, SendShortcutShiftClick, type ActionShortcut } from "@graphite/messages";
const SHOW_TOOLTIP_DELAY_MS = 500;
export function createTooltipState() {
export function createTooltipState(editor: Editor) {
const { subscribe, update } = writable({
visible: false,
element: undefined as Element | undefined,
position: { x: 0, y: 0 },
shiftClickShortcut: undefined as ActionShortcut | undefined,
altClickShortcut: undefined as ActionShortcut | undefined,
f11Shortcut: undefined as ActionShortcut | undefined,
});
let tooltipTimeout: ReturnType<typeof setTimeout> | undefined = undefined;
@@ -45,6 +51,25 @@ export function createTooltipState() {
}, SHOW_TOOLTIP_DELAY_MS);
});
editor.subscriptions.subscribeJsMessage(SendShortcutShiftClick, async (data) => {
update((state) => {
state.shiftClickShortcut = data.shortcut;
return state;
});
});
editor.subscriptions.subscribeJsMessage(SendShortcutAltClick, async (data) => {
update((state) => {
state.altClickShortcut = data.shortcut;
return state;
});
});
editor.subscriptions.subscribeJsMessage(SendShortcutF11, async (data) => {
update((state) => {
state.f11Shortcut = data.shortcut;
return state;
});
});
document.addEventListener("mousedown", closeTooltip);
document.addEventListener("keydown", closeTooltip);