Skip to content

Commit

Permalink
Add possibility to toggle scrap type (markdown <> list) (#2166)
Browse files Browse the repository at this point in the history
  • Loading branch information
PzYon authored Jul 13, 2024
1 parent e7b02f2 commit 0245479
Show file tree
Hide file tree
Showing 15 changed files with 280 additions and 138 deletions.
47 changes: 9 additions & 38 deletions app/src/components/details/scraps/QuickAddDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import React, { useState } from "react";
import { ScrapType } from "../../../serverApi/IScrapEntry";
import { ScrapsJournalType } from "../../../journalTypes/ScrapsJournalType";
import { styled, ToggleButton, ToggleButtonGroup } from "@mui/material";
import { ListScrapIcon, MarkdownScrapIcon } from "./ScrapsViewPage";
import { styled } from "@mui/material";
import { JournalSelector } from "../../common/JournalSelector";
import { Scrap } from "./Scrap";
import { UserRole } from "../../../serverApi/UserRole";
import { useAppContext } from "../../../AppContext";
import { getPermissionsForUser } from "../../overview/journals/useJournalPermissions";
import { ScrapType } from "../../../serverApi/IScrapEntry";

export const QuickAddDialog: React.FC<{
targetJournalId: string;
onSuccess?: () => void;
}> = ({ targetJournalId, onSuccess }) => {
const { user } = useAppContext();

const [type, setType] = useState<ScrapType>(ScrapType.Markdown);

const scrap = ScrapsJournalType.createBlank(true, targetJournalId, type);
const scrap = ScrapsJournalType.createBlank(
true,
targetJournalId,
ScrapType.Markdown,
);

const [journalId, setJournalId] = useState(scrap.parentId);

Expand All @@ -37,52 +38,22 @@ export const QuickAddDialog: React.FC<{
onChange={(journal) => setJournalId(journal.id)}
selectedJournalId={journalId}
/>
<ScrapTypeSelector>
<ToggleButtonGroup
value={type}
exclusive
sx={{ width: "100%", ".MuiButtonBase-root": { width: "50%" } }}
onChange={(_, value) => {
setType(
value !== null
? value
: type === ScrapType.Markdown
? ScrapType.List
: ScrapType.Markdown,
);
}}
>
<ToggleButton value={ScrapType.Markdown} color="primary">
<MarkdownScrapIcon />
&nbsp;Markdown
</ToggleButton>
<ToggleButton value={ScrapType.List} color="primary">
<ListScrapIcon />
&nbsp;List
</ToggleButton>
</ToggleButtonGroup>
</ScrapTypeSelector>

<ScrapContainer>
<Scrap
key={type}
scrap={scrap}
propsRenderStyle={"none"}
actionsRenderStyle={"save-only"}
onSuccess={onSuccess}
isQuickAdd={true}
targetJournalId={journalId}
changeTypeWithoutConfirmation={true}
/>
</ScrapContainer>
</>
);
};

const ScrapTypeSelector = styled("div")`
padding-top: ${(p) => p.theme.spacing(2)};
display: flex;
justify-content: start;
`;

const ScrapContainer = styled("div")`
padding-top: ${(p) => p.theme.spacing(2)};
margin-top: ${(p) => p.theme.spacing(2)};
Expand Down
3 changes: 3 additions & 0 deletions app/src/components/details/scraps/Scrap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const Scrap: React.FC<{
giveFocus?: () => void;
isQuickAdd?: boolean;
targetJournalId?: string;
changeTypeWithoutConfirmation?: boolean;
}> = ({
scrap: currentScrap,
journal,
Expand All @@ -28,6 +29,7 @@ export const Scrap: React.FC<{
giveFocus,
isQuickAdd,
targetJournalId,
changeTypeWithoutConfirmation,
}) => {
const [doRender, setDoRender] = useState(hasFocus);

Expand All @@ -52,6 +54,7 @@ export const Scrap: React.FC<{
giveFocus={giveFocus}
isQuickAdd={isQuickAdd}
targetJournalId={targetJournalId}
changeTypeWithoutConfirmation={changeTypeWithoutConfirmation}
>
<ScrapInner />
</ScrapContextProvider>
Expand Down
58 changes: 37 additions & 21 deletions app/src/components/details/scraps/ScrapBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ import {
getSchedulePropertyFromSchedule,
} from "../../overview/scheduled/scheduleUtils";
import { useDisplayModeContext } from "../../overview/overviewList/DisplayModeContext";
import { ActionIconButtonGroup } from "../../common/actions/ActionIconButtonGroup";
import { styled } from "@mui/material";

export const ScrapBody: React.FC<{
children: React.ReactNode;
actions: IAction[];
}> = ({ children, actions }) => {
editModeActions: IAction[];
}> = ({ children, actions, editModeActions }) => {
const { user } = useAppContext();

const {
Expand All @@ -33,26 +36,34 @@ export const ScrapBody: React.FC<{
const { isCompact } = useDisplayModeContext();

return (
<Entry
hasFocus={hasFocus}
journal={journal}
entry={scrapToRender}
actions={getActions()}
propsRenderStyle={propsRenderStyle}
giveFocus={giveFocus}
propertyOverrides={
parsedDate?.date
? [
getSchedulePropertyFromSchedule({
nextOccurrence: parsedDate.date.toString(),
recurrence: parsedDate.recurrence,
}),
]
: []
}
>
{isCompact && !hasFocus ? null : children}
</Entry>
<>
<Entry
hasFocus={hasFocus}
journal={journal}
entry={scrapToRender}
actions={getActions()}
propsRenderStyle={propsRenderStyle}
giveFocus={giveFocus}
propertyOverrides={
parsedDate?.date
? [
getSchedulePropertyFromSchedule({
nextOccurrence: parsedDate.date.toString(),
recurrence: parsedDate.recurrence,
}),
]
: []
}
>
{isCompact && !hasFocus ? null : children}

{isEditMode && editModeActions?.length ? (
<ActionsContainer>
<ActionIconButtonGroup actions={editModeActions} />
</ActionsContainer>
) : null}
</Entry>
</>
);

function getActions() {
Expand Down Expand Up @@ -98,3 +109,8 @@ export const ScrapBody: React.FC<{
return allActions;
}
};

const ActionsContainer = styled("div")`
display: flex;
padding: 3px 0 4px 3px;
`;
4 changes: 3 additions & 1 deletion app/src/components/details/scraps/ScrapContext.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createContext, useContext } from "react";
import { IScrapEntry } from "../../../serverApi/IScrapEntry";
import { IScrapEntry, ScrapType } from "../../../serverApi/IScrapEntry";
import { EntryPropsRenderStyle } from "../../common/entries/Entry";
import { IParsedDate } from "../edit/parseDate";
import { IAction } from "../../common/actions/IAction";
Expand Down Expand Up @@ -28,6 +28,7 @@ export interface IScrapContext {
giveFocus?: () => void;
hasTitleFocus: boolean;
setHasTitleFocus: (value: boolean) => void;
changeScrapType: (rows: string[], targetType: ScrapType) => void;
}

export const ScrapContext = createContext<IScrapContext>({
Expand All @@ -51,6 +52,7 @@ export const ScrapContext = createContext<IScrapContext>({
giveFocus: null,
hasTitleFocus: null,
setHasTitleFocus: null,
changeScrapType: null,
});

export const useScrapContext = () => {
Expand Down
86 changes: 78 additions & 8 deletions app/src/components/details/scraps/ScrapContextProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useEffect, useMemo, useState } from "react";
import { IScrapEntry } from "../../../serverApi/IScrapEntry";
import { IScrapEntry, ScrapType } from "../../../serverApi/IScrapEntry";
import { useAppContext } from "../../../AppContext";
import { Button } from "@mui/material";
import { Button, Typography } from "@mui/material";
import { IUpsertScrapsEntryCommand } from "../../../serverApi/commands/IUpsertScrapsEntryCommand";
import { useUpsertEntryMutation } from "../../../serverApi/reactQuery/mutations/useUpsertEntryMutation";
import { JournalType } from "../../../serverApi/JournalType";
Expand All @@ -17,6 +17,8 @@ import { ActionFactory } from "../../common/actions/ActionFactory";
import { useDialogContext } from "../../layout/dialogs/DialogContext";
import { IJournal } from "../../../serverApi/IJournal";
import { AddNewScrapStorage } from "./AddNewScrapStorage";
import { IScrapListItem } from "./list/IScrapListItem";
import { DialogFormButtonContainer } from "../../common/FormButtonContainer";

export const ScrapContextProvider: React.FC<{
children: React.ReactNode;
Expand All @@ -30,6 +32,7 @@ export const ScrapContextProvider: React.FC<{
giveFocus?: () => void;
isQuickAdd?: boolean;
targetJournalId?: string;
changeTypeWithoutConfirmation?: boolean;
}> = ({
children,
currentScrap,
Expand All @@ -42,6 +45,7 @@ export const ScrapContextProvider: React.FC<{
giveFocus,
isQuickAdd,
targetJournalId,
changeTypeWithoutConfirmation,
}) => {
const { setAppAlert } = useAppContext();
const { renderDialog } = useDialogContext();
Expand Down Expand Up @@ -156,6 +160,57 @@ export const ScrapContextProvider: React.FC<{
setNotes(currentScrap.notes);
}

function changeScrapTypeInternal(
genericNotes: string[],
targetType: ScrapType,
) {
function changeType() {
const newNotes = convertNotesToTargetType(targetType, genericNotes);

setNotes(newNotes);

setScrapToRender({
...scrapToRender,
notes: newNotes,
scrapType: targetType,
});
}

if (changeTypeWithoutConfirmation) {
changeType();
return;
}

renderDialog({
title: "Are you sure?",
render: (closeDialog) => {
return (
<>
<Typography>
Do you really want to change the type to {targetType}? Certain
formatting might be lost.
</Typography>

<DialogFormButtonContainer>
<Button variant={"contained"} onClick={closeDialog}>
No
</Button>
<Button
variant={"outlined"}
onClick={() => {
changeType();
closeDialog();
}}
>
Yes, convert to {targetType.toLowerCase()}
</Button>
</DialogFormButtonContainer>
</>
);
},
});
}

const contextValue = useMemo<IScrapContext>(
() => {
return {
Expand Down Expand Up @@ -199,6 +254,7 @@ export const ScrapContextProvider: React.FC<{
giveFocus,
hasTitleFocus,
setHasTitleFocus,
changeScrapType: changeScrapTypeInternal,
};
},
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down Expand Up @@ -235,23 +291,23 @@ export const ScrapContextProvider: React.FC<{

await upsertEntryMutation.mutateAsync({
command: {
id: currentScrap.id,
scrapType: currentScrap.scrapType,
id: scrapToRender.id,
scrapType: scrapToRender.scrapType,
notes: notesToSave,
title: parsedDate?.text ?? title,
journalAttributeValues: {},
journalId: targetJournalId ?? currentScrap.parentId,
journalId: targetJournalId ?? scrapToRender.parentId,
dateTime: new Date(),
schedule: getScheduleDefinition(
parsedDate,
currentScrap.parentId,
currentScrap?.id ?? "{0}",
scrapToRender.parentId,
scrapToRender?.id ?? "{0}",
),
} as IUpsertScrapsEntryCommand,
});

AddNewScrapStorage.clearForJournal(
isQuickAdd ? "quick-add" : currentScrap.parentId,
isQuickAdd ? "quick-add" : scrapToRender.parentId,
);
}

Expand All @@ -261,3 +317,17 @@ export const ScrapContextProvider: React.FC<{
</ScrapContext.Provider>
);
};

function convertNotesToTargetType(
targetType: ScrapType,
genericNotes: string[],
) {
switch (targetType) {
case ScrapType.List:
return JSON.stringify(
genericNotes.map((n) => ({ label: n }) as IScrapListItem),
);
case ScrapType.Markdown:
return genericNotes.map((n) => "- " + n).join("\n");
}
}
4 changes: 2 additions & 2 deletions app/src/components/details/scraps/ScrapInner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useScrapContext } from "./ScrapContext";
import { ScrapMarkdown } from "./markdown/ScrapMarkdown";
import { ScrapList } from "./list/ScrapList";
import { useDisplayModeContext } from "../../overview/overviewList/DisplayModeContext";
import { ISCrapListItem } from "./list/IScrapListItem";
import { IScrapListItem } from "./list/IScrapListItem";
import { ReadonlyTitle } from "../../overview/ReadonlyTitle";
import { ParseableDate } from "../edit/ParseableDate";

Expand Down Expand Up @@ -77,7 +77,7 @@ export const ScrapInner: React.FC = () => {
return notes;

case ScrapType.List:
return (JSON.parse(notes) as ISCrapListItem[])[0].label;
return (JSON.parse(notes) as IScrapListItem[])[0].label;

default:
throw new Error(`Unknown scrap type ${scrapToRender.scrapType}`);
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/details/scraps/list/IScrapListItem.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface ISCrapListItem {
export interface IScrapListItem {
label: string;
isCompleted: boolean;
depth: number;
Expand Down
Loading

0 comments on commit 0245479

Please sign in to comment.