web: add toolbar settings in new settings dialog

This commit is contained in:
Abdullah Atta
2023-06-22 13:04:30 +05:00
committed by Abdullah Atta
parent 90ef877942
commit 48371b6364
5 changed files with 198 additions and 198 deletions

View File

@@ -194,12 +194,6 @@ export function showEmailChangeDialog() {
));
}
export function showToolbarConfigDialog() {
return showDialog("ToolbarConfigDialog", (Dialog, perform) => (
<Dialog onClose={() => perform(null)} />
));
}
export function showError(title: string, message: string) {
return confirm({ title, message, positiveButtonText: "Okay" });
}

View File

@@ -23,6 +23,7 @@ import { Loading, Close } from "../icons";
import ReactModal from "react-modal";
import { FlexScrollContainer } from "../scroll-container";
import { SxProp } from "@theme-ui/core";
import { useStore as useThemeStore } from "../../stores/theme-store";
ReactModal.setAppElement("#root");
@@ -52,7 +53,8 @@ type DialogProps = SxProp & {
};
function BaseDialog(props: React.PropsWithChildren<DialogProps>) {
// const theme: any = useTheme();
const theme = useThemeStore((store) => store.theme);
return (
<ReactModal
isOpen={props.isOpen || false}
@@ -95,7 +97,9 @@ function BaseDialog(props: React.PropsWithChildren<DialogProps>) {
justifyContent: "stretch",
position: "relative",
overflow: "hidden",
boxShadow: "4px 5px 18px 2px #00000038",
boxShadow: `0px 0px 25px 5px ${
theme === "dark" ? "#000000aa" : "#0000004e"
}`,
borderRadius: "dialog",
...props.sx

View File

@@ -47,7 +47,6 @@ const RecoveryCodesDialog = React.lazy(
const OnboardingDialog = React.lazy(() => import("./onboarding-dialog"));
const AttachmentsDialog = React.lazy(() => import("./attachments-dialog"));
const Prompt = React.lazy(() => import("./prompt"));
const ToolbarConfigDialog = React.lazy(() => import("./toolbar-config-dialog"));
const MigrationDialog = React.lazy(() => import("./migration-dialog"));
const EmailChangeDialog = React.lazy(() => import("./email-change-dialog"));
const AddTagsDialog = React.lazy(() => import("./add-tags-dialog"));
@@ -55,7 +54,6 @@ const SettingsDialog = React.lazy(() => import("./settings"));
export const Dialogs = {
AddNotebookDialog,
ToolbarConfigDialog,
BuyDialog,
Confirm,
Prompt,

View File

@@ -18,8 +18,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Button, Flex, FlexProps, Text } from "@theme-ui/components";
import { Perform } from "../common/dialog-controller";
import Dialog from "../components/dialog";
import {
getAllTools,
getToolDefinition,
@@ -50,9 +48,9 @@ import { useEffect, useState } from "react";
import { CSS } from "@dnd-kit/utilities";
import { createPortal } from "react-dom";
import { getId } from "@notesnook/core/utils/id";
import { Label, Radio } from "@theme-ui/components";
import { db } from "../common/db";
import { useToolbarConfig } from "../components/editor/context";
import { Label } from "@theme-ui/components";
import { db } from "../../../common/db";
import { useToolbarConfig } from "../../../components/editor/context";
import {
getAllPresets,
getCurrentPreset,
@@ -60,15 +58,12 @@ import {
getPresetTools,
Preset,
PresetId
} from "../common/toolbar-config";
import { showToast } from "../utils/toast";
import { isUserPremium } from "../hooks/use-is-user-premium";
import { Pro } from "../components/icons";
export type ToolbarConfigDialogProps = {
onClose: Perform;
};
} from "../../../common/toolbar-config";
import { showToast } from "../../../utils/toast";
import { isUserPremium } from "../../../hooks/use-is-user-premium";
import { Pro } from "../../../components/icons";
export default function ToolbarConfigDialog(props: ToolbarConfigDialogProps) {
export function CustomizeToolbar() {
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
@@ -89,185 +84,177 @@ export default function ToolbarConfigDialog(props: ToolbarConfigDialogProps) {
setItems(items);
}, [currentPreset]);
useEffect(() => {
(async () => {
const tools = unflatten(items).slice(0, -1);
await db.settings?.setToolbarConfig("desktop", {
preset: currentPreset.id,
config: currentPreset.id === "custom" ? tools : undefined
});
setToolbarConfig(tools);
})();
}, [items]);
return (
<Dialog
isOpen={true}
title={"Configure toolbar"}
description={"Customize the editor toolbar to fit your needs."}
width={500}
onClose={() => props.onClose(false)}
positiveButton={{
text: "Save",
onClick: async () => {
const tools = unflatten(items).slice(0, -1);
await db.settings?.setToolbarConfig("desktop", {
preset: currentPreset.id,
config: currentPreset.id === "custom" ? tools : undefined
});
setToolbarConfig(tools);
props.onClose(true);
}
}}
negativeButton={{ text: "Cancel", onClick: () => props.onClose(false) }}
>
<Flex sx={{ flexDirection: "column" }}>
<Flex
sx={{ p: 1, justifyContent: "space-between", alignItems: "center" }}
>
<Flex>
{getAllPresets().map((preset) => (
<Label
key={preset.id}
variant="text.body"
sx={{ alignItems: "center", width: "auto", mr: 2 }}
>
<Radio
id={preset.id.toString()}
name="preset"
value={preset.id}
checked={preset.id === currentPreset.id}
disabled={preset.id === "custom" && !isUserPremium()}
onChange={(e) => {
const { value } = e.target;
if (preset.id === "custom" && !isUserPremium()) {
showToast(
"info",
"You need to be Pro to use the custom preset."
);
return;
}
setCurrentPreset(getPreset(value as PresetId));
}}
/>
{preset.title}
{preset.id === "custom" && !isUserPremium() ? (
<Pro color="primary" size={18} sx={{ ml: 1 }} />
) : null}
</Label>
))}
</Flex>
{currentPreset.editable && (
<Button
variant={"secondary"}
sx={{
display: "flex",
flexShrink: 0,
alignItems: "center",
p: 1
}}
title="Add group"
onClick={() => {
setItems(addGroup);
showToast("success", "Group added successfully");
}}
<Flex sx={{ flexDirection: "column" }}>
<Flex
sx={{ py: 2, justifyContent: "space-between", alignItems: "center" }}
>
<Flex sx={{ gap: 2 }}>
{getAllPresets().map((preset) => (
<Label
key={preset.id}
variant="text.body"
sx={{ alignItems: "center", width: "auto" }}
>
<Icon path={Icons.plus} color="text" size={18} />
</Button>
)}
<input
id={preset.id.toString()}
name="preset"
type="radio"
value={preset.id}
checked={preset.id === currentPreset.id}
defaultChecked={preset.id === currentPreset.id}
disabled={preset.id === "custom" && !isUserPremium()}
style={{ accentColor: "var(--primary)" }}
onChange={async (e) => {
const { value } = e.target;
if (preset.id === "custom" && !isUserPremium()) {
showToast(
"info",
"You need to be Pro to use the custom preset."
);
return;
}
setCurrentPreset(getPreset(value as PresetId));
}}
/>
<span style={{ marginLeft: 5 }}>{preset.title}</span>
{preset.id === "custom" && !isUserPremium() ? (
<Pro color="primary" size={18} sx={{ ml: 1 }} />
) : null}
</Label>
))}
</Flex>
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={(event) => {
if (!isUserPremium()) {
showToast("info", "You need to be Pro to customize the toolbar.");
return;
}
if (currentPreset.id !== "custom") {
setCurrentPreset((c) => ({
...getPreset("custom"),
tools: getPresetTools(c)
}));
}
const { active } = event;
const activeItem = items.find((item) => item.id === active.id);
setActiveItem(activeItem);
}}
onDragEnd={(event) => {
const { active, over } = event;
if (activeItem && over && active.id !== over.id) {
// const newIndex = items.findIndex((i) => i.id === over.id);
if (isGroup(activeItem) || isSubgroup(activeItem)) {
setItems(moveGroup(items, activeItem.id, over.id as string));
} else {
setItems(moveItem(items, activeItem.id, over.id as string));
}
setTimeout(() => {
const element = document.getElementById(over.id as string);
element?.scrollIntoView({ behavior: "auto", block: "nearest" });
}, 500);
}
setActiveItem(undefined);
}}
measuring={{
droppable: { strategy: MeasuringStrategy.Always }
}}
>
<SortableContext items={items} strategy={verticalListSortingStrategy}>
{items?.map((item) => {
const deleted = isDeleted(items, item);
const hasSubGroup =
isGroup(item) &&
!!getGroup(items, item.id)?.items.some((t) => isSubgroup(t));
const canAddSubGroup =
currentPreset.editable && !deleted && !hasSubGroup;
const canRemoveGroup = currentPreset.editable && !deleted;
const canRemoveItem = currentPreset.editable && !deleted;
return (
<TreeNodeComponent
key={item.id}
item={item}
activeItem={activeItem}
onAddSubGroup={
canAddSubGroup
? () => {
setItems((items) => addSubGroup(items, item.id));
showToast("success", "Subgroup added successfully");
}
: undefined
}
onRemoveGroup={
canRemoveGroup
? (group) => {
setItems(removeGroup(items, group.id));
}
: undefined
}
onRemoveItem={
canRemoveItem
? (item) => {
setItems(removeItem(items, item.id));
}
: undefined
}
/>
);
})}
{activeItem &&
createPortal(
<DragOverlay
// dropAnimation={dropAnimationConfig}
// modifiers={indicator ? [adjustTranslate] : undefined}
>
<TreeNodeComponent overlay item={activeItem} />
</DragOverlay>,
document.querySelector(".ReactModal__Overlay") || document.body
)}
</SortableContext>
</DndContext>
{currentPreset.editable && (
<Button
variant={"secondary"}
sx={{
display: "flex",
flexShrink: 0,
alignItems: "center",
p: 1
}}
title="Add group"
onClick={() => {
setItems(addGroup);
showToast("success", "Group added successfully");
}}
>
<Icon path={Icons.plus} color="text" size={18} />
</Button>
)}
</Flex>
</Dialog>
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={(event) => {
if (!isUserPremium()) {
showToast("info", "You need to be Pro to customize the toolbar.");
return;
}
if (currentPreset.id !== "custom") {
setCurrentPreset((c) => ({
...getPreset("custom"),
tools: getPresetTools(c)
}));
}
const { active } = event;
const activeItem = items.find((item) => item.id === active.id);
setActiveItem(activeItem);
}}
onDragEnd={(event) => {
const { active, over } = event;
if (activeItem && over && active.id !== over.id) {
// const newIndex = items.findIndex((i) => i.id === over.id);
if (isGroup(activeItem) || isSubgroup(activeItem)) {
setItems(moveGroup(items, activeItem.id, over.id as string));
} else {
setItems(moveItem(items, activeItem.id, over.id as string));
}
setTimeout(() => {
const element = document.getElementById(over.id as string);
element?.scrollIntoView({ behavior: "auto", block: "nearest" });
}, 500);
}
setActiveItem(undefined);
}}
measuring={{
droppable: { strategy: MeasuringStrategy.Always }
}}
>
<SortableContext items={items} strategy={verticalListSortingStrategy}>
{items?.map((item) => {
const deleted = isDeleted(items, item);
const hasSubGroup =
isGroup(item) &&
!!getGroup(items, item.id)?.items.some((t) => isSubgroup(t));
const canAddSubGroup =
currentPreset.editable && !deleted && !hasSubGroup;
const canRemoveGroup = currentPreset.editable && !deleted;
const canRemoveItem = currentPreset.editable && !deleted;
return (
<TreeNodeComponent
key={item.id}
item={item}
activeItem={activeItem}
onAddSubGroup={
canAddSubGroup
? () => {
setItems((items) => addSubGroup(items, item.id));
showToast("success", "Subgroup added successfully");
}
: undefined
}
onRemoveGroup={
canRemoveGroup
? (group) => {
setItems(removeGroup(items, group.id));
}
: undefined
}
onRemoveItem={
canRemoveItem
? (item) => {
setItems(removeItem(items, item.id));
}
: undefined
}
/>
);
})}
{activeItem &&
createPortal(
<DragOverlay
// dropAnimation={dropAnimationConfig}
// modifiers={indicator ? [adjustTranslate] : undefined}
>
<TreeNodeComponent overlay item={activeItem} />
</DragOverlay>,
document.querySelector(".ReactModal__Overlay") || document.body
)}
</SortableContext>
</DndContext>
</Flex>
);
}
type TreeNodeComponentProps = {
item: TreeNode;
activeItem?: TreeNode;
@@ -463,7 +450,6 @@ function unflatten(items: TreeNode[]): ToolbarDefinition {
const item = items[i];
if (isGroup(item) || isSubgroup(item)) {
const group = getGroup(items, item.id);
console.log(group?.items);
if (!group) continue;
tools.push(unflatten(group.items.slice(1)) as ToolbarGroupDefinition);
@@ -707,7 +693,7 @@ function addGroup(items: TreeNode[]) {
return newArray;
}
export function getDisabledTools(tools: TreeNode[]) {
function getDisabledTools(tools: TreeNode[]) {
const allTools = getAllTools();
const disabled: ToolbarGroupDefinition = [];
const items: Item[] = tools.filter((t) => isItem(t)) as Item[];

View File

@@ -28,6 +28,7 @@ import { getFonts } from "@notesnook/editor";
import { useSpellChecker } from "../../hooks/use-spell-checker";
import { SpellCheckerLanguages } from "./components/spell-checker-languages";
import { isDesktop } from "../../utils/platform";
import { CustomizeToolbar } from "./components/customize-toolbar";
export const EditorSettings: SettingsGroup[] = [
{
@@ -150,5 +151,22 @@ symbols (e.g. 202305261253)`,
]
}
]
},
{
key: "toolbar",
section: "editor",
header: "Toolbar",
settings: [
{
key: "customize-toolbar",
title: "Customize toolbar",
components: [
{
type: "custom",
component: CustomizeToolbar
}
]
}
]
}
];