refactor: convert Badge, Checkbox and Navigation components to TS (#3532)

* chore: convert badge components to TypeScript

Signed-off-by: Sami Mazouz <ilyasmazouz@gmail.com>

* chore: convert checkbox components to TypeScript

Signed-off-by: Sami Mazouz <ilyasmazouz@gmail.com>

* chore: convert `Navigation` component to TypeScript

Signed-off-by: Sami Mazouz <ilyasmazouz@gmail.com>

* chore: import mithril type instead
This commit is contained in:
Sami Mazouz 2022-07-14 15:54:16 +01:00 committed by GitHub
parent 707ca2d16d
commit 7471ef64d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 84 additions and 64 deletions

View File

@ -5,6 +5,7 @@ import Application, { ApplicationData } from '../common/Application';
import Navigation from '../common/components/Navigation'; import Navigation from '../common/components/Navigation';
import AdminNav from './components/AdminNav'; import AdminNav from './components/AdminNav';
import ExtensionData from './utils/ExtensionData'; import ExtensionData from './utils/ExtensionData';
import IHistory from '../common/IHistory';
export type Extension = { export type Extension = {
id: string; id: string;
@ -47,13 +48,16 @@ export default class AdminApplication extends Application {
language: 10, language: 10,
}; };
history = { history: IHistory = {
canGoBack: () => true, canGoBack: () => true,
getPrevious: () => {}, getCurrent: () => null,
getPrevious: () => null,
push: () => {},
backUrl: () => this.forum.attribute<string>('baseUrl'), backUrl: () => this.forum.attribute<string>('baseUrl'),
back: function () { back: function () {
window.location.assign(this.backUrl()); window.location.assign(this.backUrl());
}, },
home: () => {},
}; };
/** /**

View File

@ -34,6 +34,7 @@ import type Component from './Component';
import type { ComponentAttrs } from './Component'; import type { ComponentAttrs } from './Component';
import Model, { SavedModelData } from './Model'; import Model, { SavedModelData } from './Model';
import fireApplicationError from './helpers/fireApplicationError'; import fireApplicationError from './helpers/fireApplicationError';
import IHistory from './IHistory';
export type FlarumScreens = 'phone' | 'tablet' | 'desktop' | 'desktop-hd'; export type FlarumScreens = 'phone' | 'tablet' | 'desktop' | 'desktop-hd';
@ -228,6 +229,9 @@ export default class Application {
*/ */
drawer!: Drawer; drawer!: Drawer;
history: IHistory | null = null;
pane: any = null;
data!: ApplicationData; data!: ApplicationData;
private _title: string = ''; private _title: string = '';

View File

@ -156,5 +156,5 @@ export default abstract class Component<Attrs extends ComponentAttrs = Component
* *
* This can be used to assign default values for missing, optional attrs. * This can be used to assign default values for missing, optional attrs.
*/ */
static initAttrs<T>(attrs: T): void {} static initAttrs(attrs: unknown): void {}
} }

View File

@ -0,0 +1,15 @@
export interface HistoryEntry {
name: string;
title: string;
url: string;
}
export default interface IHistory {
canGoBack(): boolean;
getCurrent(): HistoryEntry | null;
getPrevious(): HistoryEntry | null;
push(name: string, title: string, url: string): void;
back(): void;
backUrl(): string;
home(): void;
}

View File

