diff --git a/apps/web/src/common/constants.ts b/apps/web/src/common/constants.ts index e5b9162dd..e7bdd9e0c 100644 --- a/apps/web/src/common/constants.ts +++ b/apps/web/src/common/constants.ts @@ -17,16 +17,6 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -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, diff --git a/apps/web/src/common/dialog-controller.tsx b/apps/web/src/common/dialog-controller.tsx index 1c8bff1ae..29b195590 100644 --- a/apps/web/src/common/dialog-controller.tsx +++ b/apps/web/src/common/dialog-controller.tsx @@ -88,6 +88,15 @@ export function showAddTagsDialog(noteIds: string[]) { )); } +export function showCreateColorDialog() { + return showDialog<"CreateColorDialog", string | undefined>( + "CreateColorDialog", + (Dialog, perform) => ( + perform(undefined)} onDone={(id) => perform(id)} /> + ) + ); +} + export function showAddNotebookDialog(parentId?: string) { return showDialog("AddNotebookDialog", (Dialog, perform) => ( { _navigate(`/colors/${color.id}`); }} diff --git a/apps/web/src/components/note/index.tsx b/apps/web/src/components/note/index.tsx index dda6ef878..ec4e1861e 100644 --- a/apps/web/src/components/note/index.tsx +++ b/apps/web/src/components/note/index.tsx @@ -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[] { diff --git a/apps/web/src/components/properties/index.tsx b/apps/web/src/components/properties/index.tsx index 0e376fbc3..1b4728101 100644 --- a/apps/web/src/components/properties/index.tsx +++ b/apps/web/src/components/properties/index.tsx @@ -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 ( - {COLORS.map((label) => { - const isChecked = - result.status === "fulfilled" && - DefaultColors[label.key] === result.value?.colorCode; - return ( - noteStore.get().setColor(label, isChecked, noteId)} - sx={{ - cursor: "pointer", - position: "relative", - alignItems: "center", - justifyContent: "space-between" - }} - data-test-id={`properties-${label.key}`} - > - - {isChecked && ( - { + const isChecked = c.id === color; + return ( + noteStore.get().setColor(c.id, isChecked, noteId)} + sx={{ + cursor: "pointer", + position: "relative", + alignItems: "center", + justifyContent: "space-between" + }} + data-test-id={`properties-${c.title}`} + > + - )} - - ); - })} + {isChecked && ( + + )} + + ); + })} ); } diff --git a/apps/web/src/dialogs/create-color-dialog.tsx b/apps/web/src/dialogs/create-color-dialog.tsx new file mode 100644 index 000000000..dff541402 --- /dev/null +++ b/apps/web/src/dialogs/create-color-dialog.tsx @@ -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 . +*/ + +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; +}; +function CreateColorDialog(props: CreateColorDialogProps) { + const colorRef = useRef(null); + const colorPickerRef = useRef(null); + return ( + props.onClose(false)} + positiveButton={{ + form: "colorForm", + type: "submit", + text: "Create" + }} + negativeButton={{ text: "Cancel", onClick: () => props.onClose(false) }} + > + { + 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); + }} + > + + + { + const color = tinycolor(e.target.value); + if (colorPickerRef.current && color.isValid()) + colorPickerRef.current.value = color.toHexString(); + }} + /> + { + if (colorRef.current) colorRef.current.value = e.target.value; + }} + /> + + + + ); +} + +export default CreateColorDialog; diff --git a/apps/web/src/dialogs/index.ts b/apps/web/src/dialogs/index.ts index cf368e053..c81357c4a 100644 --- a/apps/web/src/dialogs/index.ts +++ b/apps/web/src/dialogs/index.ts @@ -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 }; diff --git a/apps/web/src/stores/note-store.ts b/apps/web/src/stores/note-store.ts index a956e1504..8bf2025ef 100644 --- a/apps/web/src/stores/note-store.ts +++ b/apps/web/src/stores/note-store.ts @@ -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 { 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 { } } await appStore.refreshNavItems(); - this.syncNoteWithEditor(ids, "color", color.key); + this.syncNoteWithEditor(ids, "color", colorId); await this.refresh(); };