Merge pull request #954 from filecoin-project/@aminejv/new-filtering

Filtering feature
This commit is contained in:
martinalong 2021-09-28 17:07:26 -07:00 committed by GitHub
commit 48e812c828
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1157 additions and 181 deletions

View File

@ -7,7 +7,8 @@ export const sizes = {
mobile: 768,
navigation: 288,
sidebar: 416,
header: 56,
// NOTE(amine): header's height + filter navbar's height
header: 52 + 40,
tablet: 960,
desktop: 1024,
topOffset: 0, //NOTE(martina): Pushes UI down. 16 when there is a persistent announcement banner, 0 otherwise

View File

@ -0,0 +1,86 @@
import {
isImageType,
isVideoType,
isAudioType,
isDocument,
isTwitterLink,
isYoutubeLink,
isTwitchLink,
isGithubLink,
isInstagramLink,
} from "~/common/validations";
export const FILTER_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",
},
},
},
},
};
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];
};

View File

@ -424,3 +424,30 @@ export const useLockScroll = ({ lock = true } = {}) => {
return () => (document.body.style.overflow = "visible");
}, [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;
};

View File

@ -335,4 +335,4 @@ export const createSlug = (text, base = "untitled") => {
return text;
};
export const capitalize = (str = "") => str[0].toUpperCase() + str.slice(1);
export const capitalize = (str = "") => str[0]?.toUpperCase() + str?.slice(1);

View File

