UI Refactor.
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user