mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 23:19:40 +01:00
web: add support for custom colors
This commit is contained in:
@@ -17,16 +17,6 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export const COLORS = [
|
||||
{ key: "red", title: "Red" },
|
||||
{ key: "orange", title: "Orange" },
|
||||
{ key: "yellow", title: "Yellow" },
|
||||
{ key: "green", title: "Green" },
|
||||
{ key: "blue", title: "Blue" },
|
||||
{ key: "purple", title: "Purple" },
|
||||
{ key: "gray", title: "Gray" }
|
||||
] as const;
|
||||
|
||||
export const SUBSCRIPTION_STATUS = {
|
||||
BASIC: 0,
|
||||
TRIAL: 1,
|
||||
|
||||
@@ -88,6 +88,15 @@ export function showAddTagsDialog(noteIds: string[]) {
|
||||
));
|
||||
}
|
||||
|
||||
export function showCreateColorDialog() {
|
||||
return showDialog<"CreateColorDialog", string | undefined>(
|
||||
"CreateColorDialog",
|
||||
(Dialog, perform) => (
|
||||
<Dialog onClose={() => perform(undefined)} onDone={(id) => perform(id)} />
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function showAddNotebookDialog(parentId?: string) {
|
||||
return showDialog("AddNotebookDialog", (Dialog, perform) => (
|
||||
<Dialog
|
||||
|
||||
@@ -208,7 +208,7 @@ function NavigationMenu(props: NavigationMenuProps) {
|
||||
title={color.title}
|
||||
icon={Circle}
|
||||
selected={location === `/colors/${color.id}`}
|
||||
color={color.title.toLowerCase()}
|
||||
color={color.colorCode}
|
||||
onClick={() => {
|
||||
_navigate(`/colors/${color.id}`);
|
||||
}}
|
||||
|
||||
@@ -57,6 +57,7 @@ import {
|
||||
confirm,
|
||||
showAddReminderDialog,
|
||||
showAddTagsDialog,
|
||||
showCreateColorDialog,
|
||||
showMoveNoteDialog
|
||||
} from "../../common/dialog-controller";
|
||||
import { store, useStore } from "../../stores/note-store";
|
||||
@@ -68,7 +69,6 @@ import { showToast } from "../../utils/toast";
|
||||
import { hashNavigate, navigate } from "../../navigation";
|
||||
import { showPublishView } from "../publish-view";
|
||||
import IconTag from "../icon-tag";
|
||||
import { COLORS } from "../../common/constants";
|
||||
import { exportNote, exportNotes, exportToPDF } from "../../common/export";
|
||||
import { Multiselect } from "../../common/multi-select";
|
||||
import { store as selectionStore } from "../../stores/selection-store";
|
||||
@@ -81,13 +81,7 @@ import {
|
||||
getFormattedReminderTime,
|
||||
pluralize
|
||||
} from "@notesnook/common";
|
||||
import {
|
||||
Color,
|
||||
Note,
|
||||
Notebook as NotebookItem,
|
||||
Tag,
|
||||
DefaultColors
|
||||
} from "@notesnook/core";
|
||||
import { Color, Note, Notebook as NotebookItem, Tag } from "@notesnook/core";
|
||||
import { MenuItem } from "@notesnook/ui";
|
||||
import { Context } from "../list-container/types";
|
||||
import { SchemeColors } from "@notesnook/theme";
|
||||
@@ -554,18 +548,38 @@ function colorsToMenuItems(
|
||||
noteColor: Color | undefined,
|
||||
ids: string[]
|
||||
): MenuItem[] {
|
||||
return COLORS.map((color) => {
|
||||
const isChecked = !!noteColor && noteColor.title === color.title;
|
||||
return {
|
||||
return [
|
||||
{
|
||||
key: "new-color",
|
||||
type: "button",
|
||||
key: color.key,
|
||||
title: color.title,
|
||||
icon: Circle.path,
|
||||
styles: { icon: { color: DefaultColors[color.key] } },
|
||||
isChecked,
|
||||
onClick: () => store.setColor(color, isChecked, ...ids)
|
||||
} satisfies MenuItem;
|
||||
});
|
||||
title: "Add new color",
|
||||
icon: Plus.path,
|
||||
onClick: async () => {
|
||||
const id = await showCreateColorDialog();
|
||||
if (!id) return;
|
||||
await store.get().setColor(id, noteColor?.id === id, ...ids);
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "colors",
|
||||
type: "lazy-loader",
|
||||
async items() {
|
||||
const colors = await db.colors.all.items();
|
||||
return colors.map((color) => {
|
||||
const isChecked = !!noteColor && noteColor.id === color.id;
|
||||
return {
|
||||
type: "button",
|
||||
key: color.id,
|
||||
title: color.title,
|
||||
icon: Circle.path,
|
||||
styles: { icon: { color: color.colorCode } },
|
||||
isChecked,
|
||||
onClick: () => store.setColor(color.id, isChecked, ...ids)
|
||||
} satisfies MenuItem;
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
function notebooksMenuItems(ids: string[]): MenuItem[] {
|
||||
|
||||
@@ -42,8 +42,6 @@ import { PreviewSession } from "../editor/types";
|
||||
import { ListItemWrapper } from "../list-container/list-profiles";
|
||||
import { VirtualizedList } from "../virtualized-list";
|
||||
import { SessionItem } from "../session-item";
|
||||
import { COLORS } from "../../common/constants";
|
||||
import { DefaultColors } from "@notesnook/core";
|
||||
import { VirtualizedTable } from "../virtualized-table";
|
||||
|
||||
const tools = [
|
||||
@@ -204,14 +202,7 @@ export default React.memo(EditorProperties);
|
||||
|
||||
function Colors({ noteId }: { noteId: string }) {
|
||||
const color = useStore((store) => store.color);
|
||||
const result = usePromise(
|
||||
async () =>
|
||||
(
|
||||
await db.relations.to({ id: noteId, type: "note" }, "color").resolve(1)
|
||||
).at(0),
|
||||
[color]
|
||||
);
|
||||
console.log(result);
|
||||
const result = usePromise(() => db.colors.all.items(), [color]);
|
||||
return (
|
||||
<Flex
|
||||
py={2}
|
||||
@@ -221,37 +212,36 @@ function Colors({ noteId }: { noteId: string }) {
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
{COLORS.map((label) => {
|
||||
const isChecked =
|
||||
result.status === "fulfilled" &&
|
||||
DefaultColors[label.key] === result.value?.colorCode;
|
||||
return (
|
||||
<Flex
|
||||
key={label.key}
|
||||
onClick={() => noteStore.get().setColor(label, isChecked, noteId)}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
position: "relative",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between"
|
||||
}}
|
||||
data-test-id={`properties-${label.key}`}
|
||||
>
|
||||
<Circle
|
||||
size={35}
|
||||
color={DefaultColors[label.key]}
|
||||
data-test-id={`toggle-state-${isChecked ? "on" : "off"}`}
|
||||
/>
|
||||
{isChecked && (
|
||||
<Checkmark
|
||||
color="white"
|
||||
size={18}
|
||||
sx={{ position: "absolute", left: "8px" }}
|
||||
{result.status === "fulfilled" &&
|
||||
result.value.map((c) => {
|
||||
const isChecked = c.id === color;
|
||||
return (
|
||||
<Flex
|
||||
key={c.id}
|
||||
onClick={() => noteStore.get().setColor(c.id, isChecked, noteId)}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
position: "relative",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between"
|
||||
}}
|
||||
data-test-id={`properties-${c.title}`}
|
||||
>
|
||||
<Circle
|
||||
size={35}
|
||||
color={c.colorCode}
|
||||
data-test-id={`toggle-state-${isChecked ? "on" : "off"}`}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
{isChecked && (
|
||||
<Checkmark
|
||||
color="white"
|
||||
size={18}
|
||||
sx={{ position: "absolute", left: "8px" }}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
105
apps/web/src/dialogs/create-color-dialog.tsx
Normal file
105
apps/web/src/dialogs/create-color-dialog.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Box, Flex } from "@theme-ui/components";
|
||||
import Dialog from "../components/dialog";
|
||||
import Field from "../components/field";
|
||||
import { Perform } from "../common/dialog-controller";
|
||||
import { useRef } from "react";
|
||||
import tinycolor from "tinycolor2";
|
||||
import { db } from "../common/db";
|
||||
|
||||
type CreateColorDialogProps = {
|
||||
onClose: Perform;
|
||||
onDone: Perform<string>;
|
||||
};
|
||||
function CreateColorDialog(props: CreateColorDialogProps) {
|
||||
const colorRef = useRef<HTMLInputElement>(null);
|
||||
const colorPickerRef = useRef<HTMLInputElement>(null);
|
||||
return (
|
||||
<Dialog
|
||||
testId="new-color-dialog"
|
||||
isOpen={true}
|
||||
title={"Create a new color"}
|
||||
onClose={() => props.onClose(false)}
|
||||
positiveButton={{
|
||||
form: "colorForm",
|
||||
type: "submit",
|
||||
text: "Create"
|
||||
}}
|
||||
negativeButton={{ text: "Cancel", onClick: () => props.onClose(false) }}
|
||||
>
|
||||
<Box
|
||||
as="form"
|
||||
id="colorForm"
|
||||
onSubmit={async (e) => {
|
||||
e.preventDefault();
|
||||
const form = Object.fromEntries(
|
||||
new FormData(e.target as HTMLFormElement).entries()
|
||||
) as { color: string; title: string };
|
||||
const colorId = await db.colors.add({
|
||||
colorCode: form.color,
|
||||
title: form.title
|
||||
});
|
||||
props.onDone(colorId);
|
||||
}}
|
||||
>
|
||||
<Field
|
||||
required
|
||||
label="Title"
|
||||
id="title"
|
||||
name="title"
|
||||
autoFocus
|
||||
data-test-id="title-input"
|
||||
/>
|
||||
<Flex sx={{ alignItems: "end" }}>
|
||||
<Field
|
||||
inputRef={colorRef}
|
||||
required
|
||||
label="Color"
|
||||
id="color"
|
||||
name="color"
|
||||
data-test-id="color-input"
|
||||
sx={{ flex: 1 }}
|
||||
onChange={(e) => {
|
||||
const color = tinycolor(e.target.value);
|
||||
if (colorPickerRef.current && color.isValid())
|
||||
colorPickerRef.current.value = color.toHexString();
|
||||
}}
|
||||
/>
|
||||
<input
|
||||
ref={colorPickerRef}
|
||||
type="color"
|
||||
style={{
|
||||
height: 41,
|
||||
backgroundColor: "transparent",
|
||||
border: "1px solid var(--border)",
|
||||
borderRadius: 5
|
||||
}}
|
||||
onChange={(e) => {
|
||||
if (colorRef.current) colorRef.current.value = e.target.value;
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreateColorDialog;
|
||||
@@ -55,6 +55,7 @@ const ThemeDetailsDialog = React.lazy(() => import("./theme-details-dialog"));
|
||||
const BackupPasswordDialog = React.lazy(
|
||||
() => import("./backup-password-dialog")
|
||||
);
|
||||
const CreateColorDialog = React.lazy(() => import("./create-color-dialog"));
|
||||
|
||||
export const Dialogs = {
|
||||
AddNotebookDialog,
|
||||
@@ -83,5 +84,6 @@ export const Dialogs = {
|
||||
AddTagsDialog,
|
||||
SettingsDialog,
|
||||
ThemeDetailsDialog,
|
||||
BackupPasswordDialog
|
||||
BackupPasswordDialog,
|
||||
CreateColorDialog
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ import { store as selectionStore } from "./selection-store";
|
||||
import Vault from "../common/vault";
|
||||
import BaseStore from ".";
|
||||
import Config from "../utils/config";
|
||||
import { DefaultColors, Note, VirtualizedGrouping } from "@notesnook/core";
|
||||
import { Note, VirtualizedGrouping } from "@notesnook/core";
|
||||
import { Context } from "../components/list-container/types";
|
||||
|
||||
type ViewMode = "detailed" | "compact";
|
||||
@@ -129,18 +129,9 @@ class NoteStore extends BaseStore<NoteStore> {
|
||||
await this.refresh();
|
||||
};
|
||||
|
||||
setColor = async (
|
||||
color: { key: string; title: string },
|
||||
isChecked: boolean,
|
||||
...ids: string[]
|
||||
) => {
|
||||
setColor = async (colorId: string, isChecked: boolean, ...ids: string[]) => {
|
||||
await db.relations.to({ type: "note", ids }, "color").unlink();
|
||||
if (!isChecked) {
|
||||
const colorId = await db.colors.add({
|
||||
title: color.title,
|
||||
colorCode: DefaultColors[color.key]
|
||||
});
|
||||
|
||||
for (const id of ids) {
|
||||
await db.relations.add(
|
||||
{ type: "color", id: colorId },
|
||||
@@ -149,7 +140,7 @@ class NoteStore extends BaseStore<NoteStore> {
|
||||
}
|
||||
}
|
||||
await appStore.refreshNavItems();
|
||||
this.syncNoteWithEditor(ids, "color", color.key);
|
||||
this.syncNoteWithEditor(ids, "color", colorId);
|
||||
await this.refresh();
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user