@ -71,6 +71,15 @@ export const H5 = css`
${TEXT}
`;
export const H6 = css`
font-family: ${Constants.font.medium};
font-size: 0.75rem;
line-height: 1.666;
letter-spacing: -0.01px;
${TEXT}
`;
export const P1 = css`
font-family: ${Constants.font.text};
font-size: 1rem;

View File

@ -1890,10 +1890,10 @@ export const MehCircle = (props) => (
);
export const Heart = (props) => (
<svg width={20} height={21} fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<svg width={16} height={16} fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M17.367 4.342a4.584 4.584 0 00-6.484 0L10 5.225l-.883-.883a4.584 4.584 0 00-6.484 6.483l.884.883L10 18.192l6.483-6.484.884-.883a4.584 4.584 0 000-6.483v0z"
stroke="#48484A"
d="M13.893 3.073a3.667 3.667 0 00-5.186 0L8 3.78l-.707-.707A3.667 3.667 0 102.107 8.26l.706.707L8 14.153l5.187-5.186.706-.707a3.667 3.667 0 000-5.187v0z"
stroke="currentColor"
strokeWidth={1.25}
strokeLinecap="round"
strokeLinejoin="round"
@ -2019,7 +2019,8 @@ export const Mail = (props) => (
</svg>
);
export const Twitter = (props) => (
// TODO(amine): update this logo when working on object sharing
export const TwitterWhiteLogo = (props) => (
<svg width={16} height={14} fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M15.333 1a7.268 7.268 0 01-2.093 1.02 2.987 2.987 0 00-5.24 2v.667a7.107 7.107 0 01-6-3.02s-2.667 6 3.333 8.666a7.76 7.76 0 01-4.666 1.334C6.667 15 14 11.667 14 4c0-.186-.018-.371-.053-.553A5.147 5.147 0 0015.333 1z"
@ -2062,3 +2063,183 @@ export const List = (props) => (
/>
</svg>
);
export const Radio = (props) => (
<svg width={16} height={16} fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M8 9.333a1.333 1.333 0 100-2.666 1.333 1.333 0 000 2.666zM10.827 5.173a4 4 0 010 5.66m-5.654-.006a4 4 0 010-5.66m7.54-1.88a6.667 6.667 0 010 9.426m-9.426 0a6.667 6.667 0 010-9.426"
stroke="currentColor"
strokeWidth={1.25}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export const FileText = (props) => (
<svg width={16} height={16} fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M9.333 1.333H4a1.333 1.333 0 00-1.333 1.334v10.666A1.333 1.333 0 004 14.667h8a1.333 1.333 0 001.333-1.334v-8l-4-4z"
stroke="currentColor"
strokeWidth={1.25}
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M9.333 1.333v4h4M10.667 8.667H5.333M10.667 11.333H5.333M6.667 6H5.333"
stroke="#00050A"
strokeWidth={1.25}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export const Sidebar = (props) => (
<svg width={16} height={16} fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M12.667 2H3.333C2.597 2 2 2.597 2 3.333v9.334C2 13.403 2.597 14 3.333 14h9.334c.736 0 1.333-.597 1.333-1.333V3.333C14 2.597 13.403 2 12.667 2z"
stroke="currentColor"
strokeWidth={1.25}
strokeLinecap="round"
strokeLinejoin="round"
/>
<path d="M6 2v12" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
export const Clock = (props) => (
<svg width={16} height={16} fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M8 14.667A6.667 6.667 0 108 1.333a6.667 6.667 0 000 13.334z"
stroke="currentColor"
strokeWidth={1.25}
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M8 4v4l2.667 1.333"
stroke="currentColor"
strokeWidth={1.25}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export const Layout = (props) => (
<svg width={16} height={16} fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M12.667 2H3.333C2.597 2 2 2.597 2 3.333v9.334C2 13.403 2.597 14 3.333 14h9.334c.736 0 1.333-.597 1.333-1.333V3.333C14 2.597 13.403 2 12.667 2zM2 6h12M6 14V6"
stroke="currentColor"
strokeWidth={1.25}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export const Twitter = (props) => (
<svg width={16} height={16} fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M15.333 2a7.268 7.268 0 01-2.093 1.02 2.987 2.987 0 00-5.24 2v.667a7.107 7.107 0 01-6-3.02s-2.667 6 3.333 8.666a7.76 7.76 0 01-4.666 1.334C6.667 16 14 12.667 14 5c0-.186-.018-.371-.053-.553A5.147 5.147 0 0015.333 2v0z"
stroke="currentColor"
strokeWidth={1.25}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export const Bookmark = (props) => (
<svg width={16} height={16} fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M12.667 14L8 10.667 3.333 14V3.333A1.333 1.333 0 014.667 2h6.666a1.333 1.333 0 011.334 1.333V14z"
stroke="currentColor"
strokeWidth={1.25}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export const FilePlus = (props) => (
<svg width={16} height={16} fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M9.333 1.333H4a1.333 1.333 0 00-1.333 1.334v10.666A1.333 1.333 0 004 14.667h8a1.334 1.334 0 001.333-1.334v-8l-4-4z"
stroke="currentColor"
strokeWidth={1.25}
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M9.333 1.333v4h4M8 12V8M6 10h4"
stroke="#00050A"
strokeWidth={1.25}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export const Youtube = (props) => (
<svg width={16} height={16} fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M15.027 4.28a1.853 1.853 0 00-1.294-1.333C12.587 2.667 8 2.667 8 2.667s-4.587 0-5.733.306A1.853 1.853 0 00.973 4.307a19.333 19.333 0 00-.306 3.526c-.008 1.192.095 2.381.306 3.554a1.853 1.853 0 001.294 1.28c1.146.306 5.733.306 5.733.306s4.587 0 5.733-.306a1.853 1.853 0 001.294-1.334 19.33 19.33 0 00.306-3.5 19.33 19.33 0 00-.306-3.553v0z"
stroke="currentColor"
strokeWidth={1.25}
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M6.5 10.013l3.833-2.18L6.5 5.653v4.36z"
stroke="currentColor"
strokeWidth={1.25}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export const Github = (props) => (
<svg width={16} height={16} fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M6 12.667c-3.333 1-3.333-1.667-4.667-2m9.334 4v-2.58a2.246 2.246 0 00-.627-1.74c2.093-.234 4.293-1.027 4.293-4.667 0-.93-.358-1.826-1-2.5a3.38 3.38 0 00-.06-2.513s-.786-.234-2.606.986a8.92 8.92 0 00-4.667 0C4.18.433 3.393.667 3.393.667a3.38 3.38 0 00-.06 2.513 3.627 3.627 0 00-1 2.52c0 3.613 2.2 4.407 4.294 4.667A2.246 2.246 0 006 12.087v2.58"
stroke="currentColor"
strokeWidth={1.25}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export const Twitch = (props) => (
<svg width={16} height={16} fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M10.667 7.333V4.667M14 1.333H2V12h3.333v2.667L8 12h3.333L14 9.333v-8zm-6.667 6V4.667v2.666z"
stroke="currentColor"
strokeWidth={1.25}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export const Instagram = (props) => (
<svg width={16} height={16} fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M11.333 1.333H4.667a3.333 3.333 0 00-3.334 3.334v6.666a3.333 3.333 0 003.334 3.334h6.666a3.333 3.333 0 003.334-3.334V4.667a3.333 3.333 0 00-3.334-3.334z"
stroke="currentColor"
strokeWidth={1.25}
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M10.667 7.58a2.667 2.667 0 11-5.276.782 2.667 2.667 0 015.276-.782zM11.667 4.333h.006"
stroke="currentColor"
strokeWidth={1.25}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);

View File

@ -265,6 +265,9 @@ export const isMarkdown = (filename = "", type = "") => {
return filename.toLowerCase().endsWith(".md") || type.startsWith("text/plain");
};
export const isDocument = (fileName = "", type = "") =>
isMarkdown(fileName, type) || isPdfType(type) || isEpubType(type);
export const isUnityFile = async (file) => {
try {
const zip = new JSZip();
@ -288,3 +291,9 @@ export const isNFTLink = (file) => {
domain = domain.toLowerCase();
return Constants.NFTDomains.includes(domain);
};
const isLinkWithSource = (source) => (file) => file.isLink && file.source === source;
export const isTwitterLink = isLinkWithSource("Twitter");
export const isYoutubeLink = isLinkWithSource("YouTube");
export const isTwitchLink = isLinkWithSource("Twitch");
export const isGithubLink = isLinkWithSource("GitHub");
export const isInstagramLink = isLinkWithSource("Instagram");

View File

@ -1,5 +1,5 @@
export { default as ViewMoreContent } from "./ViewMoreContent";
export { default as ProfileInfo } from "./ProfileInfo";
export { default as ActivityCollectionGroup } from "./ActivityCollectionGroup";
export { default as ActivityFileGroup } from "./ActivityFileGroup";
export { default as ActivityProfileGroup } from "./ActivityProfileGroup";
export { default as ViewMoreContent } from "~/components/core/ActivityGroup/components/ViewMoreContent";
export { default as ProfileInfo } from "~/components/core/ActivityGroup/components/ProfileInfo";
export { default as ActivityCollectionGroup } from "~/components/core/ActivityGroup/components/ActivityCollectionGroup";
export { default as ActivityFileGroup } from "~/components/core/ActivityGroup/components/ActivityFileGroup";
export { default as ActivityProfileGroup } from "~/components/core/ActivityGroup/components/ActivityProfileGroup";

View File

@ -45,6 +45,7 @@ import SidebarEditTags from "~/components/sidebars/SidebarEditTags";
import ApplicationHeader from "~/components/core/ApplicationHeader";
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 { GlobalModal } from "~/components/system/components/GlobalModal";
@ -488,31 +489,34 @@ export default class ApplicationPage extends React.Component {
// }
return (
<React.Fragment>
<ApplicationLayout
sidebarName={this.state.sidebar}
page={page}
onAction={this._handleAction}
header={headerElement}
sidebar={sidebarElement}
onDismissSidebar={this._handleDismissSidebar}
isMobile={this.state.isMobile}
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
)}
</ApplicationLayout>
<PortalsProvider>
<ApplicationLayout
sidebarName={this.state.sidebar}
page={page}
onAction={this._handleAction}
header={headerElement}
sidebar={sidebarElement}
onDismissSidebar={this._handleDismissSidebar}
fileLoading={this.state.fileLoading}
isMobile={this.state.isMobile}
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
)}
</ApplicationLayout>
</PortalsProvider>
<GlobalModal />
<SearchModal
viewer={this.state.viewer}

View File

@ -4,6 +4,7 @@ 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 {
ApplicationUserControls,
@ -59,11 +60,10 @@ const STYLES_APPLICATION_HEADER_BACKGROUND = (theme) => css`
const STYLES_APPLICATION_HEADER = css`
${Styles.HORIZONTAL_CONTAINER_CENTERED};
height: ${Constants.sizes.header}px;
padding: 0px 24px;
padding: 14px 24px;
@media (max-width: ${Constants.sizes.mobile}px) {
padding: 0px 16px;
padding: 16px 16px 12px;
width: 100%;
}
`;
@ -157,74 +157,79 @@ export default function ApplicationHeader({ viewer, page, data, onAction }) {
const isSearching = searchQuery.length !== 0;
return (
<header style={{ position: "relative" }}>
<div css={STYLES_APPLICATION_HEADER}>
<div css={STYLES_LEFT}>
<Show
when={viewer}
fallback={
<Link onAction={onAction} href="/_/data" style={{ pointerEvents: "auto" }}>
<DarkSymbol style={{ height: 24, display: "block" }} />
</Link>
}
>
<ApplicationUserControls
popup={mobile ? false : state.popup}
onTogglePopup={_handleTogglePopup}
viewer={viewer}
onAction={onAction}
/>
</Show>
</div>
<div css={STYLES_MIDDLE}>
{/**TODO: update Search component */}
<Input
containerStyle={{ height: "100%" }}
full
placeholder={`Search ${!viewer ? "slate.host" : ""}`}
inputCss={STYLES_SEARCH_COMPONENT}
onSubmit={handleCreateSearch}
name="search"
{...getFieldProps()}
/>
</div>
<Upload.Provider page={page} data={data} viewer={viewer}>
<Upload.Root onAction={onAction} viewer={viewer}>
<div css={STYLES_RIGHT}>
<Actions
uploadAction={
<Upload.Trigger
enableMetrics
viewer={viewer}
aria-label="Upload"
css={STYLES_UPLOAD_BUTTON}
>
<SVG.Plus height="16px" />
</Upload.Trigger>
}
isSearching={isSearching}
isSignedOut={isSignedOut}
<div>
<header style={{ position: "relative" }}>
<div css={STYLES_APPLICATION_HEADER}>
<div css={STYLES_LEFT}>
<Show
when={viewer}
fallback={
<Link onAction={onAction} href="/_/data" style={{ pointerEvents: "auto" }}>
<DarkSymbol style={{ height: 24, display: "block" }} />
</Link>
}
>
<ApplicationUserControls
popup={mobile ? false : state.popup}
onTogglePopup={_handleTogglePopup}
viewer={viewer}
onAction={onAction}
onDismissSearch={handleDismissSearch}
/>
</div>
</Upload.Root>
</Upload.Provider>
</div>
<Show when={mobile && state.popup === "profile"}>
<ApplicationUserControlsPopup
popup={state.popup}
onTogglePopup={_handleTogglePopup}
viewer={viewer}
onAction={onAction}
style={{ pointerEvents: "auto" }}
/>
<div css={STYLES_BACKGROUND} />
</Show>
</div>
<div css={STYLES_MIDDLE}>
{/**TODO: update Search component */}
<Input
containerStyle={{ height: "100%" }}
full
placeholder={`Search ${!viewer ? "slate.host" : ""}`}
inputCss={STYLES_SEARCH_COMPONENT}
onSubmit={handleCreateSearch}
name="search"
{...getFieldProps()}
/>
</div>
<Upload.Provider page={page} data={data} viewer={viewer}>
<Upload.Root onAction={onAction} viewer={viewer}>
<div css={STYLES_RIGHT}>
<Actions
uploadAction={
<Upload.Trigger
enableMetrics
viewer={viewer}
aria-label="Upload"
css={STYLES_UPLOAD_BUTTON}
>
<SVG.Plus height="16px" />
</Upload.Trigger>
}
isSearching={isSearching}
isSignedOut={isSignedOut}
onAction={onAction}
onDismissSearch={handleDismissSearch}
/>
</div>
</Upload.Root>
</Upload.Provider>
</div>
<Show when={mobile && state.popup === "profile"}>
<ApplicationUserControlsPopup
popup={state.popup}
onTogglePopup={_handleTogglePopup}
viewer={viewer}
onAction={onAction}
style={{ pointerEvents: "auto" }}
/>
<div css={STYLES_BACKGROUND} />
</Show>
{/** NOTE(amine): a fix for a backdrop-filter bug where the filter doesn't take any effects.
* It happens when we have two elements using backdrop-filter with a parent-child relationship */}
<div css={STYLES_APPLICATION_HEADER_BACKGROUND} />
</header>
<Show when={!!viewer}>
<Filter.Navbar />
</Show>
{/** NOTE(amine): a fix for a backdrop-filter bug where the filter doesn't take any effects.
* It happens when we have two elements using backdrop-filter with a parent-child relationship */}
<div css={STYLES_APPLICATION_HEADER_BACKGROUND} />
</header>
</div>
);
}

