Merge pull request #398 from filecoin-project/@martinalong/search-v2

Replaced rehydrate with webhooks
This commit is contained in:
CAKE 2020-10-31 14:15:14 -07:00 committed by GitHub
commit 71fd7f4ae7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 668 additions and 486 deletions

View File

@ -77,6 +77,7 @@ PUBSUB_SECRET=pKLO4lbzdMrhAFKwPo9bnmq03bxQrtu3
RESOURCE_URI_UPLOAD=http://localhost:4242
RESOURCE_URI_STORAGE_UPLOAD=http://localhost:4242
RESOURCE_URI_PUBSUB=ws://localhost:6464
RESOURCE_URI_SEARCH=http://localhost:1313
```
### Setup pubsub server

View File

@ -13,6 +13,12 @@ const DEFAULT_OPTIONS = {
credentials: "include",
};
const CORS_OPTIONS = {
method: "POST",
headers: REQUEST_HEADERS,
credentials: "omit",
};
const returnJSON = async (route, options) => {
const response = await fetch(route, options);
const json = await response.json();
@ -120,8 +126,8 @@ export const search = async (data) => {
return { decorator: "NO_SERVER_TRIP", data: { results: [] } };
}
return await returnJSON(`/api/search/${data.query}`, {
...DEFAULT_OPTIONS,
return await returnJSON(`http://localhost:1313/${data.query}`, {
...CORS_OPTIONS,
body: JSON.stringify(data),
});
};

View File

