mirror of
https://github.com/flarum/framework.git
synced 2024-11-29 04:33:47 +08:00
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:
parent
707ca2d16d
commit
7471ef64d5
|
@ -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: () => {},
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 = '';
|
||||
|
|
|
@ -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 {}
|
||||
}
|
||||
|
|
15
framework/core/js/src/common/IHistory.ts
Normal file
15
framework/core/js/src/common/IHistory.ts
Normal 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;
|
||||
}
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
21
framework/core/js/src/common/components/GroupBadge.tsx
Normal file
21
framework/core/js/src/common/components/GroupBadge.tsx
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
},
|
|
@ -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';
|
|
@ -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.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue
Block a user