View File

@ -1,3 +1,3 @@
export { default as FollowButton } from "./FollowButton";
export { default as Preview } from "./Preview";
export { default as ShareButton } from "./ShareButton";
export { default as FollowButton } from "~/components/core/CollectionPreviewBlock/components/FollowButton";
export { default as Preview } from "~/components/core/CollectionPreviewBlock/components/Preview";
export { default as ShareButton } from "~/components/core/CollectionPreviewBlock/components/ShareButton";

View File

@ -0,0 +1,63 @@
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 && (
<System.P2 as="span" color="textGray">
{" "}
/{" "}
</System.P2>
)}
<button css={STYLES_BREADCRUMB_BUTTON} {...props}>
<System.P2 color={color}>{children}</System.P2>
</button>
</>
);
}
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 (
<div {...props}>
<Show when={!isCurrentViewInitial}>
<Item onClick={resetFilterState} color="textGray">
All
</Item>
<Item
includeDelimiter
color={filterState.subview ? "textGray" : "textBlack"}
onClick={changeFilterToBrowerView}
>
{Strings.capitalize(filterState.view)}
</Item>
</Show>
<Show when={filterState.subview}>
<Item includeDelimiter>{Strings.capitalize(filterState.subview)}</Item>
</Show>
</div>
);
}