@ -1,8 +1,15 @@
import Tooltip from './Tooltip'; import Tooltip from './Tooltip';
import Component from '../Component'; import Component, { ComponentAttrs } from '../Component';
import icon from '../helpers/icon'; import icon from '../helpers/icon';
import classList from '../utils/classList'; import classList from '../utils/classList';
export interface IBadgeAttrs extends ComponentAttrs {
icon: string;
type?: string;
label?: string;
color?: string;
}
/** /**
* The `Badge` component represents a user/discussion badge, indicating some * The `Badge` component represents a user/discussion badge, indicating some
* status (e.g. a discussion is stickied, a user is an admin). * status (e.g. a discussion is stickied, a user is an admin).
@ -16,7 +23,7 @@ import classList from '../utils/classList';
* *
* All other attrs will be assigned as attributes on the badge element. * All other attrs will be assigned as attributes on the badge element.
*/ */
export default class Badge extends Component { export default class Badge<CustomAttrs extends IBadgeAttrs = IBadgeAttrs> extends Component<CustomAttrs> {
view() { view() {
const { type, icon: iconName, label, color, style = {}, ...attrs } = this.attrs; const { type, icon: iconName, label, color, style = {}, ...attrs } = this.attrs;

View File

@ -1,8 +1,16 @@
import Component from '../Component'; import Component, { ComponentAttrs } from '../Component';
import LoadingIndicator from './LoadingIndicator'; import LoadingIndicator from './LoadingIndicator';
import icon from '../helpers/icon'; import icon from '../helpers/icon';
import classList from '../utils/classList'; import classList from '../utils/classList';
import withAttr from '../utils/withAttr'; import withAttr from '../utils/withAttr';
import type Mithril from 'mithril';
export interface ICheckboxAttrs extends ComponentAttrs {
state?: boolean;
loading?: boolean;
disabled?: boolean;
onchange: (checked: boolean, component: Checkbox<this>) => void;
}
/** /**
* The `Checkbox` component defines a checkbox input. * The `Checkbox` component defines a checkbox input.
@ -16,12 +24,8 @@ import withAttr from '../utils/withAttr';
* - `onchange` A callback to run when the checkbox is checked/unchecked. * - `onchange` A callback to run when the checkbox is checked/unchecked.
* - `children` A text label to display next to the checkbox. * - `children` A text label to display next to the checkbox.
*/ */
export default class Checkbox extends Component { export default class Checkbox<CustomAttrs extends ICheckboxAttrs = ICheckboxAttrs> extends Component<CustomAttrs> {
view(vnode) { view(vnode: Mithril.Vnode<CustomAttrs, this>) {
// Sometimes, false is stored in the DB as '0'. This is a temporary
// conversion layer until a more robust settings encoding is introduced
if (this.attrs.state === '0') this.attrs.state = false;
const className = classList([ const className = classList([
'Checkbox', 'Checkbox',
this.attrs.state ? 'on' : 'off', this.attrs.state ? 'on' : 'off',
@ -43,21 +47,15 @@ export default class Checkbox extends Component {
/** /**
* Get the template for the checkbox's display (tick/cross icon). * Get the template for the checkbox's display (tick/cross icon).
*
* @return {import('mithril').Children}
* @protected
*/ */
getDisplay() { protected getDisplay(): Mithril.Children {
return this.attrs.loading ? <LoadingIndicator display="unset" size="small" /> : icon(this.attrs.state ? 'fas fa-check' : 'fas fa-times'); return this.attrs.loading ? <LoadingIndicator display="unset" size="small" /> : icon(this.attrs.state ? 'fas fa-check' : 'fas fa-times');
} }
/** /**
* Run a callback when the state of the checkbox is changed. * Run a callback when the state of the checkbox is changed.
*
* @param {boolean} checked
* @protected
*/ */
onchange(checked) { protected onchange(checked: boolean): void {
if (this.attrs.onchange) this.attrs.onchange(checked, this); if (this.attrs.onchange) this.attrs.onchange(checked, this);
} }
} }

View File

@ -1,16 +0,0 @@
import Badge from './Badge';
export default class GroupBadge extends Badge {
static initAttrs(attrs) {
super.initAttrs(attrs);
if (attrs.group) {
attrs.icon = attrs.group.icon();
attrs.color = attrs.group.color();
attrs.label = typeof attrs.label === 'undefined' ? attrs.group.nameSingular() : attrs.label;
attrs.type = 'group--' + attrs.group.id();
delete attrs.group;
}
}
}

View File

@ -0,0 +1,21 @@
import Badge, { IBadgeAttrs } from './Badge';
import Group from '../models/Group';
export interface IGroupAttrs extends IBadgeAttrs {
group?: Group;
}
export default class GroupBadge<CustomAttrs extends IGroupAttrs = IGroupAttrs> extends Badge<CustomAttrs> {
static initAttrs(attrs: IGroupAttrs): void {
super.initAttrs(attrs);
if (attrs.group) {
attrs.icon = attrs.group.icon() || '';
attrs.color = attrs.group.color() || '';
attrs.label = typeof attrs.label === 'undefined' ? attrs.group.nameSingular() : attrs.label;
attrs.type = 'group--' + attrs.group.id();
delete attrs.group;
}
}
}

View File

@ -2,6 +2,7 @@ import app from '../../common/app';
import Component from '../Component'; import Component from '../Component';
import Button from './Button'; import Button from './Button';
import LinkButton from './LinkButton'; import LinkButton from './LinkButton';
import type Mithril from 'mithril';
/** /**
* The `Navigation` component displays a set of navigation buttons. Typically * The `Navigation` component displays a set of navigation buttons. Typically
@ -28,41 +29,35 @@ export default class Navigation extends Component {
onmouseenter={pane && pane.show.bind(pane)} onmouseenter={pane && pane.show.bind(pane)}
onmouseleave={pane && pane.onmouseleave.bind(pane)} onmouseleave={pane && pane.onmouseleave.bind(pane)}
> >
{history.canGoBack() ? [this.getBackButton(), this.getPaneButton()] : this.getDrawerButton()} {history?.canGoBack() ? [this.getBackButton(), this.getPaneButton()] : this.getDrawerButton()}
</div> </div>
); );
} }
/** /**
* Get the back button. * Get the back button.
*
* @return {import('mithril').Children}
* @protected
*/ */
getBackButton() { protected getBackButton(): Mithril.Children {
const { history } = app; const { history } = app;
const previous = history.getPrevious() || {}; const previous = history?.getPrevious();
return LinkButton.component({ return LinkButton.component({
className: 'Button Navigation-back Button--icon', className: 'Button Navigation-back Button--icon',
href: history.backUrl(), href: history?.backUrl(),
icon: 'fas fa-chevron-left', icon: 'fas fa-chevron-left',
'aria-label': previous.title, 'aria-label': previous?.title,
onclick: (e) => { onclick: (e: MouseEvent) => {
if (e.shiftKey || e.ctrlKey || e.metaKey || e.which === 2) return; if (e.shiftKey || e.ctrlKey || e.metaKey || e.which === 2) return;
e.preventDefault(); e.preventDefault();
history.back(); history?.back();
}, },
}); });
} }
/** /**
* Get the pane pinned toggle button. * Get the pane pinned toggle button.
*
* @return {import('mithril').Children}
* @protected
*/ */
getPaneButton() { protected getPaneButton(): Mithril.Children {
const { pane } = app; const { pane } = app;
if (!pane || !pane.active) return ''; if (!pane || !pane.active) return '';
@ -76,11 +71,8 @@ export default class Navigation extends Component {
/** /**
* Get the drawer toggle button. * Get the drawer toggle button.
*
* @return {import('mithril').Children}
* @protected
*/ */
getDrawerButton() { protected getDrawerButton(): Mithril.Children {
if (!this.attrs.drawer) return ''; if (!this.attrs.drawer) return '';
const { drawer } = app; const { drawer } = app;
@ -88,7 +80,7 @@ export default class Navigation extends Component {
return Button.component({ return Button.component({
className: 'Button Button--icon Navigation-drawer' + (user && user.newNotificationCount() ? ' new' : ''), className: 'Button Button--icon Navigation-drawer' + (user && user.newNotificationCount() ? ' new' : ''),
onclick: (e) => { onclick: (e: MouseEvent) => {
e.stopPropagation(); e.stopPropagation();
drawer.show(); drawer.show();
}, },

View File

@ -1,11 +1,11 @@
import Checkbox from './Checkbox'; import Checkbox, { ICheckboxAttrs } from './Checkbox';
/** /**
* The `Switch` component is a `Checkbox`, but with a switch display instead of * The `Switch` component is a `Checkbox`, but with a switch display instead of
* a tick/cross one. * a tick/cross one.
*/ */
export default class Switch extends Checkbox { export default class Switch extends Checkbox {
static initAttrs(attrs) { static initAttrs(attrs: ICheckboxAttrs) {
super.initAttrs(attrs); super.initAttrs(attrs);
attrs.className = (attrs.className || '') + ' Checkbox--switch'; attrs.className = (attrs.className || '') + ' Checkbox--switch';

View File

@ -1,10 +1,5 @@
import setRouteWithForcedRefresh from '../../common/utils/setRouteWithForcedRefresh'; import setRouteWithForcedRefresh from '../../common/utils/setRouteWithForcedRefresh';
import IHistory, { HistoryEntry } from '../../common/IHistory';
export interface HistoryEntry {
name: string;
title: string;
url: string;
}
/** /**
* The `History` class keeps track and manages a stack of routes that the user * The `History` class keeps track and manages a stack of routes that the user
@ -17,7 +12,7 @@ export interface HistoryEntry {
* popping the history stack will still take them back to the discussion list * popping the history stack will still take them back to the discussion list
* rather than the previous discussion. * rather than the previous discussion.
*/ */
export default class History { export default class History implements IHistory {
/** /**
* The stack of routes that have been navigated to. * The stack of routes that have been navigated to.
*/ */