mirror of
https://github.com/balzack/databag.git
synced 2025-02-14 20:49:16 +00:00
rendering unsealed assets
This commit is contained in:
parent
452679324a
commit
5f20b22250
@ -14,7 +14,7 @@ export function Conversation({ closeConversation, openDetails, cardId, channelId
|
|||||||
const thread = useRef(null);
|
const thread = useRef(null);
|
||||||
|
|
||||||
const topicRenderer = (topic) => {
|
const topicRenderer = (topic) => {
|
||||||
return (<TopicItem host={cardId == null} topic={topic}
|
return (<TopicItem host={cardId == null} contentKey={state.contentKey} topic={topic}
|
||||||
remove={() => actions.removeTopic(topic.id)}
|
remove={() => actions.removeTopic(topic.id)}
|
||||||
update={(text) => actions.updateTopic(topic, text)}
|
update={(text) => actions.updateTopic(topic, text)}
|
||||||
sealed={state.sealed && !state.contentKey}
|
sealed={state.sealed && !state.contentKey}
|
||||||
|
@ -144,16 +144,18 @@ export function useAddTopic(contentKey) {
|
|||||||
updateState({ busy: true });
|
updateState({ busy: true });
|
||||||
const type = contentKey ? 'sealedtopic' : 'superbasictopic';
|
const type = contentKey ? 'sealedtopic' : 'superbasictopic';
|
||||||
const message = (assets) => {
|
const message = (assets) => {
|
||||||
if (assets?.length) {
|
if (contentKey) {
|
||||||
return {
|
const message = {
|
||||||
assets,
|
assets: assets?.length ? assets : null,
|
||||||
text: state.messageText,
|
text: state.messageText,
|
||||||
textColor: state.textColorSet ? state.textColor : null,
|
textColor: state.textColorSet ? state.textColor : null,
|
||||||
textSize: state.textSizeSet ? state.textSize : null,
|
textSize: state.textSizeSet ? state.textSize : null,
|
||||||
}
|
}
|
||||||
|
return encryptTopicSubject({ message }, contentKey);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return {
|
return {
|
||||||
|
assets: assets?.length ? assets : null,
|
||||||
text: state.messageText,
|
text: state.messageText,
|
||||||
textColor: state.textColorSet ? state.textColor : null,
|
textColor: state.textColorSet ? state.textColor : null,
|
||||||
textSize: state.textSizeSet ? state.textSize : null,
|
textSize: state.textSizeSet ? state.textSize : null,
|
||||||
|
@ -8,10 +8,10 @@ import { ExclamationCircleOutlined, DeleteOutlined, EditOutlined, FireOutlined,
|
|||||||
import { Carousel } from 'carousel/Carousel';
|
import { Carousel } from 'carousel/Carousel';
|
||||||
import { useTopicItem } from './useTopicItem.hook';
|
import { useTopicItem } from './useTopicItem.hook';
|
||||||
|
|
||||||
export function TopicItem({ host, sealed, topic, update, remove }) {
|
export function TopicItem({ host, contentKey, sealed, topic, update, remove }) {
|
||||||
|
|
||||||
const [ modal, modalContext ] = Modal.useModal();
|
const [ modal, modalContext ] = Modal.useModal();
|
||||||
const { state, actions } = useTopicItem();
|
const { state, actions } = useTopicItem(topic, contentKey);
|
||||||
|
|
||||||
const removeTopic = () => {
|
const removeTopic = () => {
|
||||||
modal.confirm({
|
modal.confirm({
|
||||||
@ -52,16 +52,14 @@ export function TopicItem({ host, sealed, topic, update, remove }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderAsset = (asset, idx) => {
|
const renderAsset = (asset, idx) => {
|
||||||
if (asset.image) {
|
if (asset.type === 'image') {
|
||||||
return <ImageAsset thumbUrl={topic.assetUrl(asset.image.thumb, topic.id)}
|
return <ImageAsset asset={asset} />
|
||||||
fullUrl={topic.assetUrl(asset.image.full, topic.id)} />
|
|
||||||
}
|
}
|
||||||
if (asset.video) {
|
if (asset.type === 'video') {
|
||||||
return <VideoAsset thumbUrl={topic.assetUrl(asset.video.thumb, topic.id)}
|
return <VideoAsset asset={asset} />
|
||||||
lqUrl={topic.assetUrl(asset.video.lq, topic.id)} hdUrl={topic.assetUrl(asset.video.hd, topic.id)} />
|
|
||||||
}
|
}
|
||||||
if (asset.audio) {
|
if (asset.type === 'audio') {
|
||||||
return <AudioAsset label={asset.audio.label} audioUrl={topic.assetUrl(asset.audio.full, topic.id)} />
|
return <AudioAsset asset={asset} />
|
||||||
}
|
}
|
||||||
return <></>
|
return <></>
|
||||||
}
|
}
|
||||||
@ -113,7 +111,7 @@ export function TopicItem({ host, sealed, topic, update, remove }) {
|
|||||||
)}
|
)}
|
||||||
{ topic.transform === 'complete' && (
|
{ topic.transform === 'complete' && (
|
||||||
<div class="topic-assets">
|
<div class="topic-assets">
|
||||||
<Carousel pad={40} items={topic.assets} itemRenderer={renderAsset} />
|
<Carousel pad={40} items={state.assets} itemRenderer={renderAsset} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Modal } from 'antd';
|
import { Modal, Spin } from 'antd';
|
||||||
import ReactResizeDetector from 'react-resize-detector';
|
import ReactResizeDetector from 'react-resize-detector';
|
||||||
import { ImageAssetWrapper } from './ImageAsset.styled';
|
import { ImageAssetWrapper, ImageModalWrapper } from './ImageAsset.styled';
|
||||||
import { useImageAsset } from './useImageAsset.hook';
|
import { useImageAsset } from './useImageAsset.hook';
|
||||||
|
|
||||||
export function ImageAsset({ thumbUrl, fullUrl }) {
|
export function ImageAsset({ asset }) {
|
||||||
|
|
||||||
const { state, actions } = useImageAsset();
|
const { state, actions } = useImageAsset(asset);
|
||||||
const [dimension, setDimension] = useState({ width: 0, height: 0 });
|
const [dimension, setDimension] = useState({ width: 0, height: 0 });
|
||||||
|
|
||||||
const popout = () => {
|
const popout = () => {
|
||||||
@ -29,16 +29,26 @@ export function ImageAsset({ thumbUrl, fullUrl }) {
|
|||||||
if (width !== dimension.width || height !== dimension.height) {
|
if (width !== dimension.width || height !== dimension.height) {
|
||||||
setDimension({ width, height });
|
setDimension({ width, height });
|
||||||
}
|
}
|
||||||
return <img style={{ height: '100%', objectFit: 'contain' }} src={thumbUrl} alt="" />
|
return <img style={{ height: '100%', objectFit: 'contain' }} src={asset.thumb} alt="" />
|
||||||
}}
|
}}
|
||||||
</ReactResizeDetector>
|
</ReactResizeDetector>
|
||||||
<div class="viewer">
|
<div class="viewer">
|
||||||
<div class="overlay" style={{ width: dimension.width, height: dimension.height }}
|
<div class="overlay" style={{ width: dimension.width, height: dimension.height }}
|
||||||
onClick={popout} />
|
onClick={popout} />
|
||||||
<Modal centered={true} visible={state.popout} width={state.width + 12} bodyStyle={{ width: '100%', height: 'auto', paddingBottom: 6, paddingTop: 6, paddingLeft: 6, paddingRight: 6, backgroundColor: '#dddddd' }} footer={null} destroyOnClose={true} closable={false} onCancel={actions.clearPopout}>
|
<Modal centered={true} visible={state.popout} width={state.width + 12} bodyStyle={{ width: '100%', height: 'auto', paddingBottom: 6, paddingTop: 6, paddingLeft: 6, paddingRight: 6, backgroundColor: '#dddddd' }} footer={null} destroyOnClose={true} closable={false} onCancel={actions.clearPopout}>
|
||||||
<div onClick={actions.clearPopout}>
|
<ImageModalWrapper onClick={actions.clearPopout}>
|
||||||
<img style={{ width: '100%', objectFit: 'contain' }} src={fullUrl} alt="topic asset" />
|
{ state.loading && (
|
||||||
</div>
|
<div class="frame">
|
||||||
|
<img style={{ width: '100%', objectFit: 'contain' }} src={asset.thumb} alt="topic asset" />
|
||||||
|
<div class="spinner">
|
||||||
|
<Spin color={'white'} size="large" delay={250} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{ !state.loading && (
|
||||||
|
<img style={{ width: '100%', objectFit: 'contain' }} src={state.url} alt="topic asset" />
|
||||||
|
)}
|
||||||
|
</ImageModalWrapper>
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</ImageAssetWrapper>
|
</ImageAssetWrapper>
|
||||||
|
@ -40,3 +40,21 @@ export const ImageAssetWrapper = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const ImageModalWrapper = styled.div`
|
||||||
|
.frame {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-spin-dot-item {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
position: absolute;
|
||||||
|
color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
export function useImageAsset() {
|
export function useImageAsset(asset) {
|
||||||
|
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
popout: false,
|
popout: false,
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
|
loading: false,
|
||||||
|
error: false,
|
||||||
|
url: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateState = (value) => {
|
const updateState = (value) => {
|
||||||
@ -13,8 +16,16 @@ export function useImageAsset() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
setPopout: (width, height) => {
|
setPopout: async (width, height) => {
|
||||||
updateState({ popout: true, width, height });
|
if (asset.encrypted) {
|
||||||
|
updateState({ popout: true, width, height, loading: true, url: null });
|
||||||
|
const blob = await asset.getDecryptedBlob();
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
updateState({ loading: false, url });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
updateState({ popout: true, width, height, loading: false, url: asset.full });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
clearPopout: () => {
|
clearPopout: () => {
|
||||||
updateState({ popout: false });
|
updateState({ popout: false });
|
||||||
|
@ -1,16 +1,91 @@
|
|||||||
import { useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { checkResponse, fetchWithTimeout } from 'api/fetchUtil';
|
||||||
|
import { decryptBlock } from 'context/sealUtil';
|
||||||
|
|
||||||
export function useTopicItem() {
|
export function useTopicItem(topic, contentKey) {
|
||||||
|
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
editing: false,
|
editing: false,
|
||||||
message: null,
|
message: null,
|
||||||
|
assets: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(topic);
|
||||||
|
|
||||||
|
|
||||||
const updateState = (value) => {
|
const updateState = (value) => {
|
||||||
setState((s) => ({ ...s, ...value }));
|
setState((s) => ({ ...s, ...value }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const base64ToUint8Array = (base64) => {
|
||||||
|
var binaryString = atob(base64);
|
||||||
|
var bytes = new Uint8Array(binaryString.length);
|
||||||
|
for (var i = 0; i < binaryString.length; i++) {
|
||||||
|
bytes[i] = binaryString.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const assets = [];
|
||||||
|
if (topic.assets?.length) {
|
||||||
|
topic.assets.forEach(asset => {
|
||||||
|
if (asset.encrypted) {
|
||||||
|
const encrypted = true;
|
||||||
|
const { type, thumb, parts } = asset.encrypted;
|
||||||
|
const getDecryptedBlob = async () => {
|
||||||
|
let pos = 0;
|
||||||
|
let len = 0;
|
||||||
|
|
||||||
|
const slices = []
|
||||||
|
for (let i = 0; i < parts.length; i++) {
|
||||||
|
const part = parts[i];
|
||||||
|
const url = topic.assetUrl(part.partId, topic.id);
|
||||||
|
const response = await fetchWithTimeout(url, { method: 'GET' });
|
||||||
|
const block = await response.text();
|
||||||
|
const decrypted = decryptBlock(block, part.blockIv, contentKey);
|
||||||
|
const slice = base64ToUint8Array(decrypted);
|
||||||
|
slices.push(slice);
|
||||||
|
len += slice.byteLength;
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = new Uint8Array(len)
|
||||||
|
for (let i = 0; i < slices.length; i++) {
|
||||||
|
const slice = slices[i];
|
||||||
|
data.set(slice, pos);
|
||||||
|
pos += slice.byteLength
|
||||||
|
}
|
||||||
|
return new Blob([data]);
|
||||||
|
}
|
||||||
|
assets.push({ type, thumb, encrypted, getDecryptedBlob });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const encrypted = false
|
||||||
|
if (asset.image) {
|
||||||
|
const type = 'image';
|
||||||
|
const thumb = topic.assetUrl(asset.image.thumb, topic.id);
|
||||||
|
const full = topic.assetUrl(asset.image.full, topic.id);
|
||||||
|
assets.push({ type, thumb, encrypted, full });
|
||||||
|
}
|
||||||
|
else if (asset.video) {
|
||||||
|
const type = 'video';
|
||||||
|
const thumb = topic.assetUrl(asset.video.thumb, topic.id);
|
||||||
|
const lq = topic.assetUrl(asset.video.lq, topic.id);
|
||||||
|
const hd = topic.assetUrl(asset.video.hd, topic.id);
|
||||||
|
assets.push({ type, thumb, encrypted, lq, hd });
|
||||||
|
}
|
||||||
|
else if (asset.audio) {
|
||||||
|
const type = 'audio';
|
||||||
|
const label = asset.audio.label;
|
||||||
|
const full = topic.assetUrl(asset.audio.full, topic.id);
|
||||||
|
assets.push({ type, label, encrypted, full });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
updateState({ assets });
|
||||||
|
}
|
||||||
|
}, [topic.assets]);
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
setEditing: (message) => {
|
setEditing: (message) => {
|
||||||
updateState({ editing: true, message });
|
updateState({ editing: true, message });
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { Modal } from 'antd';
|
import { Modal, Spin } from 'antd';
|
||||||
import ReactResizeDetector from 'react-resize-detector';
|
import ReactResizeDetector from 'react-resize-detector';
|
||||||
import { VideoCameraOutlined } from '@ant-design/icons';
|
import { VideoCameraOutlined } from '@ant-design/icons';
|
||||||
import { VideoAssetWrapper } from './VideoAsset.styled';
|
import { VideoAssetWrapper, VideoModalWrapper } from './VideoAsset.styled';
|
||||||
import { useVideoAsset } from './useVideoAsset.hook';
|
import { useVideoAsset } from './useVideoAsset.hook';
|
||||||
|
|
||||||
export function VideoAsset({ thumbUrl, lqUrl, hdUrl }) {
|
export function VideoAsset({ asset }) {
|
||||||
|
|
||||||
const { state, actions } = useVideoAsset();
|
const { state, actions } = useVideoAsset(asset);
|
||||||
|
|
||||||
const activate = () => {
|
const activate = () => {
|
||||||
if (state.dimension.width / state.dimension.height > window.innerWidth / window.innerHeight) {
|
if (state.dimension.width / state.dimension.height > window.innerWidth / window.innerHeight) {
|
||||||
@ -28,7 +28,7 @@ export function VideoAsset({ thumbUrl, lqUrl, hdUrl }) {
|
|||||||
if (width !== state.dimension.width || height !== state.dimension.height) {
|
if (width !== state.dimension.width || height !== state.dimension.height) {
|
||||||
actions.setDimension({ width, height });
|
actions.setDimension({ width, height });
|
||||||
}
|
}
|
||||||
return <img style={{ height: '100%', objectFit: 'contain' }} src={thumbUrl} alt="" />
|
return <img style={{ height: '100%', objectFit: 'contain' }} src={asset.thumb} alt="" />
|
||||||
}}
|
}}
|
||||||
</ReactResizeDetector>
|
</ReactResizeDetector>
|
||||||
<div class="overlay" style={{ width: state.dimension.width, height: state.dimension.height }}>
|
<div class="overlay" style={{ width: state.dimension.width, height: state.dimension.height }}>
|
||||||
@ -38,8 +38,20 @@ export function VideoAsset({ thumbUrl, lqUrl, hdUrl }) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Modal centered={true} style={{ backgroundColor: '#aacc00', padding: 0 }} visible={state.active} width={state.width + 12} bodyStyle={{ paddingBottom: 0, paddingTop: 6, paddingLeft: 6, paddingRight: 6, backgroundColor: '#dddddd', margin: 0 }} footer={null} destroyOnClose={true} closable={false} onCancel={actions.clearActive}>
|
<Modal centered={true} style={{ backgroundColor: '#aacc00', padding: 0 }} visible={state.active} width={state.width + 12} bodyStyle={{ paddingBottom: 0, paddingTop: 6, paddingLeft: 6, paddingRight: 6, backgroundColor: '#dddddd', margin: 0 }} footer={null} destroyOnClose={true} closable={false} onCancel={actions.clearActive}>
|
||||||
<video autoplay="true" controls src={hdUrl} width={state.width} height={state.height}
|
<VideoModalWrapper>
|
||||||
playsinline="true" />
|
{ state.loading && (
|
||||||
|
<div class="frame">
|
||||||
|
<img style={{ width: '100%', objectFit: 'contain' }} src={asset.thumb} alt="topic asset" />
|
||||||
|
<div class="spinner">
|
||||||
|
<Spin color={'white'} size="large" delay={250} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{ !state.loading && (
|
||||||
|
<video autoplay="true" controls src={state.url} width={state.width} height={state.height}
|
||||||
|
playsinline="true" />
|
||||||
|
)}
|
||||||
|
</VideoModalWrapper>
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</VideoAssetWrapper>
|
</VideoAssetWrapper>
|
||||||
|
@ -14,3 +14,21 @@ export const VideoAssetWrapper = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const VideoModalWrapper = styled.div`
|
||||||
|
.frame {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-spin-dot-item {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
position: absolute;
|
||||||
|
color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
export function useVideoAsset() {
|
export function useVideoAsset(asset) {
|
||||||
|
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
active: false,
|
active: false,
|
||||||
dimension: { width: 0, height: 0 },
|
dimension: { width: 0, height: 0 },
|
||||||
|
loading: false,
|
||||||
|
error: false,
|
||||||
|
url: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateState = (value) => {
|
const updateState = (value) => {
|
||||||
@ -14,8 +17,16 @@ export function useVideoAsset() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
setActive: (width, height, url) => {
|
setActive: async (width, height) => {
|
||||||
updateState({ active: true, width, height });
|
if (asset.encrypted) {
|
||||||
|
updateState({ active: true, width, height, loading: true, url: null });
|
||||||
|
const blob = await asset.getDecryptedBlob();
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
updateState({ loading: false, url });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
updateState({ popout: true, width, height, loading: false, url: asset.hd });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
clearActive: () => {
|
clearActive: () => {
|
||||||
updateState({ active: false });
|
updateState({ active: false });
|
||||||
|
Loading…
Reference in New Issue
Block a user