mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-10 16:49:41 +00:00
refactor: improve css editor loading suspense
This commit is contained in:
committed by
Carlos Valente
parent
129efc33b9
commit
b2b7855114
+1
@@ -1,4 +1,5 @@
|
|||||||
.wrapper {
|
.wrapper {
|
||||||
|
min-height: 500px;
|
||||||
max-height: 500px;
|
max-height: 500px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-43
@@ -1,71 +1,38 @@
|
|||||||
import Prism from 'virtual:prismjs';
|
import Prism from 'virtual:prismjs';
|
||||||
import { forwardRef, memo, useEffect, useImperativeHandle, useState } from 'react';
|
import { memo } from 'react';
|
||||||
import Editor from 'react-simple-code-editor';
|
import Editor from 'react-simple-code-editor';
|
||||||
import 'prismjs/components/prism-css';
|
import 'prismjs/components/prism-css';
|
||||||
import 'prismjs/themes/prism-tomorrow.min.css';
|
import 'prismjs/themes/prism-tomorrow.min.css';
|
||||||
|
|
||||||
import style from './StyleEditor.module.scss';
|
import style from './StyleEditor.module.scss';
|
||||||
|
|
||||||
interface CodeEditorProps {
|
interface CodeEditorProps {
|
||||||
language: string;
|
language: string;
|
||||||
initialValue: string;
|
value: string;
|
||||||
isDirty: boolean;
|
onChange: (value: string) => void;
|
||||||
setIsDirty: (value: boolean) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const CodeEditor = forwardRef((props: CodeEditorProps, cssRef) => {
|
export default memo(CodeEditor);
|
||||||
const { language, initialValue, isDirty, setIsDirty } = props;
|
function CodeEditor({ language, onChange, value }: CodeEditorProps) {
|
||||||
|
|
||||||
const [code, setCode] = useState(initialValue);
|
|
||||||
|
|
||||||
const highlight = (code: string) => {
|
const highlight = (code: string) => {
|
||||||
const grammar = Prism.languages[language];
|
const grammar = Prism.languages[language];
|
||||||
return grammar ? Prism.highlight(code, grammar, language) : code;
|
return grammar ? Prism.highlight(code, grammar, language) : code;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = (newCode: string) => {
|
|
||||||
setCode(newCode);
|
|
||||||
};
|
|
||||||
|
|
||||||
useImperativeHandle(cssRef, () => {
|
|
||||||
return {
|
|
||||||
getCss: () => code,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// add contents to editor on mount and any change in initialValue
|
|
||||||
useEffect(() => {
|
|
||||||
setCode(initialValue);
|
|
||||||
}, [initialValue]);
|
|
||||||
|
|
||||||
// handle dirty state on change
|
|
||||||
useEffect(() => {
|
|
||||||
if (initialValue.trim() !== code.trim() && !isDirty && code.length !== 0) {
|
|
||||||
setIsDirty(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (initialValue.trim() === code.trim() && isDirty) {
|
|
||||||
setIsDirty(false);
|
|
||||||
}
|
|
||||||
}, [initialValue, code, isDirty, setIsDirty]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.wrapper}>
|
<div className={style.wrapper}>
|
||||||
<Editor
|
<Editor
|
||||||
value={code}
|
value={value}
|
||||||
padding={15}
|
padding={15}
|
||||||
onValueChange={handleChange}
|
onValueChange={onChange}
|
||||||
highlight={highlight}
|
highlight={highlight}
|
||||||
style={{
|
style={{
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
minHeight: 500,
|
minHeight: 500,
|
||||||
background: '#2d2d2d', // Background of tomorrow theme
|
background: 'transparent',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
CodeEditor.displayName = 'StyleEditor';
|
|
||||||
|
|
||||||
export default memo(CodeEditor);
|
|
||||||
|
|||||||
+9
@@ -13,3 +13,12 @@
|
|||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.editorBody {
|
||||||
|
position: relative;
|
||||||
|
min-height: 500px;
|
||||||
|
background-color: $gray-1350;
|
||||||
|
border: 1px solid $gray-1100;
|
||||||
|
border-radius: 3px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|||||||
+39
-19
@@ -1,4 +1,4 @@
|
|||||||
import { Suspense, lazy, useEffect, useRef, useState } from 'react';
|
import { Suspense, lazy, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { getCSSContents, postCSSContents, restoreCSSContents } from '../../../../../common/api/assets';
|
import { getCSSContents, postCSSContents, restoreCSSContents } from '../../../../../common/api/assets';
|
||||||
import Button from '../../../../../common/components/buttons/Button';
|
import Button from '../../../../../common/components/buttons/Button';
|
||||||
@@ -15,24 +15,21 @@ interface CodeEditorModalProps {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CSSRef {
|
|
||||||
getCss: () => string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function CodeEditorModal({ isOpen, onClose }: CodeEditorModalProps) {
|
export default function CodeEditorModal({ isOpen, onClose }: CodeEditorModalProps) {
|
||||||
const [css, setCSS] = useState('');
|
const [savedCss, setSavedCss] = useState('');
|
||||||
const [isDirty, setIsDirty] = useState(false);
|
const [draftCss, setDraftCss] = useState('');
|
||||||
|
const [isLoadingCss, setIsLoadingCss] = useState(false);
|
||||||
const [saveLoading, setSaveLoading] = useState(false);
|
const [saveLoading, setSaveLoading] = useState(false);
|
||||||
const [resetLoading, setResetLoading] = useState(false);
|
const [resetLoading, setResetLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
const cssRef = useRef<CSSRef>(null);
|
const isDirty = savedCss.trim() !== draftCss.trim();
|
||||||
|
|
||||||
const handleRestore = async () => {
|
const handleRestore = async () => {
|
||||||
try {
|
try {
|
||||||
setResetLoading(true);
|
setResetLoading(true);
|
||||||
const defaultCss = await restoreCSSContents();
|
const defaultCss = await restoreCSSContents();
|
||||||
setCSS(defaultCss);
|
setDraftCss(defaultCss);
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
/** no error handling for now */
|
/** no error handling for now */
|
||||||
} finally {
|
} finally {
|
||||||
@@ -43,11 +40,8 @@ export default function CodeEditorModal({ isOpen, onClose }: CodeEditorModalProp
|
|||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
try {
|
try {
|
||||||
setSaveLoading(true);
|
setSaveLoading(true);
|
||||||
if (cssRef.current) {
|
await postCSSContents(draftCss);
|
||||||
await postCSSContents(cssRef.current.getCss());
|
setSavedCss(draftCss);
|
||||||
setCSS(cssRef.current.getCss());
|
|
||||||
setIsDirty(false);
|
|
||||||
}
|
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
/** no error handling for now */
|
/** no error handling for now */
|
||||||
} finally {
|
} finally {
|
||||||
@@ -55,22 +49,43 @@ export default function CodeEditorModal({ isOpen, onClose }: CodeEditorModalProp
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const clear = () => setCSS('');
|
const clear = () => setDraftCss('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
let isCancelled = false;
|
||||||
|
|
||||||
async function fetchServerCSS() {
|
async function fetchServerCSS() {
|
||||||
// check for isOpen to fetch recent css
|
// check for isOpen to fetch recent css
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
try {
|
try {
|
||||||
|
setError(null);
|
||||||
|
setIsLoadingCss(true);
|
||||||
const css = await getCSSContents();
|
const css = await getCSSContents();
|
||||||
setCSS(css);
|
if (isCancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSavedCss(css);
|
||||||
|
setDraftCss(css);
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
|
if (isCancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSavedCss('');
|
||||||
|
setDraftCss('');
|
||||||
setError('Failed to load CSS from server');
|
setError('Failed to load CSS from server');
|
||||||
/** no error handling for now */
|
/** no error handling for now */
|
||||||
|
} finally {
|
||||||
|
if (!isCancelled) {
|
||||||
|
setIsLoadingCss(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fetchServerCSS();
|
fetchServerCSS();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
isCancelled = true;
|
||||||
|
};
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -81,9 +96,14 @@ export default function CodeEditorModal({ isOpen, onClose }: CodeEditorModalProp
|
|||||||
showCloseButton
|
showCloseButton
|
||||||
showBackdrop
|
showBackdrop
|
||||||
bodyElements={
|
bodyElements={
|
||||||
<Suspense fallback={null}>
|
<div className={style.editorBody}>
|
||||||
<CodeEditor ref={cssRef} initialValue={css} language='css' isDirty={isDirty} setIsDirty={setIsDirty} />
|
{!isLoadingCss && (
|
||||||
</Suspense>
|
<Suspense fallback={<Panel.Loader isLoading />}>
|
||||||
|
<CodeEditor value={draftCss} onChange={setDraftCss} language='css' />
|
||||||
|
</Suspense>
|
||||||
|
)}
|
||||||
|
<Panel.Loader isLoading={isLoadingCss} />
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
footerElements={
|
footerElements={
|
||||||
<div className={style.column}>
|
<div className={style.column}>
|
||||||
|
|||||||
Reference in New Issue
Block a user