mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-23 05:48:12 +08:00
Dialog redesign and content revamp (#1409)
* Revamp the content and design of dialogs * Add the Licenses dialog
This commit is contained in:
@@ -44,7 +44,7 @@
|
||||
createPanicManager(editor, dialog);
|
||||
createPersistenceManager(editor, portfolio);
|
||||
let dragManagerDestructor = createDragManager();
|
||||
let inputManagerDestructor = createInputManager(editor, dialog, portfolio, fullscreen);
|
||||
let inputManagerDestructor = createInputManager(editor, dialog, portfolio, document, fullscreen);
|
||||
|
||||
onMount(() => {
|
||||
// Initialize certain setup tasks required by the editor backend to be ready for the user now that the frontend is ready
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { getContext, onMount } from "svelte";
|
||||
import { githubUrl } from "@graphite/io-managers/panic";
|
||||
import { wipeDocuments } from "@graphite/io-managers/persistence";
|
||||
|
||||
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
|
||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.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 WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||
import type { DialogState } from "@graphite/state-providers/dialog";
|
||||
|
||||
@@ -20,24 +23,52 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- TODO: Use https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog for improved accessibility -->
|
||||
<FloatingMenu open={true} class="dialog-modal" type="Dialog" direction="Center" bind:this={self} data-dialog-modal>
|
||||
<LayoutRow>
|
||||
<LayoutCol class="icon-column">
|
||||
<!-- `$dialog.icon` class exists to provide special sizing in CSS to specific icons -->
|
||||
<IconLabel icon={$dialog.icon} class={$dialog.icon.toLowerCase()} />
|
||||
</LayoutCol>
|
||||
<LayoutCol class="main-column">
|
||||
{#if $dialog.widgets.layout.length > 0}
|
||||
<WidgetLayout layout={$dialog.widgets} class="details" />
|
||||
<LayoutRow class="header-area">
|
||||
<!-- `$dialog.icon` class exists to provide special sizing in CSS to specific icons -->
|
||||
<IconLabel icon={$dialog.icon} class={$dialog.icon.toLowerCase()} />
|
||||
<TextLabel>{$dialog.title}</TextLabel>
|
||||
</LayoutRow>
|
||||
<LayoutRow class="content">
|
||||
<LayoutCol class="column-1">
|
||||
{#if $dialog.column1.layout.length > 0}
|
||||
<WidgetLayout layout={$dialog.column1} class="details" />
|
||||
{/if}
|
||||
{#if ($dialog.crashDialogButtons?.length || NaN) > 0}
|
||||
<LayoutRow class="panic-buttons-row">
|
||||
{#each $dialog.crashDialogButtons || [] as button, index (index)}
|
||||
<TextButton action={() => button.callback?.()} {...button.props} />
|
||||
{/each}
|
||||
</LayoutRow>
|
||||
{#if $dialog.panicDetails}
|
||||
<div class="widget-layout details">
|
||||
<div class="widget-row"><TextLabel bold={true}>The editor crashed — sorry about that</TextLabel></div>
|
||||
<div class="widget-row"><TextLabel>Please report this by filing an issue on GitHub:</TextLabel></div>
|
||||
<div class="widget-row"><TextButton label="Report Bug" icon="Warning" noBackground={true} action={() => window.open(githubUrl($dialog.panicDetails), "_blank")} /></div>
|
||||
<div class="widget-row"><TextLabel multiline={true}>Reload the editor to continue. If this occurs<br />immediately on repeated reloads, clear storage:</TextLabel></div>
|
||||
<div class="widget-row">
|
||||
<TextButton
|
||||
label="Clear Saved Documents"
|
||||
icon="Trash"
|
||||
noBackground={true}
|
||||
action={async () => {
|
||||
await wipeDocuments();
|
||||
window.location.reload();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</LayoutCol>
|
||||
{#if $dialog.column2.layout.length > 0}
|
||||
<LayoutCol class="column-2">
|
||||
<WidgetLayout layout={$dialog.column2} class="details" />
|
||||
</LayoutCol>
|
||||
{/if}
|
||||
</LayoutRow>
|
||||
<LayoutRow class="footer-area">
|
||||
{#if $dialog.buttons.layout.length > 0}
|
||||
<WidgetLayout layout={$dialog.buttons} class="details" />
|
||||
{/if}
|
||||
{#if $dialog.panicDetails}
|
||||
<TextButton label="Copy Error Log" action={() => navigator.clipboard.writeText($dialog.panicDetails)} />
|
||||
<TextButton label="Reload" emphasized={true} action={() => window.location.reload()} />
|
||||
{/if}
|
||||
</LayoutRow>
|
||||
</FloatingMenu>
|
||||
|
||||
@@ -50,32 +81,45 @@
|
||||
|
||||
> .floating-menu-container > .floating-menu-content {
|
||||
pointer-events: auto;
|
||||
padding: 24px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.icon-column {
|
||||
margin-right: 24px;
|
||||
.header-area,
|
||||
.footer-area {
|
||||
background: var(--color-1-nearblack);
|
||||
}
|
||||
|
||||
.header-area,
|
||||
.footer-area,
|
||||
.content {
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
.header-area {
|
||||
border-radius: 4px 4px 0 0;
|
||||
|
||||
.icon-label {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
&.file,
|
||||
&.copy {
|
||||
width: 60px;
|
||||
|
||||
svg {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin: 0 -10px;
|
||||
}
|
||||
}
|
||||
.text-label {
|
||||
margin-left: 12px;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.main-column {
|
||||
.content {
|
||||
margin: -4px 0;
|
||||
|
||||
.column-1 + .column-2 {
|
||||
margin-left: 48px;
|
||||
|
||||
.text-button {
|
||||
justify-content: left;
|
||||
}
|
||||
}
|
||||
|
||||
.details.text-label {
|
||||
-webkit-user-select: text; // Required as of Safari 15.0 (Graphite's minimum version) through the latest release
|
||||
user-select: text;
|
||||
@@ -84,13 +128,22 @@
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.radio-input button {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
// Used by the "Open Demo Artwork" dialog
|
||||
.image-label {
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.panic-buttons-row {
|
||||
height: 32px;
|
||||
align-items: center;
|
||||
.footer-area {
|
||||
border-radius: 0 0 4px 4px;
|
||||
justify-content: right;
|
||||
|
||||
.text-button {
|
||||
min-width: 96px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { afterUpdate, createEventDispatcher, tick } from "svelte";
|
||||
import { onMount, afterUpdate, createEventDispatcher, tick } from "svelte";
|
||||
|
||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||
|
||||
@@ -103,9 +103,42 @@
|
||||
wasOpen = isOpen;
|
||||
}
|
||||
|
||||
// Gets the client bounds of the elements and apply relevant styles to them
|
||||
// TODO: Use DOM attribute bindings more whilst not causing recursive updates
|
||||
onMount(() => {
|
||||
// Measure the content and round up its width and height to the nearest even integer.
|
||||
// This solves antialiasing issues when the content isn't cleanly divisible by 2 and gets translated by (-50%, -50%) causing all its content to be blurry.
|
||||
const floatingMenuContentDiv = floatingMenuContent?.div();
|
||||
if (type === "Dialog" && floatingMenuContentDiv) {
|
||||
// TODO: Also use https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver to detect any changes which may affect the size of the content.
|
||||
// TODO: The current method only notices when the dialog size increases but can't detect when it decreases.
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
entries.forEach((entry) => {
|
||||
let { width, height } = entry.contentRect;
|
||||
|
||||
width = Math.ceil(width);
|
||||
if (width % 2 === 1) width += 1;
|
||||
height = Math.ceil(height);
|
||||
if (height % 2 === 1) height += 1;
|
||||
|
||||
// We have to set the style properties directly because attempting to do it through a Svelte bound property results in `afterUpdate()` being triggered
|
||||
floatingMenuContentDiv.style.setProperty("min-width", width === 0 ? "unset" : `${width}px`);
|
||||
floatingMenuContentDiv.style.setProperty("min-height", height === 0 ? "unset" : `${height}px`);
|
||||
});
|
||||
});
|
||||
resizeObserver.observe(floatingMenuContentDiv);
|
||||
}
|
||||
});
|
||||
|
||||
afterUpdate(() => {
|
||||
// Remove the size constraint after the content updates so the resize observer can measure the content and reapply a newly calculated one
|
||||
const floatingMenuContentDiv = floatingMenuContent?.div();
|
||||
if (type === "Dialog" && floatingMenuContentDiv) {
|
||||
// We have to set the style properties directly because attempting to do it through a Svelte bound property results in `afterUpdate()` being triggered
|
||||
floatingMenuContentDiv.style.setProperty("min-width", "unset");
|
||||
floatingMenuContentDiv.style.setProperty("min-height", "unset");
|
||||
}
|
||||
|
||||
// Gets the client bounds of the elements and apply relevant styles to them
|
||||
// TODO: Use DOM attribute bindings more whilst not causing recursive updates
|
||||
// Turning measuring on and off both causes the component to change, which causes the `afterUpdate()` Svelte event to fire extraneous times (hurting performance and sometimes causing an infinite loop)
|
||||
if (!measuringOngoingGuard) positionAndStyleFloatingMenu();
|
||||
});
|
||||
|
||||
@@ -69,13 +69,6 @@
|
||||
editor.instance.updateLayout(layoutTarget, widgets[index].widgetId, value);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
// function exclude<T extends Record<string, any>>(props: T, additional?: (keyof T)[]): Pick<T, Exclude<keyof T, "kind" | (typeof additional extends Array<infer K> ? K : never)>> {
|
||||
// const exclusions = ["kind", ...(additional || [])];
|
||||
|
||||
// return Object.fromEntries(Object.entries(props).filter((entry) => !exclusions.includes(entry[0]))) as any;
|
||||
// }
|
||||
|
||||
// TODO: This seems to work, but verify the correctness and terseness of this, it's adapted from https://stackoverflow.com/a/67434028/775283
|
||||
function exclude<T extends object>(props: T, additional?: (keyof T)[]): Omit<T, typeof additional extends Array<infer K> ? K : never> {
|
||||
const exclusions = ["kind", ...(additional || [])];
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
export let label: string;
|
||||
export let icon: IconName | undefined = undefined;
|
||||
export let emphasized = false;
|
||||
export let noBackground = false;
|
||||
export let minWidth = 0;
|
||||
export let disabled = false;
|
||||
export let tooltip: string | undefined = undefined;
|
||||
@@ -21,6 +22,7 @@
|
||||
class="text-button"
|
||||
class:emphasized
|
||||
class:disabled
|
||||
class:no-background={noBackground}
|
||||
class:sharp-right-corners={sharpRightCorners}
|
||||
style:min-width={minWidth > 0 ? `${minWidth}px` : ""}
|
||||
title={tooltip}
|
||||
@@ -76,10 +78,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.text-button + .text-button {
|
||||
&.no-background {
|
||||
&:not(:hover) {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.icon-label {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.widget-row > & + .text-button,
|
||||
.layout-row > & + .text-button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.widget-column > & + .text-button,
|
||||
.layout-column > & + .text-button {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.icon-label {
|
||||
position: relative;
|
||||
left: -4px;
|
||||
|
||||
@@ -120,7 +120,7 @@
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<TextButton label="New Document" icon="File" action={() => editor.instance.newDocumentDialog()} />
|
||||
<TextButton label="New Document" icon="File" noBackground={true} action={() => editor.instance.newDocumentDialog()} />
|
||||
</td>
|
||||
<td>
|
||||
<UserInputLabel keysWithLabelsGroups={[[...platformModifiers(true), { key: "KeyN", label: "N" }]]} />
|
||||
@@ -128,7 +128,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<TextButton label="Open Document" icon="Folder" action={() => editor.instance.openDocument()} />
|
||||
<TextButton label="Open Document" icon="Folder" noBackground={true} action={() => editor.instance.openDocument()} />
|
||||
</td>
|
||||
<td>
|
||||
<UserInputLabel keysWithLabelsGroups={[[...platformModifiers(false), { key: "KeyO", label: "O" }]]} />
|
||||
@@ -136,7 +136,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<TextButton label="Open Demo Artwork" icon="Image" action={() => editor.instance.demoArtworkDialog()} />
|
||||
<TextButton label="Open Demo Artwork" icon="Image" noBackground={true} action={() => editor.instance.demoArtworkDialog()} />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -289,10 +289,6 @@
|
||||
td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.text-button:not(:hover) {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user