@ -37,7 +37,7 @@ export const init = ({ resource = "", viewer, onUpdate }) => {
}, 30000 + 1000);
});
client.addEventListener("message", function(event) {
client.addEventListener("message", function (event) {
if (!client) {
return;
}

View File

@ -2,6 +2,7 @@ import * as Actions from "~/common/actions";
import * as Store from "~/common/store";
import * as Constants from "~/common/constants";
import * as Credentials from "~/common/credentials";
import * as Strings from "~/common/strings";
import { dispatchCustomEvent } from "~/common/custom-events";
import { encode } from "blurhash";
@ -212,12 +213,7 @@ export const uploadToSlate = async ({ responses, slate }) => {
skipped = addResponse.skipped;
}
}
let message = `${added || 0} file${added !== 1 ? "s" : ""} uploaded to slate. `;
if (skipped) {
message += `${skipped || 0} duplicate / existing file${
added !== 1 ? "s were" : " was"
} skipped.`;
}
let message = Strings.formatAsUploadMessage(added, skipped, true);
dispatchCustomEvent({
name: "create-alert",
detail: {

View File

@ -103,6 +103,7 @@ export const error = {
CREATE_PENDING_ERROR: "We ran into issues while uploading your data, please try again later",
PROCESS_PENDING_ERROR:
"We ran into an error while updating your uploaded data. Please try again later",
PROCESS_PENDING_USER_NOT_FOUND: "Please log in to upload files",
//Data CID Status
NO_CIDS_TO_CHECK: "There are no CIDs to check",

View File

@ -87,6 +87,16 @@ export const getCIDFromIPFS = (url) => {
return cid;
};
export const formatAsUploadMessage = (added, skipped, slate = false) => {
let message = `${added || 0} file${added !== 1 ? "s" : ""} uploaded${slate ? " to slate" : ""}. `;
if (skipped) {
message += `${skipped || 0} duplicate / existing file${
added !== 1 ? "s were" : " was"
} skipped.`;
}
return message;
};
export const formatAsFilecoinConversion = (number) => {
const filecoinNumber = new FilecoinNumber(`${number}`, "attofil");
//const inAttoFil = filecoinNumber.toAttoFil();

View File

@ -68,13 +68,8 @@ const STYLES_MESSAGE = css`
`;
const STYLES_TEXT = css`
border-bottom: 1px solid ${Constants.system.white};
max-width: ${Constants.sizes.mobile}px;
width: 100%;
@supports ((-webkit-backdrop-filter: blur(25px)) or (backdrop-filter: blur(25px))) {
border-bottom: 1px solid ${Constants.system.brand};
}
`;
const STYLES_MESSAGE_BOX = css`

View File

@ -180,9 +180,10 @@ export default class ApplicationPage extends React.Component {
}
}
//make this a function that can handle different types of inputs and updates state accordingly (rather than ahving a bunch of different functions for each type)
_handleUpdateViewer = (newViewerState) => {
console.log("update viewer");
console.log({ newViewerState });
if (this.state.viewer && newViewerState.id && newViewerState.id === this.state.viewer.id) {
this.setState({
viewer: { ...this.state.viewer, ...newViewerState, type: "VIEWER" },
@ -425,39 +426,11 @@ export default class ApplicationPage extends React.Component {
return;
}
// TODO(jim): Remove this once the viewer is being broadcasted for
// this case.
await this.rehydrate({ resetFiles: true });
// //NOTE(martina): to update the carousel to include the new file if you're on the data view page
// dispatchCustomEvent({
// name: "remote-update-carousel",
// detail: {},
// });
//NOTE(martina): to update the slate carousel to include the new file if you're on a slate page
// const navigation = NavigationData.generate(this.state.viewer);
// const next = this.state.history[this.state.currentIndex];
// const current = NavigationData.getCurrentById(navigation, next.id);
// if (
// current.target &&
// current.target.slateId &&
// this.state.viewer.slates.map((slate) => slate.id).includes(current.target.slateId)
// ) {
// dispatchCustomEvent({
// name: "remote-update-slate-screen",
// detail: {},
// });
// }
this.setState({ sidebar: null });
if (!slate) {
const { added, skipped } = processResponse.data;
let message = `${added || 0} file${added !== 1 ? "s" : ""} uploaded. `;
if (skipped) {
message += `${skipped || 0} duplicate / existing file${
added !== 1 ? "s were" : " was"
} skipped.`;
}
let message = Strings.formatAsUploadMessage(added, skipped);
dispatchCustomEvent({
name: "create-alert",
detail: {
@ -527,6 +500,7 @@ export default class ApplicationPage extends React.Component {
_handleSidebarLoading = (sidebarLoading) => this.setState({ sidebarLoading });
//change the naem to hydrate. and use this only upon initial sign in (should be the only time you need it)
rehydrate = async (options) => {
const response = await Actions.hydrateAuthenticatedUser();
@ -587,10 +561,6 @@ export default class ApplicationPage extends React.Component {
});
}
// TODO(jim): Remove this once the viewer is being broadcasted for
// this case.
await this.rehydrate();
this._handleDismissSidebar();
return response;
@ -666,8 +636,6 @@ export default class ApplicationPage extends React.Component {
});
}
// TODO(jim): Remove this once the viewer is being broadcasted for
// this case.
await this.rehydrate();
let wsclient = Websockets.getClient();
@ -893,7 +861,6 @@ export default class ApplicationPage extends React.Component {
</WebsitePrototypeWrapper>
);
}
// NOTE(jim): Authenticated.
const navigation = NavigationData.generate(this.state.viewer);
const next = this.state.history[this.state.currentIndex];
@ -924,7 +891,6 @@ export default class ApplicationPage extends React.Component {
currentIndex={this.state.currentIndex}
history={this.state.history}
onAction={this._handleAction}
onRehydrate={this.rehydrate}
onBack={this._handleBack}
onForward={this._handleForward}
onSignOut={this._handleSignOut}
@ -944,7 +910,6 @@ export default class ApplicationPage extends React.Component {
onUpload: this._handleUploadFiles,
onBack: this._handleBack,
onForward: this._handleForward,
onRehydrate: this.rehydrate,
sceneId: current.target.id,
mobile: this.state.mobile,
resources: this.props.resources,
@ -967,7 +932,6 @@ export default class ApplicationPage extends React.Component {
onUpload: this._handleUploadFiles,
onSidebarLoading: this._handleSidebarLoading,
onAction: this._handleAction,
onRehydrate: this.rehydrate,
resources: this.props.resources,
});
}
@ -1002,7 +966,6 @@ export default class ApplicationPage extends React.Component {
? current.target
: this.state.data //NOTE(martina): for slates that are not your own
}
onRehydrate={this.rehydrate}
slates={this.state.viewer.slates}
onAction={this._handleAction}
mobile={this.props.mobile}

View File

@ -140,14 +140,7 @@ export default class ApplicationHeader extends React.Component {
_handleCreateSearch = (e) => {
dispatchCustomEvent({
name: "create-modal",
detail: { modal: <SearchModal onAction={this.props.onAction} /> },
});
};
_handleRehydrate = (e) => {
this.setState({ isRefreshing: true }, async () => {
await this.props.onRehydrate();
this.setState({ isRefreshing: false });
detail: { modal: <SearchModal viewer={this.props.viewer} onAction={this.props.onAction} /> },
});
};
@ -193,16 +186,6 @@ export default class ApplicationHeader extends React.Component {
<SVG.NavigationArrow height="24px" />
</span>
</span>
<span css={STYLES_MOBILE_HIDDEN}>
<span
css={this.state.isRefreshing ? STYLES_ROTATION : STYLES_STATIC}
style={{ marginLeft: 24 }}
>
<span css={STYLES_ICON_ELEMENT} onClick={this._handleRehydrate}>
<SVG.Refresh height="20px" />
</span>
</span>
</span>
<span css={STYLES_MOBILE_HIDDEN}>
<span
css={STYLES_ICON_ELEMENT}

View File

@ -278,7 +278,6 @@ export default class CarouselSidebarSlate extends React.Component {
selected: { ...this.state.selected, [slate.id]: true },
});
}
this.props.onRehydrate();
};
_handleDownload = () => {
@ -326,7 +325,13 @@ export default class CarouselSidebarSlate extends React.Component {
});
return;
}
this.props.onRehydrate();
let message = Strings.formatAsUploadMessage(response.data.added, response.data.skipped);
dispatchCustomEvent({
name: "create-alert",
detail: {
alert: { message, status: !response.data.added ? null : "INFO" },
},
});
this.setState({ loading: false });
};
@ -363,8 +368,8 @@ export default class CarouselSidebarSlate extends React.Component {
this.setState({ loading: false });
return;
}
await this.props.onRehydrate();
this.setState({ loading: false });
console.log("got here to end of handle delete");
};
_toggleAccordion = (tab) => {

View File

@ -243,7 +243,6 @@ export default class DataView extends React.Component {
if (windowBottom >= docHeight - 600) {
this.setState({ viewLimit: this.state.viewLimit + 30 });
}
console.log(e);
};
_handleCheckScroll = Window.debounce(this._handleScroll, 200);
@ -300,7 +299,6 @@ export default class DataView extends React.Component {
dispatchCustomEvent({ name: "state-global-carousel-loading", detail: { loading: false } });
return;
}
await this.props.onRehydrate();
this._handleLoading({ cids });
this.setState({ checked: {} });
dispatchCustomEvent({
@ -357,12 +355,7 @@ export default class DataView extends React.Component {
}
const { added, skipped } = addResponse;
let message = `${added || 0} file${added !== 1 ? "s" : ""} uploaded. `;
if (skipped) {
message += `${skipped || 0} duplicate / existing file${
added !== 1 ? "s were" : " was"
} skipped.`;
}
let message = Strings.formatAsUploadMessage(added, skipped, true);
dispatchCustomEvent({
name: "create-alert",
detail: {
@ -370,7 +363,6 @@ export default class DataView extends React.Component {
},
});
await this.props.onRehydrate();
System.dispatchCustomEvent({
name: "state-global-carousel-loading",
detail: { loading: false },
@ -422,8 +414,6 @@ export default class DataView extends React.Component {
return null;
}
await this.props.onRehydrate();
System.dispatchCustomEvent({
name: "state-global-carousel-loading",
detail: { loading: false },

View File

@ -70,10 +70,7 @@ const UserEntry = ({ item }) => {
return (
<div css={STYLES_ENTRY}>
<div css={STYLES_USER_ENTRY_CONTAINER}>
<div
style={{ backgroundImage: `url(${item.data.photo})` }}
css={STYLES_PROFILE_IMAGE}
/>
<div style={{ backgroundImage: `url(${item.data.photo})` }} css={STYLES_PROFILE_IMAGE} />
{item.data.name ? <div css={STYLES_TITLE}>{item.data.name}</div> : null}
<div css={STYLES_TITLE}>@{item.username}</div>
</div>
@ -132,18 +129,14 @@ const FileEntry = ({ item }) => {
<SVG.Folder height="16px" />
</div>
<div css={STYLES_TITLE}>
{item.data.file.title || item.data.file.name}
{item.data.file.title || item.data.file.name || item.data.file.file}
</div>
{item.data.slate.owner && item.data.slate.owner.username ? (
{item.data.slate && item.data.slate.owner && item.data.slate.owner.username ? (
<div css={STYLES_TITLE}>@{item.data.slate.owner.username}</div>
) : null}
</div>
<div css={STYLES_SLATE_IMAGES_CONTAINER}>
<SlatePreviewRow
numItems={1}
slate={{ data: { objects: [item.data.file] } }}
small
/>
<SlatePreviewRow numItems={1} slate={{ data: { objects: [item.data.file] } }} small />
</div>
</div>
);
@ -154,10 +147,12 @@ export class SearchModal extends React.Component {
loading: true,
results: [],
inputValue: "",
filters: { slates: true },
};
componentDidMount = async () => {
await this.fillDirectory();
// await this.fillPersonalDirectory();
this.setState({ loading: false });
};
@ -165,19 +160,9 @@ export class SearchModal extends React.Component {
const response = await Actions.getNetworkDirectory();
this.miniSearch = new MiniSearch({
fields: ["slatename", "data.name", "username", "filename"],
storeFields: [
"type",
"slatename",
"username",
"data",
"id",
"slates",
"owner",
],
storeFields: ["type", "slatename", "username", "data", "id", "slates", "owner"],
extractField: (entry, fieldName) => {
return fieldName
.split(".")
.reduce((doc, key) => doc && doc[key], entry);
return fieldName.split(".").reduce((doc, key) => doc && doc[key], entry);
},
searchOptions: {
fuzzy: 0.15,
@ -199,16 +184,72 @@ export class SearchModal extends React.Component {
);
}
}
this.users = response.data.users;
this.slates = response.data.slates;
this.miniSearch.addAll(response.data.users);
this.miniSearch.addAll(response.data.slates);
this.miniSearch.addAll(files);
}
};
fillPersonalDirectory = () => {
this.personalSearch = new MiniSearch({
fields: ["slatename", "data.name", "username", "filename"],
storeFields: ["type", "slatename", "username", "data", "id", "slates", "owner"],
extractField: (entry, fieldName) => {
return fieldName.split(".").reduce((doc, key) => doc && doc[key], entry);
},
searchOptions: {
fuzzy: 0.15,
},
});
let files = this.props.viewer.library[0].children.map((file, i) => {
return {
type: "DATA_FILE",
id: file.id,
filename: file.name || file.file,
data: {
file: {
...file,
url: `${Constants.gateways.ipfs}/${file.cid || file.ipfs.replace("/ipfs/", "")}`,
},
index: i,
},
};
});
let slates = this.props.viewer.slates.map((slate) => {
return { ...slate, type: "SLATE", owner: this.props.viewer };
});
this.personalSearch.addAll(slates);
let userIds = [];
this.personalSearch.addAll(this.props.viewer.pendingTrusted.map((trust) => trust.owner));
userIds.push(...this.props.viewer.pendingTrusted.map((trust) => trust.owner.id));
this.personalSearch.addAll(
this.props.viewer.trusted
.filter((trust) => !userIds.includes(trust.user.id))
.map((trust) => trust.user)
);
userIds.push(...this.props.viewer.trusted.map((trust) => trust.user.id));
this.personalSearch.addAll(
this.props.viewer.subscriptions
.filter((sub) => sub.target_user_id && !userIds.includes(sub.user.id))
.map((sub) => sub.user)
);
this.personalSearch.addAll(files);
};
_handleChange = (e) => {
this.setState({ inputValue: e.target.value });
//interpret the thing with handleInterpret (and change shownValue accordingly)
};
//converts input to search filter options
_handleInterpret = (query) => {
if (query.contains("files:")) {
} else if (query.contains("users:")) {
} else if (query.contains("slates:")) {
} else if (query.contains("tags:")) {
} else if (query.contains("#")) {
} else if (query.contains("@")) {
}
};
_handleSearch = () => {
@ -230,6 +271,63 @@ export class SearchModal extends React.Component {
}
};
// _handleSearch = async () => {
// if (this.state.loading) return;
// if (this.state.filters["my"]) {
// let miniSearch = this.personalSearch;
// let filter;
// if (this.state.filters["files"]) {
// filter = {
// filter: (result) => {
// return result.type === "FILE" || result.type === "DATA_FILE";
// },
// };
// } else if (this.state.filters["users"]) {
// filter = {
// filter: (result) => result.type === "USER",
// };
// } else if (this.state.filters["slates"]) {
// filter = {
// filter: (result) => result.type === "SLATE",
// };
// }
// let searchResults;
// if (filter) {
// searchResults = miniSearch.search(this.state.inputValue, filter);
// } else {
// searchResults = miniSearch.search(this.state.inputValue);
// }
// let ids = new Set();
// for (let result of searchResults) {
// ids.add(result.id);
// }
// let autofill = miniSearch.autoSuggest(this.state.inputValue);
// for (let i = 0; i < autofill.length; i++) {
// let result;
// if (Object.keys(this.state.filter).length) {
// result = miniSearch.search(autofill[i].suggestion, this.state.filter);
// } else {
// result = miniSearch.search(autofill[i].suggestion);
// }
// if (result && result.length) {
// result = result[0];
// if (!ids.has(result.id)) {
// ids.add(result.id);
// searchResults.push(result);
// }
// }
// }
// this.setState({ results: searchResults });
// } else {
// const results = await Actions.search({
// query: this.state.inputValue,
// filter: this.state.filters,
// });
// this.setState({ results: results.data.results });
// }
// };
_handleSelect = async (value) => {
if (value.type === "SLATE") {
this.props.onAction({
@ -245,6 +343,16 @@ export class SearchModal extends React.Component {
data: value.data,
});
}
if (value.type === "DATA_FILE") {
await this.props.onAction({
type: "NAVIGATE",
value: "data",
});
dispatchCustomEvent({
name: "slate-global-open-carousel",
detail: { index: value.data.data.index },
});
}
if (value.type === "FILE") {
await this.props.onAction({
type: "NAVIGATE",
@ -281,10 +389,10 @@ export class SearchModal extends React.Component {
},
component: <SlateEntry item={item} />,
});
} else if (item.type === "FILE") {
} else if (item.type === "FILE" || item.type === "DATA_FILE") {
results.push({
value: {
type: "FILE",
type: item.type,
data: item,
},
component: <FileEntry item={item} />,
@ -301,7 +409,7 @@ export class SearchModal extends React.Component {
onSelect={this._handleSelect}
onChange={this._handleChange}
onSearch={this._handleSearch}
inputValue={this.state.inputValue}
inputValue={this.state.shownValue}
style={STYLES_SEARCH_DROPDOWN}
/>
</div>

View File

@ -89,12 +89,7 @@ export default class SidebarAddFileToSlate extends React.Component {
}
const { added, skipped } = addResponse;
let message = `${added || 0} file${added !== 1 ? "s" : ""} uploaded. `;
if (skipped) {
message += `${skipped || 0} duplicate / existing file${
added !== 1 ? "s were" : " was"
} skipped.`;
}
let message = Strings.formatAsUploadMessage(added, skipped, true);
dispatchCustomEvent({
name: "create-alert",
detail: {
@ -102,7 +97,6 @@ export default class SidebarAddFileToSlate extends React.Component {
},
});
}
await this.props.onRehydrate();
this.props.onCancel();
};

View File

@ -117,12 +117,7 @@ export default class SidebarCreateSlate extends React.Component {
}
const { added, skipped } = addResponse;
let message = `${added || 0} file${added !== 1 ? "s" : ""} uploaded. `;
if (skipped) {
message += `${skipped || 0} duplicate / existing file${
added !== 1 ? "s were" : " was"
} skipped.`;
}
let message = Strings.formatAsUploadMessage(added, skipped, true);
if (message) {
dispatchCustomEvent({
name: "create-alert",
@ -131,8 +126,6 @@ export default class SidebarCreateSlate extends React.Component {
},
});
}
await this.props.onRehydrate();
}
this.setState({ loading: false });

View File

@ -65,15 +65,13 @@ export default class SidebarSingleSlateSettings extends React.Component {
body: this.state.body,
},
});
console.log(response);
if (!response) {
dispatchCustomEvent({
name: "create-alert",
detail: {
alert: {
message:
"We're having trouble connecting right now. Please try again later",
message: "We're having trouble connecting right now. Please try again later",
},
},
});
@ -87,8 +85,7 @@ export default class SidebarSingleSlateSettings extends React.Component {
});
return this.setState({ loading: false });
}
await this.props.onSubmit({});
this.props.onCancel();
};
_handleCancel = () => {
@ -103,9 +100,7 @@ export default class SidebarSingleSlateSettings extends React.Component {
this.setState({ loading: true });
if (
!window.confirm(
"Are you sure you want to delete this Slate? This action is irreversible."
)
!window.confirm("Are you sure you want to delete this Slate? This action is irreversible.")
) {
return this.setState({ loading: false });
}
@ -119,8 +114,7 @@ export default class SidebarSingleSlateSettings extends React.Component {
name: "create-alert",
detail: {
alert: {
message:
"We're having trouble connecting right now. Please try again later",
message: "We're having trouble connecting right now. Please try again later",
},
},
});
@ -139,8 +133,6 @@ export default class SidebarSingleSlateSettings extends React.Component {
type: "NAVIGATE",
value: "V1_NAVIGATION_SLATES",
});
return await this.props.onRehydrate();
};
render() {
@ -182,19 +174,14 @@ export default class SidebarSingleSlateSettings extends React.Component {
marginTop: 12,
}}
>
Changing the slatename will change your public slate URL. Your slate
URL is:{" "}
Changing the slatename will change your public slate URL. Your slate URL is:{" "}
</System.P>
<System.P
style={{
marginTop: 12,
}}
>
<a
href={url}
target="_blank"
style={{ color: Constants.system.brand }}
>
<a href={url} target="_blank" style={{ color: Constants.system.brand }}>
https://slate.host{url}
</a>
</System.P>
@ -236,11 +223,7 @@ export default class SidebarSingleSlateSettings extends React.Component {
</System.P>
<div css={STYLES_IMAGE_BOX} style={{ marginTop: 24 }}>
<img
src={preview}
alt=""
style={{ maxWidth: "368px", maxHeight: "368px" }}
/>
<img src={preview} alt="" style={{ maxWidth: "368px", maxHeight: "368px" }} />
</div>
</div>
@ -252,20 +235,12 @@ export default class SidebarSingleSlateSettings extends React.Component {
? "Public. Anyone can search for and view this slate."
: "Private. Only you can view this slate."}
</System.P>
<System.Toggle
name="public"
onChange={this._handleChange}
active={this.state.public}
/>
<System.Toggle name="public" onChange={this._handleChange} active={this.state.public} />
</div>
</div>
<div style={{ marginTop: 40 }}>
<System.ButtonPrimary
full
onClick={this._handleSubmit}
loading={this.state.loading}
>
<System.ButtonPrimary full onClick={this._handleSubmit} loading={this.state.loading}>
Save changes
</System.ButtonPrimary>
@ -284,11 +259,7 @@ export default class SidebarSingleSlateSettings extends React.Component {
{!this.state.loading ? (
<div style={{ marginTop: 48 }}>
<System.ButtonWarning
full
onClick={this._handleDelete}
style={{ overflow: "hidden" }}
>
<System.ButtonWarning full onClick={this._handleDelete} style={{ overflow: "hidden" }}>
Delete{" "}
{this.props.current.data && this.props.current.data.name
? this.props.current.data.name

View File

@ -302,7 +302,6 @@ export class GlobalCarousel extends React.Component {
detail: { alert: { decorator: response.decorator } },
});
}
this.props.onRehydrate();
this.setState({ loading: false, saving: false });
};
@ -347,7 +346,7 @@ export class GlobalCarousel extends React.Component {
this.state.index < this.props.viewer.library[0].children.length
) {
data = this.props.viewer.library[0].children[this.state.index];
data.url = `${Constants.gateways.ipfs}/${data.cid || Strings.getCIDFromIPFS(data.ipfs)}`;
data.url = `${Constants.gateways.ipfs}/${data.cid || data.ipfs.replace("/ipfs/", "")}`;
}
if (!data) {
this._handleClose();
@ -398,7 +397,6 @@ export class GlobalCarousel extends React.Component {
saving={this.state.saving}
loading={this.state.loading}
slates={this.props.slates}
onRehydrate={this.props.onRehydrate}
onAction={this.props.onAction}
data={data}
cid={data.cid}
@ -411,7 +409,6 @@ export class GlobalCarousel extends React.Component {
loading={this.state.loading}
slates={this.props.slates}
onClose={this._handleClose}
onRehydrate={this.props.onRehydrate}
onAction={this.props.onAction}
data={data}
external={this.props.external}

View File

@ -1,5 +1,4 @@
import * as Data from "~/node_common/data";
import * as LibraryManager from "~/node_common/managers/library";
import { runQuery } from "~/node_common/data/utilities";
@ -7,32 +6,9 @@ export default async ({ owner_user_id }) => {
return await runQuery({
label: "REMOVE_PENDING_DATA_FOR_USER",
queryFn: async (DB) => {
const user = await Data.getUserById({
id: owner_user_id,
});
const pending = await DB.from("pending")
.where({ owner_user_id })
.returning("*")
.del();
let cids = [];
let noRepeats = [];
for (let entry of pending) {
if (cids.includes(entry.data.ipfs)) continue;
cids.push(entry.data.ipfs);
noRepeats.push(entry.data);
}
const pending = await DB.from("pending").where({ owner_user_id }).returning("*").del();
const { updatedUserDataFields, added, skipped } = LibraryManager.addData({
user,
files: noRepeats,
});
await Data.updateUserById({
id: user.id,
data: updatedUserDataFields,
});
return { added, skipped };
return pending;
},
errorFn: async (e) => {
return {

View File

@ -41,3 +41,4 @@ export const TEXTILE_SLACK_WEBHOOK_KEY = process.env.TEXTILE_SLACK_WEBHOOK_KEY;
export const RESOURCE_URI_UPLOAD = process.env.RESOURCE_URI_UPLOAD;
export const RESOURCE_URI_STORAGE_UPLOAD = process.env.RESOURCE_URI_STORAGE_UPLOAD;
export const RESOURCE_URI_PUBSUB = process.env.RESOURCE_URI_PUBSUB;
export const RESOURCE_URI_SEARCH = process.env.RESOURCE_URI_SEARCH;

View File

@ -21,9 +21,37 @@ const delay = async (waitMs) => {
Websocket.create();
//NOTE(martina): type = "UPDATE" will be processed by slate and lens. type = "SEARCH" will only be processed by lens
const websocketSend = async (type, data) => {
if (Strings.isEmpty(Environment.PUBSUB_SECRET)) {
return;
}
const ws = Websocket.get();
const encryptedData = await Utilities.encryptWithSecret(
JSON.stringify(data),
Environment.PUBSUB_SECRET
);
// NOTE(jim): Only allow this to be passed around encrypted.
ws.send(
JSON.stringify({
type,
iv: encryptedData.iv,
data: encryptedData.hex,
})
);
};
export const hydratePartialViewer = async (user) => {
const data = {
...Serializers.user(user),
id: user.id,
username: user.username,
data: {
name: user.data.name ? user.data.name : "",
photo: user.data.photo ? user.data.photo : "",
body: user.data.body ? user.data.body : "",
},
type: "PARTIAL_VIEWER",
library: user.data.library,
onboarding: user.data.onboarding || {},
@ -40,24 +68,237 @@ export const hydratePartialViewer = async (user) => {
: null,
};
if (Strings.isEmpty(Environment.PUBSUB_SECRET)) {
return;
websocketSend("UPDATE", data);
};
export const hydratePartialOnboarding = async (onboarding, userId) => {
const data = {
id: userId,
onboarding,
};
websocketSend("UPDATE", data);
};
export const hydratePartialSubscriptions = async (updated, userId) => {
const data = {
id: userId,
};
const user = await Data.getUserById({ id: userId });
if (!user) {
return null;
}
const ws = Websocket.get();
const encryptedData = await Utilities.encryptWithSecret(
JSON.stringify(data),
Environment.PUBSUB_SECRET
);
if (user.error) {
return null;
}
let mostRecent;
if (updated.subscriptions) {
const subscriptions = await Data.getSubscriptionsByUserId({ userId });
let r1 = await Serializers.doSubscriptions({
users: [],
slates: [],
subscriptions,
serializedUsersMap: { [user.id]: Serializers.user(user) },
serializedSlatesMap: {},
});
data.subscriptions = r1.serializedSubscriptions;
mostRecent = r1;
}
if (updated.subscribers) {
const subscribers = await Data.getSubscribersByUserId({ userId });
let r2 = await Serializers.doSubscribers({
users: [],
slates: [],
subscribers,
serializedUsersMap: mostRecent
? mostRecent.serializedUsersMap
: { [user.id]: Serializers.user(user) },
serializedSlatesMap: mostRecent ? mostRecent.serializedSlatesMap : {},
});
data.subscribers = r2.serializedSubscribers;
mostRecent = r2;
}
if (updated.trusted) {
console.log("update trusted");
const trusted = await Data.getTrustedRelationshipsByUserId({ userId });
let r3 = await Serializers.doTrusted({
users: [],
trusted,
serializedUsersMap: mostRecent
? mostRecent.serializedUsersMap
: { [user.id]: Serializers.user(user) },
serializedSlatesMap: mostRecent ? mostRecent.serializedSlatesMap : {},
});
data.trusted = r3.serializedTrusted;
mostRecent = r3;
}
if (updated.pendingTrusted) {
console.log("update pending trusted");
const pendingTrusted = await Data.getPendingTrustedRelationshipsByUserId({
userId,
});
let r4 = await Serializers.doPendingTrusted({
users: [userId],
pendingTrusted,
serializedUsersMap: mostRecent
? mostRecent.serializedUsersMap
: { [user.id]: Serializers.user(user) },
serializedSlatesMap: mostRecent ? mostRecent.serializedSlatesMap : {},
});
data.pendingTrusted = r4.serializedPendingTrusted;
}
websocketSend("UPDATE", data);
};
// NOTE(jim): Only allow this to be passed around encrypted.
ws.send(
JSON.stringify({
type: "UPDATE",
iv: encryptedData.iv,
data: encryptedData.hex,
})
);
export const hydratePartialKeys = async (keys, userId) => {
const data = {
id: userId,
keys,
};
websocketSend("UPDATE", data);
};
export const hydratePartialLibrary = async (library, userId) => {
const data = {
id: userId,
library,
};
websocketSend("UPDATE", data);
};
export const hydratePartialSlates = async (slates, userId) => {
console.log("hydrate slates");
const data = {
id: userId,
slates,
};
websocketSend("UPDATE", data);
};
// export const hydratePartialActivity = async (activity, userId) => {
// this one will need to be more complex like what is in hydrate
// websocketSend("UPDATE", data);
// };
export const hydrate = async (id) => {
const user = await Data.getUserById({
id,
});
if (!user) {
return null;
}
if (user.error) {
return null;
}
// TODO(jim): You can serialize this last because you will have all the information
// from subscriptions, trusted, and pendingTrusted most likely.
const activity = await Data.getActivityForUserId({ userId: id });
const slates = await Data.getSlatesByUserId({ userId: id });
const keys = await Data.getAPIKeysByUserId({ userId: id });
const subscriptions = await Data.getSubscriptionsByUserId({ userId: id });
const subscribers = await Data.getSubscribersByUserId({ userId: id });
let serializedUsersMap = { [user.id]: Serializers.user(user) };
let serializedSlatesMap = {};
// NOTE(jim): The most expensive call first.
const r1 = await Serializers.doSubscriptions({
users: [],
slates: [],
subscriptions,
serializedUsersMap,
serializedSlatesMap,
});
const r2 = await Serializers.doSubscribers({
users: [],
slates: [],
subscribers,
serializedUsersMap: r1.serializedUsersMap,
serializedSlatesMap: r1.serializedSlatesMap,
});
// NOTE(jim): If any trusted users are subscription users, this ends up being cheaper.
const trusted = await Data.getTrustedRelationshipsByUserId({ userId: id });
const r3 = await Serializers.doTrusted({
users: [],
trusted,
serializedUsersMap: r2.serializedUsersMap,
serializedSlatesMap: r2.serializedSlatesMap,
});
// NOTE(jim): This should be the cheapest call.
const pendingTrusted = await Data.getPendingTrustedRelationshipsByUserId({
userId: id,
});
const r4 = await Serializers.doPendingTrusted({
users: [id],
pendingTrusted,
serializedUsersMap: r3.serializedUsersMap,
serializedSlatesMap: r3.serializedSlatesMap,
});
let bytes = 0;
let imageBytes = 0;
let videoBytes = 0;
let audioBytes = 0;
let epubBytes = 0;
let pdfBytes = 0;
user.data.library[0].children.forEach((each) => {
if (each.type && each.type.startsWith("image/")) {
imageBytes += each.size;
} else if (each.type && each.type.startsWith("video/")) {
videoBytes += each.size;
} else if (each.type && each.type.startsWith("audio/")) {
audioBytes += each.size;
} else if (each.type && each.type.startsWith("application/epub")) {
epubBytes += each.size;
} else if (each.type && each.type.startsWith("application/pdf")) {
pdfBytes += each.size;
}
bytes += each.size;
});
let data = {
...Serializers.user(user),
type: "VIEWER",
library: user.data.library,
onboarding: user.data.onboarding || {},
// TODO(jim): Move this elsewhere.
allow_filecoin_directory_listing: user.data.allow_filecoin_directory_listing
? user.data.allow_filecoin_directory_listing
: null,
allow_automatic_data_storage: user.data.allow_automatic_data_storage
? user.data.allow_automatic_data_storage
: null,
allow_encrypted_data_storage: user.data.allow_encrypted_data_storage
? user.data.allow_encrypted_data_storage
: null,
// NOTE(jim): Remaining data.
stats: {
bytes,
maximumBytes: Constants.TEXTILE_ACCOUNT_BYTE_LIMIT,
imageBytes,
videoBytes,
audioBytes,
epubBytes,
pdfBytes,
},
keys,
activity,
slates,
subscriptions: r1.serializedSubscriptions,
subscribers: r2.serializedSubscribers,
trusted: r3.serializedTrusted,
pendingTrusted: r4.serializedPendingTrusted,
};
websocketSend("UPDATE", data);
};
// TODO(jim): Work on better serialization when adoption starts occuring.

View File

@ -59,6 +59,7 @@
"dotenv": "^8.2.0",
"express": "^4.17.1",
"express-rate-limit": "^5.1.3",
"fs": "0.0.1-security",
"fs-extra": "^9.0.1",
"heic2any": "0.0.3",
"isomorphic-fetch": "^3.0.0",

View File

@ -46,7 +46,12 @@ export default class IntegrationPage extends React.Component {
_handleChange = async (e) => {
const query = e.target.value;
const results = await Actions.search({ query });
const results = await Actions.search({
query,
filter: {
slates: true,
},
});
this.setState({ results: results.data.results });
};
@ -59,8 +64,7 @@ export default class IntegrationPage extends React.Component {
name: "create-alert",
detail: {
alert: {
message:
"We're having trouble connecting right now. Please try again later",
message: "We're having trouble connecting right now. Please try again later",
},
},
});
@ -135,15 +139,11 @@ export default class IntegrationPage extends React.Component {
{JSON.stringify(each, null, 1)}{" "}
<div>
{!each.data.verified ? (
<button onClick={() => this._handleAccept(each)}>
Accept
</button>
<button onClick={() => this._handleAccept(each)}>Accept</button>
) : null}
{each.data.verified ? (
<button onClick={() => this._handleDelete(each)}>
Delete
</button>
<button onClick={() => this._handleDelete(each)}>Delete</button>
) : null}
</div>
</div>
@ -162,9 +162,7 @@ export default class IntegrationPage extends React.Component {
) : null}
{each.data.verified ? (
<button onClick={() => this._handleDelete(each)}>
Delete
</button>
<button onClick={() => this._handleDelete(each)}>Delete</button>
) : null}
</div>
</div>
@ -172,15 +170,9 @@ export default class IntegrationPage extends React.Component {
})}
</div>
<div css={STYLES_COLUMN}>
<button onClick={this._handleUpdate}>
Update {this.state.viewer.username}
</button>
<button onClick={this._handleUpdate}>Update {this.state.viewer.username}</button>
<br />
<input
type="text"
onChange={this._handleChange}
placeholder="type anything to search"
/>
<input type="text" onChange={this._handleChange} placeholder="type anything to search" />
{this.state.results.map((each) => {
if (!each) {
@ -192,13 +184,9 @@ export default class IntegrationPage extends React.Component {
{JSON.stringify(each, null, 1)}{" "}
<div>
{each.type === "USER" ? (
<button onClick={() => this._handleTrust(each)}>
Trust
</button>
<button onClick={() => this._handleTrust(each)}>Trust</button>
) : null}
<button onClick={() => this._handleSubscribe(each)}>
Subscribe
</button>
<button onClick={() => this._handleSubscribe(each)}>Subscribe</button>
</div>
</div>
);
@ -210,9 +198,7 @@ export default class IntegrationPage extends React.Component {
<div css={STYLES_ITEM} key={each.id}>
{JSON.stringify(each, null, 1)}{" "}
<div>
<button onClick={() => this._handleSubscribe(each)}>
Unsubscribe
</button>
<button onClick={() => this._handleSubscribe(each)}>Unsubscribe</button>
</div>
</div>
);

View File

@ -2,6 +2,7 @@ import * as Upload from "~/node_common/upload";
import * as Utilities from "~/node_common/utilities";
import * as Data from "~/node_common/data";
import * as LibraryManager from "~/node_common/managers/library";
import * as ViewerManager from "~/node_common/managers/viewer";
import { Buckets } from "@textile/hub";
@ -13,17 +14,10 @@ export default async (req, res) => {
});
if (!user || user.error) {
return res
.status(403)
.send({ decorator: "SERVER_ADD_TO_SLATE_USER_NOT_FOUND", error: true });
return res.status(403).send({ decorator: "SERVER_ADD_TO_SLATE_USER_NOT_FOUND", error: true });
}
let {
buckets,
bucketKey,
bucketRoot,
bucketName,
} = await Utilities.getBucketAPIFromUserToken({
let { buckets, bucketKey, bucketRoot, bucketName } = await Utilities.getBucketAPIFromUserToken({
user,
});
@ -41,9 +35,7 @@ export default async (req, res) => {
});
}
let userCIDs = user.data.library[0].children.map((file) =>
file.ipfs.replace("/ipfs/", "")
);
let userCIDs = user.data.library[0].children.map((file) => file.ipfs.replace("/ipfs/", ""));
let newFiles = [];
for (let item of req.body.data.items) {
let cid = item.cid;
@ -71,7 +63,7 @@ export default async (req, res) => {
}
}
const { updatedUserDataFields } = LibraryManager.addData({
const { updatedUserDataFields, added, skipped } = LibraryManager.addData({
user,
files: newFiles,
});
@ -79,9 +71,12 @@ export default async (req, res) => {
id: user.id,
data: updatedUserDataFields,
});
if (updatedUserDataFields && updatedUserDataFields.library) {
ViewerManager.hydratePartialLibrary(updatedUserDataFields.library, id);
}
return res.status(200).send({
decorator: "SERVER_ADD_EXISTING_CID_TO_DATA",
data: true,
data: { added, skipped: skipped + req.body.data.items.length - newFiles.length },
});
};

View File

@ -1,32 +1,68 @@
import * as Upload from "~/node_common/upload";
import * as Utilities from "~/node_common/utilities";
import * as Data from "~/node_common/data";
import * as LibraryManager from "~/node_common/managers/library";
import * as ViewerManager from "~/node_common/managers/viewer";
export default async (req, res) => {
const id = Utilities.getIdFromCookie(req);
if (!id) {
return res
.status(500)
.send({ decorator: "PROCESS_PENDING_ERROR", error: true });
return res.status(500).send({ decorator: "PROCESS_PENDING_ERROR", error: true });
}
const response = await Data.deletePendingDataByUserId({ owner_user_id: id });
const user = await Data.getUserById({
id,
});
if (!response) {
return res
.status(404)
.send({ decorator: "PROCESS_PENDING_ERROR", error: true });
if (!user) {
return res.status(404).send({
decorator: "PROCESS_PENDING_USER_NOT_FOUND",
error: true,
});
}
if (response.error) {
return res
.status(500)
.send({ decorator: response.decorator, error: response.error });
if (user.error) {
return res.status(500).send({
decorator: "PROCESS_PENDING_USER_NOT_FOUND",
error: true,
});
}
const pending = await Data.deletePendingDataByUserId({ owner_user_id: id });
if (!pending) {
return res.status(404).send({ decorator: "PROCESS_PENDING_ERROR", error: true });
}
if (pending.error) {
return res.status(500).send({ decorator: response.decorator, error: response.error });
}
let cids = [];
let noRepeats = [];
for (let entry of pending) {
if (cids.includes(entry.data.ipfs)) continue;
cids.push(entry.data.ipfs);
noRepeats.push(entry.data);
}
const { updatedUserDataFields, added, skipped } = LibraryManager.addData({
user,
files: noRepeats,
});
await Data.updateUserById({
id,
data: updatedUserDataFields,
});
if (added && updatedUserDataFields && updatedUserDataFields.library) {
ViewerManager.hydratePartialLibrary(updatedUserDataFields.library, id);
}
return res.status(200).send({
decorator: "PROCESS_PENDING_DATA",
data: response,
data: { added, skipped: skipped + pending.length - noRepeats.length },
});
};

View File

@ -2,6 +2,7 @@ import * as Data from "~/node_common/data";
import * as Utilities from "~/node_common/utilities";
import * as Strings from "~/common/strings";
import * as Social from "~/node_common/social";
import * as ViewerManager from "~/node_common/managers/viewer";
export default async (req, res) => {
if (!req.body.data || !req.body.data.cids || !req.body.data.cids.length) {
@ -99,7 +100,8 @@ export default async (req, res) => {
// NOTE(jim):
// Goes through all of your slates and removes all data references.
const slates = await Data.getSlatesByUserId({ userId: id });
let refreshSlates = false;
let slates = await Data.getSlatesByUserId({ userId: id });
for (let i = 0; i < slates.length; i++) {
let slate = slates[i];
@ -108,6 +110,7 @@ export default async (req, res) => {
for (let cid of req.body.data.cids) {
if (o.url.includes(cid)) {
removal = true;
refreshSlates = true;
return false;
}
}
@ -115,7 +118,7 @@ export default async (req, res) => {
});
if (removal) {
let layouts = await Data.updateSlateById({
await Data.updateSlateById({
id: slate.id,
updated_at: new Date(),
data: {
@ -126,6 +129,11 @@ export default async (req, res) => {
}
}
if (refreshSlates) {
slates = await Data.getSlatesByUserId({ userId: id });
ViewerManager.hydratePartialSlates(slates, id);
}
// NOTE(martina):
// Removes the reposted file from other people's slates
// for (let cid of req.body.data.cids) {
@ -134,7 +142,7 @@ export default async (req, res) => {
// NOTE(jim):
// Removes the file reference from your library
const response = await Data.updateUserById({
const unsafeResponse = await Data.updateUserById({
id: user.id,
data: {
...user.data,
@ -154,6 +162,10 @@ export default async (req, res) => {
},
});
if (unsafeResponse) {
ViewerManager.hydratePartialViewer(unsafeResponse);
}
return res.status(200).send({
decorator: "SERVER_REMOVE_DATA",
success: true,

View File

@ -1,13 +1,12 @@
import * as Environment from "~/node_common/environment";
import * as Data from "~/node_common/data";
import * as Utilities from "~/node_common/utilities";
import * as ViewerManager from "~/node_common/managers/viewer";
export default async (req, res) => {
const id = Utilities.getIdFromCookie(req);
if (!id) {
return res
.status(500)
.send({ decorator: "SERVER_DELETE_API_KEY_AUTH", error: true });
return res.status(500).send({ decorator: "SERVER_DELETE_API_KEY_AUTH", error: true });
}
const user = await Data.getUserById({
@ -30,7 +29,7 @@ export default async (req, res) => {
const key = await Data.getAPIKey({ id: req.body.data.id });
if (key.owner_id !== user.id) {
if (!key || key.owner_id !== user.id) {
return res.status(403).send({
decorator: "SERVER_DELETE_API_KEY_NOT_FOUND",
error: true,
@ -67,5 +66,8 @@ export default async (req, res) => {
});
}
let keys = await Data.getAPIKeysByUserId({ userId: user.id });
ViewerManager.hydratePartialKeys(keys, user.id);
return res.status(200).send({ decorator: "SERVER_DELETE_API_KEY" });
};

View File

@ -1,15 +1,14 @@
import * as Environment from "~/node_common/environment";
import * as Data from "~/node_common/data";
import * as Utilities from "~/node_common/utilities";
import * as ViewerManager from "~/node_common/managers/viewer";
import { v4 as uuid } from "uuid";
export default async (req, res) => {
const id = Utilities.getIdFromCookie(req);
if (!id) {
return res
.status(500)
.send({ decorator: "SERVER_GENERATE_API_KEY_AUTH", error: true });
return res.status(500).send({ decorator: "SERVER_GENERATE_API_KEY_AUTH", error: true });
}
const user = await Data.getUserById({
@ -30,7 +29,7 @@ export default async (req, res) => {
});
}
const keys = await Data.getAPIKeysByUserId({ userId: user.id });
let keys = await Data.getAPIKeysByUserId({ userId: user.id });
if (keys.length > 9) {
return res.status(404).send({
@ -58,5 +57,8 @@ export default async (req, res) => {
});
}
keys = await Data.getAPIKeysByUserId({ userId: user.id });
ViewerManager.hydratePartialKeys(keys, user.id);
return res.status(200).send({ decorator: "SERVER_GENERATE_API_KEY", key });
};

View File

@ -2,6 +2,7 @@ import * as Constants from "~/node_common/constants";
import * as Utilities from "~/node_common/utilities";
import * as Data from "~/node_common/data";
import * as Strings from "~/common/strings";
import * as ViewerManager from "~/node_common/managers/viewer";
export default async (req, res) => {
const id = Utilities.getIdFromCookie(req);
@ -120,6 +121,11 @@ export default async (req, res) => {
});
}
const slates = await Data.getSlatesByUserId({ userId: id });
if (slates) {
ViewerManager.hydratePartialSlates(slates, id);
}
return res.status(200).send({
decorator: "SERVER_SLATE_ADD_TO_SLATE",
added: addlObjects.length,

View File

@ -2,6 +2,7 @@ import * as Utilities from "~/node_common/utilities";
import * as Data from "~/node_common/data";
import * as Strings from "~/common/strings";
import * as Social from "~/node_common/social";
import * as ViewerManager from "~/node_common/managers/viewer";
const SLATE_LIMIT = 50;
@ -40,7 +41,7 @@ export default async (req, res) => {
return res.status(500).send({ decorator: "SERVER_EXISTING_SLATE", error: true });
}
const slates = await Data.getSlatesByUserId({ userId: id });
let slates = await Data.getSlatesByUserId({ userId: id });
if (slates.length >= SLATE_LIMIT) {
return res.status(500).send({ decorator: "SERVER_SLATE_LIMIT", error: true });
}
@ -64,6 +65,11 @@ export default async (req, res) => {
return res.status(500).send({ decorator: "SERVER_CREATE_SLATE", error: true });
}
slates = await Data.getSlatesByUserId({ userId: id });
if (slates) {
ViewerManager.hydratePartialSlates(slates, id);
}
const userProfileURL = `https://slate.host/${user.username}`;
const userURL = `<${userProfileURL}|${user.username}>`;
Social.sendSlackMessage(

View File

@ -1,6 +1,7 @@
import * as Utilities from "~/node_common/utilities";
import * as Data from "~/node_common/data";
import * as Strings from "~/common/strings";
import * as ViewerManager from "~/node_common/managers/viewer";
export default async (req, res) => {
const id = Utilities.getIdFromCookie(req);
@ -46,5 +47,10 @@ export default async (req, res) => {
return res.status(500).send({ decorator: "SERVER_DELETE_SLATE", error: true });
}
let slates = await Data.getSlatesByUserId({ userId: id });
if (slates) {
ViewerManager.hydratePartialSlates(slates, id);
}
return res.status(200).send({ decorator: "SERVER_DELETE_SLATE", error: false });
};

View File

@ -2,6 +2,7 @@ import * as Constants from "~/node_common/constants";
import * as Utilities from "~/node_common/utilities";
import * as Data from "~/node_common/data";
import * as Strings from "~/common/strings";
import * as ViewerManager from "~/node_common/managers/viewer";
export default async (req, res) => {
const id = Utilities.getIdFromCookie(req);
@ -79,6 +80,11 @@ export default async (req, res) => {
});
}
let slates = await Data.getSlatesByUserId({ userId: id });
if (slates) {
ViewerManager.hydratePartialSlates(slates, id);
}
return res.status(200).send({
decorator: "SERVER_SLATE_REMOVE_FROM_SLATE",
slate,

View File

@ -1,6 +1,7 @@
import * as Utilities from "~/node_common/utilities";
import * as Data from "~/node_common/data";
import * as Strings from "~/common/strings";
import * as ViewerManager from "~/node_common/managers/viewer";
export default async (req, res) => {
const id = Utilities.getIdFromCookie(req);
@ -10,9 +11,7 @@ export default async (req, res) => {
if (!layoutOnly) {
if (!id) {
return res
.status(500)
.send({ decorator: "SERVER_FIND_USER_UPDATE_SLATE", error: true });
return res.status(500).send({ decorator: "SERVER_FIND_USER_UPDATE_SLATE", error: true });
}
user = await Data.getUserById({
@ -37,15 +36,11 @@ export default async (req, res) => {
const response = await Data.getSlateById({ id: req.body.data.id });
if (!response) {
return res
.status(404)
.send({ decorator: "SERVER_UPDATE_SLATE_NOT_FOUND", error: true });
return res.status(404).send({ decorator: "SERVER_UPDATE_SLATE_NOT_FOUND", error: true });
}
if (response.error) {
return res
.status(500)
.send({ decorator: "SERVER_UPDATE_SLATE_NOT_FOUND", error: true });
return res.status(500).send({ decorator: "SERVER_UPDATE_SLATE_NOT_FOUND", error: true });
}
if (!req.body.data) {
@ -87,8 +82,7 @@ export default async (req, res) => {
slatename: req.body.data.data.name
? Strings.createSlug(req.body.data.data.name)
: response.slatename,
updated_at:
layoutOnly || req.body.data.autoSave ? response.updated_at : new Date(),
updated_at: layoutOnly || req.body.data.autoSave ? response.updated_at : new Date(),
data: {
...response.data,
...req.body.data.data,
@ -97,15 +91,16 @@ export default async (req, res) => {
}
if (!slate) {
return res
.status(404)
.send({ decorator: "SERVER_UPDATE_SLATE", error: true });
return res.status(404).send({ decorator: "SERVER_UPDATE_SLATE", error: true });
}
if (slate.error) {
return res
.status(500)
.send({ decorator: "SERVER_UPDATE_SLATE", error: true });
return res.status(500).send({ decorator: "SERVER_UPDATE_SLATE", error: true });
}
let slates = await Data.getSlatesByUserId({ userId: id });
if (slates) {
ViewerManager.hydratePartialSlates(slates, id);
}
return res.status(200).send({ decorator: "SERVER_UPDATE_SLATE", slate });

View File

@ -2,6 +2,7 @@ import * as Data from "~/node_common/data";
import * as Utilities from "~/node_common/utilities";
import * as Serializers from "~/node_common/serializers";
import * as Validations from "~/common/validations";
import * as ViewerManager from "~/node_common/managers/viewer";
export default async (req, res) => {
const id = Utilities.getIdFromCookie(req);
@ -102,6 +103,8 @@ export default async (req, res) => {
return res.status(500).send({ decorator: "SERVER_UNSUBSCRIBE_ERROR", error: true });
}
ViewerManager.hydratePartialSubscriptions({ subscriptions: true }, user.id);
return res.status(200).send({ decorator: "SERVER_UNSUBSCRIBE", data: unsubscribeResponse });
}
@ -119,6 +122,8 @@ export default async (req, res) => {
return res.status(500).send({ decorator: "SERVER_SUBSCRIBE_ERROR", error: true });
}
ViewerManager.hydratePartialSubscriptions({ subscriptions: true }, user.id);
return res.status(200).send({
decorator: "SERVER_SUBSCRIBE",
data: {

View File

@ -3,6 +3,7 @@ import * as Data from "~/node_common/data";
import * as Utilities from "~/node_common/utilities";
import * as Serializers from "~/node_common/serializers";
import * as Validations from "~/common/validations";
import * as ViewerManager from "~/node_common/managers/viewer";
export default async (req, res) => {
const id = Utilities.getIdFromCookie(req);
@ -48,6 +49,8 @@ export default async (req, res) => {
data: { ...user.data, onboarding },
});
ViewerManager.hydratePartialOnboarding(onboarding, user.id);
return res.status(200).send({
decorator: "SERVER_ONBOARDING_UPDATE",
data: updateResponse,

View File

@ -2,6 +2,7 @@ import * as Environment from "~/node_common/environment";
import * as Data from "~/node_common/data";
import * as Utilities from "~/node_common/utilities";
import * as Validations from "~/common/validations";
import * as ViewerManager from "~/node_common/managers/viewer";
export default async (req, res) => {
const id = Utilities.getIdFromCookie(req);
@ -38,5 +39,7 @@ export default async (req, res) => {
id: req.body.data.id,
});
ViewerManager.hydratePartialSubscriptions({ trusted: true, pendingTrusted: true }, id);
return res.status(200).send({ decorator: "SERVER_TRUST_UPDATE", data: response });
};

View File

@ -3,6 +3,7 @@ import * as Data from "~/node_common/data";
import * as Utilities from "~/node_common/utilities";
import * as Serializers from "~/node_common/serializers";
import * as Validations from "~/common/validations";
import * as ViewerManager from "~/node_common/managers/viewer";
export default async (req, res) => {
const id = Utilities.getIdFromCookie(req);
@ -84,6 +85,8 @@ export default async (req, res) => {
data: { ...existingResponse.data, verified: true },
});
ViewerManager.hydratePartialSubscriptions({ trusted: true, pendingTrusted: true }, id);
return res.status(200).send({
decorator: "SERVER_TRUST_UPDATE",
data: {

View File

@ -3,6 +3,7 @@ import * as Data from "~/node_common/data";
import * as Utilities from "~/node_common/utilities";
import * as Serializers from "~/node_common/serializers";
import * as Validations from "~/common/validations";
import * as ViewerManager from "~/node_common/managers/viewer";
export default async (req, res) => {
const id = Utilities.getIdFromCookie(req);
@ -105,6 +106,8 @@ export default async (req, res) => {
});
}
ViewerManager.hydratePartialSubscriptions({ trusted: true, pendingTrusted: true }, id);
return res.status(200).send({
decorator: "SERVER_DELETE_TRUSTED_RELATIONSHIP",
data: deleteRelationshipResponse,
@ -127,6 +130,8 @@ export default async (req, res) => {
return res.status(500).send({ decorator: "SERVER_TRUSTED_RELATIONSHIP_ERROR", error: true });
}
ViewerManager.hydratePartialSubscriptions({ trusted: true, pendingTrusted: true }, id);
return res.status(200).send({
decorator: "SERVER_TRUSTED_RELATIONSHIP",
data: {

View File

@ -138,7 +138,6 @@ export default class SceneDirectory extends React.Component {
state = {
copyValue: "",
loading: false,
tab: 0,
contextMenu: null,
};
@ -168,28 +167,25 @@ export default class SceneDirectory extends React.Component {
_handleDelete = async (e, id) => {
this._handleHide();
e.stopPropagation();
const response = await Actions.deleteTrustRelationship({
await Actions.deleteTrustRelationship({
id: id,
});
await this.props.onRehydrate();
};
_handleAccept = async (e, id) => {
this._handleHide();
e.stopPropagation();
const response = await Actions.updateTrustRelationship({
await Actions.updateTrustRelationship({
userId: id,
});
await this.props.onRehydrate();
};
_handleFollow = async (e, id) => {
this._handleHide();
e.stopPropagation();
const response = await Actions.createSubscription({
await Actions.createSubscription({
userId: id,
});
await this.props.onRehydrate();
};
render() {

View File

@ -50,7 +50,6 @@ export default class SceneFilesFolder extends React.Component {
onAction={this.props.onAction}
viewer={this.props.viewer}
items={this.props.viewer.library[0].children}
onRehydrate={this.props.onRehydrate}
/>
) : (
<EmptyState>

View File

@ -58,7 +58,6 @@ export default class SceneHome extends React.Component {
viewer={this.props.viewer}
items={this.props.viewer.library[0].children}
onAction={this.props.onAction}
onRehydrate={this.props.onRehydrate}
/>
</div>
) : (

View File

@ -5,10 +5,7 @@ import * as Constants from "~/common/constants";
import * as SVG from "~/common/svg";
import { css } from "@emotion/react";
import {
ButtonPrimary,
ButtonSecondary,
} from "~/components/system/components/Buttons";
import { ButtonPrimary, ButtonSecondary } from "~/components/system/components/Buttons";
import { dispatchCustomEvent } from "~/common/custom-events";
import ScenePage from "~/components/core/ScenePage";
@ -28,165 +25,87 @@ const STATUS_BUTTON_MAP = {
};
export default class SceneProfile extends React.Component {
state = {
loading: false,
trustStatus: "untrusted",
followStatus: false,
followLoading: false,
trustLoading: false,
};
componentDidMount = () => {
this.setStatus(this.props.viewer);
};
componentDidUpdate(prevProps) {
if (prevProps.data.username !== this.props.data.username) {
this.setStatus(this.props.viewer);
_handleTrust = async (trustStatus, trustId) => {
if (trustStatus === "untrusted" || trustStatus === "sent") {
await Actions.createTrustRelationship({
userId: this.props.data.id,
});
} else if (trustStatus === "received") {
await Actions.updateTrustRelationship({
userId: this.props.data.id,
});
} else {
await Actions.deleteTrustRelationship({
id: trustId,
});
}
}
};
setStatus = (viewer) => {
let newState = {
trustStatus: "untrusted",
followStatus: false,
followLoading: false,
trustLoading: false,
};
_handleFollow = async () => {
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) {
let relation = trust[0];
newState.trustId = relation.id;
relation = trust[0];
trustId = relation.id;
if (relation.data.verified) {
newState.trustStatus = "trusted";
trustStatus = "trusted";
} else {
newState.trustStatus = "sent";
trustStatus = "sent";
}
}
let pendingTrust = viewer.pendingTrusted.filter((entry) => {
return entry.owner_user_id === this.props.data.id;
});
if (pendingTrust.length) {
let relation = pendingTrust[0];
newState.trustId = relation.id;
relation = pendingTrust[0];
trustId = relation.id;
if (pendingTrust[0].data.verified) {
newState.trustStatus = "trusted";
trustStatus = "trusted";
} else {
newState.trustStatus = "received";
trustStatus = "received";
}
}
if (
viewer.subscriptions.filter((entry) => {
return entry.target_user_id === this.props.data.id;
}).length
) {
newState.followStatus = true;
}
this.setState(newState);
};
followStatus = !!viewer.subscriptions.filter((entry) => {
return entry.target_user_id === this.props.data.id;
}).length;
_handleUpdate = async (e) => {
let response = await this.props.onRehydrate();
if (!response) {
dispatchCustomEvent({
name: "create-alert",
detail: {
alert: {
message:
"We're having trouble connecting right now. Please try again later",
},
},
});
return null;
}
if (response.error) {
dispatchCustomEvent({
name: "create-alert",
detail: { alert: { decorator: response.decorator } },
});
return null;
}
let viewer = response.data;
this.setStatus(viewer);
};
_handleTrust = () => {
this.setState({ trustLoading: true }, async () => {
let response;
if (
this.state.trustStatus === "untrusted" ||
this.state.trustStatus === "sent"
) {
response = await Actions.createTrustRelationship({
userId: this.props.data.id,
});
console.log(response);
} else if (this.state.trustStatus === "received") {
response = await Actions.updateTrustRelationship({
userId: this.props.data.id,
});
console.log(response);
} else {
response = await Actions.deleteTrustRelationship({
id: this.state.trustId,
});
console.log(response);
}
await this._handleUpdate();
});
};
_handleFollow = () => {
this.setState({ followLoading: true }, async () => {
let response = await Actions.createSubscription({
userId: this.props.data.id,
});
console.log(response);
await this._handleUpdate();
});
};
render() {
let buttons = (
<div css={STYLES_BUTTONS}>
{this.state.followStatus ? (
{followStatus ? (
<ButtonSecondary
loading={this.state.followLoading}
style={{ margin: "0px 8px", minWidth: 152 }}
onClick={this._handleFollow}
>
Unfollow
</ButtonSecondary>
) : (
<ButtonPrimary
loading={this.state.followLoading}
style={{ margin: "0px 8px", minWidth: 152 }}
onClick={this._handleFollow}
>
<ButtonPrimary style={{ margin: "0px 8px", minWidth: 152 }} onClick={this._handleFollow}>
Follow
</ButtonPrimary>
)}
{this.state.trustStatus === "untrusted" ||
this.state.trustStatus === "received" ? (
{trustStatus === "untrusted" || trustStatus === "received" ? (
<ButtonPrimary
loading={this.state.trustLoading}
style={{ margin: "0px 8px", minWidth: 152 }}
onClick={this._handleTrust}
onClick={() => this._handleTrust(trustStatus, trustId)}
>
{STATUS_BUTTON_MAP[this.state.trustStatus]}
{STATUS_BUTTON_MAP[trustStatus]}
</ButtonPrimary>
) : (
<ButtonSecondary
loading={this.state.trustLoading}
style={{ margin: "0px 8px", minWidth: 152 }}
onClick={this._handleTrust}
onClick={() => this._handleTrust(trustStatus, trustId)}
>
{STATUS_BUTTON_MAP[this.state.trustStatus]}
{STATUS_BUTTON_MAP[trustStatus]}
</ButtonSecondary>
)}
</div>
@ -198,11 +117,7 @@ export default class SceneProfile extends React.Component {
onAction={this.props.onAction}
creator={this.props.data}
sceneId={this.props.sceneId}
buttons={
this.props.viewer.username === this.props.data.username
? null
: buttons
}
buttons={this.props.viewer.username === this.props.data.username ? null : buttons}
isOwner={this.props.viewer.username === this.props.data.username}
/>
</ScenePage>

View File

@ -118,8 +118,6 @@ export default class SceneSettings extends React.Component {
},
});
await this.props.onRehydrate();
this.setState({ loading: false });
};

View File

@ -211,8 +211,6 @@ export default class SceneSettingsDeveloper extends React.Component {
return this.setState({ loading: false });
}
await this.props.onRehydrate();
this.setState({ loading: false });
};
@ -244,6 +242,7 @@ export default class SceneSettingsDeveloper extends React.Component {
});
return this.setState({ loading: false });
}
this.setState({ loading: false });
};
async componentDidMount() {

View File

@ -57,7 +57,6 @@ export default class SceneSlate extends React.Component {
saving: "IDLE",
isOwner: this.props.current.data.ownerId === this.props.viewer.id,
editing: false,
followLoading: false,
};
// NOTE(jim):
@ -108,12 +107,8 @@ export default class SceneSlate extends React.Component {
}
_handleFollow = () => {
this.setState({ followLoading: true }, async () => {
let response = await Actions.createSubscription({
slateId: this.props.current.id,
});
await this.props.onRehydrate();
this.setState({ followLoading: false });
Actions.createSubscription({
slateId: this.props.current.id,
});
};
@ -165,10 +160,6 @@ export default class SceneSlate extends React.Component {
}
}
if (this.state.isOwner) {
await this.props.onRehydrate();
}
this.setState({
saving: "SAVED",
});
@ -224,7 +215,6 @@ export default class SceneSlate extends React.Component {
});
return;
}
await this.props.onRehydrate();
dispatchCustomEvent({
name: "create-alert",
detail: {
@ -277,8 +267,6 @@ export default class SceneSlate extends React.Component {
return;
}
await this.props.onRehydrate();
System.dispatchCustomEvent({
name: "state-global-carousel-loading",
detail: { loading: false },
@ -323,8 +311,14 @@ export default class SceneSlate extends React.Component {
});
return;
}
let message = Strings.formatAsUploadMessage(response.data.added, response.data.skipped);
dispatchCustomEvent({
name: "create-alert",
detail: {
alert: { message, status: !response.data.added ? null : "INFO" },
},
});
this.setState({ loading: false, saving: "SAVED" });
this.props.onRehydrate();
};
_handleShowSettings = () => {
@ -336,7 +330,6 @@ export default class SceneSlate extends React.Component {
};
render() {
console.log(this.props);
const { user, data } = this.props.current;
const { body = "", preview } = data;
let objects = this.props.current.data.objects;
@ -375,19 +368,11 @@ export default class SceneSlate extends React.Component {
) : (
<div onClick={this._handleFollow}>
{following ? (
<ButtonSecondary
transparent
style={{ minWidth: 120, paddingLeft: 0 }}
loading={this.state.followLoading}
>
<ButtonSecondary transparent style={{ minWidth: 120, paddingLeft: 0 }}>
Unfollow
</ButtonSecondary>
) : (
<ButtonPrimary
transparent
style={{ minWidth: 120, paddingLeft: 0 }}
loading={this.state.followLoading}
>
<ButtonPrimary transparent style={{ minWidth: 120, paddingLeft: 0 }}>
Follow
</ButtonPrimary>
)}

View File

@ -35,13 +35,12 @@ export default class SceneSlates extends React.Component {
type: "SIDEBAR",
value: "SIDEBAR_CREATE_SLATE",
});
this.props.onRehydrate();
};
_handleSearch = () => {
dispatchCustomEvent({
name: "create-modal",
detail: { modal: <SearchModal onAction={this.props.onAction} /> },
detail: { modal: <SearchModal viewer={this.props.viewer} onAction={this.props.onAction} /> },
});
};
@ -72,10 +71,7 @@ export default class SceneSlates extends React.Component {
title="Slates"
actions={
this.state.tab === 0 ? (
<CircleButtonGray
onClick={this._handleAdd}
style={{ marginLeft: 12 }}
>
<CircleButtonGray onClick={this._handleAdd} style={{ marginLeft: 12 }}>
<SVG.Plus height="16px" />
</CircleButtonGray>
) : null
@ -106,13 +102,9 @@ export default class SceneSlates extends React.Component {
<SVG.Video height="24px" style={{ margin: "0 16px" }} />
</div>
<div style={{ marginTop: 24 }}>
Use slates to create mood boards, share files, and organize
research.
Use slates to create mood boards, share files, and organize research.
</div>
<ButtonSecondary
onClick={this._handleAdd}
style={{ marginTop: 32 }}
>
<ButtonSecondary onClick={this._handleAdd} style={{ marginTop: 32 }}>
Create slate
</ButtonSecondary>
</EmptyState>
@ -146,10 +138,7 @@ export default class SceneSlates extends React.Component {
) : (
<EmptyState>
You can follow any public slates on the network.
<ButtonSecondary
onClick={this._handleSearch}
style={{ marginTop: 32 }}
>
<ButtonSecondary onClick={this._handleSearch} style={{ marginTop: 32 }}>
Browse slates
</ButtonSecondary>
</EmptyState>

View File

@ -45,7 +45,6 @@ export default class SceneTara extends React.Component {
onAction={this.props.onAction}
viewer={this.props.viewer}
items={this.props.viewer.library[0].children}
onRehydrate={this.props.onRehydrate}
/>
) : (
<EmptyState>