feat: add max upload size setting to UI & UI improvements (#1646)

* Add preliminar Windows support for both
development and production environments.

Default profile.Data will be set to "C:\ProgramData\memos" on Windows.
Folder will be created if it does not exist, as this behavior is
expected for Windows applications.

System service installation can be achieved with third-party tools,
explained in docs/windows-service.md.

Not sure if it's worth using https://github.com/kardianos/service
to make service support built-in.

This could be a nice addition alongside #1583 (add Windows artifacts)

* feat: improve Windows support

- Fix local file storage path handling on Windows

- Improve Windows dev script

* feat: add max upload size setting to UI & more

- feat: add max upload size setting to UI

- feat: max upload size setting is checked on UI during upload,
but also enforced by the server

- fix: overflowing mobile layout for Create SSO, Create Storage
and other Settings dialogs

- feat: add HelpButton component with some links to docs were appropriate

- remove LearnMore component in favor of HelpButton

- refactor: change some if/else to switch statements

- refactor: inline some err == nil checks

! Existing databases without the new setting 'max-upload-size-mib'
will show an upload error, but this can be user-fixed by simply
setting the value on system settings UI.

* improvements requested by @boojack
This commit is contained in:
Lincoln Nogueira
2023-05-13 11:27:28 -03:00
committed by GitHub
parent 5c5199920e
commit 96021e518a
20 changed files with 591 additions and 204 deletions

View File

@ -1,11 +1,11 @@
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { Button, Divider, Input, Switch, Textarea, Typography } from "@mui/joy";
import { Button, Divider, Input, Switch, Textarea } from "@mui/joy";
import { formatBytes } from "@/helpers/utils";
import { useGlobalStore } from "@/store/module";
import * as api from "@/helpers/api";
import Icon from "../Icon";
import HelpButton from "../kit/HelpButton";
import showUpdateCustomizedProfileDialog from "../UpdateCustomizedProfileDialog";
import "@/less/settings/system-section.less";
@ -16,6 +16,7 @@ interface State {
disablePublicMemos: boolean;
additionalStyle: string;
additionalScript: string;
maxUploadSizeMiB: number;
}
const SystemSection = () => {
@ -29,6 +30,7 @@ const SystemSection = () => {
additionalStyle: systemStatus.additionalStyle,
additionalScript: systemStatus.additionalScript,
disablePublicMemos: systemStatus.disablePublicMemos,
maxUploadSizeMiB: systemStatus.maxUploadSizeMiB,
});
const [openAIConfig, setOpenAIConfig] = useState<OpenAIConfig>({
key: "",
@ -56,6 +58,7 @@ const SystemSection = () => {
additionalStyle: systemStatus.additionalStyle,
additionalScript: systemStatus.additionalScript,
disablePublicMemos: systemStatus.disablePublicMemos,
maxUploadSizeMiB: systemStatus.maxUploadSizeMiB,
});
}, [systemStatus]);
@ -175,6 +178,30 @@ const SystemSection = () => {
});
};
const handleMaxUploadSizeChanged = async (event: React.FocusEvent<HTMLInputElement>) => {
// fixes cursor skipping position on mobile
event.target.selectionEnd = event.target.value.length;
let num = parseInt(event.target.value);
if (Number.isNaN(num)) {
num = 0;
}
setState({
...state,
maxUploadSizeMiB: num,
});
event.target.value = num.toString();
globalStore.setSystemStatus({ maxUploadSizeMiB: num });
await api.upsertSystemSetting({
name: "max-upload-size-mib",
value: JSON.stringify(num),
});
};
const handleMaxUploadSizeFocus = (event: React.FocusEvent<HTMLInputElement>) => {
event.target.select();
};
return (
<div className="section-container system-section-container">
<p className="title-text">{t("common.basic")}</p>
@ -185,7 +212,7 @@ const SystemSection = () => {
<Button onClick={handleUpdateCustomizedProfileButtonClick}>{t("common.edit")}</Button>
</div>
<div className="form-label">
<span className="normal-text">
<span className="text-sm">
{t("setting.system-section.database-file-size")}: <span className="font-mono font-bold">{formatBytes(state.dbSize)}</span>
</span>
<Button onClick={handleVacuumBtnClick}>{t("common.vacuum")}</Button>
@ -203,19 +230,27 @@ const SystemSection = () => {
<span className="normal-text">{t("setting.system-section.disable-public-memos")}</span>
<Switch checked={state.disablePublicMemos} onChange={(event) => handleDisablePublicMemosChanged(event.target.checked)} />
</div>
<div className="form-label">
<div className="flex flex-row">
<span className="normal-text">{t("setting.system-section.max-upload-size")}</span>
<HelpButton icon="info" hint={t("setting.system-section.max-upload-size-hint")} hintPlacement="left" />
</div>
<Input
className="w-16"
sx={{
fontFamily: "monospace",
}}
defaultValue={state.maxUploadSizeMiB}
onFocus={handleMaxUploadSizeFocus}
onChange={handleMaxUploadSizeChanged}
/>
</div>
<Divider className="!mt-3 !my-4" />
<div className="form-label">
<span className="normal-text">{t("setting.system-section.openai-api-key")}</span>
<Typography className="!mb-1" level="body2">
<a
className="ml-2 text-sm text-blue-600 hover:opacity-80 hover:underline"
href="https://platform.openai.com/account/api-keys"
target="_blank"
>
{t("setting.system-section.openai-api-key-description")}
<Icon.ExternalLink className="inline -mt-1 ml-1 w-4 h-auto opacity-80" />
</a>
</Typography>
<div className="flex flex-row">
<span className="normal-text">{t("setting.system-section.openai-api-key")}</span>
<HelpButton hint={t("setting.system-section.openai-api-key-description")} url="https://platform.openai.com/account/api-keys" />
</div>
<Button onClick={handleSaveOpenAIConfig}>{t("common.save")}</Button>
</div>
<Input