View File

@ -0,0 +1,42 @@
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%;
padding: 20px 24px 44px;
@media (max-width: ${theme.sizes.mobile}px) {
padding: 31px 16px 44px;
}
`;
export function Content({ viewer, onAction, page, ...props }) {
const [{ filterState }] = useFilterContext();
const { objects } = filterState;
return (
<div css={STYLES_DATAVIEWER_WRAPPER} {...props}>
{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>
);
}

View File

@ -0,0 +1,213 @@
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 { css } from "@emotion/react";
import { useFilterContext } from "~/components/core/Filter/Provider";
/* -------------------------------------------------------------------------------------------------
* Shared components between filters
* -----------------------------------------------------------------------------------------------*/
const STYLES_FILTER_BUTTON_HIGHLIGHTED = (theme) => css`
background-color: ${theme.semantic.textGrayLight};
`;
const STYLES_FILTER_BUTTON = (theme) => css`
display: flex;
align-items: center;
width: 100%;
${Styles.BUTTON_RESET};
padding: 4px 8px;
border-radius: 8px;
color: ${theme.semantic.textBlack};
&:hover {
background-color: ${theme.semantic.textGrayLight};
color: ${theme.semantic.textBlack};
}
&:disabled {
color: ${theme.semantic.textGray};
pointer-events: none;
cursor: not-allowed;
}
`;
const STYLES_FILTERS_GROUP = css`
& > * + * {
margin-top: 4px !important;
}
li {
list-style: none;
}
`;
const FilterButton = ({ children, Icon, isSelected, ...props }) => (
<li>
<Typography.P2
as="button"
css={[STYLES_FILTER_BUTTON, isSelected && STYLES_FILTER_BUTTON_HIGHLIGHTED]}
{...props}
>
<Icon height={16} width={16} />
<span style={{ marginLeft: 6 }}>{children}</span>
</Typography.P2>
</li>
);
const FilterSection = ({ title, children, ...props }) => (
<div {...props}>
{title && (
<Typography.H6 style={{ paddingLeft: 8 }} color="textGray">
{title}
</Typography.H6>
)}
<ul css={STYLES_FILTERS_GROUP}>{children}</ul>
</div>
);
/* -------------------------------------------------------------------------------------------------
* InitialFilters
* -----------------------------------------------------------------------------------------------*/
function Initial({ filters, goToBrowserView }) {
const [{ filterState }, { setFilterType, resetFilterState }] = useFilterContext();
const currentFilterType = filterState.type;
const currentFilterView = filterState.view;
const changeFilter = ({ type }) => setFilterType({ view: currentFilterView, type });
return (
<>
{/** Breadcrumb All */}
<FilterSection>
<FilterButton
Icon={SVG.Clock}
isSelected={currentFilterType === filters.library}
onClick={resetFilterState}
>
My Library
</FilterButton>
</FilterSection>
<FilterSection title="Connected" style={{ marginTop: 12 }}>
<FilterButton Icon={SVG.Layout} onClick={goToBrowserView}>
Browser
</FilterButton>
</FilterSection>
<FilterSection style={{ marginTop: 12 }} title="Types">
<FilterButton
Icon={SVG.Image}
isSelected={currentFilterType === filters.images}
onClick={() => changeFilter({ type: filters.images })}
>
Images
</FilterButton>
<FilterButton
Icon={SVG.Radio}
isSelected={currentFilterType === filters.audios}
onClick={() => changeFilter({ type: filters.audios })}
>
Audios
</FilterButton>
<FilterButton
Icon={SVG.Video}
isSelected={currentFilterType === filters.videos}
onClick={() => changeFilter({ type: filters.videos })}
>
Videos
</FilterButton>
<FilterButton
Icon={SVG.FileText}
isSelected={currentFilterType === filters.documents}
onClick={() => changeFilter({ type: filters.documents })}
>
Documents
</FilterButton>
</FilterSection>
</>
);
}
/* -------------------------------------------------------------------------------------------------
* Browser Filters
* -----------------------------------------------------------------------------------------------*/
function Browser({ filters, goToSavedSubview }) {
const [{ filterState }] = useFilterContext();
const currentFilterType = filterState.type;
return (
<FilterSection>
<FilterButton Icon={SVG.Clock} isSelected={currentFilterType === filters.all}>
All
</FilterButton>
<FilterButton disabled Icon={SVG.Clock}>
History
</FilterButton>
<FilterButton disabled Icon={SVG.Bookmark}>
Bookmarks
</FilterButton>
<FilterButton Icon={SVG.FilePlus} onClick={goToSavedSubview}>
Saved
</FilterButton>
</FilterSection>
);
}
const BrowserSaved = ({ filters }) => {
const [{ filterState }, { setFilterType }] = useFilterContext();
const currentFilterType = filterState.type;
const changeSavedFilterType = (type) =>
setFilterType({ view: filterState.view, subview: filterState.subview, type });
return (
<FilterSection>
<FilterButton
Icon={SVG.Clock}
isSelected={currentFilterType === filters.all}
onClick={() => changeSavedFilterType(filters.all)}
>
All
</FilterButton>
<FilterButton
Icon={SVG.Twitter}
isSelected={currentFilterType === filters.twitter}
onClick={() => changeSavedFilterType(filters.twitter)}
>
Twitter
</FilterButton>
<FilterButton
Icon={SVG.Youtube}
isSelected={currentFilterType === filters.youtube}
onClick={() => changeSavedFilterType(filters.youtube)}
>
Youtube
</FilterButton>
<FilterButton
Icon={SVG.Github}
isSelected={currentFilterType === filters.github}
onClick={() => changeSavedFilterType(filters.github)}
>
Github
</FilterButton>
<FilterButton
Icon={SVG.Twitch}
isSelected={currentFilterType === filters.twitch}
onClick={() => changeSavedFilterType(filters.twitch)}
>
Twitch
</FilterButton>
<FilterButton
Icon={SVG.Instagram}
isSelected={currentFilterType === filters.instagram}
onClick={() => changeSavedFilterType(filters.instagram)}
>
Instagram
</FilterButton>
</FilterSection>
);
};
export { Initial, Browser, BrowserSaved };

View File

@ -0,0 +1,49 @@
import * as React from "react";
import * as ReactDOM from "react-dom";
import { usePortals } from "~/components/core/PortalsProvider";
import { css } from "@emotion/react";
import { Divider } from "~/components/system";
/* -------------------------------------------------------------------------------------------------
* Navbar Portal
* -----------------------------------------------------------------------------------------------*/
export function NavbarPortal({ children }) {
const { filterNavbar } = usePortals();
const [filterNavbarElement] = filterNavbar;
return filterNavbarElement
? ReactDOM.createPortal(
<>
<Divider height="0.5px" />
<div css={STYLES_NAVBAR}>{children}</div>
</>,
filterNavbarElement
)
: null;
}
/* -------------------------------------------------------------------------------------------------
* Navbar
* -----------------------------------------------------------------------------------------------*/
const STYLES_NAVBAR = (theme) => css`
display: flex;
justify-content: space-between;
align-items: center;
background-color: ${theme.semantic.bgWhite};
padding: 9px 24px 11px;
box-shadow: ${theme.shadow.lightSmall};
@supports ((-webkit-backdrop-filter: blur(75px)) or (backdrop-filter: blur(75px))) {
-webkit-backdrop-filter: blur(75px);
backdrop-filter: blur(75px);
background-color: ${theme.semantic.bgBlurWhite};
}
`;
export function Navbar({ children }) {
const { filterNavbar } = usePortals();
const [, setFilterElement] = filterNavbar;
return <div ref={setFilterElement}>{children}</div>;
}

View File

@ -0,0 +1,92 @@
import * as React from "react";
import { useWorker } from "~/common/hooks";
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 workerState = useFilterWorker({ filterState, setFilterObjects, library: viewer.library });
const contextValue = React.useMemo(
() => [
{ isSidebarVisible, filterState, ...workerState },
{ toggleSidebar, setFilterType, resetFilterState },
],
[isSidebarVisible, filterState, workerState]
);
return <UploadContext.Provider value={contextValue}>{children}</UploadContext.Provider>;
};
const useFilterSidebar = () => {
const [isSidebarVisible, setSidebarState] = React.useState(false);
const toggleSidebar = () => setSidebarState((prev) => !prev);
return [isSidebarVisible, toggleSidebar];
};
const useFilter = ({ library }) => {
const DEFAULT_STATE = {
view: "initial",
subview: undefined,
type: "library",
objects: library,
search: {
objects: [],
tags: [],
startDate: null,
endDate: null,
},
};
const [filterState, setFilterState] = React.useState(DEFAULT_STATE);
const setFilterType = ({ view, subview = undefined, type }) =>
setFilterState((prev) => ({ ...prev, view, subview, type }));
const setFilterObjects = (objects) => setFilterState((prev) => ({ ...prev, objects }));
const resetFilterState = () => setFilterState(DEFAULT_STATE);
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, type]
);
return workerState;
};

View File

@ -0,0 +1,112 @@
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 FilterUtilities from "~/common/filter-utilities";
import { useFilterContext } from "~/components/core/Filter/Provider";
import { Show } from "~/components/utility/Show";
import { css } from "@emotion/react";
/* -------------------------------------------------------------------------------------------------
* Sidebar trigger
* -----------------------------------------------------------------------------------------------*/
const STYLES_SIDEBAR_TRIGGER = (theme) => css`
${Styles.BUTTON_RESET};
color: ${theme.semantic.textBlack};
border-radius: 6px;
padding: 2px;
transition: background-color 0.3s;
:hover {
background-color: ${theme.semantic.bgGrayLight};
}
`;
export function SidebarTrigger() {
const [{ isSidebarVisible }, { toggleSidebar }] = useFilterContext();
return (
<button
onClick={toggleSidebar}
css={[
STYLES_SIDEBAR_TRIGGER,
(theme) =>
css({
backgroundColor: isSidebarVisible ? theme.semantic.bgGrayLight : "none",
color: isSidebarVisible ? theme.semantic.textBlack : theme.semantic.textGray,
}),
]}
>
<SVG.Sidebar style={{ display: "block" }} />
</button>
);
}
/* -------------------------------------------------------------------------------------------------
* Sidebar
* -----------------------------------------------------------------------------------------------*/
export function Sidebar() {
const [{ isSidebarVisible }] = useFilterContext();
return (
<Show when={isSidebarVisible}>
<div css={STYLES_SIDEBAR_FILTER_WRAPPER}>
<SidebarContent />
</div>
</Show>
);
}
const STYLES_SIDEBAR_FILTER_WRAPPER = (theme) => css`
position: sticky;
top: ${theme.sizes.header}px;
min-height: 100vh;
width: 236px;
max-height: calc(100vh - ${theme.sizes.header}px);
padding: 20px 24px;
background-color: ${theme.semantic.bgLight};
`;
/* -------------------------------------------------------------------------------------------------
* SidebarContent
* -----------------------------------------------------------------------------------------------*/
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 <Filters.BrowserSaved filters={filters} />;
}
return (
<Filters.Browser
filters={filters}
goToSavedSubview={() => changeFilterSubview(FILTER_SUBVIEWS_IDS.browser.saved)}
/>
);
}
return (
<Filters.Initial
filters={filters}
goToBrowserView={() => changeFilterView(FILTER_VIEWS_IDS.browser)}
/>
);
}

View File

@ -0,0 +1,35 @@
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";
/* -------------------------------------------------------------------------------------------------
* Title
* -----------------------------------------------------------------------------------------------*/
function Title() {
return <System.H5 color="textBlack">All</System.H5>;
}
/* -------------------------------------------------------------------------------------------------
* Actions
* -----------------------------------------------------------------------------------------------*/
function Actions() {
return <div />;
}
export {
Title,
Actions,
Sidebar,
SidebarTrigger,
Provider,
Navbar,
Content,
Breadcrumb,
NavbarPortal,
};

View File

@ -6,7 +6,7 @@ import * as Utilities from "~/common/utilities";
import { css } from "@emotion/react";
import ObjectPreviewPrimitive from "./ObjectPreviewPrimitive";
import ObjectPreviewPrimitive from "~/components/core/ObjectPreview/ObjectPreviewPrimitive";
import { useFont } from "~/components/core/FontFrame/hooks";
const STYLES_TEXT_PREVIEW = (theme) => css`

