plugins: support production of content on frontend with react

Massimo Melina committed Feb 14, 2023 at 16:02 UTC df030a7b38ad79ecf23cc84d92d72f0bc286c855
3 files changed +37 -13
dev-plugins.md
+23 -6
@@ -141,12 +141,29 @@ The following information applies to the default front-end, and may not apply to
141
142 ### Javascript
143 Once your script is loaded into the frontend (via `frontend_js`), you will have access to the `HFS` object in the global scope.
144 -There you'll find `HFS.onEvent` function that is the base of communication.
144
146 -`onEvent(eventName:string, callback: (object) => any)` your callback will be called on the specified event.
147 -Depending on the event you'll have an object with parameters in it, and may return some output. Refer to the specific event for further information.
145 +API at this level is done with frontend-events, that you can handle by calling
146
149 -This is a list of available frontend events, with respective object parameter and output.
147 +```typescript
148 +HFS.onEvent(eventName, callback)
149 +
150 +//type callback = (parameters: object, tools: object) => any
151 +```
152 +
153 +Parameters of your callback and meaning of returned value varies with the event name.
154 +Refer to the specific event for further information.
155 +Tools are extra data and functions to help you:
156 +- `React` whole React object, as for require('react')
157 +- `h` shortcut for React.createElement
158 +- `state` object with many values in it. [Refer here for details](https://github.com/rejetto/hfs/blob/main/frontend/src/state.ts).
159 +
160 +Some frontend-events can return Html, which can be expressed in several ways
161 +- as string, containing markup
162 +- as DOM Nodes, as for document.createElement()
163 +- as ReactElement
164 +- null, undefined, false and empty-string will just be discarded
165 +
166 +This is a list of available frontend-events, with respective object parameter and output.
167
168 - `additionalEntryProps`
169 - you receive each entry of the list, and optionally produce HTML code that will be added in the `entry-props` container.
@@ -158,11 +175,11 @@ This is a list of available frontend events, with respective object parameter an
175 - `t?: Date` generic timestamp, combination of creation-time and modified-time.
176 - `c?: Date` creation-time.
177 - `m?: Date` modified-time.
161 - - output `string | falsy`
178 + - output `Html`
179 - `afterEntryName`
180 - you receive each entry of the list, and optionally produce HTML code that will be added after the name of the entry.
181 - parameter `{ entry: Entry }` (refer above for Entry object)
165 - - output `string | falsy`
182 + - output `Html`
183
184 ## Publish your plug-in
185
frontend/src/components.ts
+8 -3
@@ -1,7 +1,7 @@
1 // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3 import { hfsEvent, hIcon } from './misc'
4 -import { createElement as h, HTMLAttributes, ReactNode, useMemo } from 'react'
4 +import { createElement as h, Fragment, HTMLAttributes, isValidElement, ReactNode, useMemo } from 'react'
5
6 export function Spinner() {
7 return hIcon('spinner', { className:'spinner' })
@@ -54,7 +54,12 @@ export function Html({ code, ...rest }:{ code:string } & HTMLAttributes<any>) {
54 }
55
56 export function useCustomCode(name: string, props={}) {
57 - const code = useMemo(()=> hfsEvent(name, props).filter(x => x === 0 || x).join(''),
57 + const code = useMemo(()=>
58 + hfsEvent(name, props)
59 + .filter(x => x === 0 || x)
60 + .map((x, key) => isValidElement(x) ? h(Fragment, { key }, x)
61 + : typeof x === 'string' ? h(Html, { key, code: x })
62 + : h('div', { key }, x)),
63 Object.values(props))
59 - return h(Html, { code, className: name })
64 + return h(Fragment, { children: code })
65 }
frontend/src/misc.ts
+6 -4
@@ -1,10 +1,11 @@
1 // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3 -import { createElement as h } from 'react'
3 +import React, { createElement as h } from 'react'
4 import { Spinner } from './components'
5 import { newDialog } from './dialog'
6 import { Icon } from './icons'
7 import { Dict } from '@hfs/shared'
8 +import { state } from './state'
9 export * from '@hfs/shared'
10
11 export function hIcon(name: string, props?:any) {
@@ -38,16 +39,17 @@ export function working() {
39
40 export function hfsEvent(name: string, params?:Dict) {
41 const output: any[] = []
41 - document.dispatchEvent(new CustomEvent('hfs.'+name, { detail:{ params, output } }))
42 + document.dispatchEvent(new CustomEvent('hfs.'+name, { detail: { params, output } }))
43 return output
44 }
45
46 const HFS: any = (window as any).HFS = {}
47
47 -HFS.onEvent = (name: string, cb: (params:any, output:any) => any) => {
48 +HFS.onEvent = (name: string, cb: (params:any, tools: any, output:any) => any) => {
49 + const tools = { h, React, state }
50 document.addEventListener('hfs.' + name, ev => {
51 const { params, output } = (ev as CustomEvent).detail
50 - const res = cb(params, output)
52 + const res = cb(params, tools, output)
53 if (res !== undefined && Array.isArray(output))
54 output.push(res)
55 })