From e849324486f68639123649291e84f21d62e3e323 Mon Sep 17 00:00:00 2001 From: Martina Date: Mon, 14 Dec 2020 17:43:16 -0800 Subject: [PATCH] optimistic updates --- common/browser-websockets.js | 1 + common/user-behaviors.js | 2 +- components/core/Application.js | 38 +++++++----- components/core/CarouselSidebarData.js | 22 ++++--- components/core/CarouselSidebarSlate.js | 30 ++++++---- components/core/DataView.js | 16 +++-- components/core/SlateLayout.js | 27 ++++++++- components/core/SlatePicker.js | 4 +- components/sidebars/SidebarAddFileToSlate.js | 6 +- .../sidebars/SidebarSingleSlateSettings.js | 17 +++++- .../system/components/GlobalCarousel.js | 4 ++ pages/api/slates/update.js | 8 ++- scenes/SceneFilesFolder.js | 1 + scenes/SceneHome.js | 1 - scenes/SceneProfile.js | 60 +++++++------------ scenes/SceneSlate.js | 12 +++- 16 files changed, 153 insertions(+), 96 deletions(-) diff --git a/common/browser-websockets.js b/common/browser-websockets.js index 3e0be5ef..8e8b9391 100644 --- a/common/browser-websockets.js +++ b/common/browser-websockets.js @@ -69,6 +69,7 @@ export const init = ({ resource = "", viewer, onUpdate }) => { } if (type === "UPDATE" && onUpdate) { + console.log("websocket on update"); onUpdate(data); } }); diff --git a/common/user-behaviors.js b/common/user-behaviors.js index 449afbbc..5f144073 100644 --- a/common/user-behaviors.js +++ b/common/user-behaviors.js @@ -183,7 +183,7 @@ export const deleteFiles = async (fileCids, fileIds = [], noAlert) => { return false; } - Events.dispatchMessage({ message: "Files successfully deleted!", status: "INFO" }); + // Events.dispatchMessage({ message: "Files successfully deleted!", status: "INFO" }); return response; } diff --git a/components/core/Application.js b/components/core/Application.js index 54aa56d9..95669be1 100644 --- a/components/core/Application.js +++ b/components/core/Application.js @@ -147,11 +147,24 @@ export default class ApplicationPage extends React.Component { } _handleUpdateViewer = (newViewerState) => { - if (this.state.viewer && newViewerState.id && newViewerState.id === this.state.viewer.id) { - this.setState({ - viewer: { ...this.state.viewer, ...newViewerState, type: "VIEWER" }, - }); - } + // let setAsyncState = (newState) => + // new Promise((resolve) => + // this.setState( + // { + // viewer: { ...this.state.viewer, ...newState, type: "VIEWER" }, + // }, + // resolve + // ) + // ); + // await setAsyncState(newViewerState); + + this.setState({ + viewer: { ...this.state.viewer, ...newViewerState, type: "VIEWER" }, + }); + }; + + _handleUpdateData = ({ data }) => { + this.setState({ data }); }; _handleSetupWebsocket = async () => { @@ -424,12 +437,6 @@ export default class ApplicationPage extends React.Component { return false; }; - _handleViewerChange = (e) => { - this.setState({ - viewer: { ...this.state.viewer, [e.target.name]: e.target.value }, - }); - }; - _handleSelectedChange = (e) => { this.setState({ selected: { ...this.state.selected, [e.target.name]: e.target.value }, @@ -484,10 +491,6 @@ export default class ApplicationPage extends React.Component { return alert(JSON.stringify(options)); }; - _handleUpdateData = ({ data }) => { - this.setState({ data }); - }; - _handleNavigateTo = (next, data = null, redirect = false) => { if (next.id) { window.history.replaceState( @@ -582,6 +585,7 @@ export default class ApplicationPage extends React.Component { render() { // NOTE(jim): Not authenticated. + console.log(this.state.viewer); if (!this.state.viewer) { return ( { + _handleDelete = (cid) => { const message = `Are you sure you want to delete this? It will be deleted from your slates as well`; if (!window.confirm(message)) { return; } - await this.setState({ loading: cid }); + let library = this.props.viewer.library; + library[0].children = library[0].children.filter((obj) => obj.cid !== cid); + this.props.onUpdateViewer({ library }); + + // await this.setState({ loading: cid }); // NOTE(jim): Accepts ID as well if CID can't be found. // Since our IDS are unique. - await UserBehaviors.deleteFiles(cid, this.props.data.id); - this.setState({ loading: false }); + UserBehaviors.deleteFiles(cid, this.props.data.id); + // this.setState({ loading: false }); }; _handleAdd = async (slate) => { - await this.setState({ pickerLoading: slate.id }); + this.setState({ + selected: { ...this.state.selected, [slate.id]: !this.state.selected[slate.id] }, + // pickerLoading: null, + }); + // await this.setState({ pickerLoading: slate.id }); if (this.state.selected[slate.id]) { await UserBehaviors.removeFromSlate({ slate, ids: [this.props.data.id] }); } else { @@ -314,10 +322,6 @@ export default class CarouselSidebarData extends React.Component { fromSlate: this.props.fromSlate, }); } - this.setState({ - selected: { ...this.state.selected, [slate.id]: !this.state.selected[slate.id] }, - pickerLoading: null, - }); }; render() { diff --git a/components/core/CarouselSidebarSlate.js b/components/core/CarouselSidebarSlate.js index ba5cf5d5..e73da334 100644 --- a/components/core/CarouselSidebarSlate.js +++ b/components/core/CarouselSidebarSlate.js @@ -286,8 +286,9 @@ export default class CarouselSidebarSlate extends React.Component { this.setState({ loading: false }); }; - _handleDelete = async (cid) => { + _handleDelete = (cid) => { if (this.props.external || !this.props.isOwner) return; + if ( !window.confirm( "Are you sure you want to delete this? It will be removed from all your slates too." @@ -295,13 +296,22 @@ export default class CarouselSidebarSlate extends React.Component { ) { return; } - console.log(this.props.data); - await this.setState({ loading: "deleting" }); + let slates = this.props.viewer.slates; + let slateId = this.props.current.id; + for (let slate of slates) { + if (slate.id === slateId) { + slate.data.objects = slate.data.objects.filter((obj) => obj.cid !== cid); + this.props.onUpdateViewer({ slates }); + break; + } + } + + // await this.setState({ loading: "deleting" }); // NOTE(jim): Accepts ID as well if CID can't be found. // Since our IDS are unique. - await UserBehaviors.deleteFiles(cid, this.props.data.id); - this.setState({ loading: false }); + UserBehaviors.deleteFiles(cid, this.props.data.id); + // this.setState({ loading: false }); }; _toggleAccordion = (tab) => { @@ -309,7 +319,11 @@ export default class CarouselSidebarSlate extends React.Component { }; _handleAdd = async (slate) => { - await this.setState({ pickerLoading: slate.id }); + this.setState({ + selected: { ...this.state.selected, [slate.id]: !this.state.selected[slate.id] }, + // pickerLoading: null, + }); + // await this.setState({ pickerLoading: slate.id }); if (this.state.selected[slate.id]) { await UserBehaviors.removeFromSlate({ slate, ids: [this.props.data.id] }); } else { @@ -319,10 +333,6 @@ export default class CarouselSidebarSlate extends React.Component { fromSlate: this.props.fromSlate, }); } - this.setState({ - selected: { ...this.state.selected, [slate.id]: !this.state.selected[slate.id] }, - pickerLoading: null, - }); }; render() { diff --git a/components/core/DataView.js b/components/core/DataView.js index 6c5bc8a7..b78eb3d7 100644 --- a/components/core/DataView.js +++ b/components/core/DataView.js @@ -341,7 +341,7 @@ export default class DataView extends React.Component { return; }; - _handleDelete = async (cid, id) => { + _handleDelete = (cid, id) => { const message = `Are you sure you want to delete these files? They will be deleted from your slates as well`; if (!window.confirm(message)) { return; @@ -365,10 +365,16 @@ export default class DataView extends React.Component { }); } - await this._handleLoading({ cids }); - await UserBehaviors.deleteFiles(cids, ids); - this._handleLoading({ cids }); - await this.setState({ checked: {} }); + let library = this.props.viewer.library; + library[0].children = library[0].children.filter( + (obj) => !ids.includes(obj.id) && !cids.includes(obj.cid) + ); + this.props.onUpdateViewer({ library }); + + // await this._handleLoading({ cids }); + UserBehaviors.deleteFiles(cids, ids); + // this._handleLoading({ cids }); + this.setState({ checked: {} }); }; _handleSelect = (index) => { diff --git a/components/core/SlateLayout.js b/components/core/SlateLayout.js index 8e1275be..1a7deb2a 100644 --- a/components/core/SlateLayout.js +++ b/components/core/SlateLayout.js @@ -1037,6 +1037,17 @@ export class SlateLayout extends React.Component { } this.setState({ checked: {} }); } + + let slates = this.props.viewer.slates; + let slateId = this.props.current.id; + for (let slate of slates) { + if (slate.id === slateId) { + slate.data.objects = slate.data.objects.filter((obj) => !ids.includes(obj.id)); + this.props.onUpdateViewer({ slates }); + break; + } + } + UserBehaviors.removeFromSlate({ slate: this.props.current, ids }); }; @@ -1063,10 +1074,22 @@ export class SlateLayout extends React.Component { } } - await this._handleLoading({ cids }); + let slates = this.props.viewer.slates; + let slateId = this.props.current.id; + for (let slate of slates) { + if (slate.id === slateId) { + slate.data.objects = slate.data.objects.filter( + (obj) => !ids.includes(obj.id) && !cids.includes(obj.cid) + ); + this.props.onUpdateViewer({ slates }); + break; + } + } + + // await this._handleLoading({ cids }); await UserBehaviors.deleteFiles(cids, ids); this._handleLoading({ cids }); - await this.setState({ checked: {} }); + this.setState({ checked: {} }); }; _handleLoading = ({ cids }) => { diff --git a/components/core/SlatePicker.js b/components/core/SlatePicker.js index 6d84efd0..5e0876b3 100644 --- a/components/core/SlatePicker.js +++ b/components/core/SlatePicker.js @@ -140,9 +140,7 @@ export class SlatePicker extends React.Component { onClick={() => this.props.onAdd(slate)} >
- {this.props.loading && this.props.loading === slate.id ? ( - - ) : selected[slate.id] ? ( + {selected[slate.id] ? ( { - await this.setState({ loading: true }); + this.props.onCancel(); + // await this.setState({ loading: true }); for (let slate of Object.values(this.state.selected)) { if (!slate) continue; await UserBehaviors.addToSlate({ @@ -66,8 +67,7 @@ export default class SidebarAddFileToSlate extends React.Component { fromSlate: this.props.sidebarData.fromSlate, }); } - this.setState({ loading: false }); - this.props.onCancel(); + // this.setState({ loading: false }); }; render() { diff --git a/components/sidebars/SidebarSingleSlateSettings.js b/components/sidebars/SidebarSingleSlateSettings.js index 2bc382bd..43cb3d4b 100644 --- a/components/sidebars/SidebarSingleSlateSettings.js +++ b/components/sidebars/SidebarSingleSlateSettings.js @@ -53,8 +53,20 @@ export default class SidebarSingleSlateSettings extends React.Component { }; _handleSubmit = async () => { - this.setState({ loading: true }); + // this.setState({ loading: true }); + let slates = this.props.viewer.slates; + for (let slate of slates) { + if (slate.id === this.props.current.id) { + slate.data.name = this.state.name; + slate.data.public = this.state.public; + slate.data.body = this.state.body; + this.props.onUpdateViewer({ slates }); + break; + } + } + + this.props.onCancel(); const response = await Actions.updateSlate({ id: this.props.current.id, data: { @@ -65,10 +77,9 @@ export default class SidebarSingleSlateSettings extends React.Component { }); if (Events.hasError(response)) { - this.setState({ loading: false }); + // this.setState({ loading: false }); return; } - this.props.onCancel(); }; _handleCancel = () => { diff --git a/components/system/components/GlobalCarousel.js b/components/system/components/GlobalCarousel.js index b0385830..f434a899 100644 --- a/components/system/components/GlobalCarousel.js +++ b/components/system/components/GlobalCarousel.js @@ -392,6 +392,7 @@ export class GlobalCarousel extends React.Component { { return res.status(500).send({ decorator: "SERVER_UPDATE_SLATE", error: true }); } - let slates = await Data.getSlatesByUserId({ userId: id }); - if (slates) { - ViewerManager.hydratePartialSlates(slates, id); + if (!layoutOnly && isOwner) { + let slates = await Data.getSlatesByUserId({ userId: id }); + if (slates) { + ViewerManager.hydratePartialSlates(slates, id); + } } if (!layoutOnly && !req.body.data.autoSave) { diff --git a/scenes/SceneFilesFolder.js b/scenes/SceneFilesFolder.js index 83e22e36..629fd253 100644 --- a/scenes/SceneFilesFolder.js +++ b/scenes/SceneFilesFolder.js @@ -39,6 +39,7 @@ export default class SceneFilesFolder extends React.Component { onAction={this.props.onAction} viewer={this.props.viewer} items={this.props.viewer.library[0].children} + onUpdateViewer={this.props.onUpdateViewer} /> ) : ( diff --git a/scenes/SceneHome.js b/scenes/SceneHome.js index 9364c46e..4c22692c 100644 --- a/scenes/SceneHome.js +++ b/scenes/SceneHome.js @@ -11,7 +11,6 @@ import { css } from "@emotion/react"; import ScenePage from "~/components/core/ScenePage"; import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview"; -import DataView from "~/components/core/DataView"; import ScenePageHeader from "~/components/core/ScenePageHeader"; const STYLES_VIDEO_BIG = css` diff --git a/scenes/SceneProfile.js b/scenes/SceneProfile.js index ecf18fa6..d5de50eb 100644 --- a/scenes/SceneProfile.js +++ b/scenes/SceneProfile.js @@ -13,14 +13,26 @@ const STYLES_BUTTONS = css` align-items: center; `; -const STATUS_BUTTON_MAP = { - trusted: "Remove peer", - untrusted: "Add peer", - sent: "Cancel request", - received: "Accept request", -}; - export default class SceneProfile extends React.Component { + state = { + isFollowing: !!this.props.viewer.subscriptions.filter((entry) => { + return entry.target_user_id === this.props.data.id; + }).length, + }; + + componentDidUpdate = (prevProps) => { + if ( + this.props.data.id !== prevProps.data.id || + this.props.viewer.subscriptions !== prevProps.viewer.subscriptions + ) { + this.setState({ + isFollowing: !!this.props.viewer.subscriptions.filter((entry) => { + return entry.target_user_id === this.props.data.id; + }).length, + }); + } + }; + // _handleTrust = async (trustStatus, trustId) => { // if (trustStatus === "untrusted" || trustStatus === "sent") { // await Actions.createTrustRelationship({ @@ -38,46 +50,16 @@ export default class SceneProfile extends React.Component { // }; _handleFollow = async () => { + this.setState({ isFollowing: !this.state.isFollowing }); await Actions.createSubscription({ userId: this.props.data.id, }); }; render() { - let trustId, followStatus, relation; - // let trustStatus = "untrusted"; - let viewer = this.props.viewer; - // let trust = viewer.trusted.filter((entry) => { - // return entry.target_user_id === this.props.data.id; - // }); - // if (trust.length) { - // relation = trust[0]; - // trustId = relation.id; - // if (relation.data.verified) { - // trustStatus = "trusted"; - // } else { - // trustStatus = "sent"; - // } - // } - // let pendingTrust = viewer.pendingTrusted.filter((entry) => { - // return entry.owner_user_id === this.props.data.id; - // }); - // if (pendingTrust.length) { - // relation = pendingTrust[0]; - // trustId = relation.id; - // if (pendingTrust[0].data.verified) { - // trustStatus = "trusted"; - // } else { - // trustStatus = "received"; - // } - // } - followStatus = !!viewer.subscriptions.filter((entry) => { - return entry.target_user_id === this.props.data.id; - }).length; - let buttons = (
- {followStatus ? ( + {this.state.isFollowing ? ( Unfollow diff --git a/scenes/SceneSlate.js b/scenes/SceneSlate.js index f2749d81..4dd44ca3 100644 --- a/scenes/SceneSlate.js +++ b/scenes/SceneSlate.js @@ -141,6 +141,15 @@ export default class SceneSlate extends React.Component { data.layouts = layouts; } if (preview) { + let slates = this.props.viewer.slates; + let slateId = this.props.current.id; + for (let slate of slates) { + if (slate.id === slateId) { + slate.data.preview = preview; + break; + } + } + this.props.onUpdateViewer({ slates }); data.preview = preview; } const response = await Actions.updateSlate({ @@ -196,7 +205,6 @@ export default class SceneSlate extends React.Component { render() { const { user, data } = this.props.current; const { body = "", preview } = data; - console.log(body); let objects = this.props.current.data.objects; let layouts = this.props.current.data.layouts; const isPublic = data.public; @@ -313,10 +321,12 @@ export default class SceneSlate extends React.Component { : "" } current={this.props.current} + onUpdateViewer={this.props.onUpdateViewer} viewer={this.props.viewer} slateId={this.props.current.id} layout={layouts && layouts.ver === "2.0" ? layouts.layout || [] : null} onSaveLayout={this._handleSaveLayout} + onSave={this._handleSave} isOwner={this.state.isOwner} fileNames={layouts && layouts.ver === "2.0" ? layouts.fileNames : false} preview={preview}