Skip to content

Commit

Permalink
Initial UI
Browse files Browse the repository at this point in the history
  • Loading branch information
personalizedrefrigerator committed Feb 22, 2024
1 parent a7b6f5b commit f29aeec
Show file tree
Hide file tree
Showing 16 changed files with 287 additions and 107 deletions.
2 changes: 1 addition & 1 deletion packages/js-draw/src/localizations/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const localization: EditorLocalization = {
filledRectanglePen: 'Ausgefülltes Rechteck',

lockRotation: 'Sperre Rotation',
paste: 'Einfügen',
copyButton__paste: 'Einfügen',

dropdownShown: (toolName) =>`Dropdown-Menü für ${toolName} angezeigt`,
dropdownHidden: (toolName) =>`Dropdown-Menü für ${toolName} versteckt`,
Expand Down
2 changes: 1 addition & 1 deletion packages/js-draw/src/localizations/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const localization: EditorLocalization = {
outlinedRectanglePen: 'Rectángulo delineado',
filledRectanglePen: 'Rectángulo sin borde',
lockRotation: 'Bloquea rotación',
paste: 'Pegar',
copyButton__paste: 'Pegar',
closeSidebar: (toolName) => `Close sidebar for ${toolName}`,
dropdownShown: (toolName) => `Menú por ${toolName} es visible`,
dropdownHidden: (toolName) => { return `Menú por ${toolName} fue ocultado`; },
Expand Down
2 changes: 1 addition & 1 deletion packages/js-draw/src/toolbar/AbstractToolbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import EraserTool from '../tools/Eraser';
import PenTool from '../tools/Pen';
import PenToolWidget from './widgets/PenToolWidget';
import EraserWidget from './widgets/EraserToolWidget';
import SelectionToolWidget from './widgets/SelectionToolWidget';
import SelectionToolWidget from './widgets/SelectionToolWidget/SelectionToolWidget';
import TextToolWidget from './widgets/TextToolWidget';
import HandToolWidget from './widgets/HandToolWidget';
import BaseWidget, { ToolbarWidgetTag } from './widgets/BaseWidget';
Expand Down
14 changes: 13 additions & 1 deletion packages/js-draw/src/toolbar/IconProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -784,7 +784,19 @@ export default class IconProvider {
`);
}

/** Unused. @deprecated */
public makeCheckIcon(): IconElemType {
return this.makeIconFromPath(`
M 14.8,55.25 43.9,86.2 85.12,13.8 71,14.5 43.8,72 27.3,55.2 Z
`);
}

public makeCopyIcon(): IconElemType {
return this.makeIconFromPath(`
M 45,10 45,55 90,55 90,10 45,10 z
M 10,25 10,90 70,90 70,60 40,60 40,25 10,25 z
`);
}

public makePasteIcon(): IconElemType {
const icon = this.makeIconFromPath(`
M 50 0 L 50 5 L 35 5 L 40 24.75 L 20 25 L 20 100 L 85 100 L 100 90 L 100 24 L 75.1 24.3 L 80 5 L 65 5 L 65 0 L 50 0 z
Expand Down
8 changes: 6 additions & 2 deletions packages/js-draw/src/toolbar/localization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ export interface ToolbarLocalization extends ToolbarUtilsLocalization {
resetView: string;
reformatSelection: string;
selectionToolKeyboardShortcuts: string;
paste: string;
copyButton__paste: string;
copyButton__copy: string;
copyButton__copied: string;
documentProperties: string;
backgroundColor: string;
imageWidthOption: string;
Expand Down Expand Up @@ -158,7 +160,9 @@ export const defaultToolbarLocalization: ToolbarLocalization = {
outlinedCirclePen: 'Outlined circle',
lockRotation: 'Lock rotation',

paste: 'Paste',
copyButton__paste: 'Paste',
copyButton__copy: 'Copy',
copyButton__copied: 'Copied',

errorImageHasZeroSize: 'Error: Image has zero size',
describeTheImage: 'Image description',
Expand Down
2 changes: 1 addition & 1 deletion packages/js-draw/src/toolbar/toolbar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
@use "./widgets/OverflowWidget.css";
@use "./widgets/PenToolWidget.scss";
@use "./widgets/HandToolWidget.scss";
@use "./widgets/SelectionToolWidget.scss";
@use "./widgets/SelectionToolWidget/SelectionToolWidget.scss";
@use "./widgets/DocumentPropertiesWidget.scss";
@use "./widgets/components/components.scss";

Expand Down
16 changes: 15 additions & 1 deletion packages/js-draw/src/toolbar/widgets/BaseWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ export default abstract class BaseWidget {
// Update title and icon
this.icon = null;
this.updateIcon();
this.label.innerText = this.getTitle();
this.updateTitle();

const longLabelCSSClass = 'long-label';
if (this.label.innerText.length > 7) {
Expand Down Expand Up @@ -420,8 +420,15 @@ export default abstract class BaseWidget {
this.container.remove();

this.#removeEditorListeners?.();
this.onRemove();
}

/**
* Called just before the widget is removed. Prefer overriding this to
* overriding the `.remove` method.
*/
protected onRemove() { }

public focus() {
this.button.focus();
}
Expand All @@ -437,7 +444,14 @@ export default abstract class BaseWidget {
this.container.classList.remove(className);
}

/** Causes the title shown for this widget to be refreshed. */
protected updateTitle() {
const newTitle = this.getTitle();
this.label.innerText = newTitle;
this.layoutManager?.onTitleChange?.(newTitle);
}

/** Causes the icon shown for this widget to be refreshed. */
protected updateIcon() {
let newIcon = this.createIcon();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import Editor from '../../../Editor';
import SelectionTool from '../../../tools/SelectionTool/SelectionTool';
import { EditorEventType } from '../../../types';
import { ToolbarLocalization } from '../../localization';
import ClipboardHandler from '../../../util/ClipboardHandler';
import BaseWidget from '../BaseWidget';
import { DispatcherEventListener } from '../../../EventDispatcher';

enum CopyPasteWidgetMode {
Copy,
Copied,
Paste,
}

export default class CopyPasteWidget extends BaseWidget {
#toolUpdateListener: DispatcherEventListener;
#pastePermissionStatus?: PermissionStatus;
#copyPermissionStatus?: PermissionStatus;

public constructor(
editor: Editor,
id: string,
private tool: SelectionTool,
private clipboardHandler: ClipboardHandler,
localizationTable?: ToolbarLocalization
) {
super(editor, id, localizationTable);

this.#toolUpdateListener = this.editor.notifier.on(EditorEventType.ToolUpdated, toolEvt => {
if (toolEvt.kind !== EditorEventType.ToolUpdated) {
throw new Error('Invalid event type!');
}

if (toolEvt.tool === this.tool) {
const selection = this.tool.getSelection();
const hasSelection = !!selection && selection.getSelectedItemCount() > 0;

this.#setMode(hasSelection ? CopyPasteWidgetMode.Copy : CopyPasteWidgetMode.Paste);
}
});
void this.#permissionsSetup();
}

async #permissionsSetup() {
this.#pastePermissionStatus = await navigator.permissions.query({
name: 'clipboard-read',
allowWithoutGesture: false,
} as any);
this.#copyPermissionStatus = await navigator.permissions.query({
name: 'clipboard-write',
allowWithoutGesture: false,
} as any);
this.#onPermissionStatusChanged();
this.#pastePermissionStatus.addEventListener('change', this.#onPermissionStatusChanged);
}

#updateDisabled() {
if (this.#mode === CopyPasteWidgetMode.Paste && this.#pastePermissionStatus?.state === 'denied') {
this.setDisabled(true);
} else if (this.#mode === CopyPasteWidgetMode.Copy && this.#copyPermissionStatus?.state === 'denied') {
this.setDisabled(true);
} else {
this.setDisabled(false);
}
}

#onPermissionStatusChanged = () => {
this.#updateDisabled();
};

