From f22e79aac267f4564bf0d80f8b10dcc73a45568b Mon Sep 17 00:00:00 2001 From: Aminejv Date: Thu, 28 Oct 2021 12:21:05 +0100 Subject: [PATCH 01/21] feat(Filter/Breadcrumb): delete Breadcrumb component --- components/core/Filter/Breadcrumb.js | 63 ---------------------------- components/core/Filter/index.js | 13 +----- scenes/SceneFilesFolder.js | 1 - 3 files changed, 1 insertion(+), 76 deletions(-) delete mode 100644 components/core/Filter/Breadcrumb.js diff --git a/components/core/Filter/Breadcrumb.js b/components/core/Filter/Breadcrumb.js deleted file mode 100644 index 8b9ce2c0..00000000 --- a/components/core/Filter/Breadcrumb.js +++ /dev/null @@ -1,63 +0,0 @@ -import * as React from "react"; -import * as System from "~/components/system"; -import * as Styles from "~/common/styles"; -import * as Strings from "~/common/strings"; - -import { useFilterContext } from "~/components/core/Filter/Provider"; -import { css } from "@emotion/react"; -import { FILTER_VIEWS_IDS, FILTER_TYPES } from "~/common/filter-utilities"; -import { Show } from "~/components/utility/Show"; - -const STYLES_BREADCRUMB_BUTTON = (theme) => css` - ${Styles.BUTTON_RESET}; - :hover { - color: ${theme.semantic.textBlack}; - } -`; - -function Item({ children, color, includeDelimiter, ...props }) { - return ( - <> - {includeDelimiter && ( - - {" "} - /{" "} - - )} - - - ); -} - -export function Breadcrumb(props) { - const [{ filterState }, { setFilterType, resetFilterState }] = useFilterContext(); - const isCurrentViewInitial = filterState.view === FILTER_VIEWS_IDS.initial; - - const changeFilterToBrowerView = () => - setFilterType({ - view: FILTER_VIEWS_IDS.browser, - type: FILTER_TYPES[FILTER_VIEWS_IDS.browser].filters.initial, - }); - - return ( -
- - - All - - - {Strings.capitalize(filterState.view)} - - - - {Strings.capitalize(filterState.subview)} - -
- ); -} diff --git a/components/core/Filter/index.js b/components/core/Filter/index.js index aeed491e..e9f6f0d1 100644 --- a/components/core/Filter/index.js +++ b/components/core/Filter/index.js @@ -2,7 +2,6 @@ import * as React from "react"; import * as System from "~/components/system"; import { Navbar, NavbarPortal } from "~/components/core/Filter/Navbar"; -import { Breadcrumb } from "~/components/core/Filter/Breadcrumb"; import { Provider } from "~/components/core/Filter/Provider"; import { Sidebar, SidebarTrigger } from "~/components/core/Filter/Sidebar"; import { Content } from "~/components/core/Filter/Content"; @@ -22,14 +21,4 @@ function Actions() { return
; } -export { - Title, - Actions, - Sidebar, - SidebarTrigger, - Provider, - Navbar, - Content, - Breadcrumb, - NavbarPortal, -}; +export { Title, Actions, Sidebar, SidebarTrigger, Provider, Navbar, Content, NavbarPortal }; diff --git a/scenes/SceneFilesFolder.js b/scenes/SceneFilesFolder.js index a8f47194..d350d9c4 100644 --- a/scenes/SceneFilesFolder.js +++ b/scenes/SceneFilesFolder.js @@ -49,7 +49,6 @@ export default function SceneFilesFolder({ viewer, page, onAction, isMobile }) {
-
From 779bbac89e82b710f11a274467f474463169727a Mon Sep 17 00:00:00 2001 From: Aminejv Date: Thu, 28 Oct 2021 12:21:32 +0100 Subject: [PATCH 02/21] feat(Filter): remove filter-files worker --- common/hooks.js | 27 --------------------------- workers/filter-files.js | 8 -------- 2 files changed, 35 deletions(-) delete mode 100644 workers/filter-files.js diff --git a/common/hooks.js b/common/hooks.js index 1f89da6a..c94bded9 100644 --- a/common/hooks.js +++ b/common/hooks.js @@ -403,33 +403,6 @@ export const useLockScroll = ({ lock = true } = { lock: true }) => { }, [lock]); }; -export const useWorker = ({ onStart, onMessage, onError } = {}, dependencies = []) => { - const workerRef = React.useRef(); - - const onStartRef = React.useRef(); - onStartRef.current = onStart; - - const onMessageRef = React.useRef(); - onMessageRef.current = onMessage; - - const onErrorRef = React.useRef(); - onErrorRef.current = onError; - - React.useEffect(() => { - const worker = new Worker(new URL("../workers/filter-files.js", import.meta.url)); - if (!worker) return; - - workerRef.current = worker; - worker.onmessage = onMessageRef.current; - worker.onerror = onErrorRef.current; - - onStartRef.current(worker); - return () => worker?.terminate(); - }, dependencies); - - return workerRef.current; -}; - export const useHover = () => { const [isHovered, setHoverState] = React.useState(false); diff --git a/workers/filter-files.js b/workers/filter-files.js deleted file mode 100644 index 4993d557..00000000 --- a/workers/filter-files.js +++ /dev/null @@ -1,8 +0,0 @@ -import { getFilterHandler } from "~/common/filter-utilities"; - -onmessage = function (e) { - const { objects = [], view, subview, type } = e.data; - const filterCallback = getFilterHandler({ view, subview, type }); - const result = objects.filter(filterCallback); - postMessage(result); -}; From 2827589e45fda5599ed4d6bb93c8c6c7722a8020 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Thu, 28 Oct 2021 16:59:44 +0100 Subject: [PATCH 03/21] feat(ApplicationHeader): update header's height --- common/constants.js | 2 +- components/core/ApplicationHeader.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/common/constants.js b/common/constants.js index 4e5f2657..3ce14f46 100644 --- a/common/constants.js +++ b/common/constants.js @@ -8,7 +8,7 @@ export const sizes = { navigation: 288, sidebar: 416, // NOTE(amine): header's height + filter navbar's height - header: 52, + header: 55, filterNavbar: 40, tablet: 960, desktop: 1024, diff --git a/components/core/ApplicationHeader.js b/components/core/ApplicationHeader.js index 1f072280..fcc199ff 100644 --- a/components/core/ApplicationHeader.js +++ b/components/core/ApplicationHeader.js @@ -50,7 +50,6 @@ const STYLES_APPLICATION_HEADER_BACKGROUND = (theme) => css` left: 0; z-index: -1; background-color: ${theme.system.white}; - box-shadow: 0 0 0 1px ${theme.semantic.bgGrayLight}; @supports ((-webkit-backdrop-filter: blur(75px)) or (backdrop-filter: blur(75px))) { -webkit-backdrop-filter: blur(75px); backdrop-filter: blur(75px); @@ -104,9 +103,10 @@ const STYLES_BACKGROUND = css` animation: fade-in 200ms ease-out; `; -const STYLES_HEADER = css` - z-index: ${Constants.zindex.header}; +const STYLES_HEADER = (theme) => css` + z-index: ${theme.zindex.header}; width: 100vw; + height: ${theme.sizes.header}px; position: fixed; right: 0; top: 0; @@ -117,7 +117,7 @@ const STYLES_FILTER_NAVBAR = (theme) => css` width: 100vw; position: fixed; right: 0; - top: ${theme.sizes.header}; + top: ${theme.sizes.header}px; `; const STYLES_UPLOAD_BUTTON = css` From 2e7606a4e9578c6d1c2f6d7dce718e2e4528e4ba Mon Sep 17 00:00:00 2001 From: Aminejv Date: Thu, 28 Oct 2021 17:00:52 +0100 Subject: [PATCH 04/21] feat(filter-utilities): simplify filtering structure --- common/filter-utilities.js | 86 +++----------------------------------- 1 file changed, 5 insertions(+), 81 deletions(-) diff --git a/common/filter-utilities.js b/common/filter-utilities.js index c4119ef3..95f6559b 100644 --- a/common/filter-utilities.js +++ b/common/filter-utilities.js @@ -1,86 +1,10 @@ -import { - isImageType, - isVideoType, - isAudioType, - isDocument, - isTwitterLink, - isYoutubeLink, - isTwitchLink, - isGithubLink, - isInstagramLink, -} from "~/common/validations"; - -export const FILTER_VIEWS_IDS = { +export const VIEWS_IDS = { initial: "initial", - browser: "browser", }; -export const FILTER_SUBVIEWS_IDS = { - browser: { saved: "saved" }, -}; - -export const FILTER_TYPES = { - [FILTER_VIEWS_IDS.initial]: { - filters: { - initial: "library", - library: "library", - images: "images", - videos: "videos", - audios: "audios", - documents: "documents", - }, - }, - [FILTER_VIEWS_IDS.browser]: { - filters: { all: "all", initial: "all" }, - subviews: { - [FILTER_SUBVIEWS_IDS.browser.saved]: { - filters: { - initial: "all", - all: "all", - twitter: "twitter", - youtube: "youtube", - twitch: "twitch", - github: "github", - instagram: "instagram", - }, - }, - }, +export const TYPES_IDS = { + initial: { + library: "library", + tags: "tags", }, }; - -const FILTERING_HANDLERS = { - [FILTER_VIEWS_IDS.initial]: { - filters: { - library: (object) => object, - images: (object) => isImageType(object.type), - videos: (object) => isVideoType(object.type), - audios: (object) => isAudioType(object.type), - documents: (object) => isDocument(object.filename, object.type), - }, - }, - [FILTER_VIEWS_IDS.browser]: { - filters: { all: (object) => object.isLink }, - subviews: { - [FILTER_SUBVIEWS_IDS.browser.saved]: { - filters: { - all: (object) => object.isLink, - twitter: isTwitterLink, - youtube: isYoutubeLink, - twitch: isTwitchLink, - github: isGithubLink, - instagram: isInstagramLink, - }, - }, - }, - }, -}; - -export const getViewData = (view) => { - return FILTER_TYPES[view]; -}; - -export const getFilterHandler = ({ view, subview, type }) => { - const nextView = FILTERING_HANDLERS[view]; - if (subview) return nextView.subviews[subview].filters[type]; - return nextView.filters[type]; -}; From 15d09405460b040d3d34ca77747a18b7bc68a232 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Thu, 28 Oct 2021 17:01:34 +0100 Subject: [PATCH 05/21] feat(Divider): add support for css prop --- components/system/components/Divider.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/components/system/components/Divider.js b/components/system/components/Divider.js index 3af52c18..a516cece 100644 --- a/components/system/components/Divider.js +++ b/components/system/components/Divider.js @@ -6,16 +6,20 @@ export const Divider = ({ width = "100%", height = "0.5px", color = Constants.system.grayLight4, + css, ...props }) => { return (
({ - height, - width, - minHeight: height, - backgroundColor: theme.system?.[color] || theme.semantic?.[color] || color, - })} + css={[ + (theme) => ({ + height, + width, + minHeight: height, + backgroundColor: theme.system?.[color] || theme.semantic?.[color] || color, + }), + css, + ]} {...props} /> ); From 0697c2d26632d7ba5e235194df89eca24e896ec7 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Thu, 28 Oct 2021 17:02:22 +0100 Subject: [PATCH 06/21] feat(Filter/Filters): add tags and remove browser filters --- components/core/Filter/Filters.js | 161 ++++++++---------------------- 1 file changed, 42 insertions(+), 119 deletions(-) diff --git a/components/core/Filter/Filters.js b/components/core/Filter/Filters.js index da8d28dc..b13ad471 100644 --- a/components/core/Filter/Filters.js +++ b/components/core/Filter/Filters.js @@ -2,6 +2,7 @@ import * as React from "react"; import * as SVG from "~/common/svg"; import * as Styles from "~/common/styles"; import * as Typography from "~/components/system/components/Typography"; +import * as FilterUtilities from "~/common/filter-utilities"; import { css } from "@emotion/react"; import { useFilterContext } from "~/components/core/Filter/Provider"; @@ -19,7 +20,7 @@ const STYLES_FILTER_BUTTON = (theme) => css` align-items: center; width: 100%; ${Styles.BUTTON_RESET}; - padding: 4px 8px; + padding: 5px 8px 3px; border-radius: 8px; color: ${theme.semantic.textBlack}; &:hover { @@ -59,7 +60,7 @@ const FilterButton = ({ children, Icon, isSelected, ...props }) => ( const FilterSection = ({ title, children, ...props }) => (
{title && ( - + {title} )} @@ -71,143 +72,65 @@ const FilterSection = ({ title, children, ...props }) => ( * InitialFilters * -----------------------------------------------------------------------------------------------*/ -function Initial({ filters, goToBrowserView }) { - const [{ filterState }, { setFilterType, resetFilterState }] = useFilterContext(); +function Library() { + const [{ filterState }, { setFilterType, hidePopup }] = useFilterContext(); const currentFilterType = filterState.type; - const currentFilterView = filterState.view; - const changeFilter = ({ type }) => setFilterType({ view: currentFilterView, type }); + const libraryFilterType = FilterUtilities.TYPES_IDS.initial.library; return ( <> - {/** Breadcrumb All */} { + setFilterType({ + view: FilterUtilities.VIEWS_IDS.initial, + type: libraryFilterType, + title: "Library", + }); + hidePopup(); + }} > My Library - - - Browser - - - - changeFilter({ type: filters.images })} - > - Images - - changeFilter({ type: filters.audios })} - > - Audios - - changeFilter({ type: filters.videos })} - > - Videos - - changeFilter({ type: filters.documents })} - > - Documents - - ); } -/* ------------------------------------------------------------------------------------------------- - * Browser Filters - * -----------------------------------------------------------------------------------------------*/ - -function Browser({ filters, goToSavedSubview }) { - const [{ filterState }] = useFilterContext(); +function Tags({ viewer, ...props }) { + const [{ filterState }, { setFilterType, hidePopup }] = useFilterContext(); const currentFilterType = filterState.type; + const tagFilterType = FilterUtilities.TYPES_IDS.initial.tags; + + const checkIsTagSelected = (slateId) => + currentFilterType === tagFilterType && filterState?.context?.slateId === slateId; + + const setSelectedTag = (slate) => + setFilterType({ + view: FilterUtilities.VIEWS_IDS.initial, + type: tagFilterType, + title: "#" + slate.slatename, + context: { slateId: slate.id }, + }); + return ( - - - All - - - History - - - Bookmarks - - - Saved - + + {viewer.slates.map((slate) => ( + (setSelectedTag(slate), hidePopup())} + > + {slate.slatename} + + ))} ); } -const BrowserSaved = ({ filters }) => { - const [{ filterState }, { setFilterType }] = useFilterContext(); - const currentFilterType = filterState.type; - - const changeSavedFilterType = (type) => - setFilterType({ view: filterState.view, subview: filterState.subview, type }); - - return ( - - changeSavedFilterType(filters.all)} - > - All - - changeSavedFilterType(filters.twitter)} - > - Twitter - - changeSavedFilterType(filters.youtube)} - > - Youtube - - changeSavedFilterType(filters.github)} - > - Github - - changeSavedFilterType(filters.twitch)} - > - Twitch - - changeSavedFilterType(filters.instagram)} - > - Instagram - - - ); -}; - -export { Initial, Browser, BrowserSaved }; +export { Library, Tags }; From ba3e2c88778191921d15d466756e307c86ba8c5b Mon Sep 17 00:00:00 2001 From: Aminejv Date: Thu, 28 Oct 2021 17:05:05 +0100 Subject: [PATCH 07/21] feat(Filter/Sidebar): add tags filters --- components/core/Filter/Sidebar.js | 76 ++++++------------------------- 1 file changed, 15 insertions(+), 61 deletions(-) diff --git a/components/core/Filter/Sidebar.js b/components/core/Filter/Sidebar.js index defd1a1f..4747f441 100644 --- a/components/core/Filter/Sidebar.js +++ b/components/core/Filter/Sidebar.js @@ -2,10 +2,9 @@ import * as React from "react"; import * as SVG from "~/common/svg"; import * as Styles from "~/common/styles"; import * as Filters from "~/components/core/Filter/Filters"; +import * as Constants from "~/common/constants"; -import * as FilterUtilities from "~/common/filter-utilities"; import { useFilterContext } from "~/components/core/Filter/Provider"; -import { Show } from "~/components/utility/Show"; import { css } from "@emotion/react"; /* ------------------------------------------------------------------------------------------------- @@ -23,19 +22,16 @@ const STYLES_SIDEBAR_TRIGGER = (theme) => css` } `; -export function SidebarTrigger() { - const [{ isSidebarVisible }, { toggleSidebar }] = useFilterContext(); +export function SidebarTrigger({ css }) { + const [{ sidebarState }, { toggleSidebar }] = useFilterContext(); return ( @@ -46,17 +42,6 @@ export function SidebarTrigger() { * Sidebar * -----------------------------------------------------------------------------------------------*/ -export function Sidebar() { - const [{ isSidebarVisible }] = useFilterContext(); - return ( - -
- -
-
- ); -} - const STYLES_SIDEBAR_FILTER_WRAPPER = (theme) => css` position: sticky; top: ${theme.sizes.header + theme.sizes.filterNavbar}px; @@ -71,46 +56,15 @@ const STYLES_SIDEBAR_FILTER_WRAPPER = (theme) => css` } `; -/* ------------------------------------------------------------------------------------------------- - * SidebarContent - * -----------------------------------------------------------------------------------------------*/ +export function Sidebar({ viewer, isMobile }) { + const [{ sidebarState }] = useFilterContext(); -function SidebarContent() { - const [{ filterState }, { setFilterType }] = useFilterContext(); - const currentView = filterState.view; - const currentSubview = filterState.subview; - - const { FILTER_VIEWS_IDS, FILTER_SUBVIEWS_IDS } = FilterUtilities; - const { filters, subviews } = FilterUtilities.getViewData(currentView); - - const changeFilterView = (view) => { - const { filters } = FilterUtilities.getViewData(view); - setFilterType({ view: view, type: filters.initial }); - }; - - const changeFilterSubview = (subview) => { - const initialType = subviews[subview].filters.initial; - setFilterType({ view: currentView, subview, type: initialType }); - }; - - if (currentView === FILTER_VIEWS_IDS.browser) { - if (currentSubview === FILTER_SUBVIEWS_IDS.browser.saved) { - const { filters } = subviews[currentSubview]; - return ; - } - - return ( - changeFilterSubview(FILTER_SUBVIEWS_IDS.browser.saved)} - /> - ); - } + if (!sidebarState.isVisible || isMobile) return null; return ( - changeFilterView(FILTER_VIEWS_IDS.browser)} - /> +
+ + +
); } From d3459a991952ddab4d7b749ba73719ed71e22ff4 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Thu, 28 Oct 2021 17:13:49 +0100 Subject: [PATCH 08/21] feat(Filter/Provider): - remove useWorker - add useFilterPopup --- components/core/Filter/Provider.js | 95 +++++++++++++----------------- 1 file changed, 42 insertions(+), 53 deletions(-) diff --git a/components/core/Filter/Provider.js b/components/core/Filter/Provider.js index be5517d5..205f3edb 100644 --- a/components/core/Filter/Provider.js +++ b/components/core/Filter/Provider.js @@ -1,26 +1,29 @@ import * as React from "react"; - -import { useWorker } from "~/common/hooks"; +import { useIsomorphicLayoutEffect } from "~/common/hooks"; +import * as FilterUtilities from "~/common/filter-utilities"; const UploadContext = React.createContext({}); - export const useFilterContext = () => React.useContext(UploadContext); export const Provider = ({ children, viewer }) => { const [isSidebarVisible, toggleSidebar] = useFilterSidebar(); - const [filterState, { setFilterType, setFilterObjects, resetFilterState }] = useFilter({ - library: viewer.library, - }); + const [isPopupVisible, { hidePopup, togglePopup }] = useFilterPopup(); - const workerState = useFilterWorker({ filterState, setFilterObjects, library: viewer.library }); + const [filterState, { setFilterType }] = useFilter({ + viewer: viewer, + }); const contextValue = React.useMemo( () => [ - { isSidebarVisible, filterState, ...workerState }, - { toggleSidebar, setFilterType, resetFilterState }, + { + sidebarState: { isVisible: isSidebarVisible }, + popupState: { isVisible: isPopupVisible }, + filterState, + }, + { toggleSidebar, setFilterType, hidePopup, togglePopup }, ], - [isSidebarVisible, filterState, workerState] + [isSidebarVisible, isPopupVisible, filterState] ); return {children}; @@ -32,12 +35,22 @@ const useFilterSidebar = () => { return [isSidebarVisible, toggleSidebar]; }; -const useFilter = ({ library }) => { +const useFilterPopup = () => { + const [isPopupVisible, setPopupVisibility] = React.useState(false); + + const hidePopup = () => setPopupVisibility(false); + const togglePopup = () => setPopupVisibility((prev) => !prev); + return [isPopupVisible, { hidePopup, togglePopup }]; +}; + +const useFilter = ({ viewer }) => { const DEFAULT_STATE = { - view: "initial", - subview: undefined, - type: "library", - objects: library, + view: FilterUtilities.VIEWS_IDS.initial, + type: FilterUtilities.TYPES_IDS.initial.library, + title: "Library", + // NOTE(amine): some filters may require additional information to work (ex: tags needs `id` to be able to get objects) + context: {}, + objects: viewer.library, search: { objects: [], tags: [], @@ -45,48 +58,24 @@ const useFilter = ({ library }) => { endDate: null, }, }; - const [filterState, setFilterState] = React.useState(DEFAULT_STATE); - const setFilterType = ({ view, subview = undefined, type }) => - setFilterState((prev) => ({ ...prev, view, subview, type })); + const setFilterType = ({ view, type, title, context }) => + setFilterState((prev) => ({ ...prev, view, type, title, context })); const setFilterObjects = (objects) => setFilterState((prev) => ({ ...prev, objects })); + useIsomorphicLayoutEffect(() => { + if (filterState.type === FilterUtilities.TYPES_IDS.initial.library) { + setFilterObjects(viewer.library); + return; + } - const resetFilterState = () => setFilterState(DEFAULT_STATE); + if (filterState.type === FilterUtilities.TYPES_IDS.initial.tags) { + const { slateId } = filterState.context; + setFilterObjects(viewer.slates.find((slate) => slate.id === slateId)?.objects || []); + return; + } + }, [filterState.type, filterState.context, filterState.view, viewer]); - return [filterState, { setFilterType, resetFilterState, setFilterObjects }]; -}; - -const useFilterWorker = ({ filterState, setFilterObjects, library }) => { - const DEFAULT_STATE = { loading: false, error: false }; - const [workerState, setWorkerState] = React.useState(DEFAULT_STATE); - - const { view, subview, type } = filterState; - - /** - * NOTE(amine): Web workers are usually pretty fast, - * but if it takes more than 500ms to handle a task, we'll show a loading screen - */ - const timeoutRef = React.useRef(); - - useWorker( - { - onStart: (worker) => { - worker.postMessage({ objects: library, view, subview, type }); - timeoutRef.current = setTimeout(() => { - setWorkerState((prev) => ({ ...prev, loading: true })); - }, 500); - }, - onError: () => setWorkerState((prev) => ({ ...prev, error: true })), - onMessage: (e) => { - clearTimeout(timeoutRef.current); - setWorkerState(DEFAULT_STATE); - setFilterObjects(e.data); - }, - }, - [view, subview, library, type] - ); - - return workerState; + return [filterState, { setFilterType, setFilterObjects }]; }; From 5233b5c056d124a7f662efd3d670621145f9bcb5 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Thu, 28 Oct 2021 17:15:22 +0100 Subject: [PATCH 09/21] feat(Filter/Popup): add responsive popup component --- components/core/Filter/Content.js | 2 +- components/core/Filter/Navbar.js | 28 +++++++++-- components/core/Filter/Popup.js | 80 +++++++++++++++++++++++++++++++ components/core/Filter/index.js | 22 ++++++++- scenes/SceneFilesFolder.js | 30 ++++++++++-- 5 files changed, 153 insertions(+), 9 deletions(-) create mode 100644 components/core/Filter/Popup.js diff --git a/components/core/Filter/Content.js b/components/core/Filter/Content.js index 965da507..3ec7e0f1 100644 --- a/components/core/Filter/Content.js +++ b/components/core/Filter/Content.js @@ -12,7 +12,7 @@ const STYLES_DATAVIEWER_WRAPPER = (theme) => css` min-height: 100vh; padding: calc(20px + ${theme.sizes.filterNavbar}px) 24px 44px; @media (max-width: ${theme.sizes.mobile}px) { - padding: 31px 16px 44px; + padding: calc(31px + ${theme.sizes.filterNavbar}px) 16px 44px; } `; diff --git a/components/core/Filter/Navbar.js b/components/core/Filter/Navbar.js index 0625a095..7a182917 100644 --- a/components/core/Filter/Navbar.js +++ b/components/core/Filter/Navbar.js @@ -1,5 +1,6 @@ import * as React from "react"; import * as ReactDOM from "react-dom"; +import * as Styles from "~/common/styles"; import { usePortals } from "~/components/core/PortalsProvider"; import { css } from "@emotion/react"; @@ -16,8 +17,9 @@ export function NavbarPortal({ children }) { return filterNavbarElement ? ReactDOM.createPortal( <> - +
{children}
+ , filterNavbarElement ) @@ -29,12 +31,27 @@ export function NavbarPortal({ children }) { * -----------------------------------------------------------------------------------------------*/ const STYLES_NAVBAR = (theme) => css` + position: relative; display: flex; justify-content: space-between; align-items: center; - background-color: ${theme.semantic.bgWhite}; padding: 9px 24px 11px; box-shadow: ${theme.shadow.lightSmall}; + + @media (max-width: ${theme.sizes.mobile}px) { + padding: 9px 16px 11px; + } +`; + +const STYLES_NAVBAR_BACKGROUND = (theme) => css` + position: absolute; + width: 100%; + height: 100%; + top: 0px; + left: 0px; + z-index: -1; + + background-color: ${theme.semantic.bgWhite}; @supports ((-webkit-backdrop-filter: blur(75px)) or (backdrop-filter: blur(75px))) { -webkit-backdrop-filter: blur(75px); backdrop-filter: blur(75px); @@ -49,5 +66,10 @@ const STYLES_NAVBAR = (theme) => css` export function Navbar({ children }) { const { filterNavbar } = usePortals(); const [, setFilterElement] = filterNavbar; - return
{children}
; + return ( +
+ {children} +
+
+ ); } diff --git a/components/core/Filter/Popup.js b/components/core/Filter/Popup.js new file mode 100644 index 00000000..1438f2e7 --- /dev/null +++ b/components/core/Filter/Popup.js @@ -0,0 +1,80 @@ +import * as React from "react"; +import * as SVG from "~/common/svg"; +import * as Styles from "~/common/styles"; +import * as Filters from "~/components/core/Filter/Filters"; + +import { useFilterContext } from "~/components/core/Filter/Provider"; +import { motion } from "framer-motion"; +import { css } from "@emotion/react"; + +/* ------------------------------------------------------------------------------------------------- + * Popup trigger + * -----------------------------------------------------------------------------------------------*/ + +export function PopupTrigger({ children, isMobile, ...props }) { + const [{ sidebarState, popupState }, { hidePopup, togglePopup }] = useFilterContext(); + + React.useEffect(() => { + if (sidebarState.isVisible) { + hidePopup(); + } + }, [sidebarState.isVisible]); + + if (sidebarState.isVisible && !isMobile) return null; + + return ( + + ); +} + +/* ------------------------------------------------------------------------------------------------- + * Popup + * -----------------------------------------------------------------------------------------------*/ +const STYLES_SIDEBAR_FILTER_WRAPPER = (theme) => css` + position: sticky; + top: ${theme.sizes.header + theme.sizes.filterNavbar}px; + width: 236px; + max-height: 420px; + overflow-y: auto; + overflow-x: hidden; + border-radius: 16px; + padding: 20px 16px; + box-shadow: ${theme.shadow.lightLarge}; + border: 1px solid ${theme.semantic.borderGrayLight}; + + background-color: ${theme.semantic.bgLight}; + @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); + } + + @media (max-width: ${theme.sizes.mobile}px) { + border-radius: unset; + width: 100%; + max-height: 375px; + padding: 15px 8px; + } +`; + +export function Popup({ viewer, css, ...props }) { + const [{ popupState }] = useFilterContext(); + + if (!popupState.isVisible) return null; + + return ( +
+ + +
+ ); +} diff --git a/components/core/Filter/index.js b/components/core/Filter/index.js index e9f6f0d1..98a88bcd 100644 --- a/components/core/Filter/index.js +++ b/components/core/Filter/index.js @@ -4,14 +4,21 @@ import * as System from "~/components/system"; import { Navbar, NavbarPortal } from "~/components/core/Filter/Navbar"; import { Provider } from "~/components/core/Filter/Provider"; import { Sidebar, SidebarTrigger } from "~/components/core/Filter/Sidebar"; +import { Popup, PopupTrigger } from "~/components/core/Filter/Popup"; import { Content } from "~/components/core/Filter/Content"; +import { useFilterContext } from "~/components/core/Filter/Provider"; /* ------------------------------------------------------------------------------------------------- * Title * -----------------------------------------------------------------------------------------------*/ function Title() { - return All; + const [{ filterState }] = useFilterContext(); + return ( + + {filterState.title} + + ); } /* ------------------------------------------------------------------------------------------------- @@ -21,4 +28,15 @@ function Actions() { return
; } -export { Title, Actions, Sidebar, SidebarTrigger, Provider, Navbar, Content, NavbarPortal }; +export { + Title, + Actions, + Sidebar, + SidebarTrigger, + Popup, + PopupTrigger, + Provider, + Navbar, + Content, + NavbarPortal, +}; diff --git a/scenes/SceneFilesFolder.js b/scenes/SceneFilesFolder.js index d350d9c4..019d4bad 100644 --- a/scenes/SceneFilesFolder.js +++ b/scenes/SceneFilesFolder.js @@ -17,12 +17,24 @@ const STYLES_SCENE_PAGE = css` `; const STYLES_FILTER_TITLE_WRAPPER = css` + ${Styles.MOBILE_HIDDEN}; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); `; +const STYLES_FILTER_POPUP_WRAPPER = (theme) => css` + position: absolute; + top: calc(${theme.sizes.filterNavbar}px + 4px); + left: 4px; + width: 100%; + @media (max-width: ${theme.sizes.mobile}px) { + top: ${theme.sizes.filterNavbar}px; + left: 0px; + } +`; + export default function SceneFilesFolder({ viewer, page, onAction, isMobile }) { const [index, setIndex] = React.useState(-1); @@ -47,9 +59,21 @@ export default function SceneFilesFolder({ viewer, page, onAction, isMobile }) { /> -
- +
+
+ +
+
+ +
+ + + + + +
+
@@ -57,7 +81,7 @@ export default function SceneFilesFolder({ viewer, page, onAction, isMobile }) {
- +
From f86bb5b8ab1e6fddf9104bbcf245dd9f86cf46b3 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Thu, 28 Oct 2021 18:03:12 +0100 Subject: [PATCH 10/21] feat(Filter/Sidebar): fix overflow --- components/core/Filter/Sidebar.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/core/Filter/Sidebar.js b/components/core/Filter/Sidebar.js index 4747f441..3373f92b 100644 --- a/components/core/Filter/Sidebar.js +++ b/components/core/Filter/Sidebar.js @@ -46,9 +46,9 @@ const STYLES_SIDEBAR_FILTER_WRAPPER = (theme) => css` position: sticky; top: ${theme.sizes.header + theme.sizes.filterNavbar}px; width: 236px; - height: 100vh; max-height: calc(100vh - ${theme.sizes.header + theme.sizes.filterNavbar}px); - padding: 20px; + overflow-y: auto; + padding: 20px 24px; background-color: ${theme.semantic.bgLight}; @media (max-width: ${theme.sizes.mobile}px) { From d65162d916c9a5bffb011645e5a43cfc326d9d05 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Mon, 29 Nov 2021 08:39:08 +0100 Subject: [PATCH 11/21] feat(ImageObjectPreview): prevent reloading images that are cached by the browser --- common/hooks.js | 6 +++ .../core/ObjectPreview/ImageObjectPreview.js | 41 ++++++++++--------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/common/hooks.js b/common/hooks.js index c94bded9..27958978 100644 --- a/common/hooks.js +++ b/common/hooks.js @@ -459,3 +459,9 @@ export const useDetectTextOverflow = ({ ref }, dependencies) => { return isTextOverflowing; }; + +let cache = {}; +export const useCache = () => { + const setCache = ({ key, value }) => (cache[key] = value); + return [cache, setCache]; +}; diff --git a/components/core/ObjectPreview/ImageObjectPreview.js b/components/core/ObjectPreview/ImageObjectPreview.js index b84676f8..4d1f1e87 100644 --- a/components/core/ObjectPreview/ImageObjectPreview.js +++ b/components/core/ObjectPreview/ImageObjectPreview.js @@ -6,8 +6,10 @@ import { AspectRatio } from "~/components/system"; import { useInView } from "~/common/hooks"; import { Blurhash } from "react-blurhash"; import { isBlurhashValid } from "blurhash"; - +import { AnimatePresence, motion } from "framer-motion"; import { css } from "@emotion/react"; +import { useCache } from "~/common/hooks"; + import ObjectPreviewPrimitive from "~/components/core/ObjectPreview/ObjectPreviewPrimitive"; const STYLES_PLACEHOLDER_ABSOLUTE = css` @@ -29,8 +31,8 @@ const STYLES_IMAGE = css` width: 100%; `; -const ImagePlaceholder = ({ blurhash }) => ( -
+const ImagePlaceholder = ({ blurhash, ...props }) => ( +
@@ -45,12 +47,9 @@ const ImagePlaceholder = ({ blurhash }) => (
-
+ ); -// NOTE(amine): cache -const cidsLoaded = {}; - export default function ImageObjectPreview({ url, file, @@ -58,22 +57,20 @@ export default function ImageObjectPreview({ tag, ...props }) { - const isCached = cidsLoaded[file.cid]; + /** NOTE(amine): To minimize the network load, we only load images when they're in view. + This creates an issue where cached images will reload each time they came to view. + To prevent reloading we'll keep track of the images that already loaded */ + const [cache, setCache] = useCache(); + const isCached = cache[file.cid]; const previewerRef = React.useRef(); - const [isLoading, setLoading] = React.useState(isCached); - const handleOnLoaded = () => { - cidsLoaded[file.cid] = true; - setLoading(false); - }; - const { isInView } = useInView({ ref: previewerRef, }); const { type, coverImage } = file; const imgTag = type.split("/")[1]; - + const imageUrl = coverImage ? coverImage?.url || Strings.getURLfromCID(coverImage?.cid) : url; const blurhash = React.useMemo(() => { return file.blurhash && isBlurhashValid(file.blurhash).result ? file.blurhash @@ -82,14 +79,14 @@ export default function ImageObjectPreview({ : null; }, [file]); - const shouldShowPlaceholder = isLoading && blurhash; - - const imageUrl = coverImage ? coverImage?.url || Strings.getURLfromCID(coverImage?.cid) : url; + const [isLoading, setLoading] = React.useState(true); + const handleOnLoaded = () => (setCache({ key: file.cid, value: true }), setLoading(false)); + const shouldShowPlaceholder = !isCached && isLoading && !!blurhash; return (
- {isInView && ( + {(isCached || isInView) && ( )} - {shouldShowPlaceholder && } + + {shouldShowPlaceholder && ( + + )} +
); From b3a79ba686413df5bc9468ff5bbce814b04acb83 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Mon, 29 Nov 2021 08:40:21 +0100 Subject: [PATCH 12/21] feat(TextObjectPreview): cache file's fetched content --- components/core/MarkdownFrame.js | 13 ++++++++++--- components/core/ObjectPreview/TextObjectPreview.js | 13 +++++++++++-- components/core/SlateMediaObject.js | 2 +- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/components/core/MarkdownFrame.js b/components/core/MarkdownFrame.js index c2339d46..70f8e68d 100644 --- a/components/core/MarkdownFrame.js +++ b/components/core/MarkdownFrame.js @@ -6,6 +6,7 @@ import * as Strings from "~/common/strings"; import { css } from "@emotion/react"; import { Markdown } from "~/components/system/components/Markdown"; import { H1, H2, H3, H4, P1, UL, OL, LI, A } from "~/components/system/components/Typography"; +import { useCache, useIsomorphicLayoutEffect } from "~/common/hooks"; const STYLES_ASSET = (theme) => css` padding: 120px calc(32px + 16px + 8px); @@ -133,12 +134,18 @@ const STYLES_INTENT = (theme) => css` ); `; -export default function MarkdownFrame({ url, date }) { - const [content, setContent] = React.useState(""); +export default function MarkdownFrame({ cid, url, date }) { + const [cache, setCache] = useCache(); + const cachedContent = cache[cid] || ""; + + const [content, setContent] = React.useState(cachedContent); + + useIsomorphicLayoutEffect(() => { + if (cachedContent) return; - React.useEffect(() => { fetch(url).then(async (res) => { const content = await res.text(); + setCache(content); setContent(content); }); }, []); diff --git a/components/core/ObjectPreview/TextObjectPreview.js b/components/core/ObjectPreview/TextObjectPreview.js index 56066ba4..f8f0dcec 100644 --- a/components/core/ObjectPreview/TextObjectPreview.js +++ b/components/core/ObjectPreview/TextObjectPreview.js @@ -5,7 +5,7 @@ import * as Styles from "~/common/styles"; import * as Utilities from "~/common/utilities"; import { P3 } from "~/components/system"; -import { useIsomorphicLayoutEffect } from "~/common/hooks"; +import { useCache, useIsomorphicLayoutEffect } from "~/common/hooks"; import { css } from "@emotion/react"; import FilePlaceholder from "~/components/core/ObjectPreview/placeholders/File"; @@ -29,12 +29,21 @@ const STYLES_TEXT_PREVIEW = (theme) => }); export default function TextObjectPreview({ url, file, ...props }) { - const [{ content, error }, setState] = React.useState({ content: "", error: undefined }); + const [cache, setCache] = useCache(); + const cachedContent = cache[file.cid] || ""; + + const [{ content, error }, setState] = React.useState({ + content: cachedContent, + error: undefined, + }); useIsomorphicLayoutEffect(() => { + if (cachedContent) return; + fetch(url) .then(async (res) => { const content = await res.text(); + setCache({ key: file.cid, value: content }); setState({ content }); }) .catch((e) => { diff --git a/components/core/SlateMediaObject.js b/components/core/SlateMediaObject.js index c01b2f78..602be0d8 100644 --- a/components/core/SlateMediaObject.js +++ b/components/core/SlateMediaObject.js @@ -198,7 +198,7 @@ export default class SlateMediaObject extends React.Component { } if (Validations.isMarkdown(file.filename, type)) { - return ; + return ; } if (Validations.isPreviewableImage(type)) { From de5bafe58fea388bb3bc914a64c6f9bfce5d8ba6 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Mon, 29 Nov 2021 17:29:27 +0100 Subject: [PATCH 13/21] feat(constants): add intercomWidget to sizes --- common/constants.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/common/constants.js b/common/constants.js index 3ce14f46..4883c24d 100644 --- a/common/constants.js +++ b/common/constants.js @@ -4,15 +4,16 @@ export const values = { }; export const sizes = { - mobile: 768, + header: 52, navigation: 288, + intercomWidget: 60, sidebar: 416, - // NOTE(amine): header's height + filter navbar's height - header: 55, filterNavbar: 40, + topOffset: 0, //NOTE(martina): Pushes UI down. 16 when there is a persistent announcement banner, 0 otherwise + + mobile: 768, tablet: 960, desktop: 1024, - topOffset: 0, //NOTE(martina): Pushes UI down. 16 when there is a persistent announcement banner, 0 otherwise }; export const system = { From e9caf827db5cf5326589d93fa864d5cb8671233e Mon Sep 17 00:00:00 2001 From: Aminejv Date: Mon, 29 Nov 2021 17:31:11 +0100 Subject: [PATCH 14/21] feat(Search/store): manage search state --- components/core/Search/store.js | 48 +++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 components/core/Search/store.js diff --git a/components/core/Search/store.js b/components/core/Search/store.js new file mode 100644 index 00000000..aedad5b7 --- /dev/null +++ b/components/core/Search/store.js @@ -0,0 +1,48 @@ +import * as Actions from "~/common/actions"; +import * as Events from "~/common/custom-events"; + +import create from "zustand"; + +const DEFAULT_STATE = { + query: "", + results: null, + isSearching: false, + isFetchingResults: false, +}; + +export const useSearchStore = create((set) => { + const search = async ({ types, query, globalSearch, tagIds, grouped }) => { + //for more context, look at node_common/managers/search/search.js + //userId: (optional) the id of the user whose stuff we are searching through. If specified, globalSearch is disregarded since the search will be limited to that user's public items. Does not apply when searching for type USER + //types: leaving it null searches everything. Doing ["SLATE"] searches just slates, doing ["USER", "FILE"] searches users and files. + //globalSearch: whether you are just searching the user's files/slates or global files/slates. This option doesn't exist for searching users since there is no notion of public or private users + //tagIds: only applies when searching files. the ids of the tags (aka collections) you are searching within. aka if you only want to search for files in a given slate, provide that slate's id. If no tag ids are provided, it searches all files + //grouped: whether to group the results by type (slate, user, file) when searching multiple types. Doesn't apply when searching only one type e.g. types: ["SLATE"] + set((prev) => ({ ...prev, isFetchingResults: true })); + const response = await Actions.search({ + types, + query, + globalSearch, + tagIds, + grouped, + }); + + Events.hasError(response); + const { results = [] } = response; + const files = results?.files || results; + const slates = results?.slates || []; + + set((prev) => ({ ...prev, results: { files, slates }, isFetchingResults: false })); + }; + + const clearSearch = () => set((prev) => ({ ...prev, ...DEFAULT_STATE })); + + const setQuery = (query) => set((prev) => ({ ...prev, isSearching: true, query })); + + return { + ...DEFAULT_STATE, + search, + setQuery, + clearSearch, + }; +}); From 8ec4aae957f6c8215f259a233952da28f8c54be1 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Mon, 29 Nov 2021 17:34:16 +0100 Subject: [PATCH 15/21] feat(Search/index): add search components --- components/core/Search/index.js | 231 ++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 components/core/Search/index.js diff --git a/components/core/Search/index.js b/components/core/Search/index.js new file mode 100644 index 00000000..7484dc93 --- /dev/null +++ b/components/core/Search/index.js @@ -0,0 +1,231 @@ +import * as React from "react"; +import * as SVG from "~/common/svg"; +import * as Styles from "~/common/styles"; + +import { css } from "@emotion/react"; +import { Input as InputPrimitive } from "~/components/system/components/Input"; +import { useSearchStore } from "~/components/core/Search/store"; +import { LoaderSpinner } from "~/components/system/components/Loaders"; +import { FileTypeGroup } from "~/components/core/FileTypeIcon"; +import { Link } from "~/components/core/Link"; + +import DataView from "~/components/core/DataView"; +import CollectionPreviewBlock from "~/components/core/CollectionPreviewBlock"; +import EmptyState from "~/components/core/EmptyState"; +import omit from "lodash.omit"; + +/* ------------------------------------------------------------------------------------------------- + * Input + * -----------------------------------------------------------------------------------------------*/ + +const STYLES_SEARCH_COMPONENT = (theme) => css` + background-color: transparent; + box-shadow: none; + height: 100%; + input { + height: 100%; + padding: 0px 4px; + border-radius: 0px; + } + &::placeholder { + color: ${theme.semantic.textGray}; + } +`; + +const useSearchViaParams = ({ params, handleSearch }) => { + const { setQuery, clearSearch } = useSearchStore(); + + React.useEffect(() => { + if (params?.s) { + setQuery(params.s); + handleSearch(params.s); + } + }, []); + + // NOTE(amine): if we change + React.useEffect(() => { + if (!params.s) clearSearch(); + }, [params.s]); +}; + +const useDebouncedSearch = ({ handleSearch }) => { + const { query } = useSearchStore(); + + const timeRef = React.useRef(); + React.useEffect(() => { + timeRef.current = setTimeout(() => handleSearch(query), 300); + return () => clearTimeout(timeRef.current); + }, [query]); +}; + +function Input({ viewer, data, page, onAction }) { + const { search, query, isFetchingResults, setQuery } = useSearchStore(); + + const handleSearch = async (query) => { + // NOTE(amine): update params with search query + onAction({ + type: "UPDATE_PARAMS", + params: query?.length > 0 ? { s: query } : omit(page.params, ["s"]), + }); + + if (!query) return; + + // NOTE(amine): searching on your own tag. + if (page.id === "NAV_SLATE" && data?.ownerId === viewer?.id) { + search({ + types: ["FILE"], + tagIds: [data.id], + query, + }); + return; + } + + //NOTE(amine): searching on another user's tag + if (page.id === "NAV_SLATE" && data?.ownerId !== viewer?.id) { + search({ + types: ["FILE"], + tagIds: [data.id], + query, + globalSearch: true, + }); + return; + } + + //NOTE(amine): searching on another user's profile + if (page.id === "NAV_PROFILE" && data?.id !== viewer?.id) { + console.log(data?.id, page.id, page.id === "NAV_PROFILE", data?.id !== viewer?.id); + search({ + types: ["SLATE", "FILE"], + userId: data.id, + query, + globalSearch: true, + grouped: true, + }); + return; + } + + //NOTE(amine): searching on library + if (viewer) { + search({ + types: ["FILE", "SLATE"], + query, + grouped: true, + }); + return; + } + + // NOTE(amine): global search + search({ + types: ["FILE", "SLATE", "USER"], + globalSearch: true, + query: query, + grouped: true, + }); + }; + + useSearchViaParams({ params: page.params, onAction, handleSearch }); + + useDebouncedSearch({ handleSearch }); + + return ( +
+ handleSearch(query)} + value={query} + onChange={(e) => setQuery(e.target.value)} + /> + + {isFetchingResults && ( +
+ +
+ )} +
+ ); +} + +/* ------------------------------------------------------------------------------------------------- + * Dismiss + * -----------------------------------------------------------------------------------------------*/ + +const STYLES_DISMISS_BUTTON = (theme) => css` + display: block; + ${Styles.BUTTON_RESET}; + color: ${theme.semantic.textGray}; +`; + +function Dismiss({ css, ...props }) { + const { clearSearch } = useSearchStore(); + + return ( + + ); +} + +/* ------------------------------------------------------------------------------------------------- + * Content + * -----------------------------------------------------------------------------------------------*/ + +const STYLES_SEARCH_CONTENT = (theme) => css` + width: 100%; + min-height: 100vh; + padding: calc(20px + ${theme.sizes.filterNavbar}px) 24px 44px; + @media (max-width: ${theme.sizes.mobile}px) { + padding: calc(31px + ${theme.sizes.filterNavbar}px) 16px 44px; + } +`; + +function Content({ onAction, viewer, page }) { + const { results } = useSearchStore(); + const { files, slates } = results; + + if (results.files.length === 0 && results.slates.length === 0) { + return ( +
+ + +
Sorry we couldn't find any results.
+
+
+ ); + } + + return ( +
+ + {slates.length > 0 ? ( +
+ {slates.map((slate) => ( + + + + ))} +
+ ) : null} +
+ ); +} + +export { Input, Dismiss, Content }; From e8f29c10a8376ea5edabbd48d55cdeef61971e31 Mon Sep 17 00:00:00 2001 From: Aminejv Date: Mon, 29 Nov 2021 17:36:35 +0100 Subject: [PATCH 16/21] feat(Filter): remove filter state --- common/filter-utilities.js | 10 --- components/core/Filter/Content.js | 43 ----------- components/core/Filter/Filters.js | 64 ++++++---------- components/core/Filter/Popup.js | 14 +++- components/core/Filter/Provider.js | 50 +----------- components/core/Filter/Sidebar.js | 16 ++-- components/core/Filter/index.js | 117 ++++++++++++++++++++++++----- 7 files changed, 145 insertions(+), 169 deletions(-) delete mode 100644 common/filter-utilities.js delete mode 100644 components/core/Filter/Content.js diff --git a/common/filter-utilities.js b/common/filter-utilities.js deleted file mode 100644 index 95f6559b..00000000 --- a/common/filter-utilities.js +++ /dev/null @@ -1,10 +0,0 @@ -export const VIEWS_IDS = { - initial: "initial", -}; - -export const TYPES_IDS = { - initial: { - library: "library", - tags: "tags", - }, -}; diff --git a/components/core/Filter/Content.js b/components/core/Filter/Content.js deleted file mode 100644 index 3ec7e0f1..00000000 --- a/components/core/Filter/Content.js +++ /dev/null @@ -1,43 +0,0 @@ -import * as React from "react"; - -import { FileTypeGroup } from "~/components/core/FileTypeIcon"; -import { css } from "@emotion/react"; - -import DataView from "~/components/core/DataView"; -import EmptyState from "~/components/core/EmptyState"; -import { useFilterContext } from "~/components/core/Filter/Provider"; - -const STYLES_DATAVIEWER_WRAPPER = (theme) => css` - width: 100%; - min-height: 100vh; - padding: calc(20px + ${theme.sizes.filterNavbar}px) 24px 44px; - @media (max-width: ${theme.sizes.mobile}px) { - padding: calc(31px + ${theme.sizes.filterNavbar}px) 16px 44px; - } -`; - -export function Content({ viewer, onAction, page, ...props }) { - const [{ filterState }] = useFilterContext(); - const { objects } = filterState; - - return ( -
- {objects.length ? ( - - ) : ( - - -
Drag and drop files into Slate to upload
-
- )} -
- ); -} diff --git a/components/core/Filter/Filters.js b/components/core/Filter/Filters.js index b13ad471..c6ab5e9c 100644 --- a/components/core/Filter/Filters.js +++ b/components/core/Filter/Filters.js @@ -2,10 +2,10 @@ import * as React from "react"; import * as SVG from "~/common/svg"; import * as Styles from "~/common/styles"; import * as Typography from "~/components/system/components/Typography"; -import * as FilterUtilities from "~/common/filter-utilities"; import { css } from "@emotion/react"; import { useFilterContext } from "~/components/core/Filter/Provider"; +import { Link } from "~/components/core/Link"; /* ------------------------------------------------------------------------------------------------- * Shared components between filters @@ -46,14 +46,14 @@ const STYLES_FILTERS_GROUP = css` const FilterButton = ({ children, Icon, isSelected, ...props }) => (
  • - - - {children} - + + + + + {children} + + +
  • ); @@ -72,26 +72,20 @@ const FilterSection = ({ title, children, ...props }) => ( * InitialFilters * -----------------------------------------------------------------------------------------------*/ -function Library() { - const [{ filterState }, { setFilterType, hidePopup }] = useFilterContext(); - const currentFilterType = filterState.type; +function Library({ page, onAction }) { + const [, { hidePopup }] = useFilterContext(); - const libraryFilterType = FilterUtilities.TYPES_IDS.initial.library; + const isSelected = page.id === "NAV_DATA"; return ( <> { - setFilterType({ - view: FilterUtilities.VIEWS_IDS.initial, - type: libraryFilterType, - title: "Library", - }); - hidePopup(); - }} + onClick={hidePopup} > My Library @@ -100,31 +94,19 @@ function Library() { ); } -function Tags({ viewer, ...props }) { - const [{ filterState }, { setFilterType, hidePopup }] = useFilterContext(); - const currentFilterType = filterState.type; - - const tagFilterType = FilterUtilities.TYPES_IDS.initial.tags; - - const checkIsTagSelected = (slateId) => - currentFilterType === tagFilterType && filterState?.context?.slateId === slateId; - - const setSelectedTag = (slate) => - setFilterType({ - view: FilterUtilities.VIEWS_IDS.initial, - type: tagFilterType, - title: "#" + slate.slatename, - context: { slateId: slate.id }, - }); +function Tags({ viewer, data, onAction, ...props }) { + const [, { hidePopup }] = useFilterContext(); return ( {viewer.slates.map((slate) => ( (setSelectedTag(slate), hidePopup())} + href={`/$/slate/${slate.id}`} + isSelected={slate.id === data?.id} + onAction={onAction} + Icon={slate.isPublic ? SVG.Hash : SVG.SecurityLock} + onClick={hidePopup} > {slate.slatename} diff --git a/components/core/Filter/Popup.js b/components/core/Filter/Popup.js index 1438f2e7..822192bb 100644 --- a/components/core/Filter/Popup.js +++ b/components/core/Filter/Popup.js @@ -29,7 +29,7 @@ export function PopupTrigger({ children, isMobile, ...props }) { {...props} > {children} - + @@ -66,15 +66,21 @@ const STYLES_SIDEBAR_FILTER_WRAPPER = (theme) => css` } `; -export function Popup({ viewer, css, ...props }) { +export function Popup({ viewer, onAction, css, data, page, ...props }) { const [{ popupState }] = useFilterContext(); if (!popupState.isVisible) return null; return (
    - - + +
    ); } diff --git a/components/core/Filter/Provider.js b/components/core/Filter/Provider.js index 205f3edb..5c7db6d1 100644 --- a/components/core/Filter/Provider.js +++ b/components/core/Filter/Provider.js @@ -1,29 +1,22 @@ import * as React from "react"; -import { useIsomorphicLayoutEffect } from "~/common/hooks"; -import * as FilterUtilities from "~/common/filter-utilities"; const UploadContext = React.createContext({}); export const useFilterContext = () => React.useContext(UploadContext); -export const Provider = ({ children, viewer }) => { +export const Provider = ({ children }) => { const [isSidebarVisible, toggleSidebar] = useFilterSidebar(); const [isPopupVisible, { hidePopup, togglePopup }] = useFilterPopup(); - const [filterState, { setFilterType }] = useFilter({ - viewer: viewer, - }); - const contextValue = React.useMemo( () => [ { sidebarState: { isVisible: isSidebarVisible }, popupState: { isVisible: isPopupVisible }, - filterState, }, - { toggleSidebar, setFilterType, hidePopup, togglePopup }, + { toggleSidebar, hidePopup, togglePopup }, ], - [isSidebarVisible, isPopupVisible, filterState] + [isSidebarVisible, isPopupVisible] ); return {children}; @@ -42,40 +35,3 @@ const useFilterPopup = () => { const togglePopup = () => setPopupVisibility((prev) => !prev); return [isPopupVisible, { hidePopup, togglePopup }]; }; - -const useFilter = ({ viewer }) => { - const DEFAULT_STATE = { - view: FilterUtilities.VIEWS_IDS.initial, - type: FilterUtilities.TYPES_IDS.initial.library, - title: "Library", - // NOTE(amine): some filters may require additional information to work (ex: tags needs `id` to be able to get objects) - context: {}, - objects: viewer.library, - search: { - objects: [], - tags: [], - startDate: null, - endDate: null, - }, - }; - const [filterState, setFilterState] = React.useState(DEFAULT_STATE); - - const setFilterType = ({ view, type, title, context }) => - setFilterState((prev) => ({ ...prev, view, type, title, context })); - - const setFilterObjects = (objects) => setFilterState((prev) => ({ ...prev, objects })); - useIsomorphicLayoutEffect(() => { - if (filterState.type === FilterUtilities.TYPES_IDS.initial.library) { - setFilterObjects(viewer.library); - return; - } - - if (filterState.type === FilterUtilities.TYPES_IDS.initial.tags) { - const { slateId } = filterState.context; - setFilterObjects(viewer.slates.find((slate) => slate.id === slateId)?.objects || []); - return; - } - }, [filterState.type, filterState.context, filterState.view, viewer]); - - return [filterState, { setFilterType, setFilterObjects }]; -}; diff --git a/components/core/Filter/Sidebar.js b/components/core/Filter/Sidebar.js index 3373f92b..23b2ed30 100644 --- a/components/core/Filter/Sidebar.js +++ b/components/core/Filter/Sidebar.js @@ -45,10 +45,10 @@ export function SidebarTrigger({ css }) { const STYLES_SIDEBAR_FILTER_WRAPPER = (theme) => css` position: sticky; top: ${theme.sizes.header + theme.sizes.filterNavbar}px; - width: 236px; + width: 300px; max-height: calc(100vh - ${theme.sizes.header + theme.sizes.filterNavbar}px); overflow-y: auto; - padding: 20px 24px; + padding: 20px 24px calc(16px + ${theme.sizes.intercomWidget}px + ${theme.sizes.filterNavbar}px); background-color: ${theme.semantic.bgLight}; @media (max-width: ${theme.sizes.mobile}px) { @@ -56,15 +56,21 @@ const STYLES_SIDEBAR_FILTER_WRAPPER = (theme) => css` } `; -export function Sidebar({ viewer, isMobile }) { +export function Sidebar({ viewer, onAction, data, page, isMobile }) { const [{ sidebarState }] = useFilterContext(); if (!sidebarState.isVisible || isMobile) return null; return (
    - - + +
    ); } diff --git a/components/core/Filter/index.js b/components/core/Filter/index.js index 98a88bcd..84712fb1 100644 --- a/components/core/Filter/index.js +++ b/components/core/Filter/index.js @@ -1,22 +1,30 @@ import * as React from "react"; import * as System from "~/components/system"; +import * as Styles from "~/common/styles"; +import * as Search from "~/components/core/Search"; -import { Navbar, NavbarPortal } from "~/components/core/Filter/Navbar"; +import { css } from "@emotion/react"; +import { NavbarPortal } from "~/components/core/Filter/Navbar"; import { Provider } from "~/components/core/Filter/Provider"; import { Sidebar, SidebarTrigger } from "~/components/core/Filter/Sidebar"; import { Popup, PopupTrigger } from "~/components/core/Filter/Popup"; -import { Content } from "~/components/core/Filter/Content"; -import { useFilterContext } from "~/components/core/Filter/Provider"; +import { useSearchStore } from "~/components/core/Search/store"; /* ------------------------------------------------------------------------------------------------- * Title * -----------------------------------------------------------------------------------------------*/ -function Title() { - const [{ filterState }] = useFilterContext(); +function Title({ page, data }) { + const { query, isSearching } = useSearchStore(); + let title = React.useMemo(() => { + if (isSearching) return `Searching for ${query}`; + if (page.id === "NAV_DATA") return "My library"; + if (page.id === "NAV_SLATE" && data?.slatename) return "# " + data?.slatename; + }, [page, data, query, isSearching]); + return ( - - {filterState.title} + + {title} ); } @@ -24,19 +32,90 @@ function Title() { /* ------------------------------------------------------------------------------------------------- * Actions * -----------------------------------------------------------------------------------------------*/ + function Actions() { return
    ; } -export { - Title, - Actions, - Sidebar, - SidebarTrigger, - Popup, - PopupTrigger, - Provider, - Navbar, - Content, - NavbarPortal, -}; +const STYLES_FILTER_TITLE_WRAPPER = css` + ${Styles.MOBILE_HIDDEN}; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +`; + +const STYLES_FILTER_POPUP_WRAPPER = (theme) => css` + position: absolute; + top: calc(${theme.sizes.filterNavbar}px + 4px); + left: 4px; + width: 100%; + @media (max-width: ${theme.sizes.mobile}px) { + top: ${theme.sizes.filterNavbar}px; + left: 0px; + } +`; + +/* ------------------------------------------------------------------------------------------------- + * Filter + * -----------------------------------------------------------------------------------------------*/ + +const STYLES_FILTER_CONTENT = (theme) => css` + ${Styles.HORIZONTAL_CONTAINER}; + width: 100%; + margin-top: ${theme.sizes.filterNavbar}px; +`; + +export default function Filter({ isActive, viewer, onAction, page, data, isMobile, children }) { + const { results, isSearching } = useSearchStore(); + + if (!isActive) { + return children; + } + + const showSearchResult = isSearching && !!results; + + return ( + <> + + +
    + +
    + +
    +
    + +
    + + + + </span> + </PopupTrigger> + </div> + + <div css={STYLES_FILTER_TITLE_WRAPPER}> + <Title page={page} data={data} /> + </div> + <Actions /> + </NavbarPortal> + <div css={STYLES_FILTER_CONTENT}> + <Sidebar + viewer={viewer} + onAction={onAction} + data={data} + page={page} + isMobile={isMobile} + /> + <div style={{ flexGrow: 1 }}> + {showSearchResult ? ( + <Search.Content viewer={viewer} page={page} onAction={onAction} /> + ) : ( + children + )} + </div> + </div> + </Provider> + </> + ); +} From 10db14380d1d82c031d42c9506b9baf89d89f47d Mon Sep 17 00:00:00 2001 From: Aminejv <elouartinra@hotmail.com> Date: Mon, 29 Nov 2021 17:39:46 +0100 Subject: [PATCH 17/21] feat(Application): wrap data, slate and profile scenes with filter components --- components/core/Application.js | 37 ++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/components/core/Application.js b/components/core/Application.js index 7385baca..0b3a02f3 100644 --- a/components/core/Application.js +++ b/components/core/Application.js @@ -46,6 +46,7 @@ import ApplicationLayout from "~/components/core/ApplicationLayout"; import WebsitePrototypeWrapper from "~/components/core/WebsitePrototypeWrapper"; import PortalsProvider from "~/components/core/PortalsProvider"; import CTATransition from "~/components/core/CTATransition"; +import Filter from "~/components/core/Filter"; import { GlobalModal } from "~/components/system/components/GlobalModal"; import { OnboardingModal } from "~/components/core/OnboardingModal"; @@ -347,6 +348,7 @@ export default class ApplicationPage extends React.Component { let body = document.documentElement || document.body; if (page.id === "NAV_SLATE" || page.id === "NAV_PROFILE") { state.loading = true; + state.data = { id: details.id }; } this.setState(state, () => { if (!popstate) { @@ -494,19 +496,28 @@ export default class ApplicationPage extends React.Component { isMac={this.props.isMac} viewer={this.state.viewer} > - {this.state.loading ? ( - <div - css={Styles.CONTAINER_CENTERED} - style={{ - width: "100vw", - height: "100vh", - }} - > - <LoaderSpinner style={{ height: 32, width: 32 }} /> - </div> - ) : ( - scene - )} + <Filter + isActive={!!this.state.viewer} + viewer={this.state.viewer} + page={page} + data={this.state.data} + isMobile={this.props.isMobile} + onAction={this._handleAction} + > + {this.state.loading ? ( + <div + css={Styles.CONTAINER_CENTERED} + style={{ + width: "100%", + height: "100vh", + }} + > + <LoaderSpinner style={{ height: 32, width: 32 }} /> + </div> + ) : ( + scene + )} + </Filter> </ApplicationLayout> </PortalsProvider> <GlobalModal /> From e910f63a328423b291062a7abb4ab6c616a539e4 Mon Sep 17 00:00:00 2001 From: Aminejv <elouartinra@hotmail.com> Date: Mon, 29 Nov 2021 17:48:46 +0100 Subject: [PATCH 18/21] feat(SceneSlate): remove slate actions --- scenes/SceneSlate.js | 120 +++++++++++++++++++------------------------ 1 file changed, 52 insertions(+), 68 deletions(-) diff --git a/scenes/SceneSlate.js b/scenes/SceneSlate.js index 3ae2d3b8..c4f45591 100644 --- a/scenes/SceneSlate.js +++ b/scenes/SceneSlate.js @@ -261,6 +261,26 @@ export default class SceneSlate extends React.Component { } } +const STYLES_RESET_SCENE_PAGE_PADDING = css` + padding: 0px; + @media (max-width: ${Constants.sizes.mobile}px) { + padding: 0px; + } +`; + +const STYLES_DATAVIEWER_WRAPPER = (theme) => css` + width: 100%; + min-height: calc(100vh - ${theme.sizes.filterNavbar}px); + padding: calc(20px + ${theme.sizes.filterNavbar}px) 24px 44px; + @media (max-width: ${theme.sizes.mobile}px) { + padding: calc(31px + ${theme.sizes.filterNavbar}px) 16px 44px; + } +`; + +const STYLES_DATAVIEWER_WRAPPER_EXTERNAL = css` + margin-top: 40px; +`; + class SlatePage extends React.Component { _copy = null; _timeout = null; @@ -394,74 +414,34 @@ class SlatePage extends React.Component { const { user, name, objects, body, isPublic, ownerId } = this.props.data; const isOwner = this.props.viewer ? ownerId === this.props.viewer.id : false; - let actions = isOwner ? ( - <span css={Styles.HORIZONTAL_CONTAINER}> - <SquareButtonGray onClick={this._handleDownload} style={{ marginRight: 16 }}> - <SVG.Download height="16px" /> - </SquareButtonGray> - <Upload.Trigger viewer={this.props.viewer} style={{ marginRight: 16 }}> - <SquareButtonGray> - <SVG.Plus height="16px" /> - </SquareButtonGray> - </Upload.Trigger> - <SquareButtonGray onClick={this._handleShowSettings}> - <SVG.Settings height="16px" /> - </SquareButtonGray> - </span> - ) : ( - <div style={{ display: `flex` }}> - <SquareButtonGray onClick={this._handleDownload} style={{ marginRight: 16 }}> - <SVG.Download height="16px" /> - </SquareButtonGray> - <div onClick={this._handleSubscribe}> - {this.state.isSubscribed ? ( - <ButtonSecondary>Unsubscribe</ButtonSecondary> - ) : ( - <ButtonPrimary>Subscribe</ButtonPrimary> - )} - </div> - </div> - ); return ( - <ScenePage> - <ScenePageHeader - wide - title={ - user && !isOwner ? ( - <span> - <Link href={`/$/user/${user.id}`} onAction={this.props.onAction}> - <span - // onClick={() => - // this.props.onAction({ - // type: "NAVIGATE", - // value: "NAV_PROFILE", - // shallow: true, - // data: user, - // }) - // } - css={STYLES_USERNAME} - > - {user.username} - </span>{" "} - </Link> - / {name} - </span> - ) : ( - <div css={Styles.HORIZONTAL_CONTAINER_CENTERED}> - <span>{name}</span> - {isOwner && !isPublic && ( - <div css={STYLES_SECURITY_LOCK_WRAPPER} style={{ marginLeft: 16 }}> - <SVG.SecurityLock height="16px" style={{ display: "block" }} /> - </div> - )} - </div> - ) - } - actions={<span css={STYLES_MOBILE_HIDDEN}>{actions}</span>} - > - {body} - </ScenePageHeader> - <span css={STYLES_MOBILE_ONLY}>{actions}</span> + <ScenePage css={!this.props.external && STYLES_RESET_SCENE_PAGE_PADDING}> + {this.props.external ? ( + <ScenePageHeader + wide + title={ + user && !isOwner ? ( + <span> + <Link href={`/$/user/${user.id}`} onAction={this.props.onAction}> + <span css={STYLES_USERNAME}>{user.username}</span>{" "} + </Link> + / {name} + </span> + ) : ( + <div css={Styles.HORIZONTAL_CONTAINER_CENTERED}> + <span>{name}</span> + {isOwner && !isPublic && ( + <div css={STYLES_SECURITY_LOCK_WRAPPER} style={{ marginLeft: 16 }}> + <SVG.SecurityLock height="16px" style={{ display: "block" }} /> + </div> + )} + </div> + ) + } + > + {body} + </ScenePageHeader> + ) : null} {objects && objects.length ? ( <> <GlobalCarousel @@ -476,7 +456,11 @@ class SlatePage extends React.Component { index={this.state.index} onChange={(index) => this.setState({ index })} /> - <div style={{ marginTop: 40 }}> + <div + css={ + this.props.external ? STYLES_DATAVIEWER_WRAPPER_EXTERNAL : STYLES_DATAVIEWER_WRAPPER + } + > <DataView key="scene-files-folder" type="collection" From e454e30c85539f25331558f3dedd26f497dc998b Mon Sep 17 00:00:00 2001 From: Aminejv <elouartinra@hotmail.com> Date: Mon, 29 Nov 2021 18:39:00 +0100 Subject: [PATCH 19/21] feat(ApplicationHeader): add search input --- components/core/ApplicationHeader.js | 85 +++------------------------- 1 file changed, 9 insertions(+), 76 deletions(-) diff --git a/components/core/ApplicationHeader.js b/components/core/ApplicationHeader.js index fcc199ff..59780c28 100644 --- a/components/core/ApplicationHeader.js +++ b/components/core/ApplicationHeader.js @@ -1,11 +1,9 @@ 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 * as Styles from "~/common/styles"; import * as Upload from "~/components/core/Upload"; -import * as Filter from "~/components/core/Filter"; -import * as Actions from "~/common/actions"; +import * as Search from "~/components/core/Search"; import { ApplicationUserControls, @@ -18,29 +16,10 @@ 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/components/Input"; +import { useMediaQuery } from "~/common/hooks"; import { AnimatePresence, motion } from "framer-motion"; - -const STYLES_SEARCH_COMPONENT = (theme) => css` - background-color: transparent; - box-shadow: none; - height: 100%; - input { - height: 100%; - padding: 0px 4px; - border-radius: 0px; - } - &::placeholder { - color: ${theme.semantic.textGray}; - } -`; - -const STYLES_DISMISS_BUTTON = (theme) => css` - display: block; - ${Styles.BUTTON_RESET}; - color: ${theme.semantic.textGray}; -`; +import { Navbar as FilterNavbar } from "~/components/core/Filter/Navbar"; +import { useSearchStore } from "~/components/core/Search/store"; const STYLES_APPLICATION_HEADER_BACKGROUND = (theme) => css` position: absolute; @@ -141,7 +120,6 @@ export default function ApplicationHeader({ viewer, page, data, onAction }) { showDropdown: false, popup: null, isRefreshing: false, - query: "", }); const _handleTogglePopup = (value) => { @@ -152,38 +130,10 @@ export default function ApplicationHeader({ viewer, page, data, onAction }) { } }; - const _handleInputChange = (e) => { - setState((prev) => ({ ...prev, query: e.target.value })); - }; - - //TODO(amine): plug in the right values - //for more context, look at node_common/managers/search/search.js - //userId: (optional) the id of the user whose stuff we are searching through. If specified, globalSearch is disregarded since the search will be limited to that user's public items. Does not apply when searching for type USER - //types: leaving it null searches everything. Doing ["SLATE"] searches just slates, doing ["USER", "FILE"] searches users and files. - //globalSearch: whether you are just searching the user's files/slates or global files/slates. This option doesn't exist for searching users since there is no notion of public or private users - //tagIds: only applies when searching files. the ids of the tags (aka collections) you are searching within. aka if you only want to search for files in a given slate, provide that slate's id. If no tag ids are provided, it searches all files - //grouped: whether to group the results by type (slate, user, file) when searching multiple types. Doesn't apply when searching only one type e.g. types: ["SLATE"] - const handleSearch = async () => { - const response = await Actions.search({ - types: null, - query: state.query, - globalSearch: true, - tagIds: null, - grouped: true, - }); - console.log(response); - }; - - const { getFieldProps, value: searchQuery, setFieldValue } = useField({ - initialValue: "", - onSubmit: handleSearch, - }); - - const handleDismissSearch = () => setFieldValue(""); + const { isSearching } = useSearchStore(); const { mobile } = useMediaQuery(); const isSignedOut = !viewer; - const isSearching = searchQuery.length !== 0; return ( <> @@ -209,17 +159,7 @@ export default function ApplicationHeader({ viewer, page, data, onAction }) { </div> <div css={STYLES_MIDDLE}> {/**TODO: update Search component */} - <Input - containerStyle={{ height: "100%" }} - full - placeholder={`Search ${!viewer ? "slate.host" : ""}`} - inputCss={STYLES_SEARCH_COMPONENT} - onSubmit={handleSearch} - name="search" - {...getFieldProps()} - onChange={_handleInputChange} - value={state.query} - /> + <Search.Input viewer={viewer} data={data} onAction={onAction} page={page} /> </div> <Upload.Provider page={page} data={data} viewer={viewer}> <Upload.Root data={data}> @@ -237,7 +177,6 @@ export default function ApplicationHeader({ viewer, page, data, onAction }) { isSearching={isSearching} isSignedOut={isSignedOut} onAction={onAction} - onDismissSearch={handleDismissSearch} /> </div> </Upload.Root> @@ -260,14 +199,14 @@ export default function ApplicationHeader({ viewer, page, data, onAction }) { </div> <div css={STYLES_FILTER_NAVBAR}> <Show when={!!viewer}> - <Filter.Navbar /> + <FilterNavbar /> </Show> </div> </> ); } -const UserActions = ({ uploadAction, isSignedOut, isSearching, onAction, onDismissSearch }) => { +const UserActions = ({ uploadAction, isSignedOut, isSearching, onAction }) => { const authActions = React.useMemo( () => ( <> @@ -315,13 +254,7 @@ const UserActions = ({ uploadAction, isSignedOut, isSearching, onAction, onDismi 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> + <Search.Dismiss style={{ marginLeft: 4 }} /> </motion.div> </Match> </Switch> From 66e9559c421dfd4d7cdf5e1ce36a2d9fc5dbadf7 Mon Sep 17 00:00:00 2001 From: Aminejv <elouartinra@hotmail.com> Date: Mon, 29 Nov 2021 18:39:41 +0100 Subject: [PATCH 20/21] feat(SceneFilesFolder): remove Filter components --- scenes/SceneFilesFolder.js | 68 ++++++++++++++------------------------ 1 file changed, 25 insertions(+), 43 deletions(-) diff --git a/scenes/SceneFilesFolder.js b/scenes/SceneFilesFolder.js index 019d4bad..3c8ab9b8 100644 --- a/scenes/SceneFilesFolder.js +++ b/scenes/SceneFilesFolder.js @@ -1,13 +1,15 @@ import * as React from "react"; import * as Constants from "~/common/constants"; -import * as Filter from "~/components/core/Filter"; import * as Styles from "~/common/styles"; import { css } from "@emotion/react"; import { GlobalCarousel } from "~/components/system/components/GlobalCarousel"; +import { FileTypeGroup } from "~/components/core/FileTypeIcon"; import ScenePage from "~/components/core/ScenePage"; import WebsitePrototypeWrapper from "~/components/core/WebsitePrototypeWrapper"; +import DataView from "~/components/core/DataView"; +import EmptyState from "~/components/core/EmptyState"; const STYLES_SCENE_PAGE = css` padding: 0px; @@ -16,22 +18,12 @@ const STYLES_SCENE_PAGE = css` } `; -const STYLES_FILTER_TITLE_WRAPPER = css` - ${Styles.MOBILE_HIDDEN}; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -`; - -const STYLES_FILTER_POPUP_WRAPPER = (theme) => css` - position: absolute; - top: calc(${theme.sizes.filterNavbar}px + 4px); - left: 4px; +const STYLES_DATAVIEWER_WRAPPER = (theme) => css` width: 100%; + min-height: calc(100vh - ${theme.sizes.filterNavbar}px); + padding: calc(20px + ${theme.sizes.filterNavbar}px) 24px 44px; @media (max-width: ${theme.sizes.mobile}px) { - top: ${theme.sizes.filterNavbar}px; - left: 0px; + padding: calc(31px + ${theme.sizes.filterNavbar}px) 16px 44px; } `; @@ -57,34 +49,24 @@ export default function SceneFilesFolder({ viewer, page, onAction, isMobile }) { index={index} onChange={(index) => setIndex(index)} /> - <Filter.Provider viewer={viewer}> - <Filter.NavbarPortal> - <div css={STYLES_FILTER_POPUP_WRAPPER}> - <Filter.Popup viewer={viewer} /> - </div> - - <div css={Styles.CONTAINER_CENTERED}> - <div css={Styles.MOBILE_HIDDEN}> - <Filter.SidebarTrigger /> - </div> - <Filter.PopupTrigger isMobile={isMobile} style={{ marginLeft: 2 }}> - <span css={Styles.MOBILE_ONLY} style={{ marginRight: 8 }}> - <Filter.Title /> - </span> - </Filter.PopupTrigger> - </div> - - <div css={STYLES_FILTER_TITLE_WRAPPER}> - <Filter.Title /> - </div> - <Filter.Actions /> - </Filter.NavbarPortal> - - <div css={Styles.HORIZONTAL_CONTAINER}> - <Filter.Sidebar viewer={viewer} isMobile={isMobile} /> - <Filter.Content onAction={onAction} viewer={viewer} page={page} /> - </div> - </Filter.Provider> + <div css={STYLES_DATAVIEWER_WRAPPER}> + {objects.length ? ( + <DataView + key="scene-files-folder" + isOwner={true} + items={objects} + onAction={onAction} + viewer={viewer} + page={page} + view="grid" + /> + ) : ( + <EmptyState> + <FileTypeGroup /> + <div style={{ marginTop: 24 }}>Drag and drop files into Slate to upload</div> + </EmptyState> + )} + </div> </ScenePage> </WebsitePrototypeWrapper> ); From 791665d861381820b57082967753a245a69f88d6 Mon Sep 17 00:00:00 2001 From: Aminejv <elouartinra@hotmail.com> Date: Mon, 29 Nov 2021 20:36:40 +0100 Subject: [PATCH 21/21] feat(Search): show search result when user is not logged in --- components/core/CollectionPreviewBlock/index.js | 9 +++++---- components/core/Filter/index.js | 9 ++++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/components/core/CollectionPreviewBlock/index.js b/components/core/CollectionPreviewBlock/index.js index c7985cbe..6b495765 100644 --- a/components/core/CollectionPreviewBlock/index.js +++ b/components/core/CollectionPreviewBlock/index.js @@ -118,9 +118,10 @@ export default function CollectionPreview({ collection, viewer, owner, onAction const title = collection.name || collection.slatename; const isOwner = viewer?.id === collection.ownerId; - const preview = React.useMemo(() => getObjectToPreview(collection.coverImage), [ - collection.coverImage, - ]); + const preview = React.useMemo( + () => getObjectToPreview(collection.coverImage), + [collection.coverImage] + ); return ( <div css={STYLES_CONTAINER}> @@ -228,7 +229,7 @@ function Metrics({ fileCount, owner, isOwner, onAction }) { </div> <div style={{ alignItems: "end" }} css={Styles.CONTAINER_CENTERED}> - {!isOwner && ( + {isOwner && ( <> <Link href={`/$/user/${owner.id}`} diff --git a/components/core/Filter/index.js b/components/core/Filter/index.js index 84712fb1..1d27e0de 100644 --- a/components/core/Filter/index.js +++ b/components/core/Filter/index.js @@ -68,13 +68,16 @@ const STYLES_FILTER_CONTENT = (theme) => css` export default function Filter({ isActive, viewer, onAction, page, data, isMobile, children }) { const { results, isSearching } = useSearchStore(); + const showSearchResult = isSearching && !!results; if (!isActive) { - return children; + return showSearchResult ? ( + <Search.Content viewer={viewer} page={page} onAction={onAction} /> + ) : ( + children + ); } - const showSearchResult = isSearching && !!results; - return ( <> <Provider>