Merge pull request #77 from learnhouse/swve/eng-22-improve-editor-topbar

Improve editor elements
This commit is contained in:
Badr B 2023-04-29 11:27:32 +02:00 committed by GitHub
commit 7e9ac3712b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 143 additions and 21 deletions

View file

@ -8,7 +8,7 @@ import { getActivity } from "@services/courses/activities";
import AuthProvider from "@components/Security/AuthProvider"; import AuthProvider from "@components/Security/AuthProvider";
import EditorWrapper from "@components/Editor/EditorWrapper"; import EditorWrapper from "@components/Editor/EditorWrapper";
import useSWR, { mutate } from "swr"; 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"; import { swrFetcher } from "@services/utils/ts/requests";
@ -16,15 +16,18 @@ function EditActivity(params: any) {
const router = useRouter(); const router = useRouter();
const activityid = params.params.activityid; const activityid = params.params.activityid;
const courseid = params.params.courseid; 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: courseInfo, error: error_course } = useSWR(`${getAPIUrl()}courses/meta/course_${courseid}`, swrFetcher);
const { data: activity, error: error_activity } = useSWR(`${getAPIUrl()}activities/activity_${activityid}`, swrFetcher); const { data: activity, error: error_activity } = useSWR(`${getAPIUrl()}activities/activity_${activityid}`, swrFetcher);
return ( return (
<AuthProvider> <AuthProvider>
{!courseInfo || !activity ? <div>Loading...</div> : <EditorWrapper course={courseInfo} activity={activity} content={activity.content}></EditorWrapper>} {!courseInfo || !activity ? <div>Loading...</div> : <EditorWrapper orgslug={orgslug} course={courseInfo} activity={activity} content={activity.content}></EditorWrapper>}
</AuthProvider> </AuthProvider>
); );
} }

View file

