mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 23:19:40 +01:00
feat: improve properties UI and add attachments section
This commit is contained in:
@@ -170,7 +170,7 @@ function Editor({ noteId, nonce }) {
|
|||||||
</Suspense>
|
</Suspense>
|
||||||
</Animated.Flex>
|
</Animated.Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Properties />
|
<Properties noteId={noteId} />
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export const Plus = createIcon(Icons.mdiPlus);
|
|||||||
export const Note = createIcon(Icons.mdiHomeVariantOutline);
|
export const Note = createIcon(Icons.mdiHomeVariantOutline);
|
||||||
export const Minus = createIcon(Icons.mdiMinus);
|
export const Minus = createIcon(Icons.mdiMinus);
|
||||||
export const Notebook = createIcon(Icons.mdiBookOutline);
|
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 ArrowLeft = createIcon(Icons.mdiArrowLeft);
|
||||||
export const ArrowRight = createIcon(Icons.mdiArrowRight);
|
export const ArrowRight = createIcon(Icons.mdiArrowRight);
|
||||||
export const ArrowDown = createIcon(Icons.mdiArrowDown);
|
export const ArrowDown = createIcon(Icons.mdiArrowDown);
|
||||||
@@ -128,8 +128,8 @@ export const Error = createIcon(Icons.mdiAlertCircle);
|
|||||||
export const Warn = createIcon(Icons.mdiAlert);
|
export const Warn = createIcon(Icons.mdiAlert);
|
||||||
export const Info = createIcon(Icons.mdiInformation);
|
export const Info = createIcon(Icons.mdiInformation);
|
||||||
|
|
||||||
export const ToggleUnchecked = createIcon(Icons.mdiToggleSwitchOff);
|
export const ToggleUnchecked = createIcon(Icons.mdiToggleSwitchOffOutline);
|
||||||
export const ToggleChecked = createIcon(Icons.mdiToggleSwitch);
|
export const ToggleChecked = createIcon(Icons.mdiToggleSwitchOutline);
|
||||||
|
|
||||||
export const Backup = createIcon(Icons.mdiBackupRestore);
|
export const Backup = createIcon(Icons.mdiBackupRestore);
|
||||||
export const Buy = createIcon(Icons.mdiCurrencyUsdCircleOutline);
|
export const Buy = createIcon(Icons.mdiCurrencyUsdCircleOutline);
|
||||||
@@ -179,3 +179,6 @@ export const Twitter = createIcon(Icons.mdiTwitter);
|
|||||||
export const Reddit = createIcon(Icons.mdiReddit);
|
export const Reddit = createIcon(Icons.mdiReddit);
|
||||||
|
|
||||||
export const Dismiss = createIcon(Icons.mdiClose);
|
export const Dismiss = createIcon(Icons.mdiClose);
|
||||||
|
|
||||||
|
export const File = createIcon(Icons.mdiFileOutline);
|
||||||
|
export const Download = createIcon(Icons.mdiArrowDown);
|
||||||
|
|||||||
@@ -1,33 +1,37 @@
|
|||||||
import React, { useCallback } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import * as Icon from "../icons";
|
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 { useStore } from "../../stores/editor-store";
|
||||||
import { COLORS } from "../../common";
|
import { AppEventManager, AppEvents, COLORS } from "../../common";
|
||||||
import { db } from "../../common/db";
|
import { db } from "../../common/db";
|
||||||
import { useStore as useAppStore } from "../../stores/app-store";
|
import { useStore as useAppStore } from "../../stores/app-store";
|
||||||
import Animated from "../animated";
|
import Animated from "../animated";
|
||||||
import Toggle from "./toggle";
|
import Toggle from "./toggle";
|
||||||
import { showMoveNoteDialog } from "../../common/dialog-controller";
|
|
||||||
import { navigate } from "../../navigation";
|
import { navigate } from "../../navigation";
|
||||||
|
import IconTag from "../icon-tag";
|
||||||
|
import FileSaver from "file-saver";
|
||||||
|
|
||||||
const tools = [
|
const tools = [
|
||||||
{ key: "pinned", icons: { on: Icon.PinFilled, off: Icon.Pin }, label: "Pin" },
|
{ key: "pinned", icon: Icon.Pin, label: "Pin" },
|
||||||
{
|
{
|
||||||
key: "favorite",
|
key: "favorite",
|
||||||
icons: { on: Icon.Star, off: Icon.StarOutline },
|
icon: Icon.StarOutline,
|
||||||
label: "Favorite",
|
label: "Favorite",
|
||||||
},
|
},
|
||||||
{ key: "locked", icons: { on: Icon.Lock, off: Icon.Unlock }, label: "Lock" },
|
{ key: "locked", icon: Icon.Unlock, label: "Lock" },
|
||||||
];
|
];
|
||||||
|
|
||||||
function Properties() {
|
function Properties({ noteId }) {
|
||||||
const color = useStore((store) => store.session.color);
|
const [attachmentsStatus, setAttachmentsStatus] = useState({});
|
||||||
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);
|
|
||||||
const arePropertiesVisible = useStore((store) => store.arePropertiesVisible);
|
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 toggleProperties = useStore((store) => store.toggleProperties);
|
||||||
const isFocusMode = useAppStore((store) => store.isFocusMode);
|
const isFocusMode = useAppStore((store) => store.isFocusMode);
|
||||||
|
|
||||||
@@ -44,183 +48,328 @@ function Properties() {
|
|||||||
[setSession, toggleLocked]
|
[setSession, toggleLocked]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
useEffect(() => {
|
||||||
!isFocusMode && (
|
const event = AppEventManager.subscribe(
|
||||||
<>
|
AppEvents.UPDATE_ATTACHMENT_PROGRESS,
|
||||||
<Animated.Flex
|
({ hash, type, total, loaded }) => {
|
||||||
animate={{
|
if (!attachments.find((a) => a.metadata.hash === hash)) return;
|
||||||
x: arePropertiesVisible ? 0 : 800,
|
setAttachmentsStatus((status) => {
|
||||||
display: arePropertiesVisible ? "flex" : "none",
|
const copy = { ...status };
|
||||||
}}
|
copy[hash] = {
|
||||||
transition={{
|
type,
|
||||||
duration: 0.3,
|
progress: Math.round((loaded / total) * 100),
|
||||||
bounceDamping: 1,
|
};
|
||||||
bounceStiffness: 1,
|
return copy;
|
||||||
ease: "easeOut",
|
});
|
||||||
}}
|
}
|
||||||
initial={false}
|
);
|
||||||
style={{
|
return () => {
|
||||||
position: "absolute",
|
event.unsubscribe();
|
||||||
right: 0,
|
};
|
||||||
zIndex: 3,
|
}, [attachments]);
|
||||||
height: "100%",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Flex
|
|
||||||
sx={{
|
|
||||||
overflowY: "auto",
|
|
||||||
overflowX: "hidden",
|
|
||||||
height: "100%",
|
|
||||||
width: "300px",
|
|
||||||
borderLeft: "1px solid",
|
|
||||||
borderLeftColor: "border",
|
|
||||||
}}
|
|
||||||
flexDirection="column"
|
|
||||||
bg="background"
|
|
||||||
px={3}
|
|
||||||
py={0}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
variant="title"
|
|
||||||
my={2}
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="space-between"
|
|
||||||
sx={{ display: "flex" }}
|
|
||||||
>
|
|
||||||
Properties
|
|
||||||
<Text
|
|
||||||
data-test-id="properties-close"
|
|
||||||
as="span"
|
|
||||||
onClick={() => toggleProperties()}
|
|
||||||
sx={{
|
|
||||||
color: "red",
|
|
||||||
height: 24,
|
|
||||||
":active": { color: "darkRed" },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon.Close />
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
{sessionId ? (
|
|
||||||
<>
|
|
||||||
<Flex>
|
|
||||||
{tools.map((tool, _) => (
|
|
||||||
<Toggle
|
|
||||||
{...tool}
|
|
||||||
key={tool.key}
|
|
||||||
toggleKey={tool.key}
|
|
||||||
onToggle={(state) => changeState(tool.key, state)}
|
|
||||||
testId={`properties-${tool.key}`}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Flex>
|
|
||||||
<Flex flexDirection="column">
|
|
||||||
{COLORS.map((label) => (
|
|
||||||
<Flex
|
|
||||||
key={label}
|
|
||||||
justifyContent="space-between"
|
|
||||||
alignItems="center"
|
|
||||||
onClick={() => setColor(label)}
|
|
||||||
sx={{ cursor: "pointer" }}
|
|
||||||
mt={4}
|
|
||||||
data-test-id={`properties-${label}`}
|
|
||||||
>
|
|
||||||
<Flex key={label} alignItems="center">
|
|
||||||
<Icon.Circle size={14} color={label.toLowerCase()} />
|
|
||||||
<Text ml={1} color="text" variant="body">
|
|
||||||
{label}
|
|
||||||
</Text>
|
|
||||||
</Flex>
|
|
||||||
{label.toLowerCase() === color?.toLowerCase() && (
|
|
||||||
<Icon.Checkmark
|
|
||||||
color="primary"
|
|
||||||
size={20}
|
|
||||||
data-test-id={`properties-${label}-check`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
))}
|
|
||||||
</Flex>
|
|
||||||
{notebooks?.length && (
|
|
||||||
<>
|
|
||||||
<Text variant="subtitle" mt={4} mb={1}>
|
|
||||||
Referenced in {notebooks.length} notebook(s):
|
|
||||||
</Text>
|
|
||||||
{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 (
|
if (isFocusMode || !sessionId) return null;
|
||||||
<Flex flexDirection="column" my={1}>
|
return (
|
||||||
<Flex
|
<>
|
||||||
onClick={() => {
|
<Animated.Flex
|
||||||
navigate(`/notebooks/${notebook.data.id}`);
|
animate={{
|
||||||
}}
|
x: arePropertiesVisible ? 0 : 800,
|
||||||
mb={1}
|
display: arePropertiesVisible ? "flex" : "none",
|
||||||
>
|
}}
|
||||||
<Icon.Notebook size={12} />
|
transition={{
|
||||||
<Text
|
duration: 0.3,
|
||||||
variant="body"
|
bounceDamping: 1,
|
||||||
ml={1}
|
bounceStiffness: 1,
|
||||||
sx={{ cursor: "pointer" }}
|
ease: "easeOut",
|
||||||
>
|
}}
|
||||||
{notebook.title}
|
initial={false}
|
||||||
</Text>
|
sx={{
|
||||||
</Flex>
|
position: "absolute",
|
||||||
{topics.map((topic) => (
|
right: 0,
|
||||||
<Flex
|
zIndex: 3,
|
||||||
mb={1}
|
height: "100%",
|
||||||
ml={2}
|
width: "300px",
|
||||||
onClick={() => {
|
borderLeft: "1px solid",
|
||||||
navigate(
|
borderLeftColor: "border",
|
||||||
`/notebooks/${notebook.data.id}/${topic.id}`
|
overflowY: "auto",
|
||||||
);
|
overflowX: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
flexDirection="column"
|
||||||
<Icon.Topic size={12} />
|
bg="background"
|
||||||
<Text
|
// px={2}
|
||||||
variant="body"
|
>
|
||||||
ml={1}
|
<Card title="Properties">
|
||||||
sx={{ cursor: "pointer" }}
|
{tools.map((tool, _) => (
|
||||||
>
|
<Toggle
|
||||||
{topic.title}
|
{...tool}
|
||||||
</Text>
|
key={tool.key}
|
||||||
</Flex>
|
toggleKey={tool.key}
|
||||||
))}
|
onToggle={(state) => changeState(tool.key, state)}
|
||||||
</Flex>
|
testId={`properties-${tool.key}`}
|
||||||
);
|
/>
|
||||||
})}
|
))}
|
||||||
</>
|
<Flex
|
||||||
)}
|
py={2}
|
||||||
<Button
|
px={2}
|
||||||
variant="secondary"
|
sx={{
|
||||||
onClick={async () => {
|
cursor: "pointer",
|
||||||
await showMoveNoteDialog([sessionId]);
|
}}
|
||||||
}}
|
justifyContent="center"
|
||||||
data-test-id="properties-add-to-nb"
|
>
|
||||||
mt={notebooks?.length ? 0 : 3}
|
{COLORS.map((label) => (
|
||||||
>
|
<Flex
|
||||||
Add to notebook
|
key={label}
|
||||||
</Button>
|
justifyContent="space-between"
|
||||||
</>
|
alignItems="center"
|
||||||
) : (
|
onClick={() => setColor(label)}
|
||||||
<Text
|
sx={{
|
||||||
variant="body"
|
cursor: "pointer",
|
||||||
sx={{ justifySelf: "center", alignSelf: "center" }}
|
position: "relative",
|
||||||
|
}}
|
||||||
|
data-test-id={`properties-${label}`}
|
||||||
>
|
>
|
||||||
Start writing to make a new note.
|
<Icon.Circle size={35} color={label.toLowerCase()} />
|
||||||
</Text>
|
{label.toLowerCase() === color?.toLowerCase() && (
|
||||||
)}
|
<Icon.Checkmark
|
||||||
|
color="static"
|
||||||
|
size={18}
|
||||||
|
sx={{ position: "absolute", left: "8px" }}
|
||||||
|
data-test-id={`properties-${label}-check`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Animated.Flex>
|
</Card>
|
||||||
</>
|
{notebooks?.length && (
|
||||||
)
|
<Card title="Referenced In">
|
||||||
|
{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 (
|
||||||
|
<Flex
|
||||||
|
py={2}
|
||||||
|
px={2}
|
||||||
|
sx={{
|
||||||
|
borderBottom: "1px solid var(--border)",
|
||||||
|
":last-of-type": { borderBottom: "none" },
|
||||||
|
cursor: "pointer",
|
||||||
|
":hover": {
|
||||||
|
bg: "hover",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
flexDirection="column"
|
||||||
|
onClick={() => {
|
||||||
|
navigate(`/notebooks/${notebook.data.id}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text variant="body" display="flex" alignItems="center">
|
||||||
|
<Icon.Notebook size={13} sx={{ flexShrink: 0, mr: 1 }} />
|
||||||
|
{notebook.title}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
flexWrap: "wrap",
|
||||||
|
}}
|
||||||
|
mt="2.5px"
|
||||||
|
>
|
||||||
|
{topics.map((topic) => (
|
||||||
|
<IconTag
|
||||||
|
title={topic.title}
|
||||||
|
text={topic.title}
|
||||||
|
key={topic.id}
|
||||||
|
icon={Icon.Topic}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
navigate(
|
||||||
|
`/notebooks/${notebook.data.id}/${topic.id}`
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
{attachments.length > 0 && (
|
||||||
|
<Card title="Attachments">
|
||||||
|
{attachments.map((attachment) => {
|
||||||
|
const attachmentStatus =
|
||||||
|
attachmentsStatus[attachment.metadata.hash];
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
//py={2}
|
||||||
|
py={0}
|
||||||
|
px={2}
|
||||||
|
sx={{
|
||||||
|
borderBottom: "1px solid var(--border)",
|
||||||
|
":last-of-type": { borderBottom: "none" },
|
||||||
|
":hover .attachment-actions": {
|
||||||
|
display: "flex",
|
||||||
|
},
|
||||||
|
":hover .attachment-size": {
|
||||||
|
display: "none",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
title={attachment.metadata.filename}
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="space-between"
|
||||||
|
>
|
||||||
|
<Flex flexDirection="column">
|
||||||
|
<Text
|
||||||
|
variant="body"
|
||||||
|
sx={{
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{formatFilename(attachment.metadata.filename)}
|
||||||
|
</Text>
|
||||||
|
{attachmentStatus && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
my: 1,
|
||||||
|
bg: "primary",
|
||||||
|
height: "2px",
|
||||||
|
width: `${attachmentStatus.progress}%`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
<Text
|
||||||
|
className="attachment-size"
|
||||||
|
variant="subBody"
|
||||||
|
flexShrink={0}
|
||||||
|
p={1}
|
||||||
|
m={1}
|
||||||
|
>
|
||||||
|
{formatBytes(attachment.length, 1)}
|
||||||
|
</Text>
|
||||||
|
<Box display="none" className="attachment-actions">
|
||||||
|
{attachmentStatus ? (
|
||||||
|
<Button
|
||||||
|
title="Cancel download"
|
||||||
|
variant="tool"
|
||||||
|
p={1}
|
||||||
|
m={1}
|
||||||
|
bg="transparent"
|
||||||
|
sx={{ ":hover": { bg: "hover" } }}
|
||||||
|
onClick={async () => {
|
||||||
|
await db.fs.cancel(
|
||||||
|
attachment.metadata.hash,
|
||||||
|
"download"
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon.Close size={16} />
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
title="Download attachment"
|
||||||
|
variant="tool"
|
||||||
|
p={1}
|
||||||
|
m={1}
|
||||||
|
bg="transparent"
|
||||||
|
sx={{ ":hover": { bg: "hover" } }}
|
||||||
|
onClick={async () => {
|
||||||
|
await db.fs.downloadFile(
|
||||||
|
attachment.metadata.hash,
|
||||||
|
attachment.metadata.hash
|
||||||
|
);
|
||||||
|
const data = await db.fs.readEncrypted(
|
||||||
|
attachment.metadata.hash,
|
||||||
|
await db.user.getEncryptionKey(),
|
||||||
|
{
|
||||||
|
iv: attachment.iv,
|
||||||
|
salt: attachment.salt,
|
||||||
|
length: attachment.length,
|
||||||
|
alg: attachment.alg,
|
||||||
|
outputType: "uint8array",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// download(
|
||||||
|
// attachment.metadata.filename,
|
||||||
|
// data,
|
||||||
|
// attachment.metadata.type
|
||||||
|
// );
|
||||||
|
FileSaver.saveAs(
|
||||||
|
new Blob([data], {
|
||||||
|
type: attachment.metadata.type,
|
||||||
|
}),
|
||||||
|
attachment.metadata.filename
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon.Download size={16} />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</Animated.Flex>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default React.memo(Properties);
|
export default React.memo(Properties);
|
||||||
|
|
||||||
|
function Card({ title, children }) {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
flexDirection="column"
|
||||||
|
//mx={1}
|
||||||
|
//mt={2}
|
||||||
|
sx={{
|
||||||
|
//border: "1px solid var(--border)",
|
||||||
|
borderRadius: "default",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
variant="subtitle"
|
||||||
|
fontSize="subtitle"
|
||||||
|
mx={2}
|
||||||
|
my={2}
|
||||||
|
color="fontTertiary"
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
{children}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|||||||
34
apps/web/src/components/properties/toggle.css
Normal file
34
apps/web/src/components/properties/toggle.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
@@ -1,24 +1,37 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Flex, Text } from "rebass";
|
import { Flex, Text } from "rebass";
|
||||||
import { useStore } from "../../stores/editor-store";
|
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) {
|
function Toggle(props) {
|
||||||
const { icons, label, onToggle, toggleKey } = props;
|
const { icon: ToggleIcon, label, onToggle, toggleKey } = props;
|
||||||
const isOn = useStore((store) => store.session[toggleKey]);
|
const isOn = useStore((store) => store.session[toggleKey]);
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
variant="columnCenter"
|
alignItems="center"
|
||||||
width="33%"
|
justifyContent="space-between"
|
||||||
py={2}
|
py={2}
|
||||||
mr={1}
|
px={2}
|
||||||
sx={{ borderRadius: "default", cursor: "pointer" }}
|
sx={{
|
||||||
|
borderBottom: "1px solid var(--border)",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
onClick={() => onToggle(!isOn)}
|
onClick={() => onToggle(!isOn)}
|
||||||
data-test-id={props.testId}
|
data-test-id={props.testId}
|
||||||
>
|
>
|
||||||
{isOn ? <icons.on color="primary" /> : <icons.off />}
|
<Text
|
||||||
<Text mt={1} variant="body" color={isOn ? "primary" : "text"}>
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
variant="body"
|
||||||
|
color={isOn ? "primary" : "text"}
|
||||||
|
>
|
||||||
|
<ToggleIcon size={13} sx={{ flexShrink: 0, mr: 1 }} />
|
||||||
{label}
|
{label}
|
||||||
</Text>
|
</Text>
|
||||||
|
<ReactToggle size={20} defaultChecked={isOn} icons={false} />
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ const getDefaultSession = () => {
|
|||||||
color: undefined,
|
color: undefined,
|
||||||
dateEdited: 0,
|
dateEdited: 0,
|
||||||
totalWords: 0,
|
totalWords: 0,
|
||||||
|
attachments: [],
|
||||||
content: {
|
content: {
|
||||||
type: "tiny",
|
type: "tiny",
|
||||||
data: "",
|
data: "",
|
||||||
@@ -102,6 +103,7 @@ class EditorStore extends BaseStore {
|
|||||||
content: content || defaultSession.content,
|
content: content || defaultSession.content,
|
||||||
totalWords: state.session.totalWords,
|
totalWords: state.session.totalWords,
|
||||||
state: SESSION_STATES.new,
|
state: SESSION_STATES.new,
|
||||||
|
attachments: db.attachments.get(note.id) || [],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
appStore.setIsEditorOpen(true);
|
appStore.setIsEditorOpen(true);
|
||||||
@@ -146,6 +148,7 @@ class EditorStore extends BaseStore {
|
|||||||
state.session.title = note.title;
|
state.session.title = note.title;
|
||||||
state.session.isSaving = false;
|
state.session.isSaving = false;
|
||||||
state.session.notebooks = note.notebooks;
|
state.session.notebooks = note.notebooks;
|
||||||
|
state.session.attachments = db.attachments.get(note.id) || [];
|
||||||
});
|
});
|
||||||
|
|
||||||
noteStore.refresh();
|
noteStore.refresh();
|
||||||
|
|||||||
Reference in New Issue
Block a user