UI Refactor.

This commit is contained in:
Patrick Fic
2026-01-12 16:06:13 -08:00
parent 8bf8d0bcac
commit ea4131102c
25 changed files with 160 additions and 159 deletions

View File

@@ -1,5 +1,5 @@
import "@ant-design/v5-patch-for-react-19";
import { Badge, ConfigProvider, Layout, Skeleton } from "antd";
import { ConfigProvider, Layout, Space } from "antd";
import { FC } from "react";
import { ErrorBoundary } from "react-error-boundary";
import { Provider } from "react-redux";
@@ -11,17 +11,15 @@ import reduxStore from "./redux/redux-store";
import { NotificationProvider } from "./util/notificationContext";
const App: FC = () => {
const isTest = window.api.isTest();
return (
<ConfigProvider
theme={{
token: {},
token: {
borderRadius: 8,
},
components: {
Card: {
borderRadius: 8,
colorBgBase: "#ffaacc",
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.1)",
},
},
}}
@@ -30,21 +28,14 @@ const App: FC = () => {
<HashRouter>
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
<NotificationProvider>
<Skeleton loading={false} active>
<Layout style={{ minHeight: "100vh" }}>
<Badge.Ribbon
text={isTest && "Connected to Test"}
color={isTest ? "red" : undefined}
>
<Layout.Content style={{ padding: "0 24px" }}>
<UpdateAvailable />
<Routes>
<Route path="/" element={<Settings />} />
</Routes>
</Layout.Content>
</Badge.Ribbon>
</Layout>
</Skeleton>
<Layout style={{ minHeight: "100vh", background: "#f0f2f5" }}>
<Layout.Content style={{ padding: "24px" }}>
<UpdateAvailable />
<Routes>
<Route path="/" element={<Settings />} />
</Routes>
</Layout.Content>
</Layout>
</NotificationProvider>
</ErrorBoundary>
</HashRouter>

View File

@@ -1,4 +1,4 @@
import { Card, Form, Input } from "antd";
import { Card, Form, Input, Space } from "antd";
import { FC, useEffect } from "react";
import { useTranslation } from "react-i18next";
import ipcTypes from "../../../../util/ipcTypes.json";
@@ -11,9 +11,11 @@ const SettingsConfig: FC = () => {
{
name: "esApiKey",
label: t("settings.labels.esApiKey"),
component: Input,
component: Input.Password,
hasFeedback: true,
componentProps: {},
componentProps: {
placeholder: "Enter your API key",
},
},
];
@@ -52,24 +54,30 @@ const SettingsConfig: FC = () => {
return (
<Card title={t("settings.labels.config")}>
<Form form={form} onFieldsChange={handleFieldChange} layout="vertical">
{settingFields.map((field) => (
<Form.Item
key={field.name}
label={t(`settings.labels.${field.name}`, field.label)}
name={field.name}
hasFeedback={field.hasFeedback}
validateStatus="validating"
rules={[
{
required: true,
message: t(`settings.validation.${field.name}Required`),
},
]}
>
<field.component {...field.componentProps} />
</Form.Item>
))}
<Form
form={form}
onFieldsChange={handleFieldChange}
layout="vertical"
size="large"
>
<Space direction="vertical" size="middle" style={{ width: "100%" }}>
{settingFields.map((field) => (
<Form.Item
key={field.name}
label={t(`settings.labels.${field.name}`, field.label)}
name={field.name}
hasFeedback={field.hasFeedback}
rules={[
{
required: true,
message: t(`settings.validation.${field.name}Required`),
},
]}
>
<field.component {...field.componentProps} />
</Form.Item>
))}
</Space>
</Form>
</Card>
);

View File

@@ -1,5 +1,5 @@
import { DeleteFilled, FileAddFilled } from "@ant-design/icons";
import { Button, Card, Space, Timeline } from "antd";
import { DeleteOutlined, FolderAddOutlined } from "@ant-design/icons";
import { Button, Card, Empty, List, Space, Typography } from "antd";
import { useEffect, useState, FC } from "react";
import { useTranslation } from "react-i18next";
import ipcTypes from "../../../../util/ipcTypes.json";
@@ -33,32 +33,46 @@ const SettingsWatchedPaths: FC = () => {
};
return (
<Card
title={t("settings.labels.watchedpaths")}
extra={
<Button onClick={handleAddPath} icon={<FileAddFilled />}>
{t("settings.actions.addpath")}
</Button>
}
>
<Timeline
items={watchedPaths.map((path, index) => ({
key: index,
children: (
<Space align="baseline">
{path}
<>
<List
locale={{
emptyText: (
<Empty
description={"No watched paths configured"}
image={Empty.PRESENTED_IMAGE_SIMPLE}
/>
),
}}
dataSource={watchedPaths}
renderItem={(path) => (
<List.Item
actions={[
<Button
size="small"
key="delete"
danger
type="text"
icon={<DeleteFilled />}
icon={<DeleteOutlined />}
onClick={() => handleRemovePath(path)}
/>
</Space>
),
}))}
>
Remove
</Button>,
]}
>
<Typography.Text code copyable>
{path}
</Typography.Text>
</List.Item>
)}
/>
</Card>
<Button
type="dashed"
style={{ width: "100%", marginTop: 16 }}
onClick={handleAddPath}
icon={<FolderAddOutlined />}
>
{t("settings.actions.addpath")}
</Button>
</>
);
};
export default SettingsWatchedPaths;

View File

@@ -14,20 +14,19 @@ import {
Badge,
Button,
Card,
Col,
Divider,
Flex,
InputNumber,
Row,
Radio,
RadioChangeEvent,
Space,
Switch,
Typography,
} from "antd";
import { FC, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import ipcTypes from "../../../../util/ipcTypes.json";
const colSpans = {
md: 12,
sm: 24,
};
import SettingsWatchedPaths from "./Settings.WatchedPaths";
const SettingsWatcher: FC = () => {
const { t } = useTranslation();
@@ -63,10 +62,10 @@ const SettingsWatcher: FC = () => {
window.electron.ipcRenderer.send(ipcTypes.toMain.watcher.stop);
};
const toggleWatcherMode = (checked: boolean): void => {
const toggleWatcherMode = (e: RadioChangeEvent): void => {
window.electron.ipcRenderer
.invoke(ipcTypes.toMain.settings.watcher.setpolling, {
enabled: !checked,
enabled: !e.target.value,
interval: pollingState.interval,
})
.then((storePollingState: { enabled: boolean; interval: number }) => {
@@ -89,69 +88,70 @@ const SettingsWatcher: FC = () => {
};
return (
<Badge.Ribbon
text={
<Card
title={t("settings.labels.watcherstatus")}
extra={
isWatcherStarted ? (
<Space>
<CheckCircleFilled />
{t("settings.labels.started")}
</Space>
<Button
danger
block
icon={<PauseCircleOutlined />}
onClick={handleStop}
>
{t("settings.actions.stopwatcher")}
</Button>
) : (
<Space>
<ExclamationCircleFilled />
{t("settings.labels.stopped")}
</Space>
<Button
type="primary"
block
icon={<PlayCircleOutlined />}
onClick={handleStart}
>
{t("settings.actions.startwatcher")}
</Button>
)
}
color={isWatcherStarted ? "green" : "red"}
>
<Card title={t("settings.labels.watcherstatus")}>
<Row gutter={[16, 16]}>
<Col {...colSpans}>
{isWatcherStarted ? (
<Button
danger
icon={<PauseCircleOutlined />}
onClick={handleStop}
>
{t("settings.actions.stopwatcher")}
</Button>
) : (
<Button
type="primary"
icon={<PlayCircleOutlined />}
onClick={handleStart}
>
{t("settings.actions.startwatcher")}
</Button>
)}
</Col>
<Col {...colSpans}>
<Space direction="vertical" wrap>
<Switch
checked={!pollingState.enabled}
onChange={toggleWatcherMode}
checkedChildren={t("settings.labels.watchermoderealtime")}
unCheckedChildren={t("settings.labels.watchermodepolling")}
/>
{pollingState.enabled && (
<Space size="small" direction="vertical" wrap>
<span>{t("settings.labels.pollinginterval")}</span>
<InputNumber
title={t("settings.labels.pollinginterval")}
disabled={!pollingState.enabled}
min={1000}
value={pollingState.interval}
onChange={handlePollingIntervalChange}
/>
</Space>
)}
{watcherError && <Alert message={watcherError} />}
</Space>
</Col>
</Row>
</Card>
</Badge.Ribbon>
<Space orientation="vertical" size="middle" style={{ width: "100%" }}>
<SettingsWatchedPaths />
<Radio.Group
block
options={[
{ label: "Real-time", value: true },
{ label: "Polling", value: false },
]}
value={!pollingState.enabled}
onChange={toggleWatcherMode}
optionType="button"
buttonStyle="solid"
/>
{pollingState.enabled && (
<Flex align="center" gap="middle">
<Typography.Text>
{t("settings.labels.pollinginterval")}
</Typography.Text>
<InputNumber
min={1000}
step={1000}
value={pollingState.interval}
onChange={handlePollingIntervalChange}
style={{ width: 150 }}
/>
</Flex>
)}
{watcherError && (
<Alert
title="Watcher Error"
description={watcherError}
type="error"
showIcon
/>
)}
</Space>
</Card>
);
};
export default SettingsWatcher;

View File

@@ -1,27 +1,16 @@
// renderer/Settings.tsx
import { Col, Row } from "antd";
import { Space } from "antd";
import { FC } from "react";
import SettingsWatchedPaths from "./Settings.WatchedPaths";
import SettingsWatcher from "./Settings.Watcher";
import SettingsConfig from "./Settings.Config";
const colSpans = {
span: 24,
};
import SettingsWatcher from "./Settings.Watcher";
const Settings: FC = () => {
return (
<Row gutter={[16, 16]}>
<Col {...colSpans}>
<SettingsWatchedPaths />
</Col>
<Col {...colSpans}>
<SettingsWatcher />
</Col>
<Col {...colSpans}>
<SettingsConfig />
</Col>
</Row>
<Space orientation="vertical" size="large" style={{ width: "100%" }}>
<SettingsWatcher />
<SettingsConfig />
</Space>
);
};

View File

@@ -7,14 +7,13 @@ import * as Sentry from "@sentry/electron/renderer";
// Extend the Window interface to include the api property
declare global {
interface Window {
api: {
isTest: () => boolean;
};
}
interface Window {
api: {
isTest: () => boolean;
};
}
}
Sentry.init({
dsn: "https://ba41d22656999a8c1fd63bcb7df98650@o492140.ingest.us.sentry.io/4509074139447296",
});