@ -9,8 +9,8 @@ import { ToolbarButtons } from "./Toolbar/ToolbarButtons";
import { motion, AnimatePresence } from "framer-motion"; import { motion, AnimatePresence } from "framer-motion";
import Image from "next/image"; import Image from "next/image";
import styled from "styled-components"; import styled from "styled-components";
import { getBackendUrl } from "@services/config/config"; import { getBackendUrl, getUriWithOrg } 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"; import Avvvatars from "avvvatars-react";
// extensions // extensions
import InfoCallout from "./Extensions/Callout/Info/InfoCallout"; import InfoCallout from "./Extensions/Callout/Info/InfoCallout";
@ -18,22 +18,30 @@ import WarningCallout from "./Extensions/Callout/Warning/WarningCallout";
import ImageBlock from "./Extensions/Image/ImageBlock"; import ImageBlock from "./Extensions/Image/ImageBlock";
import Youtube from "@tiptap/extension-youtube"; import Youtube from "@tiptap/extension-youtube";
import VideoBlock from "./Extensions/Video/VideoBlock"; import VideoBlock from "./Extensions/Video/VideoBlock";
import { Save } from "lucide-react"; import { Eye, Save } from "lucide-react";
import MathEquationBlock from "./Extensions/MathEquation/MathEquationBlock"; import MathEquationBlock from "./Extensions/MathEquation/MathEquationBlock";
import PDFBlock from "./Extensions/PDF/PDFBlock"; import PDFBlock from "./Extensions/PDF/PDFBlock";
import QuizBlock from "./Extensions/Quiz/QuizBlock"; import QuizBlock from "./Extensions/Quiz/QuizBlock";
import ToolTip from "@components/UI/Tooltip/Tooltip";
import Link from "next/link";
interface Editor { interface Editor {
content: string; content: string;
ydoc: any; ydoc: any;
provider: any; provider: any;
activity: any; activity: any;
orgslug: string
course: any; course: any;
setContent: (content: string) => void; setContent: (content: string) => void;
} }
function Editor(props: Editor) { function Editor(props: Editor) {
const auth: any = React.useContext(AuthContext); 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({ const editor: any = useEditor({
editable: true, editable: true,
@ -107,18 +115,20 @@ function Editor(props: Editor) {
<EditorTop> <EditorTop>
<EditorDocSection> <EditorDocSection>
<EditorInfoWrapper> <EditorInfoWrapper>
<EditorInfoLearnHouseLogo width={25} height={25} src={learnhouseIcon} alt="" /> <Link href="/">
<EditorInfoThumbnail src={`${getBackendUrl()}content/uploads/img/${props.course.course.thumbnail}`} alt=""></EditorInfoThumbnail> <EditorInfoLearnHouseLogo width={25} height={25} src={learnhouseIcon} alt="" />
</Link>
<Link target="_blank" href={`/course/${course_id}`}>
<EditorInfoThumbnail src={`${getBackendUrl()}content/uploads/img/${props.course.course.thumbnail}`} alt=""></EditorInfoThumbnail>
</Link>
<EditorInfoDocName> <EditorInfoDocName>
{" "} {" "}
<b>{props.course.course.name}</b> <SlashIcon /> {props.activity.name}{" "} <b>{props.course.course.name}</b> <SlashIcon /> {props.activity.name}{" "}
</EditorInfoDocName> </EditorInfoDocName>
<EditorSaveButton onClick={() => props.setContent(editor.getJSON())}>
Save <Save size={11} />
</EditorSaveButton>
</EditorInfoWrapper> </EditorInfoWrapper>
<EditorButtonsWrapper> <EditorButtonsWrapper>
<ToolbarButtons editor={editor} /> <ToolbarButtons editor={editor} />
</EditorButtonsWrapper> </EditorButtonsWrapper>
</EditorDocSection> </EditorDocSection>
<EditorUsersSection> <EditorUsersSection>
@ -126,6 +136,11 @@ function Editor(props: Editor) {
{!auth.isAuthenticated && <span>Loading</span>} {!auth.isAuthenticated && <span>Loading</span>}
{auth.isAuthenticated && <Avvvatars value={auth.userInfo.user_object.user_id} style="shape" />} {auth.isAuthenticated && <Avvvatars value={auth.userInfo.user_object.user_id} style="shape" />}
</EditorUserProfileWrapper> </EditorUserProfileWrapper>
<DividerVerticalIcon style={{ marginTop: "auto", marginBottom: "auto", color: "grey" }} />
<EditorLeftOptionsSection>
<EditorLeftOptionsSaveButton onClick={() => props.setContent(editor.getJSON())}> Save </EditorLeftOptionsSaveButton>
<ToolTip content="Preview"><Link target="_blank" href={`/course/${course_id}/activity/${activity_id}`}><EditorLeftOptionsPreviewButton> <Eye size={15} /> </EditorLeftOptionsPreviewButton></Link></ToolTip>
</EditorLeftOptionsSection>
</EditorUsersSection> </EditorUsersSection>
</EditorTop> </EditorTop>
</motion.div> </motion.div>
@ -186,11 +201,63 @@ const EditorDocSection = styled.div`
`; `;
const EditorUsersSection = styled.div` const EditorUsersSection = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: row;
justify-content: center; justify-content: center;
align-items: 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 // Inside EditorDocSection
const EditorInfoWrapper = styled.div` const EditorInfoWrapper = styled.div`
display: flex; display: flex;

View file

@ -3,14 +3,17 @@ import * as Y from "yjs";
import { WebrtcProvider } from "y-webrtc"; import { WebrtcProvider } from "y-webrtc";
import Editor from "./Editor"; import Editor from "./Editor";
import { updateActivity } from "@services/courses/activities"; import { updateActivity } from "@services/courses/activities";
import { toast } from "react-hot-toast";
import Toast from "@components/UI/Toast/Toast";
interface EditorWrapperProps { interface EditorWrapperProps {
content: string; content: string;
activity: any; activity: any;
course:any course: any
orgslug: string;
} }
function EditorWrapper(props: EditorWrapperProps) : JSX.Element { function EditorWrapper(props: EditorWrapperProps): JSX.Element {
// A new Y document // A new Y document
const ydoc = new Y.Doc(); const ydoc = new Y.Doc();
const [providerState, setProviderState] = React.useState<any>({}); const [providerState, setProviderState] = React.useState<any>({});
@ -18,24 +21,37 @@ function EditorWrapper(props: EditorWrapperProps) : JSX.Element {
const [isLoading, setIsLoading] = React.useState(true); const [isLoading, setIsLoading] = React.useState(true);
function createRTCProvider() { function createRTCProvider() {
// const provider = new WebrtcProvider(props.activity.activity_id, ydoc); // const provider = new WebrtcProvider(props.activity.activity_id, ydoc);
// setYdocState(ydoc); // setYdocState(ydoc);
// setProviderState(provider); // setProviderState(provider);
setIsLoading(false); setIsLoading(false);
} }
async function setContent(content: any) { async function setContent(content: any) {
let activity = props.activity; let activity = props.activity;
activity.content = content; 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: <b>Activity saved!</b>,
error: <b>Could not save.</b>,
}
);
} }
if (isLoading) { if (isLoading) {
createRTCProvider(); createRTCProvider();
return <div>Loading...</div>; return <div>Loading...</div>;
} else { } else {
return <Editor course={props.course} activity={props.activity} content={props.content} setContent={setContent} provider={providerState} ydoc={ydocState}></Editor>; return <>
<Toast></Toast>
<Editor orgslug={props.orgslug} course={props.course} activity={props.activity} content={props.content} setContent={setContent} provider={providerState} ydoc={ydocState}></Editor>;
</>
} }
} }

View file

@ -0,0 +1,11 @@
import React from 'react'
import { Toaster } from 'react-hot-toast';
function Toast() {
return (
<><Toaster /></>
)
}
export default Toast

View file

@ -31,6 +31,7 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1", "react-beautiful-dnd": "^13.1.1",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1",
"react-katex": "^3.0.1", "react-katex": "^3.0.1",
"react-spinners": "^0.13.8", "react-spinners": "^0.13.8",
"styled-components": "^6.0.0-beta.9", "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", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
"integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" "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": { "node_modules/react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "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", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
"integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" "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": { "react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",

View file

@ -32,6 +32,7 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1", "react-beautiful-dnd": "^13.1.1",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1",
"react-katex": "^3.0.1", "react-katex": "^3.0.1",
"react-spinners": "^0.13.8", "react-spinners": "^0.13.8",
"styled-components": "^6.0.0-beta.9", "styled-components": "^6.0.0-beta.9",

View file

@ -118,7 +118,7 @@ async def get_course_meta(request: Request, course_id: str, current_user: Public
for chapter in chapters: for chapter in chapters:
chapters_list_with_activities.append( chapters_list_with_activities.append(
{"id": chapters[chapter]["id"], "name": chapters[chapter]["name"], "activities": [activities_list[activity] for activity in chapters[chapter]["activityIds"]]}) {"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 # Get activity by user
trail = await trails.find_one( trail = await trails.find_one(