plugins: exports.isTheme "light" and "dark"
Massimo Melina committed
May 16, 2024 at 11:24 UTC
0b53e0e30e896ca95f5fa8d746c9c08b9676d030
7 files changed
+23
-19
admin/src/DataTable.ts
+6
-5
@@ -9,12 +9,13 @@ import { Center, Flex, useBreakpoint } from './mui'
9
10
const ACTIONS = 'Actions'
11
12
+export type DataTableColumn<R extends GridValidRowModel=any> = GridColDef<R> & {
13
+ hidden?: boolean
14
+ hideUnder?: Breakpoint | number
15
+ mergeRender?: { other: string, override?: Partial<GridColDef<R>> } & BoxProps
16
+}
17
interface DataTableProps<R extends GridValidRowModel=any> extends Omit<DataGridProps<R>, 'columns'> {
13
- columns: Array<GridColDef<R> & {
14
- hidden?: boolean
15
- hideUnder?: Breakpoint | number
16
- mergeRender?: { other: string, override?: Partial<GridColDef<R>> } & BoxProps
17
- }>
18
+ columns: Array<DataTableColumn<R>>
19
actions?: ({ row, id }: any) => ReactNode[]
20
actionsProps?: Partial<GridColDef<R>> & { hideUnder?: Breakpoint | number }
21
initializing?: boolean
admin/src/InstalledPlugins.ts
+7
-7
@@ -3,7 +3,7 @@
3
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'
6
+import { DataTable, DataTableColumn } from './DataTable'
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'
@@ -14,7 +14,7 @@ import { BoolField, Field, FieldProps, MultiSelectField, NumberField, SelectFiel
14
import { ArrayField } from './ArrayField'
15
import FileField from './FileField'
16
import { PLUGIN_ERRORS } from './PluginsPage'
17
-import { Btn, hTooltip, IconBtn } from './mui'
17
+import { Btn, hTooltip, IconBtn, iconTooltip } from './mui'
18
19
export default function InstalledPlugins({ updates }: { updates?: true }) {
20
const { list, updateEntry, error, updateList, initializing } = useApiList(updates ? 'get_plugin_updates' : 'get_plugins')
@@ -194,17 +194,17 @@ function UsernameField({ value, onChange, ...rest }: FieldProps<string>) {
194
})
195
}
196
197
-export const descriptionField = {
197
+export const descriptionField: DataTableColumn = {
198
field: 'description',
199
mergeRender: { other: 'isTheme', sx: { float: 'left' } },
200
}
201
202
-export const themeField = {
202
+export const themeField: DataTableColumn = {
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
207
+ renderCell({ value }) {
208
+ return value && iconTooltip(ThemeIcon, _.isString(value) ? `${value} theme` : "theme", { fontSize: '1.2rem', mr: '.3em' })
209
}
210
-} as const
\ No newline at end of file
210
+}
\ No newline at end of file
dev-plugins.md
+3
-2
@@ -46,8 +46,9 @@ 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`.
49
+- `isTheme: boolean | "light" | "dark"` 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
+ Special values "light" and "dark" to declare the theme is (for example) dark and forces HFS to use dark-theme as a base.
52
- `depend: { repo: string, version: number }[]` declare what other plugins this depends on. (JSON syntax)
53
- `repo: string | object` pointer to a GitHub repo where this plugin is hosted. (JSON syntax)
54
- the string form is for GitHub repos. Example: "rejetto/file-icons"
frontend/src/options.ts
+2
-2
@@ -4,7 +4,7 @@ import { newDialog } from './dialog'
4
import { state, useSnapState } from './state'
5
import { createElement as h } from 'react'
6
import { Checkbox, FlexV, Select } from './components'
7
-import { hIcon, MAX_TILE_SIZE, SORT_BY_OPTIONS, THEME_OPTIONS } from './misc'
7
+import { getHFS, hIcon, MAX_TILE_SIZE, SORT_BY_OPTIONS, THEME_OPTIONS } from './misc'
8
import { MenuLink } from './menu'
9
import { t } from './i18n'
10
import _ from 'lodash'
@@ -67,7 +67,7 @@ export function showOptions (){
67
}),
68
),
69
70
- h(Select, {
70
+ !getHFS().forceTheme && h(Select, {
71
options: _.map(THEME_OPTIONS, (value, label) => ({ label: t(["theme:", "Theme:", ]) + ' ' + t(label), value })),
72
value: snap.theme,
73
onChange(v) {
frontend/src/useTheme.ts
+2
-1
@@ -3,6 +3,7 @@
3
import { useSnapState } from './state'
4
import { useEffect } from 'react'
5
import { useMediaQuery } from 'usehooks-ts'
6
+import { getHFS } from '@hfs/shared'
7
8
export default function useTheme() {
9
const { theme } = useSnapState()
@@ -10,7 +11,7 @@ export default function useTheme() {
11
useEffect(()=>{
12
const e = document.body
13
if (!e) return
13
- const name = theme || (isDarkMode ? 'dark' : 'light')
14
+ const name = getHFS().forceTheme || theme || (isDarkMode ? 'dark' : 'light')
15
const pre = 'theme-'
16
const ct = pre + name
17
const list = e.classList
src/plugins.ts
+2
-2
@@ -261,7 +261,7 @@ export interface CommonPluginInterface {
261
apiRequired?: number | [number,number]
262
repo?: Repo
263
depend?: Depend
264
- isTheme?: boolean
264
+ isTheme?: boolean | 'light' | 'dark'
265
}
266
export interface AvailablePlugin extends CommonPluginInterface {
267
branch?: string
@@ -485,7 +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)
488
+ pl.isTheme = tryJson(/exports.isTheme *= *(true|false|"light"|"dark")/.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) )
src/serveGuiFiles.ts
+1
@@ -110,6 +110,7 @@ async function treatIndex(ctx: Koa.Context, filesUri: string, body: string) {
110
loadScripts: Object.fromEntries(mapPlugins((p, id) => [id, p.frontend_js?.map(f => f.includes('//') ? f : pub + id + '/' + f)])),
111
prefixUrl: ctx.state.revProxyPath,
112
dontOverwriteUploading: dontOverwriteUploading.get(),
113
+ forceTheme: mapPlugins(p => _.isString(p.isTheme) ? p.isTheme : undefined).find(Boolean),
114
customHtml: _.omit(Object.fromEntries(customHtmlState.sections),
115
['top', 'bottom']), // exclude the sections we already apply in this phase
116
...newObj(FRONTEND_OPTIONS, (v, k) => getConfig(k)),