mirror of
https://github.com/balzack/databag.git
synced 2025-02-14 12:39:17 +00:00
adding previous carousel as starting point
This commit is contained in:
parent
d21bbaf349
commit
b5f3ee2710
162
net/web/src/carousel/Carousel.jsx
Normal file
162
net/web/src/carousel/Carousel.jsx
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
|
import { Skeleton } from 'antd';
|
||||||
|
import { CarouselWrapper } from './Carousel.styled';
|
||||||
|
import { RightOutlined, LeftOutlined, CloseOutlined, PictureOutlined, FireOutlined } from '@ant-design/icons';
|
||||||
|
import ReactResizeDetector from 'react-resize-detector';
|
||||||
|
|
||||||
|
export function Carousel({ ready, error, items, itemRenderer, itemRemove }) {
|
||||||
|
const [slots, setSlots] = useState([]);
|
||||||
|
const [carouselRef, setCarouselRef] = useState(false);
|
||||||
|
const [itemIndex, setItemIndex] = useState(0);
|
||||||
|
const [scrollLeft, setScrollLeft] = useState('hidden');
|
||||||
|
const [scrollRight, setScrollRight] = useState('hidden');
|
||||||
|
const FUDGE = 1;
|
||||||
|
|
||||||
|
let carousel = useRef();
|
||||||
|
let itemWidth = useRef(new Map());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setScroll('smooth');
|
||||||
|
setArrows();
|
||||||
|
}, [itemIndex, items]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setScroll('auto');
|
||||||
|
}, [carouselRef]);
|
||||||
|
|
||||||
|
const updateItemIndex = (val) => {
|
||||||
|
setItemIndex((i) => {
|
||||||
|
if (i + val < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return i + val;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onLeft = () => {
|
||||||
|
if (itemIndex > 0) {
|
||||||
|
updateItemIndex(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onRight = () => {
|
||||||
|
if(itemIndex + 1 < items.length) {
|
||||||
|
updateItemIndex(+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setScroll = (behavior) => {
|
||||||
|
let pos = FUDGE;
|
||||||
|
for (let i = 0; i < itemIndex; i++) {
|
||||||
|
pos += itemWidth.current.get(i) + 32;
|
||||||
|
}
|
||||||
|
if (carousel.current) {
|
||||||
|
carousel.current.scrollTo({ top: 0, left: pos, behavior });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setArrows = () => {
|
||||||
|
if (itemIndex == 0) {
|
||||||
|
setScrollLeft('hidden');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setScrollLeft('unset');
|
||||||
|
}
|
||||||
|
if (itemIndex + 1 >= items.length) {
|
||||||
|
setScrollRight('hidden');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setScrollRight('unset');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const RemoveItem = ({ index }) => {
|
||||||
|
if (itemRemove) {
|
||||||
|
return <div class="delitem" onClick={() => itemRemove(index)}><CloseOutlined /></div>
|
||||||
|
}
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let assets = [];
|
||||||
|
if (ready) {
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
assets.push((
|
||||||
|
<ReactResizeDetector handleWidth={true} handleHeight={false}>
|
||||||
|
{({ width, height }) => {
|
||||||
|
itemWidth.current.set(i, width);
|
||||||
|
return (
|
||||||
|
<div class="item noselect">
|
||||||
|
<div class="asset">{ itemRenderer(items[i], i) }</div>
|
||||||
|
<RemoveItem index={i} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</ReactResizeDetector>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (items.length > 0) {
|
||||||
|
assets.push(<div class="space"> </div>)
|
||||||
|
}
|
||||||
|
if (itemIndex >= items.length) {
|
||||||
|
if (items.length > 0) {
|
||||||
|
setItemIndex(items.length - 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setItemIndex(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setSlots(assets);
|
||||||
|
setScroll();
|
||||||
|
setArrows();
|
||||||
|
}, [ready, items]);
|
||||||
|
|
||||||
|
const onRefSet = (r) => {
|
||||||
|
if (r != null) {
|
||||||
|
carousel.current = r;
|
||||||
|
setCarouselRef(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ready || error) {
|
||||||
|
return (
|
||||||
|
<CarouselWrapper>
|
||||||
|
<div class="carousel">
|
||||||
|
{error && (
|
||||||
|
<div class="status">
|
||||||
|
<FireOutlined style={{ fontSize: 32, color: '#ff8888' }} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!ready && !error && (
|
||||||
|
<div class="status">
|
||||||
|
<PictureOutlined style={{ fontSize: 32 }} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div class="arrows">
|
||||||
|
<div class="arrow" onClick={onRight}><LeftOutlined style={{ visibility: 'hidden' }} /></div>
|
||||||
|
<div class="arrow" onClick={onLeft}><RightOutlined style={{ visibility: 'hidden' }} /></div>
|
||||||
|
</div>
|
||||||
|
</CarouselWrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slots.length != 0) {
|
||||||
|
return (
|
||||||
|
<CarouselWrapper>
|
||||||
|
<div class="carousel" ref={onRefSet}>
|
||||||
|
{slots}
|
||||||
|
</div>
|
||||||
|
<div class="arrows">
|
||||||
|
<div class="arrow" onClick={onRight}><LeftOutlined style={{ visibility: scrollRight }} /></div>
|
||||||
|
<div class="arrow" onClick={onLeft}><RightOutlined style={{ visibility: scrollLeft }} /></div>
|
||||||
|
</div>
|
||||||
|
</CarouselWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <></>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
89
net/web/src/carousel/Carousel.styled.js
Normal file
89
net/web/src/carousel/Carousel.styled.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const CarouselWrapper = styled.div`
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
width: 100%;
|
||||||
|
height: 128px;
|
||||||
|
margin-top: 16px;
|
||||||
|
|
||||||
|
.carousel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding-left: 16px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
/* hide scrollbar for IE, Edge and Firefox */
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
width: 128px;
|
||||||
|
height: 128px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #888888;
|
||||||
|
background-color: #eeeeee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrows {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
height: 50%;
|
||||||
|
background-color: #888888;
|
||||||
|
color: white;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
margin-right: 32px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delitem {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: #888888;
|
||||||
|
color: white;
|
||||||
|
border-bottom-left-radius: 2px;
|
||||||
|
padding-left: 2px;
|
||||||
|
padding-right: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset {
|
||||||
|
height: 128px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space {
|
||||||
|
height: 128px;
|
||||||
|
padding-left: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.object {
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
21
net/web/src/carousel/useCarousel.js
Normal file
21
net/web/src/carousel/useCarousel.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { useContext, useState, useEffect } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
export function useCarousel() {
|
||||||
|
|
||||||
|
const [state, setState] = useState({
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateState = (value) => {
|
||||||
|
setState((s) => ({ ...s, ...value }));
|
||||||
|
}
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
};
|
||||||
|
|
||||||
|
return { state, actions };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -36,7 +36,7 @@ console.log(state);
|
|||||||
<div class="line" />
|
<div class="line" />
|
||||||
</div>
|
</div>
|
||||||
<div class="topic">
|
<div class="topic">
|
||||||
<AddTopic />
|
<AddTopic cardId={cardId} channelId={channelId} />
|
||||||
</div>
|
</div>
|
||||||
</ConversationWrapper>
|
</ConversationWrapper>
|
||||||
);
|
);
|
||||||
|
@ -4,18 +4,56 @@ import { Input, Menu, Dropdown } from 'antd';
|
|||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
import { FontColorsOutlined, FontSizeOutlined, PaperClipOutlined, SendOutlined } from '@ant-design/icons';
|
import { FontColorsOutlined, FontSizeOutlined, PaperClipOutlined, SendOutlined } from '@ant-design/icons';
|
||||||
import { SketchPicker } from "react-color";
|
import { SketchPicker } from "react-color";
|
||||||
|
import { AudioFile } from './audioFile/AudioFile';
|
||||||
|
import { VideoFile } from './videoFile/VideoFile';
|
||||||
|
import { Carousel } from 'carousel/Carousel';
|
||||||
|
|
||||||
export function AddTopic() {
|
export function AddTopic({ cardId, channelId }) {
|
||||||
|
|
||||||
const { state, actions } = useAddTopic();
|
|
||||||
|
|
||||||
|
const { state, actions } = useAddTopic(cardId, channelId);
|
||||||
|
const attachImage = useRef(null);
|
||||||
|
const attachAudio = useRef(null);
|
||||||
|
const attachVideo = useRef(null);
|
||||||
const msg = useRef();
|
const msg = useRef();
|
||||||
|
|
||||||
const keyDown = (e) => {
|
const keyDown = (e) => {
|
||||||
if (e.key === 'Enter' && !e.shiftKey) {
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
msg.current.blur();
|
msg.current.blur();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onSelectImage = (e) => {
|
||||||
|
actions.addImage(e.target.files[0]);
|
||||||
|
attachImage.current.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSelectAudio = (e) => {
|
||||||
|
actions.addAudio(e.target.files[0]);
|
||||||
|
attachAudio.current.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSelectVideo = (e) => {
|
||||||
|
actions.addVideo(e.target.files[0]);
|
||||||
|
attachVideo.current.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderItem = (item, index) => {
|
||||||
|
if (item.image) {
|
||||||
|
return <img style={{ height: '100%', objectFit: 'contain' }} src={item.url} alt="" />
|
||||||
|
}
|
||||||
|
if (item.audio) {
|
||||||
|
return <AudioFile onLabel={(label) => actions.setLabel(index, label)}/>
|
||||||
|
}
|
||||||
|
if (item.video) {
|
||||||
|
return <VideoFile onPosition={(pos) => actions.setPosition(index, pos)} url={item.url} />
|
||||||
|
}
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeItem = (index) => {
|
||||||
|
actions.removeAsset(index);
|
||||||
|
}
|
||||||
|
|
||||||
const picker = (
|
const picker = (
|
||||||
<Menu style={{ backgroundColor: 'unset', boxShadow: 'unset' }}>
|
<Menu style={{ backgroundColor: 'unset', boxShadow: 'unset' }}>
|
||||||
<SketchPicker disableAlpha={true}
|
<SketchPicker disableAlpha={true}
|
||||||
@ -37,20 +75,25 @@ export function AddTopic() {
|
|||||||
const attacher = (
|
const attacher = (
|
||||||
<Menu>
|
<Menu>
|
||||||
<Menu.Item key="0">
|
<Menu.Item key="0">
|
||||||
<div>Add Image</div>
|
<input type='file' name="asset" accept="image/*" ref={attachImage} onChange={e => onSelectImage(e)} style={{display: 'none'}}/>
|
||||||
|
<div onClick={() => attachImage.current.click()}>Attach Image</div>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key="1">
|
<Menu.Item key="1">
|
||||||
<div>Add Video</div>
|
<input type='file' name="asset" accept="audio/*" ref={attachAudio} onChange={e => onSelectAudio(e)} style={{display: 'none'}}/>
|
||||||
|
<div onClick={() => attachAudio.current.click()}>Attach Audio</div>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key="2">
|
<Menu.Item key="2">
|
||||||
<div>Add Audio</div>
|
<input type='file' name="asset" accept="video/*" ref={attachVideo} onChange={e => onSelectVideo(e)} style={{display: 'none'}}/>
|
||||||
|
<div onClick={() => attachVideo.current.click()}>Attach Video</div>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AddTopicWrapper>
|
<AddTopicWrapper>
|
||||||
<div class="carousel"></div>
|
<div class="carousel">
|
||||||
|
<Carousel ready={true} items={state.assets} itemRenderer={renderItem} itemRemove={removeItem} />
|
||||||
|
</div>
|
||||||
<div class="message">
|
<div class="message">
|
||||||
<Input.TextArea ref={msg} placeholder="New Message" spellCheck="true" autoSize={{ minRows: 2, maxRows: 6 }}
|
<Input.TextArea ref={msg} placeholder="New Message" spellCheck="true" autoSize={{ minRows: 2, maxRows: 6 }}
|
||||||
autocapitalize="none" enterkeyhint="send" onKeyDown={(e) => keyDown(e)} />
|
autocapitalize="none" enterkeyhint="send" onKeyDown={(e) => keyDown(e)} />
|
||||||
|
@ -28,8 +28,8 @@ export const AddTopicWrapper = styled.div`
|
|||||||
flex-align: center;
|
flex-align: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 32px;
|
width: 36px;
|
||||||
height: 32px;
|
height: 36px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 1px solid ${Colors.divider};
|
border: 1px solid ${Colors.divider};
|
||||||
background-color: ${Colors.white};
|
background-color: ${Colors.white};
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import ReactResizeDetector from 'react-resize-detector';
|
||||||
|
import { SoundOutlined } from '@ant-design/icons';
|
||||||
|
import { AudioFileWrapper, LabelInput } from './AudioFile.styled';
|
||||||
|
|
||||||
|
export function AudioFile({ onLabel }) {
|
||||||
|
|
||||||
|
const [state, setState] = useState({ height: 0 });
|
||||||
|
|
||||||
|
const updateState = (value) => {
|
||||||
|
setState((s) => ({ ...s, ...value }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AudioFileWrapper>
|
||||||
|
<ReactResizeDetector handleWidth={false} handleHeight={true}>
|
||||||
|
{({ height }) => {
|
||||||
|
if (height != state.height) {
|
||||||
|
updateState({ height });
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div class="square" style={{ width: state.height }}>
|
||||||
|
<SoundOutlined style={{ fontSize: 32, color: '#eeeeee' }} />
|
||||||
|
<LabelInput placeholder="Label" bordered={false} onChange={(e) => onLabel(e.target.value)}/>;
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</ReactResizeDetector>
|
||||||
|
</AudioFileWrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
import { Input } from 'antd';
|
||||||
|
|
||||||
|
export const AudioFileWrapper = styled.div`
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.square {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #444444;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const LabelInput = styled(Input)`
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
bottom: 0;
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
`
|
||||||
|
|
@ -1,16 +1,100 @@
|
|||||||
import { useContext, useState } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
|
import { CardContext } from 'context/CardContext';
|
||||||
|
import { ChannelContext } from 'context/ChannelContext';
|
||||||
|
|
||||||
export function useAddTopic() {
|
export function useAddTopic(cardId, channelId) {
|
||||||
|
|
||||||
const [state, setState] = useState({});
|
const [state, setState] = useState({
|
||||||
|
assets: [],
|
||||||
|
messageText: null,
|
||||||
|
textColor: '#444444',
|
||||||
|
textColorSet: false,
|
||||||
|
textSize: 14,
|
||||||
|
textSizeSet: false,
|
||||||
|
busy: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const card = useContext(CardContext);
|
||||||
|
const channel = useContext(ChannelContext);
|
||||||
|
|
||||||
const updateState = (value) => {
|
const updateState = (value) => {
|
||||||
setState((s) => ({ ...s, ...value }));
|
setState((s) => ({ ...s, ...value }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const addAsset = (value) => {
|
||||||
|
setState((s) => {
|
||||||
|
let assets = [...s.assets, value];
|
||||||
|
return { ...s, assets };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateAsset = (index, value) => {
|
||||||
|
setState((s) => {
|
||||||
|
s.assets[index] = { ...s.assets[index], ...value };
|
||||||
|
return { ...s };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeAsset = (index) => {
|
||||||
|
setState((s) => {
|
||||||
|
s.assets.splice(index, 1);
|
||||||
|
let assets = [...s.assets];
|
||||||
|
return { ...s, assets };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
setTextColor: (textColor) => {
|
addImage: (image) => {
|
||||||
updateState({ textColor });
|
let url = URL.createObjectURL(image);
|
||||||
|
addAsset({ image, url })
|
||||||
|
},
|
||||||
|
addVideo: (video) => {
|
||||||
|
let url = URL.createObjectURL(video);
|
||||||
|
addAsset({ video, url, position: 0 })
|
||||||
|
},
|
||||||
|
addAudio: (audio) => {
|
||||||
|
let url = URL.createObjectURL(audio);
|
||||||
|
addAsset({ audio, url, label: '' })
|
||||||
|
},
|
||||||
|
setLabel: (index, label) => {
|
||||||
|
updateAsset(index, { label });
|
||||||
|
},
|
||||||
|
setPosition: (index, position) => {
|
||||||
|
updateAsset(index, { position });
|
||||||
|
},
|
||||||
|
removeAsset: (idx) => { removeAsset(idx) },
|
||||||
|
setTextColor: (value) => {
|
||||||
|
updateState({ textColorSet: true, textColor: value });
|
||||||
|
},
|
||||||
|
setMessageText: (value) => {
|
||||||
|
updateState({ messageText: value });
|
||||||
|
},
|
||||||
|
setTextSize: (value) => {
|
||||||
|
updateState({ textSizeSet: true, textSize: value });
|
||||||
|
},
|
||||||
|
addTopic: async () => {
|
||||||
|
if (!state.busy) {
|
||||||
|
updateState({ busy: true });
|
||||||
|
try {
|
||||||
|
let message = {
|
||||||
|
text: state.messageText,
|
||||||
|
textColor: state.textColorSet ? state.textColor : null,
|
||||||
|
textSize: state.textSizeSet ? state.textSize : null,
|
||||||
|
};
|
||||||
|
if (cardId) {
|
||||||
|
await card.actions.addChannelTopic(cardId, channelId, message, state.assets);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await channel.actions.addChannelTopic(channelId, message, state.assets);
|
||||||
|
}
|
||||||
|
updateState({ messageText: null, textColor: '#444444', textColorSet: false, textSize: 12, textSizeSet: false, assets: [] });
|
||||||
|
}
|
||||||
|
catch(err) {
|
||||||
|
console.log(err);
|
||||||
|
window.alert("failed to add message");
|
||||||
|
}
|
||||||
|
updateState({ busy: false });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
|
import ReactPlayer from 'react-player'
|
||||||
|
import ReactResizeDetector from 'react-resize-detector';
|
||||||
|
import { RightOutlined, LeftOutlined } from '@ant-design/icons';
|
||||||
|
import { VideoFileWrapper, LabelInput } from './VideoFile.styled';
|
||||||
|
|
||||||
|
export function VideoFile({ url, onPosition }) {
|
||||||
|
|
||||||
|
const [state, setState] = useState({ width: 0, height: 0 });
|
||||||
|
const [playing, setPlaying] = useState(false);
|
||||||
|
const player = useRef(null);
|
||||||
|
const seek = useRef(0);
|
||||||
|
|
||||||
|
const updateState = (value) => {
|
||||||
|
setState((s) => ({ ...s, ...value }));
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSeek = (offset) => {
|
||||||
|
if (player.current) {
|
||||||
|
let len = player.current.getDuration();
|
||||||
|
if (len > 128) {
|
||||||
|
offset *= Math.floor(len / 128);
|
||||||
|
}
|
||||||
|
seek.current += offset;
|
||||||
|
if (seek.current < 0 || seek.current >= len) {
|
||||||
|
seek.current = 0;
|
||||||
|
}
|
||||||
|
onPosition(seek.current);
|
||||||
|
player.current.seekTo(seek.current, 'seconds');
|
||||||
|
setPlaying(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onPause = () => {
|
||||||
|
setPlaying(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VideoFileWrapper>
|
||||||
|
<ReactResizeDetector handleWidth={true} handleHeight={true}>
|
||||||
|
{({ width, height }) => {
|
||||||
|
if (width != state.width || height != state.height) {
|
||||||
|
updateState({ width, height });
|
||||||
|
}
|
||||||
|
return <ReactPlayer ref={player} playing={playing} playbackRate={0} controls={false} height="100%" width="auto" url={url}
|
||||||
|
onStart={() => onPause()} onPlay={() => onPause()} />
|
||||||
|
}}
|
||||||
|
</ReactResizeDetector>
|
||||||
|
<div class="overlay" style={{ width: state.width, height: state.height }}>
|
||||||
|
<div class="arrows">
|
||||||
|
<div class="left-arrow">
|
||||||
|
<div class="icon" onClick={() => onSeek(-1)}>
|
||||||
|
<LeftOutlined style={{ fontSize: 32, color: '#eeeeee' }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right-arrow">
|
||||||
|
<div class="icon" onClick={() => onSeek(1)}>
|
||||||
|
<RightOutlined style={{ fontSize: 32, color: '#eeeeee' }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</VideoFileWrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const VideoFileWrapper = styled.div`
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.arrows {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.left-arrow {
|
||||||
|
width: 50%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-begin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-arrow {
|
||||||
|
width: 50%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user