diff --git a/common/constants.js b/common/constants.js index c586eee0..ae44cd8e 100644 --- a/common/constants.js +++ b/common/constants.js @@ -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, diff --git a/common/environment.js b/common/environment.js index f5ed9623..f315491e 100644 --- a/common/environment.js +++ b/common/environment.js @@ -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; diff --git a/common/hooks.js b/common/hooks.js index 159f442f..9a516ff1 100644 --- a/common/hooks.js +++ b/common/hooks.js @@ -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 = []) => { diff --git a/common/strings.js b/common/strings.js index 4d7285f9..95a3a2af 100644 --- a/common/strings.js +++ b/common/strings.js @@ -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) => { diff --git a/common/utilities.js b/common/utilities.js index ab24f7c8..9453a5c0 100644 --- a/common/utilities.js +++ b/common/utilities.js @@ -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) { diff --git a/components/core/ApplicationHeader.js b/components/core/ApplicationHeader.js index 3fd83cb1..f4e49b10 100644 --- a/components/core/ApplicationHeader.js +++ b/components/core/ApplicationHeader.js @@ -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 ? ( - - ) : ( - Ctrl - ); - - 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 = ( -
- - - Search Slate... - -
- ); + const handleDismissSearch = () => setFieldValue(""); - //NOTE(martina): signed out view - return ( -
-
-
- + const { mobile } = useMediaQuery(); + const isSignedOut = !viewer; + const isSearching = searchQuery.length !== 0; + + return ( +
+
+
+ -
{searchComponent}
-
-
- {searchComponent} -
-
- - - - Sign in - - - - - - Sign up - - -
-
-
- ); - } - const mobilePopup = ( - // { - // e.stopPropagation(); - // e.preventDefault(); - // this._handleTogglePopup(e); - // }} - // > - <> + } + > + + +
+
+ {/**TODO: update Search component */} + +
+
+ +
+
+
- - // - ); - - const mobileDropdown = ( - <> - { - e.stopPropagation(); - e.preventDefault(); - this.setState({ showDropdown: false }); - }} - > -
- {this.props.navigation - .filter((item) => item.mainNav) - .map((item) => ( - this.setState({ showDropdown: false })} - > -
- {item.name} -
- - ))} -
- Search -
-
-
-
- - ); - - return ( - <> -
-
- -
-
- - - -
{ - this.props.onAction({ - type: "SIDEBAR", - value: "SIDEBAR_ADD_FILE_TO_BUCKET", - }); - }} - style={{ marginRight: 24, marginLeft: 24 }} - > - -
-
-
-
- {navigation.map((item, i) => ( - -
- {item.name} -
- - ))} -
- Search -
-
-
-
- - - -
-
-
- -
-
-
- this.setState({ showDropdown: !this.state.showDropdown, popup: null }) - } - > - -
-
-
- - - -
-
- - - -
-
- {this.state.popup === "profile" - ? mobilePopup - : this.state.showDropdown - ? mobileDropdown - : null} -
-
- - ); - } + + {/** 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 */} +
+
+ ); } + +const Actions = ({ isSignedOut, isSearching, onAction, onUpload, onDismissSearch }) => { + const authActions = React.useMemo( + () => ( + <> + + + + Sign in + + + + + + Sign up + + + + ), + [onAction] + ); + + const uploadAction = React.useMemo( + () => ( + + ), + [onUpload] + ); + + return ( + + + {uploadAction} + + } + > + {authActions} + + + + + + + + ); +}; diff --git a/components/core/ApplicationLayout.js b/components/core/ApplicationLayout.js index afe4e34f..246a9086 100644 --- a/components/core/ApplicationLayout.js +++ b/components/core/ApplicationLayout.js @@ -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 {
{this.props.header && ( -
- {this.props.header} -
+ <> +
{this.props.header}
+
+ {this.props.header} +
+ )} 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 ( -// { -// onTogglePopup(); -// update({ -// name: user.data.name, -// email: user.email, -// customAttributes: { -// slate_userid: user.id, -// username: user.username, -// }, -// }); -// show(); -// }} -// > -// Help -// -// ); -// }; + 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 ( +
+
+
+ ); +}; 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 = ( -
-
- -
+ if (this.props.popup !== "profile") return null; -
-
- {this.props.viewer.data.name || `@${this.props.viewer.username}`} -
-
- {`${ - this.props.viewer.library.length - } File${this.props.viewer.library.length === 1 ? "" : "s"}`} - {`${this.props.viewer.slates.length} Collection${ - this.props.viewer.slates.length === 1 ? "" : "s" - }`} -
+ 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 = ( + +
+ +

+ {username} +

+
+ + {objectsLength} {Strings.pluralize("Object", objectsLength)} + + + {Strings.bytesToSize(stats.bytes, 0)} of {Strings.bytesToSize(stats.maximumBytes, 0)}{" "} + Stored +
+
- ); + + ); - const navigation = [ - [ - { - text: ( + const ExtensionButton = ( +
+ + Install Slate browser extension + +
+ ); + + const navigation = [ + [ + { + text: ( +
Profile - ), - }, - { - text: ( +
+ ), + }, + { + text: ( +
Directory - ), - }, - ], - [ - { - text: ( +
+ ), + }, + ], + [ + { + text: ( +
Filecoin - ), - }, - { - text: ( +
+ ), + }, + { + text: ( +
Storage deal - ), - }, - { - text: ( +
+ ), + }, + { + text: ( +
API - ), - }, - ], - [ - { - text: ( +
+ ), + }, + ], + [ + { + text: ( +
Settings - ), +
+ ), + }, + { + text:
Sign out
, + onClick: (e) => { + this._handleSignOut(e); }, - ], - [ - // { - // text: ( - // - // ), - // }, - { - text: "Sign out", - onClick: (e) => { - this._handleSignOut(e); - }, - }, - ], - ]; + }, + ...(!this.state.isExtensionDownloaded ? [{ text: ExtensionButton }] : []), + ], + ]; - return ( - <> -
- this.props.onTogglePopup()} - > - - -
-
- this.props.onTogglePopup()} - > - - -
- - ); - } - return null; + return ( + <> +
+ this.props.onTogglePopup()} + > + + +
+
+ this.props.onTogglePopup()} + > + + +
+ + ); } } @@ -296,14 +333,14 @@ export class ApplicationUserControls extends React.Component { let tooltip = ; return (
-
this.props.onTogglePopup("profile")} style={{ position: "relative", cursor: "pointer" }} > - - {this.props.popup === "profile" ? tooltip : null} -
+ + + {this.props.popup === "profile" ? tooltip : null}
); } diff --git a/components/core/ProfilePhoto.js b/components/core/ProfilePhoto.js index 805dbab1..20ddb4a5 100644 --- a/components/core/ProfilePhoto.js +++ b/components/core/ProfilePhoto.js @@ -34,14 +34,7 @@ function BoringAvatar({ avatarCss, ...props }) { let avatarUrl = `https://source.boringavatars.com/marble/${props.size}/${props.userId}?square&colors=${colors}`; return ( - profile preview + profile preview ); } @@ -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 ? ( - + ) : ( - + )} ); diff --git a/components/core/ScenePage.js b/components/core/ScenePage.js index c44d4d3c..08231e7c 100644 --- a/components/core/ScenePage.js +++ b/components/core/ScenePage.js @@ -14,8 +14,8 @@ const STYLES_SCENE = css` } `; -export const ScenePage = (props) => ( -
+export const ScenePage = ({ css, ...props }) => ( +
{props.children}
); diff --git a/components/core/SearchModal.js b/components/core/SearchModal.js index 599f43d0..6c98654a 100644 --- a/components/core/SearchModal.js +++ b/components/core/SearchModal.js @@ -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) { diff --git a/components/system/components/PopoverNavigation.js b/components/system/components/PopoverNavigation.js index 3bb0a6ce..8ed5d024 100644 --- a/components/system/components/PopoverNavigation.js +++ b/components/system/components/PopoverNavigation.js @@ -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 ( -
{ - e.stopPropagation(); - e.preventDefault(); - }} - > - {this.props.topSection ? this.props.topSection : null} - {this.props.navigation.map((section, i) => ( -
- {section.map((each, j) => ( -
-
{each.text}
-
- ))} -
- ))} -
- ); - } +export function PopoverNavigation({ + containerCss, + sectionCss, + sectionItemCss, + css, + topSection = null, + navigation, + itemStyle, + ...props +}) { + return ( +
{ + e.stopPropagation(); + e.preventDefault(); + }} + {...props} + > + {topSection} + {navigation.map((section, i) => ( +
+ {section.map((each, j) => ( +
+ + {each.text} + +
+ ))} +
+ ))} +
+ ); } diff --git a/components/utility/Show.js b/components/utility/Show.js new file mode 100644 index 00000000..bcb70305 --- /dev/null +++ b/components/utility/Show.js @@ -0,0 +1 @@ +export const Show = ({ children, when, fallback = null }) => (when ? children : fallback); diff --git a/components/utility/Switch.js b/components/utility/Switch.js new file mode 100644 index 00000000..a22c8844 --- /dev/null +++ b/components/utility/Switch.js @@ -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; +}); diff --git a/pages/_document.js b/pages/_document.js new file mode 100644 index 00000000..7638d279 --- /dev/null +++ b/pages/_document.js @@ -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 ( + + + + {/** 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*/} +
+
+ + + + ); + } +} + +export default MyDocument; diff --git a/scenes/SceneFilesFolder.js b/scenes/SceneFilesFolder.js index 87387848..dac61603 100644 --- a/scenes/SceneFilesFolder.js +++ b/scenes/SceneFilesFolder.js @@ -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}`} > - + this.setState({ index })} /> - { - this.props.onAction({ - type: "SIDEBAR", - value: "SIDEBAR_ADD_FILE_TO_BUCKET", - }); - }} - style={{ whiteSpace: "nowrap", marginRight: 24 }} - > - Upload data - - } - /> -
- , - value: { tab: "grid" }, - }, - { - title: , - value: { tab: "table" }, - }, - ]} - value={tab} - onAction={this.props.onAction} - style={{ margin: "0 0 24px 0" }} - /> -
-
this.setState({ keyboardTooltip: false })} - > - this.setState({ keyboardTooltip: true })} - > - - - {this.state.keyboardTooltip ? ( -
-
-

- Keyboard shortcuts -

-
-
-

shift + click

-

- select a range of items between two selections -

-
-
-

shift + drag

-

- select items by draging over them -

-
-
-

alt + drag

-

- deselect items by draging over them -

-
-
- ) : null} -
-
-
-
- - - - {this.state.filterTooltip ? ( - { - this.setState({ filterTooltip: false }); - }} - > -
-
-
- this._handleFiletypeFilter("image")} - boxStyle={{ height: 20, width: 20 }} - > - Image - -
-
- this._handleFiletypeFilter("audio")} - boxStyle={{ height: 20, width: 20 }} - > - Audio - -
-
- this._handleFiletypeFilter("video")} - boxStyle={{ height: 20, width: 20 }} - > - Video - -
-
- this._handleFiletypeFilter("epub")} - boxStyle={{ height: 20, width: 20 }} - > - Epub - -
-
- this._handleFiletypeFilter("pdf")} - boxStyle={{ height: 20, width: 20 }} - > - Pdf - -
-
-
-
this._handlePrivacyFilter("ALL")} - > - All -
-
this._handlePrivacyFilter("PRIVATE")} - > - Private -
-
this._handlePrivacyFilter("PUBLIC")} - > - Public -
-
-
-
- ) : null} -
-
-
{files.length ? ( { - let viewer = this.props.viewer; - const res = Utilities.getPublicAndPrivateFiles({ viewer }); - return { ...viewer, library: res.publicFiles }; - }; - render() { let user = this.props.data; if (!user) {