mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-21 16:38:23 +01:00
flash error messages (#369)
This commit is contained in:
parent
936d4bfb30
commit
174cf3d39d
@ -100,3 +100,44 @@ a {
|
||||
transition-delay: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.flash-error-container {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
z-index: var(--zindex-flash-error-container);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
.flash-error {
|
||||
background: var(--error-color);
|
||||
color: var(--main-text-color);
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 280px;
|
||||
border: 1px solid transparent;
|
||||
max-height: 100px;
|
||||
cursor: pointer;
|
||||
|
||||
.flash-error-scroll {
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&.hovered {
|
||||
border: 1px solid var(--main-text-color);
|
||||
}
|
||||
|
||||
.flash-error-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.flash-error-message {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
import { useWaveObjectValue } from "@/app/store/wos";
|
||||
import { Workspace } from "@/app/workspace/workspace";
|
||||
import { ContextMenuModel } from "@/store/contextmenu";
|
||||
import { PLATFORM, WOS, atoms, getApi, globalStore, useSettingsPrefixAtom } from "@/store/global";
|
||||
import { PLATFORM, WOS, atoms, getApi, globalStore, removeFlashError, useSettingsPrefixAtom } from "@/store/global";
|
||||
import { appHandleKeyDown } from "@/store/keymodel";
|
||||
import { getWebServerEndpoint } from "@/util/endpoints";
|
||||
import { getElemAsStr } from "@/util/focusutil";
|
||||
@ -251,6 +251,78 @@ const AppKeyHandlers = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const FlashError = () => {
|
||||
const flashErrors = jotai.useAtomValue(atoms.flashErrors);
|
||||
const [hoveredId, setHoveredId] = React.useState<string>(null);
|
||||
const [ticker, setTicker] = React.useState<number>(0);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (flashErrors.length == 0 || hoveredId != null) {
|
||||
return;
|
||||
}
|
||||
const now = Date.now();
|
||||
for (let ferr of flashErrors) {
|
||||
if (ferr.expiration == null || ferr.expiration < now) {
|
||||
removeFlashError(ferr.id);
|
||||
}
|
||||
}
|
||||
setTimeout(() => setTicker(ticker + 1), 1000);
|
||||
}, [flashErrors, ticker, hoveredId]);
|
||||
|
||||
if (flashErrors.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function copyError(id: string) {
|
||||
const ferr = flashErrors.find((f) => f.id === id);
|
||||
if (ferr == null) {
|
||||
return;
|
||||
}
|
||||
let text = "";
|
||||
if (ferr.title != null) {
|
||||
text += ferr.title;
|
||||
}
|
||||
if (ferr.message != null) {
|
||||
if (text.length > 0) {
|
||||
text += "\n";
|
||||
}
|
||||
text += ferr.message;
|
||||
}
|
||||
navigator.clipboard.writeText(text);
|
||||
}
|
||||
|
||||
function convertNewlinesToBreaks(text) {
|
||||
return text.split("\n").map((part, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{part}
|
||||
<br />
|
||||
</React.Fragment>
|
||||
));
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flash-error-container">
|
||||
{flashErrors.map((err, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className={clsx("flash-error", { hovered: hoveredId === err.id })}
|
||||
onClick={() => copyError(err.id)}
|
||||
onMouseEnter={() => setHoveredId(err.id)}
|
||||
onMouseLeave={() => setHoveredId(null)}
|
||||
title="Click to Copy Error Message"
|
||||
>
|
||||
<div className="flash-error-scroll">
|
||||
{err.title != null ? <div className="flash-error-title">{err.title}</div> : null}
|
||||
{err.message != null ? (
|
||||
<div className="flash-error-message">{convertNewlinesToBreaks(err.message)}</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const AppInner = () => {
|
||||
const prefersReducedMotion = jotai.useAtomValue(atoms.prefersReducedMotionAtom);
|
||||
const client = jotai.useAtomValue(atoms.client);
|
||||
@ -281,6 +353,7 @@ const AppInner = () => {
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<Workspace />
|
||||
</DndProvider>
|
||||
<FlashError />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -148,6 +148,7 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) {
|
||||
const connStatuses = Array.from(ConnStatusMap.values()).map((atom) => get(atom));
|
||||
return connStatuses;
|
||||
});
|
||||
const flashErrorsAtom = atom<FlashErrorType[]>([]);
|
||||
atoms = {
|
||||
// initialized in wave.ts (will not be null inside of application)
|
||||
windowId: windowIdAtom,
|
||||
@ -167,6 +168,7 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) {
|
||||
typeAheadModalAtom,
|
||||
modalOpen,
|
||||
allConnStatus: allConnStatusAtom,
|
||||
flashErrors: flashErrorsAtom,
|
||||
};
|
||||
}
|
||||
|
||||
@ -504,6 +506,22 @@ function getConnStatusAtom(conn: string): PrimitiveAtom<ConnStatus> {
|
||||
return rtn;
|
||||
}
|
||||
|
||||
function pushFlashError(ferr: FlashErrorType) {
|
||||
if (ferr.expiration == null) {
|
||||
ferr.expiration = Date.now() + 5000;
|
||||
}
|
||||
ferr.id = crypto.randomUUID();
|
||||
globalStore.set(atoms.flashErrors, (prev) => {
|
||||
return [...prev, ferr];
|
||||
});
|
||||
}
|
||||
|
||||
function removeFlashError(id: string) {
|
||||
globalStore.set(atoms.flashErrors, (prev) => {
|
||||
return prev.filter((ferr) => ferr.id !== id);
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
atoms,
|
||||
counterInc,
|
||||
@ -524,8 +542,10 @@ export {
|
||||
loadConnStatus,
|
||||
openLink,
|
||||
PLATFORM,
|
||||
pushFlashError,
|
||||
refocusNode,
|
||||
registerBlockComponentModel,
|
||||
removeFlashError,
|
||||
setNodeFocus,
|
||||
setPlatform,
|
||||
subscribeToConnEvents,
|
||||
|
@ -57,6 +57,7 @@
|
||||
--zindex-layout-overlay-container: 4;
|
||||
--zindex-layout-magnified-node: 5;
|
||||
--zindex-block-mask-inner: 10;
|
||||
--zindex-flash-error-container: 550;
|
||||
|
||||
// z-indexes in xterm.css
|
||||
// xterm-helpers: 5
|
||||
|
9
frontend/types/custom.d.ts
vendored
9
frontend/types/custom.d.ts
vendored
@ -23,6 +23,7 @@ declare global {
|
||||
typeAheadModalAtom: jotai.PrimitiveAtom<TypeAheadModalType>;
|
||||
modalOpen: jotai.PrimitiveAtom<boolean>;
|
||||
allConnStatus: jotai.Atom<ConnStatus[]>;
|
||||
flashErrors: jotai.PrimitiveAtom<FlashErrorType[]>;
|
||||
};
|
||||
|
||||
type WritableWaveObjectAtom<T extends WaveObj> = jotai.WritableAtom<T, [value: T], void>;
|
||||
@ -273,6 +274,14 @@ declare global {
|
||||
connName: string;
|
||||
baseDir: string;
|
||||
};
|
||||
|
||||
type FlashErrorType = {
|
||||
id: string;
|
||||
icon: string;
|
||||
title: string;
|
||||
message: string;
|
||||
expiration: number;
|
||||
};
|
||||
}
|
||||
|
||||
export {};
|
||||
|
@ -22,6 +22,7 @@ import {
|
||||
initGlobal,
|
||||
initGlobalWaveEventSubs,
|
||||
loadConnStatus,
|
||||
pushFlashError,
|
||||
subscribeToConnEvents,
|
||||
} from "@/store/global";
|
||||
import * as WOS from "@/store/wos";
|
||||
@ -51,6 +52,7 @@ loadFonts();
|
||||
(window as any).countersPrint = countersPrint;
|
||||
(window as any).countersClear = countersClear;
|
||||
(window as any).getLayoutModelForActiveTab = getLayoutModelForActiveTab;
|
||||
(window as any).pushFlashError = pushFlashError;
|
||||
|
||||
document.title = `The Next Wave (${windowId.substring(0, 8)})`;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user