added public files

This commit is contained in:
Martina 2021-01-08 20:13:00 -08:00
parent cd3a62833c
commit 5fcf748f20
10 changed files with 255 additions and 24 deletions

View File

@ -266,6 +266,13 @@ export const updateData = async (data) => {
}); });
}; };
export const toggleFilePrivacy = async (data) => {
return await returnJSON(`/api/data/toggle-privacy`, {
...DEFAULT_OPTIONS,
body: JSON.stringify({ data }),
});
};
export const deleteBucketItems = async (data) => { export const deleteBucketItems = async (data) => {
return await returnJSON(`/api/data/remove`, { return await returnJSON(`/api/data/remove`, {
...DEFAULT_OPTIONS, ...DEFAULT_OPTIONS,

View File

@ -15,6 +15,7 @@ import { LoaderSpinner } from "~/components/system/components/Loaders";
import { SlatePicker } from "~/components/core/SlatePicker"; import { SlatePicker } from "~/components/core/SlatePicker";
import { Input } from "~/components/system/components/Input"; import { Input } from "~/components/system/components/Input";
import { Boundary } from "~/components/system/components/fragments/Boundary"; import { Boundary } from "~/components/system/components/fragments/Boundary";
import { Toggle } from "~/components/system/components/Toggle";
const DEFAULT_BOOK = const DEFAULT_BOOK =
"https://slate.textile.io/ipfs/bafkreibk32sw7arspy5kw3p5gkuidfcwjbwqyjdktd5wkqqxahvkm2qlyi"; "https://slate.textile.io/ipfs/bafkreibk32sw7arspy5kw3p5gkuidfcwjbwqyjdktd5wkqqxahvkm2qlyi";
@ -80,11 +81,6 @@ const STYLES_DISMISS_BOX = css`
} }
`; `;
const STYLES_CONTAINER = css`
width: 100%;
height: 100%;
`;
const STYLES_META = css` const STYLES_META = css`
padding: 14px 0px 8px 0px; padding: 14px 0px 8px 0px;
overflow-wrap: break-word; overflow-wrap: break-word;
@ -238,6 +234,7 @@ export default class CarouselSidebarData extends React.Component {
name: Strings.isEmpty(this.props.data.name) ? "" : this.props.data.name, name: Strings.isEmpty(this.props.data.name) ? "" : this.props.data.name,
selected: {}, selected: {},
isPublic: false, isPublic: false,
inPublicSlates: false,
copyValue: "", copyValue: "",
loading: false, loading: false,
changingPreview: false, changingPreview: false,
@ -249,18 +246,18 @@ export default class CarouselSidebarData extends React.Component {
this.setState({ unsavedChanges: true }); this.setState({ unsavedChanges: true });
if (this.props.isOwner && !this.props.external) { if (this.props.isOwner && !this.props.external) {
this.debounceInstance = Window.debounce(() => this._handleSave(), 3000); this.debounceInstance = Window.debounce(() => this._handleSave(), 3000);
let isPublic = false; let inPublicSlates = false;
let selected = {}; let selected = {};
const id = this.props.data.id; const id = this.props.data.id;
for (let slate of this.props.slates) { for (let slate of this.props.slates) {
if (slate.data.objects.some((o) => o.id === id)) { if (slate.data.objects.some((o) => o.id === id)) {
if (slate.data.public) { if (slate.data.public) {
isPublic = true; inPublicSlates = true;
} }
selected[slate.id] = true; selected[slate.id] = true;
} }
} }
this.setState({ selected, isPublic }); this.setState({ selected, inPublicSlates, isPublic: this.props.data.public });
} }
}; };
@ -383,7 +380,46 @@ export default class CarouselSidebarData extends React.Component {
}); });
}; };
_handleToggleVisibility = async (e) => {
const isVisible = this.state.inPublicSlates || this.state.isPublic;
let selected = this.state.selected;
if (this.state.inPublicSlates) {
console.log("in public slates");
const slateIds = Object.entries(this.state.selected)
.filter((entry) => entry[1])
.map((entry) => entry[0]);
const publicSlateIds = [];
const publicSlateNames = [];
for (let slate of this.props.slates) {
if (slate.data.public && slateIds.includes(slate.id)) {
publicSlateNames.push(slate.data.name);
publicSlateIds.push(slate.id);
selected[slate.id] = false;
}
}
const message = `Making this file private will remove it from the following public slates: ${publicSlateNames.join(
", "
)}. Do you wish to continue?`;
if (!window.confirm(message)) {
return;
}
}
let response = await Actions.toggleFilePrivacy({
data: {
id: this.props.data.id,
public: !isVisible,
},
});
console.log(response);
if (isVisible) {
this.setState({ inPublicSlates: false, isPublic: false, selected });
} else {
this.setState({ isPublic: true });
}
};
render() { render() {
const isVisible = this.state.inPublicSlates || this.state.isPublic;
const { cid, file, name, coverImage, type, size, url, blurhash } = this.props.data; const { cid, file, name, coverImage, type, size, url, blurhash } = this.props.data;
const elements = []; const elements = [];
if (this.props.onClose) { if (this.props.onClose) {
@ -395,7 +431,7 @@ export default class CarouselSidebarData extends React.Component {
} }
elements.push( elements.push(
<div key="s-2" css={STYLES_CONTAINER}> <div key="s-2" style={{ marginBottom: 80 }}>
<div css={STYLES_META}> <div css={STYLES_META}>
{this.state.isEditing ? ( {this.state.isEditing ? (
<Boundary enabled onOutsideRectEvent={this._handleEditFilename}> <Boundary enabled onOutsideRectEvent={this._handleEditFilename}>
@ -499,12 +535,26 @@ export default class CarouselSidebarData extends React.Component {
</div> </div>
)} )}
<div css={STYLES_SECTION_HEADER} style={{ margin: "48px 0px 8px 0px" }}> <div css={STYLES_SECTION_HEADER} style={{ margin: "48px 0px 8px 0px" }}>
Privacy Visibility
</div> </div>
<div style={{ color: Constants.system.darkGray, lineHeight: "1.5", marginBottom: 104 }}> <div
{this.state.isPublic style={{
? "Public. This file is currently visible to others and searchable within Slate through public slates." display: "flex",
: "Private. This file is currently not visible to others unless they have the link."} flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
margin: "16px 0 16px 0",
}}
>
<div style={{ color: Constants.system.darkGray, lineHeight: "1.5" }}>
{isVisible ? "Everyone" : "Link only"}
</div>
<Toggle dark active={isVisible} onChange={this._handleToggleVisibility} />
</div>
<div style={{ color: Constants.system.darkGray, marginTop: 8 }}>
{isVisible
? "This file is currently visible to everyone and searchable within Slate through public slates."
: "This file is currently not visible to others unless they have the link."}
</div> </div>
<input <input
css={STYLES_HIDDEN} css={STYLES_HIDDEN}

View File

@ -50,12 +50,19 @@ export class Toggle extends React.Component {
css={STYLES_TOGGLE} css={STYLES_TOGGLE}
onClick={this._handleChange} onClick={this._handleChange}
style={{ style={{
backgroundColor: this.props.active ? Constants.system.brand : null, backgroundColor: this.props.active
? Constants.system.brand
: this.props.dark
? Constants.system.grayBlack
: null,
}} }}
> >
<figure <figure
css={STYLES_DIAL} css={STYLES_DIAL}
style={{ transform: this.props.active ? `translateX(28px)` : null }} style={{
transform: this.props.active ? `translateX(28px)` : null,
background: this.props.dark ? Constants.system.border : null,
}}
/> />
</div> </div>
</div> </div>

View File

@ -125,8 +125,9 @@ export const editItem = ({ user, update }) => {
if (library[0].children[i].id === update.data.id) { if (library[0].children[i].id === update.data.id) {
library[0].children[i] = { library[0].children[i] = {
...library[0].children[i], ...library[0].children[i],
coverImage: update.data?.coverImage, ...update.data,
name: update.data?.name, // coverImage: update.data?.coverImage,
// name: update.data?.name,
}; };
break; break;
} }

View File

@ -219,7 +219,7 @@ export const getById = async ({ id }) => {
} }
// TODO(jim): You can serialize this last because you will have all the information // TODO(jim): You can serialize this last because you will have all the information
// from subscriptionsed, trusted, and pendingTrusted most likely. // from subscriptions, trusted, and pendingTrusted most likely.
let activity = await Data.getActivityForUserId({ userId: id }); let activity = await Data.getActivityForUserId({ userId: id });
const slates = await Data.getSlatesByUserId({ userId: id }); const slates = await Data.getSlatesByUserId({ userId: id });
const keys = await Data.getAPIKeysByUserId({ userId: id }); const keys = await Data.getAPIKeysByUserId({ userId: id });

View File

@ -35,6 +35,7 @@ export default class ProfilePage extends React.Component {
}; };
render() { render() {
console.log(this.props.creator);
const title = this.props.creator ? `${this.props.creator.username}` : "404"; const title = this.props.creator ? `${this.props.creator.username}` : "404";
const url = `https://slate.host/${title}`; const url = `https://slate.host/${title}`;
const description = this.props.creator.data.body; const description = this.props.creator.data.body;

View File

@ -0,0 +1,82 @@
import * as Data from "~/node_common/data";
import * as Utilities from "~/node_common/utilities";
import * as LibraryManager from "~/node_common/managers/library";
import * as ViewerManager from "~/node_common/managers/viewer";
import * as SearchManager from "~/node_common/managers/search";
export default async (req, res) => {
const id = Utilities.getIdFromCookie(req);
if (!id) {
return res.status(500).send({ decorator: "SERVER_EDIT_DATA", error: true });
}
const user = await Data.getUserById({ id });
if (!user || user.error) {
return res.status(403).send({ decorator: "SERVER_EDIT_DATA_USER_NOT_FOUND", error: true });
}
let newUserData = LibraryManager.editItem({ user, update: req.body.data });
let response = await Data.updateUserById({
id: user.id,
data: newUserData,
});
if (!response || response.error) {
return res.status(500).send({ decorator: "SERVER_EDIT_DATA_NOT_UPDATED", error: true });
}
let slates = await Data.getSlatesByUserId({ userId: id });
if (!slates || slates.error) {
return res.status(500).send({ decorator: "SERVER_SLATES_NOT_FOUND", error: true });
}
if (!req.body.data.data.public) {
//NOTE(martina): if toggling a file to private, must remove it from any public slates as well
let slatesChanged = false;
for (let slate of slates) {
let edited = false;
if (!slate.data.public) {
continue;
}
let objects = slate.data.objects.filter((obj) => {
if (obj.id === req.body.data.data.id) {
edited = true;
slatesChanged = true;
return false;
}
return true;
});
if (edited) {
await Data.updateSlateById({
id: slate.id,
updated_at: new Date(),
data: {
...slate.data,
objects,
},
});
SearchManager.updateSlate(slate, "EDIT");
}
}
if (slatesChanged) {
let userSlates = await Data.getSlatesByUserId({ userId: id });
if (!userSlates || userSlates.error) {
return res.status(500).send({ decorator: "SERVER_SLATES_NOT_FOUND", error: true });
}
ViewerManager.hydratePartialSlates(userSlates, id);
} else {
//remove it from the search index as an individual file
}
} else {
//add to search index as an individual file
}
if (newUserData && newUserData.library) {
ViewerManager.hydratePartialLibrary(newUserData.library, id);
}
return res.status(200).send({
decorator: "SERVER_EDIT_DATA",
data: {},
});
};

View File

@ -15,6 +15,8 @@ export default async (req, res) => {
error: true, error: true,
}); });
} }
let library = user.data.library;
user = Serializers.user(user); user = Serializers.user(user);
let slates = await Data.getSlatesByUserId({ let slates = await Data.getSlatesByUserId({
@ -29,10 +31,48 @@ export default async (req, res) => {
}); });
} }
} }
let publicFileIds = [];
user.slates = []; user.slates = [];
for (let slate of slates) { for (let slate of slates) {
user.slates.push(Serializers.slate(slate)); user.slates.push(Serializers.slate(slate));
if (slate.data.public) {
publicFileIds.push(...slate.data.objects.map((obj) => obj.id));
} }
}
if (library && library.length) {
library[0].children = library[0].children.filter((file) => {
return file.public || publicFileIds.includes(file.id);
});
}
user.library = library;
const subscriptions = await Data.getSubscriptionsByUserId({ userId: req.body.data.id });
const subscribers = await Data.getSubscribersByUserId({ userId: req.body.data.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,
});
user.subscriptions = r1.serializedSubscriptions;
const r2 = await Serializers.doSubscribers({
users: [],
slates: [],
subscribers,
serializedUsersMap: r1.serializedUsersMap,
serializedSlatesMap: r1.serializedSlatesMap,
});
user.subscribers = r2.serializedSubscribers;
return res.status(200).send({ return res.status(200).send({
decorator: "SERIALIZED_USER", decorator: "SERIALIZED_USER",

View File

@ -77,6 +77,7 @@ export default class SceneProfile extends React.Component {
targetUser = response.data; targetUser = response.data;
} }
console.log(targetUser);
window.history.replaceState(window.history.state, "A slate user", `/${targetUser.username}`); window.history.replaceState(window.history.state, "A slate user", `/${targetUser.username}`);

View File

@ -224,7 +224,7 @@ app.prepare().then(async () => {
}); });
} }
const creator = await Data.getUserByUsername({ let creator = await Data.getUserByUsername({
username: req.params.username, username: req.params.username,
}); });
@ -236,11 +236,56 @@ app.prepare().then(async () => {
return res.redirect("/404"); return res.redirect("/404");
} }
let library = creator.data.library;
creator = Serializers.user(creator);
const slates = await Data.getSlatesByUserId({ const slates = await Data.getSlatesByUserId({
userId: creator.id, userId: creator.id,
publicOnly: true, publicOnly: true,
}); });
let publicFileIds = [];
for (let slate of slates) {
publicFileIds.push(...slate.data.objects.map((obj) => obj.id));
}
creator.slates = slates;
if (library && library.length) {
library[0].children = library[0].children.filter((file) => {
return file.public || publicFileIds.includes(file.id);
});
}
creator.library = library;
const subscriptions = await Data.getSubscriptionsByUserId({ userId: creator.id });
const subscribers = await Data.getSubscribersByUserId({ userId: creator.id });
let serializedUsersMap = { [creator.id]: creator };
let serializedSlatesMap = {};
// NOTE(jim): The most expensive call first.
const r1 = await Serializers.doSubscriptions({
users: [],
slates: [],
subscriptions,
serializedUsersMap,
serializedSlatesMap,
});
creator.subscriptions = r1.serializedSubscriptions;
const r2 = await Serializers.doSubscribers({
users: [],
slates: [],
subscribers,
serializedUsersMap: r1.serializedUsersMap,
serializedSlatesMap: r1.serializedSlatesMap,
});
creator.subscribers = r2.serializedSubscribers;
let exploreSlates = []; let exploreSlates = [];
if (Environment.IS_PRODUCTION) { if (Environment.IS_PRODUCTION) {
@ -281,7 +326,7 @@ app.prepare().then(async () => {
return app.render(req, res, "/_/profile", { return app.render(req, res, "/_/profile", {
viewer, viewer,
creator: Serializers.user({ ...creator, slates }), creator,
mobile, mobile,
resources: EXTERNAL_RESOURCES, resources: EXTERNAL_RESOURCES,
exploreSlates, exploreSlates,
@ -324,9 +369,6 @@ app.prepare().then(async () => {
return res.redirect("/404"); return res.redirect("/404");
} }
console.log(slate.data.public);
console.log(slate.data.ownerId);
console.log(id);
if (!slate.data.public && slate.data.ownerId !== id) { if (!slate.data.public && slate.data.ownerId !== id) {
return res.redirect("/403"); return res.redirect("/403");
} }