mirror of
https://github.com/balzack/databag.git
synced 2025-02-11 19:19:16 +00:00
rendering upload progress
This commit is contained in:
parent
eefa325adf
commit
ba1782e8b0
@ -2726,6 +2726,12 @@ paths:
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: confirm
|
||||
in: query
|
||||
description: confirmed state of topic
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
responses:
|
||||
'200':
|
||||
description: success
|
||||
|
@ -20,6 +20,8 @@ require (
|
||||
github.com/theckman/go-securerandom v0.1.1 // indirect
|
||||
github.com/valyala/fastjson v1.6.3 // indirect
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
|
||||
golang.org/x/tools v0.1.11 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||
gorm.io/driver/sqlite v1.2.6 // indirect
|
||||
gorm.io/gorm v1.22.5 // indirect
|
||||
|
@ -39,8 +39,23 @@ github.com/theckman/go-securerandom v0.1.1 h1:5KctSyM0D5KKFK+bsypIyLq7yik0CEaI5i
|
||||
github.com/theckman/go-securerandom v0.1.1/go.mod h1:bmkysLfBH6i891sBpcP4xRM3XIB7jMeiKJB31jlResI=
|
||||
github.com/valyala/fastjson v1.6.3 h1:tAKFnnwmeMGPbwJ7IwxcTPCNr3uIzoIj3/Fh90ra4xc=
|
||||
github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY=
|
||||
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
|
@ -13,6 +13,7 @@ func SetChannelTopicSubject(w http.ResponseWriter, r *http.Request) {
|
||||
// scan parameters
|
||||
params := mux.Vars(r)
|
||||
topicId := params["topicId"]
|
||||
confirm := r.FormValue("confirm");
|
||||
|
||||
var subject Subject
|
||||
if err := ParseRequest(r, w, &subject); err != nil {
|
||||
@ -52,6 +53,16 @@ func SetChannelTopicSubject(w http.ResponseWriter, r *http.Request) {
|
||||
if res := tx.Model(topicSlot.Topic).Update("data_type", subject.DataType).Error; res != nil {
|
||||
return res
|
||||
}
|
||||
if confirm == "true" {
|
||||
if res := tx.Model(topicSlot.Topic).Update("status", APP_TOPICCONFIRMED).Error; res != nil {
|
||||
return res
|
||||
}
|
||||
}
|
||||
if confirm == "false" {
|
||||
if res := tx.Model(topicSlot.Topic).Update("status", APP_TOPICUNCONFIRMED).Error; res != nil {
|
||||
return res
|
||||
}
|
||||
}
|
||||
if res := tx.Model(&topicSlot.Topic).Update("detail_revision", act.ChannelRevision + 1).Error; res != nil {
|
||||
return res
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
"@testing-library/react": "^12.0.0",
|
||||
"@testing-library/user-event": "^13.2.1",
|
||||
"antd": "^4.19.1",
|
||||
"axios": "^0.27.2",
|
||||
"base-64": "^1.0.0",
|
||||
"react": "^17.0.2",
|
||||
"react-color": "^2.19.3",
|
||||
|
@ -8,6 +8,7 @@ import { CardContextProvider } from 'context/CardContext';
|
||||
import { ChannelContextProvider } from 'context/ChannelContext';
|
||||
import { ConversationContextProvider } from 'context/ConversationContext';
|
||||
import { StoreContextProvider } from 'context/StoreContext';
|
||||
import { UploadContextProvider } from 'context/UploadContext';
|
||||
import { Home } from './Home/Home';
|
||||
import { Admin } from './Admin/Admin';
|
||||
import { Login } from './Login/Login';
|
||||
@ -22,49 +23,51 @@ import 'antd/dist/antd.min.css';
|
||||
function App() {
|
||||
|
||||
return (
|
||||
<ChannelContextProvider>
|
||||
<CardContextProvider>
|
||||
<GroupContextProvider>
|
||||
<ArticleContextProvider>
|
||||
<ProfileContextProvider>
|
||||
<AccountContextProvider>
|
||||
<StoreContextProvider>
|
||||
<AppContextProvider>
|
||||
<div style={{ position: 'absolute', width: '100vw', height: '100vh', backgroundColor: '#8fbea7' }}>
|
||||
<img src={login} alt="" style={{ position: 'absolute', width: '33%', bottom: 0, right: 0 }}/>
|
||||
</div>
|
||||
<div style={{ position: 'absolute', width: '100vw', height: '100vh' }}>
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/" element={ <Home /> } />
|
||||
<Route path="/login" element={ <Login /> } />
|
||||
<Route path="/admin" element={ <Admin /> } />
|
||||
<Route path="/create" element={ <Create /> } />
|
||||
<Route path="/user" element={ <User /> }>
|
||||
<Route path="profile" element={<Profile />} />
|
||||
<Route path="contact/:guid" element={<Contact />} />
|
||||
<Route path="conversation/:cardId/:channelId" element={
|
||||
<ConversationContextProvider>
|
||||
<Conversation />
|
||||
</ConversationContextProvider>
|
||||
} />
|
||||
<Route path="conversation/:channelId" element={
|
||||
<ConversationContextProvider>
|
||||
<Conversation />
|
||||
</ConversationContextProvider>
|
||||
} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</Router>
|
||||
</div>
|
||||
</AppContextProvider>
|
||||
</StoreContextProvider>
|
||||
</AccountContextProvider>
|
||||
</ProfileContextProvider>
|
||||
</ArticleContextProvider>
|
||||
</GroupContextProvider>
|
||||
</CardContextProvider>
|
||||
</ChannelContextProvider>
|
||||
<UploadContextProvider>
|
||||
<ChannelContextProvider>
|
||||
<CardContextProvider>
|
||||
<GroupContextProvider>
|
||||
<ArticleContextProvider>
|
||||
<ProfileContextProvider>
|
||||
<AccountContextProvider>
|
||||
<StoreContextProvider>
|
||||
<AppContextProvider>
|
||||
<div style={{ position: 'absolute', width: '100vw', height: '100vh', backgroundColor: '#8fbea7' }}>
|
||||
<img src={login} alt="" style={{ position: 'absolute', width: '33%', bottom: 0, right: 0 }}/>
|
||||
</div>
|
||||
<div style={{ position: 'absolute', width: '100vw', height: '100vh' }}>
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/" element={ <Home /> } />
|
||||
<Route path="/login" element={ <Login /> } />
|
||||
<Route path="/admin" element={ <Admin /> } />
|
||||
<Route path="/create" element={ <Create /> } />
|
||||
<Route path="/user" element={ <User /> }>
|
||||
<Route path="profile" element={<Profile />} />
|
||||
<Route path="contact/:guid" element={<Contact />} />
|
||||
<Route path="conversation/:cardId/:channelId" element={
|
||||
<ConversationContextProvider>
|
||||
<Conversation />
|
||||
</ConversationContextProvider>
|
||||
} />
|
||||
<Route path="conversation/:channelId" element={
|
||||
<ConversationContextProvider>
|
||||
<Conversation />
|
||||
</ConversationContextProvider>
|
||||
} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</Router>
|
||||
</div>
|
||||
</AppContextProvider>
|
||||
</StoreContextProvider>
|
||||
</AccountContextProvider>
|
||||
</ProfileContextProvider>
|
||||
</ArticleContextProvider>
|
||||
</GroupContextProvider>
|
||||
</CardContextProvider>
|
||||
</ChannelContextProvider>
|
||||
</UploadContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ export function useAddTopic() {
|
||||
textSize: 14,
|
||||
textSizeSet: false,
|
||||
busy: false,
|
||||
progress: null,
|
||||
});
|
||||
|
||||
const { cardId, channelId } = useParams();
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect, useRef } from 'react'
|
||||
import { ExclamationCircleOutlined, CloseOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import { useConversation } from './useConversation.hook';
|
||||
import { Button, Input, Checkbox, Modal, Spin, Tooltip } from 'antd'
|
||||
import { Button, Input, Progress, Checkbox, Modal, Spin, Tooltip } from 'antd'
|
||||
import { ConversationWrapper, ConversationButton, EditButton, CloseButton, ListItem, BusySpin, Offsync } from './Conversation.styled';
|
||||
import { AutoSizer, CellMeasurer, CellMeasurerCache, List } from 'react-virtualized';
|
||||
import { AddTopic } from './AddTopic/AddTopic';
|
||||
@ -45,6 +45,20 @@ export function Conversation() {
|
||||
setShowEdit(true);
|
||||
}
|
||||
|
||||
const uploadProgress = () => {
|
||||
let progress = [];
|
||||
for (let entry of state.progress) {
|
||||
progress.push(
|
||||
<div class="progress">
|
||||
<div class="index">{ entry.index }/{ entry.count }</div>
|
||||
<Progress percent={Math.floor(100 * entry.active?.loaded / entry.active?.total)} size="small" showInfo={false} />
|
||||
<Button type="link">Cancel</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return progress;
|
||||
}
|
||||
|
||||
const onMembers = () => {
|
||||
setShowMembers(true);
|
||||
}
|
||||
@ -100,6 +114,11 @@ export function Conversation() {
|
||||
</div>
|
||||
</div>
|
||||
<div class="thread">
|
||||
{ state.progress && (
|
||||
<div class="uploading">
|
||||
{ uploadProgress() }
|
||||
</div>
|
||||
)}
|
||||
<VirtualList id={state.channelId + state.cardId}
|
||||
items={state.topics} itemRenderer={topicRenderer} onMore={onMoreTopics} />
|
||||
<BusySpin size="large" delay="1000" spinning={state.loading} />
|
||||
|
@ -72,7 +72,36 @@ export const ConversationWrapper = styled.div`
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
overflow: auto;
|
||||
|
||||
.uploading {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 10;
|
||||
border-bottom: 1px solid #888888;
|
||||
border-left: 1px solid #888888;
|
||||
border-bottom-left-radius: 4px;
|
||||
padding-left: 8px;
|
||||
background-color: #ffffff;
|
||||
|
||||
.progress {
|
||||
width: 250px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.index {
|
||||
display: flex;
|
||||
width: 64px;
|
||||
justify-content: center;
|
||||
color: #444444;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -2,6 +2,7 @@ import { useContext, useState, useEffect, useRef } from 'react';
|
||||
import { useNavigate, useLocation, useParams } from "react-router-dom";
|
||||
import { ConversationContext } from 'context/ConversationContext';
|
||||
import { StoreContext } from 'context/StoreContext';
|
||||
import { UploadContext } from 'context/UploadContext';
|
||||
|
||||
export function useConversation() {
|
||||
|
||||
@ -18,11 +19,21 @@ export function useConversation() {
|
||||
const navigate = useNavigate();
|
||||
const conversation = useContext(ConversationContext);
|
||||
const store = useContext(StoreContext);
|
||||
const upload = useContext(UploadContext);
|
||||
|
||||
const updateState = (value) => {
|
||||
setState((s) => ({ ...s, ...value }));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (cardId) {
|
||||
updateState({ progress: upload.state.progress.get(`${cardId}:${channelId}`) });
|
||||
}
|
||||
else {
|
||||
updateState({ progress: upload.state.progress.get(`:${channelId}`) });
|
||||
}
|
||||
}, [upload]);
|
||||
|
||||
const actions = {
|
||||
close: () => {
|
||||
navigate('/user')
|
||||
|
@ -1,8 +1,15 @@
|
||||
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||
|
||||
export async function addChannelTopic(token, channelId, message, assets ) {
|
||||
|
||||
if (assets == null || assets.length == 0) {
|
||||
export async function addChannelTopic(token, channelId, message, assets ): string {
|
||||
|
||||
if (message == null && (assets == null || assets.length == 0)) {
|
||||
let topic = await fetchWithTimeout(`/content/channels/${channelId}/topics?agent=${token}`,
|
||||
{ method: 'POST', body: JSON.stringify({}) });
|
||||
checkResponse(topic);
|
||||
let slot = await topic.json();
|
||||
return slot.id;
|
||||
}
|
||||
else if (assets == null || assets.length == 0) {
|
||||
let subject = { data: JSON.stringify(message, (key, value) => {
|
||||
if (value !== null) return value
|
||||
}), datatype: 'superbasictopic' };
|
||||
@ -10,6 +17,8 @@ export async function addChannelTopic(token, channelId, message, assets ) {
|
||||
let topic = await fetchWithTimeout(`/content/channels/${channelId}/topics?agent=${token}&confirm=true`,
|
||||
{ method: 'POST', body: JSON.stringify(subject) });
|
||||
checkResponse(topic);
|
||||
let slot = await topic.json();
|
||||
return slot.id;
|
||||
}
|
||||
else {
|
||||
|
||||
@ -78,6 +87,7 @@ export async function addChannelTopic(token, channelId, message, assets ) {
|
||||
let confirmed = await fetchWithTimeout(`/content/channels/${channelId}/topics/${slot.id}/confirmed?agent=${token}`,
|
||||
{ method: 'PUT', body: JSON.stringify('confirmed') });
|
||||
checkResponse(confirmed);
|
||||
return slot.id;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,13 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||
|
||||
export async function addContactChannelTopic(server, token, channelId, message, assets ) {
|
||||
|
||||
if (assets == null || assets.length == 0) {
|
||||
|
||||
if (message == null || (assets == null || assets.length == 0)) {
|
||||
let topic = await fetchWithTimeout(`https://${server}/content/channels/${channelId}/topics?contact=${token}`,
|
||||
{ method: 'POST', body: JSON.stringify({}) });
|
||||
checkResponse(topic);
|
||||
return await topic.json().id;
|
||||
}
|
||||
else if (assets == null || assets.length == 0) {
|
||||
let subject = { data: JSON.stringify(message, (key, value) => {
|
||||
if (value !== null) return value
|
||||
}), datatype: 'superbasictopic' };
|
||||
@ -11,9 +16,9 @@ export async function addContactChannelTopic(server, token, channelId, message,
|
||||
let topic = await fetchWithTimeout(`https://${server}/content/channels/${channelId}/topics?contact=${token}&confirm=true`,
|
||||
{ method: 'POST', body: JSON.stringify(subject) });
|
||||
checkResponse(topic);
|
||||
return await topic.json().id;
|
||||
}
|
||||
else {
|
||||
|
||||
let topic = await fetchWithTimeout(`https://${server}/content/channels/${channelId}/topics?contact=${token}`,
|
||||
{ method: 'POST', body: JSON.stringify({}) });
|
||||
checkResponse(topic);
|
||||
@ -79,6 +84,7 @@ export async function addContactChannelTopic(server, token, channelId, message,
|
||||
let confirmed = await fetchWithTimeout(`https://${server}/content/channels/${channelId}/topics/${slot.id}/confirmed?contact=${token}`,
|
||||
{ method: 'PUT', body: JSON.stringify('confirmed') });
|
||||
checkResponse(confirmed);
|
||||
return slot.id;
|
||||
}
|
||||
}
|
||||
|
||||
|
48
net/web/src/api/setChannelTopicAsset.js
Normal file
48
net/web/src/api/setChannelTopicAsset.js
Normal file
@ -0,0 +1,48 @@
|
||||
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||
|
||||
export async function setChannelTopicSubject(token, channelId, topicId, asset) {
|
||||
if (asset.image) {
|
||||
const formData = new FormData();
|
||||
formData.append('asset', asset.image);
|
||||
let transform = encodeURIComponent(JSON.stringify(["ithumb;photo", "icopy;photo"]));
|
||||
let topicAsset = await fetch(`/content/channels/${channelId}/topics/${slot.id}/assets?transforms=${transform}&agent=${token}`, { method: 'POST', body: formData });
|
||||
checkResponse(topicAsset);
|
||||
let assetEntry = await topicAsset.json();
|
||||
return {
|
||||
image: {
|
||||
thumb: assetEntry.find(item => item.transform === 'ithumb;photo').assetId,
|
||||
full: assetEntry.find(item => item.transform === 'icopy;photo').assetId,
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (asset.video) {
|
||||
const formData = new FormData();
|
||||
formData.append('asset', asset.video);
|
||||
let thumb = 'vthumb;video;' + asset.position;
|
||||
let transform = encodeURIComponent(JSON.stringify(["vlq;video", "vhd;video", thumb]));
|
||||
let topicAsset = await fetch(`/content/channels/${channelId}/topics/${slot.id}/assets?transforms=${transform}&agent=${token}`, { method: 'POST', body: formData });
|
||||
checkResponse(topicAsset);
|
||||
let assetEntry = await topicAsset.json();
|
||||
return {
|
||||
video: {
|
||||
thumb: assetEntry.find(item => item.transform === thumb).assetId,
|
||||
lq: assetEntry.find(item => item.transform === 'vlq;video').assetId,
|
||||
hd: assetEntry.find(item => item.transform === 'vhd;video').assetId,
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (asset.audio) {
|
||||
const formData = new FormData();
|
||||
formData.append('asset', asset.audio);
|
||||
let transform = encodeURIComponent(JSON.stringify(["acopy;audio"]));
|
||||
let topicAsset = await fetch(`/content/channels/${channelId}/topics/${slot.id}/assets?transforms=${transform}&agent=${token}`, { method: 'POST', body: formData });
|
||||
checkResponse(topicAsset);
|
||||
let assetEntry = await topicAsset.json();
|
||||
return {
|
||||
audio: {
|
||||
label: asset.label,
|
||||
full: assetEntry.find(item => item.transform === 'acopy;audio').assetId,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ export async function setChannelTopicSubject(token, channelId, topicId, data) {
|
||||
if (value !== null) return value
|
||||
}), datatype: 'superbasictopic' };
|
||||
|
||||
let channel = await fetchWithTimeout(`/content/channels/${channelId}/topics/${topicId}/subject?agent=${token}`,
|
||||
let channel = await fetchWithTimeout(`/content/channels/${channelId}/topics/${topicId}/subject?agent=${token}&confirm=true`,
|
||||
{ method: 'PUT', body: JSON.stringify(subject) });
|
||||
checkResponse(channel);
|
||||
}
|
||||
|
48
net/web/src/api/setContactChannelTopicAsset.js
Normal file
48
net/web/src/api/setContactChannelTopicAsset.js
Normal file
@ -0,0 +1,48 @@
|
||||
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||
|
||||
export async function setContactChannelTopicSubject(server, token, channelId, topicId, asset) {
|
||||
if (asset.image) {
|
||||
const formData = new FormData();
|
||||
formData.append('asset', asset.image);
|
||||
let transform = encodeURIComponent(JSON.stringify(["ithumb;photo", "icopy;photo"]));
|
||||
let topicAsset = await fetch(`https://${server}/content/channels/${channelId}/topics/${slot.id}/assets?transforms=${transform}&contact=${token}`, { method: 'POST', body: formData });
|
||||
checkResponse(topicAsset);
|
||||
let assetEntry = await topicAsset.json();
|
||||
return {
|
||||
image: {
|
||||
thumb: assetEntry.find(item => item.transform === 'ithumb;photo').assetId,
|
||||
full: assetEntry.find(item => item.transform === 'icopy;photo').assetId,
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (asset.video) {
|
||||
const formData = new FormData();
|
||||
formData.append('asset', asset.video);
|
||||
let thumb = "vthumb;video;" + asset.position
|
||||
let transform = encodeURIComponent(JSON.stringify(["vhd;video", "vlq;video", thumb]));
|
||||
let topicAsset = await fetch(`https://${server}/content/channels/${channelId}/topics/${slot.id}/assets?transforms=${transform}&contact=${token}`, { method: 'POST', body: formData });
|
||||
checkResponse(topicAsset);
|
||||
let assetEntry = await topicAsset.json();
|
||||
return {
|
||||
video: {
|
||||
thumb: assetEntry.find(item => item.transform === thumb).assetId,
|
||||
lq: assetEntry.find(item => item.transform === 'vlq;video').assetId,
|
||||
hd: assetEntry.find(item => item.transform === 'vhd;video').assetId,
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (asset.audio) {
|
||||
const formData = new FormData();
|
||||
formData.append('asset', asset.audio);
|
||||
let transform = encodeURIComponent(JSON.stringify(["acopy;audio"]));
|
||||
let topicAsset = await fetch(`https://${server}/content/channels/${channelId}/topics/${slot.id}/assets?transforms=${transform}&contact=${token}`, { method: 'POST', body: formData });
|
||||
checkResponse(topicAsset);
|
||||
let assetEntry = await topicAsset.json();
|
||||
return {
|
||||
audio: {
|
||||
label: asset.label,
|
||||
full: assetEntry.find(item => item.transform === 'acopy;audio').assetId,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ export async function setContactChannelTopicSubject(server, token, channelId, to
|
||||
if (value !== null) return value
|
||||
}), datatype: 'superbasictopic' };
|
||||
|
||||
let channel = await fetchWithTimeout(`https://${server}//content/channels/${channelId}/topics/${topicId}/subject?contact=${token}`,
|
||||
let channel = await fetchWithTimeout(`https://${server}//content/channels/${channelId}/topics/${topicId}/subject?contact=${token}&confirm=true`,
|
||||
{ method: 'PUT', body: JSON.stringify(subject) });
|
||||
checkResponse(channel);
|
||||
}
|
||||
|
14
net/web/src/context/UploadContext.js
Normal file
14
net/web/src/context/UploadContext.js
Normal file
@ -0,0 +1,14 @@
|
||||
import { createContext } from 'react';
|
||||
import { useUploadContext } from './useUploadContext.hook';
|
||||
|
||||
export const UploadContext = createContext({});
|
||||
|
||||
export function UploadContextProvider({ children }) {
|
||||
const { state, actions } = useUploadContext();
|
||||
return (
|
||||
<UploadContext.Provider value={{ state, actions }}>
|
||||
{children}
|
||||
</UploadContext.Provider>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
import { useEffect, useState, useRef, useContext } from 'react';
|
||||
import { getContactChannels } from 'api/getContactChannels';
|
||||
import { getContactChannelDetail } from 'api/getContactChannelDetail';
|
||||
import { getContactChannelSummary } from 'api/getContactChannelSummary';
|
||||
@ -22,12 +22,14 @@ import { getContactChannelTopic } from 'api/getContactChannelTopic';
|
||||
import { getContactChannelTopicAssetUrl } from 'api/getContactChannelTopicAssetUrl';
|
||||
import { addCard } from 'api/addCard';
|
||||
import { removeCard } from 'api/removeCard';
|
||||
import { UploadContext } from 'context/UploadContext';
|
||||
|
||||
export function useCardContext() {
|
||||
const [state, setState] = useState({
|
||||
init: false,
|
||||
cards: new Map(),
|
||||
});
|
||||
const upload = useContext(UploadContext);
|
||||
const access = useRef(null);
|
||||
const revision = useRef(null);
|
||||
const next = useRef(null);
|
||||
@ -281,11 +283,29 @@ export function useCardContext() {
|
||||
let node = cardProfile.node;
|
||||
await setContactChannelTopicSubject(node, token, channelId, topicId, data);
|
||||
},
|
||||
addChannelTopic: async (cardId, channelId, message, assets) => {
|
||||
addChannelTopic: async (cardId, channelId, message, files) => {
|
||||
let { cardProfile, cardDetail } = cards.current.get(cardId).data;
|
||||
let token = cardProfile.guid + '.' + cardDetail.token;
|
||||
let node = cardProfile.node;
|
||||
await addContactChannelTopic(node, token, channelId, message, assets);
|
||||
if (files?.length) {
|
||||
const topicId = await addContactChannelTopic(node, token, channelId, null, null);
|
||||
upload.actions.addContactTopic(node, token, cardId, channelId, topicId, files, async (assets) => {
|
||||
console.log("success, finalize topic");
|
||||
message.assets = assets;
|
||||
await setContactChannelTopicSubject(node, token, channelId, topicId, message);
|
||||
}, async () => {
|
||||
console.log("failed, delete topic");
|
||||
try {
|
||||
await removeContactChannelTopic(node, token, channelId, topicId);
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
await addContactChannelTopic(node, token, channelId, message, files);
|
||||
}
|
||||
},
|
||||
getChannel: (cardId, channelId) => {
|
||||
let card = cards.current.get(cardId);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
import { useEffect, useState, useRef, useContext } from 'react';
|
||||
import { getChannels } from 'api/getChannels';
|
||||
import { getChannelDetail } from 'api/getChannelDetail';
|
||||
import { getChannelSummary } from 'api/getChannelSummary';
|
||||
@ -13,11 +13,14 @@ import { getChannelTopicAssetUrl } from 'api/getChannelTopicAssetUrl';
|
||||
import { setChannelSubject } from 'api/setChannelSubject';
|
||||
import { setChannelCard } from 'api/setChannelCard';
|
||||
import { clearChannelCard } from 'api/clearChannelCard';
|
||||
import { UploadContext } from 'context/UploadContext';
|
||||
|
||||
export function useChannelContext() {
|
||||
const [state, setState] = useState({
|
||||
init: false,
|
||||
channels: new Map(),
|
||||
});
|
||||
const upload = useContext(UploadContext);
|
||||
const access = useRef(null);
|
||||
const revision = useRef(null);
|
||||
const channels = useRef(new Map());
|
||||
@ -117,8 +120,26 @@ export function useChannelContext() {
|
||||
setChannelTopicSubject: async (channelId, topicId, data) => {
|
||||
return await setChannelTopicSubject(access.current, channelId, topicId, data);
|
||||
},
|
||||
addChannelTopic: async (channelId, message, assets) => {
|
||||
await addChannelTopic(access.current, channelId, message, assets);
|
||||
addChannelTopic: async (channelId, message, files) => {
|
||||
if (files?.length) {
|
||||
const topicId = await addChannelTopic(access.current, channelId, null, null);
|
||||
upload.actions.addTopic(access.current, channelId, topicId, files, async (assets) => {
|
||||
console.log("success, finalize topic");
|
||||
message.assets = assets;
|
||||
await setChannelTopicSubject(access.current, channelId, topicId, message);
|
||||
}, async () => {
|
||||
console.log("failed, delete topic");
|
||||
try {
|
||||
await removeChannelTopic(access.current, channelId, topicId);
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
await addChannelTopic(access.current, channelId, message, files);
|
||||
}
|
||||
},
|
||||
getChannel: (channelId) => {
|
||||
return channels.current.get(channelId);
|
||||
|
173
net/web/src/context/useUploadContext.hook.js
Normal file
173
net/web/src/context/useUploadContext.hook.js
Normal file
@ -0,0 +1,173 @@
|
||||
import { useEffect, useState, useRef, useContext } from 'react';
|
||||
import axios from 'axios';
|
||||
|
||||
export function useUploadContext() {
|
||||
|
||||
const [state, setState] = useState({
|
||||
progress: new Map(),
|
||||
});
|
||||
const channels = useRef(new Map());
|
||||
const index = useRef(0);
|
||||
|
||||
const updateState = (value) => {
|
||||
setState((s) => ({ ...s, ...value }));
|
||||
};
|
||||
|
||||
const updateComplete = (channel, topic) => {
|
||||
let topics = channels.current.get(channel);
|
||||
if (topics) {
|
||||
topics.delete(topic);
|
||||
}
|
||||
updateProgress();
|
||||
}
|
||||
|
||||
const updateProgress = () => {
|
||||
let progress = new Map();
|
||||
channels.current.forEach((topics, channel) => {
|
||||
let assets = [];
|
||||
topics.forEach((entry, topic, map) => {
|
||||
let active = entry.active ? 1 : 0;
|
||||
assets.push({
|
||||
upload: entry.index,
|
||||
topicId: topic,
|
||||
active: entry.active,
|
||||
index: entry.assets.length + active,
|
||||
count: entry.assets.length + entry.files.length + active,
|
||||
error: entry.error,
|
||||
});
|
||||
});
|
||||
if (assets.length) {
|
||||
progress.set(channel, assets.sort((a, b) => (a.upload < b.upload) ? 1 : -1));
|
||||
}
|
||||
updateState({ progress });
|
||||
});
|
||||
}
|
||||
|
||||
const actions = {
|
||||
addTopic: (token, channelId, topicId, files, success, failure) => {
|
||||
const entry = {
|
||||
index: index.current,
|
||||
url: `/content/channels/${channelId}/topics/${topicId}/assets?agent=${token}`,
|
||||
files,
|
||||
assets: [],
|
||||
current: null,
|
||||
error: false,
|
||||
success,
|
||||
failure
|
||||
}
|
||||
index.current += 1;
|
||||
const key = `:${channelId}`;
|
||||
if (!channels.current.has(key)) {
|
||||
channels.current.set(key, new Map());
|
||||
}
|
||||
const topics = channels.current.get(key);
|
||||
topics.set(topicId, entry);
|
||||
upload(entry, updateProgress, () => { updateComplete(key, topicId) } );
|
||||
},
|
||||
cancelTopic: (channelId, topicId) => {
|
||||
},
|
||||
addContactTopic: (server, token, cardId, channelId, topicId, files, success, failure) => {
|
||||
const entry = {
|
||||
index: index.current,
|
||||
url: `https://${server}/content/channels/${channelId}/topics/${topicId}/assets?contact=${token}`,
|
||||
files,
|
||||
assets: [],
|
||||
current: null,
|
||||
error: false,
|
||||
success,
|
||||
failure
|
||||
}
|
||||
index.current += 1;
|
||||
const key = `${cardId}:${channelId}`;
|
||||
if (!channels.current.has(key)) {
|
||||
channels.current.set(key, new Map());
|
||||
}
|
||||
const topics = channels.current.get(key);
|
||||
topics.set(topicId, entry);
|
||||
upload(entry, updateProgress, () => { updateComplete(key, topicId) });
|
||||
},
|
||||
cancelContactTopic: (cardId, channelId, topicId) => {
|
||||
},
|
||||
reset: () => {
|
||||
}
|
||||
}
|
||||
|
||||
return { state, actions }
|
||||
}
|
||||
|
||||
async function upload(entry, update, complete) {
|
||||
if (!entry.files?.length) {
|
||||
entry.success(entry.assets);
|
||||
complete();
|
||||
}
|
||||
else {
|
||||
const file = entry.files.shift();
|
||||
entry.active = {};
|
||||
try {
|
||||
if (file.image) {
|
||||
const formData = new FormData();
|
||||
formData.append('asset', file.image);
|
||||
let transform = encodeURIComponent(JSON.stringify(["ithumb;photo", "icopy;photo"]));
|
||||
let asset = await axios.post(`${entry.url}&transforms=${transform}`, formData, {
|
||||
onUploadProgress: (ev) => {
|
||||
const { loaded, total } = ev;
|
||||
entry.active = { loaded, total }
|
||||
update();
|
||||
},
|
||||
});
|
||||
entry.assets.push({
|
||||
image: {
|
||||
thumb: asset.data.find(item => item.transform === 'ithumb;photo').assetId,
|
||||
full: asset.data.find(item => item.transform === 'icopy;photo').assetId,
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (file.video) {
|
||||
const formData = new FormData();
|
||||
formData.append('asset', file.video);
|
||||
let thumb = 'vthumb;video;' + file.position;
|
||||
let transform = encodeURIComponent(JSON.stringify(["vlq;video", "vhd;video", thumb]));
|
||||
let asset = await axios.post(`${entry.url}&transforms=${transform}`, formData, {
|
||||
onUploadProgress: (ev) => {
|
||||
const { loaded, total } = ev;
|
||||
entry.active = { loaded, total }
|
||||
update();
|
||||
},
|
||||
});
|
||||
entry.assets.push({
|
||||
video: {
|
||||
thumb: asset.data.find(item => item.transform === thumb).assetId,
|
||||
lq: asset.data.find(item => item.transform === 'vlq;video').assetId,
|
||||
hd: asset.data.find(item => item.transform === 'vhd;video').assetId,
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (file.audio) {
|
||||
const formData = new FormData();
|
||||
formData.append('asset', file.audio);
|
||||
let transform = encodeURIComponent(JSON.stringify(["acopy;audio"]));
|
||||
let asset = await axios.post(`${entry.url}&transforms=${transform}`, formData, {
|
||||
onUploadProgress: (ev) => {
|
||||
const { loaded, total } = ev;
|
||||
entry.active = { loaded, total }
|
||||
update();
|
||||
},
|
||||
});
|
||||
entry.assets.push({
|
||||
audio: {
|
||||
label: asset.label,
|
||||
full: asset.data.find(item => item.transform === 'acopy;audio').assetId,
|
||||
}
|
||||
});
|
||||
}
|
||||
entry.active = null;
|
||||
upload(entry, update, complete);
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
entry.failure();
|
||||
entry.error = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2589,6 +2589,14 @@ axe-core@^4.3.5:
|
||||
resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.4.1.tgz"
|
||||
integrity sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw==
|
||||
|
||||
axios@^0.27.2:
|
||||
version "0.27.2"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972"
|
||||
integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==
|
||||
dependencies:
|
||||
follow-redirects "^1.14.9"
|
||||
form-data "^4.0.0"
|
||||
|
||||
axobject-query@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz"
|
||||
@ -4456,6 +4464,11 @@ follow-redirects@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz"
|
||||
integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==
|
||||
|
||||
follow-redirects@^1.14.9:
|
||||
version "1.15.1"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5"
|
||||
integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==
|
||||
|
||||
fork-ts-checker-webpack-plugin@^6.5.0:
|
||||
version "6.5.0"
|
||||
resolved "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.0.tgz"
|
||||
@ -4484,6 +4497,15 @@ form-data@^3.0.0:
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
form-data@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
|
||||
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
forwarded@0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz"
|
||||
|
Loading…
Reference in New Issue
Block a user