From a39b526d645b2f1ed7e86f7deeb0e42edfdbfff2 Mon Sep 17 00:00:00 2001 From: marcobaobao Date: Fri, 15 Nov 2024 11:29:13 +0100 Subject: [PATCH] editable templates --- frontend/src/components/TemplateTextField.tsx | 67 ++++++++++++++++ frontend/src/components/TemplatesEditor.tsx | 66 ++++++++-------- frontend/src/hooks/useI18n.ts | 3 +- server/rest/container.go | 1 + server/rest/handlers.go | 76 ++++++++++++------- server/rest/service.go | 16 ++++ 6 files changed, 167 insertions(+), 62 deletions(-) create mode 100644 frontend/src/components/TemplateTextField.tsx diff --git a/frontend/src/components/TemplateTextField.tsx b/frontend/src/components/TemplateTextField.tsx new file mode 100644 index 0000000..521df37 --- /dev/null +++ b/frontend/src/components/TemplateTextField.tsx @@ -0,0 +1,67 @@ +import { FC, useState, useTransition } from 'react' + +import DeleteIcon from '@mui/icons-material/Delete' +import EditIcon from '@mui/icons-material/Edit' +import { + Button, + Grid, + TextField +} from '@mui/material' +import { useI18n } from '../hooks/useI18n' +import { CustomTemplate } from '../types' + +interface Props { + template: CustomTemplate + onChange: (template: CustomTemplate) => void + onDelete: (id: string) => void +} + +const TemplateTextField: FC = ({ template, onChange, onDelete }) => { + const { i18n } = useI18n() + + const [editedTemplate, setEditedTemplate] = useState(template) + + return ( + + + setEditedTemplate({ ...editedTemplate, name: e.target.value })} + /> + + + setEditedTemplate({ ...editedTemplate, content: e.target.value })} + InputProps={{ + endAdornment:
+ + +
+ }} + /> +
+
+ ) +} + +export default TemplateTextField \ No newline at end of file diff --git a/frontend/src/components/TemplatesEditor.tsx b/frontend/src/components/TemplatesEditor.tsx index c642e6b..5277152 100644 --- a/frontend/src/components/TemplatesEditor.tsx +++ b/frontend/src/components/TemplatesEditor.tsx @@ -1,6 +1,7 @@ import AddIcon from '@mui/icons-material/Add' import CloseIcon from '@mui/icons-material/Close' import DeleteIcon from '@mui/icons-material/Delete' +import EditIcon from '@mui/icons-material/Edit' import { Alert, AppBar, @@ -26,6 +27,7 @@ import { useI18n } from '../hooks/useI18n' import { ffetch } from '../lib/httpClient' import { CustomTemplate } from '../types' import { useAtomValue } from 'jotai' +import TemplateTextField from './TemplateTextField' const Transition = forwardRef(function Transition( props: TransitionProps & { @@ -55,11 +57,11 @@ const TemplatesEditor: React.FC = ({ open, onClose }) => { useEffect(() => { if (open) { - getTemplates() + fetchTemplates() } }, [open]) - const getTemplates = async () => { + const fetchTemplates = async () => { const task = ffetch(`${serverAddr}/api/v1/template/all`) const either = await task() @@ -89,7 +91,7 @@ const TemplatesEditor: React.FC = ({ open, onClose }) => { (l) => pushMessage(l, 'warning'), () => { pushMessage('Added template') - getTemplates() + fetchTemplates() setTemplateName('') setTemplateContent('') } @@ -97,6 +99,26 @@ const TemplatesEditor: React.FC = ({ open, onClose }) => { ) } + const updateTemplate = async (template: CustomTemplate) => { + const task = ffetch(`${serverAddr}/api/v1/template`, { + method: 'PATCH', + body: JSON.stringify(template) + }) + + const either = await task() + + pipe( + either, + matchW( + (l) => pushMessage(l, 'warning'), + (r) => { + pushMessage(`Updated template ${r.name}`) + fetchTemplates() + } + ) + ) + } + const deleteTemplate = async (id: string) => { const task = ffetch(`${serverAddr}/api/v1/template/${id}`, { method: 'DELETE', @@ -110,7 +132,7 @@ const TemplatesEditor: React.FC = ({ open, onClose }) => { (l) => pushMessage(l, 'warning'), () => { pushMessage('Deleted template') - getTemplates() + fetchTemplates() } ) ) @@ -188,38 +210,12 @@ const TemplatesEditor: React.FC = ({ open, onClose }) => { {templates.map(template => ( - - - - - - { - startTransition(() => { deleteTemplate(template.id) }) - }}> - - - }} - /> - - + template={template} + onChange={updateTemplate} + onDelete={deleteTemplate} + /> ))} diff --git a/frontend/src/hooks/useI18n.ts b/frontend/src/hooks/useI18n.ts index 3f1864e..d98d187 100644 --- a/frontend/src/hooks/useI18n.ts +++ b/frontend/src/hooks/useI18n.ts @@ -5,6 +5,7 @@ export const useI18n = () => { const instance = useAtomValue(i18nBuilderState) return { - i18n: instance + i18n: instance, + t: instance.t } } \ No newline at end of file diff --git a/server/rest/container.go b/server/rest/container.go index 33dadf7..35dbb3b 100644 --- a/server/rest/container.go +++ b/server/rest/container.go @@ -34,6 +34,7 @@ func ApplyRouter(args *ContainerArgs) func(chi.Router) { r.Post("/cookies", h.SetCookies()) r.Delete("/cookies", h.DeleteCookies()) r.Post("/template", h.AddTemplate()) + r.Patch("/template", h.UpdateTemplate()) r.Get("/template/all", h.GetTemplates()) r.Delete("/template/{id}", h.DeleteTemplate()) } diff --git a/server/rest/handlers.go b/server/rest/handlers.go index 35cb4e0..5ddcb78 100644 --- a/server/rest/handlers.go +++ b/server/rest/handlers.go @@ -34,9 +34,9 @@ func (h *Handler) Exec() http.HandlerFunc { return } - err = json.NewEncoder(w).Encode(id) - if err != nil { + if err := json.NewEncoder(w).Encode(id); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) + return } } } @@ -61,6 +61,7 @@ func (h *Handler) ExecPlaylist() http.HandlerFunc { if err := json.NewEncoder(w).Encode("ok"); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) + return } } } @@ -75,13 +76,14 @@ func (h *Handler) ExecLivestream() http.HandlerFunc { if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) + return } h.service.ExecLivestream(req) - err := json.NewEncoder(w).Encode("ok") - if err != nil { + if err := json.NewEncoder(w).Encode("ok"); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) + return } } } @@ -98,9 +100,9 @@ func (h *Handler) Running() http.HandlerFunc { return } - err = json.NewEncoder(w).Encode(res) - if err != nil { + if err := json.NewEncoder(w).Encode(res); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) + return } } } @@ -134,21 +136,19 @@ func (h *Handler) SetCookies() http.HandlerFunc { req := new(internal.SetCookiesRequest) - err := json.NewDecoder(r.Body).Decode(req) - if err != nil { + if err := json.NewDecoder(r.Body).Decode(req); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } - err = h.service.SetCookies(r.Context(), req.Cookies) - if err != nil { + if err := h.service.SetCookies(r.Context(), req.Cookies); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - err = json.NewEncoder(w).Encode("ok") - if err != nil { + if err := json.NewEncoder(w).Encode("ok"); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) + return } } } @@ -157,15 +157,14 @@ func (h *Handler) DeleteCookies() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - err := h.service.SetCookies(r.Context(), "") - if err != nil { + if err := h.service.SetCookies(r.Context(), ""); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - err = json.NewEncoder(w).Encode("ok") - if err != nil { + if err := json.NewEncoder(w).Encode("ok"); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) + return } } } @@ -178,8 +177,7 @@ func (h *Handler) AddTemplate() http.HandlerFunc { req := new(internal.CustomTemplate) - err := json.NewDecoder(r.Body).Decode(req) - if err != nil { + if err := json.NewDecoder(r.Body).Decode(req); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } @@ -189,15 +187,14 @@ func (h *Handler) AddTemplate() http.HandlerFunc { return } - err = h.service.SaveTemplate(r.Context(), req) - if err != nil { + if err := h.service.SaveTemplate(r.Context(), req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - err = json.NewEncoder(w).Encode("ok") - if err != nil { + if err := json.NewEncoder(w).Encode("ok"); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) + return } } } @@ -221,6 +218,33 @@ func (h *Handler) GetTemplates() http.HandlerFunc { } } +func (h *Handler) UpdateTemplate() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + + w.Header().Set("Content-Type", "application/json") + + req := &internal.CustomTemplate{} + + if err := json.NewDecoder(r.Body).Decode(req); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + res, err := h.service.UpdateTemplate(r.Context(), req) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if err := json.NewEncoder(w).Encode(res); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } +} + func (h *Handler) DeleteTemplate() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() @@ -229,15 +253,14 @@ func (h *Handler) DeleteTemplate() http.HandlerFunc { id := chi.URLParam(r, "id") - err := h.service.DeleteTemplate(r.Context(), id) - if err != nil { + if err := h.service.DeleteTemplate(r.Context(), id); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - err = json.NewEncoder(w).Encode("ok") - if err != nil { + if err := json.NewEncoder(w).Encode("ok"); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) + return } } } @@ -266,6 +289,7 @@ func (h *Handler) GetVersion() http.HandlerFunc { if err := json.NewEncoder(w).Encode(res); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) + return } } } diff --git a/server/rest/service.go b/server/rest/service.go index 9b05326..e24b96e 100644 --- a/server/rest/service.go +++ b/server/rest/service.go @@ -133,6 +133,22 @@ func (s *Service) GetTemplates(ctx context.Context) (*[]internal.CustomTemplate, return &templates, nil } +func (s *Service) UpdateTemplate(ctx context.Context, t *internal.CustomTemplate) (*internal.CustomTemplate, error) { + conn, err := s.db.Conn(ctx) + if err != nil { + return nil, err + } + + defer conn.Close() + + _, err = conn.ExecContext(ctx, "UPDATE templates SET name = ?, content = ? WHERE id = ?", t.Name, t.Content, t.Id) + if err != nil { + return nil, err + } + + return t, nil +} + func (s *Service) DeleteTemplate(ctx context.Context, id string) error { conn, err := s.db.Conn(ctx) if err != nil {