#mode: CopyPasteWidgetMode;
#setMode(mode: CopyPasteWidgetMode) {
if (mode === this.#mode) return;

this.#mode = mode;

this.updateIcon();
this.updateTitle();
this.#updateDisabled();
}

protected override getTitle(): string {
if (this.#mode === CopyPasteWidgetMode.Copy) {
return this.localizationTable.copyButton__copy;
} else if (this.#mode === CopyPasteWidgetMode.Copied) {
return this.localizationTable.copyButton__copied;
} else {
return this.localizationTable.copyButton__paste;
}
}

protected override createIcon(): Element | null {
if (this.#mode === CopyPasteWidgetMode.Copy) {
return this.editor.icons.makeCopyIcon();
} else if (this.#mode === CopyPasteWidgetMode.Copied) {
return this.editor.icons.makeCheckIcon();
} else {
return this.editor.icons.makePasteIcon();
}
}

protected override handleClick(): void {
if (this.#mode === CopyPasteWidgetMode.Paste) {
this.clipboardHandler.paste();
} else if (this.#mode === CopyPasteWidgetMode.Copy) {
this.clipboardHandler.copy();

this.#setMode(CopyPasteWidgetMode.Copied);

setTimeout(() => {
if (this.#mode === CopyPasteWidgetMode.Copied) {
this.#setMode(CopyPasteWidgetMode.Paste);
}
}, 600);
}
}

