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}::`, "}}"); wrap(`{{c${highestCloze}::`, "}}");
} }
const iconButton = dynamicComponent(IconButton); const iconButton = dynamicComponent<typeof IconButton, IconButtonProps>(IconButton);
export const clozeButton = iconButton<IconButtonProps, "tooltip">(
{ export function getClozeButton() {
return iconButton({
id: "cloze", id: "cloze",
icon: bracketsIcon, icon: bracketsIcon,
onClick: onCloze, onClick: onCloze,
}, tooltip: tr.editingClozeDeletionCtrlandshiftandc(),
{ });
tooltip: tr.editingClozeDeletionCtrlandshiftandc, }
}
);

View File

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

View File

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

View File

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

View File

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

View File

@ -18,7 +18,7 @@ import micIcon from "./mic.svg";
import functionIcon from "./function-variant.svg"; import functionIcon from "./function-variant.svg";
import xmlIcon from "./xml.svg"; import xmlIcon from "./xml.svg";
import { clozeButton } from "./cloze"; import { getClozeButton } from "./cloze";
function onAttachment(): void { function onAttachment(): void {
bridgeCommand("attach"); bridgeCommand("attach");
@ -32,105 +32,89 @@ function onHtmlEdit(): void {
bridgeCommand("htmlEdit"); 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 mathjaxMenuId = "mathjaxMenu";
const mathjaxMenu = dropdownMenu<DropdownMenuProps>( const iconButton = dynamicComponent<typeof IconButton, IconButtonProps>(IconButton);
{ const withDropdownMenu = dynamicComponent<
id: mathjaxMenuId, typeof WithDropdownMenu,
menuItems: [ WithDropdownMenuProps
dropdownItem<DropdownItemProps, "label">( >(WithDropdownMenu);
{ const dropdownMenu = dynamicComponent<typeof DropdownMenu, DropdownMenuProps>(
// @ts-expect-error DropdownMenu
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 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, button: mathjaxButton,
menuId: mathjaxMenuId, menuId: mathjaxMenuId,
}, });
{}
);
const htmlButton = iconButton<IconButtonProps, "tooltip">( const htmlButton = iconButton({
{
icon: xmlIcon, icon: xmlIcon,
onClick: onHtmlEdit, onClick: onHtmlEdit,
},
{
tooltip: tr.editingHtmlEditor, tooltip: tr.editingHtmlEditor,
} });
);
const buttonGroup = dynamicComponent(ButtonGroup); return buttonGroup({
export const templateGroup = buttonGroup<ButtonGroupProps>(
{
id: "template", id: "template",
buttons: [ buttons: [
attachmentButton, attachmentButton,
recordButton, recordButton,
clozeButton, getClozeButton(),
mathjaxButtonWithMenu, mathjaxButtonWithMenu,
htmlButton, 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; component: T;
} }
export const dynamicComponent = <Comp extends typeof SvelteComponentDev>( export const dynamicComponent = <
component: Comp Comp extends typeof SvelteComponentDev,
) => < DefaultProps = NonNullable<ConstructorParameters<Comp>[0]["props"]>
Props extends NonNullable<ConstructorParameters<Comp>[0]["props"]>,
Lazy extends string = never
>( >(
props: Omit<Props, Lazy>, component: Comp
lazyProps: { ) => <Props = DefaultProps>(props: Props): DynamicSvelteComponent<Comp> & Props => {
[Property in keyof Pick<Props, Lazy>]: () => Pick<Props, Lazy>[Property]; return { component, ...props };
}
): 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;
}; };