Skip to content

Commit

Permalink
implement a config error button + message modal that shows the errors (
Browse files Browse the repository at this point in the history
  • Loading branch information
sawka authored Oct 14, 2024
1 parent a15b339 commit a629b28
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 5 deletions.
6 changes: 6 additions & 0 deletions frontend/app/modals/messagemodal.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

.message-modal {
min-width: 400px;
}
24 changes: 24 additions & 0 deletions frontend/app/modals/messagemodal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

import { Modal } from "@/app/modals/modal";
import { modalsModel } from "@/app/store/modalmodel";

import { ReactNode } from "react";
import "./messagemodal.less";

const MessageModal = ({ children }: { children: ReactNode }) => {
function closeModal() {
modalsModel.popModal();
}

return (
<Modal className="message-modal" onOk={() => closeModal()} onClose={() => closeModal()}>
{children}
</Modal>
);
};

MessageModal.displayName = "MessageModal";

export { MessageModal };
2 changes: 2 additions & 0 deletions frontend/app/modals/modalregistry.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

import { MessageModal } from "@/app/modals/messagemodal";
import { AboutModal } from "./about";
import { TosModal } from "./tos";
import { UserInputModal } from "./userinputmodal";
Expand All @@ -9,6 +10,7 @@ const modalRegistry: { [key: string]: React.ComponentType<any> } = {
[TosModal.displayName || "TosModal"]: TosModal,
[UserInputModal.displayName || "UserInputModal"]: UserInputModal,
[AboutModal.displayName || "AboutModal"]: AboutModal,
[MessageModal.displayName || "MessageModal"]: MessageModal,
};

export const getModalComponent = (key: string): React.ComponentType<any> | undefined => {
Expand Down
17 changes: 17 additions & 0 deletions frontend/app/tab/tabbar.less
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@
}
}

.config-error-message {
max-width: 500px;

h3 {
margin-bottom: 10px;
}

margin-bottom: 20px;
}

.tab-bar-wrapper {
position: relative;
user-select: none;
Expand Down Expand Up @@ -53,6 +63,13 @@
color: var(--accent-color);
}

.config-error-button {
height: 80%;
margin: auto 4px;
color: black;
flex: 0 0 fit-content;
}

