Skip to content

Commit

Permalink
refactor: convert page components to TypeScript (#3538)
Browse files Browse the repository at this point in the history
* fix(a11y): color preview fields have no aria label
* refactor: convert page components to TypeScript

Co-authored-by: David Wheatley <hi@davwheat.dev>
Signed-off-by: Sami Mazouz <ilyasmazouz@gmail.com>
  • Loading branch information
SychO9 and davwheat authored Aug 8, 2022
1 parent 44825f1 commit 1948f25
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 63 deletions.
2 changes: 2 additions & 0 deletions framework/core/js/src/admin/AdminApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export interface AdminApplicationData extends ApplicationData {
extensions: Record<string, Extension>;
settings: Record<string, string>;
modelStatistics: Record<string, { total: number }>;
displayNameDrivers: string[];
slugDrivers: Record<string, string[]>;
}

export default class AdminApplication extends Application {
Expand Down
8 changes: 5 additions & 3 deletions framework/core/js/src/admin/components/AdminPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export type HTMLInputTypes =

export interface CommonSettingsItemOptions extends Mithril.Attributes {
setting: string;
label: Mithril.Children;
label?: Mithril.Children;
help?: Mithril.Children;
className?: string;
}
Expand Down Expand Up @@ -137,6 +137,8 @@ export type AdminHeaderAttrs = AdminHeaderOptions & Partial<Omit<Mithril.Attribu
export type SettingValue = string;
export type MutableSettings = Record<string, Stream<SettingValue>>;

export type SaveSubmitEvent = SubmitEvent & { redraw: boolean };

export default abstract class AdminPage<CustomAttrs extends IPageAttrs = IPageAttrs> extends Page<CustomAttrs> {
settings: MutableSettings = {};
loading: boolean = false;
Expand All @@ -162,7 +164,7 @@ export default abstract class AdminPage<CustomAttrs extends IPageAttrs = IPageAt
*
* Calls `this.saveSettings` when the button is clicked.
*/
submitButton(vnode: Mithril.Vnode<CustomAttrs, this>): Mithril.Children {
submitButton(): Mithril.Children {
return (
<Button onclick={this.saveSettings.bind(this)} className="Button Button--primary" loading={this.loading} disabled={!this.isChanged()}>
{app.translator.trans('core.admin.settings.submit_button')}
Expand Down Expand Up @@ -385,7 +387,7 @@ export default abstract class AdminPage<CustomAttrs extends IPageAttrs = IPageAt
/**
* Saves the modified settings to the database.
*/
saveSettings(e: SubmitEvent & { redraw: boolean }) {
saveSettings(e: SaveSubmitEvent) {
e.preventDefault();

app.alerts.clear();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import EditCustomFooterModal from './EditCustomFooterModal';
import UploadImageButton from './UploadImageButton';
import AdminPage from './AdminPage';
import ItemList from '../../common/utils/ItemList';
import type Mithril from 'mithril';

export default class AppearancePage extends AdminPage {
headerInfo() {
Expand Down Expand Up @@ -77,7 +78,7 @@ export default class AppearancePage extends AdminPage {
}

colorItems() {
const items = new ItemList();
const items = new ItemList<Mithril.Children>();

items.add('helpText', <div className="helpText">{app.translator.trans('core.admin.appearance.colors_text')}</div>, 80);

Expand All @@ -88,11 +89,13 @@ export default class AppearancePage extends AdminPage {
type: 'color-preview',
setting: 'theme_primary_color',
placeholder: '#aaaaaa',
ariaLabel: app.translator.trans('core.admin.appearance.colors_primary_label'),
})}
{this.buildSettingComponent({
type: 'color-preview',
setting: 'theme_secondary_color',
placeholder: '#aaaaaa',
ariaLabel: app.translator.trans('core.admin.appearance.colors_secondary_label'),
})}
</div>,
70
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,27 @@ import app from '../../admin/app';
import FieldSet from '../../common/components/FieldSet';
import ItemList from '../../common/utils/ItemList';
import AdminPage from './AdminPage';
import type { IPageAttrs } from '../../common/components/Page';
import type Mithril from 'mithril';

export default class BasicsPage extends AdminPage {
oninit(vnode) {
export type HomePageItem = { path: string; label: Mithril.Children };

export default class BasicsPage<CustomAttrs extends IPageAttrs = IPageAttrs> extends AdminPage<CustomAttrs> {
localeOptions: Record<string, string> = {};
displayNameOptions: Record<string, string> = {};
slugDriverOptions: Record<string, Record<string, string>> = {};

oninit(vnode: Mithril.Vnode<CustomAttrs, this>) {
super.oninit(vnode);

this.localeOptions = {};
const locales = app.data.locales;
for (const i in locales) {
this.localeOptions[i] = `${locales[i]} (${i})`;
}
Object.keys(app.data.locales).forEach((i) => {
this.localeOptions[i] = `${app.data.locales[i]} (${i})`;
});

this.displayNameOptions = {};
const displayNameDrivers = app.data.displayNameDrivers;
displayNameDrivers.forEach(function (identifier) {
app.data.displayNameDrivers.forEach((identifier) => {
this.displayNameOptions[identifier] = identifier;
}, this);
});

this.slugDriverOptions = {};
Object.keys(app.data.slugDrivers).forEach((model) => {
this.slugDriverOptions[model] = {};

Expand Down Expand Up @@ -100,6 +103,7 @@ export default class BasicsPage extends AdminPage {

{Object.keys(this.slugDriverOptions).map((model) => {
const options = this.slugDriverOptions[model];

if (Object.keys(options).length > 1) {
return this.buildSettingComponent({
type: 'select',
Expand All @@ -109,6 +113,8 @@ export default class BasicsPage extends AdminPage {
help: app.translator.trans('core.admin.basics.slug_driver_text', { model }),
});
}

return null;
})}

{this.submitButton()}
Expand All @@ -119,11 +125,9 @@ export default class BasicsPage extends AdminPage {
/**
* Build a list of options for the default homepage. Each option must be an
* object with `path` and `label` properties.
*
* @return {ItemList<{ path: string, label: import('mithril').Children }>}
*/
homePageItems() {
const items = new ItemList();
const items = new ItemList<HomePageItem>();

items.add('allDiscussions', {
path: '/all',
Expand Down
2 changes: 1 addition & 1 deletion framework/core/js/src/admin/components/ExtensionPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export default class ExtensionPage<Attrs extends ExtensionPageAttrs = ExtensionP
{settings ? (
<div className="Form">
{settings.map(this.buildSettingComponent.bind(this))}
<div className="Form-group">{this.submitButton(vnode)}</div>
<div className="Form-group">{this.submitButton()}</div>
</div>
) : (
<h3 className="ExtensionPage-subHeader">{app.translator.trans('core.admin.extension.no_settings')}</h3>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,30 @@ import Button from '../../common/components/Button';
import Alert from '../../common/components/Alert';
import LoadingIndicator from '../../common/components/LoadingIndicator';
import AdminPage from './AdminPage';
import type { IPageAttrs } from '../../common/components/Page';
import type { AlertIdentifier } from '../../common/states/AlertManagerState';
import type Mithril from 'mithril';
import type { SaveSubmitEvent } from './AdminPage';

export interface MailSettings {
data: {
attributes: {
fields: Record<string, any>;
sending: boolean;
errors: any[];
};
};
}

export default class MailPage<CustomAttrs extends IPageAttrs = IPageAttrs> extends AdminPage<CustomAttrs> {
sendingTest = false;
status?: { sending: boolean; errors: any };
driverFields?: Record<string, any>;
testEmailSuccessAlert?: AlertIdentifier;

export default class MailPage extends AdminPage {
oninit(vnode) {
oninit(vnode: Mithril.Vnode<CustomAttrs, this>) {
super.oninit(vnode);

this.sendingTest = false;
this.refresh();
}

Expand All @@ -28,14 +46,14 @@ export default class MailPage extends AdminPage {
this.status = { sending: false, errors: {} };

app
.request({
.request<MailSettings>({
method: 'GET',
url: app.forum.attribute('apiUrl') + '/mail/settings',
})
.then((response) => {
this.driverFields = response['data']['attributes']['fields'];
this.status.sending = response['data']['attributes']['sending'];
this.status.errors = response['data']['attributes']['errors'];
this.driverFields = response.data.attributes.fields;
this.status!.sending = response.data.attributes.sending;
this.status!.errors = response.data.attributes.errors;

this.loading = false;
m.redraw();
Expand All @@ -47,7 +65,7 @@ export default class MailPage extends AdminPage {
return <LoadingIndicator />;
}

const fields = this.driverFields[this.setting('mail_driver')()];
const fields = this.driverFields![this.setting('mail_driver')()];
const fieldKeys = Object.keys(fields);

return (
Expand All @@ -60,10 +78,10 @@ export default class MailPage extends AdminPage {
{this.buildSettingComponent({
type: 'select',
setting: 'mail_driver',
options: Object.keys(this.driverFields).reduce((memo, val) => ({ ...memo, [val]: val }), {}),
options: Object.keys(this.driverFields!).reduce((memo, val) => ({ ...memo, [val]: val }), {}),
label: app.translator.trans('core.admin.email.driver_heading'),
})}
{this.status.sending ||
{this.status!.sending ||
Alert.component(
{
dismissible: false,
Expand All @@ -84,7 +102,7 @@ export default class MailPage extends AdminPage {
setting: field,
options: fieldInfo,
}),
this.status.errors[field] && <p className="ValidationError">{this.status.errors[field]}</p>,
this.status!.errors[field] && <p className="ValidationError">{this.status!.errors[field]}</p>,
];
})}
</div>
Expand All @@ -93,7 +111,7 @@ export default class MailPage extends AdminPage {
{this.submitButton()}

<FieldSet label={app.translator.trans('core.admin.email.send_test_mail_heading')} className="MailPage-MailSettings">
<div className="helpText">{app.translator.trans('core.admin.email.send_test_mail_text', { email: app.session.user.email() })}</div>
<div className="helpText">{app.translator.trans('core.admin.email.send_test_mail_text', { email: app.session.user!.email() })}</div>
{Button.component(
{
className: 'Button Button--primary',
Expand All @@ -108,10 +126,11 @@ export default class MailPage extends AdminPage {
}

sendTestEmail() {
if (this.saving || this.sendingTest) return;
if (this.sendingTest) return;

this.sendingTest = true;
app.alerts.dismiss(this.testEmailSuccessAlert);

if (this.testEmailSuccessAlert) app.alerts.dismiss(this.testEmailSuccessAlert);

app
.request({
Expand All @@ -129,7 +148,7 @@ export default class MailPage extends AdminPage {
});
}

saveSettings(e) {
super.saveSettings(e).then(this.refresh());
saveSettings(e: SaveSubmitEvent) {
return super.saveSettings(e).then(() => this.refresh());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ export default class PermissionsPage extends AdminPage {
return [
<div className="PermissionsPage-groups">
{app.store
.all('groups')
.filter((group) => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()) === -1)
.all<Group>('groups')
.filter((group) => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()!) === -1)
.map((group) => (
<button className="Button Group" onclick={() => app.modal.show(EditGroupModal, { group })}>
{GroupBadge.component({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import app from '../../forum/app';
import Page from '../../common/components/Page';
import Page, { IPageAttrs } from '../../common/components/Page';
import NotificationList from './NotificationList';
import type Mithril from 'mithril';
import extractText from '../../common/utils/extractText';

/**
* The `NotificationsPage` component shows the notifications list. It is only
* used on mobile devices where the notifications dropdown is within the drawer.
*/
export default class NotificationsPage extends Page {
oninit(vnode) {
export default class NotificationsPage<CustomAttrs extends IPageAttrs = IPageAttrs> extends Page<CustomAttrs> {
oninit(vnode: Mithril.Vnode<CustomAttrs, this>) {
super.oninit(vnode);

app.history.push('notifications');
app.history.push('notifications', extractText(app.translator.trans('core.forum.notifications.title')));

app.notifications.load();

Expand Down
Loading

0 comments on commit 1948f25

Please sign in to comment.