From 5b2187c311b5997d18a08ff8995837f28122434d Mon Sep 17 00:00:00 2001 From: swve Date: Wed, 26 Apr 2023 22:17:10 +0200 Subject: [PATCH 1/4] feat: redesign save button and add preview button --- front/components/Editor/Editor.tsx | 70 +++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/front/components/Editor/Editor.tsx b/front/components/Editor/Editor.tsx index 8f46b34a..e57546bb 100644 --- a/front/components/Editor/Editor.tsx +++ b/front/components/Editor/Editor.tsx @@ -10,7 +10,7 @@ import { motion, AnimatePresence } from "framer-motion"; import Image from "next/image"; import styled from "styled-components"; import { getBackendUrl } from "@services/config/config"; -import { SlashIcon } from "@radix-ui/react-icons"; +import { DividerVerticalIcon, EyeOpenIcon, SlashIcon } from "@radix-ui/react-icons"; import Avvvatars from "avvvatars-react"; // extensions import InfoCallout from "./Extensions/Callout/Info/InfoCallout"; @@ -18,10 +18,11 @@ import WarningCallout from "./Extensions/Callout/Warning/WarningCallout"; import ImageBlock from "./Extensions/Image/ImageBlock"; import Youtube from "@tiptap/extension-youtube"; import VideoBlock from "./Extensions/Video/VideoBlock"; -import { Save } from "lucide-react"; +import { Eye, Save } from "lucide-react"; import MathEquationBlock from "./Extensions/MathEquation/MathEquationBlock"; import PDFBlock from "./Extensions/PDF/PDFBlock"; import QuizBlock from "./Extensions/Quiz/QuizBlock"; +import ToolTip from "@components/UI/Tooltip/Tooltip"; interface Editor { content: string; @@ -113,12 +114,10 @@ function Editor(props: Editor) { {" "} {props.course.course.name} {props.activity.name}{" "} - props.setContent(editor.getJSON())}> - Save - + - + @@ -126,6 +125,11 @@ function Editor(props: Editor) { {!auth.isAuthenticated && Loading} {auth.isAuthenticated && } + + + props.setContent(editor.getJSON())}> Save + + @@ -186,11 +190,63 @@ const EditorDocSection = styled.div` `; const EditorUsersSection = styled.div` display: flex; - flex-direction: column; + flex-direction: row; justify-content: center; align-items: center; `; +const EditorLeftOptionsSection = styled.div` + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; +`; + +const EditorLeftOptionsSaveButton = styled.button` + background-color: #8783f7; + border-radius: 8px; + border: none; + color: white; + padding: 8px; + margin-left: 10px; + margin-right: 10px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + outline: none; + + + &:hover { + background-color: #4a44f9; + opacity: 0.8; + } +`; + +const EditorLeftOptionsPreviewButton = styled.button` + background-color: #a4a4a449; + border-radius: 8px; + border: none; + color: #000000; + padding: 8px; + margin-right: 10px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + outline: none; + + // center icon + display: flex; + justify-content: center; + align-items: center; + + &:hover { + background-color: #c0bfbf; + opacity: 0.8; + } + +`; + + // Inside EditorDocSection const EditorInfoWrapper = styled.div` display: flex; From 0a38f5bda71b3dddcd22f00d6dedc444928b1b1a Mon Sep 17 00:00:00 2001 From: swve Date: Wed, 26 Apr 2023 22:45:57 +0200 Subject: [PATCH 2/4] feat: implement editor preview functionality --- .../[courseid]/activity/[activityid]/edit/page.tsx | 7 +++++-- front/components/Editor/Editor.tsx | 11 +++++++++-- front/components/Editor/EditorWrapper.tsx | 3 ++- src/services/courses/courses.py | 2 +- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/front/app/_editor/course/[courseid]/activity/[activityid]/edit/page.tsx b/front/app/_editor/course/[courseid]/activity/[activityid]/edit/page.tsx index 6539e6c6..8e6e2b82 100644 --- a/front/app/_editor/course/[courseid]/activity/[activityid]/edit/page.tsx +++ b/front/app/_editor/course/[courseid]/activity/[activityid]/edit/page.tsx @@ -8,7 +8,7 @@ import { getActivity } from "@services/courses/activities"; import AuthProvider from "@components/Security/AuthProvider"; import EditorWrapper from "@components/Editor/EditorWrapper"; import useSWR, { mutate } from "swr"; -import { getAPIUrl } from "@services/config/config"; +import { getAPIUrl, getOrgFromUri } from "@services/config/config"; import { swrFetcher } from "@services/utils/ts/requests"; @@ -16,15 +16,18 @@ function EditActivity(params: any) { const router = useRouter(); const activityid = params.params.activityid; const courseid = params.params.courseid; + const orgslug = params.params.orgslug; const { data: courseInfo, error: error_course } = useSWR(`${getAPIUrl()}courses/meta/course_${courseid}`, swrFetcher); const { data: activity, error: error_activity } = useSWR(`${getAPIUrl()}activities/activity_${activityid}`, swrFetcher); + + return ( - {!courseInfo || !activity ?
Loading...
: } + {!courseInfo || !activity ?
Loading...
: }
); } diff --git a/front/components/Editor/Editor.tsx b/front/components/Editor/Editor.tsx index e57546bb..d5e10371 100644 --- a/front/components/Editor/Editor.tsx +++ b/front/components/Editor/Editor.tsx @@ -9,7 +9,7 @@ import { ToolbarButtons } from "./Toolbar/ToolbarButtons"; import { motion, AnimatePresence } from "framer-motion"; import Image from "next/image"; import styled from "styled-components"; -import { getBackendUrl } from "@services/config/config"; +import { getBackendUrl, getUriWithOrg } from "@services/config/config"; import { DividerVerticalIcon, EyeOpenIcon, SlashIcon } from "@radix-ui/react-icons"; import Avvvatars from "avvvatars-react"; // extensions @@ -23,18 +23,25 @@ import MathEquationBlock from "./Extensions/MathEquation/MathEquationBlock"; import PDFBlock from "./Extensions/PDF/PDFBlock"; import QuizBlock from "./Extensions/Quiz/QuizBlock"; import ToolTip from "@components/UI/Tooltip/Tooltip"; +import Link from "next/link"; interface Editor { content: string; ydoc: any; provider: any; activity: any; + orgslug : string course: any; setContent: (content: string) => void; } function Editor(props: Editor) { const auth: any = React.useContext(AuthContext); + // remove course_ from course_id + const course_id = props.course.course.course_id.substring(7); + + // remove activity_ from activity_id + const activity_id = props.activity.activity_id.substring(9); const editor: any = useEditor({ editable: true, @@ -128,7 +135,7 @@ function Editor(props: Editor) { props.setContent(editor.getJSON())}> Save - + diff --git a/front/components/Editor/EditorWrapper.tsx b/front/components/Editor/EditorWrapper.tsx index 359e09a3..21021cc0 100644 --- a/front/components/Editor/EditorWrapper.tsx +++ b/front/components/Editor/EditorWrapper.tsx @@ -8,6 +8,7 @@ interface EditorWrapperProps { content: string; activity: any; course:any + orgslug: string; } function EditorWrapper(props: EditorWrapperProps) : JSX.Element { @@ -35,7 +36,7 @@ function EditorWrapper(props: EditorWrapperProps) : JSX.Element { createRTCProvider(); return
Loading...
; } else { - return ; + return ; } } diff --git a/src/services/courses/courses.py b/src/services/courses/courses.py index 55b12d67..f2a8a3fa 100644 --- a/src/services/courses/courses.py +++ b/src/services/courses/courses.py @@ -118,7 +118,7 @@ async def get_course_meta(request: Request, course_id: str, current_user: Public for chapter in chapters: chapters_list_with_activities.append( {"id": chapters[chapter]["id"], "name": chapters[chapter]["name"], "activities": [activities_list[activity] for activity in chapters[chapter]["activityIds"]]}) - course = Course(**course) + course = CourseInDB(**course) # Get activity by user trail = await trails.find_one( From 660ffa3b68ca23587e0aff8329d62f4fcad5486e Mon Sep 17 00:00:00 2001 From: swve Date: Wed, 26 Apr 2023 22:51:11 +0200 Subject: [PATCH 3/4] feat: make editor topbar elements linkable --- front/components/Editor/Editor.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/front/components/Editor/Editor.tsx b/front/components/Editor/Editor.tsx index d5e10371..34f12a21 100644 --- a/front/components/Editor/Editor.tsx +++ b/front/components/Editor/Editor.tsx @@ -30,7 +30,7 @@ interface Editor { ydoc: any; provider: any; activity: any; - orgslug : string + orgslug: string course: any; setContent: (content: string) => void; } @@ -115,8 +115,12 @@ function Editor(props: Editor) { - - + + + + + + {" "} {props.course.course.name} {props.activity.name}{" "} From 9e5c20687f7d8a01843937cf7c4919245ced4efc Mon Sep 17 00:00:00 2001 From: swve Date: Sat, 29 Apr 2023 11:10:15 +0200 Subject: [PATCH 4/4] feat: use toasts for dynamic activity editor --- front/components/Editor/EditorWrapper.tsx | 31 +++++++++++++++++------ front/components/UI/Toast/Toast.tsx | 11 ++++++++ front/package-lock.json | 24 ++++++++++++++++++ front/package.json | 1 + 4 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 front/components/UI/Toast/Toast.tsx diff --git a/front/components/Editor/EditorWrapper.tsx b/front/components/Editor/EditorWrapper.tsx index 21021cc0..6b5bc38e 100644 --- a/front/components/Editor/EditorWrapper.tsx +++ b/front/components/Editor/EditorWrapper.tsx @@ -3,15 +3,17 @@ import * as Y from "yjs"; import { WebrtcProvider } from "y-webrtc"; import Editor from "./Editor"; import { updateActivity } from "@services/courses/activities"; +import { toast } from "react-hot-toast"; +import Toast from "@components/UI/Toast/Toast"; interface EditorWrapperProps { content: string; activity: any; - course:any + course: any orgslug: string; } -function EditorWrapper(props: EditorWrapperProps) : JSX.Element { +function EditorWrapper(props: EditorWrapperProps): JSX.Element { // A new Y document const ydoc = new Y.Doc(); const [providerState, setProviderState] = React.useState({}); @@ -19,24 +21,37 @@ function EditorWrapper(props: EditorWrapperProps) : JSX.Element { const [isLoading, setIsLoading] = React.useState(true); function createRTCProvider() { - // const provider = new WebrtcProvider(props.activity.activity_id, ydoc); - // setYdocState(ydoc); - // setProviderState(provider); + // const provider = new WebrtcProvider(props.activity.activity_id, ydoc); + // setYdocState(ydoc); + // setProviderState(provider); setIsLoading(false); } + + async function setContent(content: any) { let activity = props.activity; activity.content = content; - const res = await updateActivity(activity, activity.activity_id); - alert(JSON.stringify(res)); + + toast.promise( + updateActivity(activity, activity.activity_id), + { + loading: 'Saving...', + success: Activity saved!, + error: Could not save., + } + ); } if (isLoading) { createRTCProvider(); return
Loading...
; } else { - return ; + return <> + + ; + + } } diff --git a/front/components/UI/Toast/Toast.tsx b/front/components/UI/Toast/Toast.tsx new file mode 100644 index 00000000..b297ec45 --- /dev/null +++ b/front/components/UI/Toast/Toast.tsx @@ -0,0 +1,11 @@ +import React from 'react' +import { Toaster } from 'react-hot-toast'; + + +function Toast() { + return ( + <> + ) +} + +export default Toast \ No newline at end of file diff --git a/front/package-lock.json b/front/package-lock.json index f3c41c75..f13162c8 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -31,6 +31,7 @@ "react": "^18.2.0", "react-beautiful-dnd": "^13.1.1", "react-dom": "^18.2.0", + "react-hot-toast": "^2.4.1", "react-katex": "^3.0.1", "react-spinners": "^0.13.8", "styled-components": "^6.0.0-beta.9", @@ -6952,6 +6953,21 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" }, + "node_modules/react-hot-toast": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz", + "integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==", + "dependencies": { + "goober": "^2.1.10" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -13070,6 +13086,14 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" }, + "react-hot-toast": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz", + "integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==", + "requires": { + "goober": "^2.1.10" + } + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/front/package.json b/front/package.json index 1182935c..92e40679 100644 --- a/front/package.json +++ b/front/package.json @@ -32,6 +32,7 @@ "react": "^18.2.0", "react-beautiful-dnd": "^13.1.1", "react-dom": "^18.2.0", + "react-hot-toast": "^2.4.1", "react-katex": "^3.0.1", "react-spinners": "^0.13.8", "styled-components": "^6.0.0-beta.9",