View File

@ -8,7 +8,7 @@ import { Blurhash } from "react-blurhash";
import { isBlurhashValid } from "blurhash";
import { css } from "@emotion/react";
import ObjectPreviewPrimitive from "./ObjectPreviewPrimitive";
import ObjectPreviewPrimitive from "~/components/core/ObjectPreview/ObjectPreviewPrimitive";
const STYLES_PLACEHOLDER_ABSOLUTE = css`
position: absolute;
@ -48,6 +48,9 @@ const ImagePlaceholder = ({ blurhash }) => (
</div>
);
// NOTE(amine): cache
const cidsLoaded = {};
export default function ImageObjectPreview({
url,
file,
@ -55,9 +58,14 @@ export default function ImageObjectPreview({
tag,
...props
}) {
const isCached = cidsLoaded[file.cid];
const previewerRef = React.useRef();
const [isLoading, setLoading] = React.useState(true);
const handleOnLoaded = () => setLoading(false);
const [isLoading, setLoading] = React.useState(isCached);
const handleOnLoaded = () => {
cidsLoaded[file.cid] = true;
setLoading(false);
};
const { isInView } = useInView({
ref: previewerRef,

View File

@ -4,12 +4,11 @@ import * as Styles from "~/common/styles";
import { css } from "@emotion/react";
import { H5, P2, P3 } from "~/components/system/components/Typography";
import { AspectRatio } from "~/components/system";
// import { LikeButton, SaveButton } from "./components";
// import { useSaveHandler } from "~/common/hooks";
import { motion, useAnimation } from "framer-motion";
import { useMounted, useMediaQuery } from "~/common/hooks";
import ImageObjectPreview from "./ImageObjectPreview";
import ImageObjectPreview from "~/components/core/ObjectPreview/ImageObjectPreview";
const STYLES_WRAPPER = (theme) => css`
position: relative;

