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

View File

@ -34,6 +34,7 @@ import type Component from './Component';
import type { ComponentAttrs } from './Component';
import Model, { SavedModelData } from './Model';
import fireApplicationError from './helpers/fireApplicationError';
import IHistory from './IHistory';
export type FlarumScreens = 'phone' | 'tablet' | 'desktop' | 'desktop-hd';
@ -228,6 +229,9 @@ export default class Application {
*/
drawer!: Drawer;
history: IHistory | null = null;
pane: any = null;
data!: ApplicationData;
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.
*/
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 Component from '../Component';
import Component, { ComponentAttrs } from '../Component';
import icon from '../helpers/icon';
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
* 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.
*/
export default class Badge extends Component {
export default class Badge<CustomAttrs extends IBadgeAttrs = IBadgeAttrs> extends Component<CustomAttrs> {
view() {
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 icon from '../helpers/icon';
import classList from '../utils/classList';
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.
@ -16,12 +24,8 @@ import withAttr from '../utils/withAttr';
* - `onchange` A callback to run when the checkbox is checked/unchecked.
* - `children` A text label to display next to the checkbox.
*/
export default class Checkbox extends Component {
view(vnode) {
// 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;
export default class Checkbox<CustomAttrs extends ICheckboxAttrs = ICheckboxAttrs> extends Component<CustomAttrs> {
view(vnode: Mithril.Vnode<CustomAttrs, this>) {
const className = classList([
'Checkbox',
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).
*
* @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');
}
/**
* 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);
}
}

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 Button from './Button';
import LinkButton from './LinkButton';
import type Mithril from 'mithril';
/**
* 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)}
onmouseleave={pane && pane.onmouseleave.bind(pane)}
>
{history.canGoBack() ? [this.getBackButton(), this.getPaneButton()] : this.getDrawerButton()}
{history?.canGoBack() ? [this.getBackButton(), this.getPaneButton()] : this.getDrawerButton()}
</div>
);
}
/**
* Get the back button.
*
* @return {import('mithril').Children}
* @protected
*/
getBackButton() {
protected getBackButton(): Mithril.Children {
const { history } = app;
const previous = history.getPrevious() || {};
const previous = history?.getPrevious();
return LinkButton.component({
className: 'Button Navigation-back Button--icon',
href: history.backUrl(),
href: history?.backUrl(),
icon: 'fas fa-chevron-left',
'aria-label': previous.title,
onclick: (e) => {
'aria-label': previous?.title,
onclick: (e: MouseEvent) => {
if (e.shiftKey || e.ctrlKey || e.metaKey || e.which === 2) return;
e.preventDefault();
history.back();
history?.back();
},
});
}
/**
* Get the pane pinned toggle button.
*
* @return {import('mithril').Children}
* @protected
*/
getPaneButton() {
protected getPaneButton(): Mithril.Children {
const { pane } = app;
if (!pane || !pane.active) return '';
@ -76,11 +71,8 @@ export default class Navigation extends Component {
/**
* Get the drawer toggle button.
*
* @return {import('mithril').Children}
* @protected
*/
getDrawerButton() {
protected getDrawerButton(): Mithril.Children {
if (!this.attrs.drawer) return '';
const { drawer } = app;
@ -88,7 +80,7 @@ export default class Navigation extends Component {
return Button.component({
className: 'Button Button--icon Navigation-drawer' + (user && user.newNotificationCount() ? ' new' : ''),
onclick: (e) => {
onclick: (e: MouseEvent) => {
e.stopPropagation();
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
* a tick/cross one.
*/
export default class Switch extends Checkbox {
static initAttrs(attrs) {
static initAttrs(attrs: ICheckboxAttrs) {
super.initAttrs(attrs);
attrs.className = (attrs.className || '') + ' Checkbox--switch';

View File

@ -1,10 +1,5 @@
import setRouteWithForcedRefresh from '../../common/utils/setRouteWithForcedRefresh';
export interface HistoryEntry {
name: string;
title: string;
url: string;
}
import IHistory, { HistoryEntry } from '../../common/IHistory';
/**
* 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
* rather than the previous discussion.
*/
export default class History {
export default class History implements IHistory {
/**
* The stack of routes that have been navigated to.
*/