simplified frontend-side plugins' injection

Massimo Melina committed Jan 15, 2022 at 14:45 UTC b0d828aa98bb2df9e1adb6a8431db7cf6a42ccc7
5 files changed +21 -45
frontend/public/index.html
+1 -4
@@ -5,15 +5,12 @@
5 <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
6 <meta name="viewport" content="width=device-width, initial-scale=1" />
7 <meta name="theme-color" content="#000000" />
8 - <meta
9 - name="description"
10 - content="Web site created using create-react-app"
11 - />
8 <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
9 <link href="icons.css" rel="stylesheet" />
10 <title>File Server</title>
11 </head>
12 <body>
13 + _HFS_PLUGINS_
14 <noscript>You need to enable JavaScript to run this app.</noscript>
15 <div id="root"></div>
16 </body>
frontend/src/App.tsx
+1 -26
@@ -1,44 +1,19 @@
1 import { BrowserRouter, Route, Routes } from "react-router-dom"
2 -import { createElement as h, Fragment, useEffect, useState } from 'react'
2 +import { createElement as h, Fragment } from 'react'
3 import { BrowseFiles } from "./BrowseFiles";
4 import { Dialogs } from './dialog'
5 -import { useApi } from "./api";
5 import useTheme from "./useTheme";
6
7 function App() {
9 - const extras = useApi('extras_to_load')
8 useTheme()
11 - if (!useImportJs(extras))
12 - return null
9 return h(Fragment, {},
10 h(BrowserRouter, {},
11 h(Routes, {},
12 h(Route, { path:'*', element:h(BrowseFiles) })
13 )
14 ),
19 - extras?.css?.map((href:string) =>
20 - h('link', { key:href, rel:"stylesheet", type:"text/css", href })),
15 h(Dialogs)
16 )
17 }
18
19 export default App;
26 -
27 -// return true when all is loaded
28 -function useImportJs(extras:{ js?: string[] }) {
29 - const [ready, setReady] = useState(false)
30 - useEffect(() => {
31 - if (!extras) return
32 - const toImport = extras.js
33 - let missing = toImport?.length || 0
34 - if (!missing)
35 - return setReady(true)
36 - for (const url of toImport!) {
37 - const el = document.createElement('script')
38 - el.src = url
39 - el.onload = ()=> setReady(!--missing)
40 - document.body.appendChild(el)
41 - }
42 - }, [extras])
43 - return ready
44 -}
src/frontEndApis.ts
-10
@@ -1,6 +1,4 @@
1 import { ApiHandlers } from './apis'
2 -import { mapPlugins } from './plugins'
3 -import { PLUGINS_PUB_URI } from './const'
2 import * as api_file_list from './api.file_list'
3 import * as api_auth from './api.auth'
4 import { setConfig } from './config'
@@ -11,14 +9,6 @@ export const frontEndApis: ApiHandlers = {
9
10 ...api_auth,
11
14 - async extras_to_load() {
15 - const css = mapPlugins((plug,k) =>
16 - plug.frontend_css?.map(f => PLUGINS_PUB_URI + k + '/' + f)).flat().filter(Boolean)
17 - const js = mapPlugins((plug,k) =>
18 - plug.frontend_js?.map(f => PLUGINS_PUB_URI + k + '/' + f)).flat().filter(Boolean)
19 - return { css, js }
20 - },
21 -
12 async config({ values }, ctx) {
13 if (isLocalhost(ctx.ip))
14 setConfig(values)
src/serveFrontend.ts
+19 -3
@@ -1,8 +1,9 @@
1 import proxy from 'koa-better-http-proxy'
2 import Koa from 'koa'
3 import fs from 'fs/promises'
4 -import { DEV, FRONTEND_URI, METHOD_NOT_ALLOWED, NO_CONTENT } from './const'
4 +import { DEV, FRONTEND_URI, METHOD_NOT_ALLOWED, NO_CONTENT, PLUGINS_PUB_URI } from './const'
5 import { serveFile } from './serveFile'
6 +import { mapPlugins } from './plugins'
7
8 export const serveFrontend = DEV ? serveProxyFrontend() : serveStaticFrontend()
9
@@ -12,7 +13,7 @@ function serveProxyFrontend() {
13 filter: ctx => ctx.method === 'GET' || (ctx.status = METHOD_NOT_ALLOWED) && false,
14 proxyReqPathResolver: (ctx) => ctx.path.endsWith('/') ? '/' : ctx.path,
15 userResDecorator: (res, data, req) => {
15 - return req.url.endsWith('/') ? replaceFrontEndRes(data.toString('utf8'))
16 + return req.url.endsWith('/') ? treatIndex(data.toString('utf8'))
17 : data
18 }
19 })
@@ -30,7 +31,7 @@ function serveStaticFrontend() : Koa.Middleware {
31 if (method !== 'GET')
32 return ctx.status = METHOD_NOT_ALLOWED
33 if (path.endsWith('/')) {
33 - ctx.body = replaceFrontEndRes(String(await fs.readFile(BASE + 'index.html')))
34 + ctx.body = treatIndex(String(await fs.readFile(BASE + 'index.html')))
35 ctx.type = 'html'
36 } else {
37 const fullPath = BASE + path.slice(1)
@@ -49,3 +50,18 @@ function serveStaticFrontend() : Koa.Middleware {
50 function replaceFrontEndRes(body: string) {
51 return body.replace(/((?:src|href) *= *['"])\/?(?![a-z]+:\/\/)/g, '$1'+FRONTEND_URI)
52 }
53 +
54 +function treatIndex(body: string) {
55 + return replaceFrontEndRes(body)
56 + // replacing this text allow us to avoid injecting in frontends that don't support plugins. Don't use a <--comment--> or it will be removed by webpack
57 + .replace('_HFS_PLUGINS_', pluginsInjection)
58 +}
59 +
60 +function pluginsInjection() {
61 + const css = mapPlugins((plug,k) =>
62 + plug.frontend_css?.map(f => PLUGINS_PUB_URI + k + '/' + f)).flat().filter(Boolean)
63 + const js = mapPlugins((plug,k) =>
64 + plug.frontend_js?.map(f => PLUGINS_PUB_URI + k + '/' + f)).flat().filter(Boolean)
65 + return css.map(uri => `\n<link rel='stylesheet' type='text/css' href='${uri}'/>`).join('')
66 + + js.map(uri => `\n<script defer src='${uri}'></script>`).join('')
67 +}
todo.md
-2
@@ -1,7 +1,5 @@
1 # To do
2 -- inject extras directly into <head> ? easier for makers and faster to load
2 - confirm archive
4 -- automatic theme based on system preferences
3 - archive button as a link that can be copied
4 - emoji as an alternative to icons?
5 - smaller icons file?