feat: init new editor design

This commit is contained in:
swve 2022-12-03 00:39:50 +01:00
parent d361e68dc0
commit f349378ff9
15 changed files with 264 additions and 136 deletions

View file

@ -4,19 +4,28 @@ import StarterKit from "@tiptap/starter-kit";
import Collaboration from "@tiptap/extension-collaboration"; import Collaboration from "@tiptap/extension-collaboration";
import CollaborationCursor from "@tiptap/extension-collaboration-cursor"; import CollaborationCursor from "@tiptap/extension-collaboration-cursor";
import { AuthContext } from "../Security/AuthProvider"; import { AuthContext } from "../Security/AuthProvider";
import learnhouseIcon from "public/learnhouse_icon.png";
import { ToolbarButtons } from "./Toolbar/ToolbarButtons"; import { ToolbarButtons } from "./Toolbar/ToolbarButtons";
import Image from "next/image";
import styled from "styled-components";
import { getBackendUrl } from "../../services/config";
import { RocketIcon, SlashIcon, TriangleLeftIcon, TriangleRightIcon } from "@radix-ui/react-icons";
interface Editor { interface Editor {
content: string; content: string;
ydoc: any; ydoc: any;
provider: any; provider: any;
element: 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);
console.log(props.element);
console.log(props.course);
const editor : any = useEditor({ const editor: any = useEditor({
extensions: [ extensions: [
StarterKit.configure({ StarterKit.configure({
// The Collaboration extension comes with its own history handling // The Collaboration extension comes with its own history handling
@ -41,12 +50,131 @@ function Editor(props: Editor) {
return ( return (
<div> <div>
File <button onClick={() => props.setContent(editor.getJSON())}>save</button> <EditorTop>
<br /><hr /> <EditorDocSection>
<ToolbarButtons editor={editor} /> <EditorInfoWrapper>
<EditorContent editor={editor} style={{ backgroundColor: "white" }} /> <EditorInfoLearnHouseLogo width={23} height={23} src={learnhouseIcon} alt="" />
<EditorInfoThumbnail src={`${getBackendUrl()}content/uploads/img/${props.course.course.thumbnail}`} alt=""></EditorInfoThumbnail>
<EditorInfoDocName>
{" "}
<b>{props.course.course.name}</b> <SlashIcon /> {props.element.name}{" "}
</EditorInfoDocName>
<EditorSaveButton onClick={() => props.setContent(editor.getJSON())}>
<RocketIcon></RocketIcon>
</EditorSaveButton>
</EditorInfoWrapper>
<EditorButtonsWrapper>
<ToolbarButtons editor={editor} />
</EditorButtonsWrapper>
</EditorDocSection>
<EditorUsersSection></EditorUsersSection>
</EditorTop>
<EditorContentWrapper>
<EditorContent editor={editor} />
</EditorContentWrapper>
</div> </div>
); );
} }
const EditorTop = styled.div`
background-color: white;
border-radius: 15px;
margin: 40px;
margin-bottom: 20px;
padding: 10px;
`;
// Inside EditorTop
const EditorDocSection = styled.div`
display: flex;
flex-direction: column;
`;
const EditorUsersSection = styled.div`
display: flex;
flex-direction: column;
`;
// Inside EditorDocSection
const EditorInfoWrapper = styled.div`
display: flex;
flex-direction: row;
margin-bottom: 5px;
`;
const EditorButtonsWrapper = styled.div``;
// Inside EditorUsersSection
const EditorUserProfileWrapper = styled.div``;
// Inside EditorInfoWrapper
//..todo
const EditorInfoLearnHouseLogo = styled(Image)`
border-radius: 6px;
margin-right: 15px;
`;
const EditorInfoDocName = styled.div`
font-size: 16px;
justify-content: center;
align-items: center;
display: flex;
margin-left: 10px;
svg {
margin-left: 4px;
margin-right: 4px;
color: #909090;
}
`;
const EditorSaveButton = styled.div`
display: flex;
border-radius: 6px;
width: 25px;
height: 25px;
padding: 5px;
font-size: 5px;
margin-right: 5px;
margin-left: 7px;
&.is-active {
background: rgba(176, 176, 176, 0.5);
&:hover {
background: rgba(139, 139, 139, 0.5);
cursor: pointer;
}
}
&:hover {
background: rgba(217, 217, 217, 0.48);
cursor: pointer;
}
`;
const EditorInfoThumbnail = styled.img`
height: 25px;
width: 56px;
object-fit: cover;
object-position: top;
border-radius: 7px;
margin-left: 5px;
`;
const EditorContentWrapper = styled.div`
margin: 40px;
background-color: white;
// disable chrome outline
.ProseMirror {
padding: 10px;
&:focus {
outline: none !important;
outline-style: none !important;
box-shadow: none !important;
}
}
`;
export default Editor; export default Editor;

View file

@ -7,6 +7,7 @@ import { updateElement } from "../../services/courses/elements";
interface EditorWrapperProps { interface EditorWrapperProps {
content: string; content: string;
element: any; element: any;
course:any
} }
function EditorWrapper(props: EditorWrapperProps) { function EditorWrapper(props: EditorWrapperProps) {
@ -18,7 +19,6 @@ function EditorWrapper(props: EditorWrapperProps) {
function createRTCProvider() { function createRTCProvider() {
const provider = new WebrtcProvider(props.element.element_id, ydoc); const provider = new WebrtcProvider(props.element.element_id, ydoc);
setYdocState(ydoc); setYdocState(ydoc);
setProviderState(provider); setProviderState(provider);
setIsLoading(false); setIsLoading(false);
@ -28,12 +28,13 @@ function EditorWrapper(props: EditorWrapperProps) {
let element = props.element; let element = props.element;
element.content = content; element.content = content;
const res = await updateElement(element, element.element_id); const res = await updateElement(element, element.element_id);
alert(JSON.stringify(res));
} }
if (isLoading) { if (isLoading) {
createRTCProvider(); createRTCProvider();
} else { } else {
return <Editor content={props.content} setContent={setContent} provider={providerState} ydoc={ydocState}></Editor>; return <Editor course={props.course} element={props.element} content={props.content} setContent={setContent} provider={providerState} ydoc={ydocState}></Editor>;
} }
} }

View file

@ -1,99 +1,82 @@
export const ToolbarButtons = ({ editor }: any) => { import styled from "styled-components";
if (!editor) { import { FontBoldIcon, FontItalicIcon, StrikethroughIcon, ArrowLeftIcon, ArrowRightIcon } from "@radix-ui/react-icons";
return null;
}
return ( export const ToolbarButtons = ({ editor }: any) => {
<> if (!editor) {
<button return null;
onClick={() => editor.chain().focus().toggleBold().run()} }
disabled={!editor.can().chain().focus().toggleBold().run()}
className={editor.isActive("bold") ? "is-active" : ""} return (
> <ToolButtonsWrapper>
bold <ToolBtn onClick={() => editor.chain().focus().toggleBold().run()} className={editor.isActive("bold") ? "is-active" : ""}>
</button> <FontBoldIcon />
<button </ToolBtn>
onClick={() => editor.chain().focus().toggleItalic().run()} <ToolBtn onClick={() => editor.chain().focus().toggleItalic().run()} className={editor.isActive("italic") ? "is-active" : ""}>
disabled={!editor.can().chain().focus().toggleItalic().run()} <FontItalicIcon />
className={editor.isActive("italic") ? "is-active" : ""} </ToolBtn>
> <ToolBtn onClick={() => editor.chain().focus().toggleStrike().run()} className={editor.isActive("strike") ? "is-active" : ""}>
italic <StrikethroughIcon />
</button> </ToolBtn>
<button
onClick={() => editor.chain().focus().toggleStrike().run()} <ToolBtn onClick={() => editor.chain().focus().undo().run()}>
disabled={!editor.can().chain().focus().toggleStrike().run()} <ArrowLeftIcon />
className={editor.isActive("strike") ? "is-active" : ""} </ToolBtn>
> <ToolBtn onClick={() => editor.chain().focus().redo().run()}>
strike <ArrowRightIcon />
</button> </ToolBtn>
<button <ToolSelect onChange={(e) => editor.chain().focus().toggleHeading({ level: parseInt(e.target.value) }).run() }>
onClick={() => editor.chain().focus().toggleCode().run()} <option value="1">Heading 1</option>
disabled={!editor.can().chain().focus().toggleCode().run()} <option value="2">Heading 2</option>
className={editor.isActive("code") ? "is-active" : ""} <option value="3">Heading 3</option>
> <option value="4">Heading 4</option>
code <option value="5">Heading 5</option>
</button> <option value="6">Heading 6</option>
<button onClick={() => editor.chain().focus().unsetAllMarks().run()}>clear marks</button> </ToolSelect>
<button onClick={() => editor.chain().focus().clearNodes().run()}>clear nodes</button> </ToolButtonsWrapper>
<button onClick={() => editor.chain().focus().setParagraph().run()} className={editor.isActive("paragraph") ? "is-active" : ""}> );
paragraph };
</button>
<button const ToolButtonsWrapper = styled.div`
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()} display: flex;
className={editor.isActive("heading", { level: 1 }) ? "is-active" : ""} flex-direction: row;
> align-items: left;
h1 justify-content: left;
</button> `;
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} const ToolBtn = styled.div`
className={editor.isActive("heading", { level: 2 }) ? "is-active" : ""} display: flex;
> background: rgba(217, 217, 217, 0.24);
h2 border-radius: 6px;
</button> width: 25px;
<button height: 25px;
onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} padding: 5px;
className={editor.isActive("heading", { level: 3 }) ? "is-active" : ""} font-size: 5px;
> margin-right: 5px;
h3
</button> &.is-active {
<button background: rgba(176, 176, 176, 0.5);
onClick={() => editor.chain().focus().toggleHeading({ level: 4 }).run()}
className={editor.isActive("heading", { level: 4 }) ? "is-active" : ""} &:hover {
> background: rgba(139, 139, 139, 0.5);
h4 cursor: pointer;
</button> }
<button }
onClick={() => editor.chain().focus().toggleHeading({ level: 5 }).run()}
className={editor.isActive("heading", { level: 5 }) ? "is-active" : ""} &:hover {
> background: rgba(217, 217, 217, 0.48);
h5 cursor: pointer;
</button> }
<button `;
onClick={() => editor.chain().focus().toggleHeading({ level: 6 }).run()}
className={editor.isActive("heading", { level: 6 }) ? "is-active" : ""} const ToolSelect = styled.select`
> display: flex;
h6 background: rgba(217, 217, 217, 0.24);
</button> border-radius: 6px;
<button onClick={() => editor.chain().focus().toggleBulletList().run()} className={editor.isActive("bulletList") ? "is-active" : ""}> width: 100px;
bullet list border: none;
</button> height: 25px;
<button onClick={() => editor.chain().focus().toggleOrderedList().run()} className={editor.isActive("orderedList") ? "is-active" : ""}> padding: 5px;
ordered list font-size: 11px;
</button> font-family: "DM Sans";
<button onClick={() => editor.chain().focus().toggleCodeBlock().run()} className={editor.isActive("codeBlock") ? "is-active" : ""}> margin-right: 5px;
code block `;
</button>
<button onClick={() => editor.chain().focus().toggleBlockquote().run()} className={editor.isActive("blockquote") ? "is-active" : ""}>
blockquote
</button>
<button onClick={() => editor.chain().focus().setHorizontalRule().run()}>horizontal rule</button>
<button onClick={() => editor.chain().focus().setHardBreak().run()}>hard break</button>
<button onClick={() => editor.chain().focus().undo().run()} disabled={!editor.can().chain().focus().undo().run()}>
undo
</button>
<button onClick={() => editor.chain().focus().redo().run()} disabled={!editor.can().chain().focus().redo().run()}>
redo
</button>
</>
);
};

View file

@ -4,7 +4,7 @@ import styled from "styled-components";
import learnhouseBigIcon from "public/learnhouse_bigicon.png"; import learnhouseBigIcon from "public/learnhouse_bigicon.png";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { PreAlphaLabel } from "../components/rename/UI/Layout"; import { PreAlphaLabel } from "../components//UI/Layout";
const Home: NextPage = () => { const Home: NextPage = () => {
return ( return (

View file

@ -1,8 +1,8 @@
import Router from "next/router"; import Router from "next/router";
import React from "react"; import React from "react";
import { Header } from "../components/rename/UI/Header"; import { Header } from "../components//UI/Header";
import Layout from "../components/rename/UI/Layout"; import Layout from "../components//UI/Layout";
import { Title } from "../components/rename/UI/Elements/Styles/Title"; import { Title } from "../components//UI/Elements/Styles/Title";
import { loginAndGetToken } from "../services/auth/auth"; import { loginAndGetToken } from "../services/auth/auth";
const Login = () => { const Login = () => {

View file

@ -1,9 +1,9 @@
import React from "react"; import React from "react";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import styled from "styled-components"; import styled from "styled-components";
import { Header } from "../../../../../../components/rename/UI/Header"; import { Header } from "../../../../../../components//UI/Header";
import Layout from "../../../../../../components/rename/UI/Layout"; import Layout from "../../../../../../components//UI/Layout";
import { Title } from "../../../../../../components/rename/UI/Elements/Styles/Title"; import { Title } from "../../../../../../components//UI/Elements/Styles/Title";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd"; import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import { initialData, initialData2 } from "../../../../../../components/Drags/data"; import { initialData, initialData2 } from "../../../../../../components/Drags/data";
import Chapter from "../../../../../../components/Drags/Chapter"; import Chapter from "../../../../../../components/Drags/Chapter";

View file

@ -1,12 +1,13 @@
import { default as React, useEffect, useRef } from "react"; import { default as React, useEffect, useRef } from "react";
import Layout from "../../../../../../../components/rename/UI/Layout"; import Layout from "../../../../../../../components//UI/Layout";
import { Title } from "../../../../../../../components/rename/UI/Elements/Styles/Title"; import { Title } from "../../../../../../../components//UI/Elements/Styles/Title";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { getElement } from "../../../../../../../services/courses/elements"; import { getElement } from "../../../../../../../services/courses/elements";
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 { getCourseMetadata } from "../../../../../../../services/courses/courses";
// Workaround (Next.js SSR doesn't support tip tap editor) // Workaround (Next.js SSR doesn't support tip tap editor)
const Editor: any = dynamic(() => import("../../../../../../../components/Editor/EditorWrapper") as any, { const Editor: any = dynamic(() => import("../../../../../../../components/Editor/EditorWrapper") as any, {
@ -15,25 +16,40 @@ const Editor: any = dynamic(() => import("../../../../../../../components/Editor
function EditElement() { function EditElement() {
const router = useRouter(); const router = useRouter();
const { elementid } = router.query; const { elementid, courseid } = router.query;
const [element, setElement] = React.useState<any>({}); const [element, setElement] = React.useState<any>({});
const [courseInfo, setCourseInfo] = React.useState({}) as any;
const [isLoading, setIsLoading] = React.useState(true); const [isLoading, setIsLoading] = React.useState(true);
async function fetchElementData() { async function fetchElementData() {
const element = await getElement("element_" + elementid); const element = await getElement("element_" + elementid);
setElement(element); setElement(element);
}
async function fetchCourseInfo() {
const course = await getCourseMetadata("course_" + courseid);
setCourseInfo(course);
}
async function fetchAllData() {
await fetchElementData();
await fetchCourseInfo();
setIsLoading(false); setIsLoading(false);
} }
React.useEffect(() => { React.useEffect(() => {
if (router.isReady) { if (router.isReady) {
fetchElementData(); fetchAllData();
} }
return () => {}; return () => {};
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [router.isReady]); }, [router.isReady]);
return <AuthProvider>{isLoading ? <div>Loading...</div> : <EditorWrapper element={element} content={element.content}></EditorWrapper>}</AuthProvider>; return (
<AuthProvider>
{isLoading ? <div>Loading...</div> : <EditorWrapper course={courseInfo} element={element} content={element.content}></EditorWrapper>}
</AuthProvider>
);
} }
export default EditElement; export default EditElement;

View file

@ -6,7 +6,7 @@ import Text from "@tiptap/extension-text";
import { generateHTML } from "@tiptap/html"; import { generateHTML } from "@tiptap/html";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import React, { useMemo } from "react"; import React, { useMemo } from "react";
import Layout from "../../../../../../../components/rename/UI/Layout"; import Layout from "../../../../../../../components//UI/Layout";
import { getElement } from "../../../../../../../services/courses/elements"; import { getElement } from "../../../../../../../services/courses/elements";
import { getBackendUrl } from "../../../../../../../services/config"; import { getBackendUrl } from "../../../../../../../services/config";

View file

@ -3,7 +3,7 @@ import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import React from "react"; import React from "react";
import styled from "styled-components"; import styled from "styled-components";
import Layout from "../../../../../components/rename/UI/Layout"; import Layout from "../../../../../components//UI/Layout";
import { getAPIUrl, getBackendUrl } from "../../../../../services/config"; import { getAPIUrl, getBackendUrl } from "../../../../../services/config";
import { getCourse, getCourseMetadata } from "../../../../../services/courses/courses"; import { getCourse, getCourseMetadata } from "../../../../../services/courses/courses";

View file

@ -2,9 +2,9 @@ import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import React from "react"; import React from "react";
import styled from "styled-components"; import styled from "styled-components";
import { Header } from "../../../../components/rename/UI/Header"; import { Header } from "../../../../components//UI/Header";
import Layout from "../../../../components/rename/UI/Layout"; import Layout from "../../../../components//UI/Layout";
import { Title } from "../../../../components/rename/UI/Elements/Styles/Title"; import { Title } from "../../../../components//UI/Elements/Styles/Title";
import { getBackendUrl } from "../../../../services/config"; import { getBackendUrl } from "../../../../services/config";
import { deleteCourseFromBackend, getOrgCourses } from "../../../../services/courses/courses"; import { deleteCourseFromBackend, getOrgCourses } from "../../../../services/courses/courses";
import { getOrganizationContextInfo } from "../../../../services/orgs"; import { getOrganizationContextInfo } from "../../../../services/orgs";

View file

@ -1,8 +1,8 @@
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import React from "react"; import React from "react";
import { Header } from "../../../../../components/rename/UI/Header"; import { Header } from "../../../../../components//UI/Header";
import Layout from "../../../../../components/rename/UI/Layout"; import Layout from "../../../../../components//UI/Layout";
import { Title } from "../../../../../components/rename/UI/Elements/Styles/Title"; import { Title } from "../../../../../components//UI/Elements/Styles/Title";
import { createNewCourse } from "../../../../../services/courses/courses"; import { createNewCourse } from "../../../../../services/courses/courses";
import { getOrganizationContextInfo } from "../../../../../services/orgs"; import { getOrganizationContextInfo } from "../../../../../services/orgs";

View file

@ -1,8 +1,8 @@
import React from "react"; import React from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Layout from "../../../components/rename/UI/Layout"; import Layout from "../../../components//UI/Layout";
import { Title } from "../../../components/rename/UI/Elements/Styles/Title"; import { Title } from "../../../components//UI/Elements/Styles/Title";
import { Header } from "../../../components/rename/UI/Header"; import { Header } from "../../../components//UI/Header";
import Link from "next/link"; import Link from "next/link";
const OrgHomePage = () => { const OrgHomePage = () => {

View file

@ -1,7 +1,7 @@
import Link from "next/link"; import Link from "next/link";
import React from "react"; import React from "react";
import Layout from "../../components/rename/UI/Layout"; import Layout from "../../components//UI/Layout";
import { Title } from "../../components/rename/UI/Elements/Styles/Title"; import { Title } from "../../components//UI/Elements/Styles/Title";
import { deleteOrganizationFromBackend, getUserOrganizations } from "../../services/orgs"; import { deleteOrganizationFromBackend, getUserOrganizations } from "../../services/orgs";
const Organizations = () => { const Organizations = () => {

View file

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import Layout from "../../components/rename/UI/Layout"; import Layout from "../../components//UI/Layout";
import { Title } from "../../components/rename/UI/Elements/Styles/Title"; import { Title } from "../../components//UI/Elements/Styles/Title";
import { createNewOrganization } from "../../services/orgs"; import { createNewOrganization } from "../../services/orgs";
const Organizations = () => { const Organizations = () => {

View file

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import { Header } from "../components/rename/UI/Header"; import { Header } from "../components//UI/Header";
import Layout from "../components/rename/UI/Layout"; import Layout from "../components//UI/Layout";
import { Title } from "../components/rename/UI/Elements/Styles/Title"; import { Title } from "../components//UI/Elements/Styles/Title";
import { signup } from "../services/auth/auth"; import { signup } from "../services/auth/auth";
const SignUp = () => { const SignUp = () => {