Merge pull request #895 from filecoin-project/@aminejv/new-top-nav

Update: new top navigation
This commit is contained in:
martinalong 2021-08-27 13:21:57 -07:00 committed by GitHub
commit 5563574dc3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 584 additions and 1063 deletions

View File

@ -105,6 +105,7 @@ export const semantic = {
bgBlurBlack: "rgba(0, 5, 10, 0.5)",
bgBlurBlackOP: "rgba(0, 5, 10, 0.85)",
bgBlurBlackTRN: "rgba(0, 5, 10, 0.3)",
bgBlurDark: "rgba(28, 29, 30, 0.7)",
bgBlurDark6: "rgba(28, 29, 30, 0.5)",
bgBlurDark6OP: "rgba(28, 29, 30, 0.85)",
bgBlurDark6TRN: "rgba(28, 29, 30, 0.3)",
@ -113,6 +114,7 @@ export const semantic = {
borderGrayLight: system.grayLight5,
borderDark: system.grayDark6,
borderGrayDark: system.grayDark5,
borderGrayLight4: system.grayLight4,
bgBlue: system.blueLight6,
bgGreen: system.greenLight6,

View File

@ -7,3 +7,8 @@ export const ACTIVITY_FEATURE_FLAG = NODE_ENV === "development" || NODE_ENV ===
export const URI_SHOVEL = process.env.NEXT_PUBLIC_URI_SHOVEL;
export const URI_FIJI = process.env.NEXT_PUBLIC_URI_FIJI;
export const URI_LENS = process.env.NEXT_PUBLIC_URI_LENS;
//NOTE(amine): Extensions links
export const EXTENSION_CHROME = process.env.NEXT_PUBLIC_EXTENSION_CHROME;
export const EXTENSION_FIREFOX = process.env.NEXT_PUBLIC_EXTENSION_FIREFOX;
export const EXTENSION_SAFARI = process.env.NEXT_PUBLIC_EXTENSION_SAFARI;

View File

@ -173,15 +173,16 @@ export const useField = ({
if (event) event(e);
});
/** ---------- NOTE(amine): Input Handlers ---------- */
const handleFieldChange = (e) =>
const setFieldValue = (value) =>
setState((prev) => ({
...prev,
value: e.target.value,
value,
error: undefined,
touched: false,
}));
/** ---------- NOTE(amine): Input Handlers ---------- */
const handleFieldChange = (e) => setFieldValue(e.target.value);
const handleOnBlur = () => {
// NOTE(amine): validate the inputs onBlur and touch the current input
let error = {};
@ -204,10 +205,10 @@ export const useField = ({
if (!onSubmit) return;
setState((prev) => ({ ...prev, isSubmitting: true }));
onSubmit(state.value)
.then(() => {
?.then(() => {
setState((prev) => ({ ...prev, isSubmitting: false }));
})
.catch(() => {
?.catch(() => {
setState((prev) => ({ ...prev, isSubmitting: false }));
});
};
@ -223,7 +224,7 @@ export const useField = ({
onSubmit: handleFormOnSubmit,
});
return { getFieldProps, value: state.value, isSubmitting: state.isSubmitting };
return { getFieldProps, value: state.value, setFieldValue, isSubmitting: state.isSubmitting };
};
export const useIntersection = ({ onIntersect, ref }, dependencies = []) => {

View File

@ -182,7 +182,7 @@ export const bytesToSize = (bytes, decimals = 2) => {
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${(bytes / Math.pow(k, i)).toFixed(dm)} ${sizes[i]}`;
return `${(bytes / Math.pow(k, i)).toFixed(dm)}${sizes[i]}`;
};
export const getRemainingTime = (seconds) => {

View File

@ -31,27 +31,6 @@ export const getImageUrlIfExists = (file, sizeLimit = null) => {
}
};
export const getPublicAndPrivateFiles = ({ viewer }) => {
let publicFileIds = [];
for (let slate of viewer.slates) {
if (slate.isPublic) {
publicFileIds.push(...slate.objects.map((obj) => obj.id));
}
}
let publicFiles = [];
let privateFiles = [];
let library = viewer.library || [];
for (let file of library) {
if (file.isPublic || publicFileIds.includes(file.id)) {
publicFiles.push(file);
} else {
privateFiles.push(file);
}
}
return { publicFiles, privateFiles };
};
export const generateNumberByStep = ({ min, max, step = 1 }) => {
var numbers = [];
for (var n = min; n <= max; n += step) {

View File

@ -9,46 +9,43 @@ import {
ApplicationUserControlsPopup,
} from "~/components/core/ApplicationUserControls";
import { css, keyframes } from "@emotion/react";
import { Boundary } from "~/components/system/components/fragments/Boundary";
import { PopoverNavigation } from "~/components/system";
import { css } from "@emotion/react";
import { DarkSymbol } from "~/common/logo";
import { Link } from "~/components/core/Link";
import { ButtonPrimary, ButtonTertiary } from "~/components/system/components/Buttons";
import { Match, Switch } from "~/components/utility/Switch";
import { Show } from "~/components/utility/Show";
import { useField, useMediaQuery } from "~/common/hooks";
import { Input } from "~/components/system";
import { AnimatePresence, motion } from "framer-motion";
const STYLES_NAV_LINKS = css`
display: flex;
flex-direction: row;
@media (max-width: ${Constants.sizes.mobile}px) {
flex-direction: column;
overflow: hidden;
const STYLES_SEARCH_COMPONENT = (theme) => css`
background-color: transparent;
border-radius: 8px;
box-shadow: none;
height: 100%;
input {
height: 100%;
padding: 0px;
}
&::placeholder {
color: ${theme.semantic.textGray};
}
`;
const STYLES_NAV_LINK = css`
color: ${Constants.semantic.textGray};
text-decoration: none;
transition: 200ms ease color;
const STYLES_DISMISS_BUTTON = (theme) => css`
display: block;
cursor: pointer;
padding: 4px 24px;
font-size: ${Constants.typescale.lvl1};
:hover {
color: ${Constants.system.blue};
}
@media (max-width: ${Constants.sizes.mobile}px) {
border-bottom: 1px solid ${Constants.system.grayLight2};
margin: 0px 24px;
padding: 12px 0px;
${Styles.P2};
}
${Styles.BUTTON_RESET};
color: ${theme.semantic.textGray};
`;
const STYLES_APPLICATION_HEADER_CONTAINER = (theme) => css`
const STYLES_APPLICATION_HEADER_BACKGROUND = (theme) => css`
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: -1;
background-color: ${theme.system.white};
box-shadow: 0 0 0 1px ${theme.semantic.bgGrayLight};
@ -60,17 +57,12 @@ const STYLES_APPLICATION_HEADER_CONTAINER = (theme) => css`
`;
const STYLES_APPLICATION_HEADER = css`
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
${"" /* justify-content: space-between; */}
width: 100%;
${Styles.HORIZONTAL_CONTAINER_CENTERED};
height: ${Constants.sizes.header}px;
${"" /* padding: 0 24px 0 16px; */}
padding: 0px 32px;
padding: 0px 24px;
@media (max-width: ${Constants.sizes.mobile}px) {
padding: 0px 24px;
padding: 0px 16px;
width: 100%;
}
`;
@ -83,11 +75,9 @@ const STYLES_LEFT = css`
`;
const STYLES_MIDDLE = css`
min-width: 10%;
width: 100%;
padding: 0 24px;
display: flex;
justify-content: center;
flex-grow: 1;
height: 100%;
padding: 0 12px;
`;
const STYLES_RIGHT = css`
@ -101,7 +91,7 @@ const STYLES_BACKGROUND = css`
position: absolute;
width: 100vw;
height: 100vh;
background-color: ${Constants.semantic.bgBlurDark6};
background-color: ${Constants.semantic.bgBlurDark};
pointer-events: auto;
@keyframes fade-in {
@ -116,307 +106,188 @@ const STYLES_BACKGROUND = css`
`;
const STYLES_UPLOAD_BUTTON = css`
${Styles.CONTAINER_CENTERED};
${Styles.BUTTON_RESET};
background-color: ${Constants.semantic.bgGrayLight};
border-radius: 8px;
width: 24px;
height: 24px;
cursor: pointer;
pointer-events: auto;
${Styles.CONTAINER_CENTERED};
`;
export default class ApplicationHeader extends React.Component {
keysPressed = {};
searchModKey = this.props.isMac ? (
<SVG.MacCommand height="12px" style={{ display: "block", paddingLeft: 8, paddingRight: 8 }} />
) : (
<span style={{ display: "block", paddingLeft: 8, paddingRight: 8 }}>Ctrl</span>
);
state = {
export default function ApplicationHeader({ viewer, onAction }) {
const [state, setState] = React.useState({
showDropdown: false,
popup: null,
isRefreshing: false,
};
});
componentDidMount = () => {
window.addEventListener("keydown", this._handleKeyDown);
window.addEventListener("keyup", this._handleKeyUp);
};
_handleKeyDown = (e) => {
let prevValue = this.keysPressed[e.key];
if (prevValue) {
return;
}
this.keysPressed[e.key] = true;
if ((this.keysPressed["Control"] || this.keysPressed["Meta"]) && this.keysPressed["f"]) {
e.preventDefault();
e.stopPropagation();
this._handleCreateSearch();
const _handleTogglePopup = (value) => {
if (!value || state.popup === value) {
setState((prev) => ({ ...prev, popup: null }));
} else {
setState((prev) => ({ ...prev, popup: value, showDropdown: false }));
}
};
_handleKeyUp = (e) => {
this.keysPressed = {};
};
_handleCreateSearch = (e) => {
this.setState({ showDropdown: false });
const handleCreateSearch = (searchQuery) => {
setState((prev) => ({ ...prev, showDropdown: false }));
Events.dispatchCustomEvent({
name: "show-search",
detail: {},
detail: {
initialValue: searchQuery,
},
});
};
_handleTogglePopup = (value) => {
if (!value || this.state.popup === value) {
this.setState({ popup: null });
} else {
this.setState({ popup: value, showDropdown: false });
}
};
const {
getFieldProps,
value: searchQuery,
setFieldValue,
} = useField({
initialValue: "",
onSubmit: handleCreateSearch,
});
render() {
const navigation = this.props.navigation.filter((item) => item.mainNav);
const handleUpload = React.useCallback(() => {
onAction({ type: "SIDEBAR", value: "SIDEBAR_ADD_FILE_TO_BUCKET" });
}, [onAction]);
if (!this.props.viewer) {
const searchComponent = (
<div
onClick={this._handleCreateSearch}
css={Styles.HORIZONTAL_CONTAINER_CENTERED}
style={{ border: "none", pointerEvents: "auto", cursor: "pointer", paddingLeft: 12 }}
>
<SVG.Search
height="16px"
style={{ color: Constants.semantic.textGrayDark, marginRight: 8 }}
/>
<span css={Styles.P2} style={{ color: Constants.semantic.textGray }}>
Search Slate...
</span>
</div>
);
const handleDismissSearch = () => setFieldValue("");
//NOTE(martina): signed out view
return (
<header css={STYLES_APPLICATION_HEADER_CONTAINER}>
<div css={STYLES_APPLICATION_HEADER}>
<div css={STYLES_LEFT}>
<Link onAction={this.props.onAction} href="/_/data" style={{ pointerEvents: "auto" }}>
const { mobile } = useMediaQuery();
const isSignedOut = !viewer;
const isSearching = searchQuery.length !== 0;
return (
<header style={{ position: "relative" }}>
<div css={STYLES_APPLICATION_HEADER}>
<div css={STYLES_LEFT}>
<Show
when={viewer}
fallback={
<Link onAction={onAction} href="/_/data" style={{ pointerEvents: "auto" }}>
<DarkSymbol style={{ height: 24, display: "block" }} />
</Link>
<div css={Styles.MOBILE_ONLY}>{searchComponent}</div>
</div>
<div css={STYLES_MIDDLE}>
<span css={Styles.MOBILE_HIDDEN}>{searchComponent}</span>
</div>
<div css={STYLES_RIGHT}>
<Link
href="/_/auth?tab=signin"
onAction={this.props.onAction}
style={{ pointerEvents: "auto" }}
>
<span css={Styles.MOBILE_HIDDEN}>
<ButtonTertiary
style={{
padding: "0px 12px",
minHeight: "30px",
fontFamily: Constants.font.text,
marginRight: 8,
}}
>
Sign in
</ButtonTertiary>
</span>
</Link>
<Link
href="/_/auth?tab=signup"
onAction={this.props.onAction}
style={{ pointerEvents: "auto" }}
>
<ButtonPrimary
style={{
padding: "0px 12px",
minHeight: "30px",
fontFamily: Constants.font.text,
}}
>
Sign up
</ButtonPrimary>
</Link>
</div>
</div>
</header>
);
}
const mobilePopup = (
// <Boundary
// captureResize={false}
// captureScroll={false}
// enabled={this.state.popup === "profile"}
// onOutsideRectEvent={(e) => {
// e.stopPropagation();
// e.preventDefault();
// this._handleTogglePopup(e);
// }}
// >
<>
}
>
<ApplicationUserControls
popup={mobile ? false : state.popup}
onTogglePopup={_handleTogglePopup}
viewer={viewer}
onAction={onAction}
/>
</Show>
</div>
<div css={STYLES_MIDDLE}>
{/**TODO: update Search component */}
<Input
containerStyle={{ height: "100%" }}
full
placeholder={`Search ${!viewer ? "slate.host" : ""}`}
inputCss={STYLES_SEARCH_COMPONENT}
onSubmit={handleCreateSearch}
name="search"
{...getFieldProps()}
/>
</div>
<div css={STYLES_RIGHT}>
<Actions
isSearching={isSearching}
isSignedOut={isSignedOut}
onAction={onAction}
onUpload={handleUpload}
onDismissSearch={handleDismissSearch}
/>
</div>
</div>
<Show when={mobile && state.popup === "profile"}>
<ApplicationUserControlsPopup
popup={this.state.popup}
onTogglePopup={this._handleTogglePopup}
viewer={this.props.viewer}
onAction={this.props.onAction}
style={{ pointerEvents: "auto", paddingBottom: 16 }}
popup={state.popup}
onTogglePopup={_handleTogglePopup}
viewer={viewer}
onAction={onAction}
style={{ pointerEvents: "auto" }}
/>
<div css={STYLES_BACKGROUND} />
</>
// </Boundary>
);
const mobileDropdown = (
<>
<Boundary
captureResize={false}
captureScroll={false}
enabled={this.state.showDropdown}
onOutsideRectEvent={(e) => {
e.stopPropagation();
e.preventDefault();
this.setState({ showDropdown: false });
}}
>
<div css={STYLES_NAV_LINKS} style={{ pointerEvents: "auto", paddingBottom: 16 }}>
{this.props.navigation
.filter((item) => item.mainNav)
.map((item) => (
<Link
key={item.id}
href={item.pathname}
onAction={this.props.onAction}
onClick={() => this.setState({ showDropdown: false })}
>
<div
css={STYLES_NAV_LINK}
style={{
color: this.props.activePage === item.id ? Constants.system.black : null,
}}
>
{item.name}
</div>
</Link>
))}
<div
onClick={this._handleCreateSearch}
css={STYLES_NAV_LINK}
style={{ border: "none" }}
>
Search
</div>
</div>
</Boundary>
<div css={STYLES_BACKGROUND} />
</>
);
return (
<>
<div style={{ width: "100vw", height: "100vh", position: "absolute" }} />
<header css={STYLES_APPLICATION_HEADER_CONTAINER}>
<span css={Styles.MOBILE_HIDDEN}>
<div css={STYLES_APPLICATION_HEADER}>
<div css={STYLES_LEFT}>
<Link
onAction={this.props.onAction}
href="/_/data"
style={{ pointerEvents: "auto" }}
>
<DarkSymbol style={{ height: 24, display: "block" }} />
</Link>
<div
css={STYLES_UPLOAD_BUTTON}
onClick={() => {
this.props.onAction({
type: "SIDEBAR",
value: "SIDEBAR_ADD_FILE_TO_BUCKET",
});
}}
style={{ marginRight: 24, marginLeft: 24 }}
>
<SVG.Plus height="16px" />
</div>
</div>
<div css={STYLES_MIDDLE}>
<div css={STYLES_NAV_LINKS} style={{ pointerEvents: "auto" }}>
{navigation.map((item, i) => (
<Link key={item.id} href={item.pathname} onAction={this.props.onAction}>
<div
css={STYLES_NAV_LINK}
style={{
color: this.props.activePage === item.id ? Constants.system.black : null,
}}
>
{item.name}
</div>
</Link>
))}
<div onClick={this._handleCreateSearch} css={STYLES_NAV_LINK}>
Search
</div>
</div>
</div>
<div css={STYLES_RIGHT}>
<span style={{ pointerEvents: "auto", marginLeft: 24 }}>
<ApplicationUserControls
popup={this.state.popup}
onTogglePopup={this._handleTogglePopup}
viewer={this.props.viewer}
onAction={this.props.onAction}
/>
</span>
</div>
</div>
</span>
<span css={Styles.MOBILE_ONLY}>
<div css={STYLES_APPLICATION_HEADER}>
<div css={STYLES_LEFT}>
<div
css={Styles.ICON_CONTAINER}
style={{ pointerEvents: "auto" }}
onClick={() =>
this.setState({ showDropdown: !this.state.showDropdown, popup: null })
}
>
<SVG.MenuMinimal height="16px" />
</div>
</div>
<div css={STYLES_MIDDLE}>
<Link
onAction={this.props.onAction}
href="/_/data"
style={{ pointerEvents: "auto" }}
>
<DarkSymbol style={{ height: 24, display: "block" }} />
</Link>
</div>
<div css={STYLES_RIGHT}>
<span style={{ pointerEvents: "auto", marginLeft: 24 }}>
<ApplicationUserControls
popup={false}
onTogglePopup={this._handleTogglePopup}
viewer={this.props.viewer}
onAction={this.props.onAction}
/>
</span>
</div>
</div>
{this.state.popup === "profile"
? mobilePopup
: this.state.showDropdown
? mobileDropdown
: null}
</span>
</header>
</>
);
}
</Show>
{/** NOTE(amine): a fix for a backdrop-filter bug where the filter doesn't take any effects.
* It happens when we have two elements using backdrop-filter with a parent-child relationship */}
<div css={STYLES_APPLICATION_HEADER_BACKGROUND} />
</header>
);
}
const Actions = ({ isSignedOut, isSearching, onAction, onUpload, onDismissSearch }) => {
const authActions = React.useMemo(
() => (
<>
<Link href="/_/auth?tab=signin" onAction={onAction} style={{ pointerEvents: "auto" }}>
<span css={Styles.MOBILE_HIDDEN}>
<ButtonTertiary
style={{
padding: "0px 12px",
minHeight: "30px",
fontFamily: Constants.font.text,
marginRight: 8,
}}
>
Sign in
</ButtonTertiary>
</span>
</Link>
<Link href="/_/auth?tab=signup" onAction={onAction} style={{ pointerEvents: "auto" }}>
<ButtonPrimary
style={{ padding: "0px 12px", minHeight: "30px", fontFamily: Constants.font.text }}
>
Sign up
</ButtonPrimary>
</Link>
</>
),
[onAction]
);
const uploadAction = React.useMemo(
() => (
<button css={STYLES_UPLOAD_BUTTON} onClick={onUpload}>
<SVG.Plus height="16px" />
</button>
),
[onUpload]
);
return (
<AnimatePresence>
<Switch
fallback={
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ y: 10, opacity: 0 }}
>
{uploadAction}
</motion.div>
}
>
<Match when={isSignedOut}>{authActions}</Match>
<Match when={isSearching}>
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ y: -10, opacity: 0 }}
>
<button
onClick={onDismissSearch}
style={{ marginRight: 4 }}
css={STYLES_DISMISS_BUTTON}
>
<SVG.Dismiss style={{ display: "block" }} height={16} width={16} />
</button>
</motion.div>
</Match>
</Switch>
</AnimatePresence>
);
};

View File

@ -31,7 +31,6 @@ const STYLES_NO_VISIBLE_SCROLL = css`
const STYLES_HEADER = css`
z-index: ${Constants.zindex.header};
height: ${Constants.sizes.header}px;
pointer-events: none;
width: 100vw;
position: fixed;
right: 0;
@ -45,12 +44,10 @@ const STYLES_CONTENT = css`
min-width: 10%;
min-height: 100vh;
position: relative;
margin-top: ${Constants.sizes.topOffset}px;
@media (max-width: ${Constants.sizes.mobile}px) {
padding-left: 0px;
padding: 0 0 88px 0;
margin-top: calc(${Constants.sizes.topOffset}px + 36px);
}
`;
@ -266,12 +263,15 @@ export default class ApplicationLayout extends React.Component {
<div css={STYLES_CONTENT}>
<GlobalTooltip />
{this.props.header && (
<div
css={STYLES_HEADER}
style={{ top: this.props.isMobile ? this.state.headerTop : null }}
>
{this.props.header}
</div>
<>
<div style={{ visibility: "hidden" }}>{this.props.header}</div>
<div
css={STYLES_HEADER}
style={{ top: this.props.isMobile ? this.state.headerTop : null }}
>
{this.props.header}
</div>
</>
)}
<Alert
noWarning={

View File

@ -2,18 +2,19 @@ import * as React from "react";
import * as Constants from "~/common/constants";
import * as Styles from "~/common/styles";
import * as UserBehaviors from "~/common/user-behaviors";
import * as Strings from "~/common/strings";
import * as Environment from "~/common/environment";
import { PopoverNavigation } from "~/components/system";
import { ButtonPrimaryFull, PopoverNavigation } from "~/components/system";
import { css } from "@emotion/react";
import { Link } from "~/components/core/Link";
import ProfilePhoto from "~/components/core/ProfilePhoto";
import { Boundary } from "~/components/system/components/fragments/Boundary";
import { useIntercom } from "react-use-intercom";
import { H4, P3 } from "~/components/system/components/Typography";
import ProfilePhoto from "~/components/core/ProfilePhoto";
const STYLES_HEADER = css`
position: relative;
margin-left: 16px;
@media (max-width: ${Constants.sizes.mobile}px) {
padding: 0px;
@ -21,113 +22,126 @@ const STYLES_HEADER = css`
}
`;
const STYLES_PROFILE = css`
position: relative;
display: flex;
align-items: center;
justify-content: flex-start;
min-width: 10%;
width: 204px;
color: ${Constants.system.black};
background-color: ${Constants.system.white};
font-size: 12px;
text-decoration: none;
border-radius: 4px;
min-height: 48px;
cursor: pointer;
border: 1px solid rgba(229, 229, 229, 0.5);
box-shadow: 0 0 7px 0 rgba(0, 0, 0, 0.03);
@media (max-width: ${Constants.sizes.mobile}px) {
display: none;
}
`;
const STYLES_PROFILE_MOBILE = css`
display: flex;
align-items: center;
`;
const STYLES_PROFILE_IMAGE = css`
background-color: ${Constants.semantic.bgLight};
background-size: cover;
background-position: 50% 50%;
flex-shrink: 0;
height: 24px;
width: 24px;
border-radius: 8px;
cursor: pointer;
const STYLES_POPOVER_CONTANIER = (theme) => css`
padding: 16px 20px;
border-radius: 16px;
border: 1px solid ${theme.semantic.borderGrayLight4};
box-shadow: ${theme.shadow.lightLarge};
${"" /* @media (max-width: ${Constants.sizes.mobile}px) {
height: 24px;
width: 24px;
} */}
`;
const STYLES_PROFILE_USERNAME = css`
min-width: 10%;
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 12px;
user-select: none;
font-family: ${Constants.font.medium};
font-size: ${Constants.typescale.lvl1};
`;
const STYLES_ITEM_BOX_MOBILE = css`
display: flex;
align-items: center;
justify-content: center;
padding: 4px;
background-color: ${Constants.system.white};
cursor: pointer;
border-radius: 4px;
border-left: 2px solid ${Constants.semantic.bgLight};
`;
const STYLES_ITEM_BOX = css`
display: flex;
align-items: center;
justify-content: center;
height: 100%;
padding: 8px;
padding-right: 9px;
transition: 200ms ease all;
border-left: 2px solid ${Constants.semantic.bgLight};
:hover {
color: ${Constants.system.blue};
@supports ((-webkit-backdrop-filter: blur(75px)) or (backdrop-filter: blur(75px))) {
background-color: ${theme.semantic.bgBlurWhite};
-webkit-backdrop-filter: blur(75px);
backdrop-filter: blur(75px);
}
`;
// const OpenIntercom = ({ user, onTogglePopup }) => {
// const { show, update } = useIntercom();
const STYLES_POPOVER_SECTION = (theme) => css`
border-top: 1px solid ${theme.semantic.borderGrayLight4};
border-bottom: none;
padding: 0;
margin: 0;
padding-top: 8px;
padding-bottom: 8px;
// return (
// <span
// style={{ cursor: "pointer", display: "block" }}
// onClick={() => {
// onTogglePopup();
// update({
// name: user.data.name,
// email: user.email,
// customAttributes: {
// slate_userid: user.id,
// username: user.username,
// },
// });
// show();
// }}
// >
// Help
// </span>
// );
// };
p {
display: block;
width: 100%;
}
:last-child {
padding-bottom: 0px;
}
* + * {
margin-top: 4px;
}
`;
const STYLES_POPOVER_SECTION_ITEM = (theme) => css`
position: relative;
padding: 0px;
width: calc(100% + 16px);
left: -8px;
a {
display: block;
}
`;
const STYLES_SECTION_ITEM_HOVER = (theme) => css`
padding: 1px 8px 3px;
border-radius: 8px;
&:hover {
background-color: ${theme.system.grayLight4};
}
`;
const STYLES_DATAMETER_WRAPPER = (theme) => css`
width: 100%;
min-width: 240px;
height: 8px;
background-color: ${theme.semantic.bgBlurWhiteTRN};
border: 1px solid ${theme.semantic.borderGrayLight4};
border-radius: 2px;
overflow: hidden;
`;
const STYLES_DATAMETER = (theme) => css`
height: 100%;
background-color: ${theme.system.blue};
border-radius: 2px;
`;
const DataMeter = ({ bytes = 1000, maximumBytes = 4000, ...props }) => {
const percentage = bytes / maximumBytes;
return (
<div css={STYLES_DATAMETER_WRAPPER} {...props}>
<div
style={{
width: `calc(${percentage} * 100%)`,
}}
css={STYLES_DATAMETER}
/>
</div>
);
};
export class ApplicationUserControlsPopup extends React.Component {
state = {
isExtensionDownloaded: false,
};
componentDidMount() {
if (document) {
const isExtensionDownloaded = this._checkIfExtensionIsDownloaded();
this.setState({ isExtensionDownloaded });
}
}
_checkIfExtensionIsDownloaded = () => {
const extensionElement = document.getElementById("browser_extension");
if (!extensionElement) return false;
return extensionElement.className.includes("isDownloaded");
};
_handleExtensionDownloadLink = () => {
const testUserAgent = (regex) => regex.test(window.navigator.userAgent);
const isFirefox = testUserAgent(/firefox/i);
const firefoxLink = Environment.EXTENSION_FIREFOX;
if (isFirefox && firefoxLink) return window.open(firefoxLink, "_blank");
const isSafari = testUserAgent(/safari/i);
const safariLink = Environment.EXTENSION_SAFARI;
if (isSafari && safariLink) return window.open(safariLink, "_blank");
window.open(Environment.EXTENSION_CHROME, "_blank");
};
_handleAction = (props) => {
this.props.onTogglePopup();
this.props.onAction(props);
@ -141,153 +155,176 @@ export class ApplicationUserControlsPopup extends React.Component {
};
render() {
if (this.props.popup === "profile") {
const topSection = (
<div css={Styles.HORIZONTAL_CONTAINER} style={{ marginBottom: 14 }}>
<div style={{ marginRight: "16px", cursor: "default" }}>
<ProfilePhoto user={this.props.viewer} size={46} />
</div>
if (this.props.popup !== "profile") return null;
<div
css={Styles.VERTICAL_CONTAINER}
style={{
height: 46,
justifyContent: "space-between",
}}
>
<div css={Styles.H4}>
{this.props.viewer.data.name || `@${this.props.viewer.username}`}
</div>
<div css={Styles.HORIZONTAL_CONTAINER}>
<span css={Styles.P3} style={{ marginRight: 8 }}>{`${
this.props.viewer.library.length
} File${this.props.viewer.library.length === 1 ? "" : "s"}`}</span>
<span css={Styles.P3}>{`${this.props.viewer.slates.length} Collection${
this.props.viewer.slates.length === 1 ? "" : "s"
}`}</span>
</div>
const username = this.props.viewer.data.name || `@${this.props.viewer.username}`;
const objectsLength = this.props.viewer.library.length;
const { stats } = this.props.viewer;
const topSection = (
<Link href="/_/data" onAction={this._handleAction}>
<div style={{ marginBottom: 16 }} css={Styles.VERTICAL_CONTAINER_CENTERED}>
<ProfilePhoto user={this.props.viewer} style={{ borderRadius: "12px" }} size={48} />
<H4 color="textBlack" style={{ marginTop: 10 }}>
{username}
</H4>
<div style={{ marginTop: 6 }} css={Styles.HORIZONTAL_CONTAINER}>
<P3 color="textBlack" style={{ marginRight: 8 }}>
{objectsLength} {Strings.pluralize("Object", objectsLength)}
</P3>
<P3 color="textBlack" style={{ marginLeft: 8 }}>
{Strings.bytesToSize(stats.bytes, 0)} of {Strings.bytesToSize(stats.maximumBytes, 0)}{" "}
Stored
</P3>
</div>
<DataMeter
bytes={stats.bytes}
maximumBytes={stats.maximumBytes}
style={{ marginTop: 8 }}
/>
</div>
);
</Link>
);
const navigation = [
[
{
text: (
const ExtensionButton = (
<div css={Styles.MOBILE_HIDDEN}>
<ButtonPrimaryFull
style={{
padding: "0px 12px",
marginTop: "4px",
marginBottom: "28px",
minHeight: "30px",
fontFamily: Constants.font.text,
}}
onClick={this._handleExtensionDownloadLink}
>
Install Slate browser extension
</ButtonPrimaryFull>
</div>
);
const navigation = [
[
{
text: (
<div css={STYLES_SECTION_ITEM_HOVER}>
<Link href={`/$/user/${this.props.viewer.id}`} onAction={this._handleAction}>
Profile
</Link>
),
},
{
text: (
</div>
),
},
{
text: (
<div css={STYLES_SECTION_ITEM_HOVER}>
<Link href={"/_/directory"} onAction={this._handleAction}>
Directory
</Link>
),
},
],
[
{
text: (
</div>
),
},
],
[
{
text: (
<div css={STYLES_SECTION_ITEM_HOVER}>
<Link href={"/_/filecoin"} onAction={this._handleAction}>
Filecoin
</Link>
),
},
{
text: (
</div>
),
},
{
text: (
<div css={STYLES_SECTION_ITEM_HOVER}>
<Link href={"/_/storage-deal"} onAction={this._handleAction}>
Storage deal
</Link>
),
},
{
text: (
</div>
),
},
{
text: (
<div css={STYLES_SECTION_ITEM_HOVER}>
<Link href={"/_/api"} onAction={this._handleAction}>
API
</Link>
),
},
],
[
{
text: (
</div>
),
},
],
[
{
text: (
<div css={STYLES_SECTION_ITEM_HOVER}>
<Link href={"/_/settings"} onAction={this._handleAction}>
Settings
</Link>
),
</div>
),
},
{
text: <div css={STYLES_SECTION_ITEM_HOVER}> Sign out</div>,
onClick: (e) => {
this._handleSignOut(e);
},
],
[
// {
// text: (
// <OpenIntercom
// style={{ display: "block" }}
// user={this.props.viewer}
// onTogglePopup={this.props.onTogglePopup}
// />
// ),
// },
{
text: "Sign out",
onClick: (e) => {
this._handleSignOut(e);
},
},
],
];
},
...(!this.state.isExtensionDownloaded ? [{ text: ExtensionButton }] : []),
],
];
return (
<>
<div css={Styles.MOBILE_ONLY}>
<Boundary
captureResize={true}
captureScroll={false}
enabled={this.props.popup === "profile"}
onOutsideRectEvent={() => this.props.onTogglePopup()}
>
<PopoverNavigation
style={{
width: "max-content",
position: "relative",
border: "none",
boxShadow: "none",
width: "100vw",
background: "none",
pointerEvents: "auto",
}}
css={Styles.H4}
itemStyle={{ fontSize: Constants.typescale.lvl0 }}
topSection={topSection}
navigation={navigation}
/>
</Boundary>
</div>
<div css={Styles.MOBILE_HIDDEN}>
<Boundary
captureResize={true}
captureScroll={false}
enabled={this.props.popup === "profile"}
onOutsideRectEvent={() => this.props.onTogglePopup()}
>
<PopoverNavigation
style={{
top: 36,
right: 0,
width: "max-content",
}}
css={Styles.H4}
itemStyle={{ fontSize: Constants.typescale.lvl0 }}
topSection={topSection}
navigation={navigation}
/>
</Boundary>
</div>
</>
);
}
return null;
return (
<>
<div css={Styles.MOBILE_ONLY}>
<Boundary
captureResize={true}
captureScroll={false}
enabled={this.props.popup === "profile"}
onOutsideRectEvent={() => this.props.onTogglePopup()}
>
<PopoverNavigation
style={{
position: "relative",
border: "none",
boxShadow: "none",
background: "none",
pointerEvents: "auto",
}}
containerCss={STYLES_POPOVER_CONTANIER}
sectionCss={STYLES_POPOVER_SECTION}
sectionItemCss={STYLES_POPOVER_SECTION_ITEM}
css={Styles.H4}
itemStyle={{ fontSize: Constants.typescale.lvl0 }}
topSection={topSection}
navigation={navigation}
/>
</Boundary>
</div>
<div css={Styles.MOBILE_HIDDEN}>
<Boundary
captureResize={true}
captureScroll={false}
enabled={this.props.popup === "profile"}
onOutsideRectEvent={() => this.props.onTogglePopup()}
>
<PopoverNavigation
style={{
top: 34,
left: "-12px",
width: "max-content",
}}
containerCss={STYLES_POPOVER_CONTANIER}
sectionCss={STYLES_POPOVER_SECTION}
sectionItemCss={STYLES_POPOVER_SECTION_ITEM}
css={Styles.H4}
itemStyle={{ fontSize: Constants.typescale.lvl0 }}
topSection={topSection}
navigation={navigation}
/>
</Boundary>
</div>
</>
);
}
}
@ -296,14 +333,14 @@ export class ApplicationUserControls extends React.Component {
let tooltip = <ApplicationUserControlsPopup {...this.props} />;
return (
<div css={STYLES_HEADER}>
<div
css={STYLES_PROFILE_MOBILE}
<button
css={[Styles.BUTTON_RESET, STYLES_PROFILE_MOBILE]}
onClick={() => this.props.onTogglePopup("profile")}
style={{ position: "relative", cursor: "pointer" }}
>
<ProfilePhoto user={this.props.viewer} size={24} />
{this.props.popup === "profile" ? tooltip : null}
</div>
<ProfilePhoto user={this.props.viewer} style={{ borderRadius: "8px" }} size={24} />
</button>
{this.props.popup === "profile" ? tooltip : null}
</div>
);
}

View File

@ -34,14 +34,7 @@ function BoringAvatar({ avatarCss, ...props }) {
let avatarUrl = `https://source.boringavatars.com/marble/${props.size}/${props.userId}?square&colors=${colors}`;
return (
<Dismissible captureResize={false} captureScroll={true}>
<img
src={avatarUrl}
css={[avatarCss, STYLES_AVATAR]}
style={{
cursor: "pointer",
}}
alt="profile preview"
/>
<img src={avatarUrl} css={[avatarCss, STYLES_AVATAR]} alt="profile preview" />
</Dismissible>
);
}
@ -55,7 +48,6 @@ function UploadedAvatar({ avatarCss, ...props }) {
style={{
...props.style,
backgroundImage: `url('${props.url}')`,
cursor: "pointer",
}}
>
{props.visible ? props.popover : null}
@ -64,7 +56,7 @@ function UploadedAvatar({ avatarCss, ...props }) {
);
}
export default function ProfilePhoto({ size, ...props }) {
export default function ProfilePhoto({ size, style, ...props }) {
// NOTE(amine): will calculate only when the size prop changes
const memoizedSizeProp = useMemoCompare(size, isEqual);
const STYLES_SIZE = React.useMemo(() => {
@ -78,9 +70,9 @@ export default function ProfilePhoto({ size, ...props }) {
return (
<>
{props.user.data.photo ? (
<UploadedAvatar url={props.user.data.photo} avatarCss={STYLES_SIZE} />
<UploadedAvatar url={props.user.data.photo} style={style} avatarCss={STYLES_SIZE} />
) : (
<BoringAvatar userId={props.user.id} avatarCss={STYLES_SIZE} />
<BoringAvatar userId={props.user.id} style={style} avatarCss={STYLES_SIZE} />
)}
</>
);

View File

@ -14,8 +14,8 @@ const STYLES_SCENE = css`
}
`;
export const ScenePage = (props) => (
<div css={STYLES_SCENE} {...props}>
export const ScenePage = ({ css, ...props }) => (
<div css={[STYLES_SCENE, css]} {...props}>
<div style={props.contentstyle}>{props.children}</div>
</div>
);

View File

@ -477,8 +477,8 @@ export class SearchModal extends React.Component {
this._handleHide();
};
_handleShow = async () => {
this.setState({ modal: true });
_handleShow = async (e) => {
this.setState({ modal: true, inputValue: e.detail.initialValue });
await this.fillLocalDirectory();
this.setState({ loading: false });
if (!this.initialized) {

View File

@ -1,8 +1,8 @@
import * as React from "react";
import * as Constants from "~/common/constants";
import * as Styles from "~/common/styles";
import { css } from "@emotion/react";
import { P2 } from "~/components/system/components/Typography";
const STYLES_POPOVER = css`
z-index: ${Constants.zindex.tooltip};
@ -19,8 +19,8 @@ const STYLES_POPOVER = css`
border: 1px solid ${Constants.semantic.borderGrayLight};
`;
const STYLES_POPOVER_SECTION = css`
border-bottom: 1px solid ${Constants.semantic.borderGrayLight};
const STYLES_POPOVER_SECTION = (theme) => css`
border-bottom: 1px solid ${theme.semantic.borderGrayLight};
padding-bottom: 6px;
margin-bottom: 6px;
@ -44,33 +44,42 @@ const STYLES_POPOVER_ITEM = css`
}
`;
export class PopoverNavigation extends React.Component {
render() {
return (
<div
css={STYLES_POPOVER}
style={this.props.style}
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
}}
>
{this.props.topSection ? this.props.topSection : null}
{this.props.navigation.map((section, i) => (
<div css={STYLES_POPOVER_SECTION}>
{section.map((each, j) => (
<div
key={`${i}-${j}`}
css={STYLES_POPOVER_ITEM}
style={this.props.itemStyle}
onClick={each.onClick}
>
<div css={Styles.H5 || this.props.css}>{each.text}</div>
</div>
))}
</div>
))}
</div>
);
}
export function PopoverNavigation({
containerCss,
sectionCss,
sectionItemCss,
css,
topSection = null,
navigation,
itemStyle,
...props
}) {
return (
<div
css={[STYLES_POPOVER, containerCss]}
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
}}
{...props}
>
{topSection}
{navigation.map((section, i) => (
<div css={[STYLES_POPOVER_SECTION, sectionCss]} key={i}>
{section.map((each, j) => (
<div
key={`${i}-${j}`}
css={[STYLES_POPOVER_ITEM, sectionItemCss]}
style={itemStyle}
onClick={each.onClick}
>
<P2 color="textBlack" css={css}>
{each.text}
</P2>
</div>
))}
</div>
))}
</div>
);
}

View File

@ -0,0 +1 @@
export const Show = ({ children, when, fallback = null }) => (when ? children : fallback);

View File

@ -0,0 +1,24 @@
import * as React from "react";
export const Match = React.memo(({ children /** when */ }) => {
return children;
});
// NOTE(amine): displayName is used to assert that direct children of Switch are the Match components
Match.displayName = "$";
export const Switch = React.memo(({ children, fallback = null }) => {
if (Array.isArray(children)) {
for (let element of children) {
if (element.type.displayName !== "$")
console.error("Switch component requires Match component as its children");
if (element.props.when) return element;
}
return fallback;
}
if (children?.props?.when) return children;
return fallback;
});

25
pages/_document.js Normal file
View File

@ -0,0 +1,25 @@
import Document, { Html, Head, Main, NextScript } from "next/document";
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
render() {
return (
<Html>
<Head />
<body>
{/** NOTE(amine): used to communicate with the extension via classNames.
* e.g. if the extension is installed on the user's browser, it will add 'isDownloaded' to className*/}
<div id="browser_extension" />
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;

View File

@ -1,243 +1,29 @@
import * as React from "react";
import * as Constants from "~/common/constants";
import * as SVG from "~/common/svg";
import * as Events from "~/common/custom-events";
import { css } from "@emotion/react";
import { ButtonPrimary, ButtonTertiary } from "~/components/system/components/Buttons";
import { FileTypeGroup } from "~/components/core/FileTypeIcon";
import { TabGroup, PrimaryTabGroup, SecondaryTabGroup } from "~/components/core/TabGroup";
import { CheckBox } from "~/components/system/components/CheckBox.js";
import { Boundary } from "~/components/system/components/fragments/Boundary";
import { GlobalCarousel } from "~/components/system/components/GlobalCarousel";
import { getPublicAndPrivateFiles } from "~/common/utilities";
import ScenePage from "~/components/core/ScenePage";
import DataView from "~/components/core/DataView";
import DataMeter from "~/components/core/DataMeterDetailed";
import ScenePageHeader from "~/components/core/ScenePageHeader";
import EmptyState from "~/components/core/EmptyState";
import WebsitePrototypeWrapper from "~/components/core/WebsitePrototypeWrapper";
const POLLING_INTERVAL = 10000;
const STYLES_CONTAINER_WRAPPER = css`
display: flex;
align-items: center;
justify-content: flex-end;
margin-bottom: 20px;
`;
const STYLES_CONTAINER = css`
height: 60px;
display: flex;
flex-direction: row-reverse;
justify-content: flex-start;
align-items: center;
`;
const STYLES_COMMAND_WRAPPER = css`
padding: 4px 0;
display: flex;
align-items: center;
justify-content: center;
`;
//TODO(toast): Constants for SDS in future
const STYLES_TOOLTIP_ANCHOR = css`
border: 1px solid ${Constants.semantic.borderGrayLight};
background-color: ${Constants.system.white};
border-radius: 4px;
right: 0px;
top: 48px;
padding: 20px 24px;
box-shadow: 0px 8px 24px rgba(178, 178, 178, 0.2);
position: absolute;
display: flex;
z-index: ${Constants.zindex.tooltip};
`;
const STYLES_FILETYPE_TOOLTIP = css`
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
border-right: 1px solid ${Constants.semantic.borderGrayLight};
padding-right: 4px;
`;
const STYLES_PRIVACY_TOOLTIP = css`
display: flex;
flex-direction: column;
justify-content: flex-start;
font-family: ${Constants.font.text};
font-size: ${Constants.typescale.lvl0};
padding-left: 24px;
`;
const STYLES_CHECKBOX_LABEL = css`
padding-top: 0;
position: relative;
top: -4px;
font-family: ${Constants.font.text};
font-size: ${Constants.typescale.lvl0};
user-select: none;
`;
const STYLES_BUTTONS_ROW = css`
display: flex;
flex-direction: row;
align-items: center;
`;
const STYLES_TOOLTIP_TEXT = css`
font-family: ${Constants.font.text};
font-size: 12px;
`;
const STYLES_COMMAND_TOOLTIP_ANCHOR = css`
border: 1px solid ${Constants.semantic.bgLight};
background-color: ${Constants.system.white};
border-radius: 4px;
display: flex;
flex-direction: column;
gap: 12px;
align-items: flex-start;
justify-content: space-around;
box-shadow: 0px 8px 24px rgba(178, 178, 178, 0.2);
width: 275px;
position: absolute;
top: 4px;
right: 50px;
z-index: ${Constants.zindex.tooltip};
padding: 12px;
const STYLES_SCENE_PAGE = css`
padding: 20px 24px 44px;
@media (max-width: ${Constants.sizes.mobile}px) {
padding: 31px 16px 44px;
}
`;
export default class SceneFilesFolder extends React.Component {
state = {
filterTooltip: false,
fileTypes: {
image: false,
video: false,
audio: false,
epub: false,
pdf: false,
},
filtersActive: false,
privacy: "ALL",
filteredFiles: this.props.viewer?.library,
keyboardTooltip: false,
index: -1,
};
// componentDidMount = () => {
// this.openCarouselToItem();
// };
componentDidUpdate = (prevProps, prevState) => {
if (prevProps.viewer.library !== this.props.viewer.library) {
if (this.state.filtersActive) {
this._filterFiles();
}
}
// if (prevProps.page.params !== this.props.page.params) {
// this.openCarouselToItem();
// }
};
_handleFilterTooltip = () => {
this.setState({ filterTooltip: !this.state.filterTooltip });
};
_handlePrivacyFilter = (filter) => {
this.setState({ privacy: filter }, this._filterFiles);
};
_getPrivacyFilteredFiles = () => {
let filter = this.state.privacy;
const viewer = this.props.viewer;
if (filter === "ALL") {
return viewer.library;
}
const filtered = getPublicAndPrivateFiles({ viewer });
if (filter === "PUBLIC") {
return filtered.publicFiles;
} else if (filter === "PRIVATE") {
return filtered.privateFiles;
}
};
_handleFiletypeFilter = (type) => {
this.setState(
{ fileTypes: { ...this.state.fileTypes, [type]: !this.state.fileTypes[type] } },
this._filterFiles
);
};
_filterFiles = () => {
const filters = this.state.fileTypes;
let fileTypeFiltersActive = Object.values(filters).some((val) => val === true);
let filtersActive = fileTypeFiltersActive || this.state.privacy !== "ALL";
if (!filtersActive) {
this.setState({ filtersActive });
return;
}
let filteredFiles = this._getPrivacyFilteredFiles();
if (fileTypeFiltersActive && this.props.viewer?.library?.length) {
filteredFiles = filteredFiles.filter((file) => {
return (
(filters.image && file.data.type.startsWith("image/")) ||
(filters.video && file.data.type.startsWith("video/")) ||
(filters.audio && file.data.type.startsWith("audio/")) ||
(filters.epub && file.data.type.startsWith("application/epub")) ||
(filters.pdf && file.data.type.startsWith("application/pdf"))
);
});
}
this.setState({
filteredFiles,
filtersActive,
});
};
// openCarouselToItem = () => {
// if (!this.props.page?.params || !this.props.viewer.library?.length) {
// return;
// }
// let index = -1;
// let params = this.props.page.params;
// if (params?.fileId || params?.cid || params?.index) {
// if (params?.index) {
// index = params.index;
// } else {
// let library = this.props.viewer.library || [];
// for (let i = 0; i < library.length; i++) {
// let obj = library[i];
// if ((obj.cid && obj.cid === params?.cid) || (obj.id && obj.id === params?.fileId)) {
// index = i;
// break;
// }
// }
// }
// }
// if (index !== -1) {
// Events.dispatchCustomEvent({
// name: "slate-global-open-carousel",
// detail: { index },
// });
// }
// };
render() {
let files = this.state.filtersActive ? this.state.filteredFiles : this.props.viewer?.library;
files = files || [];
let files = this.props.viewer?.library;
const tab = this.props.page.params?.tab || "grid";
return (
@ -245,7 +31,7 @@ export default class SceneFilesFolder extends React.Component {
title={`${this.props.page.pageTitle} • Slate`}
url={`${Constants.hostname}${this.props.page.pathname}`}
>
<ScenePage>
<ScenePage css={STYLES_SCENE_PAGE}>
<GlobalCarousel
carouselType="DATA"
viewer={this.props.viewer}
@ -257,211 +43,6 @@ export default class SceneFilesFolder extends React.Component {
index={this.state.index}
onChange={(index) => this.setState({ index })}
/>
<DataMeter
stats={this.props.viewer.stats}
style={{ marginBottom: 64 }}
buttons={
<ButtonPrimary
onClick={() => {
this.props.onAction({
type: "SIDEBAR",
value: "SIDEBAR_ADD_FILE_TO_BUCKET",
});
}}
style={{ whiteSpace: "nowrap", marginRight: 24 }}
>
Upload data
</ButtonPrimary>
}
/>
<div css={STYLES_CONTAINER_WRAPPER}>
<SecondaryTabGroup
tabs={[
{
title: <SVG.GridView height="24px" style={{ display: "block" }} />,
value: { tab: "grid" },
},
{
title: <SVG.TableView height="24px" style={{ display: "block" }} />,
value: { tab: "table" },
},
]}
value={tab}
onAction={this.props.onAction}
style={{ margin: "0 0 24px 0" }}
/>
<div css={STYLES_CONTAINER}>
<div
css={STYLES_BUTTONS_ROW}
style={{ position: "relative", padding: 10, marginRight: 8 }}
onMouseLeave={() => this.setState({ keyboardTooltip: false })}
>
<span
css={STYLES_COMMAND_WRAPPER}
onMouseEnter={() => this.setState({ keyboardTooltip: true })}
>
<SVG.InfoCircle
height="20px"
style={{
color: this.state.keyboardTooltip
? Constants.system.grayDark2
: Constants.system.grayLight2,
}}
/>
</span>
{this.state.keyboardTooltip ? (
<div css={STYLES_COMMAND_TOOLTIP_ANCHOR}>
<div>
<p
css={STYLES_TOOLTIP_TEXT}
style={{
fontFamily: Constants.font.semiBold,
fontSize: 14,
paddingBottom: 4,
}}
>
Keyboard shortcuts
</p>
</div>
<div>
<p css={STYLES_TOOLTIP_TEXT}>shift + click</p>
<p css={STYLES_TOOLTIP_TEXT} style={{ color: Constants.system.grayLight2 }}>
select a range of items between two selections
</p>
</div>
<div>
<p css={STYLES_TOOLTIP_TEXT}>shift + drag</p>
<p css={STYLES_TOOLTIP_TEXT} style={{ color: Constants.system.grayLight2 }}>
select items by draging over them
</p>
</div>
<div>
<p css={STYLES_TOOLTIP_TEXT}>alt + drag</p>
<p css={STYLES_TOOLTIP_TEXT} style={{ color: Constants.system.grayLight2 }}>
deselect items by draging over them
</p>
</div>
</div>
) : null}
</div>
</div>
<div css={STYLES_CONTAINER}>
<div style={{ position: "relative" }}>
<ButtonTertiary
style={{ paddingLeft: 12, paddingRight: 12 }}
onClick={this._handleFilterTooltip}
>
<SVG.Filter
height="18px"
style={{
color: this.state.filtersActive
? Constants.system.blue
: Constants.semantic.textGray,
}}
/>
</ButtonTertiary>
{this.state.filterTooltip ? (
<Boundary
captureResize={true}
captureScroll={false}
enabled
onOutsideRectEvent={() => {
this.setState({ filterTooltip: false });
}}
>
<div css={STYLES_TOOLTIP_ANCHOR}>
<div css={STYLES_FILETYPE_TOOLTIP}>
<div style={{ width: 100 }}>
<CheckBox
name="image"
value={this.state.fileTypes.image}
onChange={() => this._handleFiletypeFilter("image")}
boxStyle={{ height: 20, width: 20 }}
>
<span css={STYLES_CHECKBOX_LABEL}>Image</span>
</CheckBox>
</div>
<div style={{ width: 100, marginTop: 12 }}>
<CheckBox
name="audio"
value={this.state.fileTypes.audio}
onChange={() => this._handleFiletypeFilter("audio")}
boxStyle={{ height: 20, width: 20 }}
>
<span css={STYLES_CHECKBOX_LABEL}>Audio</span>
</CheckBox>
</div>
<div style={{ width: 100, marginTop: 12 }}>
<CheckBox
name="video"
value={this.state.fileTypes.video}
onChange={() => this._handleFiletypeFilter("video")}
boxStyle={{ height: 20, width: 20 }}
>
<span css={STYLES_CHECKBOX_LABEL}>Video</span>
</CheckBox>
</div>
<div style={{ width: 100, marginTop: 12 }}>
<CheckBox
name="epub"
value={this.state.fileTypes.epub}
onChange={() => this._handleFiletypeFilter("epub")}
boxStyle={{ height: 20, width: 20 }}
>
<span css={STYLES_CHECKBOX_LABEL}>Epub</span>
</CheckBox>
</div>
<div style={{ width: 100, marginTop: 12 }}>
<CheckBox
name="pdf"
value={this.state.fileTypes.pdf}
onChange={() => this._handleFiletypeFilter("pdf")}
boxStyle={{ height: 20, width: 20 }}
>
<span css={STYLES_CHECKBOX_LABEL}>Pdf</span>
</CheckBox>
</div>
</div>
<div css={STYLES_PRIVACY_TOOLTIP}>
<div
style={{
color: this.state.privacy === "ALL" ? Constants.system.blue : null,
cursor: "pointer",
marginTop: 1,
}}
onClick={() => this._handlePrivacyFilter("ALL")}
>
All
</div>
<div
style={{
color:
this.state.privacy === "PRIVATE" ? Constants.system.blue : "inherit",
cursor: "pointer",
marginTop: 17,
}}
onClick={() => this._handlePrivacyFilter("PRIVATE")}
>
Private
</div>
<div
style={{
color:
this.state.privacy === "PUBLIC" ? Constants.system.blue : "inherit",
cursor: "pointer",
marginTop: 18,
}}
onClick={() => this._handlePrivacyFilter("PUBLIC")}
>
Public
</div>
</div>
</div>
</Boundary>
) : null}
</div>
</div>
</div>
{files.length ? (
<DataView

View File

@ -134,12 +134,6 @@ export default class SceneProfile extends React.Component {
// // }
// };
getFilteredViewer = () => {
let viewer = this.props.viewer;
const res = Utilities.getPublicAndPrivateFiles({ viewer });
return { ...viewer, library: res.publicFiles };
};
render() {
let user = this.props.data;
if (!user) {