mirror of
https://github.com/flarum/core.git
synced 2025-08-20 23:31:27 +02:00
feat: extension list UI (#4066)
This commit is contained in:
@@ -15,12 +15,7 @@ export default class ControlSection extends Component<ComponentAttrs> {
|
||||
|
||||
view() {
|
||||
return (
|
||||
<div className="ExtensionPage-permissions ExtensionManager-controlSection">
|
||||
<div className="ExtensionPage-permissions-header">
|
||||
<div className="container">
|
||||
<h2 className="ExtensionTitle">{app.translator.trans('flarum-extension-manager.admin.sections.control.title')}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ExtensionPage-settings ExtensionManager-controlSection">
|
||||
<div className="container">
|
||||
{app.data['flarum-extension-manager.writable_dirs'] ? (
|
||||
<Form>
|
||||
|
@@ -0,0 +1,298 @@
|
||||
import app from 'flarum/admin/app';
|
||||
import Component, { type ComponentAttrs } from 'flarum/common/Component';
|
||||
import Form from 'flarum/common/components/Form';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import type Mithril from 'mithril';
|
||||
import LoadingIndicator from 'flarum/common/components/LoadingIndicator';
|
||||
import ItemList from 'flarum/common/utils/ItemList';
|
||||
import Input from 'flarum/common/components/Input';
|
||||
import Stream from 'flarum/common/utils/Stream';
|
||||
import Alert from 'flarum/common/components/Alert';
|
||||
import listItems from 'flarum/common/helpers/listItems';
|
||||
import LinkButton from 'flarum/common/components/LinkButton';
|
||||
import Dropdown from 'flarum/common/components/Dropdown';
|
||||
|
||||
import type ExternalExtension from '../models/ExternalExtension';
|
||||
import ExtensionCard from './ExtensionCard';
|
||||
import Pagination from 'flarum/common/components/Pagination';
|
||||
import InfoTile from 'flarum/common/components/InfoTile';
|
||||
import classList from 'flarum/common/utils/classList';
|
||||
import { throttle } from 'flarum/common/utils/throttleDebounce';
|
||||
|
||||
export interface IDiscoverSectionAttrs extends ComponentAttrs {}
|
||||
|
||||
export default class DiscoverSection<CustomAttrs extends IDiscoverSectionAttrs = IDiscoverSectionAttrs> extends Component<CustomAttrs> {
|
||||
protected search = Stream('');
|
||||
protected warningsDismissed = Stream(false);
|
||||
|
||||
oninit(vnode: Mithril.Vnode<CustomAttrs, this>) {
|
||||
super.oninit(vnode);
|
||||
|
||||
app.extensionManager.extensions.goto(1);
|
||||
|
||||
this.warningsDismissed(localStorage.getItem('flarum-extension-manager.warningsDismissed') === 'true');
|
||||
}
|
||||
|
||||
load(page = 1) {
|
||||
app.extensionManager.extensions.goto(page);
|
||||
}
|
||||
|
||||
view() {
|
||||
return (
|
||||
<div className="ExtensionPage-settings ExtensionManager-DiscoverSection">
|
||||
<div className="container">
|
||||
<Form>
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.sections.discover.title')}</label>
|
||||
<div className="helpText">
|
||||
{app.translator.trans('flarum-extension-manager.admin.sections.discover.description')}
|
||||
{this.warningsDismissed() && (
|
||||
<Button
|
||||
className="Button Button--text Button--warning Button--more"
|
||||
icon="fas fa-exclamation-triangle"
|
||||
onclick={() => this.setWarningDismissed(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!this.warningsDismissed() && (
|
||||
<div className="ExtensionManager-warnings Form-group">
|
||||
<Alert className="ExtensionManager-primaryWarning" type="warning" dismissible={true} ondismiss={() => this.setWarningDismissed(true)}>
|
||||
<ul>{listItems(this.warningItems().toArray())}</ul>
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
<div className="Tabs">
|
||||
<div className="Tabs-nav">{this.tabItems().toArray()}</div>
|
||||
<div className="Tabs-content">
|
||||
<hr className="Tabs-divider" />
|
||||
<div className="ExtensionManager-DiscoverSection-toolbar">
|
||||
<div className="ExtensionManager-DiscoverSection-toolbar-primary">{this.toolbarPrimaryItems().toArray()}</div>
|
||||
<div className="ExtensionManager-DiscoverSection-toolbar-secondary">{this.toolbarSecondaryItems().toArray()}</div>
|
||||
</div>
|
||||
{this.extensionList()}
|
||||
<div className="ExtensionManager-DiscoverSection-footer">{this.footerItems().toArray()}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
tabFilters(): Record<string, { label: Mithril.Children; active: () => boolean }> {
|
||||
return {
|
||||
'': {
|
||||
label: app.translator.trans('flarum-extension-manager.admin.sections.discover.tabs.discover'),
|
||||
active: () => !app.extensionManager.extensions.getParams().filter?.type,
|
||||
},
|
||||
extension: {
|
||||
label: app.translator.trans('flarum-extension-manager.admin.sections.discover.tabs.extensions'),
|
||||
active: () => app.extensionManager.extensions.getParams().filter?.type === 'extension',
|
||||
},
|
||||
locale: {
|
||||
label: app.translator.trans('flarum-extension-manager.admin.sections.discover.tabs.languages'),
|
||||
active: () => app.extensionManager.extensions.getParams().filter?.type === 'locale',
|
||||
},
|
||||
theme: {
|
||||
label: app.translator.trans('flarum-extension-manager.admin.sections.discover.tabs.themes'),
|
||||
active: () => app.extensionManager.extensions.getParams().filter?.type === 'theme',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
tabItems() {
|
||||
const items = new ItemList();
|
||||
|
||||
const tabs = this.tabFilters();
|
||||
|
||||
Object.keys(tabs).forEach((key) => {
|
||||
const tab = tabs[key];
|
||||
|
||||
items.add(
|
||||
key,
|
||||
<Button
|
||||
className="Button Button--link"
|
||||
active={tab.active()}
|
||||
onclick={() => {
|
||||
app.extensionManager.extensions.changeFilter('type', key);
|
||||
}}
|
||||
>
|
||||
{tab.label}
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
warningItems() {
|
||||
const items = new ItemList<Mithril.Children>();
|
||||
|
||||
items.add('accessWarning', app.translator.trans('flarum-extension-manager.admin.settings.access_warning'));
|
||||
|
||||
if (app.data.debugEnabled) {
|
||||
items.add('devModeWarning', app.translator.trans('flarum-extension-manager.admin.settings.debug_mode_warning'));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private applySearch = throttle(1200, (value: string) => {
|
||||
const params = app.extensionManager.extensions.getParams();
|
||||
|
||||
app.extensionManager.extensions.refreshParams({ ...params, filter: { ...params.filter, q: value } }, 1);
|
||||
});
|
||||
|
||||
toolbarPrimaryItems() {
|
||||
const items = new ItemList();
|
||||
|
||||
items.add(
|
||||
'search',
|
||||
<Input
|
||||
value={this.search()}
|
||||
onchange={(value: string) => {
|
||||
this.search(value);
|
||||
this.applySearch(value);
|
||||
}}
|
||||
inputAttrs={{ className: 'FormControl-alt' }}
|
||||
clearable={true}
|
||||
placeholder={app.translator.trans('flarum-extension-manager.admin.sections.discover.search')}
|
||||
prefixIcon="fas fa-search"
|
||||
/>
|
||||
);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
toolbarSecondaryItems() {
|
||||
const items = new ItemList();
|
||||
|
||||
const sortMap = app.extensionManager.extensions.sortMap();
|
||||
|
||||
const sortOptions = Object.keys(sortMap).reduce((acc: any, sortId) => {
|
||||
const sort = sortMap[sortId];
|
||||
acc[sortId] = typeof sort !== 'string' ? sort.label : sort;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
items.add(
|
||||
'sort',
|
||||
<Dropdown
|
||||
buttonClassName="Button"
|
||||
label={sortOptions[app.extensionManager.extensions.getParams().sort] || Object.keys(sortMap).map((key) => sortOptions[key])[0]}
|
||||
accessibleToggleLabel={app.translator.trans('flarum-extension-manager.admin.sections.discover.sort.toggle_dropdown_accessible_label')}
|
||||
>
|
||||
{Object.keys(sortOptions).map((value) => {
|
||||
const label = sortOptions[value];
|
||||
const active = app.extensionManager.extensions.getParams().sort === value;
|
||||
|
||||
return (
|
||||
<Button icon={active ? 'fas fa-check' : true} onclick={() => app.extensionManager.extensions.changeSort(value)} active={active}>
|
||||
{label}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</Dropdown>
|
||||
);
|
||||
|
||||
const is = app.extensionManager.extensions.getParams().filter?.is?.[0] ?? null;
|
||||
const activeType = is || 'all';
|
||||
|
||||
items.add(
|
||||
'party',
|
||||
<Dropdown
|
||||
buttonClassName="Button"
|
||||
label={app.translator.trans('flarum-extension-manager.admin.sections.discover.party_filter.' + activeType)}
|
||||
accessibleToggleLabel={app.translator.trans('flarum-extension-manager.admin.sections.discover.party_filter.toggle_dropdown_accessible_label')}
|
||||
>
|
||||
{['all', 'premium'].map((party) => (
|
||||
<Button
|
||||
icon={activeType === party ? 'fas fa-check' : true}
|
||||
onclick={() => {
|
||||
app.extensionManager.extensions.changeFilter('is', party === 'all' ? undefined : [party]);
|
||||
}}
|
||||
active={activeType === party}
|
||||
>
|
||||
{app.translator.trans('flarum-extension-manager.admin.sections.discover.party_filter.' + party)}
|
||||
</Button>
|
||||
))}
|
||||
</Dropdown>
|
||||
);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
extensionList() {
|
||||
if (!app.extensionManager.extensions.hasItems() && app.extensionManager.extensions.isLoading()) {
|
||||
return <LoadingIndicator display="block" />;
|
||||
}
|
||||
|
||||
if (!app.extensionManager.extensions.hasItems()) {
|
||||
return (
|
||||
<div className="ExtensionManager-DiscoverSection-list ExtensionManager-DiscoverSection-list--empty">
|
||||
<InfoTile icon="fas fa-plug-circle-exclamation">
|
||||
{app.translator.trans('flarum-extension-manager.admin.sections.discover.empty_results')}
|
||||
</InfoTile>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classList('ExtensionManager-DiscoverSection-list', {
|
||||
'loading-container': app.extensionManager.extensions.isLoading(),
|
||||
})}
|
||||
>
|
||||
<div className="ExtensionManager-DiscoverSection-list-inner">
|
||||
{app.extensionManager.extensions
|
||||
.getPages()
|
||||
.map((page) => page.items.map((extension: ExternalExtension) => <ExtensionCard extension={extension} key={extension.name()} />))}
|
||||
</div>
|
||||
{app.extensionManager.extensions.hasItems() && app.extensionManager.extensions.isLoading() && <LoadingIndicator size="large" />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
footerItems() {
|
||||
const items = new ItemList<Mithril.Children>();
|
||||
|
||||
items.add(
|
||||
'pagination',
|
||||
<Pagination
|
||||
total={app.extensionManager.extensions.totalItems}
|
||||
perPage={app.extensionManager.extensions.pageSize}
|
||||
currentPage={app.extensionManager.extensions.getLocation().page}
|
||||
onChange={(page: number) => {
|
||||
const current = app.extensionManager.extensions.getLocation().page;
|
||||
|
||||
if (current === page) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.load(page);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
items.add(
|
||||
'premiumTermsLink',
|
||||
<LinkButton
|
||||
className="Button Button--link"
|
||||
href="https://flarum.org/terms/premium-extensions"
|
||||
external={true}
|
||||
target="_blank"
|
||||
icon="fas fa-circle-info"
|
||||
>
|
||||
{app.translator.trans('flarum-extension-manager.admin.sections.discover.premium_extension_terms')}
|
||||
</LinkButton>
|
||||
);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private setWarningDismissed(dismissed: boolean) {
|
||||
this.warningsDismissed(dismissed);
|
||||
localStorage.setItem('flarum-extension-manager.warningsDismissed', dismissed ? 'true' : 'false');
|
||||
}
|
||||
}
|
@@ -0,0 +1,292 @@
|
||||
import Component, { type ComponentAttrs } from 'flarum/common/Component';
|
||||
import Icon from 'flarum/common/components/Icon';
|
||||
import Badge from 'flarum/common/components/Badge';
|
||||
import app from 'flarum/admin/app';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import formatAmount from 'flarum/common/utils/formatAmount';
|
||||
import { type Extension as ExtensionInfo } from 'flarum/admin/AdminApplication';
|
||||
import ExternalExtension from '../models/ExternalExtension';
|
||||
import { UpdatedPackage } from '../states/ControlSectionState';
|
||||
import ItemList from 'flarum/common/utils/ItemList';
|
||||
import type Mithril from 'mithril';
|
||||
import classList from 'flarum/common/utils/classList';
|
||||
import Label from './Label';
|
||||
import Tooltip from 'flarum/common/components/Tooltip';
|
||||
import Dropdown from 'flarum/common/components/Dropdown';
|
||||
import WhyNotModal from './WhyNotModal';
|
||||
import LinkButton from 'flarum/common/components/LinkButton';
|
||||
|
||||
export type CommonExtension = ExternalExtension | ExtensionInfo;
|
||||
|
||||
export interface IExtensionAttrs extends ComponentAttrs {
|
||||
extension: CommonExtension;
|
||||
updates?: UpdatedPackage;
|
||||
onClickUpdate?:
|
||||
| CallableFunction
|
||||
| {
|
||||
soft: CallableFunction;
|
||||
hard: CallableFunction;
|
||||
};
|
||||
whyNotWarning?: boolean;
|
||||
isCore?: boolean;
|
||||
updatable?: boolean;
|
||||
isDanger?: boolean;
|
||||
}
|
||||
|
||||
export default class ExtensionCard<CustomAttrs extends IExtensionAttrs = IExtensionAttrs> extends Component<CustomAttrs> {
|
||||
getExtension() {
|
||||
return this.attrs.extension instanceof ExternalExtension ? this.attrs.extension.toLocalExtension() : this.attrs.extension;
|
||||
}
|
||||
|
||||
view() {
|
||||
const extension = this.getExtension();
|
||||
const { isCore, isDanger } = this.attrs;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classList('ExtensionCard', {
|
||||
'ExtensionCard--core': isCore,
|
||||
'ExtensionCard--danger': isDanger,
|
||||
})}
|
||||
>
|
||||
<div className="ExtensionCard-header">
|
||||
{this.icon()}
|
||||
<Tooltip text={extension.name}>
|
||||
<h4>{extension.extra['flarum-extension'].title}</h4>
|
||||
</Tooltip>
|
||||
{this.attrs.extension instanceof ExternalExtension && <div className="ExtensionCard-badges">{this.badges().toArray()}</div>}
|
||||
<div className="ExtensionCard-actions">{this.actionItems().toArray()}</div>
|
||||
</div>
|
||||
<div className="ExtensionCard-body">
|
||||
<p>{extension.description}</p>
|
||||
</div>
|
||||
<div className="ExtensionCard-footer">
|
||||
<div className="ExtensionCard-meta">{this.metaItems().toArray()}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
icon() {
|
||||
const extension = this.getExtension();
|
||||
|
||||
if (this.attrs.extension instanceof ExternalExtension && extension.id in app.data.extensions) {
|
||||
extension.icon = app.data.extensions[extension.id].icon;
|
||||
}
|
||||
|
||||
const style: any = extension.icon || {};
|
||||
|
||||
if (
|
||||
!extension.icon?.name &&
|
||||
this.attrs.extension instanceof ExternalExtension &&
|
||||
!(extension.id in app.data.extensions) &&
|
||||
this.attrs.extension.iconUrl()
|
||||
) {
|
||||
style.backgroundImage = `url(${this.attrs.extension.iconUrl()})`;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="ExtensionIcon" style={extension.icon}>
|
||||
{extension.icon?.name ? <Icon name={extension.icon.name} /> : null}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
badges() {
|
||||
const items = new ItemList<Mithril.Children>();
|
||||
|
||||
const extension = this.attrs.extension as ExternalExtension;
|
||||
|
||||
if (extension.isSupported()) {
|
||||
items.add(
|
||||
'compatible',
|
||||
<Badge
|
||||
icon="fas fa-check"
|
||||
type="success"
|
||||
label={app.translator.trans('flarum-extension-manager.admin.sections.discover.extension.badges.compatible')}
|
||||
className="Badge--flat Badge--square"
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
items.add(
|
||||
'incompatible',
|
||||
<Badge
|
||||
icon="fas fa-times"
|
||||
type="danger"
|
||||
label={app.translator.trans('flarum-extension-manager.admin.sections.discover.extension.badges.incompatible')}
|
||||
className="Badge--flat Badge--square"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (extension.isPremium()) {
|
||||
items.add(
|
||||
'premium',
|
||||
<Badge
|
||||
icon="fas fa-dollar-sign"
|
||||
label={app.translator.trans('flarum-extension-manager.admin.sections.discover.extension.badges.premium')}
|
||||
className="ExtensionCard-badge--premium Badge--flat Badge--square"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!extension.isStable()) {
|
||||
items.add(
|
||||
'unstable',
|
||||
<Badge
|
||||
icon="fas fa-flask"
|
||||
label={app.translator.trans('flarum-extension-manager.admin.sections.discover.extension.badges.unstable')}
|
||||
className="Badge--flat Badge--square Badge--danger"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (extension.name().split('/')[0] === 'fof') {
|
||||
items.add(
|
||||
'fof',
|
||||
<Badge
|
||||
icon="fas fa-users"
|
||||
label={app.translator.trans('flarum-extension-manager.admin.sections.discover.extension.badges.fof')}
|
||||
className="Badge--flat Badge--square"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (extension.name().split('/')[0] === 'flarum') {
|
||||
items.add(
|
||||
'flarum',
|
||||
<Badge
|
||||
icon="fab fa-flarum"
|
||||
label={app.translator.trans('flarum-extension-manager.admin.sections.discover.extension.badges.flarum')}
|
||||
className="ExtensionCard-badge--flarum Badge--flat Badge--square"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
metaItems() {
|
||||
const items = new ItemList<Mithril.Children>();
|
||||
|
||||
const { updates, isCore } = this.attrs;
|
||||
const latestVersion = updates ? updates['latest-minor'] ?? (updates['latest-major'] && !isCore ? updates['latest-major'] : null) : null;
|
||||
|
||||
if (this.attrs.extension instanceof ExternalExtension) {
|
||||
items.add(
|
||||
'downloads',
|
||||
<span>
|
||||
<Icon name="fas fa-circle-down" />
|
||||
{app.translator.trans('flarum-extension-manager.admin.sections.discover.extension.downloads', {
|
||||
count: this.attrs.extension.downloads(),
|
||||
formattedCount: formatAmount(this.attrs.extension.downloads()),
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
items.add(
|
||||
'version',
|
||||
<div className="ExtensionCard-version">
|
||||
<span className="ExtensionCard-version-current">{this.version(updates!['version'])}</span>
|
||||
{latestVersion ? (
|
||||
<>
|
||||
<Icon name="fas fa-arrow-right" />
|
||||
<Label className="ExtensionCard-version-latest" type={updates!['latest-minor'] ? 'success' : 'warning'}>
|
||||
{this.version(latestVersion)}
|
||||
</Label>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.attrs.extension instanceof ExternalExtension) {
|
||||
items.add('version', <div className="ExtensionCard-version">v{this.version(this.attrs.extension.highestVersion())}</div>);
|
||||
|
||||
items.add(
|
||||
'link',
|
||||
<LinkButton
|
||||
className="Button Button--ua-reset Button--link Button--icon"
|
||||
href={this.attrs.extension.httpUri()}
|
||||
target="_blank"
|
||||
icon="fas fa-external-link-alt"
|
||||
external={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
actionItems() {
|
||||
const items = new ItemList<Mithril.Children>();
|
||||
|
||||
const { updates, extension, onClickUpdate, whyNotWarning } = this.attrs;
|
||||
|
||||
if (extension instanceof ExternalExtension) {
|
||||
if (!(extension.extensionId() in app.data.extensions)) {
|
||||
items.add(
|
||||
'install',
|
||||
<Button
|
||||
className="Button Button--icon Button--flat"
|
||||
icon="fas fa-cloud-arrow-down"
|
||||
onclick={() => {
|
||||
app.extensionManager.control.requirePackage({ package: extension.name() });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
items.add('installed', <Button className="Button Button--icon Button--flat Button--success" icon="fas fa-check-circle" disabled={true} />);
|
||||
}
|
||||
} else {
|
||||
if (onClickUpdate && typeof onClickUpdate === 'function') {
|
||||
items.add(
|
||||
'update',
|
||||
<Tooltip text={app.translator.trans('flarum-extension-manager.admin.extensions.update')}>
|
||||
<Button
|
||||
icon="fas fa-cloud-arrow-down"
|
||||
className="Button Button--icon Button--flat"
|
||||
onclick={onClickUpdate}
|
||||
aria-label={app.translator.trans('flarum-extension-manager.admin.extensions.update')}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
} else if (onClickUpdate) {
|
||||
items.add(
|
||||
'update',
|
||||
<Dropdown
|
||||
buttonClassName="Button Button--icon Button--flat"
|
||||
icon="fas fa-ellipsis"
|
||||
label={app.translator.trans('flarum-extension-manager.admin.extensions.update')}
|
||||
>
|
||||
<Button icon="fas fa-cloud-arrow-down" onclick={onClickUpdate.soft}>
|
||||
{app.translator.trans('flarum-extension-manager.admin.extensions.update_soft_label')}
|
||||
</Button>
|
||||
<Button icon="fas fa-rotate" onclick={onClickUpdate.hard} disabled={!updates!['direct-dependency']}>
|
||||
{app.translator.trans('flarum-extension-manager.admin.extensions.update_hard_label')}
|
||||
</Button>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
if (whyNotWarning)
|
||||
items.add(
|
||||
'whyNot',
|
||||
<Tooltip text={app.translator.trans('flarum-extension-manager.admin.extensions.check_why_it_failed_updating')}>
|
||||
<Button
|
||||
icon="fas fa-exclamation-circle"
|
||||
className="Button Button--icon Button--flat Button--danger"
|
||||
onclick={() => app.modal.show(WhyNotModal, { package: extension.name })}
|
||||
aria-label={app.translator.trans('flarum-extension-manager.admin.extensions.check_why_it_failed_updating')}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
version(v: string): string {
|
||||
return v.charAt(0) === 'v' ? v.substring(1) : v;
|
||||
}
|
||||
}
|
@@ -1,99 +0,0 @@
|
||||
import type Mithril from 'mithril';
|
||||
import app from 'flarum/admin/app';
|
||||
import Component, { ComponentAttrs } from 'flarum/common/Component';
|
||||
import classList from 'flarum/common/utils/classList';
|
||||
import Icon from 'flarum/common/components/Icon';
|
||||
import Tooltip from 'flarum/common/components/Tooltip';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import { Extension } from 'flarum/admin/AdminApplication';
|
||||
|
||||
import { UpdatedPackage } from '../states/ControlSectionState';
|
||||
import WhyNotModal from './WhyNotModal';
|
||||
import Label from './Label';
|
||||
import Dropdown from 'flarum/common/components/Dropdown';
|
||||
|
||||
export interface ExtensionItemAttrs extends ComponentAttrs {
|
||||
extension: Extension;
|
||||
updates: UpdatedPackage;
|
||||
onClickUpdate:
|
||||
| CallableFunction
|
||||
| {
|
||||
soft: CallableFunction;
|
||||
hard: CallableFunction;
|
||||
};
|
||||
whyNotWarning?: boolean;
|
||||
isCore?: boolean;
|
||||
updatable?: boolean;
|
||||
isDanger?: boolean;
|
||||
}
|
||||
|
||||
export default class ExtensionItem<Attrs extends ExtensionItemAttrs = ExtensionItemAttrs> extends Component<Attrs> {
|
||||
view(vnode: Mithril.Vnode<Attrs, this>): Mithril.Children {
|
||||
const { extension, updates, onClickUpdate, whyNotWarning, isCore, isDanger } = this.attrs;
|
||||
const latestVersion = updates['latest-minor'] ?? (updates['latest-major'] && !isCore ? updates['latest-major'] : null);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classList({
|
||||
'ExtensionManager-extension': true,
|
||||
'ExtensionManager-extension--core': isCore,
|
||||
'ExtensionManager-extension--danger': isDanger,
|
||||
})}
|
||||
>
|
||||
<div className="ExtensionManager-extension-icon ExtensionIcon" style={extension.icon}>
|
||||
{extension.icon ? <Icon name={extension.icon.name} /> : ''}
|
||||
</div>
|
||||
<div className="ExtensionManager-extension-info">
|
||||
<div className="ExtensionManager-extension-name">{extension.extra['flarum-extension'].title}</div>
|
||||
<div className="ExtensionManager-extension-version">
|
||||
<span className="ExtensionManager-extension-version-current">{this.version(updates['version'])}</span>
|
||||
{latestVersion ? (
|
||||
<Label className="ExtensionManager-extension-version-latest" type={updates['latest-minor'] ? 'success' : 'warning'}>
|
||||
{this.version(latestVersion)}
|
||||
</Label>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ExtensionManager-extension-controls">
|
||||
{onClickUpdate && typeof onClickUpdate === 'function' ? (
|
||||
<Tooltip text={app.translator.trans('flarum-extension-manager.admin.extensions.update')}>
|
||||
<Button
|
||||
icon="fas fa-arrow-alt-circle-up"
|
||||
className="Button Button--icon Button--flat"
|
||||
onclick={onClickUpdate}
|
||||
aria-label={app.translator.trans('flarum-extension-manager.admin.extensions.update')}
|
||||
/>
|
||||
</Tooltip>
|
||||
) : onClickUpdate ? (
|
||||
<Dropdown
|
||||
buttonClassName="Button Button--icon Button--flat"
|
||||
icon="fas fa-arrow-alt-circle-up"
|
||||
label={app.translator.trans('flarum-extension-manager.admin.extensions.update')}
|
||||
>
|
||||
<Button icon="fas fa-arrow-alt-circle-up" className="Button" onclick={onClickUpdate.soft}>
|
||||
{app.translator.trans('flarum-extension-manager.admin.extensions.update_soft_label')}
|
||||
</Button>
|
||||
<Button icon="fas fa-arrow-alt-circle-up" className="Button" onclick={onClickUpdate.hard} disabled={!updates['direct-dependency']}>
|
||||
{app.translator.trans('flarum-extension-manager.admin.extensions.update_hard_label')}
|
||||
</Button>
|
||||
</Dropdown>
|
||||
) : null}
|
||||
{whyNotWarning ? (
|
||||
<Tooltip text={app.translator.trans('flarum-extension-manager.admin.extensions.check_why_it_failed_updating')}>
|
||||
<Button
|
||||
icon="fas fa-exclamation-circle"
|
||||
className="Button Button--icon Button--flat Button--danger"
|
||||
onclick={() => app.modal.show(WhyNotModal, { package: extension.name })}
|
||||
aria-label={app.translator.trans('flarum-extension-manager.admin.extensions.check_why_it_failed_updating')}
|
||||
/>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
version(v: string): string {
|
||||
return v.charAt(0) === 'v' ? v.substring(1) : v;
|
||||
}
|
||||
}
|
@@ -23,7 +23,7 @@ export default class Installer extends Component<InstallerAttrs> {
|
||||
<label htmlFor="install-extension">{app.translator.trans('flarum-extension-manager.admin.extensions.install')}</label>
|
||||
<div className="helpText">
|
||||
{app.translator.trans('flarum-extension-manager.admin.extensions.install_help', {
|
||||
extiverse: <a href="https://extiverse.com">extiverse.com</a>,
|
||||
link: <a href="https://flarum.org/extensions">flarum.org</a>,
|
||||
semantic_link: <a href="https://devhints.io/semver" />,
|
||||
code: <code />,
|
||||
})}
|
||||
|
@@ -7,7 +7,7 @@ import Alert from 'flarum/common/components/Alert';
|
||||
|
||||
import { UpdatedPackage, UpdateState } from '../states/ControlSectionState';
|
||||
import WhyNotModal from './WhyNotModal';
|
||||
import ExtensionItem from './ExtensionItem';
|
||||
import ExtensionCard from './ExtensionCard';
|
||||
import classList from 'flarum/common/utils/classList';
|
||||
|
||||
export interface MajorUpdaterAttrs extends ComponentAttrs {
|
||||
@@ -27,7 +27,6 @@ export default class MajorUpdater<T extends MajorUpdaterAttrs = MajorUpdaterAttr
|
||||
}
|
||||
|
||||
view(): Mithril.Children {
|
||||
// @todo move Form-group--danger class to core for reuse
|
||||
return (
|
||||
<div
|
||||
className={classList('Form-group Form-group--danger ExtensionManager-majorUpdate', {
|
||||
@@ -63,7 +62,7 @@ export default class MajorUpdater<T extends MajorUpdaterAttrs = MajorUpdaterAttr
|
||||
{this.updateState.incompatibleExtensions.length ? (
|
||||
<div className="ExtensionManager-majorUpdate-incompatibleExtensions ExtensionManager-extensions-grid">
|
||||
{this.updateState.incompatibleExtensions.map((extension: string) => (
|
||||
<ExtensionItem
|
||||
<ExtensionCard
|
||||
extension={app.data.extensions[extension.replace('flarum-', '').replace('flarum-ext-', '').replace('/', '-')]}
|
||||
updates={{}}
|
||||
onClickUpdate={null}
|
||||
|
@@ -1,40 +0,0 @@
|
||||
import app from 'flarum/admin/app';
|
||||
import Component, { ComponentAttrs } from 'flarum/common/Component';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import QueueState from '../states/QueueState';
|
||||
|
||||
interface PaginationAttrs extends ComponentAttrs {
|
||||
list: QueueState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo make it abstract in core for reusability.
|
||||
*/
|
||||
export default class Pagination extends Component<PaginationAttrs> {
|
||||
view() {
|
||||
return (
|
||||
<nav className="Pagination UserListPage-gridPagination">
|
||||
<Button
|
||||
disabled={!this.attrs.list.hasPrev() || app.extensionManager.control.isLoading()}
|
||||
title={app.translator.trans('core.admin.users.pagination.back_button')}
|
||||
onclick={() => this.attrs.list.prev()}
|
||||
icon="fas fa-chevron-left"
|
||||
className="Button Button--icon UserListPage-backBtn"
|
||||
/>
|
||||
<span className="UserListPage-pageNumber">
|
||||
{app.translator.trans('core.admin.users.pagination.page_counter', {
|
||||
current: this.attrs.list.pageNumber() + 1,
|
||||
total: this.attrs.list.getTotalPages(),
|
||||
})}
|
||||
</span>
|
||||
<Button
|
||||
disabled={!this.attrs.list.hasNext() || app.extensionManager.control.isLoading()}
|
||||
title={app.translator.trans('core.admin.users.pagination.next_button')}
|
||||
onclick={() => this.attrs.list.next()}
|
||||
icon="fas fa-chevron-right"
|
||||
className="Button Button--icon UserListPage-nextBtn"
|
||||
/>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
}
|
@@ -9,12 +9,13 @@ import Icon from 'flarum/common/components/Icon';
|
||||
import ItemList from 'flarum/common/utils/ItemList';
|
||||
import extractText from 'flarum/common/utils/extractText';
|
||||
import Link from 'flarum/common/components/Link';
|
||||
import Pagination from 'flarum/common/components/Pagination';
|
||||
import classList from 'flarum/common/utils/classList';
|
||||
|
||||
import Label from './Label';
|
||||
import TaskOutputModal from './TaskOutputModal';
|
||||
import humanDuration from '../utils/humanDuration';
|
||||
import Task, { TaskOperations } from '../models/Task';
|
||||
import Pagination from './Pagination';
|
||||
|
||||
interface QueueTableColumn extends ComponentAttrs {
|
||||
label: string;
|
||||
@@ -30,7 +31,7 @@ export default class QueueSection extends Component<{}> {
|
||||
|
||||
view() {
|
||||
return (
|
||||
<section id="ExtensionManager-queueSection" className="ExtensionPage-permissions ExtensionManager-queueSection">
|
||||
<section id="ExtensionManager-queueSection" className="ExtensionPage-settings ExtensionManager-queueSection">
|
||||
<div className="ExtensionPage-permissions-header ExtensionManager-queueSection-header">
|
||||
<div className="container">
|
||||
<h2 className="ExtensionTitle">{app.translator.trans('flarum-extension-manager.admin.sections.queue.title')}</h2>
|
||||
@@ -174,32 +175,43 @@ export default class QueueSection extends Component<{}> {
|
||||
|
||||
return (
|
||||
<>
|
||||
<table className="Table ExtensionManager-queueTable">
|
||||
<thead>
|
||||
<tr>
|
||||
{columns.toArray().map((item, index) => (
|
||||
<th key={index}>{item.label}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tasks.map((task, index) => (
|
||||
<tr key={index}>
|
||||
{columns.toArray().map((item, index) => {
|
||||
const { label, content, ...attrs } = item;
|
||||
|
||||
return (
|
||||
<td key={index} {...attrs}>
|
||||
{content(task)}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
<div
|
||||
className={classList('Table-container', {
|
||||
'loading-container': tasks && app.extensionManager.queue.isLoading(),
|
||||
})}
|
||||
>
|
||||
<table className="Table ExtensionManager-queueTable">
|
||||
<thead>
|
||||
<tr>
|
||||
{columns.toArray().map((item, index) => (
|
||||
<th key={index}>{item.label}</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tasks.map((task, index) => (
|
||||
<tr key={index}>
|
||||
{columns.toArray().map((item, index) => {
|
||||
const { label, content, ...attrs } = item;
|
||||
|
||||
<Pagination list={app.extensionManager.queue} />
|
||||
return (
|
||||
<td key={index} {...attrs}>
|
||||
{content(task)}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{tasks && app.extensionManager.queue.isLoading() && <LoadingIndicator size="large" />}
|
||||
</div>
|
||||
<Pagination
|
||||
total={app.extensionManager.queue.getTotalItems()}
|
||||
currentPage={app.extensionManager.queue.pageNumber() + 1}
|
||||
perPage={app.extensionManager.queue.getPerPage()}
|
||||
onChange={(page: number) => app.extensionManager.queue.goto(page)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@@ -6,36 +6,32 @@ import ItemList from 'flarum/common/utils/ItemList';
|
||||
import QueueSection from './QueueSection';
|
||||
import ControlSection from './ControlSection';
|
||||
import ConfigureComposer from './ConfigureComposer';
|
||||
import Alert from 'flarum/common/components/Alert';
|
||||
import listItems from 'flarum/common/helpers/listItems';
|
||||
import ConfigureAuth from './ConfigureAuth';
|
||||
import DiscoverSection from './DiscoverSection';
|
||||
|
||||
export default class SettingsPage extends ExtensionPage {
|
||||
content() {
|
||||
const settings = app.registry.getSettings(this.extension.id);
|
||||
|
||||
const warnings = [app.translator.trans('flarum-extension-manager.admin.settings.access_warning')];
|
||||
|
||||
if (app.data.debugEnabled) warnings.push(app.translator.trans('flarum-extension-manager.admin.settings.debug_mode_warning'));
|
||||
|
||||
return (
|
||||
<div className="ExtensionPage-settings">
|
||||
<div className="container">
|
||||
<div className="ExtensionManager-warnings Form-group">
|
||||
<Alert className="ExtensionManager-primaryWarning" type="warning" dismissible={false}>
|
||||
<ul>{listItems(warnings)}</ul>
|
||||
</Alert>
|
||||
</div>
|
||||
{settings ? (
|
||||
<div className="FormSectionGroup ExtensionManager-SettingsGroups">
|
||||
<div className="FormSection">
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.settings.title')}</label>
|
||||
<div className="Form">{settings.map(this.buildSettingComponent.bind(this))}</div>
|
||||
<div className="Form-group Form--controls">{this.submitButton()}</div>
|
||||
</div>
|
||||
<ConfigureComposer buildSettingComponent={this.buildSettingComponent} />
|
||||
<ConfigureAuth buildSettingComponent={this.buildSettingComponent} />
|
||||
</div>
|
||||
[
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.sections.settings.title')}</label>
|
||||
<div className="helpText">{app.translator.trans('flarum-extension-manager.admin.sections.settings.description')}</div>
|
||||
</div>,
|
||||
<div className="FormSectionGroup ExtensionManager-SettingsGroups">
|
||||
<div className="FormSection">
|
||||
<label>{app.translator.trans('flarum-extension-manager.admin.settings.title')}</label>
|
||||
<div className="Form">{settings.map(this.buildSettingComponent.bind(this))}</div>
|
||||
<div className="Form-group Form--controls">{this.submitButton()}</div>
|
||||
</div>
|
||||
<ConfigureComposer buildSettingComponent={this.buildSettingComponent} />
|
||||
<ConfigureAuth buildSettingComponent={this.buildSettingComponent} />
|
||||
</div>,
|
||||
]
|
||||
) : (
|
||||
<h3 className="ExtensionPage-subHeader">{app.translator.trans('core.admin.extension.no_settings')}</h3>
|
||||
)}
|
||||
@@ -47,9 +43,11 @@ export default class SettingsPage extends ExtensionPage {
|
||||
sections(vnode: Mithril.VnodeDOM<ExtensionPageAttrs, this>): ItemList<unknown> {
|
||||
const items = super.sections(vnode);
|
||||
|
||||
items.setPriority('content', 10);
|
||||
items.add('discover', <DiscoverSection />, 15);
|
||||
|
||||
items.add('control', <ControlSection />, 8);
|
||||
items.add('control', <ControlSection />, 10);
|
||||
|
||||
items.setPriority('content', 8);
|
||||
|
||||
if (app.data.settings['flarum-extension-manager.queue_jobs'] !== '0' && app.data.settings['flarum-extension-manager.queue_jobs']) {
|
||||
items.add('queue', <QueueSection />, 5);
|
||||
|
@@ -4,9 +4,9 @@ import Button from 'flarum/common/components/Button';
|
||||
import humanTime from 'flarum/common/helpers/humanTime';
|
||||
import LoadingIndicator from 'flarum/common/components/LoadingIndicator';
|
||||
import MajorUpdater from './MajorUpdater';
|
||||
import ExtensionItem from './ExtensionItem';
|
||||
import { Extension } from 'flarum/admin/AdminApplication';
|
||||
import ItemList from 'flarum/common/utils/ItemList';
|
||||
import InfoTile from 'flarum/common/components/InfoTile';
|
||||
import ExtensionCard from './ExtensionCard';
|
||||
|
||||
export interface IUpdaterAttrs extends ComponentAttrs {}
|
||||
|
||||
@@ -59,8 +59,8 @@ export default class Updater extends Component<IUpdaterAttrs> {
|
||||
|
||||
if (!(state.extensionUpdates.length || hasMinorCoreUpdate)) {
|
||||
return (
|
||||
<div className="ExtensionManager-extensions">
|
||||
<span className="helpText">{app.translator.trans('flarum-extension-manager.admin.updater.up_to_date')}</span>
|
||||
<div className="ExtensionManager-extensions ExtensionManager-extensions--empty">
|
||||
<InfoTile icon="fas fa-plug-circle-check">{app.translator.trans('flarum-extension-manager.admin.updater.up_to_date')}</InfoTile>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -69,7 +69,7 @@ export default class Updater extends Component<IUpdaterAttrs> {
|
||||
<div className="ExtensionManager-extensions">
|
||||
<div className="ExtensionManager-extensions-grid">
|
||||
{hasMinorCoreUpdate ? (
|
||||
<ExtensionItem
|
||||
<ExtensionCard
|
||||
extension={state.coreUpdate!.extension}
|
||||
updates={state.coreUpdate!.package}
|
||||
isCore={true}
|
||||
@@ -77,8 +77,8 @@ export default class Updater extends Component<IUpdaterAttrs> {
|
||||
whyNotWarning={state.lastUpdateRun.limitedPackages().includes('flarum/core')}
|
||||
/>
|
||||
) : null}
|
||||
{state.extensionUpdates.map((extension: Extension) => (
|
||||
<ExtensionItem
|
||||
{state.extensionUpdates.map((extension) => (
|
||||
<ExtensionCard
|
||||
extension={extension}
|
||||
updates={state.packageUpdates[extension.id]}
|
||||
onClickUpdate={{
|
||||
|
@@ -2,8 +2,14 @@ import Extend from 'flarum/common/extenders';
|
||||
import app from 'flarum/admin/app';
|
||||
import extractText from 'flarum/common/utils/extractText';
|
||||
import SettingsPage from './components/SettingsPage';
|
||||
import Task from './models/Task';
|
||||
import ExternalExtension from './models/ExternalExtension';
|
||||
|
||||
export default [
|
||||
new Extend.Store() //
|
||||
.add('extension-manager-tasks', Task)
|
||||
.add('external-extensions', ExternalExtension),
|
||||
|
||||
new Extend.Admin()
|
||||
.setting(() => ({
|
||||
setting: 'flarum-extension-manager.queue_jobs',
|
||||
|
@@ -4,7 +4,6 @@ import ExtensionPage from 'flarum/admin/components/ExtensionPage';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import LoadingModal from 'flarum/admin/components/LoadingModal';
|
||||
import isExtensionEnabled from 'flarum/admin/utils/isExtensionEnabled';
|
||||
import Task from './models/Task';
|
||||
import jumpToQueue from './utils/jumpToQueue';
|
||||
import { AsyncBackendResponse } from './shims';
|
||||
import ExtensionManagerState from './states/ExtensionManagerState';
|
||||
@@ -12,8 +11,6 @@ import ExtensionManagerState from './states/ExtensionManagerState';
|
||||
export { default as extend } from './extend';
|
||||
|
||||
app.initializers.add('flarum-extension-manager', (app) => {
|
||||
app.store.models['extension-manager-tasks'] = Task;
|
||||
|
||||
app.extensionManager = new ExtensionManagerState();
|
||||
|
||||
if (app.data['flarum-extension-manager.using_sync_queue']) {
|
||||
|
@@ -0,0 +1,73 @@
|
||||
import Model from 'flarum/common/Model';
|
||||
import app from 'flarum/admin/app';
|
||||
import type { Extension } from 'flarum/admin/AdminApplication';
|
||||
|
||||
export default class ExternalExtension extends Model {
|
||||
extensionId = Model.attribute<string>('extensionId');
|
||||
name = Model.attribute<string>('name');
|
||||
title = Model.attribute<string>('title');
|
||||
description = Model.attribute<string>('description');
|
||||
iconUrl = Model.attribute<string>('iconUrl');
|
||||
icon = Model.attribute<{
|
||||
name: string;
|
||||
[key: string]: string;
|
||||
}>('icon');
|
||||
highestVersion = Model.attribute<string>('highestVersion');
|
||||
httpUri = Model.attribute<string>('httpUri');
|
||||
discussUri = Model.attribute<string>('discussUri');
|
||||
vendor = Model.attribute<string>('vendor');
|
||||
isPremium = Model.attribute<boolean>('isPremium');
|
||||
isLocale = Model.attribute<boolean>('isLocale');
|
||||
locale = Model.attribute<string>('locale');
|
||||
latestFlarumVersionSupported = Model.attribute<string>('latestFlarumVersionSupported');
|
||||
downloads = Model.attribute<number>('downloads');
|
||||
readonly installed = false;
|
||||
|
||||
public isSupported(): boolean {
|
||||
const currentVersion = app.data.settings.version;
|
||||
const latestCompatibleVersion = this.latestFlarumVersionSupported();
|
||||
|
||||
// If stability is not the same, it's not compatible.
|
||||
if (currentVersion.split('-')[1] !== latestCompatibleVersion.split('-')[1]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Minor versions are compatible.
|
||||
return currentVersion.split('.')[0] === latestCompatibleVersion.split('.')[0];
|
||||
}
|
||||
|
||||
public isStable(): boolean {
|
||||
const split = this.highestVersion().split('-');
|
||||
|
||||
if (split.length === 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const stability = split[1].split('.');
|
||||
|
||||
return stability[0] === 'stable';
|
||||
}
|
||||
|
||||
public toLocalExtension(): Extension {
|
||||
return {
|
||||
id: this.extensionId(),
|
||||
name: this.name(),
|
||||
version: this.highestVersion(),
|
||||
description: this.description(),
|
||||
icon: this.icon() || {
|
||||
name: 'fas fa-box-open',
|
||||
backgroundColor: '#117187',
|
||||
color: '#fff',
|
||||
},
|
||||
links: {
|
||||
discuss: this.discussUri(),
|
||||
website: this.httpUri(),
|
||||
},
|
||||
extra: {
|
||||
'flarum-extension': {
|
||||
title: this.title(),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
import app from 'flarum/admin/app';
|
||||
import PaginatedListState, { SortMap } from 'flarum/common/states/PaginatedListState';
|
||||
import ExternalExtension from '../models/ExternalExtension';
|
||||
|
||||
export default class ExtensionListState extends PaginatedListState<ExternalExtension> {
|
||||
get type(): string {
|
||||
return 'external-extensions';
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(
|
||||
{
|
||||
sort: '-downloads',
|
||||
},
|
||||
1,
|
||||
12
|
||||
);
|
||||
}
|
||||
|
||||
sortMap(): SortMap {
|
||||
return {
|
||||
'-createdAt': {
|
||||
sort: '-createdAt',
|
||||
label: app.translator.trans('flarum-extension-manager.admin.sections.discover.sort.latest', {}, true),
|
||||
},
|
||||
'-downloads': {
|
||||
sort: '-downloads',
|
||||
label: app.translator.trans('flarum-extension-manager.admin.sections.discover.sort.top', {}, true),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
@@ -1,7 +1,9 @@
|
||||
import QueueState from './QueueState';
|
||||
import ControlSectionState from './ControlSectionState';
|
||||
import ExtensionListState from './ExtensionListState';
|
||||
|
||||
export default class ExtensionManagerState {
|
||||
public queue: QueueState = new QueueState();
|
||||
public control: ControlSectionState = new ControlSectionState();
|
||||
public extensions: ExtensionListState = new ExtensionListState();
|
||||
}
|
||||
|
@@ -8,9 +8,10 @@ export default class QueueState {
|
||||
private limit = 20;
|
||||
private offset = 0;
|
||||
private total = 0;
|
||||
private loading = false;
|
||||
|
||||
load(params?: ApiQueryParamsPlural, actionTaken = false): Promise<Task[]> {
|
||||
this.tasks = null;
|
||||
this.loading = true;
|
||||
params = {
|
||||
page: {
|
||||
limit: this.limit,
|
||||
@@ -22,7 +23,7 @@ export default class QueueState {
|
||||
|
||||
return app.store.find<Task[]>('extension-manager-tasks', params || {}).then((data) => {
|
||||
this.tasks = data;
|
||||
this.total = data.payload.meta?.total || 0;
|
||||
this.total = data.payload.meta?.page?.total || 0;
|
||||
|
||||
m.redraw();
|
||||
|
||||
@@ -40,14 +41,24 @@ export default class QueueState {
|
||||
app.extensionManager.control.setLoading(null);
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
isLoading() {
|
||||
return this.loading;
|
||||
}
|
||||
|
||||
getItems() {
|
||||
return this.tasks;
|
||||
}
|
||||
|
||||
getTotalItems() {
|
||||
return this.total;
|
||||
}
|
||||
|
||||
getTotalPages(): number {
|
||||
return Math.ceil(this.total / this.limit);
|
||||
}
|
||||
@@ -56,6 +67,10 @@ export default class QueueState {
|
||||
return Math.ceil(this.offset / this.limit);
|
||||
}
|
||||
|
||||
getPerPage() {
|
||||
return this.limit;
|
||||
}
|
||||
|
||||
hasPrev(): boolean {
|
||||
return this.pageNumber() !== 0;
|
||||
}
|
||||
@@ -78,6 +93,11 @@ export default class QueueState {
|
||||
}
|
||||
}
|
||||
|
||||
goto(page: number): void {
|
||||
this.offset = (page - 1) * this.limit;
|
||||
this.load();
|
||||
}
|
||||
|
||||
pollQueue(actionTaken = false): void {
|
||||
if (this.polling) {
|
||||
clearTimeout(this.polling);
|
||||
|
Reference in New Issue
Block a user