plugins: exports.isTheme

Massimo Melina committed May 16, 2024 at 11:08 UTC 0a46f1f7f1ce03f038b619f63b9d7129c03f76dd
4 files changed +31 -7
admin/src/InstalledPlugins.ts
+19 -3
@@ -4,7 +4,7 @@ import { apiCall, useApiEx, useApiList } from './api'
4 import { createElement as h, Fragment, useEffect } from 'react'
5 import { Box, Link } from '@mui/material'
6 import { DataTable } from './DataTable'
7 -import { Delete, Error as ErrorIcon, PlayCircle, Settings, StopCircle, Upgrade } from '@mui/icons-material'
7 +import { Delete, Error as ErrorIcon, FormatPaint as ThemeIcon, PlayCircle, Settings, StopCircle, Upgrade } from '@mui/icons-material'
8 import { HTTP_FAILED_DEPENDENCY, prefix, with_, xlate } from './misc'
9 import { alertDialog, formDialog, toast } from './dialog'
10 import _ from 'lodash'
@@ -44,8 +44,9 @@ export default function InstalledPlugins({ updates }: { updates?: true }) {
44 width: 70,
45 hideUnder: 'sm',
46 },
47 + themeField,
48 {
48 - field: 'description',
49 + ...descriptionField,
50 flex: 1,
51 hideUnder: 'sm',
52 },
@@ -191,4 +192,19 @@ function UsernameField({ value, onChange, ...rest }: FieldProps<string>) {
192 helperText: "Only users, no groups here",
193 ...rest,
194 })
194 -}
\ No newline at end of file
195 +}
196 +
197 +export const descriptionField = {
198 + field: 'description',
199 + mergeRender: { other: 'isTheme', sx: { float: 'left' } },
200 +}
201 +
202 +export const themeField = {
203 + field: 'isTheme',
204 + headerName: "theme",
205 + hidden: true,
206 + type: 'boolean',
207 + renderCell({ value }: any) {
208 + return value ? h(ThemeIcon, { fontSize: 'small', sx: { mr: '.3em' } }) : null
209 + }
210 +} as const
\ No newline at end of file
admin/src/OnlinePlugins.ts
+3 -2
@@ -7,7 +7,7 @@ import { HTTP_FAILED_DEPENDENCY, xlate } from './misc'
7 import { Download, Search } from '@mui/icons-material'
8 import { StringField } from '@hfs/mui-grid-form'
9 import { useDebounce } from 'usehooks-ts'
10 -import { renderName, startPlugin } from './InstalledPlugins'
10 +import { descriptionField, renderName, startPlugin, themeField } from './InstalledPlugins'
11 import { state, useSnapState } from './state'
12 import { alertDialog, confirmDialog, toast } from './dialog'
13 import _ from 'lodash'
@@ -55,8 +55,9 @@ export default function OnlinePlugins() {
55 field: 'license',
56 width: 80,
57 },
58 + themeField,
59 {
59 - field: 'description',
60 + ...descriptionField,
61 flex: 3,
62 hideUnder: 'sm',
63 },
dev-plugins.md
+4 -2
@@ -46,6 +46,8 @@ All the following properties are optional unless otherwise specified.
46 - `description: string` try to explain what this plugin is for. (JSON syntax)
47 - `version: number` use progressive numbers to distinguish each release
48 - `apiRequired: number | [min:number,max:number]` declare version(s) for which the plugin is designed for. Mandatory. [Refer to API version history](#api-version-history)
49 +- `isTheme: boolean` set true if this is a theme that's not supposed to work together with other themes.
50 + Running a theme will cause other themes to be stopped. Missing this, HFS will check if the name of the plugin ends with `-theme`.
51 - `depend: { repo: string, version: number }[]` declare what other plugins this depends on. (JSON syntax)
52 - `repo: string | object` pointer to a GitHub repo where this plugin is hosted. (JSON syntax)
53 - the string form is for GitHub repos. Example: "rejetto/file-icons"
@@ -153,9 +155,9 @@ Based on `type`, other properties are supported:
155
156 The `api` object you get as parameter of the `init` contains the following:
157
156 -- `getConfig(key: string): any` get config's value set up by using `exports.config`.
158 +- `getConfig(key: string): any` get plugin's config value, described in `exports.config`.
159
158 -- `setConfig(key: string, value: any)` set config's value set up by using `exports.config`.
160 +- `setConfig(key: string, value: any)` set plugin's config value.
161
162 - `subscribeConfig(key: string, callback: (value: any) => void): Unsubscriber`
163 will call `callback` with initial value and then at each change.
src/plugins.ts
+5
@@ -54,6 +54,8 @@ export async function stopPlugin(id: string) {
54 }
55
56 export async function startPlugin(id: string) {
57 + if (getPluginInfo(id)?.isTheme)
58 + await Promise.all(mapPlugins((pl, id) => pl.isTheme && stopPlugin(id)))
59 enablePlugin(id)
60 await waitRunning(id)
61 }
@@ -180,6 +182,7 @@ export class Plugin implements CommonPluginInterface {
182 get version(): undefined | number { return this.data?.version }
183 get description(): undefined | string { return this.data?.description }
184 get apiRequired(): undefined | number | [number,number] { return this.data?.apiRequired }
185 + get isTheme(): undefined | boolean { return this.data?.isTheme }
186 get repo(): undefined | Repo { return this.data?.repo }
187 get depend(): undefined | Depend { return this.data?.depend }
188
@@ -258,6 +261,7 @@ export interface CommonPluginInterface {
261 apiRequired?: number | [number,number]
262 repo?: Repo
263 depend?: Depend
264 + isTheme?: boolean
265 }
266 export interface AvailablePlugin extends CommonPluginInterface {
267 branch?: string
@@ -481,6 +485,7 @@ export function parsePluginSource(id: string, source: string) {
485 pl.repo = tryJson(/exports.repo *= *(.*);? *$/m.exec(source)?.[1])
486 pl.version = Number(/exports.version *= *(\d*\.?\d+)/.exec(source)?.[1]) ?? undefined
487 pl.apiRequired = tryJson(/exports.apiRequired *= *([ \d.,[\]]+)/.exec(source)?.[1]) ?? undefined
488 + pl.isTheme = tryJson(/exports.isTheme *= *(true|false)/.exec(source)?.[1]) ?? (id.endsWith('-theme') || undefined)
489 pl.depend = tryJson(/exports.depend *= *(\[.*\])/m.exec(source)?.[1])?.filter((x: any) =>
490 typeof x.repo === 'string' && x.version === undefined || typeof x.version === 'number'
491 || console.warn("plugin dependency discarded", x) )