main
ts 88 lines 3.79 KB
Raw
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, Fragment, useEffect, useMemo, useState } from 'react';
4 import { Field, SelectField } from '@hfs/mui-grid-form'
5 import { apiCall, useApiEx } from './api'
6 import { Alert, Box } from '@mui/material'
7 import { Dict, HTTP_MESSAGES, prefix, md, isNumeric, CFG } from './misc'
8 import { hTooltip, IconBtn, reloadBtn, useCtrlShortcutButton, wikiLink } from './mui'
9 import { Save } from '@mui/icons-material'
10 import _ from 'lodash'
11 import { useDebounce } from 'usehooks-ts'
12 import { TextEditor } from './TextEditor';
13 import { state, useSnapState } from './state'
14 import { PageProps } from './App'
15 import { switchBtn } from './VerticalSwitch';
16 import { adminApis } from '../../src/adminApis'
17
18 const names: any = {
19 top: "Top of HTML Body",
20 bottom: "Bottom of HTML Body",
21 }
22
23 export default function CustomHtmlPage({ setTitleSide }: PageProps) {
24 const { data, reload } = useApiEx<typeof adminApis.get_custom_html>('get_custom_html')
25 const { customHtmlSection: section } = useSnapState()
26 const [all, setAll] = useState<Dict<string>>({})
27 const [saved, setSaved] = useState({})
28 useEffect(() => {
29 if (!data) return
30 setSaved(data.sections)
31 setEnabled(data.enabled)
32 }, [data])
33 useEffect(() => setAll(saved), [saved])
34 const options = useMemo(() => {
35 const keys = _.sortBy(Object.keys(all), isNumeric) // http codes at the bottom
36 if (keys.length && !keys.includes(section))
37 state.customHtmlSection = _.findKey(all, Boolean) || keys?.[0] || '' // prefer any key with content
38 return keys.map(x => ({
39 value: x,
40 label: (names[x] || prefix('HTTP ', HTTP_MESSAGES[x as any]) || _.startCase(x)) + (all[x]?.trim() ? ' *' : '')
41 }))
42 }, [useDebounce(all, 500)])
43 const anyChange = useMemo(() => !_.isEqualWith(saved, all, (a,b) => !a && !b || undefined),
44 [saved, all])
45 const [enabled, setEnabled] = useState<boolean>()
46 setTitleSide(useMemo(() => h(Box, { sx: { display: { xs: 'none', md: 'block' } } },
47 h(Alert, { severity: 'info' },
48 md("Add HTML code to some parts of the Front-end. It's saved to file `custom.html`, that you can edit directly with your editor of choice. "),
49 wikiLink('customization', "More help")
50 ),
51 h(Alert, { severity: 'info' }, "To customize icons ", wikiLink('customization#icons', "read documentation") ),
52 ), []))
53 return h(Fragment, {},
54 h(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1, mb: 1 } },
55 h(SelectField as Field<string>, {
56 label: "Section",
57 value: section,
58 options,
59 onChange: v => state.customHtmlSection = v
60 }),
61 reloadBtn(reload),
62 h(IconBtn, {
63 ref: useCtrlShortcutButton(['s', 'Enter']).ref,
64 icon: Save,
65 title: "Save\n(ctrl+s)",
66 modified: anyChange,
67 doneAnimation: true,
68 onClick: save,
69 }),
70 hTooltip("Enable all sections", undefined, switchBtn(enabled, async v => {
71 await apiCall('set_config', { values: { [CFG.disable_custom_html]: !v } })
72 setEnabled(v)
73 })),
74 ),
75 h(TextEditor, {
76 lang: section === 'style' ? 'css' : section === 'script' ? 'js' : 'html',
77 value: all?.[section] || '',
78 style: { background: '#8881' },
79 onValueChange(v: string) {
80 setAll(all => ({ ...all, [section]: v }))
81 },
82 }),
83 )
84
85 function save() {
86 return apiCall('set_custom_html', { sections: all }).then(() => setSaved(all))
87 }
88 }