mirror of
https://github.com/balzack/databag.git
synced 2025-02-14 12:39:17 +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 topicRenderer = (topic) => {
|
||||
return (<TopicItem host={cardId == null} topic={topic}
|
||||
return (<TopicItem host={cardId == null} contentKey={state.contentKey} topic={topic}
|
||||
remove={() => actions.removeTopic(topic.id)}
|
||||
update={(text) => actions.updateTopic(topic, text)}
|
||||
sealed={state.sealed && !state.contentKey}
|
||||
|
@ -144,16 +144,18 @@ export function useAddTopic(contentKey) {
|
||||
updateState({ busy: true });
|
||||
const type = contentKey ? 'sealedtopic' : 'superbasictopic';
|
||||
const message = (assets) => {
|
||||
if (assets?.length) {
|
||||
return {
|
||||
assets,
|
||||
if (contentKey) {
|
||||
const message = {
|
||||
assets: assets?.length ? assets : null,
|
||||
text: state.messageText,
|
||||
textColor: state.textColorSet ? state.textColor : null,
|
||||
textSize: state.textSizeSet ? state.textSize : null,
|
||||
}
|
||||
return encryptTopicSubject({ message }, contentKey);
|
||||
}
|
||||
else {
|
||||
return {
|
||||
assets: assets?.length ? assets : null,
|
||||
text: state.messageText,
|
||||
textColor: state.textColorSet ? state.textColor : null,
|
||||
textSize: state.textSizeSet ? state.textSize : null,
|
||||
|
@ -8,10 +8,10 @@ import { ExclamationCircleOutlined, DeleteOutlined, EditOutlined, FireOutlined,
|
||||
import { Carousel } from 'carousel/Carousel';
|
||||
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 { state, actions } = useTopicItem();
|
||||
const { state, actions } = useTopicItem(topic, contentKey);
|
||||
|
||||
const removeTopic = () => {
|
||||
modal.confirm({
|
||||
@ -52,16 +52,14 @@ export function TopicItem({ host, sealed, topic, update, remove }) {
|
||||
};
|
||||
|
||||
const renderAsset = (asset, idx) => {
|
||||
if (asset.image) {
|
||||
return <ImageAsset thumbUrl={topic.assetUrl(asset.image.thumb, topic.id)}
|
||||
fullUrl={topic.assetUrl(asset.image.full, topic.id)} />
|
||||
if (asset.type === 'image') {
|
||||
return <ImageAsset asset={asset} />
|
||||
}
|
||||
if (asset.video) {
|
||||
return <VideoAsset thumbUrl={topic.assetUrl(asset.video.thumb, topic.id)}
|
||||
lqUrl={topic.assetUrl(asset.video.lq, topic.id)} hdUrl={topic.assetUrl(asset.video.hd, topic.id)} />
|
||||
if (asset.type === 'video') {
|
||||
return <VideoAsset asset={asset} />
|
||||
}
|
||||
if (asset.audio) {
|
||||
return <AudioAsset label={asset.audio.label} audioUrl={topic.assetUrl(asset.audio.full, topic.id)} />
|
||||
if (asset.type === 'audio') {
|
||||
return <AudioAsset asset={asset} />
|
||||
}
|
||||
return <></>
|
||||
}
|
||||
@ -113,7 +111,7 @@ export function TopicItem({ host, sealed, topic, update, remove }) {
|
||||
)}
|
||||
{ topic.transform === 'complete' && (
|
||||
<div class="topic-assets">
|
||||
<Carousel pad={40} items={topic.assets} itemRenderer={renderAsset} />
|
||||
<Carousel pad={40} items={state.assets} itemRenderer={renderAsset} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
@ -1,12 +1,12 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Modal } from 'antd';
|
||||
import { Modal, Spin } from 'antd';
|
||||
import ReactResizeDetector from 'react-resize-detector';
|
||||
import { ImageAssetWrapper } from './ImageAsset.styled';
|
||||
import { ImageAssetWrapper, ImageModalWrapper } from './ImageAsset.styled';
|
||||
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 popout = () => {
|
||||
@ -29,16 +29,26 @@ export function ImageAsset({ thumbUrl, fullUrl }) {
|
||||
if (width !== dimension.width || height !== dimension.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>
|
||||
<div class="viewer">
|
||||
<div class="overlay" style={{ width: dimension.width, height: dimension.height }}
|
||||
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}>
|
||||
<div onClick={actions.clearPopout}>
|
||||
<img style={{ width: '100%', objectFit: 'contain' }} src={fullUrl} alt="topic asset" />
|
||||
</div>
|
||||
<ImageModalWrapper onClick={actions.clearPopout}>
|
||||
{ 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 && (
|
||||
<img style={{ width: '100%', objectFit: 'contain' }} src={state.url} alt="topic asset" />
|
||||
)}
|
||||
</ImageModalWrapper>
|
||||
</Modal>
|
||||
</div>
|
||||
</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';
|
||||
|
||||
export function useImageAsset() {
|
||||
export function useImageAsset(asset) {
|
||||
|
||||
const [state, setState] = useState({
|
||||
popout: false,
|
||||
width: 0,
|
||||
height: 0,
|
||||
loading: false,
|
||||
error: false,
|
||||
url: null,
|
||||
});
|
||||
|
||||
const updateState = (value) => {
|
||||
@ -13,8 +16,16 @@ export function useImageAsset() {
|
||||
}
|
||||
|
||||
const actions = {
|
||||
setPopout: (width, height) => {
|
||||
updateState({ popout: true, width, height });
|
||||
setPopout: async (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: () => {
|
||||
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({
|
||||
editing: false,
|
||||
message: null,
|
||||
assets: [],
|
||||
});
|
||||
|
||||
console.log(topic);
|
||||
|
||||
|
||||
const updateState = (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 = {
|
||||
setEditing: (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 { VideoCameraOutlined } from '@ant-design/icons';
|
||||
import { VideoAssetWrapper } from './VideoAsset.styled';
|
||||
import { VideoAssetWrapper, VideoModalWrapper } from './VideoAsset.styled';
|
||||
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 = () => {
|
||||
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) {
|
||||
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>
|
||||
<div class="overlay" style={{ width: state.dimension.width, height: state.dimension.height }}>
|
||||
@ -38,8 +38,20 @@ export function VideoAsset({ thumbUrl, lqUrl, hdUrl }) {
|
||||
</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}>
|
||||
<video autoplay="true" controls src={hdUrl} width={state.width} height={state.height}
|
||||
playsinline="true" />
|
||||
<VideoModalWrapper>
|
||||
{ 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>
|
||||
</div>
|
||||
</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';
|
||||
|
||||
export function useVideoAsset() {
|
||||
export function useVideoAsset(asset) {
|
||||
|
||||
const [state, setState] = useState({
|
||||
width: 0,
|
||||
height: 0,
|
||||
active: false,
|
||||
dimension: { width: 0, height: 0 },
|
||||
loading: false,
|
||||
error: false,
|
||||
url: null,
|
||||
});
|
||||
|
||||
const updateState = (value) => {
|
||||
@ -14,8 +17,16 @@ export function useVideoAsset() {
|
||||
}
|
||||
|
||||
const actions = {
|
||||
setActive: (width, height, url) => {
|
||||
updateState({ active: true, width, height });
|
||||
setActive: async (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: () => {
|
||||
updateState({ active: false });
|
||||
|
Loading…
Reference in New Issue
Block a user