View File

@ -9,7 +9,7 @@ import { useIsomorphicLayoutEffect } from "~/common/hooks";
import { css } from "@emotion/react";
import FilePlaceholder from "~/components/core/ObjectPreview/placeholders/File";
import ObjectPreviewPrimitive from "./ObjectPreviewPrimitive";
import ObjectPreviewPrimitive from "~/components/core/ObjectPreview/ObjectPreviewPrimitive";
const STYLES_CONTAINER = css`
position: relative;

View File

@ -1,2 +1,2 @@
export { default as LikeButton } from "./LikeButton";
export { default as SaveButton } from "./SaveButton";
export { default as LikeButton } from "~/components/core/ObjectPreview/components/LikeButton";
export { default as SaveButton } from "~/components/core/ObjectPreview/components/SaveButton";

View File

@ -5,15 +5,15 @@ import * as Typography from "~/components/system/components/Typography";
import { css } from "@emotion/react";
import PdfPlaceholder from "./PDF";
import AudioPlaceholder from "./Audio";
import CodePlaceholder from "./Code";
import EpubPlaceholder from "./EPUB";
import KeynotePlaceholder from "./Keynote";
import Object3DPlaceholder from "./3D";
import FilePlaceholder from "./File";
import VideoPlaceholder from "./Video";
import LinkPlaceholder from "./Link";
import PdfPlaceholder from "~/components/core/ObjectPreview/placeholders/PDF";
import AudioPlaceholder from "~/components/core/ObjectPreview/placeholders/Audio";
import CodePlaceholder from "~/components/core/ObjectPreview/placeholders/Code";
import EpubPlaceholder from "~/components/core/ObjectPreview/placeholders/EPUB";
import KeynotePlaceholder from "~/components/core/ObjectPreview/placeholders/Keynote";
import Object3DPlaceholder from "~/components/core/ObjectPreview/placeholders/3D";
import FilePlaceholder from "~/components/core/ObjectPreview/placeholders/File";
import VideoPlaceholder from "~/components/core/ObjectPreview/placeholders/Video";
import LinkPlaceholder from "~/components/core/ObjectPreview/placeholders/Link";
const STYLES_PLACEHOLDER_CONTAINER = (theme) => css`
position: relative;

