feat: add elements

This commit is contained in:
swve 2022-11-11 20:36:09 +01:00
parent 938cfcb4b3
commit 6f2cc5bdc6
23 changed files with 241 additions and 104 deletions

View file

@ -1,9 +1,51 @@
import React from "react"; import React from "react";
import styled from "styled-components"; import styled from "styled-components";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd"; import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import Element, { ElementWrapper } from "./element"; import Element, { ElementWrapper } from "./Element";
import Link from "next/link";
import { motion } from "framer-motion"; function Chapter(props: any) {
return (
<Draggable key={props.info.list.chapter.id} draggableId={props.info.list.chapter.id} index={props.index}>
{(provided, snapshot) => (
<ChapterWrapper
{...provided.dragHandleProps}
{...provided.draggableProps}
ref={provided.innerRef}
// isDragging={snapshot.isDragging}
key={props.info.list.chapter.id}
>
<h3>
{props.info.list.chapter.name}{" "}
<button
onClick={() => {
props.openNewElementModal(props.info.list.chapter.id);
}}
>
Create Element
</button>
<button
onClick={() => {
props.deleteChapter(props.info.list.chapter.id);
}}
>
X
</button>
</h3>
<Droppable key={props.info.list.chapter.id} droppableId={props.info.list.chapter.id} type="element">
{(provided) => (
<ElementsList {...provided.droppableProps} ref={provided.innerRef}>
{props.info.list.elements.map((element: any, index: any) => (
<Element key={element.id} element={element} index={index}></Element>
))}
{provided.placeholder}
</ElementsList>
)}
</Droppable>
</ChapterWrapper>
)}
</Draggable>
);
}
const ChapterWrapper = styled.div` const ChapterWrapper = styled.div`
margin-bottom: 5px; margin-bottom: 5px;
@ -17,45 +59,6 @@ const ChapterWrapper = styled.div`
transition: all 0.2s ease; transition: all 0.2s ease;
`; `;
function Chapter(props: any) {
return (
<Draggable key={props.info.list.chapter.id} draggableId={props.info.list.chapter.id} index={props.index}>
{(provided, snapshot) => (
<ChapterWrapper
{...provided.dragHandleProps}
{...provided.draggableProps}
ref={provided.innerRef}
isDragging={snapshot.isDragging}
key={props.info.list.chapter.id}
>
<h3>
{props.info.list.chapter.name}{" "}
<button
onClick={() => {
props.deleteChapter(props.info.list.chapter.id);
}}
>
X
</button>
</h3>
<Droppable key={props.info.list.chapter.id} droppableId={props.info.list.chapter.id} type="element">
{(provided) => (
<ElementsList {...provided.droppableProps} ref={provided.innerRef}>
{props.info.list.elements.map((element: any, index: any) => (
<Element key={element.id} element={element} index={index}></Element>
))}
{provided.placeholder}
</ElementsList>
)}
</Droppable>
</ChapterWrapper>
)}
</Draggable>
);
}
const ElementsList = styled.div` const ElementsList = styled.div`
padding: 10px; padding: 10px;
`; `;

View file

@ -7,7 +7,7 @@ function Element(props: any) {
<Draggable key={props.element.id} draggableId={props.element.id} index={props.index}> <Draggable key={props.element.id} draggableId={props.element.id} index={props.index}>
{(provided) => ( {(provided) => (
<ElementWrapper key={props.element.id} {...provided.draggableProps} {...provided.dragHandleProps} ref={provided.innerRef}> <ElementWrapper key={props.element.id} {...provided.draggableProps} {...provided.dragHandleProps} ref={provided.innerRef}>
{props.element.content} <p>{props.element.name} </p>
</ElementWrapper> </ElementWrapper>
)} )}
</Draggable> </Draggable>

View file

@ -0,0 +1,39 @@
import React, { useState } from "react";
import Modal from "../Modal";
function NewElementModal({ closeModal, submitElement, chapterId }: any) {
const [elementName, setElementName] = useState("");
const [elementDescription, setElementDescription] = useState("");
const handleElementNameChange = (e: any) => {
setElementName(e.target.value);
};
const handleElementDescriptionChange = (e: any) => {
setElementDescription(e.target.value);
};
const handleSubmit = async (e: any) => {
e.preventDefault();
console.log({ elementName, elementDescription, chapterId });
submitElement({
name: elementName,
chapterId: chapterId,
type: "dynamic",
});
};
return (
<Modal>
<h1>
Add New Element <button onClick={closeModal}>X</button>
</h1>
<input type="text" onChange={handleElementNameChange} placeholder="Element Name" /> <br />
<input type="text" onChange={handleElementDescriptionChange} placeholder="Element Description" />
<br />
<button onClick={handleSubmit}>Add Element</button>
</Modal>
);
}
export default NewElementModal;

View file

@ -1,5 +1,4 @@
import React from "react"; import React from "react";
import { Menu } from "./elements/menu";
import Link from 'next/link' import Link from 'next/link'
import styled from "styled-components"; import styled from "styled-components";

View file

@ -1,10 +1,9 @@
import React from "react"; import React from "react";
import Head from "next/head"; import Head from "next/head";
import { Header } from "./header";
import styled from "styled-components"; import styled from "styled-components";
import AuthProvider from "../security/AuthProvider"; import AuthProvider from "../security/AuthProvider";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { Menu } from "./elements/menu"; import { Menu } from "./elements/Menu";
const Layout = (props: any) => { const Layout = (props: any) => {
const variants = { const variants = {

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/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/ui/header"; import { Header } from "../components/ui/Header";
import Layout from "../components/ui/layout"; import Layout from "../components/ui/Layout";
import { Title } from "../components/ui/styles/title"; import { Title } from "../components/ui/styles/Title";
import { loginAndGetToken } from "../services/auth/auth"; import { loginAndGetToken } from "../services/auth/auth";
const Login = () => { const Login = () => {

View file

@ -1,7 +1,7 @@
import { default as React, useEffect, useRef } from "react"; import { default as React, useEffect, useRef } from "react";
import Layout from "../../../../../../components/ui/layout"; import Layout from "../../../../../../components/ui/Layout";
import { Title } from "../../../../../../components/ui/styles/title"; import { Title } from "../../../../../../components/ui/styles/Title";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { AuthContext } from "../../../../../../components/security/AuthProvider"; import { AuthContext } from "../../../../../../components/security/AuthProvider";

View file

@ -1,27 +1,38 @@
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/ui/header"; import { Header } from "../../../../../../components/ui/Header";
import Layout from "../../../../../../components/ui/layout"; import Layout from "../../../../../../components/ui/Layout";
import { Title } from "../../../../../../components/ui/styles/title"; import { Title } from "../../../../../../components/ui/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";
import { createChapter, deleteChapter, getCourseChaptersMetadata } from "../../../../../../services/chapters"; import { createChapter, deleteChapter, getCourseChaptersMetadata } from "../../../../../../services/courses/chapters";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import NewChapterModal from "../../../../../../components/modals/chapters/new"; import NewChapterModal from "../../../../../../components/modals/CourseEdit/NewChapter";
import NewElementModal from "../../../../../../components/modals/CourseEdit/NewElement";
import { createElement } from "../../../../../../services/courses/elements";
function CourseEdit() { function CourseEdit() {
const router = useRouter(); const router = useRouter();
// Initial Course State
const [data, setData] = useState(initialData2) as any; const [data, setData] = useState(initialData2) as any;
// New Chapter Modal State
const [newChapterModal, setNewChapterModal] = useState(false) as any; const [newChapterModal, setNewChapterModal] = useState(false) as any;
// New Element Modal State
const [newElementModal, setNewElementModal] = useState(false) as any;
const [newElementModalData, setNewElementModalData] = useState("") as any;
// Check window availability
const [winReady, setwinReady] = useState(false); const [winReady, setwinReady] = useState(false);
const { courseid } = router.query; const { courseid } = router.query;
async function getCourseChapters() { async function getCourseChapters() {
const courseChapters = await getCourseChaptersMetadata(courseid); const courseChapters = await getCourseChaptersMetadata(courseid);
setData(courseChapters); setData(courseChapters);
console.log( "courseChapters" , courseChapters); console.log("courseChapters", courseChapters);
} }
useEffect(() => { useEffect(() => {
@ -59,20 +70,44 @@ function CourseEdit() {
setNewChapterModal(false); setNewChapterModal(false);
}; };
// Submit new element
const submitElement = async (element: any) => {
console.log("submitElement", element);
await createElement(element, element.chapterId);
getCourseChapters();
setNewElementModal(false);
};
const deleteChapterUI = async (chapterId: any) => { const deleteChapterUI = async (chapterId: any) => {
console.log("deleteChapter", chapterId); console.log("deleteChapter", chapterId);
await deleteChapter(chapterId); await deleteChapter(chapterId);
getCourseChapters(); getCourseChapters();
}; };
const openNewElementModal = async (chapterId: any) => {
console.log("openNewElementModal", chapterId);
setNewElementModal(true);
setNewElementModalData(chapterId);
};
/*
Modals
*/
// Close new chapter modal // Close new chapter modal
const closeModal = () => { const closeNewChapterModal = () => {
setNewChapterModal(false); setNewChapterModal(false);
}; };
const closeNewElementModal = () => {
setNewElementModal(false);
};
/*
Drag and drop functions
*/
const onDragEnd = (result: any) => { const onDragEnd = (result: any) => {
const { destination, source, draggableId, type } = result; const { destination, source, draggableId, type } = result;
console.log(result); console.log(result);
@ -175,21 +210,34 @@ function CourseEdit() {
<Layout> <Layout>
<Header></Header> <Header></Header>
<Title> <Title>
Edit Course Chapters <button onClick={()=> {setNewChapterModal(true)}}>+</button> Edit Course Chapters{" "}
<button
onClick={() => {
setNewChapterModal(true);
}}
>
+
</button>
</Title> </Title>
{newChapterModal && <NewChapterModal closeModal={closeModal} submitChapter={submitChapter}></NewChapterModal>} {newChapterModal && <NewChapterModal closeModal={closeNewChapterModal} submitChapter={submitChapter}></NewChapterModal>}
{newElementModal && <NewElementModal closeModal={closeNewElementModal} submitElement={submitElement} chapterId={newElementModalData}></NewElementModal>}
<br /> <br />
{winReady && ( {winReady && (
<ChapterlistWrapper> <ChapterlistWrapper>
<DragDropContext onDragEnd={onDragEnd}> <DragDropContext onDragEnd={onDragEnd}>
<Droppable key="chapters" droppableId="chapters" type="chapter"> <Droppable key="chapters" droppableId="chapters" type="chapter">
{(provided) => ( {(provided) => (
<div key={"chapters"} {...provided.droppableProps} ref={provided.innerRef}> <>
{getChapters().map((info: any, index: any) => ( <div key={"chapters"} {...provided.droppableProps} ref={provided.innerRef}>
<Chapter deleteChapter={deleteChapterUI} key={index} info={info} index={index}></Chapter> {getChapters().map((info: any, index: any) => (
))} <>
{provided.placeholder} <Chapter openNewElementModal={openNewElementModal} deleteChapter={deleteChapterUI} key={index} info={info} index={index}></Chapter>
</div> </>
))}
{provided.placeholder}
</div>
</>
)} )}
</Droppable> </Droppable>
</DragDropContext> </DragDropContext>

View file

@ -1,9 +1,9 @@
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/ui/layout"; import Layout from "../../../../../components/ui/Layout";
import { getAPIUrl, getBackendUrl } from "../../../../../services/config"; import { getAPIUrl, getBackendUrl } from "../../../../../services/config";
import { getCourse } from "../../../../../services/courses"; import { getCourse } from "../../../../../services/courses/courses";
import { getOrganizationContextInfo } from "../../../../../services/orgs"; import { getOrganizationContextInfo } from "../../../../../services/orgs";
const CourseIdPage = () => { const CourseIdPage = () => {

View file

@ -2,11 +2,11 @@ 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/ui/header"; import { Header } from "../../../../components/ui/Header";
import Layout from "../../../../components/ui/layout"; import Layout from "../../../../components/ui/Layout";
import { Title } from "../../../../components/ui/styles/title"; import { Title } from "../../../../components/ui/styles/Title";
import { getBackendUrl } from "../../../../services/config"; import { getBackendUrl } from "../../../../services/config";
import { deleteCourseFromBackend, getOrgCourses } from "../../../../services/courses"; import { deleteCourseFromBackend, getOrgCourses } from "../../../../services/courses/courses";
import { getOrganizationContextInfo } from "../../../../services/orgs"; import { getOrganizationContextInfo } from "../../../../services/orgs";
const CoursesIndexPage = () => { const CoursesIndexPage = () => {

View file

@ -1,9 +1,9 @@
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import React from "react"; import React from "react";
import { Header } from "../../../../../components/ui/header"; import { Header } from "../../../../../components/ui/Header";
import Layout from "../../../../../components/ui/layout"; import Layout from "../../../../../components/ui/Layout";
import { Title } from "../../../../../components/ui/styles/title"; import { Title } from "../../../../../components/ui/styles/Title";
import { createNewCourse } from "../../../../../services/courses"; import { createNewCourse } from "../../../../../services/courses/courses";
import { getOrganizationContextInfo } from "../../../../../services/orgs"; import { getOrganizationContextInfo } from "../../../../../services/orgs";
const NewCoursePage = () => { const NewCoursePage = () => {

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/ui/layout"; import Layout from "../../../components/ui/Layout";
import { Title } from "../../../components/ui/styles/title"; import { Title } from "../../../components/ui/styles/Title";
import { Header } from "../../../components/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,8 +1,8 @@
import Link from "next/link"; import Link from "next/link";
import React from "react"; import React from "react";
import AuthenticatedOnly from "../../components/security/AuthenticatedOnly"; import AuthenticatedOnly from "../../components/security/AuthenticatedOnly";
import Layout from "../../components/ui/layout"; import Layout from "../../components/ui/Layout";
import { Title } from "../../components/ui/styles/title"; import { Title } from "../../components/ui/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/ui/layout"; import Layout from "../../components/ui/Layout";
import { Title } from "../../components/ui/styles/title"; import { Title } from "../../components/ui/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/ui/header"; import { Header } from "../components/ui/Header";
import Layout from "../components/ui/layout"; import Layout from "../components/ui/Layout";
import { Title } from "../components/ui/styles/title"; import { Title } from "../components/ui/styles/Title";
import { signup } from "../services/auth/auth"; import { signup } from "../services/auth/auth";
const SignUp = () => { const SignUp = () => {

View file

@ -1,5 +1,5 @@
import { initialData } from "../components/drags/data"; import { initialData } from "../../components/drags/data";
import { getAPIUrl } from "./config"; import { getAPIUrl } from "../config";
export async function getCourseChaptersMetadata(course_id: any) { export async function getCourseChaptersMetadata(course_id: any) {
const HeadersConfig = new Headers({ "Content-Type": "application/json" }); const HeadersConfig = new Headers({ "Content-Type": "application/json" });

View file

@ -1,4 +1,4 @@
import { getAPIUrl } from "./config"; import { getAPIUrl } from "../config";
export async function getOrgCourses(org_id: number) { export async function getOrgCourses(org_id: number) {
const HeadersConfig = new Headers({ "Content-Type": "application/json" }); const HeadersConfig = new Headers({ "Content-Type": "application/json" });

View file

@ -0,0 +1,27 @@
import { getAPIUrl } from "../config";
export async function createElement(data: any, chapter_id: any) {
data.content = {}
console.log("data", data, chapter_id);
// remove chapter_id from data
delete data.chapterId;
const HeadersConfig = new Headers({ "Content-Type": "application/json" });
const requestOptions: any = {
method: "POST",
headers: HeadersConfig,
redirect: "follow",
credentials: "include",
body: JSON.stringify(data),
};
const result: any = await fetch(`${getAPIUrl()}elements/?coursechapter_id=${chapter_id}`, requestOptions)
.then((result) => result.json())
.catch((error) => console.log("error", error));
console.log("result", result);
return result;
}

View file

@ -4,20 +4,18 @@ from typing import List
from uuid import uuid4 from uuid import uuid4
from pydantic import BaseModel from pydantic import BaseModel
from src.services.courses.courses import Course, CourseInDB from src.services.courses.courses import Course, CourseInDB
from src.services.courses.elements import Element, ElementInDB
from src.services.database import create_config_collection, check_database, create_database, learnhouseDB, learnhouseDB from src.services.database import create_config_collection, check_database, create_database, learnhouseDB, learnhouseDB
from src.services.security import verify_user_rights_with_roles from src.services.security import verify_user_rights_with_roles
from src.services.users import PublicUser from src.services.users import PublicUser
from fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks, UploadFile, File from fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks, UploadFile, File
class CourseElement(BaseModel):
element_id: str
class CourseChapter(BaseModel): class CourseChapter(BaseModel):
name: str name: str
description: str description: str
elements: List[CourseElement] elements: list
class CourseChapterInDB(CourseChapter): class CourseChapterInDB(CourseChapter):
@ -164,13 +162,19 @@ async def get_coursechapters_meta(course_id: str, current_user: PublicUser):
await check_database() await check_database()
coursechapters = learnhouseDB["coursechapters"] coursechapters = learnhouseDB["coursechapters"]
courses = learnhouseDB["courses"] courses = learnhouseDB["courses"]
elements = learnhouseDB["elements"]
coursechapters = coursechapters.find( coursechapters = coursechapters.find(
{"course_id": course_id}).sort("name", 1) {"course_id": course_id}).sort("name", 1)
course = courses.find_one({"course_id": course_id}) course = courses.find_one({"course_id": course_id})
course = Course(**course) # type: ignore course = Course(**course) # type: ignore
# elements
coursechapter_elementIds_global = []
# chapters # chapters
chapters = {} chapters = {}
for coursechapter in coursechapters: for coursechapter in coursechapters:
@ -178,15 +182,27 @@ async def get_coursechapters_meta(course_id: str, current_user: PublicUser):
coursechapter_elementIds = [] coursechapter_elementIds = []
for element in coursechapter.elements: for element in coursechapter.elements:
coursechapter_elementIds.append(element.element_id) coursechapter_elementIds.append(element)
coursechapter_elementIds_global.append(element)
chapters[coursechapter.coursechapter_id] = { chapters[coursechapter.coursechapter_id] = {
"id": coursechapter.coursechapter_id, "name": coursechapter.name, "elementIds": coursechapter_elementIds "id": coursechapter.coursechapter_id, "name": coursechapter.name, "elementIds": coursechapter_elementIds
} }
# elements
elements_list = {}
for element in elements.find({"element_id": {"$in": coursechapter_elementIds_global}}):
element = ElementInDB(**element)
elements_list[element.element_id] = {
"id": element.element_id, "name": element.name, "type": element.type , "content": element.content
}
final = { final = {
"chapters": chapters, "chapters": chapters,
"chapterOrder": course.chapters "chapterOrder": course.chapters,
"elements" : elements_list
} }
return final return final

View file

@ -8,11 +8,11 @@ from datetime import datetime
#### Classes #################################################### #### Classes ####################################################
class Element(BaseModel): class Element(BaseModel):
name: str name: str
element_type: str type: str
content: str content: object
class ElementInDB(Element): class ElementInDB(Element):
@ -29,9 +29,10 @@ class ElementInDB(Element):
#################################################### ####################################################
async def create_element(element_object: Element, coursechapter_id : str , current_user: PublicUser): async def create_element(element_object: Element, coursechapter_id: str, current_user: PublicUser):
await check_database() await check_database()
elements = learnhouseDB["elements"] elements = learnhouseDB["elements"]
coursechapters = learnhouseDB["coursechapters"]
# generate element_id # generate element_id
element_id = str(f"element_{uuid4()}") element_id = str(f"element_{uuid4()}")
@ -47,6 +48,10 @@ async def create_element(element_object: Element, coursechapter_id : str , curre
datetime.now()), coursechapter_id=coursechapter_id, updateDate=str(datetime.now()), element_id=element_id) datetime.now()), coursechapter_id=coursechapter_id, updateDate=str(datetime.now()), element_id=element_id)
elements.insert_one(element.dict()) elements.insert_one(element.dict())
# update chapter
coursechapters.update_one({"coursechapter_id": coursechapter_id}, {
"$addToSet": {"elements": element_id}})
return element return element
@ -88,7 +93,7 @@ async def update_element(element_object: Element, element_id: str, current_user:
datetime_object = datetime.now() datetime_object = datetime.now()
updated_course = ElementInDB( updated_course = ElementInDB(
element_id=element_id,coursechapter_id=element["coursechapter_id"] ,creationDate=creationDate, updateDate=str(datetime_object), **element_object.dict()) element_id=element_id, coursechapter_id=element["coursechapter_id"], creationDate=creationDate, updateDate=str(datetime_object), **element_object.dict())
elements.update_one({"element_id": element_id}, { elements.update_one({"element_id": element_id}, {
"$set": updated_course.dict()}) "$set": updated_course.dict()})

View file

@ -26,6 +26,7 @@ class Elements(BaseModel):
collections: List[str] collections: List[str]
organizations: List[str] organizations: List[str]
coursechapters: List[str] coursechapters: List[str]
elements : List[str]
class Role(BaseModel): class Role(BaseModel):