Merge pull request #195 from filecoin-project/@martinalong/empty-states

Small tweaks to slates page and directory
This commit is contained in:
martinalong 2020-08-31 21:47:40 -07:00 committed by GitHub
commit 3ec1fc56eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 396 additions and 192 deletions

View File

@ -735,3 +735,20 @@ export const NoImage = (props) => (
<path d="M15.28 15.28C14.9481 15.765 14.5134 16.171 14.0068 16.469C13.5002 16.7669 12.9342 16.9496 12.3489 17.004C11.7637 17.0584 11.1737 16.9831 10.6209 16.7836C10.0681 16.5841 9.56601 16.2652 9.15042 15.8496C8.73483 15.434 8.41593 14.9319 8.2164 14.3791C8.01688 13.8263 7.94163 13.2363 7.99601 12.6511C8.05039 12.0658 8.23306 11.4998 8.53103 10.9932C8.829 10.4866 9.23495 10.0519 9.72 9.72M21 21H3C2.46957 21 1.96086 20.7893 1.58579 20.4142C1.21071 20.0391 1 19.5304 1 19V8C1 7.46957 1.21071 6.96086 1.58579 6.58579C1.96086 6.21071 2.46957 6 3 6H6L21 21ZM9 3H15L17 6H21C21.5304 6 22.0391 6.21071 22.4142 6.58579C22.7893 6.96086 23 7.46957 23 8V17.34L9 3Z" />
</svg>
);
export const Lock = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
height={props.height}
style={props.style}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M19 11H5C3.89543 11 3 11.8954 3 13V20C3 21.1046 3.89543 22 5 22H19C20.1046 22 21 21.1046 21 20V13C21 11.8954 20.1046 11 19 11Z" />
<path d="M7 11V7C7 5.67392 7.52678 4.40215 8.46447 3.46447C9.40215 2.52678 10.6739 2 12 2C13.3261 2 14.5979 2.52678 15.5355 3.46447C16.4732 4.40215 17 5.67392 17 7V11" />
</svg>
);

View File

@ -60,7 +60,12 @@ export default class Profile extends React.Component {
})
}
>
<SlatePreviewBlock slate={slate} />
<SlatePreviewBlock
slate={slate}
editing={
relation.slate.data.ownerId === this.props.viewer.id
}
/>
</div>
);
}

View File

@ -27,7 +27,7 @@ const STYLES_RIGHT = css`
const STYLES_HEADER = css`
box-sizing: border-box;
font-family: ${Constants.font.text};
font-family: ${Constants.font.medium};
font-size: ${Constants.typescale.lvl4};
padding: 0;
margin-bottom: 8px;

View File

