mirror of
https://github.com/aljazceru/landscape-template.git
synced 2025-12-27 11:14:33 +01:00
feat: adding video to text editor
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
|
||||
import InsertVideoModal from './InsertVideoModal';
|
||||
|
||||
import { ModalsDecorator } from 'src/utils/storybook/decorators';
|
||||
|
||||
export default {
|
||||
title: 'Shared/Inputs/Text Editor/Insert Video Modal',
|
||||
component: InsertVideoModal,
|
||||
|
||||
decorators: [ModalsDecorator]
|
||||
} as ComponentMeta<typeof InsertVideoModal>;
|
||||
|
||||
const Template: ComponentStory<typeof InsertVideoModal> = (args) => <InsertVideoModal {...args} />;
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import React, { FormEvent, useState } from 'react'
|
||||
import { ModalCard, modalCardVariants } from 'src/Components/Modals/ModalsContainer/ModalsContainer'
|
||||
import { motion } from 'framer-motion'
|
||||
import { IoClose } from 'react-icons/io5'
|
||||
import Button from 'src/Components/Button/Button'
|
||||
import { useAppDispatch } from 'src/utils/hooks'
|
||||
import { PayloadAction } from '@reduxjs/toolkit'
|
||||
|
||||
interface Props extends ModalCard {
|
||||
callbackAction: PayloadAction<{ src: string, alt?: string }>
|
||||
}
|
||||
|
||||
export default function InsertVideoModal({ onClose, direction, callbackAction, ...props }: Props) {
|
||||
|
||||
const [idInput, setIdInput] = useState("")
|
||||
const [altInput, setAltInput] = useState("")
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleSubmit = (e: FormEvent) => {
|
||||
e.preventDefault()
|
||||
if (idInput.length > 10) {
|
||||
// onInsert({ src: idInput, alt: altInput })
|
||||
const action = Object.assign({}, callbackAction);
|
||||
action.payload = { src: idInput, alt: altInput }
|
||||
dispatch(action)
|
||||
onClose?.();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
custom={direction}
|
||||
variants={modalCardVariants}
|
||||
initial='initial'
|
||||
animate="animate"
|
||||
exit='exit'
|
||||
className="modal-card max-w-[660px] p-24 rounded-xl relative"
|
||||
>
|
||||
<IoClose className='absolute text-body2 top-24 right-24 hover:cursor-pointer' onClick={onClose} />
|
||||
<h2 className='text-h5 font-bold'>Insert Youtube Video</h2>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="grid gap-16 mt-32">
|
||||
<div className='md:col-span-2'>
|
||||
<p className="text-body5">
|
||||
Video Id
|
||||
</p>
|
||||
<div className="input-wrapper mt-8 relative">
|
||||
<input
|
||||
type='text'
|
||||
className="input-text"
|
||||
value={idInput}
|
||||
onChange={e => setIdInput(e.target.value)}
|
||||
placeholder='Zi7sRMcJT-o'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-16 justify-end mt-32">
|
||||
<Button onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type='submit' color='primary' >
|
||||
Insert
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
@@ -75,10 +75,7 @@ WithPreview.args = {
|
||||
|
||||
#### heading4
|
||||
|
||||
###### heading6
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
###### heading6
|
||||
|
||||
some text with **bold**, _italic,_ underline, [www.link.com](//www.link.com)
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
TableExtension,
|
||||
TrailingNodeExtension,
|
||||
UnderlineExtension,
|
||||
IframeExtension,
|
||||
} from 'remirror/extensions';
|
||||
import { ExtensionPriority, InvalidContentHandler } from 'remirror';
|
||||
import { EditorComponent, Remirror, useHelpers, useRemirror } from '@remirror/react';
|
||||
@@ -77,6 +78,7 @@ export default function TextEditor({ placeholder, initialContent }: Props) {
|
||||
// new TableExtension(),
|
||||
new MarkdownExtension({ copyAsMarkdown: false }),
|
||||
new NodeFormattingExtension(),
|
||||
new IframeExtension(),
|
||||
/**
|
||||
* `HardBreakExtension` allows us to create a newline inside paragraphs.
|
||||
* e.g. in a list item
|
||||
|
||||
@@ -32,7 +32,7 @@ export default function ImageToolButton({ classes }: Props) {
|
||||
})
|
||||
}, [commands])
|
||||
|
||||
useReduxEffect(onInsertImage, 'INSERT_IMAGE_IN_EDITOR')
|
||||
useReduxEffect(onInsertImage, INSERT_IMAGE_ACTION.type)
|
||||
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function ImageToolButton({ classes }: Props) {
|
||||
Modal: "InsertImageModal",
|
||||
props: {
|
||||
callbackAction: {
|
||||
type: 'INSERT_IMAGE_IN_EDITOR',
|
||||
type: INSERT_IMAGE_ACTION.type,
|
||||
payload: {
|
||||
src: "",
|
||||
alt: ""
|
||||
|
||||
@@ -2,6 +2,7 @@ import ImageToolButton from './ImageToolBtn';
|
||||
import HeadingsToolButton from './HeadingsToolBtn';
|
||||
import DefaultToolButton from './DefaultToolBtn';
|
||||
import { Command, isCommand } from './helpers';
|
||||
import VideoToolButton from './VideoToolBtn';
|
||||
|
||||
interface Props {
|
||||
cmd: Command
|
||||
@@ -36,6 +37,9 @@ export default function ToolButton({ cmd,
|
||||
return <HeadingsToolButton classes={classes} />
|
||||
|
||||
|
||||
if (cmd === 'youtube')
|
||||
return <VideoToolButton classes={classes} />
|
||||
|
||||
if (cmd === 'img')
|
||||
return <ImageToolButton classes={classes} />
|
||||
|
||||
|
||||
71
src/Components/Inputs/TextEditor/ToolButton/VideoToolBtn.tsx
Normal file
71
src/Components/Inputs/TextEditor/ToolButton/VideoToolBtn.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import { useActive, useCommands } from '@remirror/react';
|
||||
import { useAppDispatch } from 'src/utils/hooks';
|
||||
import { openModal } from 'src/redux/features/modals.slice';
|
||||
import { useReduxEffect } from 'src/utils/hooks/useReduxEffect';
|
||||
import { useCallback } from 'react';
|
||||
import { createAction } from '@reduxjs/toolkit';
|
||||
import { cmdToBtn } from './helpers';
|
||||
|
||||
interface Props {
|
||||
classes: {
|
||||
button: string,
|
||||
icon: string,
|
||||
active: string,
|
||||
enabled: string
|
||||
disabled: string
|
||||
}
|
||||
}
|
||||
|
||||
const INSERT_VIDEO_ACTION = createAction<{ src: string }>('VIDEO_INSERTED_IN_EDITOR')({ src: '' })
|
||||
|
||||
export default function VideoToolButton({ classes }: Props) {
|
||||
|
||||
const commands = useCommands();
|
||||
const active = useActive();
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
|
||||
const onInsertVideo = useCallback(({ payload: { src } }: typeof INSERT_VIDEO_ACTION) => {
|
||||
commands.addYouTubeVideo({
|
||||
video: src,
|
||||
|
||||
})
|
||||
}, [commands])
|
||||
|
||||
useReduxEffect(onInsertVideo, INSERT_VIDEO_ACTION.type)
|
||||
|
||||
|
||||
const { activeCmd, cmd, tip, Icon } = cmdToBtn['youtube'];
|
||||
const onClick = () => {
|
||||
dispatch(openModal({
|
||||
Modal: "InsertVideoModal",
|
||||
props: {
|
||||
callbackAction: {
|
||||
type: INSERT_VIDEO_ACTION.type,
|
||||
payload: {
|
||||
src: "",
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type='button'
|
||||
data-tip={tip}
|
||||
className={`
|
||||
${classes.button}
|
||||
${(activeCmd && active[activeCmd]()) && classes.active}
|
||||
${commands[cmd].enabled({ video: "" }) ? classes.enabled : classes.disabled}
|
||||
`}
|
||||
onClick={onClick}
|
||||
>
|
||||
<Icon className={classes.icon} />
|
||||
</button>
|
||||
)
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FiBold, FiItalic, FiType, FiUnderline, FiAlignCenter, FiAlignLeft, FiAlignRight, FiCode } from 'react-icons/fi'
|
||||
import { FaListOl, FaListUl, FaUndo, FaRedo, FaImage } from 'react-icons/fa'
|
||||
import { FaListOl, FaListUl, FaUndo, FaRedo, FaImage, FaYoutube } from 'react-icons/fa'
|
||||
import { BiCodeCurly } from 'react-icons/bi';
|
||||
|
||||
|
||||
@@ -92,6 +92,13 @@ export const cmdToBtn = {
|
||||
Icon: FaImage,
|
||||
},
|
||||
|
||||
youtube: {
|
||||
cmd: 'addYouTubeVideo',
|
||||
activeCmd: 'iframe',
|
||||
tip: "Insert Video",
|
||||
Icon: FaYoutube,
|
||||
},
|
||||
|
||||
|
||||
} as const
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ export default function Toolbar() {
|
||||
<ToolButton cmd='bulletList' />
|
||||
<ToolButton cmd='orderedList' />
|
||||
<ToolButton cmd='img' />
|
||||
<ToolButton cmd='youtube' />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Login_ScanningWalletCard, Login_ExternalWalletCard, Login_NativeWalletC
|
||||
import { ProjectDetailsCard } from "src/features/Projects/pages/ProjectPage/ProjectDetailsCard";
|
||||
import VoteCard from "src/features/Projects/pages/ProjectPage/VoteCard/VoteCard";
|
||||
import InsertImageModal from 'src/Components/Inputs/TextEditor/InsertImageModal/InsertImageModal'
|
||||
import InsertVideoModal from 'src/Components/Inputs/TextEditor/InsertVideoModal/InsertVideoModal'
|
||||
import { Claim_FundWithdrawCard, Claim_CopySignatureCard, Claim_GenerateSignatureCard, Claim_SubmittedCard } from "src/features/Projects/pages/ProjectPage/ClaimProject";
|
||||
import { ModalCard } from "src/Components/Modals/ModalsContainer/ModalsContainer";
|
||||
import { ComponentProps } from "react";
|
||||
@@ -28,7 +29,10 @@ export const ALL_MODALS = {
|
||||
Claim_CopySignatureCard,
|
||||
Claim_SubmittedCard,
|
||||
Claim_FundWithdrawCard,
|
||||
InsertImageModal
|
||||
|
||||
// Text Editor Modals
|
||||
InsertImageModal,
|
||||
InsertVideoModal,
|
||||
}
|
||||
|
||||
type ExcludeBaseModalProps<U> = Omit<U, keyof ModalCard>
|
||||
|
||||
@@ -1,36 +1,41 @@
|
||||
|
||||
//
|
||||
//
|
||||
// This file is for overriding various libraries native styles
|
||||
// -----------------------------------------------------------
|
||||
|
||||
|
||||
// Re mirror
|
||||
// ---------------
|
||||
.remirror-editor-wrapper ul {
|
||||
list-style: disc !important;
|
||||
padding: revert;
|
||||
margin: revert;
|
||||
list-style: disc !important;
|
||||
padding: revert;
|
||||
margin: revert;
|
||||
}
|
||||
|
||||
.remirror-editor-wrapper ol {
|
||||
list-style: decimal !important;
|
||||
padding: revert;
|
||||
margin: revert;
|
||||
list-style: decimal !important;
|
||||
padding: revert;
|
||||
margin: revert;
|
||||
}
|
||||
|
||||
.remirror-editor-wrapper iframe,
|
||||
.remirror-theme iframe {
|
||||
width: calc(min(100%, 550px));
|
||||
margin: 0 auto;
|
||||
aspect-ratio: 16/9;
|
||||
}
|
||||
|
||||
// React Modals
|
||||
// ----------------
|
||||
.ReactModal__Overlay {
|
||||
opacity: 0;
|
||||
transition: opacity 900ms ease-in-out;
|
||||
opacity: 0;
|
||||
transition: opacity 900ms ease-in-out;
|
||||
}
|
||||
|
||||
.ReactModal__Overlay--after-open {
|
||||
opacity: 1;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.ReactModal__Overlay--before-close {
|
||||
opacity: 0;
|
||||
transition-timing-function: ease-in;
|
||||
transition-duration: 400ms;
|
||||
opacity: 0;
|
||||
transition-timing-function: ease-in;
|
||||
transition-duration: 400ms;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user