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