Skip to content

Commit

Permalink
chore: convert Dropdown components to TS (#3608)
Browse files Browse the repository at this point in the history
* chore: convert `Dropdown` components to TS
* chore(review): `buttonClassName` technically not required
* chore(review): `accessibleToggleLabel` technically not required
* chore(review): use `classList` where possible
* chore: `yarn format`
* Update framework/core/js/src/common/components/Dropdown.tsx
* chore(review): use `includes`
* chore(review): define constant of excluded groups
* chore(review): use `null coalesce` and `logical or` assignments
* chore(review): `null coalesce`
* chore(review): `any` to `typeof Component`
* chore(review): `classList`
* chore(review): `yarn format`
* chore: fix typing issues after typescript update

Signed-off-by: Sami Mazouz <sychocouldy@gmail.com>
Co-authored-by: David Wheatley <hi@davwheat.dev>
  • Loading branch information
SychO9 and davwheat committed Nov 27, 2022
1 parent 0e238a9 commit 5bc47c0
Show file tree
Hide file tree
Showing 14 changed files with 368 additions and 322 deletions.
1 change: 1 addition & 0 deletions framework/core/js/src/admin/AdminApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface AdminApplicationData extends ApplicationData {
modelStatistics: Record<string, { total: number }>;
displayNameDrivers: string[];
slugDrivers: Record<string, string[]>;
permissions: Record<string, string[]>;
}

export default class AdminApplication extends Application {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import app from '../../admin/app';
import Dropdown from '../../common/components/Dropdown';
import Dropdown, { IDropdownAttrs } from '../../common/components/Dropdown';
import Button from '../../common/components/Button';
import Separator from '../../common/components/Separator';
import Group from '../../common/models/Group';
import Badge from '../../common/components/Badge';
import GroupBadge from '../../common/components/GroupBadge';
import Mithril from 'mithril';

function badgeForId(id) {
function badgeForId(id: string) {
const group = app.store.getById('groups', id);

return group ? GroupBadge.component({ group, label: null }) : '';
}

function filterByRequiredPermissions(groupIds, permission) {
function filterByRequiredPermissions(groupIds: string[], permission: string) {
app.getRequiredPermissions(permission).forEach((required) => {
const restrictToGroupIds = app.data.permissions[required] || [];

Expand All @@ -32,24 +33,28 @@ function filterByRequiredPermissions(groupIds, permission) {
return groupIds;
}

export default class PermissionDropdown extends Dropdown {
static initAttrs(attrs) {
export interface IPermissionDropdownAttrs extends IDropdownAttrs {
permission: string;
}

export default class PermissionDropdown<CustomAttrs extends IPermissionDropdownAttrs = IPermissionDropdownAttrs> extends Dropdown<CustomAttrs> {
static initAttrs(attrs: IPermissionDropdownAttrs) {
super.initAttrs(attrs);

attrs.className = 'PermissionDropdown';
attrs.buttonClassName = 'Button Button--text';
}

view(vnode) {
view(vnode: Mithril.Vnode<CustomAttrs, this>) {
const children = [];

let groupIds = app.data.permissions[this.attrs.permission] || [];

groupIds = filterByRequiredPermissions(groupIds, this.attrs.permission);

const everyone = groupIds.indexOf(Group.GUEST_ID) !== -1;
const members = groupIds.indexOf(Group.MEMBER_ID) !== -1;
const adminGroup = app.store.getById('groups', Group.ADMINISTRATOR_ID);
const everyone = groupIds.includes(Group.GUEST_ID);
const members = groupIds.includes(Group.MEMBER_ID);
const adminGroup = app.store.getById<Group>('groups', Group.ADMINISTRATOR_ID)!;

if (everyone) {
this.attrs.label = Badge.component({ icon: 'fas fa-globe' });
Expand Down Expand Up @@ -89,40 +94,42 @@ export default class PermissionDropdown extends Dropdown {
{
icon: !everyone && !members ? 'fas fa-check' : true,
disabled: !everyone && !members,
onclick: (e) => {
onclick: (e: MouseEvent) => {
if (e.shiftKey) e.stopPropagation();
this.save([]);
},
},
[badgeForId(adminGroup.id()), ' ', adminGroup.namePlural()]
[badgeForId(adminGroup.id()!), ' ', adminGroup.namePlural()]
)
);

[].push.apply(
children,
app.store
.all('groups')
.filter((group) => [Group.ADMINISTRATOR_ID, Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()) === -1)
.map((group) =>
Button.component(
{
icon: groupIds.indexOf(group.id()) !== -1 ? 'fas fa-check' : true,
onclick: (e) => {
if (e.shiftKey) e.stopPropagation();
this.toggle(group.id());
},
disabled: this.isGroupDisabled(group.id()) && this.isGroupDisabled(Group.MEMBER_ID) && this.isGroupDisabled(Group.GUEST_ID),
// These groups are defined above, appearing first in the list.
const excludedGroups = [Group.ADMINISTRATOR_ID, Group.GUEST_ID, Group.MEMBER_ID];

const groupButtons = app.store
.all<Group>('groups')
.filter((group) => !excludedGroups.includes(group.id()!))
.map((group) =>
Button.component(
{
icon: groupIds.includes(group.id()!) ? 'fas fa-check' : true,
onclick: (e: MouseEvent) => {
if (e.shiftKey) e.stopPropagation();
this.toggle(group.id()!);
},
[badgeForId(group.id()), ' ', group.namePlural()]
)
disabled: this.isGroupDisabled(group.id()!) && this.isGroupDisabled(Group.MEMBER_ID) && this.isGroupDisabled(Group.GUEST_ID),
},
[badgeForId(group.id()!), ' ', group.namePlural()]
)
);
);

children.push(...groupButtons);
}

return super.view({ ...vnode, children });
}

save(groupIds) {
save(groupIds: string[]) {
const permission = this.attrs.permission;

app.data.permissions[permission] = groupIds;
Expand All @@ -134,7 +141,7 @@ export default class PermissionDropdown extends Dropdown {
});
}

toggle(groupId) {
toggle(groupId: string) {
const permission = this.attrs.permission;

let groupIds = app.data.permissions[permission] || [];
Expand All @@ -151,7 +158,7 @@ export default class PermissionDropdown extends Dropdown {
this.save(groupIds);
}

isGroupDisabled(id) {
return filterByRequiredPermissions([id], this.attrs.permission).indexOf(id) === -1;
isGroupDisabled(id: string) {
return !filterByRequiredPermissions([id], this.attrs.permission).includes(id);
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
import app from '../../admin/app';
import avatar from '../../common/helpers/avatar';
import username from '../../common/helpers/username';
import Dropdown from '../../common/components/Dropdown';
import Dropdown, { IDropdownAttrs } from '../../common/components/Dropdown';
import Button from '../../common/components/Button';
import ItemList from '../../common/utils/ItemList';
import type Mithril from 'mithril';

export interface ISessionDropdownAttrs extends IDropdownAttrs {}

/**
* The `SessionDropdown` component shows a button with the current user's
* avatar/name, with a dropdown of session controls.
*/
export default class SessionDropdown extends Dropdown {
static initAttrs(attrs) {
export default class SessionDropdown<CustomAttrs extends ISessionDropdownAttrs = ISessionDropdownAttrs> extends Dropdown<CustomAttrs> {
static initAttrs(attrs: ISessionDropdownAttrs) {
super.initAttrs(attrs);

attrs.className = 'SessionDropdown';
attrs.buttonClassName = 'Button Button--user Button--flat';
attrs.menuClassName = 'Dropdown-menu--right';
}

view(vnode) {
view(vnode: Mithril.Vnode<CustomAttrs, this>) {
return super.view({ ...vnode, children: this.items().toArray() });
}

Expand All @@ -30,11 +33,9 @@ export default class SessionDropdown extends Dropdown {

/**
* Build an item list for the contents of the dropdown menu.
*
* @return {ItemList}
*/
items() {
const items = new ItemList();
items(): ItemList<Mithril.Children> {
const items = new ItemList<Mithril.Children>();

items.add(
'logOut',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import app from '../app';
import SelectDropdown from '../../common/components/SelectDropdown';
import SelectDropdown, { ISelectDropdownAttrs } from '../../common/components/SelectDropdown';
import Button from '../../common/components/Button';
import saveSettings from '../utils/saveSettings';
import Mithril from 'mithril';

export default class SettingDropdown extends SelectDropdown {
static initAttrs(attrs) {
export type SettingDropdownOption = {
value: any;
label: string;
};

export interface ISettingDropdownAttrs extends ISelectDropdownAttrs {
setting?: string;
options: Array<SettingDropdownOption>;
}

export default class SettingDropdown<CustomAttrs extends ISettingDropdownAttrs = ISettingDropdownAttrs> extends SelectDropdown<CustomAttrs> {
static initAttrs(attrs: ISettingDropdownAttrs) {
super.initAttrs(attrs);

attrs.className = 'SettingDropdown';
Expand All @@ -13,21 +24,21 @@ export default class SettingDropdown extends SelectDropdown {
attrs.defaultLabel = 'Custom';

if ('key' in attrs) {
attrs.setting = attrs.key;
attrs.setting = attrs.key?.toString();
delete attrs.key;
}
}

view(vnode) {
view(vnode: Mithril.Vnode<CustomAttrs, this>) {
return super.view({
...vnode,
children: this.attrs.options.map(({ value, label }) => {
const active = app.data.settings[this.attrs.setting] === value;
const active = app.data.settings[this.attrs.setting!] === value;

return Button.component(
{
icon: active ? 'fas fa-check' : true,
onclick: saveSettings.bind(this, { [this.attrs.setting]: value }),
onclick: saveSettings.bind(this, { [this.attrs.setting!]: value }),
active,
},
label
Expand Down
140 changes: 0 additions & 140 deletions framework/core/js/src/common/components/Dropdown.js

This file was deleted.

Loading

0 comments on commit 5bc47c0

Please sign in to comment.