diff --git a/apps/web/src/components/editor/index.js b/apps/web/src/components/editor/index.js
index 971137f3a..10f8bc8af 100644
--- a/apps/web/src/components/editor/index.js
+++ b/apps/web/src/components/editor/index.js
@@ -170,7 +170,7 @@ function Editor({ noteId, nonce }) {
-
+
);
}
diff --git a/apps/web/src/components/icons/index.js b/apps/web/src/components/icons/index.js
index d1ce92f49..6e21482e6 100644
--- a/apps/web/src/components/icons/index.js
+++ b/apps/web/src/components/icons/index.js
@@ -58,7 +58,7 @@ export const Plus = createIcon(Icons.mdiPlus);
export const Note = createIcon(Icons.mdiHomeVariantOutline);
export const Minus = createIcon(Icons.mdiMinus);
export const Notebook = createIcon(Icons.mdiBookOutline);
-export const Notebook2 = createIcon(Icons.mdiBookOutline);
+export const Notebook2 = createIcon(Icons.mdiNotebookOutline);
export const ArrowLeft = createIcon(Icons.mdiArrowLeft);
export const ArrowRight = createIcon(Icons.mdiArrowRight);
export const ArrowDown = createIcon(Icons.mdiArrowDown);
@@ -128,8 +128,8 @@ export const Error = createIcon(Icons.mdiAlertCircle);
export const Warn = createIcon(Icons.mdiAlert);
export const Info = createIcon(Icons.mdiInformation);
-export const ToggleUnchecked = createIcon(Icons.mdiToggleSwitchOff);
-export const ToggleChecked = createIcon(Icons.mdiToggleSwitch);
+export const ToggleUnchecked = createIcon(Icons.mdiToggleSwitchOffOutline);
+export const ToggleChecked = createIcon(Icons.mdiToggleSwitchOutline);
export const Backup = createIcon(Icons.mdiBackupRestore);
export const Buy = createIcon(Icons.mdiCurrencyUsdCircleOutline);
@@ -179,3 +179,6 @@ export const Twitter = createIcon(Icons.mdiTwitter);
export const Reddit = createIcon(Icons.mdiReddit);
export const Dismiss = createIcon(Icons.mdiClose);
+
+export const File = createIcon(Icons.mdiFileOutline);
+export const Download = createIcon(Icons.mdiArrowDown);
diff --git a/apps/web/src/components/properties/index.js b/apps/web/src/components/properties/index.js
index cc84661c8..a52f39fc2 100644
--- a/apps/web/src/components/properties/index.js
+++ b/apps/web/src/components/properties/index.js
@@ -1,33 +1,37 @@
-import React, { useCallback } from "react";
+import React, { useCallback, useEffect, useState } from "react";
import * as Icon from "../icons";
-import { Flex, Text, Button } from "rebass";
+import { Flex, Text, Button, Box } from "rebass";
import { useStore } from "../../stores/editor-store";
-import { COLORS } from "../../common";
+import { AppEventManager, AppEvents, COLORS } from "../../common";
import { db } from "../../common/db";
import { useStore as useAppStore } from "../../stores/app-store";
import Animated from "../animated";
import Toggle from "./toggle";
-import { showMoveNoteDialog } from "../../common/dialog-controller";
import { navigate } from "../../navigation";
+import IconTag from "../icon-tag";
+import FileSaver from "file-saver";
const tools = [
- { key: "pinned", icons: { on: Icon.PinFilled, off: Icon.Pin }, label: "Pin" },
+ { key: "pinned", icon: Icon.Pin, label: "Pin" },
{
key: "favorite",
- icons: { on: Icon.Star, off: Icon.StarOutline },
+ icon: Icon.StarOutline,
label: "Favorite",
},
- { key: "locked", icons: { on: Icon.Lock, off: Icon.Unlock }, label: "Lock" },
+ { key: "locked", icon: Icon.Unlock, label: "Lock" },
];
-function Properties() {
- const color = useStore((store) => store.session.color);
- const toggleLocked = useStore((store) => store.toggleLocked);
- const sessionId = useStore((store) => store.session.id);
- const notebooks = useStore((store) => store.session.notebooks);
- const setSession = useStore((store) => store.setSession);
- const setColor = useStore((store) => store.setColor);
+function Properties({ noteId }) {
+ const [attachmentsStatus, setAttachmentsStatus] = useState({});
const arePropertiesVisible = useStore((store) => store.arePropertiesVisible);
+ const color = useStore((store) => store.session.color);
+ const notebooks = useStore((store) => store.session.notebooks);
+ const attachments = useStore((store) => store.session.attachments);
+
+ const toggleLocked = useStore((store) => store.toggleLocked);
+ const setSession = useStore((store) => store.setSession);
+ const sessionId = useStore((store) => store.session.id);
+ const setColor = useStore((store) => store.setColor);
const toggleProperties = useStore((store) => store.toggleProperties);
const isFocusMode = useAppStore((store) => store.isFocusMode);
@@ -44,183 +48,328 @@ function Properties() {
[setSession, toggleLocked]
);
- return (
- !isFocusMode && (
- <>
-
-
-
- Properties
- toggleProperties()}
- sx={{
- color: "red",
- height: 24,
- ":active": { color: "darkRed" },
- }}
- >
-
-
-
- {sessionId ? (
- <>
-
- {tools.map((tool, _) => (
- changeState(tool.key, state)}
- testId={`properties-${tool.key}`}
- />
- ))}
-
-
- {COLORS.map((label) => (
- setColor(label)}
- sx={{ cursor: "pointer" }}
- mt={4}
- data-test-id={`properties-${label}`}
- >
-
-
-
- {label}
-
-
- {label.toLowerCase() === color?.toLowerCase() && (
-
- )}
-
- ))}
-
- {notebooks?.length && (
- <>
-
- Referenced in {notebooks.length} notebook(s):
-
- {notebooks.map((ref) => {
- const notebook = db.notebooks.notebook(ref.id);
- if (!notebook) return null;
- const topics = ref.topics.reduce((topics, topicId) => {
- const topic = notebook.topics.topic(topicId);
- if (!!topic && !!topic._topic)
- topics.push(topic._topic);
- return topics;
- }, []);
+ useEffect(() => {
+ const event = AppEventManager.subscribe(
+ AppEvents.UPDATE_ATTACHMENT_PROGRESS,
+ ({ hash, type, total, loaded }) => {
+ if (!attachments.find((a) => a.metadata.hash === hash)) return;
+ setAttachmentsStatus((status) => {
+ const copy = { ...status };
+ copy[hash] = {
+ type,
+ progress: Math.round((loaded / total) * 100),
+ };
+ return copy;
+ });
+ }
+ );
+ return () => {
+ event.unsubscribe();
+ };
+ }, [attachments]);
- return (
-
- {
- navigate(`/notebooks/${notebook.data.id}`);
- }}
- mb={1}
- >
-
-
- {notebook.title}
-
-
- {topics.map((topic) => (
- {
- navigate(
- `/notebooks/${notebook.data.id}/${topic.id}`
- );
- }}
- >
-
-
- {topic.title}
-
-
- ))}
-
- );
- })}
- >
- )}
-
- >
- ) : (
-
+
+
+ {tools.map((tool, _) => (
+ changeState(tool.key, state)}
+ testId={`properties-${tool.key}`}
+ />
+ ))}
+
+ {COLORS.map((label) => (
+ setColor(label)}
+ sx={{
+ cursor: "pointer",
+ position: "relative",
+ }}
+ data-test-id={`properties-${label}`}
>
- Start writing to make a new note.
-
- )}
+
+ {label.toLowerCase() === color?.toLowerCase() && (
+
+ )}
+
+ ))}
-
- >
- )
+
+ {notebooks?.length && (
+
+ {notebooks.map((ref) => {
+ const notebook = db.notebooks.notebook(ref.id);
+ if (!notebook) return null;
+ const topics = ref.topics.reduce((topics, topicId) => {
+ const topic = notebook.topics.topic(topicId);
+ if (!!topic && !!topic._topic) topics.push(topic._topic);
+ return topics;
+ }, []);
+
+ return (
+ {
+ navigate(`/notebooks/${notebook.data.id}`);
+ }}
+ >
+
+
+ {notebook.title}
+
+
+
+ {topics.map((topic) => (
+ {
+ e.stopPropagation();
+ navigate(
+ `/notebooks/${notebook.data.id}/${topic.id}`
+ );
+ }}
+ />
+ ))}
+
+
+ );
+ })}
+
+ )}
+ {attachments.length > 0 && (
+
+ {attachments.map((attachment) => {
+ const attachmentStatus =
+ attachmentsStatus[attachment.metadata.hash];
+ return (
+
+
+
+ {formatFilename(attachment.metadata.filename)}
+
+ {attachmentStatus && (
+
+ )}
+
+
+ {formatBytes(attachment.length, 1)}
+
+
+ {attachmentStatus ? (
+
+ ) : (
+
+ )}
+
+
+ );
+ })}
+
+ )}
+
+ >
);
}
export default React.memo(Properties);
+
+function Card({ title, children }) {
+ return (
+
+
+ {title}
+
+ {children}
+
+ );
+}
+
+function formatBytes(bytes, decimals = 2) {
+ if (bytes === 0) return "0B";
+
+ const k = 1024;
+ const dm = decimals < 0 ? 0 : decimals;
+ const sizes = ["B", "K", "M", "G", "T", "P", "E", "Z", "Y"];
+
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + sizes[i];
+}
+
+function formatFilename(filename) {
+ const MAX_LENGTH = 28;
+ if (filename.length > MAX_LENGTH) {
+ return (
+ filename.substr(0, MAX_LENGTH / 2) +
+ "..." +
+ filename.substr(-(MAX_LENGTH / 3))
+ );
+ }
+ return filename;
+}
diff --git a/apps/web/src/components/properties/toggle.css b/apps/web/src/components/properties/toggle.css
new file mode 100644
index 000000000..2b151f0f6
--- /dev/null
+++ b/apps/web/src/components/properties/toggle.css
@@ -0,0 +1,34 @@
+.react-toggle {
+ display: flex;
+ align-items: center;
+}
+
+.react-toggle-thumb {
+ box-shadow: none;
+}
+
+.react-toggle-track {
+ width: 30px;
+ height: 18px;
+}
+
+.react-toggle-thumb {
+ width: 16px;
+ height: 16px;
+ top: 0px;
+ left: 1px;
+ margin-top: 1px;
+}
+
+.react-toggle--checked .react-toggle-thumb {
+ left: 18px;
+ border-color: var(--primary);
+}
+
+.react-toggle:active:not(.react-toggle--disabled) .react-toggle-thumb {
+ box-shadow: none;
+}
+
+.react-toggle--focus .react-toggle-thumb {
+ box-shadow: none;
+}
diff --git a/apps/web/src/components/properties/toggle.js b/apps/web/src/components/properties/toggle.js
index ee5e96ba4..1e00f4806 100644
--- a/apps/web/src/components/properties/toggle.js
+++ b/apps/web/src/components/properties/toggle.js
@@ -1,24 +1,37 @@
import React from "react";
import { Flex, Text } from "rebass";
import { useStore } from "../../stores/editor-store";
+import ReactToggle from "react-toggle";
+import "react-toggle/style.css";
+import { Label } from "@rebass/forms";
+import "./toggle.css";
function Toggle(props) {
- const { icons, label, onToggle, toggleKey } = props;
+ const { icon: ToggleIcon, label, onToggle, toggleKey } = props;
const isOn = useStore((store) => store.session[toggleKey]);
return (
onToggle(!isOn)}
data-test-id={props.testId}
>
- {isOn ? : }
-
+
+
{label}
+
);
}
diff --git a/apps/web/src/stores/editor-store.js b/apps/web/src/stores/editor-store.js
index 388200f94..37e3bd529 100644
--- a/apps/web/src/stores/editor-store.js
+++ b/apps/web/src/stores/editor-store.js
@@ -30,6 +30,7 @@ const getDefaultSession = () => {
color: undefined,
dateEdited: 0,
totalWords: 0,
+ attachments: [],
content: {
type: "tiny",
data: "",
@@ -102,6 +103,7 @@ class EditorStore extends BaseStore {
content: content || defaultSession.content,
totalWords: state.session.totalWords,
state: SESSION_STATES.new,
+ attachments: db.attachments.get(note.id) || [],
};
});
appStore.setIsEditorOpen(true);
@@ -146,6 +148,7 @@ class EditorStore extends BaseStore {
state.session.title = note.title;
state.session.isSaving = false;
state.session.notebooks = note.notebooks;
+ state.session.attachments = db.attachments.get(note.id) || [];
});
noteStore.refresh();