mirror of
https://github.com/filecoin-project/slate.git
synced 2024-11-25 19:55:26 +03:00
Merge pull request #895 from filecoin-project/@aminejv/new-top-nav
Update: new top navigation
This commit is contained in:
commit
5563574dc3
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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 = []) => {
|
||||
|
@ -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) => {
|
||||
|
@ -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) {
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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={
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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) {
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
1
components/utility/Show.js
Normal file
1
components/utility/Show.js
Normal file
@ -0,0 +1 @@
|
||||
export const Show = ({ children, when, fallback = null }) => (when ? children : fallback);
|
24
components/utility/Switch.js
Normal file
24
components/utility/Switch.js
Normal 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
25
pages/_document.js
Normal 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;
|
@ -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
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user