slate: back to a stable place

This commit is contained in:
@wwwjim 2020-09-02 23:00:02 -07:00
parent 3054b781c3
commit aa948a60f8
13 changed files with 226 additions and 121 deletions

View File

@ -72,6 +72,13 @@ export const getSlateBySlatename = async (data) => {
});
};
export const getSlateById = async (data) => {
return await returnJSON(`/api/slates/get`, {
...DEFAULT_OPTIONS,
body: JSON.stringify({ data }),
});
};
export const deleteTrustRelationship = async (data) => {
return await returnJSON(`/api/users/trust-delete`, {
...DEFAULT_OPTIONS,

View File

@ -44,6 +44,8 @@ import ApplicationLayout from "~/components/core/ApplicationLayout";
import WebsitePrototypeWrapper from "~/components/core/WebsitePrototypeWrapper";
import Cookies from "universal-cookie";
import { dispatchCustomEvent } from "~/common/custom-events";
const cookies = new Cookies();
const SIDEBARS = {
@ -215,6 +217,11 @@ export default class ApplicationPage extends React.Component {
// NOTE(jim): Rehydrates user.
await this.rehydrate({ resetFiles: true });
dispatchCustomEvent({
name: "remote-update-slate-screen",
detail: {},
});
};
rehydrate = async (options) => {
@ -228,7 +235,7 @@ export default class ApplicationPage extends React.Component {
console.log("REHYDRATION CALL", response);
const updates = {
viewer: response.data,
viewer: JSON.parse(JSON.stringify(response.data)),
};
if (options && options.resetFiles) {
@ -269,8 +276,6 @@ export default class ApplicationPage extends React.Component {
});
}
console.log({ response });
await this.rehydrate();
this._handleDismissSidebar();

View File

@ -220,7 +220,6 @@ export default class Slate extends React.Component {
return (
<div css={STYLES_CONTAINER}>
<ResponsiveReactGridLayout
className="layout"
columns={COLUMN_MAP}
layouts={this.props.layouts}
isDraggable={!!this.props.editing}
@ -235,6 +234,7 @@ export default class Slate extends React.Component {
>
{this.generateDOM()}
</ResponsiveReactGridLayout>
{this.props.editing ? (
<div css={STYLES_ACTIONS}>
<span css={STYLES_ACTION_BUTTON} onClick={this._handleResetLayout}>
@ -242,7 +242,8 @@ export default class Slate extends React.Component {
</span>
<span
css={STYLES_ACTION_BUTTON}
onClick={this._handleSaveLayout}
onMouseUp={this._handleSaveLayout}
onTouchEnd={this._handleSaveLayout}
style={{
backgroundColor:
this.props.saving === "IDLE" ? Constants.system.brand : null,

View File

@ -300,9 +300,12 @@ export default class SlateMediaObjectSidebar extends React.Component {
if (this.props.cid) {
elements.push(
<div css={STYLES_SIDEBAR} style={{ height: "auto" }}>
<div
css={STYLES_SIDEBAR}
key="sidebar-media-open-file"
style={{ height: "auto" }}
>
<a
key="sidebar-media-open-file"
css={STYLES_BUTTON}
href={Strings.getCIDGatewayURL(this.props.cid)}
target="_blank"
@ -310,7 +313,6 @@ export default class SlateMediaObjectSidebar extends React.Component {
Open file in new tab &nbsp;&nbsp;
</a>
<a
key="sidebar-media-download-file"
css={STYLES_BUTTON}
href={Strings.getCIDGatewayURL(this.props.cid)}
target="_blank"

View File

@ -115,6 +115,11 @@ export default class SidebarAddFileToBucket extends React.Component {
}
await this.props.onRehydrate({ resetFiles: true });
dispatchCustomEvent({
name: "remote-update-slate-screen",
detail: {},
});
};
render() {

View File

@ -12,8 +12,8 @@ export default async ({ id }) => {
},
errorFn: async (e) => {
return {
error: "DELETE_TRUSTED_RELATIONSHIP_BY_ID",
source: e,
decorator: "DELETE_TRUSTED_RELATIONSHIP_BY_ID",
error: e,
};
},
});

View File

@ -1,6 +1,5 @@
import * as Utilities from "~/node_common/utilities";
import * as Data from "~/node_common/data";
import * as Powergate from "~/node_common/powergate";
import * as Constants from "~/node_common/constants";
import * as Serializers from "~/node_common/serializers";

View File

@ -2,6 +2,23 @@ import * as Data from "~/node_common/data";
import * as Utilities from "~/node_common/utilities";
import * as Strings from "~/common/strings";
const generateLayout = (items) => {
return items.map((item, i) => {
var y = Math.ceil(Math.random() * 4) + 1;
return {
x: (i * 2) % 10,
y: 0,
w: 2,
h: 2,
minW: 2,
minH: 2,
// NOTE(jim): Library quirk thats required.
i: i.toString(),
};
});
};
export default async (req, res) => {
if (Strings.isEmpty(req.body.data.cid)) {
return res
@ -62,15 +79,16 @@ export default async (req, res) => {
const slates = await Data.getSlatesByUserId({ userId: id });
for (let i = 0; i < slates.length; i++) {
const slate = slates[i];
await Data.updateSlateById({
const objects = slate.data.objects.filter(
(o) => !o.url.includes(req.body.data.cid)
);
const layouts = await Data.updateSlateById({
id: slate.id,
updated_at: new Date(),
data: {
...slate.data,
objects: slate.data.objects.filter(
(o) => !o.url.includes(req.body.data.cid)
),
objects,
layouts: { lg: generateLayout(objects) },
},
});
}

View File

@ -2,6 +2,23 @@ import * as Constants from "~/node_common/constants";
import * as Utilities from "~/node_common/utilities";
import * as Data from "~/node_common/data";
const generateLayout = (items) => {
return items.map((item, i) => {
var y = Math.ceil(Math.random() * 4) + 1;
return {
x: (i * 2) % 10,
y: 0,
w: 2,
h: 2,
minW: 2,
minH: 2,
// NOTE(jim): Library quirk thats required.
i: i.toString(),
};
});
};
export default async (req, res) => {
const id = Utilities.getIdFromCookie(req);
if (!id) {
@ -57,19 +74,8 @@ export default async (req, res) => {
},
];
let layouts = slate.data.layouts;
if (layouts) {
const keys = Object.keys(slate.data.layouts);
for (let i = 0; i < keys.length; i++) {
layouts[keys[i]].push({
x: (objects.length * 2) % 10,
y: 0,
w: 2,
h: 2,
i: `${objects.length}`.toString(),
});
}
}
// TODO(jim): Preserve layouts when adding.
let layouts = { lg: generateLayout(objects) };
const update = await Data.updateSlateById({
id: slate.id,

46
pages/api/slates/get.js Normal file
View File

@ -0,0 +1,46 @@
import * as Utilities from "~/node_common/utilities";
import * as Data from "~/node_common/data";
import * as Strings from "~/common/strings";
export default async (req, res) => {
const id = Utilities.getIdFromCookie(req);
if (!id) {
return res.status(500).send({ decorator: "SERVER_GET_SLATE", error: true });
}
const user = await Data.getUserById({
id,
});
if (!user) {
return res.status(404).send({
decorator: "SERVER_GET_SLATE_USER_NOT_FOUND",
error: true,
});
}
if (user.error) {
return res.status(500).send({
decorator: "SERVER_GET_SLATE_USER_NOT_FOUND",
error: true,
});
}
const response = await Data.getSlateById({ id: req.body.data.id });
if (!response) {
return res
.status(404)
.send({ decorator: "SERVER_GET_SLATE_NOT_FOUND", error: true });
}
if (response.error) {
return res
.status(500)
.send({ decorator: "SERVER_GET_SLATE_NOT_FOUND", error: true });
}
return res
.status(200)
.send({ decorator: "SERVER_UPDATE_SLATE", slate: response });
};

View File

@ -4,6 +4,23 @@ import * as LibraryManager from "~/node_common/managers/library";
import * as Strings from "~/common/strings";
import * as Upload from "~/node_common/upload";
const generateLayout = (items) => {
return items.map((item, i) => {
var y = Math.ceil(Math.random() * 4) + 1;
return {
x: (i * 2) % 10,
y: 0,
w: 2,
h: 2,
minW: 2,
minH: 2,
// NOTE(jim): Library quirk thats required.
i: i.toString(),
};
});
};
// NOTE(jim): To support multipart request.
export const config = {
api: {
@ -121,21 +138,8 @@ export default async (req, res) => {
};
const objects = [...slate.data.objects, newSlateObjectEntity];
let layouts = slate.data.layouts;
if (layouts) {
const keys = Object.keys(slate.data.layouts);
for (let i = 0; i < keys.length; i++) {
layouts[keys[i]].push({
x: (objects.length * 2) % 10,
y: 0,
w: 2,
h: 2,
minW: 2,
minH: 2,
i: `${objects.length}`.toString(),
});
}
}
// TODO(jim): Preserve layouts when adding.
let layouts = { lg: generateLayout(objects) };
const updatedSlate = await Data.updateSlateById({
id: slate.id,

View File

@ -31,12 +31,7 @@ const moveIndex = (set, fromIndex, toIndex) => {
const setStateData = (source) => {
return {
name: source.data.name,
username: source.owner ? source.owner.username : null,
slatename: source.slatename,
public: source.data.public,
objects: source.data.objects,
body: source.data.body,
layouts: source.data.layouts
? source.data.layouts
: { lg: generateLayout(source.data.objects) },
@ -44,6 +39,9 @@ const setStateData = (source) => {
};
export default class SceneSlate extends React.Component {
_timeout = null;
_remoteLock = false;
state = {
...setStateData(this.props.current, this.props.viewer),
loading: false,
@ -51,39 +49,61 @@ export default class SceneSlate extends React.Component {
editing: this.props.current.data.ownerId === this.props.viewer.id,
};
// NOTE(jim):
// The purpose of this is to update the Scene appropriately when
// it changes but isn't mounted.
componentDidUpdate(prevProps) {
if (prevProps.current.id !== this.props.current.id) {
this.setState({
...setStateData(this.props.current, this.props.viewer),
loading: false,
saving: "IDLE",
editing: this.props.current.data.ownerId === this.props.viewer.id,
});
this._handleUpdateCarousel({
objects: this.props.current.data.objects,
editing: this.state.editing,
});
}
}
componentDidMount() {
this._handleUpdateCarousel(this.state);
window.addEventListener(
"remote-update-slate-screen",
this._handleRemoteUpdate
);
}
// NOTE(jim):
// When we are swapping Scenes.
componentDidUpdate(prevProps) {
const oldDate = prevProps.current.updated_at;
const newDate = this.props.current.updated_at;
const updated = newDate > oldDate;
componentWillUnmount() {
window.removeEventListener(
"update-current-slate",
this._handleRemoteUpdate
);
}
if (!updated) {
return null;
}
_handleRemoteUpdate = async ({ detail }) => {
if (!this._remoteLock) {
this._remoteLock = true;
const response = await Actions.getSlateById({
id: this.props.current.id,
});
let editing;
this.props.viewer.slates.forEach((slate) => {
if (slate.id === this.props.current.slateId) {
editing = true;
if (!response || response.error) {
this._remoteLock = false;
return;
}
});
this.setState({
...setStateData(this.props.current, this.props.viewer),
loading: false,
saving: "SAVED",
editing,
});
this.setState({ layouts: null, objects: null });
this._handleUpdateCarousel({
objects: this.props.current.data.objects,
});
}
const { slate } = response;
console.log(slate);
await this._handleSave(null, slate.data.objects, slate.data.layouts);
this._remoteLock = false;
}
};
_handleUpdate = async (e) => {
let response = await this.props.onRehydrate();
@ -110,7 +130,7 @@ export default class SceneSlate extends React.Component {
};
_handleChangeLayout = async (layout, layouts) => {
this.setState({ layouts });
this.setState({ layouts, saving: "IDLE" });
};
_handleSaveLayout = async () => {
@ -124,16 +144,14 @@ export default class SceneSlate extends React.Component {
};
_handleSave = async (e, objects, layouts) => {
this.setState({ loading: true });
this.setState({ loading: true, saving: "SAVING" });
const response = await Actions.updateSlate({
id: this.props.current.id,
data: {
name: this.props.current.data.name,
objects: objects ? objects : this.state.objects,
layouts: layouts ? layouts : this.state.layouts,
public: this.state.public,
body: this.state.body,
name: this.state.name,
},
});
@ -149,7 +167,16 @@ export default class SceneSlate extends React.Component {
await this.props.onRehydrate();
this._handleUpdateCarousel(this.state);
this.setState({
saving: "SAVED",
layouts: layouts ? layouts : this.state.layouts,
objects: objects ? objects : this.state.objects,
});
this._handleUpdateCarousel({
objects: objects ? objects : this.state.objects,
editing: this.state.editing,
});
};
_handleObjectSave = async (object) => {
@ -166,8 +193,6 @@ export default class SceneSlate extends React.Component {
}
}
this.setState({ objects });
await this._handleSave(null, objects);
System.dispatchCustomEvent({
@ -223,24 +248,14 @@ export default class SceneSlate extends React.Component {
return o.id !== id;
});
// NOTE(jim): Every time we remove an object from a slate.
// We will want to remove the object from the layouts too.
const keys = Object.keys(this.state.layouts);
let layouts = this.state.layouts;
for (let j = 0; j < keys.length; j++) {
layouts[keys[j]] = layouts[keys[j]].filter((each, i) => {
return i !== index;
});
}
// TODO(jim): This is a brute force way to handle this.
const layouts = { lg: generateLayout(objects) };
const response = await Actions.updateSlate({
id: this.props.current.slateId,
data: {
name: this.props.current.data.name,
objects,
layouts,
public: this.state.public,
body: this.state.body,
name: this.state.name,
},
});
@ -260,9 +275,10 @@ export default class SceneSlate extends React.Component {
alert(`TODO: ${response.decorator}`);
}
this._handleUpdateCarousel({ objects, editing: this.state.editing });
this.setState({ layouts: null, objects: null });
await this.props.onRehydrate();
this._handleUpdateCarousel(this.state);
this.setState({ layouts, objects });
System.dispatchCustomEvent({
name: "state-global-carousel-loading",
@ -293,13 +309,9 @@ export default class SceneSlate extends React.Component {
};
render() {
const {
username,
slatename,
objects,
body = "A slate.",
name,
} = this.state;
const { username, slatename, data, name } = this.props.current;
const { body = "A slate." } = data;
const { objects, layouts } = this.state;
return (
<ScenePage style={{ padding: `88px 24px 128px 24px` }}>
@ -357,17 +369,18 @@ export default class SceneSlate extends React.Component {
>
<ProcessedText text={body} />
</ScenePageHeader>
<Slate
key={slatename}
editing={this.state.editing}
saving={this.state.saving}
items={objects}
layouts={this.state.layouts}
onLayoutChange={this._handleChangeLayout}
onLayoutSave={this._handleSaveLayout}
onMoveIndex={this._handleMoveIndex}
onSelect={this._handleSelect}
/>
{layouts ? (
<Slate
editing={this.state.editing}
saving={this.state.saving}
items={objects}
layouts={layouts}
onLayoutChange={this._handleChangeLayout}
onLayoutSave={this._handleSaveLayout}
onMoveIndex={this._handleMoveIndex}
onSelect={this._handleSelect}
/>
) : null}
</ScenePage>
);
}

View File

@ -50,7 +50,9 @@ export default class SceneSlates extends React.Component {
const slates = this.props.viewer.slates.map((each) => {
return {
...each,
url: `https://slate.host/${this.props.viewer.username}/${each.slatename}`,
url: `https://slate.host/${this.props.viewer.username}/${
each.slatename
}`,
public: each.data.public,
objects: <span css={STYLES_NUMBER}>{each.data.objects.length}</span>,
};
@ -75,11 +77,6 @@ export default class SceneSlates extends React.Component {
</div>
));
// TODO(jim): Refactor later.
const slateButtons = [
{ name: "Create slate", type: "SIDEBAR", value: "SIDEBAR_CREATE_SLATE" },
];
console.log(this.props);
return (
<ScenePage>
<ScenePageHeader
@ -101,8 +98,9 @@ export default class SceneSlates extends React.Component {
value={this.state.tab}
onChange={(value) => this.setState({ tab: value })}
/>
{this.state.tab === 0 ? (
this.props.data.children.length ? (
this.props.data && this.props.data.children.length ? (
this.props.data.children.map((slate) => (
<div
key={slate.id}
@ -124,6 +122,7 @@ export default class SceneSlates extends React.Component {
</EmptyState>
)
) : null}
{this.state.tab === 1 ? (
subscriptions.length ? (
subscriptions