View File

@ -0,0 +1,16 @@
import * as React from "react";
/**
* NOTE(amine): Component used to assign Portals in client side
*/
const LayoutContext = React.createContext({});
export default function PortalsProvider({ children }) {
const filterNavbar = React.useState();
const contextValue = React.useMemo(() => ({ filterNavbar }), [filterNavbar]);
return <LayoutContext.Provider value={contextValue}>{children}</LayoutContext.Provider>;
}
export const usePortals = () => React.useContext(LayoutContext);

View File

@ -152,7 +152,7 @@ export const ShareModalPrimitive = ({
<button css={Styles.BUTTON_RESET} onClick={onTwitterSharing}>
<span>
<div css={[STYLES_SOCIAL_BUTTON, STYLES_TWITTER_BUTTON]}>
<SVG.Twitter style={{ display: "block" }} />
<SVG.TwitterWhiteLogo style={{ display: "block" }} />
</div>
<P3 style={{ marginTop: 4 }} css={STYLES_TEXT_CENTER}>
Twitter

View File

@ -191,6 +191,17 @@ export const H5 = ({ as = "h5", nbrOflines, children, color, ...props }) => {
);
};
export const H6 = ({ as = "h6", nbrOflines, children, color, ...props }) => {
const TRUNCATE_STYLE = React.useMemo(() => truncateElements(nbrOflines), [nbrOflines]);
const COLOR_STYLES = useColorProp(color);
return jsx(
as,
{ ...props, css: [Styles.H6, TRUNCATE_STYLE, COLOR_STYLES, props?.css] },
children
);
};
export const P1 = ({ as = "p", nbrOflines, children, color, ...props }) => {
const TRUNCATE_STYLE = React.useMemo(() => truncateElements(nbrOflines), [nbrOflines]);
const COLOR_STYLES = useColorProp(color);

View File

@ -68,6 +68,7 @@ import {
H3,
H4,
H5,
H6,
P1,
P2,
P3,
@ -160,6 +161,7 @@ export {
H3,
H4,
H5,
H6,
P1,
P2,
P3,

View File

@ -2,6 +2,11 @@ const withOffline = require("next-offline");
const nextConfig = {
webpack5: true,
eslint: {
// Warning: This allows production builds to successfully complete even if
// your project has ESLint errors.
ignoreDuringBuilds: true,
},
};
module.exports = withOffline(nextConfig);

View File

@ -66,7 +66,7 @@
"moment": "^2.29.1",
"morgan": "^1.10.0",
"multihashing-async": "^2.1.2",
"next": "^10.0.7",
"next": "^11.1.2",
"next-offline": "^5.0.5",
"oauth": "^0.9.15",
"pg": "^8.5.1",

View File

@ -6,7 +6,7 @@ import { css } from "@emotion/react";
// import { SecondaryTabGroup } from "~/components/core/TabGroup";
import { LoaderSpinner } from "~/components/system/components/Loaders";
import { useIntersection } from "common/hooks";
import { useActivity } from "./hooks";
import { useActivity } from "~/scenes/SceneActivity/hooks";
import { GlobalCarousel } from "~/components/system/components/GlobalCarousel";
import ScenePage from "~/components/core/ScenePage";

View File

@ -1,70 +1,69 @@
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 { FileTypeGroup } from "~/components/core/FileTypeIcon";
import { GlobalCarousel } from "~/components/system/components/GlobalCarousel";
import ScenePage from "~/components/core/ScenePage";
import DataView from "~/components/core/DataView";
import EmptyState from "~/components/core/EmptyState";
import WebsitePrototypeWrapper from "~/components/core/WebsitePrototypeWrapper";
const STYLES_SCENE_PAGE = css`
padding: 20px 24px 44px;
padding: 0px;
@media (max-width: ${Constants.sizes.mobile}px) {
padding: 31px 16px 44px;
padding: 0px;
}
`;
export default class SceneFilesFolder extends React.Component {
state = {
index: -1,
};
const STYLES_FILTER_TITLE_WRAPPER = css`
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
`;
render() {
let files = this.props.viewer?.library;
const tab = this.props.page.params?.tab || "grid";
export default function SceneFilesFolder({ viewer, page, onAction, isMobile }) {
const [index, setIndex] = React.useState(-1);
return (
<WebsitePrototypeWrapper
title={`${this.props.page.pageTitle} • Slate`}
url={`${Constants.hostname}${this.props.page.pathname}`}
>
<ScenePage css={STYLES_SCENE_PAGE}>
<GlobalCarousel
carouselType="DATA"
viewer={this.props.viewer}
objects={files}
onAction={this.props.onAction}
isMobile={this.props.isMobile}
params={this.props.page.params}
isOwner={true}
index={this.state.index}
onChange={(index) => this.setState({ index })}
/>
let objects = viewer.library;
// const tab = page.params?.tab || "grid";
{files.length ? (
<DataView
key="scene-files-folder"
onAction={this.props.onAction}
viewer={this.props.viewer}
items={files}
view={tab}
isOwner={true}
page={this.props.page}
/>
) : (
<EmptyState>
<FileTypeGroup />
<div style={{ marginTop: 24 }}>
Drag and drop files into Slate to upload, or press the plus button to save a file or
link
</div>
</EmptyState>
)}
</ScenePage>
</WebsitePrototypeWrapper>
);
}
return (
<WebsitePrototypeWrapper
title={`${page.pageTitle} • Slate`}
url={`${Constants.hostname}${page.pathname}`}
>
<ScenePage css={STYLES_SCENE_PAGE}>
<GlobalCarousel
carouselType="DATA"
viewer={viewer}
objects={objects}
onAction={onAction}
isMobile={isMobile}
params={page.params}
isOwner={true}
index={index}
onChange={(index) => setIndex(index)}
/>
<Filter.Provider viewer={viewer}>
<Filter.NavbarPortal>
<div css={Styles.CONTAINER_CENTERED}>
<Filter.SidebarTrigger />
<Filter.Breadcrumb style={{ marginLeft: 16 }} />
</div>
<div css={STYLES_FILTER_TITLE_WRAPPER}>
<Filter.Title />
</div>
<Filter.Actions />
</Filter.NavbarPortal>
<div css={Styles.HORIZONTAL_CONTAINER}>
<Filter.Sidebar />
<Filter.Content onAction={onAction} viewer={viewer} page={page} />
</div>
</Filter.Provider>
</ScenePage>
</WebsitePrototypeWrapper>
);
}

8
workers/filter-files.js Normal file
View File

@ -0,0 +1,8 @@
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);
};