@ -18,6 +18,7 @@ const STYLES_DROPDOWN = css`
overflow: hidden;
width: 100%;
scrollbar-width: none;
padding-bottom: 24px;
::-webkit-scrollbar {
display: none;
@ -165,36 +166,35 @@ export class SearchDropdown extends React.Component {
style={{ position: "absolute", left: "12px", top: "10px" }}
/>
</div>
{
<div
data-menu
ref={(c) => {
this._optionRoot = c;
}}
css={STYLES_DROPDOWN}
style={this.props.style}
>
{(this.props.results && this.props.results.length
? this.props.results
: this.props.defaultResults
).map((each, i) => (
<div
key={each.value.data.id}
css={STYLES_DROPDOWN_ITEM}
style={{
borderColor:
this.state.selectedIndex === i
? Constants.system.border
: Constants.system.white,
...this.props.itemStyle,
}}
onClick={() => this.props.onSelect(each.value)}
>
{each.component}
</div>
))}
</div>
}
<div
data-menu
ref={(c) => {
this._optionRoot = c;
}}
css={STYLES_DROPDOWN}
style={this.props.style}
>
{(this.props.results && this.props.results.length
? this.props.results
: this.props.defaultResults
).map((each, i) => (
<div
key={each.value.data.id}
css={STYLES_DROPDOWN_ITEM}
style={{
borderColor:
this.state.selectedIndex === i
? Constants.system.border
: Constants.system.white,
...this.props.itemStyle,
}}
onClick={() => this.props.onSelect(each.value)}
>
{each.component}
</div>
))}
</div>
</div>
);
}

View File

@ -1,8 +1,7 @@
//doable with vw. calculate vw - sidebar (if mobile size), divided by numItems = max-width and max-height
import React from "react";
import React, { Component } from "react";
import * as Constants from "~/common/constants";
import * as SVG from "~/common/svg";
import { css } from "@emotion/react";
import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview";
@ -58,20 +57,86 @@ const STYLES_BLOCK = css`
font-size: 12px;
text-align: left;
margin: 24px auto;
width: 95%;
width: 100%;
max-width: 980px;
cursor: pointer;
`;
const STYLES_SLATE_NAME = css`
const STYLES_TITLE_LINE = css`
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
font-size: ${Constants.typescale.lvl1};
margin-bottom: 16px;
`;
export default function SlatePreviewBlock(props) {
return (
<div css={STYLES_BLOCK}>
<div css={STYLES_SLATE_NAME}>{props.slate.data.name}</div>
<SlatePreviewRow {...props} previewStyle={props.previewStyle} />
</div>
);
const STYLES_BUTTON = css`
display: inline-block;
margin-left: 12px;
padding: 4px 8px;
cursor: pointer;
color: ${Constants.system.brand};
`;
const STYLES_COPY_INPUT = css`
pointer-events: none;
position: absolute;
tabindex: -1;
opacity: 0;
`;
export default class SlatePreviewBlock extends Component {
_ref;
state = {
copyable: "",
};
_handleCopy = (e, value) => {
console.log("copy");
this.setState({ copyable: value }, () => {
this._ref.select();
document.execCommand("copy");
});
e.stopPropagation();
};
render() {
return (
<div css={STYLES_BLOCK}>
<div css={STYLES_TITLE_LINE}>
<strong style={{ fontSize: Constants.typescale.lvl2 }}>
{this.props.slate.data.name}
</strong>
{this.props.editing && !this.props.slate.data.public ? (
<div style={{ marginLeft: "24px" }}>
<SVG.Lock height="16px" />
</div>
) : null}
{this.props.editing ? (
<div style={{ justifySelf: "end" }}>
<div
css={STYLES_BUTTON}
onClick={(e) => this._handleCopy(e, this.props.slate.id)}
>
Copy ID
</div>
</div>
) : null}
</div>
<SlatePreviewRow
{...this.props}
previewStyle={this.props.previewStyle}
/>
<input
readonly
ref={(c) => {
this._ref = c;
}}
value={this.state.copyable}
css={STYLES_COPY_INPUT}
/>
</div>
);
}
}

View File

@ -14,17 +14,16 @@ const STYLES_TAB = css`
margin-right: 24px;
cursor: pointer;
display: inline-block;
font-size: ${Constants.typescale.lvl2};
font-size: ${Constants.typescale.lvl1};
@media (max-width: ${Constants.sizes.mobile}px) {
font-size: ${Constants.typescale.lvl1};
margin-right: 12px;
}
`;
const STYLES_TAB_GROUP = css`
border-bottom: 1px solid ${Constants.system.gray};
margin: 24px 0px 24px 0px;
margin: 32px 0px 24px 0px;
`;
const STYLES_USER_ENTRY = css`
@ -75,6 +74,13 @@ function UserEntry({ user, button, onClick }) {
);
}
const STYLES_EMPTY_MESSAGE = css`
display: flex;
align-items: center;
justify-content: center;
height: 400px;
`;
export default class SceneDirectory extends React.Component {
state = {
loading: false,
@ -118,6 +124,126 @@ export default class SceneDirectory extends React.Component {
};
render() {
let requests = this.state.viewer.pendingTrusted
.filter((relation) => {
return !relation.data.verified;
})
.map((relation) => {
let button = (
<div
css={STYLES_ACTION_BUTTON}
onClick={() => this._handleAccept(relation.owner.id)}
>
Accept
</div>
);
return (
<UserEntry
user={relation.owner}
button={button}
onClick={() => {
this.props.onAction({
type: "NAVIGATE",
value: this.props.sceneId,
scene: "PROFILE",
data: relation.owner,
});
}}
/>
);
});
let trusted = this.state.viewer.pendingTrusted
.filter((relation) => {
return relation.data.verified;
})
.map((relation) => {
let button = (
<div
css={STYLES_ACTION_BUTTON}
onClick={() => this._handleDelete(relation.id)}
>
Remove
</div>
);
return (
<UserEntry
user={relation.owner}
button={button}
onClick={() => {
this.props.onAction({
type: "NAVIGATE",
value: this.props.sceneId,
scene: "PROFILE",
data: relation.owner,
});
}}
/>
);
});
if (!trusted) {
trusted = [];
}
trusted.push(
...this.state.viewer.trusted
.filter((relation) => {
return relation.data.verified;
})
.map((relation) => {
let button = (
<div
css={STYLES_ACTION_BUTTON}
onClick={() => this._handleDelete(relation.id)}
>
Remove
</div>
);
return (
<UserEntry
user={relation.user}
button={button}
onClick={() => {
this.props.onAction({
type: "NAVIGATE",
value: this.props.sceneId,
scene: "PROFILE",
data: relation.user,
});
}}
/>
);
})
);
let following = this.state.viewer.subscriptions
.filter((relation) => {
return !!relation.target_user_id;
})
.map((relation) => {
let button = (
<div
css={STYLES_ACTION_BUTTON}
onClick={() => this._handleFollow(relation.user.id)}
>
Unfollow
</div>
);
return (
<UserEntry
user={relation.user}
button={button}
onClick={() => {
this.props.onAction({
type: "NAVIGATE",
value: this.props.sceneId,
scene: "PROFILE",
data: relation.user,
});
}}
/>
);
});
return (
<ScenePage>
<ScenePageHeader title="Directory" />
@ -160,126 +286,42 @@ export default class SceneDirectory extends React.Component {
Following
</div>
</div>
{this.state.tab === "requests"
? this.state.viewer.pendingTrusted
.filter((relation) => {
return !relation.data.verified;
})
.map((relation) => {
let button = (
<div
css={STYLES_ACTION_BUTTON}
onClick={() => this._handleAccept(relation.owner.id)}
>
Accept
</div>
);
return (
<UserEntry
user={relation.owner}
button={button}
onClick={() => {
this.props.onAction({
type: "NAVIGATE",
value: this.props.sceneId,
scene: "PROFILE",
data: relation.owner,
});
}}
/>
);
})
: null}
{this.state.tab === "peers" ? (
<div>
{this.state.viewer.pendingTrusted
.filter((relation) => {
return relation.data.verified;
})
.map((relation) => {
let button = (
<div
css={STYLES_ACTION_BUTTON}
onClick={() => this._handleDelete(relation.id)}
>
Remove
</div>
);
return (
<UserEntry
user={relation.owner}
button={button}
onClick={() => {
this.props.onAction({
type: "NAVIGATE",
value: this.props.sceneId,
scene: "PROFILE",
data: relation.owner,
});
}}
/>
);
})}
{this.state.viewer.trusted
.filter((relation) => {
return relation.data.verified;
})
.map((relation) => {
let button = (
<div
css={STYLES_ACTION_BUTTON}
onClick={() => this._handleDelete(relation.id)}
>
Remove
</div>
);
return (
<UserEntry
user={relation.user}
button={button}
onClick={() => {
this.props.onAction({
type: "NAVIGATE",
value: this.props.sceneId,
scene: "PROFILE",
data: relation.user,
});
}}
/>
);
})}
</div>
{this.state.tab === "requests" ? (
requests.length ? (
requests
) : (
<div css={STYLES_EMPTY_MESSAGE}>
<div style={{ maxWidth: "400px", textAlign: "center" }}>
No requests at the moment! Once someone sends you a trust
request it'll appear here.
</div>
</div>
)
) : null}
{this.state.tab === "peers" ? (
trusted.length ? (
trusted
) : (
<div css={STYLES_EMPTY_MESSAGE}>
<div style={{ maxWidth: "400px", textAlign: "center" }}>
You have no peers yet. Get started by searching for your friends
and sending them a peer request!
</div>
</div>
)
) : null}
{this.state.tab === "following" ? (
following.length ? (
following
) : (
<div css={STYLES_EMPTY_MESSAGE}>
<div style={{ maxWidth: "400px", textAlign: "center" }}>
You are not following anybody. Get started by searching for your
friends and clicking follow!
</div>
</div>
)
) : null}
{this.state.tab === "following"
? this.state.viewer.subscriptions
.filter((relation) => {
return !!relation.target_user_id;
})
.map((relation) => {
let button = (
<div
css={STYLES_ACTION_BUTTON}
onClick={() => this._handleFollow(relation.user.id)}
>
Unfollow
</div>
);
return (
<UserEntry
user={relation.user}
button={button}
onClick={() => {
this.props.onAction({
type: "NAVIGATE",
value: this.props.sceneId,
scene: "PROFILE",
data: relation.user,
});
}}
/>
);
})
: null}
</ScenePage>
);
}

View File

@ -1,6 +1,7 @@
import * as React from "react";
import * as Constants from "~/common/constants";
import * as System from "~/components/system";
import * as SVG from "~/components/system/svg";
import { css } from "@emotion/react";
@ -8,6 +9,7 @@ import ScenePage from "~/components/core/ScenePage";
import ScenePageHeader from "~/components/core/ScenePageHeader";
import Section from "~/components/core/Section";
import SlatePreviewBlock from "~/components/core/SlatePreviewBlock";
import CircleButtonGray from "~/components/core/CircleButtonGray";
const STYLES_NUMBER = css`
font-family: ${Constants.font.semiBold};
@ -48,8 +50,37 @@ const STYLES_ACTION_BUTTON = css`
}
`;
const STYLES_TAB = css`
padding: 8px 8px 8px 0px;
margin-right: 24px;
cursor: pointer;
display: inline-block;
font-size: ${Constants.typescale.lvl1};
@media (max-width: ${Constants.sizes.mobile}px) {
margin-right: 12px;
}
`;
const STYLES_TAB_GROUP = css`
${"" /* border-bottom: 1px solid ${Constants.system.gray}; */}
margin: 32px 0px 24px 0px;
`;
// TODO(jim): Slates design.
export default class SceneSlates extends React.Component {
state = {
tab: "my",
};
_handleAdd = () => {
return this.props.onAction({
name: "Create slate",
type: "SIDEBAR",
value: "SIDEBAR_CREATE_SLATE",
});
};
render() {
// TODO(jim): Refactor later.
const slates = this.props.viewer.slates.map((each) => {
@ -68,37 +99,81 @@ export default class SceneSlates extends React.Component {
console.log(this.props);
return (
<ScenePage>
<ScenePageHeader title="Slates">
This scene is currently a work in progress.
</ScenePageHeader>
{this.props.data.children.map((slate) => (
<ScenePageHeader
title="Slates"
actions={
this.state.tab === "my" ? (
<CircleButtonGray
onMouseUp={this._handleAdd}
onTouchEnd={this._handleAdd}
style={{ marginLeft: 12 }}
>
<SVG.Plus height="16px" />
</CircleButtonGray>
) : null
}
/>
<div css={STYLES_TAB_GROUP}>
<div
key={slate.id}
onClick={() =>
this.props.onAction({
type: "NAVIGATE",
value: slate.id,
data: slate,
})
}
css={STYLES_TAB}
style={{
color:
this.state.tab === "my"
? Constants.system.pitchBlack
: Constants.system.gray,
}}
onClick={() => this.setState({ tab: "my" })}
>
<SlatePreviewBlock slate={slate} />
My Slates
</div>
))}
<div css={STYLES_ACTIONS}>
<span
css={STYLES_ACTION_BUTTON}
onClick={() =>
this.props.onAction({
name: "Create slate",
type: "SIDEBAR",
value: "SIDEBAR_CREATE_SLATE",
})
}
<div
css={STYLES_TAB}
style={{
color:
this.state.tab === "following"
? Constants.system.pitchBlack
: Constants.system.gray,
}}
onClick={() => this.setState({ tab: "following" })}
>
Create slate
</span>
Following
</div>
</div>
{this.state.tab === "my"
? this.props.data.children.map((slate) => (
<div
key={slate.id}
onClick={() =>
this.props.onAction({
type: "NAVIGATE",
value: slate.id,
data: slate,
})
}
>
<SlatePreviewBlock slate={slate} editing />
</div>
))
: null}
{this.state.tab === "following" ? "Coming soon!" : null}
{/* this.props.viewer.subscriptions
.filter((each) => {
return !!each.target_slate_id;
})
.map((relation) => (
<div
key={relation.slate.id}
onClick={() =>
this.props.onAction({
type: "NAVIGATE",
value: "V1_NAVIGATION_SLATE",
data: relation.slate,
})
}
>
<SlatePreviewBlock slate={relation.slate} />
</div>
)) */}
</ScenePage>
);
}