public override onRemove(): void {
this.#toolUpdateListener.remove();
this.#copyPermissionStatus?.removeEventListener?.('change', this.#onPermissionStatusChanged);
this.#pastePermissionStatus?.removeEventListener?.('change', this.#onPermissionStatusChanged);
}
}
Original file line number Diff line number Diff line change
@@ -1,90 +1,18 @@
import { Color4 } from '@js-draw/math';
import { isRestylableComponent } from '../../components/RestylableComponent';
import Editor from '../../Editor';
import uniteCommands from '../../commands/uniteCommands';
import SelectionTool from '../../tools/SelectionTool/SelectionTool';
import { EditorEventType } from '../../types';
import { KeyPressEvent } from '../../inputEvents';
import { ToolbarLocalization } from '../localization';
import makeColorInput from './components/makeColorInput';
import ActionButtonWidget from './ActionButtonWidget';
import BaseToolWidget from './BaseToolWidget';
import { resizeImageToSelectionKeyboardShortcut } from './keybindings';
import makeSeparator from './components/makeSeparator';
import { toolbarCSSPrefix } from '../constants';
import HelpDisplay from '../utils/HelpDisplay';
import ClipboardHandler from '../../util/ClipboardHandler';
import BaseWidget from './BaseWidget';

const makeFormatMenu = (
editor: Editor,
selectionTool: SelectionTool,
localizationTable: ToolbarLocalization,
) => {
const container = document.createElement('div');
container.classList.add(
'selection-format-menu', `${toolbarCSSPrefix}spacedList`, `${toolbarCSSPrefix}indentedList`
);

const colorRow = document.createElement('div');
const colorLabel = document.createElement('label');
const colorInputControl = makeColorInput(editor, color => {
const selection = selectionTool.getSelection();

if (selection) {
const updateStyleCommands = [];

for (const elem of selection.getSelectedObjects()) {
if (isRestylableComponent(elem)) {
updateStyleCommands.push(elem.updateStyle({ color }));
}
}

const unitedCommand = uniteCommands(updateStyleCommands);
editor.dispatch(unitedCommand);
}
});
const { input: colorInput, container: colorInputContainer } = colorInputControl;

colorLabel.innerText = localizationTable.colorLabel;

const update = () => {
const selection = selectionTool.getSelection();
if (selection && selection.getSelectedItemCount() > 0) {
colorInput.disabled = false;
container.classList.remove('disabled');

const colors = [];
for (const elem of selection.getSelectedObjects()) {
if (isRestylableComponent(elem)) {
const color = elem.getStyle().color;
if (color) {
colors.push(color);
}
}
}
colorInputControl.setValue(Color4.average(colors));
} else {
colorInput.disabled = true;
container.classList.add('disabled');
colorInputControl.setValue(Color4.transparent);
}
};

colorRow.replaceChildren(colorLabel, colorInputContainer);
container.replaceChildren(colorRow);

return {
addTo: (parent: HTMLElement) => {
parent.appendChild(container);
},
update,
registerHelpText: (helpDisplay: HelpDisplay) => {
helpDisplay.registerTextHelpForElement(colorRow, localizationTable.selectionDropdown__changeColorHelpText);
colorInputControl.registerWithHelpTextDisplay(helpDisplay);
},
};
};
import Editor from '../../../Editor';
import SelectionTool from '../../../tools/SelectionTool/SelectionTool';
import { EditorEventType } from '../../../types';
import { KeyPressEvent } from '../../../inputEvents';
import { ToolbarLocalization } from '../../localization';
import ActionButtonWidget from '../ActionButtonWidget';
import BaseToolWidget from '../BaseToolWidget';
import { resizeImageToSelectionKeyboardShortcut } from '../keybindings';
import makeSeparator from '../components/makeSeparator';
import { toolbarCSSPrefix } from '../../constants';
import HelpDisplay from '../../utils/HelpDisplay';
import ClipboardHandler from '../../../util/ClipboardHandler';
import BaseWidget from '../BaseWidget';
import makeFormatMenu from './makeFormatMenu';
import CopyPasteWidget from './CopyPasteWidget';

export default class SelectionToolWidget extends BaseToolWidget {
private updateFormatMenu: ()=>void = () => {};
Expand Down Expand Up @@ -135,14 +63,12 @@ export default class SelectionToolWidget extends BaseToolWidget {

const clipboardHandler = new ClipboardHandler(this.editor);
if (clipboardHandler.canCopyPasteWithoutEvent()) {
copyPasteWidget = new ActionButtonWidget(
editor, 'copy-paste-btn',
() => editor.icons.makePasteIcon(),
this.localizationTable.paste,
() => {
clipboardHandler.paste();
},
localization,
copyPasteWidget = new CopyPasteWidget(
editor,
'copy-paste-widget',
this.tool,
clipboardHandler,
this.localizationTable,
);
}

Expand All @@ -158,7 +84,6 @@ export default class SelectionToolWidget extends BaseToolWidget {
resizeButton.setDisabled(missingSelection);
deleteButton.setDisabled(missingSelection);
duplicateButton.setDisabled(missingSelection);
copyPasteWidget?.setDisabled(!missingSelection);
};
updateDisabled(true);

Expand Down Expand Up @@ -235,4 +160,4 @@ export default class SelectionToolWidget extends BaseToolWidget {

return true;
}
}
}
Loading

0 comments on commit f29aeec

Please sign in to comment.