| 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 { apiCall, useApiEx, useApiList } from './api' |
| 5 | import { DataTable, fillFlexParentSx } from './DataTable' |
| 6 | import { Alert, Box } from '@mui/material' |
| 7 | import { Delete, Upload } from '@mui/icons-material' |
| 8 | import { CFG, readFile, selectFiles } from './misc' |
| 9 | import { Btn, IconBtn } from './mui' |
| 10 | import { PageProps } from './App' |
| 11 | import _ from 'lodash' |
| 12 | import { alertDialog, toast } from './dialog' |
| 13 | import { Field, SelectField } from '@hfs/mui-grid-form'; |
| 14 | |
| 15 | export default function LangPage({ setTitleSide }: PageProps) { |
| 16 | const { list, error, connecting, reload } = useApiList('get_langs') |
| 17 | const langs = useMemo(() => ['en', ..._.uniq(list.map(x => x.code))], [list]) |
| 18 | setTitleSide(useMemo(() => |
| 19 | h(Alert, { severity: 'info', sx: { display: { xs: 'none', sm: 'inherit' } } }, "Translation is limited to the Front-end and doesn't apply to the Admin-panel"), |
| 20 | [])) |
| 21 | return h(Fragment, {}, |
| 22 | h(Box, { sx: { mt: 1, maxWidth: '50em', flex: 1, ...fillFlexParentSx } }, |
| 23 | h(Box, { sx: { mb: 1, display: 'flex' } }, |
| 24 | h(Btn, { icon: Upload, onClick: add }, "Add"), |
| 25 | h(Box, { sx: { flex: 1 } }), |
| 26 | h(ForceLang, { langs }), |
| 27 | ), |
| 28 | h(DataTable, { |
| 29 | error, |
| 30 | loading: connecting, |
| 31 | rows: useMemo(() => _.sortBy(list, x => (x.embedded ? 2 : 1) + x.code), [list.length]), // multi-sorting is only in pro version of DataGrid |
| 32 | hideFooter: true, |
| 33 | fillFlex: true, |
| 34 | columns: [ |
| 35 | { |
| 36 | field: 'code', |
| 37 | width: 110, |
| 38 | valueFormatter: (value: string | undefined) => value?.toUpperCase(), |
| 39 | }, |
| 40 | { |
| 41 | field: 'version', |
| 42 | width: 120, |
| 43 | hideUnder: 'sm', |
| 44 | }, |
| 45 | { |
| 46 | field: 'author', |
| 47 | flex: 1, |
| 48 | hideUnder: 'sm', |
| 49 | } |
| 50 | ], |
| 51 | actions: ({ row }) => [ |
| 52 | h(IconBtn, { |
| 53 | icon: Delete, |
| 54 | title: row.embedded ? "Cannot delete (embedded)" : "Delete", |
| 55 | confirm: `Delete language code "${row.code}"?`, |
| 56 | disabled: row.embedded, |
| 57 | async onClick() { |
| 58 | await apiCall('del_lang', _.pick(row, 'code')) |
| 59 | reload() |
| 60 | toast("Deleted") |
| 61 | } |
| 62 | }), |
| 63 | ] |
| 64 | }) |
| 65 | ) |
| 66 | ) |
| 67 | |
| 68 | function add() { |
| 69 | selectFiles(async list => { |
| 70 | if (!list) return |
| 71 | const langs: any = {} |
| 72 | for (const f of list) |
| 73 | langs[f.name] = await readFile(f) |
| 74 | try { |
| 75 | await apiCall('add_langs', { langs }) |
| 76 | reload() |
| 77 | toast("Loaded") |
| 78 | } |
| 79 | catch (e: any) { |
| 80 | await alertDialog(e) |
| 81 | } |
| 82 | }, { accept: '.json' }) |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | function ForceLang({ langs }: { langs: string[] }) { |
| 87 | const K = CFG.force_lang |
| 88 | const { data, reload, loading } = useApiEx('get_config', { only: [K] }) |
| 89 | const [lang, setLang] = useState() |
| 90 | useEffect(() => setLang(data?.[K] ?? lang), [data]) |
| 91 | const [saving, setSaving] = useState<string>() |
| 92 | |
| 93 | return h(SelectField as Field<string>, { |
| 94 | fullWidth: false, |
| 95 | size: 'small', |
| 96 | disabled: Boolean(loading) || typeof saving === 'string', |
| 97 | value: saving ?? lang, |
| 98 | async onChange(v) { |
| 99 | setSaving(v) |
| 100 | try { |
| 101 | await apiCall('set_config', { values: { [K]: v } }) |
| 102 | await reload() |
| 103 | } |
| 104 | finally { setSaving(undefined) } |
| 105 | }, |
| 106 | options: [ |
| 107 | { label: "Respect browser language", value: '' }, |
| 108 | ...langs.map(x => ({ value: x, label: "Force language: " + x })) |
| 109 | ] |
| 110 | }) |
| 111 | } |