diff --git a/net/server/internal/store/schema.go b/net/server/internal/store/schema.go index a60b3195..27d052fb 100644 --- a/net/server/internal/store/schema.go +++ b/net/server/internal/store/schema.go @@ -238,6 +238,7 @@ type Topic struct { Created int64 `gorm:"autoCreateTime"` Updated int64 `gorm:"autoUpdateTime"` TagRevision int64 `gorm:"not null"` + Account Account Channel *Channel Assets []Asset Tags []Tag diff --git a/net/server/internal/transcodeUtil.go b/net/server/internal/transcodeUtil.go index 82a22826..c47a3d7d 100644 --- a/net/server/internal/transcodeUtil.go +++ b/net/server/internal/transcodeUtil.go @@ -1,7 +1,6 @@ package databag import ( - "time" "os" "io" "hash/crc32" @@ -106,7 +105,6 @@ func transcodeAsset(asset *store.Asset) { cmd.Stdout = &stdout var stderr bytes.Buffer cmd.Stderr = &stderr -time.Sleep(time.Second); if err := cmd.Run(); err != nil { LogMsg(stdout.String()) @@ -138,7 +136,7 @@ time.Sleep(time.Second); func UpdateAsset(asset *store.Asset, status string, crc uint32, size int64) (err error) { - act := asset.Account + topic := store.Topic{}; err = store.DB.Transaction(func(tx *gorm.DB) error { asset.Crc = crc asset.Size = size @@ -146,19 +144,22 @@ func UpdateAsset(asset *store.Asset, status string, crc uint32, size int64) (err if res := tx.Model(store.Asset{}).Where("id = ?", asset.ID).Updates(asset).Error; res != nil { return res } - if res := tx.Model(&asset.Topic).Update("detail_revision", act.ChannelRevision + 1).Error; res != nil { + if res := tx.Preload("Account").Preload("TopicSlot").Preload("Channel.ChannelSlot").First(&topic, asset.Topic.ID).Error; res != nil { + return res; + } + if res := tx.Model(&topic).Update("detail_revision", topic.Account.ChannelRevision + 1).Error; res != nil { return res } - if res := tx.Model(&asset.Topic.TopicSlot).Update("revision", act.ChannelRevision + 1).Error; res != nil { + if res := tx.Model(&topic.TopicSlot).Update("revision", topic.Account.ChannelRevision + 1).Error; res != nil { return res } - if res := tx.Model(&asset.Channel).Update("topic_revision", act.ChannelRevision + 1).Error; res != nil { + if res := tx.Model(&topic.Channel).Update("topic_revision", topic.Account.ChannelRevision + 1).Error; res != nil { return res } - if res := tx.Model(&asset.Channel.ChannelSlot).Update("revision", act.ChannelRevision + 1).Error; res != nil { + if res := tx.Model(&topic.Channel.ChannelSlot).Update("revision", topic.Account.ChannelRevision + 1).Error; res != nil { return res } - if res := tx.Model(&act).Update("channel_revision", act.ChannelRevision + 1).Error; res != nil { + if res := tx.Model(&topic.Account).Update("channel_revision", topic.Account.ChannelRevision + 1).Error; res != nil { return res } return nil @@ -169,19 +170,19 @@ func UpdateAsset(asset *store.Asset, status string, crc uint32, size int64) (err // determine affected contact list cards := make(map[string]store.Card) - for _, card := range asset.Channel.Cards { + for _, card := range topic.Channel.Cards { cards[card.Guid] = card } - for _, group := range asset.Channel.Groups { + for _, group := range topic.Channel.Groups { for _, card := range group.Cards { cards[card.Guid] = card } } // notify - SetStatus(&act) + SetStatus(&topic.Account) for _, card := range cards { - SetContactChannelNotification(&act, &card) + SetContactChannelNotification(&topic.Account, &card) } return diff --git a/net/web/src/User/Conversation/TopicItem/TopicItem.jsx b/net/web/src/User/Conversation/TopicItem/TopicItem.jsx index e2e6a400..d8955779 100644 --- a/net/web/src/User/Conversation/TopicItem/TopicItem.jsx +++ b/net/web/src/User/Conversation/TopicItem/TopicItem.jsx @@ -4,6 +4,7 @@ import ReactResizeDetector from 'react-resize-detector'; import { useTopicItem } from './useTopicItem.hook'; import { Avatar } from 'avatar/Avatar'; import { CommentOutlined } from '@ant-design/icons'; +import { Carousel } from 'Carousel/Carousel'; export function TopicItem({ topic }) { @@ -13,7 +14,14 @@ export function TopicItem({ topic }) { let nameClass = state.name ? 'set' : 'unset'; let d = new Date(); let offset = d.getTime() / 1000 - state.created; - + + const renderAsset = (asset) => { + if (asset.image) { + return + } + return <> + } + return (
@@ -27,7 +35,8 @@ export function TopicItem({ topic }) {
-
{ state.message }
+ +
{ state.message?.text }
) diff --git a/net/web/src/User/Conversation/TopicItem/useTopicItem.hook.js b/net/web/src/User/Conversation/TopicItem/useTopicItem.hook.js index bc0cba2e..42b09158 100644 --- a/net/web/src/User/Conversation/TopicItem/useTopicItem.hook.js +++ b/net/web/src/User/Conversation/TopicItem/useTopicItem.hook.js @@ -13,6 +13,9 @@ export function useTopicItem(topic) { imageUrl: null, message: null, created: null, + status: null, + transform: null, + assets: [], }); const profile = useContext(ProfileContext); @@ -24,10 +27,19 @@ export function useTopicItem(topic) { } useEffect(() => { + + const { status, transform, data } = topic.data.topicDetail; let message; - if( topic.data.topicDetail.status === 'confirmed') { + let assets = []; + if (status === 'confirmed') { try { - message = JSON.parse(topic.data.topicDetail.data).text; + message = JSON.parse(data); + if (transform === 'complete') { + if (message.assets) { + assets = message.assets; + delete message.assets; + } + } } catch(err) { console.log(err); @@ -38,16 +50,19 @@ export function useTopicItem(topic) { const { guid, created } = topic.data.topicDetail; if (profile.state.profile.guid == guid) { const { name, handle, imageUrl } = profile.actions.getProfile(); - updateState({ name, handle, imageUrl, message, created }); + updateState({ name, handle, imageUrl, status, message, transform, assets, created }); } else { const { name, handle, imageUrl } = card.actions.getCardProfileByGuid(guid); - updateState({ name, handle, imageUrl, message, created }); + updateState({ name, handle, imageUrl, status, message, transform, assets, created }); } } }, [profile, card, conversation, topic]); const actions = { + getAssetUrl: (assetId) => { + return conversation.actions.getAssetUrl(topic.id, assetId); + } }; return { state, actions }; diff --git a/net/web/src/api/addChannelTopic.js b/net/web/src/api/addChannelTopic.js index bae5f84e..15341a32 100644 --- a/net/web/src/api/addChannelTopic.js +++ b/net/web/src/api/addChannelTopic.js @@ -2,31 +2,72 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; export async function addChannelTopic(token, channelId, message, assets ) { - let subject = { data: JSON.stringify(message, (key, value) => { - if (value !== null) return value - }), datatype: 'superbasictopic' }; - if (assets == null || assets.length == 0) { + let subject = { data: JSON.stringify(message, (key, value) => { + if (value !== null) return value + }), datatype: 'superbasictopic' }; + let topic = await fetchWithTimeout(`/content/channels/${channelId}/topics?agent=${token}&confirm=true`, { method: 'POST', body: JSON.stringify(subject) }); checkResponse(topic); } else { + let topic = await fetchWithTimeout(`/content/channels/${channelId}/topics?agent=${token}`, { method: 'POST', body: JSON.stringify({}) }); checkResponse(topic); let slot = await topic.json(); // add each asset + message.assets = []; for (let asset of assets) { - const formData = new FormData(); - formData.append('asset', asset.image); - let transform = encodeURIComponent(JSON.stringify(["ithumb;photo"])); - let topicAsset = await fetch(`/content/channels/${channelId}/topics/${slot.id}/assets?transforms=${transform}&agent=${token}`, { method: 'POST', body: formData }); - checkResponse(topicAsset); -console.log(await topicAsset.json()); + 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(); + message.assets.push({ + 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 transform = encodeURIComponent(JSON.stringify(["vthumb;video", "vcopy;video"])); + 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(); + message.assets.push({ + image: { + thumb: assetEntry.find(item => item.transform === 'vthumb;video').assetId, + full: assetEntry.find(item => item.transform === 'vcopy;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(); + message.assets.push({ + image: { + full: assetEntry.find(item => item.transform === 'acopy;audio').assetId, + } + }); + } } + let subject = { data: JSON.stringify(message, (key, value) => { + if (value !== null) return value + }), datatype: 'superbasictopic' }; + let unconfirmed = await fetchWithTimeout(`/content/channels/${channelId}/topics/${slot.id}/subject?agent=${token}`, { method: 'PUT', body: JSON.stringify(subject) }); checkResponse(unconfirmed); diff --git a/net/web/src/api/getChannelTopicAssetUrl.js b/net/web/src/api/getChannelTopicAssetUrl.js new file mode 100644 index 00000000..c20fb234 --- /dev/null +++ b/net/web/src/api/getChannelTopicAssetUrl.js @@ -0,0 +1,4 @@ +export function getChannelTopicAssetUrl(token, channelId, topicId, assetId) { + return `/content/channels/${channelId}/topics/${topicId}/assets/${assetId}?agent=${token}` +} + diff --git a/net/web/src/api/getContactChannelTopicAssetUrl.js b/net/web/src/api/getContactChannelTopicAssetUrl.js new file mode 100644 index 00000000..6051f74b --- /dev/null +++ b/net/web/src/api/getContactChannelTopicAssetUrl.js @@ -0,0 +1,4 @@ +export function getContactChannelTopicAssetUrl(server, token, channelId, topicId, assetId) { + return `https://${server}/content/channels/${channelId}/topics/${topicId}/assets/${assetId}?contact=${token}` +} + diff --git a/net/web/src/context/useCardContext.hook.js b/net/web/src/context/useCardContext.hook.js index 251b1f3b..f7ec578d 100644 --- a/net/web/src/context/useCardContext.hook.js +++ b/net/web/src/context/useCardContext.hook.js @@ -15,6 +15,7 @@ import { getCardCloseMessage } from 'api/getCardCloseMessage'; import { setCardCloseMessage } from 'api/setCardCloseMessage'; import { getContactChannelTopics } from 'api/getContactChannelTopics'; import { getContactChannelTopic } from 'api/getContactChannelTopic'; +import { getContactChannelTopicAssetUrl } from 'api/getContactChannelTopicAssetUrl'; import { addCard } from 'api/addCard'; import { removeCard } from 'api/removeCard'; @@ -249,6 +250,12 @@ export function useCardContext() { setCardCloseMessage: async (server, message) => { return await setCardCloseMessage(server, message); }, + getContactChannelTopicAssetUrl: (cardId, channelId, topicId, assetId) => { + let card = cards.current.get(cardId); + let node = card.data.cardProfile.node; + let token = card.data.cardProfile.guid + "." + card.data.cardDetail.token; + return getContactChannelTopicAssetUrl(node, token, channelId, topicId, assetId); + } } return { state, actions } diff --git a/net/web/src/context/useChannelContext.hook.js b/net/web/src/context/useChannelContext.hook.js index 710f7b70..2c1ef8db 100644 --- a/net/web/src/context/useChannelContext.hook.js +++ b/net/web/src/context/useChannelContext.hook.js @@ -5,6 +5,7 @@ import { addChannel } from 'api/addChannel'; import { addChannelTopic } from 'api/addChannelTopic'; import { getChannelTopics } from 'api/getChannelTopics'; import { getChannelTopic } from 'api/getChannelTopic'; +import { getChannelTopicAssetUrl } from 'api/getChannelTopicAssetUrl'; export function useChannelContext() { const [state, setState] = useState({ @@ -91,6 +92,9 @@ export function useChannelContext() { getChannelTopic: async (channelId, topicId) => { return await getChannelTopic(access.current, channelId, topicId); }, + getChannelTopicAssetUrl: (channelId, topicId, assetId) => { + return getChannelTopicAssetUrl(access.current, channelId, topicId, assetId); + } } return { state, actions } diff --git a/net/web/src/context/useConversationContext.hook.js b/net/web/src/context/useConversationContext.hook.js index 75ebcf7b..1d724f26 100644 --- a/net/web/src/context/useConversationContext.hook.js +++ b/net/web/src/context/useConversationContext.hook.js @@ -13,6 +13,7 @@ export function useConversationContext() { const channel = useContext(ChannelContext); const topics = useRef(new Map()); const revision = useRef(null); + const count = useRef(0); const conversationId = useRef(null); const updateState = (value) => { @@ -21,12 +22,11 @@ export function useConversationContext() { const setTopics = async () => { const { cardId, channelId } = conversationId.current; - + if (cardId) { let rev = card.actions.getChannelRevision(cardId, channelId); if (revision.current != rev) { let delta = await card.actions.getChannelTopics(cardId, channelId, revision.current); - console.log(delta); for (let topic of delta) { if (topic.data == null) { topics.current.delete(topic.id); @@ -37,7 +37,7 @@ export function useConversationContext() { cur = { id: topic.id, data: {} }; } if (topic.data.detailRevision != cur.data.detailRevision) { - if(topic.data.topicDetail != null) { + if(topic.data.topicDetail) { cur.data.topicDetail = topic.data.topicDetail; cur.data.detailRevision = topic.data.detailRevision; } @@ -108,11 +108,20 @@ export function useConversationContext() { return; } - try { - setTopics(); + if (count.current == 0) { + count.current += 1; + while(count.current > 0) { + try { + await setTopics(); + } + catch (err) { + console.log(err); + } + count.current -= 1; + } } - catch (err) { - console.log(err); + else { + count.current += 1; } }; @@ -129,6 +138,15 @@ export function useConversationContext() { topics.current = new Map(); updateState({ init: false, topics: topics.current }); updateConversation(); + }, + getAssetUrl: (topicId, assetId) => { + const { cardId, channelId } = conversationId.current; + if (conversationId.current.cardId) { + return card.actions.getContactChannelTopicAssetUrl(cardId, channelId, topicId, assetId); + } + else { + return channel.actions.getChannelTopicAssetUrl(channelId, topicId, assetId); + } } }