.add-tab-btn {
width: 22px;
height: 100%;
Expand Down
71 changes: 70 additions & 1 deletion frontend/app/tab/tabbar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

import { Button } from "@/app/element/button";
import { modalsModel } from "@/app/store/modalmodel";
import { WindowDrag } from "@/element/windowdrag";
import { deleteLayoutModelForTab } from "@/layout/index";
import { atoms, getApi, isDev, PLATFORM } from "@/store/global";
Expand Down Expand Up @@ -37,6 +39,69 @@ interface TabBarProps {
workspace: Workspace;
}

const ConfigErrorMessage = () => {
const fullConfig = useAtomValue(atoms.fullConfigAtom);

if (fullConfig?.configerrors == null || fullConfig?.configerrors.length == 0) {
return (
<div className="config-error-message">
<h3>Configuration Clean</h3>
<p>There are no longer any errors detected in your config.</p>
</div>
);
}
if (fullConfig?.configerrors.length == 1) {
const singleError = fullConfig.configerrors[0];
return (
<div className="config-error-message">
<h3>Configuration Error</h3>
<div>
{singleError.file}: {singleError.err}
</div>
</div>
);
}
return (
<div className="config-error-message">
<h3>Configuration Error</h3>
<ul>
{fullConfig.configerrors.map((error, index) => (
<li key={index}>
{error.file}: {error.err}
</li>
))}
</ul>
</div>
);
};

const ConfigErrorIcon = ({ buttonRef }: { buttonRef: React.RefObject<HTMLElement> }) => {
const fullConfig = useAtomValue(atoms.fullConfigAtom);

function handleClick() {
modalsModel.pushModal("MessageModal", { children: <ConfigErrorMessage /> });
}

if (fullConfig?.configerrors == null || fullConfig?.configerrors.length == 0) {
return null;
}
return (
<Button
ref={buttonRef as React.RefObject<HTMLButtonElement>}
className="config-error-button red"
onClick={handleClick}
>
<i className="fa fa-solid fa-exclamation-triangle" />
Config Error
</Button>
);
return (
<div className="config-error" ref={buttonRef as React.RefObject<HTMLDivElement>}>
<i className="fa fa-solid fa-exclamation-triangle" />
</div>
);
};

const TabBar = React.memo(({ workspace }: TabBarProps) => {
const [tabIds, setTabIds] = useState<string[]>([]);
const [dragStartPositions, setDragStartPositions] = useState<number[]>([]);
Expand Down Expand Up @@ -67,6 +132,7 @@ const TabBar = React.memo(({ workspace }: TabBarProps) => {
const tabWidthRef = useRef<number>(TAB_DEFAULT_WIDTH);
const scrollableRef = useRef<boolean>(false);
const updateStatusButtonRef = useRef<HTMLButtonElement>(null);
const configErrorButtonRef = useRef<HTMLElement>(null);
const prevAllLoadedRef = useRef<boolean>(false);

const windowData = useAtomValue(atoms.waveWindow);
Expand Down Expand Up @@ -124,8 +190,10 @@ const TabBar = React.memo(({ workspace }: TabBarProps) => {
const windowDragLeftWidth = draggerLeftRef.current.getBoundingClientRect().width;
const addBtnWidth = addBtnRef.current.getBoundingClientRect().width;
const updateStatusLabelWidth = updateStatusButtonRef.current?.getBoundingClientRect().width ?? 0;
const configErrorWidth = configErrorButtonRef.current?.getBoundingClientRect().width ?? 0;
const spaceForTabs =
tabbarWrapperWidth - (windowDragLeftWidth + DRAGGER_RIGHT_MIN_WIDTH + addBtnWidth + updateStatusLabelWidth);
tabbarWrapperWidth -
(windowDragLeftWidth + DRAGGER_RIGHT_MIN_WIDTH + addBtnWidth + updateStatusLabelWidth + configErrorWidth);

const numberOfTabs = tabIds.length;
const totalDefaultTabWidth = numberOfTabs * TAB_DEFAULT_WIDTH;
Expand Down Expand Up @@ -510,6 +578,7 @@ const TabBar = React.memo(({ workspace }: TabBarProps) => {
</div>
<WindowDrag ref={draggerRightRef} className="right" />
<UpdateStatusBanner buttonRef={updateStatusButtonRef} />
<ConfigErrorIcon buttonRef={configErrorButtonRef} />
</div>
);
});
Expand Down
3 changes: 1 addition & 2 deletions frontend/app/tab/updatebanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import "./updatebanner.less";

const UpdateStatusBannerComponent = ({ buttonRef }: { buttonRef: React.RefObject<HTMLButtonElement> }) => {
const appUpdateStatus = useAtomValue(atoms.updaterStatusAtom);
const [updateStatusMessage, setUpdateStatusMessage] = useState<string>();
let [updateStatusMessage, setUpdateStatusMessage] = useState<string>();
const [dismissBannerTimeout, setDismissBannerTimeout] = useState<NodeJS.Timeout>();

useEffect(() => {
Expand Down Expand Up @@ -52,7 +52,6 @@ const UpdateStatusBannerComponent = ({ buttonRef }: { buttonRef: React.RefObject
function onClick() {
getApi().installAppUpdate();
}

if (updateStatusMessage) {
return (
<Button
Expand Down
14 changes: 14 additions & 0 deletions pkg/util/utilfn/utilfn.go
Original file line number Diff line number Diff line change
Expand Up @@ -932,3 +932,17 @@ func WriteFileIfDifferent(fileName string, contents []byte) (bool, error) {
}
return true, nil
}

func GetLineColFromOffset(barr []byte, offset int) (int, int) {
line := 1
col := 1
for i := 0; i < offset && i < len(barr); i++ {
if barr[i] == '\n' {
line++
col = 1
} else {
col++
}
}
return line, col
}
51 changes: 49 additions & 2 deletions pkg/wconfig/settingsconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ import (

const SettingsFile = "settings.json"

const AnySchema = `
{
"type": "object",
"additionalProperties": true
}
`

type MetaSettingsType struct {
waveobj.MetaMapType
}
Expand Down Expand Up @@ -117,18 +124,58 @@ type FullConfigType struct {
ConfigErrors []ConfigError `json:"configerrors" configfile:"-"`
}

func goBackWS(barr []byte, offset int) int {
if offset >= len(barr) {
offset = offset - 1
}
for i := offset - 1; i >= 0; i-- {
if barr[i] == ' ' || barr[i] == '\t' || barr[i] == '\n' || barr[i] == '\r' {
continue
}
return i
}
return 0
}

func isTrailingCommaError(barr []byte, offset int) bool {
if offset >= len(barr) {
offset = offset - 1
}
offset = goBackWS(barr, offset)
if barr[offset] == '}' {
offset = goBackWS(barr, offset)
if barr[offset] == ',' {
return true
}
}
return false
}

func readConfigHelper(fileName string, barr []byte, readErr error) (waveobj.MetaMapType, []ConfigError) {
var cerrs []ConfigError
if readErr != nil && !os.IsNotExist(readErr) {
cerrs = append(cerrs, ConfigError{File: "defaults:" + fileName, Err: readErr.Error()})
cerrs = append(cerrs, ConfigError{File: fileName, Err: readErr.Error()})
}
if len(barr) == 0 {
return nil, cerrs
}
var rtn waveobj.MetaMapType
err := json.Unmarshal(barr, &rtn)
if err != nil {
cerrs = append(cerrs, ConfigError{File: "defaults:" + fileName, Err: err.Error()})
if syntaxErr, ok := err.(*json.SyntaxError); ok {
offset := syntaxErr.Offset
if offset > 0 {
offset = offset - 1
}
lineNum, colNum := utilfn.GetLineColFromOffset(barr, int(offset))
isTrailingComma := isTrailingCommaError(barr, int(offset))
if isTrailingComma {
err = fmt.Errorf("json syntax error at line %d, col %d: probably an extra trailing comma: %v", lineNum, colNum, syntaxErr)
} else {
err = fmt.Errorf("json syntax error at line %d, col %d: %v", lineNum, colNum, syntaxErr)
}
}
cerrs = append(cerrs, ConfigError{File: fileName, Err: err.Error()})
}
return rtn, cerrs
}
Expand Down

0 comments on commit a629b28

Please sign in to comment.