added carousel to profiles and fixed not updating bug

This commit is contained in:
Martina 2021-01-16 17:53:02 -08:00
parent 7f4f6a4b04
commit c4301e74b8
9 changed files with 282 additions and 295 deletions

View File

@ -52,7 +52,6 @@ import WebsitePrototypeWrapper from "~/components/core/WebsitePrototypeWrapper";
import { GlobalModal } from "~/components/system/components/GlobalModal"; import { GlobalModal } from "~/components/system/components/GlobalModal";
import { OnboardingModal } from "~/components/core/OnboardingModal"; import { OnboardingModal } from "~/components/core/OnboardingModal";
import { GlobalCarousel } from "~/components/system/components/GlobalCarousel";
import { SearchModal } from "~/components/core/SearchModal"; import { SearchModal } from "~/components/core/SearchModal";
import { Alert } from "~/components/core/Alert"; import { Alert } from "~/components/core/Alert";
import { announcements } from "~/components/core/OnboardingModal"; import { announcements } from "~/components/core/OnboardingModal";
@ -690,15 +689,6 @@ export default class ApplicationPage extends React.Component {
> >
{scene} {scene}
</ApplicationLayout> </ApplicationLayout>
<GlobalCarousel
onUpdateViewer={this._handleUpdateViewer}
resources={this.props.resources}
viewer={this.state.viewer}
current={this.state.data}
slates={this.state.viewer.slates}
onAction={this._handleAction}
mobile={this.props.mobile}
/>
<GlobalModal /> <GlobalModal />
<SearchModal <SearchModal
viewer={this.state.viewer} viewer={this.state.viewer}

View File

@ -82,6 +82,7 @@ const STYLES_DISMISS_BOX = css`
`; `;
const STYLES_META = css` const STYLES_META = css`
text-align: start;
padding: 14px 0px 8px 0px; padding: 14px 0px 8px 0px;
overflow-wrap: break-word; overflow-wrap: break-word;
`; `;
@ -343,6 +344,7 @@ export default class CarouselSidebarData extends React.Component {
}; };
_handleDelete = (cid) => { _handleDelete = (cid) => {
if (!this.props.isOwner) return;
const message = `Are you sure you want to delete this? It will be deleted from your slates as well`; const message = `Are you sure you want to delete this? It will be deleted from your slates as well`;
if (!window.confirm(message)) { if (!window.confirm(message)) {
return; return;
@ -384,7 +386,6 @@ export default class CarouselSidebarData extends React.Component {
const isVisible = this.state.inPublicSlates || this.state.isPublic; const isVisible = this.state.inPublicSlates || this.state.isPublic;
let selected = this.state.selected; let selected = this.state.selected;
if (this.state.inPublicSlates) { if (this.state.inPublicSlates) {
console.log("in public slates");
const slateIds = Object.entries(this.state.selected) const slateIds = Object.entries(this.state.selected)
.filter((entry) => entry[1]) .filter((entry) => entry[1])
.map((entry) => entry[0]); .map((entry) => entry[0]);
@ -410,7 +411,6 @@ export default class CarouselSidebarData extends React.Component {
public: !isVisible, public: !isVisible,
}, },
}); });
console.log(response);
if (isVisible) { if (isVisible) {
this.setState({ inPublicSlates: false, isPublic: false, selected }); this.setState({ inPublicSlates: false, isPublic: false, selected });
} else { } else {
@ -445,7 +445,10 @@ export default class CarouselSidebarData extends React.Component {
/> />
</Boundary> </Boundary>
) : ( ) : (
<span css={STYLES_META_TITLE} target="_blank" onClick={this._handleEditFilename}> <span
css={STYLES_META_TITLE}
onClick={this.props.external ? () => {} : this._handleEditFilename}
>
{this.state.name} {this.state.name}
</span> </span>
)} )}
@ -462,37 +465,45 @@ export default class CarouselSidebarData extends React.Component {
</div> </div>
</div> </div>
<div css={STYLES_ACTIONS}> <div css={STYLES_ACTIONS}>
<div css={STYLES_ACTION} onClick={() => this._handleCopy(cid, "cidCopying")}> {this.props.isOwner ? (
<SVG.CopyAndPaste height="24px" /> <div css={STYLES_ACTION} onClick={() => this._handleCopy(cid, "cidCopying")}>
<span style={{ marginLeft: 16 }}> <SVG.CopyAndPaste height="24px" />
{this.state.loading === "cidCopying" ? "Copied!" : "Copy file CID"} <span style={{ marginLeft: 16 }}>
</span> {this.state.loading === "cidCopying" ? "Copied!" : "Copy file CID"}
</div> </span>
</div>
) : null}
<div css={STYLES_ACTION} onClick={() => this._handleCopy(url, "gatewayUrlCopying")}> <div css={STYLES_ACTION} onClick={() => this._handleCopy(url, "gatewayUrlCopying")}>
<SVG.Data height="24px" /> <SVG.Data height="24px" />
<span style={{ marginLeft: 16 }}> <span style={{ marginLeft: 16 }}>
{this.state.loading === "gatewayUrlCopying" ? "Copied!" : "Copy gateway URL"} {this.state.loading === "gatewayUrlCopying" ? "Copied!" : "Copy external URL"}
</span> </span>
</div> </div>
<div css={STYLES_ACTION} onClick={this._handleDownload}> <div css={STYLES_ACTION} onClick={this._handleDownload}>
<SVG.Download height="24px" /> <SVG.Download height="24px" />
<span style={{ marginLeft: 16 }}>Download</span> <span style={{ marginLeft: 16 }}>Download</span>
</div> </div>
<div css={STYLES_ACTION} onClick={() => this._handleDelete(cid)}> {this.props.isOwner ? (
<SVG.Trash height="24px" /> <div css={STYLES_ACTION} onClick={() => this._handleDelete(cid)}>
<span style={{ marginLeft: 16 }}>Delete</span> <SVG.Trash height="24px" />
</div> <span style={{ marginLeft: 16 }}>Delete</span>
</div>
) : null}
</div> </div>
<div css={STYLES_SECTION_HEADER}>Connected Slates</div> {this.props.external ? null : (
<SlatePicker <React.Fragment>
dark <div css={STYLES_SECTION_HEADER}>Connected Slates</div>
slates={this.props.slates} <SlatePicker
onCreateSlate={this._handleCreateSlate} dark
selectedColor={Constants.system.white} slates={this.props.slates}
files={[this.props.data]} onCreateSlate={this._handleCreateSlate}
selected={this.state.selected} selectedColor={Constants.system.white}
onAdd={this._handleAdd} files={[this.props.data]}
/> selected={this.state.selected}
onAdd={this._handleAdd}
/>
</React.Fragment>
)}
{type && Validations.isPreviewableImage(type) ? null : ( {type && Validations.isPreviewableImage(type) ? null : (
<div> <div>
<System.P css={STYLES_SECTION_HEADER} style={{ margin: "48px 0px 8px 0px" }}> <System.P css={STYLES_SECTION_HEADER} style={{ margin: "48px 0px 8px 0px" }}>
@ -534,28 +545,32 @@ export default class CarouselSidebarData extends React.Component {
</div> </div>
</div> </div>
)} )}
<div css={STYLES_SECTION_HEADER} style={{ margin: "48px 0px 8px 0px" }}> {this.props.isOwner ? (
Visibility <React.Fragment>
</div> <div css={STYLES_SECTION_HEADER} style={{ margin: "48px 0px 8px 0px" }}>
<div Visibility
style={{ </div>
display: "flex", <div
flexDirection: "row", style={{
alignItems: "center", display: "flex",
justifyContent: "space-between", flexDirection: "row",
margin: "16px 0 16px 0", alignItems: "center",
}} justifyContent: "space-between",
> margin: "16px 0 16px 0",
<div style={{ color: Constants.system.darkGray, lineHeight: "1.5" }}> }}
{isVisible ? "Everyone" : "Link only"} >
</div> <div style={{ color: Constants.system.darkGray, lineHeight: "1.5" }}>
<Toggle dark active={isVisible} onChange={this._handleToggleVisibility} /> {isVisible ? "Everyone" : "Link only"}
</div> </div>
<div style={{ color: Constants.system.darkGray, marginTop: 8 }}> <Toggle dark active={isVisible} onChange={this._handleToggleVisibility} />
{isVisible </div>
? "This file is currently visible to everyone and searchable within Slate through public slates." <div style={{ color: Constants.system.darkGray, marginTop: 8 }}>
: "This file is currently not visible to others unless they have the link."} {isVisible
</div> ? "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>
</React.Fragment>
) : null}
<input <input
css={STYLES_HIDDEN} css={STYLES_HIDDEN}
ref={(c) => { ref={(c) => {

View File

@ -4,7 +4,14 @@ import * as Strings from "~/common/strings";
import * as SVG from "~/common/svg"; import * as SVG from "~/common/svg";
import * as Actions from "~/common/actions"; import * as Actions from "~/common/actions";
import { GlobalCarousel } from "~/components/system/components/GlobalCarousel";
import { css } from "@emotion/react"; import { css } from "@emotion/react";
import { ButtonPrimary, ButtonSecondary } from "~/components/system/components/Buttons";
import { TabGroup, SecondaryTabGroup } from "~/components/core/TabGroup";
import { Boundary } from "~/components/system/components/fragments/Boundary";
import { PopoverNavigation } from "~/components/system/components/PopoverNavigation";
import { FileTypeGroup } from "~/components/core/FileTypeIcon";
//FOR TARA: try and group imports by style (import *, import { Whatever }, import Whatever). Just a neatness preference
import ProcessedText from "~/components/core/ProcessedText"; import ProcessedText from "~/components/core/ProcessedText";
import SlatePreviewBlocks from "~/components/core/SlatePreviewBlock"; import SlatePreviewBlocks from "~/components/core/SlatePreviewBlock";
@ -12,12 +19,6 @@ import CTATransition from "~/components/core/CTATransition";
import DataView from "~/components/core/DataView"; import DataView from "~/components/core/DataView";
import EmptyState from "~/components/core/EmptyState"; import EmptyState from "~/components/core/EmptyState";
import { SceneUtils } from "three";
import { TabGroup, SecondaryTabGroup } from "~/components/core/TabGroup";
import { Boundary } from "~/components/system/components/fragments/Boundary";
import { PopoverNavigation } from "~/components/system/components/PopoverNavigation";
import { FileTypeGroup } from "~/components/core/FileTypeIcon";
const STYLES_PROFILE_BACKGROUND = css` const STYLES_PROFILE_BACKGROUND = css`
background-color: ${Constants.system.white}; background-color: ${Constants.system.white};
width: 100%; width: 100%;
@ -60,7 +61,8 @@ const STYLES_INFO = css`
`; `;
const STYLES_PROFILE_IMAGE = css` const STYLES_PROFILE_IMAGE = css`
background-color: ${Constants.system.white}; background-color: ${Constants.system.foreground};
${"" /* FOR TARA: I like to put a background color that stands out from the background so that in the case that the background image isn't found, it doesn't just show blank. It at least shows a square there */}
background-size: cover; background-size: cover;
background-position: 50% 50%; background-position: 50% 50%;
width: 120px; width: 120px;
@ -108,8 +110,8 @@ const STYLES_STATS = css`
`; `;
const STYLES_STAT = css` const STYLES_STAT = css`
margin-right: 8px; margin: 0px 12px;
width: 112px; ${"" /* width: 112px; */}
flex-shrink: 0; flex-shrink: 0;
`; `;
@ -233,70 +235,53 @@ export default class Profile extends React.Component {
peerTab: 0, peerTab: 0,
copyValue: "", copyValue: "",
contextMenu: null, contextMenu: null,
followingSlates: [],
publicSlates: [], publicSlates: [],
publicFiles: [], publicFiles: [],
pseudoPrivateFiles: [], pseudoPrivateFiles: [],
}; isFollowing: this.props.external
? false
_handleCopy = (e, value) => { : !!this.props.viewer.subscriptions.filter((entry) => {
e.stopPropagation(); return entry.target_user_id === this.props.data.id;
this.setState({ copyValue: value }, () => { }).length,
this._ref.select(); //FOR TARA: here's an example of using the !! where it's needed. I want isFollowing to be true (if a subscription to this user was found) or false, not 0 or 1, so I use a !! to convert the number to a boolean
document.execCommand("copy");
this._handleHide();
});
};
_handleHide = (e) => {
this.setState({ contextMenu: null });
};
_handleClick = (e, value) => {
e.stopPropagation();
if (this.state.contextMenu === value) {
this._handleHide();
} else {
this.setState({ contextMenu: value });
}
};
_handleFollow = async (e, id) => {
this._handleHide();
e.stopPropagation();
await Actions.createSubscription({
userId: id,
});
}; };
componentDidMount = async () => { componentDidMount = async () => {
await this.filterByVisibility(); await this.filterByVisibility();
await this.fetchUsername();
}; };
componentDidUpdate = async () => { componentDidUpdate = async () => {
if (this.props.viewer?.library[0].children.length !== this.lastLength) { if (
this.lastLength != null &&
this.props.creator?.library[0].children.length !== this.lastLength
) {
//FOR TARA: I just added the "this.lastLength != null" so that we don't get the double-call here where component did mount calls filterbyvisiblity,
//then filterbyvisibility changes the library length to not null,
//then componentdidupdate sees that the length has changed from null to some number, then calls filterbyvisibility again
//FOR TARA: I also made it this.props.creator rather than this.props.viewer. Not a big deal, but this is b/c if you are viewing someone else's profile and your OWN library changes, you shouldn't have to do filterbyvisibility again b/c it won't affect anything
this.filterByVisibility(); this.filterByVisibility();
} }
}; };
fetchUsername = async () => { //FOR TARA: you don't need to do actions.getserialized profile to get the username of the slate owner here. it was already accessible through subscription.slate.username, and you don't need to pass it in
let followingSlates = []; //Without that call, it makes more sense to put this inside the render method so I moved this there now. You can delete this function
let subscriptions = this.props.creator.subscriptions ? this.props.creator.subscriptions : null; // fetchUsername = async () => {
for (let subscription of subscriptions) { // let followingSlates = [];
if (subscription.slate != null) { // let subscriptions = this.props.creator.subscriptions ? this.props.creator.subscriptions : [];
followingSlates.push(subscription.slate); // for (let subscription of subscriptions) {
} // if (subscription.slate != null) {
} // followingSlates.push(subscription.slate);
// }
// }
for (let followingSlate of followingSlates) { // for (let followingSlate of followingSlates) {
let id = { id: followingSlate.data.ownerId }; // let id = { id: followingSlate.data.ownerId };
let slateOwner = await Actions.getSerializedProfile(id); // let slateOwner = await Actions.getSerializedProfile(id); //martina: do we need to do serialized? or just normal? Or some other way of getting teh username that's more efficient?
followingSlate.username = slateOwner.data?.username; // followingSlate.username = slateOwner.data?.username;
} // }
this.setState({ followingSlates: followingSlates }); // this.setState({ followingSlates: followingSlates });
}; // };
filterByVisibility = async () => { filterByVisibility = async () => {
let publicFiles = []; let publicFiles = [];
@ -334,24 +319,53 @@ export default class Profile extends React.Component {
publicFiles: publicFiles, publicFiles: publicFiles,
pseudoPrivateFiles: pseudoPrivateFiles, pseudoPrivateFiles: pseudoPrivateFiles,
}); });
this.lastLength = this.props.viewer?.library[0].children.length; this.lastLength = this.props.creator?.library[0].children.length;
};
_handleCopy = (e, value) => {
e.stopPropagation();
this.setState({ copyValue: value }, () => {
this._ref.select();
document.execCommand("copy");
this._handleHide();
});
};
_handleHide = (e) => {
this.setState({ contextMenu: null });
};
_handleClick = (e, value) => {
e.stopPropagation();
if (this.state.contextMenu === value) {
this._handleHide();
} else {
this.setState({ contextMenu: value });
}
};
_handleFollow = async (e, id) => {
this._handleHide();
e.stopPropagation();
await Actions.createSubscription({
userId: id,
});
}; };
render() { render() {
const external = !this.props.onAction;
let data = this.props.creator ? this.props.creator : this.props.data;
let exploreSlates = this.props.exploreSlates;
let subscriptions = this.props.isOwner
? this.props.viewer.subscriptions
: this.props.creator.subscriptions
? this.props.creator.subscriptions
: null;
let subscribers = this.props.isOwner
? this.props.viewer.subscribers
: this.props.creator.subscribers
? this.props.creator.subscribers
: null;
let isOwner = this.props.creator.username === this.props.viewer?.username; let isOwner = this.props.creator.username === this.props.viewer?.username;
let data = this.props.creator ? this.props.creator : this.props.data;
//FOR TARA: i made a chnage in scene profile so that if it is your own profile, creator is equivalent to viewer, so there is no longer a need to check for that here
//FOR TARA: also, it's always safer to make the "default" for an array an empty array, not null. Because if you try iterate over null, it will error.
let subscriptions = this.props.creator.subscriptions || [];
let subscribers = this.props.creator.subscribers || [];
//FOR TARA: this is where I moved the "fetchUsernames" function to
let followingSlates = subscriptions
.filter((relation) => {
return !!relation.target_slate_id;
})
.map((relation) => relation.slate);
let following = subscriptions let following = subscriptions
.filter((relation) => { .filter((relation) => {
@ -415,7 +429,7 @@ export default class Profile extends React.Component {
<UserEntry <UserEntry
key={relation.id} key={relation.id}
user={relation.user} user={relation.user}
button={button} button={this.props.external ? null : button}
onClick={() => { onClick={() => {
this.props.onAction({ this.props.onAction({
type: "NAVIGATE", type: "NAVIGATE",
@ -486,7 +500,7 @@ export default class Profile extends React.Component {
<UserEntry <UserEntry
key={relation.id} key={relation.id}
user={relation.owner} user={relation.owner}
button={button} button={this.props.external ? null : button} //FOR TARA: clicking on a following/follower person errors if you are in the external view. I recommend making it check if this.props.external, in which case do a link instead (wrap it with an <a href={urlHere}> tag). See SlatePreviewBlocks for an example if you need one
onClick={() => { onClick={() => {
this.props.onAction({ this.props.onAction({
type: "NAVIGATE", type: "NAVIGATE",
@ -499,12 +513,37 @@ export default class Profile extends React.Component {
); );
}); });
let total = 0; //FOR TARA: I wanted to introduce you to the "reduce" function. It basically walks through all the items in an array and reduces it down to one "summary" value
for (let slate of data.slates) { //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
total += slate.data.objects.length; //This allows you to quickly take the sum of the lengths of the objects in each slate for example, or get the maximum value in an array
} //It isn't necessary to use since you can usually use a for loop instead, but good to know in case you encounter it later
// let total = 0;
// for (let slate of data.slates) {
// total += slate.data.objects.length;
// }
let total = data.slates.reduce((total, slate) => {
return total + slate.data?.objects?.length || 0; //FOR TARA: should use optional chaining and add a default value of 0 in case there are some slates where slate.data or slate.data.objects is null
}, 0);
return ( return (
<div> <div>
<GlobalCarousel
carouselType="data"
onUpdateViewer={this.props.onUpdateViewer}
resources={this.props.resources}
viewer={this.props.viewer}
objects={
this.state.fileTab === 0
? this.props.creator.library[0].children
: this.state.fileTab === 1
? this.state.publicFiles
: this.state.pseudoPrivateFiles
}
isOwner={isOwner}
onAction={this.props.onAction}
mobile={this.props.mobile}
external={this.props.external}
/>
<div css={STYLES_PROFILE_BACKGROUND}> <div css={STYLES_PROFILE_BACKGROUND}>
<div css={STYLES_PROFILE_INFO}> <div css={STYLES_PROFILE_INFO}>
<div <div
@ -513,7 +552,29 @@ export default class Profile extends React.Component {
/> />
<div css={STYLES_INFO}> <div css={STYLES_INFO}>
<div css={STYLES_NAME}>{Strings.getPresentationName(data)}</div> <div css={STYLES_NAME}>{Strings.getPresentationName(data)}</div>
<div css={STYLES_BUTTON}>{this.props.buttons}</div> {!isOwner && !this.props.external && (
<div css={STYLES_BUTTON}>
{this.state.isFollowing ? (
<ButtonSecondary
onClick={(e) => {
this.setState({ isFollowing: false });
this._handleFollow(e, this.props.creator.id);
}}
>
Unfollow
</ButtonSecondary>
) : (
<ButtonPrimary
onClick={(e) => {
this.setState({ isFollowing: true });
this._handleFollow(e, this.props.creator.id);
}}
>
Follow
</ButtonPrimary>
)}
</div>
)}
{data.data.body ? ( {data.data.body ? (
<div css={STYLES_DESCRIPTION}> <div css={STYLES_DESCRIPTION}>
<ProcessedText text={data.data.body} /> <ProcessedText text={data.data.body} />
@ -522,14 +583,13 @@ export default class Profile extends React.Component {
<div css={STYLES_STATS}> <div css={STYLES_STATS}>
<div css={STYLES_STAT}> <div css={STYLES_STAT}>
<div style={{ fontFamily: `${Constants.font.text}` }}> <div style={{ fontFamily: `${Constants.font.text}` }}>
{total}{" "} {total} <span style={{ color: `${Constants.system.darkGray}` }}>Files</span>
<span style={{ color: `${Constants.system.darkGray}` }}>Public data</span>
</div> </div>
</div> </div>
<div css={STYLES_STAT}> <div css={STYLES_STAT}>
<div style={{ fontFamily: `${Constants.font.text}` }}> <div style={{ fontFamily: `${Constants.font.text}` }}>
{data.slates.length}{" "} {data.slates.length}{" "}
<span style={{ color: `${Constants.system.darkGray}` }}>Public slates</span> <span style={{ color: `${Constants.system.darkGray}` }}>Slates</span>
</div> </div>
</div> </div>
</div> </div>
@ -558,7 +618,7 @@ export default class Profile extends React.Component {
<div style={{ display: `flex` }}> <div style={{ display: `flex` }}>
{isOwner && ( {isOwner && (
<SecondaryTabGroup <SecondaryTabGroup
tabs={["All files", "Everyone can see", "Link access only"]} tabs={["All files", "Everyone can view", "Link access only"]}
value={this.state.fileTab} value={this.state.fileTab}
onChange={(value) => this.setState({ fileTab: value })} onChange={(value) => this.setState({ fileTab: value })}
style={{ margin: "0 0 24px 0" }} style={{ margin: "0 0 24px 0" }}
@ -574,6 +634,8 @@ export default class Profile extends React.Component {
style={{ margin: "0 0 24px 0", justifyContent: "flex-end" }} style={{ margin: "0 0 24px 0", justifyContent: "flex-end" }}
/> />
</div> </div>
{/* FOR TARA: I want you to see how you can compress these following lines 625-713. There is a lot of repeated code here and you don't need to be restating DataView and EmptyState so many times.
You could just change what is passed into items for DataView (or even make a variable named items at the render method, assign it to the right thing, then just pass it into items) and what is displayed in the text for EmptyState */}
{isOwner ? ( {isOwner ? (
<div> <div>
{this.state.fileTab === 0 ? ( {this.state.fileTab === 0 ? (
@ -655,7 +717,9 @@ export default class Profile extends React.Component {
) : ( ) : (
<EmptyState> <EmptyState>
<FileTypeGroup /> <FileTypeGroup />
<div style={{ marginTop: 24 }}>Drag and drop files into Slate to upload</div> <div style={{ marginTop: 24 }}>
This user does not have any public files yet
</div>
</EmptyState> </EmptyState>
)} )}
</div> </div>
@ -665,7 +729,7 @@ export default class Profile extends React.Component {
{this.state.tab === 1 ? ( {this.state.tab === 1 ? (
<div> <div>
<SecondaryTabGroup <SecondaryTabGroup
tabs={["Public Slates", "Following Slates"]} tabs={["Slates", "Following"]}
value={this.state.slateTab} value={this.state.slateTab}
onChange={(value) => this.setState({ slateTab: value })} onChange={(value) => this.setState({ slateTab: value })}
style={{ margin: "0 0 24px 0" }} style={{ margin: "0 0 24px 0" }}
@ -675,7 +739,7 @@ export default class Profile extends React.Component {
{this.state.publicSlates && this.state.publicSlates.length ? ( {this.state.publicSlates && this.state.publicSlates.length ? (
<SlatePreviewBlocks <SlatePreviewBlocks
isOwner={this.props.isOwner} isOwner={this.props.isOwner}
external={this.props.onAction ? false : true} external={this.props.external}
slates={this.state.publicSlates} slates={this.state.publicSlates}
username={data.username} username={data.username}
onAction={this.props.onAction} onAction={this.props.onAction}
@ -683,24 +747,24 @@ export default class Profile extends React.Component {
) : ( ) : (
<EmptyState> <EmptyState>
<SVG.Slate height="24px" style={{ marginBottom: 24 }} /> <SVG.Slate height="24px" style={{ marginBottom: 24 }} />
There aren't any public slates yet. This user does not have any public slates yet
</EmptyState> </EmptyState>
)} )}
</div> </div>
) : null} ) : null}
{this.state.slateTab === 1 ? ( {this.state.slateTab === 1 ? (
<div> <div>
{this.state.followingSlates && this.state.followingSlates.length ? ( {followingSlates?.length ? ( //FOR TARA: with the optional chaining (see the link I sent you), you can replace "followingSlates && followingSlates.length" with just followingSlates?.length
<SlatePreviewBlocks <SlatePreviewBlocks
isOwner={false} isOwner={false}
external={this.props.onAction ? false : true} external={this.props.external}
slates={this.state.followingSlates} slates={followingSlates}
onAction={this.props.onAction} onAction={this.props.onAction}
/> />
) : ( ) : (
<EmptyState> <EmptyState>
<SVG.Slate height="24px" style={{ marginBottom: 24 }} /> <SVG.Slate height="24px" style={{ marginBottom: 24 }} />
There aren't any following slates yet. This user is not following any slates yet
</EmptyState> </EmptyState>
)} )}
</div> </div>
@ -722,7 +786,7 @@ export default class Profile extends React.Component {
) : ( ) : (
<EmptyState> <EmptyState>
<SVG.Users height="24px" style={{ marginBottom: 24 }} /> <SVG.Users height="24px" style={{ marginBottom: 24 }} />
There isn't any following yet. This user is not following anyone yet
</EmptyState> </EmptyState>
)} )}
</div> </div>
@ -734,7 +798,7 @@ export default class Profile extends React.Component {
) : ( ) : (
<EmptyState> <EmptyState>
<SVG.Users height="24px" style={{ marginBottom: 24 }} /> <SVG.Users height="24px" style={{ marginBottom: 24 }} />
There aren't any followers yet. This user does not have any followers yet
</EmptyState> </EmptyState>
)} )}
</div> </div>

View File

@ -10,6 +10,7 @@ import * as Events from "~/common/custom-events";
import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview"; import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview";
import CTATransition from "~/components/core/CTATransition"; import CTATransition from "~/components/core/CTATransition";
import { GlobalCarousel } from "~/components/system/components/GlobalCarousel";
import { CheckBox } from "~/components/system/components/CheckBox"; import { CheckBox } from "~/components/system/components/CheckBox";
import { css } from "@emotion/react"; import { css } from "@emotion/react";
import { LoaderSpinner } from "~/components/system/components/Loaders"; import { LoaderSpinner } from "~/components/system/components/Loaders";
@ -1105,6 +1106,17 @@ export class SlateLayout extends React.Component {
let unit = this.state.unit; let unit = this.state.unit;
return ( return (
<div> <div>
<GlobalCarousel
carouselType="slate"
onUpdateViewer={this.props.onUpdateViewer}
viewer={this.props.viewer}
objects={this.state.items}
current={this.props.current}
onAction={this.props.onAction}
mobile={this.props.mobile}
isOwner={this.props.isOwner}
external={this.props.external}
/>
{this.props.isOwner ? ( {this.props.isOwner ? (
this.state.editing ? ( this.state.editing ? (
<div <div

View File

@ -11,6 +11,7 @@ import { Alert } from "~/components/core/Alert";
import CarouselSidebarSlate from "~/components/core/CarouselSidebarSlate"; import CarouselSidebarSlate from "~/components/core/CarouselSidebarSlate";
import CarouselSidebarData from "~/components/core/CarouselSidebarData"; import CarouselSidebarData from "~/components/core/CarouselSidebarData";
import SlateMediaObject from "~/components/core/SlateMediaObject"; import SlateMediaObject from "~/components/core/SlateMediaObject";
import { CompressedPixelFormat } from "three";
const STYLES_ROOT = css` const STYLES_ROOT = css`
position: fixed; position: fixed;
@ -124,6 +125,12 @@ export class GlobalCarousel extends React.Component {
window.removeEventListener("slate-global-close-carousel", this._handleClose); window.removeEventListener("slate-global-close-carousel", this._handleClose);
}; };
componentDidUpdate = (prevProps) => {
if (this.state.index >= (this.props.objects?.length || 0)) {
this._handleClose();
}
};
_handleKeyDown = (e) => { _handleKeyDown = (e) => {
const inputs = document.querySelectorAll("input"); const inputs = document.querySelectorAll("input");
for (let i = 0; i < inputs.length; i++) { for (let i = 0; i < inputs.length; i++) {
@ -155,20 +162,16 @@ export class GlobalCarousel extends React.Component {
}; };
_handleOpen = (e) => { _handleOpen = (e) => {
let carouselType = if (e.detail.index < 0 || e.detail.index >= this.props.objects.length) {
!this.props.current || return;
(this.props.current && }
(this.props.current.decorator === "FOLDER" || this.props.current.decorator === "ACTIVITY"))
? "data"
: "slate";
this.setState({ this.setState({
carouselType: carouselType,
visible: true, visible: true,
index: e.detail.index || 0, index: e.detail.index || 0,
baseURL: window.location.pathname, baseURL: window.location.pathname,
}); });
if (carouselType === "slate" && this.props.current.data?.objects) { if (this.props.carouselType === "slate") {
const data = this.props.current.data.objects[e.detail.index]; const data = this.props.objects[e.detail.index];
window.history.replaceState( window.history.replaceState(
{ ...window.history.state, cid: data.cid }, { ...window.history.state, cid: data.cid },
null, null,
@ -193,31 +196,13 @@ export class GlobalCarousel extends React.Component {
_handleNext = () => { _handleNext = () => {
let index = this.state.index + 1; let index = this.state.index + 1;
if ( if (index >= this.props.objects.length) {
this.state.carouselType === "slate" && index = 0;
this.props.current.data &&
this.props.current.data.objects
) {
if (index >= this.props.current.data.objects.length) {
index = 0;
}
} else if (
this.state.carouselType === "data" &&
this.props.viewer &&
this.props.viewer.library
) {
if (index >= this.props.viewer.library[0].children.length) {
index = 0;
}
} }
this.setState({ index }); this.setState({ index });
if ( if (this.props.carouselType === "slate" && this.state.baseURL) {
this.state.carouselType === "slate" && const data = this.props.objects[index];
this.state.baseURL &&
this.props.current.data?.objects
) {
const data = this.props.current.data.objects[index];
window.history.replaceState( window.history.replaceState(
{ ...window.history.state, cid: data.cid }, { ...window.history.state, cid: data.cid },
"", "",
@ -229,28 +214,12 @@ export class GlobalCarousel extends React.Component {
_handlePrevious = () => { _handlePrevious = () => {
let index = this.state.index - 1; let index = this.state.index - 1;
if (index < 0) { if (index < 0) {
if ( index = this.props.objects.length - 1;
this.state.carouselType === "slate" &&
this.props.current.data &&
this.props.current.data.objects
) {
index = this.props.current.data.objects.length - 1;
} else if (
this.state.carouselType === "data" &&
this.props.viewer &&
this.props.viewer.library
) {
index = this.props.viewer.library[0].children.length - 1;
}
} }
this.setState({ index }); this.setState({ index });
if ( if (this.props.carouselType === "slate" && this.state.baseURL) {
this.state.carouselType === "slate" && const data = this.props.objects[index];
this.state.baseURL &&
this.props.current.data?.objects
) {
const data = this.props.current.data.objects[index];
window.history.replaceState( window.history.replaceState(
{ ...window.history.state, cid: data.cid }, { ...window.history.state, cid: data.cid },
"", "",
@ -260,9 +229,9 @@ export class GlobalCarousel extends React.Component {
}; };
_handleSave = async (details, index) => { _handleSave = async (details, index) => {
if (this.state.carouselType === "slate") { let objects = this.props.objects;
if (this.props.viewer.id !== this.props.current.data.ownerId || this.props.external) return; if (!this.props.isOwner || this.props.external) return;
let objects = this.props.current.data.objects; if (this.props.carouselType === "slate") {
objects[index] = { ...objects[index], ...details }; objects[index] = { ...objects[index], ...details };
const response = await Actions.updateSlate({ const response = await Actions.updateSlate({
id: this.props.current.id, id: this.props.current.id,
@ -270,9 +239,7 @@ export class GlobalCarousel extends React.Component {
}); });
Events.hasError(response); Events.hasError(response);
} }
if (this.state.carouselType === "data") { if (this.props.carouselType === "data") {
if (this.props.external) return;
let objects = this.props.viewer.library[0].children;
objects[index] = { ...objects[index], ...details }; objects[index] = { ...objects[index], ...details };
const response = await Actions.updateData({ const response = await Actions.updateData({
id: this.props.viewer.id, id: this.props.viewer.id,
@ -283,35 +250,16 @@ export class GlobalCarousel extends React.Component {
}; };
render() { render() {
if (!this.state.visible || !this.state.carouselType || this.state.index < 0) { if (!this.state.visible || !this.props.carouselType || this.state.index < 0) {
return null; return null;
} }
let data; let data = this.props.objects[this.state.index];
let isOwner; let isOwner = this.props.isOwner;
let isRepost; let isRepost;
if ( if (this.props.carouselType === "slate") {
this.state.carouselType === "slate" &&
this.props.current?.data?.objects?.length &&
this.state.index < this.props.current.data.objects.length
) {
data = this.props.current.data.objects[this.state.index];
data.cid = Strings.urlToCid(data.url);
isRepost = this.props.external ? false : this.props.current.data.ownerId !== data.ownerId; isRepost = this.props.external ? false : this.props.current.data.ownerId !== data.ownerId;
isOwner = this.props.external } else if (this.props.carouselType === "data") {
? false
: this.props.viewer.id === this.props.current.data.ownerId;
} else if (
this.state.carouselType === "data" &&
this.props.viewer?.library[0]?.children?.length &&
this.state.index < this.props.viewer.library[0].children.length
) {
data = this.props.viewer.library[0].children[this.state.index];
data.url = Strings.getCIDGatewayURL(data.cid); data.url = Strings.getCIDGatewayURL(data.cid);
isOwner = this.props.external ? false : true;
}
if (!data) {
this._handleClose();
return null;
} }
let slide = <SlateMediaObject data={data} />; let slide = <SlateMediaObject data={data} />;
return ( return (
@ -383,14 +331,14 @@ export class GlobalCarousel extends React.Component {
</span> </span>
</div> </div>
<span css={STYLES_MOBILE_HIDDEN}> <span css={STYLES_MOBILE_HIDDEN}>
{this.state.carouselType === "data" ? ( {this.props.carouselType === "data" ? (
<CarouselSidebarData <CarouselSidebarData
viewer={this.props.viewer} viewer={this.props.viewer}
display={this.state.showSidebar ? "block" : "none"} display={this.state.showSidebar ? "block" : "none"}
onUpdateViewer={this.props.onUpdateViewer} onUpdateViewer={this.props.onUpdateViewer}
onClose={this._handleClose} onClose={this._handleClose}
key={data.id} key={data.id}
slates={this.props.slates} slates={this.props.viewer?.slates || []}
onAction={this.props.onAction} onAction={this.props.onAction}
resources={this.props.resources} resources={this.props.resources}
data={data} data={data}
@ -407,7 +355,7 @@ export class GlobalCarousel extends React.Component {
onUpdateViewer={this.props.onUpdateViewer} onUpdateViewer={this.props.onUpdateViewer}
current={this.props.current} current={this.props.current}
key={data.id} key={data.id}
slates={this.props.slates} slates={this.props.viewer?.slates || []}
onClose={this._handleClose} onClose={this._handleClose}
onAction={this.props.onAction} onAction={this.props.onAction}
data={data} data={data}
@ -416,7 +364,6 @@ export class GlobalCarousel extends React.Component {
isOwner={isOwner} isOwner={isOwner}
isRepost={isRepost} isRepost={isRepost}
index={this.state.index} index={this.state.index}
external={this.props.external}
/> />
)} )}
</span> </span>

View File

@ -35,7 +35,6 @@ 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;
@ -52,7 +51,7 @@ export default class ProfilePage extends React.Component {
<WebsitePrototypeWrapper title={title} description={description} url={url} image={image}> <WebsitePrototypeWrapper title={title} description={description} url={url} image={image}>
<WebsitePrototypeHeader /> <WebsitePrototypeHeader />
<div css={STYLES_ROOT}> <div css={STYLES_ROOT}>
<Profile {...this.props} buttons={buttons} /> <Profile {...this.props} buttons={buttons} external />
</div> </div>
{this.state.visible && ( {this.state.visible && (
<div> <div>

View File

@ -13,7 +13,6 @@ import { ViewAllButton } from "~/components/core/ViewAll";
import { SlateLayout } from "~/components/core/SlateLayout"; import { SlateLayout } from "~/components/core/SlateLayout";
import { SlateLayoutMobile } from "~/components/core/SlateLayoutMobile"; import { SlateLayoutMobile } from "~/components/core/SlateLayoutMobile";
import { GlobalModal } from "~/components/system/components/GlobalModal"; import { GlobalModal } from "~/components/system/components/GlobalModal";
import { GlobalCarousel } from "~/components/system/components/GlobalCarousel";
import ProcessedText from "~/components/core/ProcessedText"; import ProcessedText from "~/components/core/ProcessedText";
import WebsitePrototypeWrapper from "~/components/core/WebsitePrototypeWrapper"; import WebsitePrototypeWrapper from "~/components/core/WebsitePrototypeWrapper";
@ -317,6 +316,7 @@ export default class SlatePage extends React.Component {
items={objects} items={objects}
fileNames={layouts && layouts.ver === "2.0" ? layouts.fileNames : false} fileNames={layouts && layouts.ver === "2.0" ? layouts.fileNames : false}
onSelect={this._handleSelect} onSelect={this._handleSelect}
external
/> />
) : ( ) : (
<SlateLayout <SlateLayout
@ -335,7 +335,6 @@ export default class SlatePage extends React.Component {
)} )}
</div> </div>
</div> </div>
<GlobalCarousel external current={this.props.slate} mobile={this.props.mobile} />
<GlobalModal /> <GlobalModal />
{this.state.visible && ( {this.state.visible && (
<div> <div>

View File

@ -4,6 +4,7 @@ import * as SVG from "~/common/svg";
import { ButtonPrimary } from "~/components/system/components/Buttons"; import { ButtonPrimary } from "~/components/system/components/Buttons";
import { FileTypeGroup } from "~/components/core/FileTypeIcon"; import { FileTypeGroup } from "~/components/core/FileTypeIcon";
import { PrimaryTabGroup, SecondaryTabGroup } from "~/components/core/TabGroup"; import { PrimaryTabGroup, SecondaryTabGroup } from "~/components/core/TabGroup";
import { GlobalCarousel } from "~/components/system/components/GlobalCarousel";
import ScenePage from "~/components/core/ScenePage"; import ScenePage from "~/components/core/ScenePage";
import DataView from "~/components/core/DataView"; import DataView from "~/components/core/DataView";
@ -45,6 +46,16 @@ export default class SceneFilesFolder extends React.Component {
/> />
} }
/> />
<GlobalCarousel
carouselType="data"
onUpdateViewer={this.props.onUpdateViewer}
resources={this.props.resources}
viewer={this.props.viewer}
objects={this.props.viewer?.library[0]?.children || []}
onAction={this.props.onAction}
mobile={this.props.mobile}
isOwner={true}
/>
<DataMeter <DataMeter
stats={this.props.viewer.stats} stats={this.props.viewer.stats}
style={{ marginBottom: 64 }} style={{ marginBottom: 64 }}

View File

@ -3,19 +3,12 @@ import * as Actions from "~/common/actions";
import * as SVG from "~/common/svg"; import * as SVG from "~/common/svg";
import { css } from "@emotion/react"; import { css } from "@emotion/react";
import { ButtonPrimary, ButtonSecondary } from "~/components/system/components/Buttons";
import { LoaderSpinner } from "~/components/system/components/Loaders"; import { LoaderSpinner } from "~/components/system/components/Loaders";
import ScenePage from "~/components/core/ScenePage"; import ScenePage from "~/components/core/ScenePage";
import Profile from "~/components/core/Profile"; import Profile from "~/components/core/Profile";
import EmptyState from "~/components/core/EmptyState"; import EmptyState from "~/components/core/EmptyState";
const STYLES_BUTTONS = css`
display: inline-flex;
flex-direction: row;
align-items: center;
`;
const STYLES_LOADER = css` const STYLES_LOADER = css`
display: flex; display: flex;
align-items: center; align-items: center;
@ -75,7 +68,7 @@ export default class SceneProfile extends React.Component {
window.history.replaceState(window.history.state, "A slate user", `/${targetUser.username}`); window.history.replaceState(window.history.state, "A slate user", `/${targetUser.username}`);
this.props.onUpdateData({ data: targetUser }); this.props.onUpdateData({ data: targetUser });
this.setState({ profile: targetUser }); this.setState({ profile: targetUser }); //martina: keep this here? or use the data itself rather than using this?
}; };
render() { render() {
@ -98,58 +91,15 @@ export default class SceneProfile extends React.Component {
); );
} }
return <ProfilePage {...this.props} data={this.state.profile} />; //FOR TARA: this is the change that'll allow it to update "automatically" if you're viewing your own profile and make changes
} //if you are viewing your own profile (aka this.state.profile.id === this.props.viewer.id), it'll set it to viewer directly, rather than a stale copy of viewer
}
class ProfilePage extends React.Component {
state = {
isFollowing: !!this.props.viewer.subscriptions.filter((entry) => {
return entry.target_user_id === this.props.data.id;
}).length,
};
componentDidUpdate = (prevProps) => {
if (
this.props.data.id !== prevProps.data.id ||
this.props.viewer.subscriptions !== prevProps.viewer.subscriptions
) {
this.setState({
isFollowing: !!this.props.viewer.subscriptions.filter((entry) => {
return entry.target_user_id === this.props.data.id;
}).length,
});
}
};
_handleFollow = async () => {
this.setState({ isFollowing: !this.state.isFollowing });
await Actions.createSubscription({
userId: this.props.data.id,
});
};
render() {
let buttons = (
<div css={STYLES_BUTTONS}>
{this.state.isFollowing ? (
<ButtonSecondary onClick={this._handleFollow}>Unfollow</ButtonSecondary>
) : (
<ButtonPrimary onClick={this._handleFollow}>Follow</ButtonPrimary>
)}
</div>
);
return ( return (
<ScenePage style={{ padding: `0` }}> <Profile
<Profile {...this.props}
{...this.props} creator={
onAction={this.props.onAction} this.state.profile.id === this.props.viewer.id ? this.props.viewer : this.state.profile
creator={this.props.data} }
sceneId={this.props.sceneId} />
buttons={this.props.viewer.username === this.props.data.username ? null : buttons}
isOwner={this.props.viewer.username === this.props.data.username}
/>
</ScenePage>
); );
} }
} }