mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: init and add video extension to editor/canva
This commit is contained in:
parent
8d0efdb93e
commit
9f916449c5
9 changed files with 163 additions and 10 deletions
|
|
@ -7,6 +7,7 @@ import WarningCallout from "../Editor/Extensions/Callout/Warning/WarningCallout"
|
|||
import ImageBlock from "../Editor/Extensions/Image/ImageBlock";
|
||||
import Youtube from "@tiptap/extension-youtube";
|
||||
import { EditorContentWrapper } from "../Editor/Editor";
|
||||
import VideoBlock from "../Editor/Extensions/Video/VideoBlock";
|
||||
|
||||
interface Editor {
|
||||
content: string;
|
||||
|
|
@ -31,6 +32,10 @@ function Canva(props: Editor) {
|
|||
editable: isEditable,
|
||||
element: props.element,
|
||||
}),
|
||||
VideoBlock.configure({
|
||||
editable: true,
|
||||
element: props.element,
|
||||
}),
|
||||
Youtube.configure({
|
||||
controls: true,
|
||||
modestBranding: true,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import InfoCallout from "./Extensions/Callout/Info/InfoCallout";
|
|||
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";
|
||||
|
||||
interface Editor {
|
||||
content: string;
|
||||
|
|
@ -48,6 +49,10 @@ function Editor(props: Editor) {
|
|||
editable: true,
|
||||
element: props.element,
|
||||
}),
|
||||
VideoBlock.configure({
|
||||
editable: true,
|
||||
element: props.element,
|
||||
}),
|
||||
Youtube.configure({
|
||||
controls: true,
|
||||
modestBranding: true,
|
||||
|
|
|
|||
|
|
@ -4,23 +4,28 @@ import { ReactNodeViewRenderer } from "@tiptap/react";
|
|||
import VideoBlockComponent from "./VideoBlockComponent";
|
||||
|
||||
export default Node.create({
|
||||
name: "calloutWarning",
|
||||
name: "blockVideo",
|
||||
group: "block",
|
||||
draggable: true,
|
||||
content: "inline*",
|
||||
atom: true,
|
||||
|
||||
// TODO : multi line support
|
||||
addAttributes() {
|
||||
return {
|
||||
fileObject: {
|
||||
default: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
tag: "callout-warning",
|
||||
tag: "block-video",
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ["callout-info", mergeAttributes(HTMLAttributes), 0];
|
||||
return ["block-video", mergeAttributes(HTMLAttributes), 0];
|
||||
},
|
||||
|
||||
addNodeView() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
import { NodeViewWrapper } from "@tiptap/react";
|
||||
import { AlertTriangle, Image, Video } from "lucide-react";
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { getBackendUrl } from "../../../../services/config";
|
||||
import { uploadNewVideoFile } from "../../../../services/files/video";
|
||||
|
||||
function VideoBlockComponents(props: any) {
|
||||
const [video, setVideo] = React.useState(null);
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
const [fileObject, setfileObject] = React.useState(props.node.attrs.fileObject);
|
||||
|
||||
const handleVideoChange = (event: React.ChangeEvent<any>) => {
|
||||
setVideo(event.target.files[0]);
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: any) => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
let object = await uploadNewVideoFile(video, props.extension.options.element.element_id);
|
||||
setIsLoading(false);
|
||||
setfileObject(object);
|
||||
props.updateAttributes({
|
||||
fileObject: object,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<NodeViewWrapper className="block-video">
|
||||
{!fileObject && (
|
||||
<BlockVideoWrapper contentEditable={props.extension.options.editable}>
|
||||
<div>
|
||||
<Video color="#e1e0e0" size={50} />
|
||||
<br />
|
||||
</div>
|
||||
<input onChange={handleVideoChange} type="file" name="" id="" />
|
||||
<br />
|
||||
<button onClick={handleSubmit}>Submit</button>
|
||||
</BlockVideoWrapper>
|
||||
)}
|
||||
{fileObject && (
|
||||
<BlockVideo>
|
||||
<video
|
||||
controls
|
||||
src={`${getBackendUrl()}content/uploads/files/videos/${props.extension.options.element.element_id}/${fileObject.file_id}.${
|
||||
fileObject.file_format
|
||||
}`}
|
||||
></video>
|
||||
</BlockVideo>
|
||||
)}
|
||||
{isLoading && (
|
||||
<div>
|
||||
<AlertTriangle color="#e1e0e0" size={50} />
|
||||
</div>
|
||||
)}
|
||||
</NodeViewWrapper>
|
||||
);
|
||||
}
|
||||
const BlockVideoWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #f9f9f9;
|
||||
border-radius: 3px;
|
||||
padding: 30px;
|
||||
min-height: 74px;
|
||||
border: ${(props) => (props.contentEditable ? "2px dashed #713f1117" : "none")};
|
||||
|
||||
// center
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
`;
|
||||
|
||||
const BlockVideo = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
video {
|
||||
width: 100%;
|
||||
border-radius: 6px;
|
||||
height: 300px;
|
||||
// cover
|
||||
object-fit: cover;
|
||||
}
|
||||
`;
|
||||
export default VideoBlockComponents;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import styled from "styled-components";
|
||||
import { FontBoldIcon, FontItalicIcon, StrikethroughIcon, ArrowLeftIcon, ArrowRightIcon, OpacityIcon } from "@radix-ui/react-icons";
|
||||
import { AlertCircle, AlertTriangle, ImagePlus, Info, Youtube } from "lucide-react";
|
||||
import { AlertCircle, AlertTriangle, ImagePlus, Info, Video, Youtube } from "lucide-react";
|
||||
|
||||
export const ToolbarButtons = ({ editor }: any) => {
|
||||
if (!editor) {
|
||||
|
|
@ -74,6 +74,19 @@ export const ToolbarButtons = ({ editor }: any) => {
|
|||
>
|
||||
<ImagePlus size={15} />
|
||||
</ToolBtn>
|
||||
<ToolBtn
|
||||
onClick={() =>
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.insertContent({
|
||||
type: "blockVideo",
|
||||
})
|
||||
.run()
|
||||
}
|
||||
>
|
||||
<Video size={15} />
|
||||
</ToolBtn>
|
||||
<ToolBtn onClick={() => addYoutubeVideo()}>
|
||||
<Youtube size={15} />
|
||||
</ToolBtn>
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ const AuthProvider = (props: any) => {
|
|||
}
|
||||
} else {
|
||||
setAuth({ access_token, isAuthenticated: false, userInfo, isLoading });
|
||||
router.push("/login");
|
||||
//router.push("/login");
|
||||
}
|
||||
} catch (error) {
|
||||
router.push("/");
|
||||
|
|
|
|||
38
front/services/files/video.ts
Normal file
38
front/services/files/video.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import { getAPIUrl } from "../config";
|
||||
|
||||
export async function uploadNewVideoFile(file: any, element_id: string) {
|
||||
const HeadersConfig = new Headers();
|
||||
|
||||
// Send file thumbnail as form data
|
||||
const formData = new FormData();
|
||||
formData.append("file_object", file);
|
||||
formData.append("element_id", element_id);
|
||||
|
||||
const requestOptions: any = {
|
||||
method: "POST",
|
||||
headers: HeadersConfig,
|
||||
redirect: "follow",
|
||||
credentials: "include",
|
||||
body: formData,
|
||||
};
|
||||
|
||||
return fetch(`${getAPIUrl()}files/video`, requestOptions)
|
||||
.then((result) => result.json())
|
||||
.catch((error) => console.log("error", error));
|
||||
}
|
||||
|
||||
export async function getVideoFile(file_id: string) {
|
||||
const HeadersConfig = new Headers({ "Content-Type": "application/json" });
|
||||
|
||||
const requestOptions: any = {
|
||||
method: "GET",
|
||||
headers: HeadersConfig,
|
||||
redirect: "follow",
|
||||
credentials: "include",
|
||||
};
|
||||
|
||||
|
||||
return fetch(`${getAPIUrl()}files/video?file_id=${file_id}`, requestOptions)
|
||||
.then((result) => result.json())
|
||||
.catch((error) => console.log("error", error));
|
||||
}
|
||||
|
|
@ -55,7 +55,8 @@ async def create_video_file(video_file: UploadFile, element_id: str):
|
|||
)
|
||||
|
||||
# create folder for element
|
||||
os.mkdir(f"content/uploads/files/videos/{element_id}")
|
||||
if not os.path.exists(f"content/uploads/files/videos/{element_id}"):
|
||||
os.mkdir(f"content/uploads/files/videos/{element_id}")
|
||||
|
||||
# upload file to server
|
||||
with open(f"content/uploads/files/videos/{element_id}/{file_id}.{file_format}", 'wb') as f:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue