@samitouri / QOSami-HFS / commits / 8dd4144e

admin/plugin/updates: new "change log" column #897

Massimo Melina committed Feb 24, 2025 at 13:49 UTC 8dd4144eea1bcda43dab3e5efc12f59fbcbac83b
4 files changed +37 -5
admin/src/InstalledPlugins.ts
+28 -2
@@ -2,7 +2,7 @@
2
3 import { apiCall, useApiEx, useApiList } from './api'
4 import { createElement as h, Fragment, useEffect } from 'react'
5 -import { Box, Breakpoint, Link, Paper, useTheme } from '@mui/material'
5 +import { Box, Breakpoint, Link, Paper, Table, TableCell, TableRow, useTheme } from '@mui/material'
6 import { DataTable, DataTableColumn } from './DataTable'
7 import {
8 Clear, Delete, Error as ErrorIcon, FormatPaint as ThemeIcon, ListAlt, PlayCircle, Settings, StopCircle, Upgrade
@@ -20,6 +20,7 @@ import { PLUGIN_ERRORS } from './PluginsPage'
20 import { Btn, Flex, hTooltip, IconBtn, iconTooltip, usePauseButton } from './mui'
21 import VfsPathField from './VfsPathField'
22
23 +// updates=true will show the "check updates" version of the page
24 export default function InstalledPlugins({ updates }: { updates?: true }) {
25 const { list, error, setList, initializing } = useApiList(updates ? 'get_plugin_updates' : 'get_plugins')
26 useEffect(() => {
@@ -37,6 +38,7 @@ export default function InstalledPlugins({ updates }: { updates?: true }) {
38 fillFlex: true,
39 initializing,
40 disableColumnSelector: true,
41 + getRowHeight: updates && (({ model }) => model.changelog ? 'auto' as const : 50),
42 noRows: updates && `No updates available. Only plugins available on "search online" are checked.`,
43 columns: [
44 {
@@ -46,12 +48,13 @@ export default function InstalledPlugins({ updates }: { updates?: true }) {
48 minWidth: 150,
49 renderCell: renderName,
50 valueGetter({ row }) { return row.repo || row.id },
49 - mergeRender: { description: { fontSize: 'x-small' } }
51 + mergeRender: { [updates ? 'changelog' : 'description']: { fontSize: 'x-small' } }
52 },
53 {
54 field: 'version',
55 width: 70,
56 hideUnder: 'sm',
57 + mergeRender: { installedVersion: { fontSize: 'x-small' } }
58 },
59 themeField,
60 {
@@ -59,6 +62,29 @@ export default function InstalledPlugins({ updates }: { updates?: true }) {
62 flex: 1,
63 hideUnder: 'sm',
64 },
65 + {
66 + field: 'installedVersion',
67 + hidden: true,
68 + renderCell: ({ value }) => value && `Yours ${value}`
69 + },
70 + {
71 + field: 'changelog',
72 + headerName: "Change log",
73 + flex: 2,
74 + hideUnder: 'sm',
75 + hidden: !updates,
76 + sx: { flexDirection: 'column', alignItems: 'flex-start' },
77 + renderCell({ value, row }) {
78 + if (!Array.isArray(value)) return null
79 + return h(Table, { sx: { td: { p: 0 } } },
80 + _.uniq(_.sortBy(value, 'version').filter(x => _.isString(x.message) && x.message && x.version > row.installedVersion))
81 + .map((x, i) => h(TableRow, { key: i },
82 + h(TableCell, {}, "• ", x.version, ': '),
83 + h(TableCell, {}, md(x.message))
84 + ))
85 + )
86 + }
87 + }
88 ],
89 footerSide: () => !updates && pauseButton,
90 actions: ({ row, id }) => updates ? [
dev-plugins.md
+5 -2
@@ -93,8 +93,10 @@ All the following properties are optional unless otherwise specified.
93 Note that in this example we are pointing to a github repo just for clarity. You are not supposed to use this
94 complicated object form to link github, use the string form.
95 Plugins with custom repos are not included in search results, but the update feature will still work.
96 +- `changelog: { version: number, message: string }[]` the UI will show only entries with version greater than currently installed.
97 + You can use `md` syntax for the message.
98
97 -WARNING: All the properties above are a bit special and must go in `exports` only (thus, not returned in `init`) and the syntax
99 +**WARNING:** All the properties above are a bit special and must go in `exports` only (thus, not returned in `init`) and the syntax
100 used must be strictly JSON (thus, no single quotes, only double quotes for strings and objects), and must fit one line.
101
102 - `init: (api: object) => void | object | function` described in the previous section. If an object is returned,
@@ -780,7 +782,8 @@ If you want to override a text regardless of the language, use the special langu
782 ## API version history
783
784 - 12.0 (v0.57.0)
783 - - backend event: finalizingLogin, httpsServerOptions
785 + - backend event: finalizingLogin, httpsServerOptions
786 + - exports.changelog
787 - 11.6 (v0.56.0)
788 - api.setError
789 - frontend events: afterBreadcrumbs, afterFolderStats, afterFilter
src/api.plugins.ts
+1
@@ -53,6 +53,7 @@ const apis: ApiHandlers = {
53 if (online.version === disk.version) return // different, not just newer ones, in case a version was retired
54 list.add(Object.assign(online, {
55 id: disk.id, // id is installation-dependant, and online cannot know
56 + installedVersion: disk.version,
57 repo: serialize(disk).repo, // show the user the current repo we are getting this update from, not a possibly-changed future one
58 downgrade: online.version! < disk.version,
59 downloading: _.isString(online.repo) && downloading[online.repo],
src/plugins.ts
+3 -1
@@ -371,6 +371,7 @@ export interface CommonPluginInterface {
371 depend?: Depend
372 isTheme?: boolean | 'light' | 'dark'
373 preview?: string | string[]
374 + changelog?: unknown
375 }
376 export interface AvailablePlugin extends CommonPluginInterface {
377 branch?: string
@@ -640,9 +641,10 @@ export function parsePluginSource(id: string, source: string) {
641 pl.apiRequired = tryJson(/exports.apiRequired *= *([ \d.,[\]]+)/.exec(source)?.[1]) ?? undefined
642 pl.isTheme = tryJson(/exports.isTheme *= *(true|false|"light"|"dark")/.exec(source)?.[1]) ?? (id.endsWith('-theme') || undefined)
643 pl.preview = tryJson(/exports.preview *= *(.+)/.exec(source)?.[1]) ?? undefined
643 - pl.depend = tryJson(/exports.depend *= *(\[.*])/m.exec(source)?.[1])?.filter((x: any) =>
644 + pl.depend = tryJson(/exports.depend *= *(\[[\s\S]*?])/m.exec(source)?.[1])?.filter((x: any) =>
645 typeof x.repo === 'string' && x.version === undefined || typeof x.version === 'number'
646 || console.warn("plugin dependency discarded", x) )
647 + pl.changelog = tryJson(/exports.changelog *= *(\[[\s\S]*?])/m.exec(source)?.[1])
648 if (Array.isArray(pl.apiRequired) && (pl.apiRequired.length !== 2 || !pl.apiRequired.every(_.isFinite))) // validate [from,to] form
649 pl.apiRequired = undefined
650 calculateBadApi(pl)