Switch from lazy loading of properties to wrapping the buttons in a function

This commit is contained in:
Henrik Giesel 2021-04-09 18:42:41 +02:00
parent d6ad5084f1
commit b364ae5542
7 changed files with 200 additions and 292 deletions

View File

@ -33,14 +33,13 @@ function onCloze(event: MouseEvent): void {
wrap(`{{c${highestCloze}::`, "}}");
}
const iconButton = dynamicComponent(IconButton);
export const clozeButton = iconButton<IconButtonProps, "tooltip">(
{
const iconButton = dynamicComponent<typeof IconButton, IconButtonProps>(IconButton);
export function getClozeButton() {
return iconButton({
id: "cloze",
icon: bracketsIcon,
onClick: onCloze,
},
{
tooltip: tr.editingClozeDeletionCtrlandshiftandc,
tooltip: tr.editingClozeDeletionCtrlandshiftandc(),
});
}
);

View File

@ -25,35 +25,27 @@ function wrapWithForecolor(color: string): void {
document.execCommand("forecolor", false, color);
}
const iconButton = dynamicComponent(IconButton);
const forecolorButton = iconButton<IconButtonProps, "tooltip">(
{
const iconButton = dynamicComponent<typeof IconButton, IconButtonProps>(IconButton);
const colorPicker = dynamicComponent<typeof ColorPicker, ColorPickerProps>(ColorPicker);
const buttonGroup = dynamicComponent<typeof ButtonGroup, ButtonGroupProps>(ButtonGroup);
export function getColorGroup() {
const forecolorButton = iconButton({
icon: squareFillIcon,
className: "forecolor",
onClick: () => wrapWithForecolor(getForecolor()),
},
{
tooltip: tr.editingSetForegroundColourF7,
}
);
tooltip: tr.editingSetForegroundColourF7(),
});
const colorPicker = dynamicComponent(ColorPicker);
const colorpickerButton = colorPicker<ColorPickerProps, "tooltip">(
{
const colorpickerButton = colorPicker({
className: "rainbow",
onChange: ({ currentTarget }) =>
setForegroundColor((currentTarget as HTMLInputElement).value),
},
{
tooltip: tr.editingChangeColourF8,
}
);
tooltip: tr.editingChangeColourF8(),
});
const buttonGroup = dynamicComponent(ButtonGroup);
export const colorGroup = buttonGroup<ButtonGroupProps>(
{
return buttonGroup({
id: "color",
buttons: [forecolorButton, colorpickerButton],
},
{}
);
});
}

View File

@ -13,72 +13,51 @@ import superscriptIcon from "./format-superscript.svg";
import subscriptIcon from "./format-subscript.svg";
import eraserIcon from "./eraser.svg";
const commandIconButton = dynamicComponent(CommandIconButton);
const commandIconButton = dynamicComponent<
typeof CommandIconButton,
CommandIconButtonProps
>(CommandIconButton);
const buttonGroup = dynamicComponent<typeof ButtonGroup, ButtonGroupProps>(ButtonGroup);
const boldButton = commandIconButton<CommandIconButtonProps, "tooltip">(
{
export function getFormatGroup() {
const boldButton = commandIconButton({
icon: boldIcon,
command: "bold",
},
{
tooltip: tr.editingBoldTextCtrlandb,
}
);
tooltip: tr.editingBoldTextCtrlandb(),
});
const italicButton = commandIconButton<CommandIconButtonProps, "tooltip">(
{
const italicButton = commandIconButton({
icon: italicIcon,
command: "italic",
},
{
tooltip: tr.editingItalicTextCtrlandi,
}
);
tooltip: tr.editingItalicTextCtrlandi(),
});
const underlineButton = commandIconButton<CommandIconButtonProps, "tooltip">(
{
const underlineButton = commandIconButton({
icon: underlineIcon,
command: "underline",
},
{
tooltip: tr.editingUnderlineTextCtrlandu,
}
);
tooltip: tr.editingUnderlineTextCtrlandu(),
});
const superscriptButton = commandIconButton<CommandIconButtonProps, "tooltip">(
{
const superscriptButton = commandIconButton({
icon: superscriptIcon,
command: "superscript",
},
{
tooltip: tr.editingSuperscriptCtrlandand,
}
);
tooltip: tr.editingSuperscriptCtrlandand(),
});
const subscriptButton = commandIconButton<CommandIconButtonProps, "tooltip">(
{
const subscriptButton = commandIconButton({
icon: subscriptIcon,
command: "subscript",
},
{
tooltip: tr.editingSubscriptCtrland,
}
);
tooltip: tr.editingSubscriptCtrland(),
});
const removeFormatButton = commandIconButton<CommandIconButtonProps, "tooltip">(
{
const removeFormatButton = commandIconButton({
icon: eraserIcon,
command: "removeFormat",
activatable: false,
},
{
tooltip: tr.editingRemoveFormattingCtrlandr,
}
);
tooltip: tr.editingRemoveFormattingCtrlandr(),
});
const buttonGroup = dynamicComponent(ButtonGroup);
export const formatGroup = buttonGroup<ButtonGroupProps>(
{
return buttonGroup({
id: "format",
buttons: [
boldButton,
@ -88,6 +67,5 @@ export const formatGroup = buttonGroup<ButtonGroupProps>(
subscriptButton,
removeFormatButton,
],
},
{}
);
});
}

View File

@ -5,17 +5,17 @@ import ButtonGroup from "./ButtonGroup.svelte";
import type { ButtonGroupProps } from "./ButtonGroup";
import { dynamicComponent } from "sveltelib/dynamicComponent";
import { writable } from "svelte/store";
import { Writable, writable } from "svelte/store";
import EditorToolbarSvelte from "./EditorToolbar.svelte";
import { checkNightMode } from "anki/nightmode";
import { setupI18n, ModuleName } from "anki/i18n";
import { notetypeGroup } from "./notetype";
import { formatGroup } from "./format";
import { colorGroup } from "./color";
import { templateGroup, templateMenus } from "./template";
import { getNotetypeGroup } from "./notetype";
import { getFormatGroup } from "./format";
import { getColorGroup } from "./color";
import { getTemplateGroup, getTemplateMenus } from "./template";
import { Identifiable, search, add, insert } from "./identifiable";
@ -35,17 +35,26 @@ function toggleComponent(component: Hideable) {
component.hidden = !component.hidden;
}
const defaultButtons = [notetypeGroup, formatGroup, colorGroup, templateGroup];
const defaultMenus = [...templateMenus];
const buttonGroup = dynamicComponent<typeof ButtonGroup, ButtonGroupProps>(ButtonGroup);
class EditorToolbar extends HTMLElement {
component?: SvelteComponent;
buttons = writable(defaultButtons);
menus = writable(defaultMenus);
buttons?: Writable<
(DynamicSvelteComponent<typeof ButtonGroup> & ButtonGroupProps)[]
>;
menus?: Writable<DynamicSvelteComponent[]>;
connectedCallback(): void {
setupI18n({ modules: [ModuleName.EDITING] }).then(() => {
this.buttons = writable([
getNotetypeGroup(),
getFormatGroup(),
getColorGroup(),
getTemplateGroup(),
]);
this.menus = writable([...getTemplateMenus()]);
this.component = new EditorToolbarSvelte({
target: this,
props: {
@ -58,14 +67,12 @@ class EditorToolbar extends HTMLElement {
}
updateButtonGroup<T>(
update: (component: DynamicSvelteComponent<typeof ButtonGroup> & T) => void,
update: (
component: DynamicSvelteComponent<typeof ButtonGroup> & ButtonGroupProps & T
) => void,
group: string | number
): void {
this.buttons.update(
(
buttonGroups: (DynamicSvelteComponent<typeof ButtonGroup> &
ButtonGroupProps)[]
) => {
this.buttons?.update((buttonGroups) => {
const foundGroup = search(buttonGroups, group);
if (foundGroup) {
@ -77,8 +84,7 @@ class EditorToolbar extends HTMLElement {
}
return buttonGroups;
}
);
});
}
showButtonGroup(group: string | number): void {
@ -94,29 +100,17 @@ class EditorToolbar extends HTMLElement {
}
insertButtonGroup(newGroup: ButtonGroupProps, group: string | number = 0) {
const buttonGroup = dynamicComponent(ButtonGroup);
this.buttons.update(
(
buttonGroups: (DynamicSvelteComponent<typeof ButtonGroup> &
ButtonGroupProps)[]
) => {
const newButtonGroup = buttonGroup<ButtonGroupProps>(newGroup, {});
this.buttons?.update((buttonGroups) => {
const newButtonGroup = buttonGroup(newGroup);
return insert(buttonGroups, newButtonGroup, group);
}
);
});
}
addButtonGroup(newGroup: ButtonGroupProps, group: string | number = -1) {
const buttonGroup = dynamicComponent(ButtonGroup);
this.buttons.update(
(
buttonGroups: (DynamicSvelteComponent<typeof ButtonGroup> &
ButtonGroupProps)[]
) => {
const newButtonGroup = buttonGroup<ButtonGroupProps>(newGroup, {});
this.buttons?.update((buttonGroups) => {
const newButtonGroup = buttonGroup(newGroup);
return add(buttonGroups, newButtonGroup, group);
}
);
});
}
updateButton<T>(
@ -124,11 +118,7 @@ class EditorToolbar extends HTMLElement {
group: string | number,
button: string | number
): void {
this.updateButtonGroup(
(
foundGroup: DynamicSvelteComponent<typeof ButtonGroup> &
ButtonGroupProps
) => {
this.updateButtonGroup((foundGroup) => {
const foundButton = search(
foundGroup.buttons as (DynamicSvelteComponent & Identifiable)[],
button
@ -137,9 +127,7 @@ class EditorToolbar extends HTMLElement {
if (foundButton) {
update(foundButton as DynamicSvelteComponent & T);
}
},
group
);
}, group);
}
showButton(group: string | number, button: string | number): void {
@ -159,18 +147,13 @@ class EditorToolbar extends HTMLElement {
group: string | number,
button: string | number = 0
) {
this.updateButtonGroup(
(
component: DynamicSvelteComponent<typeof ButtonGroup> & ButtonGroupProps
) => {
this.updateButtonGroup((component) => {
component.buttons = insert(
component.buttons as (DynamicSvelteComponent & Identifiable)[],
newButton,
button
);
},
group
);
}, group);
}
addButton(
@ -178,18 +161,13 @@ class EditorToolbar extends HTMLElement {
group: string | number,
button: string | number = -1
) {
this.updateButtonGroup(
(
component: DynamicSvelteComponent<typeof ButtonGroup> & ButtonGroupProps
) => {
this.updateButtonGroup((component) => {
component.buttons = add(
component.buttons as (DynamicSvelteComponent & Identifiable)[],
newButton,
button
);
},
group
);
}, group);
}
}

View File

@ -7,34 +7,26 @@ import { dynamicComponent } from "sveltelib/dynamicComponent";
import { bridgeCommand } from "anki/bridgecommand";
import * as tr from "anki/i18n";
const labelButton = dynamicComponent(LabelButton);
const fieldsButton = labelButton<LabelButtonProps, "label" | "tooltip">(
{
const labelButton = dynamicComponent<typeof LabelButton, LabelButtonProps>(LabelButton);
const buttonGroup = dynamicComponent<typeof ButtonGroup, ButtonGroupProps>(ButtonGroup);
export function getNotetypeGroup() {
const fieldsButton = labelButton({
onClick: () => bridgeCommand("fields"),
disables: false,
},
{
label: () => `${tr.editingFields()}...`,
tooltip: tr.editingCustomizeFields,
}
);
label: `${tr.editingFields()}...`,
tooltip: tr.editingCustomizeFields(),
});
const cardsButton = labelButton<LabelButtonProps, "label" | "tooltip">(
{
const cardsButton = labelButton({
onClick: () => bridgeCommand("cards"),
disables: false,
},
{
label: () => `${tr.editingCards()}...`,
tooltip: tr.editingCustomizeCardTemplatesCtrlandl,
}
);
label: `${tr.editingCards()}...`,
tooltip: tr.editingCustomizeCardTemplatesCtrlandl(),
});
const buttonGroup = dynamicComponent(ButtonGroup);
export const notetypeGroup = buttonGroup<ButtonGroupProps>(
{
return buttonGroup({
id: "notetype",
buttons: [fieldsButton, cardsButton],
},
{}
);
});
}

View File

@ -18,7 +18,7 @@ import micIcon from "./mic.svg";
import functionIcon from "./function-variant.svg";
import xmlIcon from "./xml.svg";
import { clozeButton } from "./cloze";
import { getClozeButton } from "./cloze";
function onAttachment(): void {
bridgeCommand("attach");
@ -32,105 +32,89 @@ function onHtmlEdit(): void {
bridgeCommand("htmlEdit");
}
const iconButton = dynamicComponent(IconButton);
const withDropdownMenu = dynamicComponent(WithDropdownMenu);
const dropdownMenu = dynamicComponent(DropdownMenu);
const dropdownItem = dynamicComponent(DropdownItem);
const attachmentButton = iconButton<IconButtonProps, "tooltip">(
{
icon: paperclipIcon,
onClick: onAttachment,
},
{
tooltip: tr.editingAttachPicturesaudiovideoF3,
}
);
const recordButton = iconButton(
{ icon: micIcon, onClick: onRecord },
{
tooltip: tr.editingRecordAudioF5,
}
);
const mathjaxButton = iconButton<Omit<IconButtonProps, "onClick" | "tooltip">>(
{
icon: functionIcon,
},
{}
);
const mathjaxMenuId = "mathjaxMenu";
const mathjaxMenu = dropdownMenu<DropdownMenuProps>(
{
id: mathjaxMenuId,
menuItems: [
dropdownItem<DropdownItemProps, "label">(
{
// @ts-expect-error
onClick: () => wrap("\\(", "\\)"),
tooltip: "test",
endLabel: "test",
},
{ label: tr.editingMathjaxInline }
),
dropdownItem<DropdownItemProps, "label">(
{
// @ts-expect-error
onClick: () => wrap("\\[", "\\]"),
tooltip: "test",
endLabel: "test",
},
{ label: tr.editingMathjaxBlock }
),
dropdownItem<DropdownItemProps, "label">(
{
// @ts-expect-error
onClick: () => wrap("\\(\\ce{", "}\\)"),
tooltip: "test",
endLabel: "test",
},
{ label: tr.editingMathjaxChemistry }
),
],
},
{}
const iconButton = dynamicComponent<typeof IconButton, IconButtonProps>(IconButton);
const withDropdownMenu = dynamicComponent<
typeof WithDropdownMenu,
WithDropdownMenuProps
>(WithDropdownMenu);
const dropdownMenu = dynamicComponent<typeof DropdownMenu, DropdownMenuProps>(
DropdownMenu
);
const dropdownItem = dynamicComponent<typeof DropdownItem, DropdownItemProps>(
DropdownItem
);
const buttonGroup = dynamicComponent<typeof ButtonGroup, ButtonGroupProps>(ButtonGroup);
const mathjaxButtonWithMenu = withDropdownMenu<WithDropdownMenuProps>(
{
export function getTemplateGroup() {
const attachmentButton = iconButton({
icon: paperclipIcon,
onClick: onAttachment,
tooltip: tr.editingAttachPicturesaudiovideoF3(),
});
const recordButton = iconButton({
icon: micIcon,
onClick: onRecord,
tooltip: tr.editingRecordAudioF5(),
});
const mathjaxButton = iconButton({
icon: functionIcon,
foo: 5,
});
const mathjaxButtonWithMenu = withDropdownMenu({
button: mathjaxButton,
menuId: mathjaxMenuId,
},
{}
);
});
const htmlButton = iconButton<IconButtonProps, "tooltip">(
{
const htmlButton = iconButton({
icon: xmlIcon,
onClick: onHtmlEdit,
},
{
tooltip: tr.editingHtmlEditor,
}
);
});
const buttonGroup = dynamicComponent(ButtonGroup);
export const templateGroup = buttonGroup<ButtonGroupProps>(
{
return buttonGroup({
id: "template",
buttons: [
attachmentButton,
recordButton,
clozeButton,
getClozeButton(),
mathjaxButtonWithMenu,
htmlButton,
],
},
{}
);
});
}
export const templateMenus = [mathjaxMenu];
export function getTemplateMenus() {
const mathjaxMenu = dropdownMenu({
id: mathjaxMenuId,
menuItems: [
dropdownItem({
// @ts-expect-error
onClick: () => wrap("\\(", "\\)"),
tooltip: "test",
endLabel: "test",
label: tr.editingMathjaxInline(),
}),
dropdownItem({
// @ts-expect-error
onClick: () => wrap("\\[", "\\]"),
tooltip: "test",
endLabel: "test",
label: tr.editingMathjaxBlock(),
}),
dropdownItem({
// @ts-expect-error
onClick: () => wrap("\\(\\ce{", "}\\)"),
tooltip: "test",
endLabel: "test",
label: tr.editingMathjaxChemistry(),
}),
],
});
return [mathjaxMenu];
}

View File

@ -6,26 +6,11 @@ export interface DynamicSvelteComponent<
component: T;
}
export const dynamicComponent = <Comp extends typeof SvelteComponentDev>(
component: Comp
) => <
Props extends NonNullable<ConstructorParameters<Comp>[0]["props"]>,
Lazy extends string = never
export const dynamicComponent = <
Comp extends typeof SvelteComponentDev,
DefaultProps = NonNullable<ConstructorParameters<Comp>[0]["props"]>
>(
props: Omit<Props, Lazy>,
lazyProps: {
[Property in keyof Pick<Props, Lazy>]: () => Pick<Props, Lazy>[Property];
}
): DynamicSvelteComponent<Comp> & Props => {
const dynamicComponent = { component, ...props };
for (const property in lazyProps) {
const get = lazyProps[property];
const propertyDescriptor: TypedPropertyDescriptor<
Pick<Props, Lazy>[Extract<keyof Pick<Props, Lazy>, string>]
> = { get, enumerable: true };
Object.defineProperty(dynamicComponent, property, propertyDescriptor);
}
return dynamicComponent as DynamicSvelteComponent<Comp> & Props;
component: Comp
) => <Props = DefaultProps>(props: Props): DynamicSvelteComponent<Comp> & Props => {
return { component, ...props };
};