optimistic updates

This commit is contained in:
Martina 2020-12-14 17:43:16 -08:00
parent 91c47f0776
commit e849324486
16 changed files with 153 additions and 96 deletions

View File

@ -69,6 +69,7 @@ export const init = ({ resource = "", viewer, onUpdate }) => {
}
if (type === "UPDATE" && onUpdate) {
console.log("websocket on update");
onUpdate(data);
}
});

View File

@ -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;
}

View File

@ -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 (
<WebsitePrototypeWrapper
@ -630,12 +634,12 @@ export default class ApplicationPage extends React.Component {
viewer: this.state.viewer,
selected: this.state.selected,
onSelectedChange: this._handleSelectedChange,
onViewerChange: this._handleViewerChange,
onAction: this._handleAction,
onUpload: this._handleUploadFiles,
onBack: this._handleBack,
onForward: this._handleForward,
onUpdateData: this._handleUpdateData,
onUpdateViewer: this._handleUpdateViewer,
sceneId: current.target.id,
mobile: this.state.mobile,
resources: this.props.resources,
@ -654,6 +658,7 @@ export default class ApplicationPage extends React.Component {
onCancel: this._handleDismissSidebar,
onUpload: this._handleUploadFiles,
onAction: this._handleAction,
onUpdateViewer: this._handleUpdateViewer,
resources: this.props.resources,
});
}
@ -682,6 +687,7 @@ export default class ApplicationPage extends React.Component {
{scene}
</ApplicationLayout>
<GlobalCarousel
onUpdateViewer={this._handleUpdateViewer}
resources={this.props.resources}
viewer={this.state.viewer}
current={

View File

@ -289,22 +289,30 @@ export default class CarouselSidebarData extends React.Component {
}, 1000);
};
_handleDelete = async (cid) => {
_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() {

View File

@ -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() {

View File

@ -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) => {

View File

@ -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 }) => {

View File

@ -140,9 +140,7 @@ export class SlatePicker extends React.Component {
onClick={() => this.props.onAdd(slate)}
>
<div css={STYLES_ICON_BOX}>
{this.props.loading && this.props.loading === slate.id ? (
<LoaderSpinner style={{ height: 20, width: 20, margin: "2px 8px 2px 2px" }} />
) : selected[slate.id] ? (
{selected[slate.id] ? (
<SVG.Slate
height="24px"
style={{

View File

@ -57,7 +57,8 @@ export default class SidebarAddFileToSlate extends React.Component {
};
_handleSubmit = async () => {
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() {

View File

@ -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 = () => {

View File

@ -392,6 +392,7 @@ export class GlobalCarousel extends React.Component {
<CarouselSidebarData
viewer={this.props.viewer}
display={this.state.showSidebar && !isUnityGame ? "block" : "none"}
onUpdateViewer={this.props.onUpdateViewer}
onClose={this._handleClose}
key={data.id}
saving={this.state.saving}
@ -405,6 +406,9 @@ export class GlobalCarousel extends React.Component {
) : (
<CarouselSidebarSlate
display={this.state.showSidebar && !isUnityGame ? "block" : "none"}
viewer={this.props.viewer}
onUpdateViewer={this.props.onUpdateViewer}
current={this.props.current}
key={data.id}
saving={this.state.saving}
loading={this.state.loading}

View File

@ -99,9 +99,11 @@ export default async (req, res) => {
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) {

View File

@ -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}
/>
) : (
<EmptyState>

View File

@ -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`

View File

@ -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 = (
<div css={STYLES_BUTTONS}>
{followStatus ? (
{this.state.isFollowing ? (
<ButtonSecondary style={{ marginRight: 8 }} onClick={this._handleFollow}>
Unfollow
</ButtonSecondary>

View File

@ -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}