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) )