fix: admin/fs: in file dialog, changing base_url won't update the form

Massimo Melina committed Apr 15, 2024 at 21:49 UTC 9de74e0bdf012477b33580838079bc53113b7578
3 files changed +22 -5
admin/src/FileForm.ts
+8 -2
@@ -8,7 +8,8 @@ import { BoolField, DisplayField, Field, FieldProps, Form, MultiSelectField, Sel
8 import { apiCall, UseApi } from './api'
9 import {
10 basename, defaultPerms, formatBytes, formatTimestamp, isEqualLax, isWhoObject, newDialog, objSameKeys,
11 - onlyTruthy, prefix, VfsPerms, wantArray, Who, WhoObject, matches, HTTP_MESSAGES, xlate, md, Callback
11 + onlyTruthy, prefix, VfsPerms, wantArray, Who, WhoObject, matches, HTTP_MESSAGES, xlate, md, Callback,
12 + useRequestRender
13 } from './misc'
14 import { Btn, IconBtn, LinkBtn, modifiedProps, useBreakpoint, wikiLink } from './mui'
15 import { reloadVfs, VfsNode } from './VfsPage'
@@ -289,7 +290,12 @@ interface LinkFieldProps extends FieldProps<string> {
290 statusApi: UseApi<any> // receive status from parent, to avoid asking server at each click on a file
291 }
292 function LinkField({ value, statusApi }: LinkFieldProps) {
292 - const { data, reload, error } = statusApi
293 + const { reload, error } = statusApi
294 + // workaround to get fresh data and be rerendered even when mounted inside imperative dialog
295 + const requestRender = useRequestRender()
296 + useEffect(() => statusApi.sub(requestRender), [])
297 + const data = statusApi.getData()
298 +
299 const urls: string[] = data?.urls.https || data?.urls.http
300 const baseHost = data?.baseUrl && new URL(data.baseUrl).host
301 const root = useMemo(() => baseHost && _.find(data.roots, (root, host) => matches(baseHost, host)),
shared/api.ts
+6 -3
@@ -1,8 +1,8 @@
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 _ from 'lodash';
4 -import { useCallback, useEffect, useRef } from 'react';
5 -import { Dict, Falsy, getPrefixUrl, pendingPromise, useStateMounted, wait } from '.'
4 +import { useCallback, useEffect, useMemo, useRef } from 'react';
5 +import { Dict, EventEmitter, Falsy, getPrefixUrl, pendingPromise, useStateMounted, wait } from '.'
6
7 export const API_URL = '/~/api/'
8
@@ -112,7 +112,10 @@ export function useApi<T=any>(cmd: string | Falsy, params?: object, options: Api
112 setForcer(v => v + 1)
113 reloadingRef.current = pendingPromise()
114 }, [setForcer])
115 - return { data, setData, error, reload, loading: loadingRef.current || reloadingRef.current, getData: () => dataRef.current, }
115 + const ee = useMemo(() => new EventEmitter, [])
116 + const sub = useCallback((cb: EventListener) => ee.on('data', cb), [])
117 + useEffect(() => ee.emit('data'), [data])
118 + return { data, setData, error, reload, sub, loading: loadingRef.current || reloadingRef.current, getData: () => dataRef.current, }
119 }
120
121 type EventHandler = (type:string, data?:any) => void
shared/index.ts
+8
@@ -140,6 +140,14 @@ export function createDurationFormatter({ locale=undefined, unitDisplay='narrow'
140 }
141 }
142
143 +export class EventEmitter extends EventTarget {
144 + emit(name: string) { this.dispatchEvent(new Event(name)) }
145 + on(name: string, cb: EventListener) {
146 + this.addEventListener(name, cb)
147 + return () => this.removeEventListener(name, cb)
148 + }
149 +}
150 +
151 Element.prototype.replaceChildren ||= function(this:Element, addNodes) { // polyfill
152 while (this.lastChild) this.removeChild(this.lastChild);
153 if (addNodes !== undefined) this.append(addNodes);