✨ Shared with me UI (#72)
This commit is contained in:
@@ -132,11 +132,12 @@ export class DriveApiClient {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async search(searchString: string, options?: BaseSearchOptions) {
|
static async search(searchString: string, view?: string,options?: BaseSearchOptions) {
|
||||||
const companyId = options?.company_id ? options.company_id : Workspace.currentGroupId;
|
const companyId = options?.company_id ? options.company_id : Workspace.currentGroupId;
|
||||||
const query = `/internal/services/documents/v1/companies/${companyId}/search`;
|
const query = `/internal/services/documents/v1/companies/${companyId}/search`;
|
||||||
const searchData = {
|
const searchData = {
|
||||||
search: searchString,
|
search: searchString,
|
||||||
|
view: view
|
||||||
};
|
};
|
||||||
const res = await Api.post<SearchDocumentsBody, { entities: DriveItem[] }>(query, searchData);
|
const res = await Api.post<SearchDocumentsBody, { entities: DriveItem[] }>(query, searchData);
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
@@ -147,4 +148,18 @@ export class DriveApiClient {
|
|||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async sharedWithMe(options?: BaseSearchOptions) {
|
||||||
|
const companyId = options?.company_id ? options.company_id : Workspace.currentGroupId;
|
||||||
|
const query = `/internal/services/documents/v1/companies/${companyId}/shared-with-me`;
|
||||||
|
const filterData = {};
|
||||||
|
const res = await Api.post<SearchDocumentsBody, { entities: DriveItem[] }>(query, filterData);
|
||||||
|
this.logger.debug(
|
||||||
|
`Drive shared with me by filter "${JSON.stringify(filterData)}". Found`,
|
||||||
|
res.entities.length,
|
||||||
|
'drive item(s)',
|
||||||
|
);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useCallback } from 'react';
|
|||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
import { DriveApiClient } from '../api-client/api-client';
|
import { DriveApiClient } from '../api-client/api-client';
|
||||||
import { DriveItemAtom, DriveItemChildrenAtom } from '../state/store';
|
import { DriveItemAtom, DriveItemChildrenAtom } from '../state/store';
|
||||||
import { DriveItem, DriveItemVersion } from '../types';
|
import { DriveItem, DriveItemDetails, DriveItemVersion } from '../types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the children of a drive item
|
* Returns the children of a drive item
|
||||||
@@ -18,20 +18,59 @@ export const useDriveActions = () => {
|
|||||||
({ set, snapshot }) =>
|
({ set, snapshot }) =>
|
||||||
async (parentId: string) => {
|
async (parentId: string) => {
|
||||||
if (parentId) {
|
if (parentId) {
|
||||||
try {
|
if (parentId == "shared-with-me") {
|
||||||
const details = await DriveApiClient.get(companyId, parentId);
|
const details = {
|
||||||
|
path: [
|
||||||
|
{
|
||||||
|
id: "shared-with-me",
|
||||||
|
name: "Shared with me"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
item: {
|
||||||
|
id: "root",
|
||||||
|
parent_id: "",
|
||||||
|
company_id: "",
|
||||||
|
workspace_id: "",
|
||||||
|
name: "Shared with me",
|
||||||
|
size: 0,
|
||||||
|
description: "",
|
||||||
|
tags: [],
|
||||||
|
in_trash: false,
|
||||||
|
is_directory: true,
|
||||||
|
extension: "",
|
||||||
|
added: "",
|
||||||
|
last_modified: "",
|
||||||
|
last_version_cache: {},
|
||||||
|
access_info: {},
|
||||||
|
},
|
||||||
|
versions: [],
|
||||||
|
children: [],
|
||||||
|
access: "manage",
|
||||||
|
websockets: [
|
||||||
|
{
|
||||||
|
room: "/companies/aa7ffdd0-fadb-11ed-b891-510fe3501b2b/documents/item/root",
|
||||||
|
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhYTU0Y2YyMC1mYWRiLTExZWQtYjg5MS01MTBmZTM1MDFiMmIiLCJuYW1lIjoiL2NvbXBhbmllcy9hYTdmZmRkMC1mYWRiLTExZWQtYjg5MS01MTBmZTM1MDFiMmIvZG9jdW1lbnRzL2l0ZW0vcm9vdCIsImlhdCI6MTY4ODk1MDk2OCwibmJmIjoxNjg2MjcyNTA4fQ.H6BFRcLG3Op32sqKi45Pf1s2YKcVbMxGZGPJal06l1g"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
set(DriveItemChildrenAtom(parentId), details.children);
|
set(DriveItemChildrenAtom(parentId), details.children);
|
||||||
set(DriveItemAtom(parentId), details);
|
|
||||||
for (const child of details.children) {
|
|
||||||
const currentValue = snapshot.getLoadable(DriveItemAtom(child.id)).contents;
|
|
||||||
if (!currentValue) {
|
|
||||||
//only update if not already in cache to avoid concurrent updates
|
|
||||||
set(DriveItemAtom(child.id), { item: child });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return details;
|
return details;
|
||||||
} catch (e) {
|
} else {
|
||||||
ToasterService.error('Unable to load your files.');
|
try {
|
||||||
|
const details = await DriveApiClient.get(companyId, parentId);
|
||||||
|
set(DriveItemChildrenAtom(parentId), details.children);
|
||||||
|
set(DriveItemAtom(parentId), details);
|
||||||
|
for (const child of details.children) {
|
||||||
|
const currentValue = snapshot.getLoadable(DriveItemAtom(child.id)).contents;
|
||||||
|
if (!currentValue) {
|
||||||
|
//only update if not already in cache to avoid concurrent updates
|
||||||
|
set(DriveItemAtom(child.id), { item: child });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return details;
|
||||||
|
} catch (e) {
|
||||||
|
ToasterService.error('Unable to load your files.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { DriveApiClient } from '@features/drive/api-client/api-client';
|
||||||
|
import { useGlobalEffect } from '@features/global/hooks/use-global-effect';
|
||||||
|
import { LoadingState } from '@features/global/state/atoms/Loading';
|
||||||
|
import { delayRequest } from '@features/global/utils/managedSearchRequest';
|
||||||
|
import useRouterCompany from '@features/router/hooks/use-router-company';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
|
import { SharedWithMeDriveItemsResultsState } from '../state/shared-with-me-drive-items-result';
|
||||||
|
import { SearchInputState } from '../../search/state/search-input';
|
||||||
|
|
||||||
|
export const useSharedWithMeDriveItemsLoading = () => {
|
||||||
|
return useRecoilValue(LoadingState('useSearchDriveItems'));
|
||||||
|
};
|
||||||
|
|
||||||
|
let currentQuery = '';
|
||||||
|
|
||||||
|
export const useSharedWithMeDriveItems = () => {
|
||||||
|
const companyId = useRouterCompany();
|
||||||
|
const searchInput = useRecoilValue(SearchInputState);
|
||||||
|
const [loading, setLoading] = useRecoilState(LoadingState('useSearchDriveItems'));
|
||||||
|
|
||||||
|
const [items, setItems] = useRecoilState(SharedWithMeDriveItemsResultsState(companyId));
|
||||||
|
|
||||||
|
const opt = _.omitBy(
|
||||||
|
{
|
||||||
|
limit: 25,
|
||||||
|
workspace_id: searchInput.workspaceId,
|
||||||
|
company_id: companyId,
|
||||||
|
channel_id: searchInput.channelId,
|
||||||
|
},
|
||||||
|
_.isUndefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
const refresh = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
const query = searchInput.query;
|
||||||
|
currentQuery = query;
|
||||||
|
|
||||||
|
const response = await DriveApiClient.sharedWithMe(opt);
|
||||||
|
console.log("response is: ", response);
|
||||||
|
const results = response.entities || [];
|
||||||
|
|
||||||
|
const update = {
|
||||||
|
results,
|
||||||
|
nextPage: '',
|
||||||
|
// nextPage: response.next_page_token,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (currentQuery !== query) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setItems(update);
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadMore = async () => {
|
||||||
|
//Not implemented
|
||||||
|
console.error('Not implemented');
|
||||||
|
};
|
||||||
|
|
||||||
|
useGlobalEffect(
|
||||||
|
'useSearchDriveItems',
|
||||||
|
() => {
|
||||||
|
(async () => {
|
||||||
|
setLoading(true);
|
||||||
|
if (searchInput.query) {
|
||||||
|
delayRequest('useSearchDriveItems', async () => {
|
||||||
|
await refresh();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
},
|
||||||
|
[searchInput.channelId, searchInput.workspaceId],
|
||||||
|
);
|
||||||
|
|
||||||
|
return { loading, driveItems: [...items.results], loadMore, refresh };
|
||||||
|
};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { DriveItem } from '@features/drive/types';
|
||||||
|
import { atomFamily, selectorFamily } from 'recoil';
|
||||||
|
|
||||||
|
export type SharedWithMeDriveItemsResults = {
|
||||||
|
results: DriveItem[];
|
||||||
|
nextPage: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SharedWithMeDriveItemsResultsState = atomFamily<SharedWithMeDriveItemsResults, string>({
|
||||||
|
key: 'SharedWithMeDriveItemsResultsState',
|
||||||
|
default: () => ({ results: [], nextPage: '' }),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SearchFilesResultsNumberSelector = selectorFamily<number, string>({
|
||||||
|
key: 'SharedWithMeDriveItemsResultsNumberSelector',
|
||||||
|
get:
|
||||||
|
(companyId: string) =>
|
||||||
|
({ get }) => {
|
||||||
|
const snapshot = get(SharedWithMeDriveItemsResultsState(companyId));
|
||||||
|
return snapshot.results.length;
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
|
import RouterService from '@features/router/services/router-service';
|
||||||
|
import { RouterState } from '@features/router/state/atoms/router';
|
||||||
|
import { RouteViewSelector } from '@features/router/state/selectors/router-selector';
|
||||||
|
|
||||||
|
export default function useRouteView() {
|
||||||
|
const setClientState = useSetRecoilState(RouterState);
|
||||||
|
RouterService.setRecoilState = setClientState;
|
||||||
|
const viewId = useRecoilValue(RouteViewSelector);
|
||||||
|
|
||||||
|
return viewId;
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@ export type RouteType = {
|
|||||||
|
|
||||||
export type ClientStateType = {
|
export type ClientStateType = {
|
||||||
companyId?: string;
|
companyId?: string;
|
||||||
|
viewId?: string;
|
||||||
workspaceId?: string;
|
workspaceId?: string;
|
||||||
channelId?: string;
|
channelId?: string;
|
||||||
messageId?: string;
|
messageId?: string;
|
||||||
@@ -47,6 +48,7 @@ class RouterServices extends Observable {
|
|||||||
//List of client sub paths
|
//List of client sub paths
|
||||||
clientSubPathnames: Readonly<string[]> = [
|
clientSubPathnames: Readonly<string[]> = [
|
||||||
'/client/:companyId',
|
'/client/:companyId',
|
||||||
|
'/client/:companyId/v/:viewId',
|
||||||
'/client/:companyId/w/:workspaceId',
|
'/client/:companyId/w/:workspaceId',
|
||||||
'/client/:companyId/w/:workspaceId/c/:channelId',
|
'/client/:companyId/w/:workspaceId/c/:channelId',
|
||||||
'/client/:companyId/w/:workspaceId/c/:channelId/t/:threadId',
|
'/client/:companyId/w/:workspaceId/c/:channelId/t/:threadId',
|
||||||
@@ -61,6 +63,7 @@ class RouterServices extends Observable {
|
|||||||
|
|
||||||
pathnames: Readonly<Pathnames> = {
|
pathnames: Readonly<Pathnames> = {
|
||||||
CLIENT: '/client',
|
CLIENT: '/client',
|
||||||
|
SHARED_WITH_ME: '/client/:companyId/shared-with-me',
|
||||||
SHARED: '/shared/:companyId/:appName/:documentId/t/:token',
|
SHARED: '/shared/:companyId/:appName/:documentId/t/:token',
|
||||||
LOGIN: '/login',
|
LOGIN: '/login',
|
||||||
LOGOUT: '/logout',
|
LOGOUT: '/logout',
|
||||||
@@ -107,6 +110,15 @@ class RouterServices extends Observable {
|
|||||||
withErrorBoundary: true,
|
withErrorBoundary: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: this.pathnames.SHARED_WITH_ME,
|
||||||
|
key: 'client_shared',
|
||||||
|
exact: false,
|
||||||
|
component: App,
|
||||||
|
options: {
|
||||||
|
withErrorBoundary: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: this.pathnames.SHARED,
|
path: this.pathnames.SHARED,
|
||||||
key: 'shared',
|
key: 'shared',
|
||||||
@@ -167,6 +179,7 @@ class RouterServices extends Observable {
|
|||||||
|
|
||||||
const reducedState: any = {
|
const reducedState: any = {
|
||||||
companyId: match?.params?.companyId || '',
|
companyId: match?.params?.companyId || '',
|
||||||
|
viewId: match?.params?.viewId || '',
|
||||||
workspaceId: match?.params?.workspaceId || '',
|
workspaceId: match?.params?.workspaceId || '',
|
||||||
channelId: match?.params?.channelId || '',
|
channelId: match?.params?.channelId || '',
|
||||||
messageId: match?.params?.messageId || '',
|
messageId: match?.params?.messageId || '',
|
||||||
@@ -177,6 +190,7 @@ class RouterServices extends Observable {
|
|||||||
token: match?.params?.token || '',
|
token: match?.params?.token || '',
|
||||||
appName: match?.params?.appName || '',
|
appName: match?.params?.appName || '',
|
||||||
shared: !!this.match(this.pathnames.SHARED),
|
shared: !!this.match(this.pathnames.SHARED),
|
||||||
|
sharedWithMe: !!this.match(this.pathnames.SHARED_WITH_ME),
|
||||||
};
|
};
|
||||||
|
|
||||||
const queryParameters = this.allowedQueryParameters[match?.path];
|
const queryParameters = this.allowedQueryParameters[match?.path];
|
||||||
@@ -270,6 +284,8 @@ class RouterServices extends Observable {
|
|||||||
return (
|
return (
|
||||||
`${this.pathnames.CLIENT}` +
|
`${this.pathnames.CLIENT}` +
|
||||||
(state.companyId ? `/${state.companyId}` : '') +
|
(state.companyId ? `/${state.companyId}` : '') +
|
||||||
|
(state.viewId ? `/v/${state.viewId}` : '') +
|
||||||
|
(state.sharedWithMe ? `/shared-with-me` : '') +
|
||||||
(state.workspaceId ? `/w/${state.workspaceId}` : '') +
|
(state.workspaceId ? `/w/${state.workspaceId}` : '') +
|
||||||
(state.channelId ? `/c/${state.channelId}` : '') +
|
(state.channelId ? `/c/${state.channelId}` : '') +
|
||||||
(state.threadId ? `/t/${state.threadId}` : '') +
|
(state.threadId ? `/t/${state.threadId}` : '') +
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ export const RouterCompanySelector = selector<string>({
|
|||||||
get: ({ get }) => get(RouterState)?.companyId || '',
|
get: ({ get }) => get(RouterState)?.companyId || '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const RouteViewSelector = selector<string>({
|
||||||
|
key: 'RouterViewSelector',
|
||||||
|
get: ({ get }) => get(RouterState)?.viewId || '',
|
||||||
|
});
|
||||||
|
|
||||||
export const RouterWorkspaceSelector = selector<string>({
|
export const RouterWorkspaceSelector = selector<string>({
|
||||||
key: 'RouterWorkspaceSelector',
|
key: 'RouterWorkspaceSelector',
|
||||||
get: ({ get }) => get(RouterState)?.workspaceId || '',
|
get: ({ get }) => get(RouterState)?.workspaceId || '',
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export const useSearchDriveItems = () => {
|
|||||||
const query = searchInput.query;
|
const query = searchInput.query;
|
||||||
currentQuery = query;
|
currentQuery = query;
|
||||||
|
|
||||||
const response = await DriveApiClient.search(searchInput.query, opt);
|
const response = await DriveApiClient.search(searchInput.query, "", opt);
|
||||||
let results = response.entities || [];
|
let results = response.entities || [];
|
||||||
if (isRecent)
|
if (isRecent)
|
||||||
results = results.sort(
|
results = results.sort(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ChevronDownIcon } from '@heroicons/react/outline';
|
import { ChevronDownIcon, DotsHorizontalIcon } from '@heroicons/react/outline';
|
||||||
import { Button } from '@atoms/button/button';
|
import { Button } from '@atoms/button/button';
|
||||||
import { Base, BaseSmall, Subtitle, Title } from '@atoms/text';
|
import { Base, BaseSmall, Subtitle, Title } from '@atoms/text';
|
||||||
import Menu from '@components/menus/menu';
|
import Menu from '@components/menus/menu';
|
||||||
@@ -11,6 +11,7 @@ import { useDriveUpload } from '@features/drive/hooks/use-drive-upload';
|
|||||||
import { DriveItemSelectedList } from '@features/drive/state/store';
|
import { DriveItemSelectedList } from '@features/drive/state/store';
|
||||||
import { formatBytes } from '@features/drive/utils';
|
import { formatBytes } from '@features/drive/utils';
|
||||||
import useRouterCompany from '@features/router/hooks/use-router-company';
|
import useRouterCompany from '@features/router/hooks/use-router-company';
|
||||||
|
import useRouterView from '@features/router/hooks/use-router-view';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { memo, Suspense, useCallback, useEffect, useRef, useState } from 'react';
|
import { memo, Suspense, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { atomFamily, useRecoilState, useSetRecoilState } from 'recoil';
|
import { atomFamily, useRecoilState, useSetRecoilState } from 'recoil';
|
||||||
@@ -25,6 +26,9 @@ import { CreateModalAtom } from './modals/create';
|
|||||||
import { PropertiesModal } from './modals/properties';
|
import { PropertiesModal } from './modals/properties';
|
||||||
import { AccessModal } from './modals/update-access';
|
import { AccessModal } from './modals/update-access';
|
||||||
import { VersionsModal } from './modals/versions';
|
import { VersionsModal } from './modals/versions';
|
||||||
|
import { SharedFilesTable } from './shared-files-table';
|
||||||
|
import RouterServices from '@features/router/services/router-service';
|
||||||
|
import useRouteState from 'app/features/router/hooks/use-route-state';
|
||||||
|
|
||||||
export const DriveCurrentFolderAtom = atomFamily<
|
export const DriveCurrentFolderAtom = atomFamily<
|
||||||
string,
|
string,
|
||||||
@@ -119,119 +123,129 @@ export default memo(
|
|||||||
.sort((a, b) => a.name.localeCompare(b.name));
|
.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
const onBuildContextMenu = useOnBuildContextMenu(children, initialParentId);
|
const onBuildContextMenu = useOnBuildContextMenu(children, initialParentId);
|
||||||
|
const { viewId } = useRouteState();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UploadZone
|
<>
|
||||||
overClassName={''}
|
{viewId == 'shared-with-me' ? (
|
||||||
className="h-full overflow-hidden w-full relative"
|
<>
|
||||||
disableClick
|
<SharedFilesTable />
|
||||||
parent={''}
|
</>
|
||||||
multiple={true}
|
) : (
|
||||||
allowPaste={true}
|
<UploadZone
|
||||||
ref={uploadZoneRef}
|
overClassName={''}
|
||||||
driveCollectionKey={uploadZone}
|
className="h-full overflow-hidden"
|
||||||
onAddFiles={async (_, event) => {
|
disableClick
|
||||||
const tree = await getFilesTree(event);
|
parent={''}
|
||||||
setCreationModalState({ parent_id: '', open: false });
|
multiple={true}
|
||||||
uploadTree(tree, {
|
allowPaste={true}
|
||||||
companyId,
|
ref={uploadZoneRef}
|
||||||
parentId,
|
driveCollectionKey={uploadZone}
|
||||||
});
|
onAddFiles={async (_, event) => {
|
||||||
}}
|
const tree = await getFilesTree(event);
|
||||||
>
|
setCreationModalState({ parent_id: '', open: false });
|
||||||
<DriveRealtimeObject id={parentId} key={parentId} />
|
uploadTree(tree, {
|
||||||
<VersionsModal />
|
companyId,
|
||||||
<AccessModal />
|
parentId,
|
||||||
<PropertiesModal />
|
});
|
||||||
<ConfirmDeleteModal />
|
}}
|
||||||
<ConfirmTrashModal />
|
>
|
||||||
<Suspense fallback={<></>}>
|
<DriveRealtimeObject id={parentId} key={parentId} />
|
||||||
<DrivePreview />
|
<VersionsModal />
|
||||||
</Suspense>
|
<AccessModal />
|
||||||
|
<PropertiesModal />
|
||||||
|
<ConfirmDeleteModal />
|
||||||
|
<ConfirmTrashModal />
|
||||||
|
<Suspense fallback={<></>}>
|
||||||
|
<DrivePreview />
|
||||||
|
</Suspense>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'flex flex-col grow h-full overflow-hidden ' +
|
||||||
|
(loading && (!children?.length || loadingParentChange) ? 'opacity-50 ' : '')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row shrink-0 items-center mb-4">
|
||||||
|
<HeaderPath path={path || []} inTrash={inTrash} setParentId={setParentId} />
|
||||||
|
<div className="grow" />
|
||||||
|
|
||||||
<div
|
{access !== 'read' && (
|
||||||
className={
|
<BaseSmall>{formatBytes(item?.size || 0)} used in this folder</BaseSmall>
|
||||||
'flex flex-col grow h-full overflow-hidden ' +
|
)}
|
||||||
(loading && (!children?.length || loadingParentChange) ? 'opacity-50 ' : '')
|
<Menu menu={() => onBuildContextMenu(details)}>
|
||||||
}
|
{' '}
|
||||||
>
|
<Button theme="secondary" className="ml-4 flex flex-row items-center">
|
||||||
<div className="flex flex-row shrink-0 items-center mb-4">
|
<span>{selectedCount > 1 ? `${selectedCount} items` : 'More'} </span>
|
||||||
<HeaderPath path={path || []} inTrash={inTrash} setParentId={setParentId} />
|
|
||||||
<div className="grow" />
|
|
||||||
|
|
||||||
{access !== 'read' && (
|
<ChevronDownIcon className="h-4 w-4 ml-2 -mr-1" />
|
||||||
<BaseSmall>{formatBytes(item?.size || 0)} used in this folder</BaseSmall>
|
</Button>
|
||||||
)}
|
</Menu>
|
||||||
<Menu menu={() => onBuildContextMenu(details)}>
|
</div>
|
||||||
{' '}
|
|
||||||
<Button theme="secondary" className="ml-4 flex flex-row items-center">
|
|
||||||
<span>{selectedCount > 1 ? `${selectedCount} items` : 'More'} </span>
|
|
||||||
|
|
||||||
<ChevronDownIcon className="h-4 w-4 ml-2 -mr-1" />
|
<div className="grow overflow-auto">
|
||||||
</Button>
|
{folders.length > 0 && (
|
||||||
</Menu>
|
<>
|
||||||
</div>
|
<Title className="mb-2 block">Folders</Title>
|
||||||
|
|
||||||
<div className="grow overflow-auto">
|
{folders.map((child, index) => (
|
||||||
{folders.length > 0 && (
|
<FolderRow
|
||||||
<>
|
key={index}
|
||||||
<Title className="mb-2 block">Folders</Title>
|
className={
|
||||||
|
(index === 0 ? 'rounded-t-md ' : '') +
|
||||||
|
(index === folders.length - 1 ? 'rounded-b-md ' : '')
|
||||||
|
}
|
||||||
|
item={child}
|
||||||
|
onClick={() => {
|
||||||
|
return setParentId(child.id);
|
||||||
|
}}
|
||||||
|
checked={checked[child.id] || false}
|
||||||
|
onCheck={v =>
|
||||||
|
setChecked(_.pickBy({ ...checked, [child.id]: v }, _.identity))
|
||||||
|
}
|
||||||
|
onBuildContextMenu={() => onBuildContextMenu(details, child)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<div className="my-6" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{folders.map((child, index) => (
|
<Title className="mb-2 block">Documents</Title>
|
||||||
<FolderRow
|
|
||||||
|
{documents.length === 0 && !loading && (
|
||||||
|
<div className="mt-4 text-center border-2 border-dashed rounded-md p-8">
|
||||||
|
<Subtitle className="block mb-2">Nothing here.</Subtitle>
|
||||||
|
{!inTrash && access != 'read' && (
|
||||||
|
<>
|
||||||
|
<Base>
|
||||||
|
Drag and drop files to upload them or click on the 'Add document' button.
|
||||||
|
</Base>
|
||||||
|
<br />
|
||||||
|
<Button onClick={() => openItemModal()} theme="primary" className="mt-4">
|
||||||
|
Add document or folder
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{documents.map((child, index) => (
|
||||||
|
<DocumentRow
|
||||||
key={index}
|
key={index}
|
||||||
className={
|
className={
|
||||||
(index === 0 ? 'rounded-t-md ' : '') +
|
(index === 0 ? 'rounded-t-md ' : '') +
|
||||||
(index === folders.length - 1 ? 'rounded-b-md ' : '')
|
(index === documents.length - 1 ? 'rounded-b-md ' : '')
|
||||||
}
|
}
|
||||||
item={child}
|
item={child}
|
||||||
onClick={() => {
|
|
||||||
return setParentId(child.id);
|
|
||||||
}}
|
|
||||||
checked={checked[child.id] || false}
|
checked={checked[child.id] || false}
|
||||||
onCheck={v => setChecked(_.pickBy({ ...checked, [child.id]: v }, _.identity))}
|
onCheck={v => setChecked(_.pickBy({ ...checked, [child.id]: v }, _.identity))}
|
||||||
onBuildContextMenu={() => onBuildContextMenu(details, child)}
|
onBuildContextMenu={() => onBuildContextMenu(details, child)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<div className="my-6" />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Title className="mb-2 block">Documents</Title>
|
|
||||||
|
|
||||||
{documents.length === 0 && !loading && (
|
|
||||||
<div className="mt-4 text-center border-2 border-dashed rounded-md p-8">
|
|
||||||
<Subtitle className="block mb-2">Nothing here.</Subtitle>
|
|
||||||
{!inTrash && access != 'read' && (
|
|
||||||
<>
|
|
||||||
<Base>
|
|
||||||
Drag and drop files to upload them or click on the 'Add document' button.
|
|
||||||
</Base>
|
|
||||||
<br />
|
|
||||||
<Button onClick={() => openItemModal()} theme="primary" className="mt-4">
|
|
||||||
Add document or folder
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
</UploadZone>
|
||||||
{documents.map((child, index) => (
|
)}
|
||||||
<DocumentRow
|
</>
|
||||||
key={index}
|
|
||||||
className={
|
|
||||||
(index === 0 ? 'rounded-t-md ' : '') +
|
|
||||||
(index === documents.length - 1 ? 'rounded-b-md ' : '')
|
|
||||||
}
|
|
||||||
item={child}
|
|
||||||
checked={checked[child.id] || false}
|
|
||||||
onCheck={v => setChecked(_.pickBy({ ...checked, [child.id]: v }, _.identity))}
|
|
||||||
onBuildContextMenu={() => onBuildContextMenu(details, child)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</UploadZone>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -289,3 +289,64 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const onBuildFileTypeContextMenu = () => {
|
||||||
|
const menuItems = [
|
||||||
|
{
|
||||||
|
type: 'menu',
|
||||||
|
text: 'PDF',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'menu',
|
||||||
|
text: 'DOC',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'menu',
|
||||||
|
text: 'PNG',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return menuItems;
|
||||||
|
};
|
||||||
|
export const onBuildPeopleContextMenu = () => {
|
||||||
|
const menuItems = [
|
||||||
|
{
|
||||||
|
type: 'menu',
|
||||||
|
text: 'Dwho',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return menuItems;
|
||||||
|
};
|
||||||
|
export const onBuildDateContextMenu = () => {
|
||||||
|
const menuItems = [
|
||||||
|
{
|
||||||
|
type: 'menu',
|
||||||
|
text: 'All',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'menu',
|
||||||
|
text: 'Today',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'menu',
|
||||||
|
text: 'Last week',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'menu',
|
||||||
|
text: 'Last month',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'menu',
|
||||||
|
text: 'Range',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return menuItems;
|
||||||
|
};
|
||||||
|
export const onBuildFileContextMenu = (id: string) => {
|
||||||
|
const menuItems = [
|
||||||
|
{
|
||||||
|
type: 'menu',
|
||||||
|
text: 'Download'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return menuItems;
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { ChevronDownIcon, ChevronUpIcon, DotsHorizontalIcon } from '@heroicons/react/outline';
|
||||||
|
import { Button } from '@atoms/button/button';
|
||||||
|
import { Base, BaseSmall, Title } from '@atoms/text';
|
||||||
|
import Menu from '@components/menus/menu';
|
||||||
|
import {
|
||||||
|
onBuildFileTypeContextMenu,
|
||||||
|
onBuildPeopleContextMenu,
|
||||||
|
onBuildDateContextMenu,
|
||||||
|
onBuildFileContextMenu,
|
||||||
|
} from './context-menu';
|
||||||
|
import { useSharedWithMeDriveItems } from '@features/drive/hooks/use-shared-with-me-drive-items';
|
||||||
|
|
||||||
|
export const SharedFilesTable = () => {
|
||||||
|
const { driveItems, loading } = useSharedWithMeDriveItems();
|
||||||
|
const [sortBy, setSortBy] = useState('name');
|
||||||
|
const [sortOrder, setSortOrder] = useState('asc');
|
||||||
|
|
||||||
|
const handleSort = (column: string) => {
|
||||||
|
if (sortBy === column) {
|
||||||
|
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
|
||||||
|
} else {
|
||||||
|
setSortBy(column);
|
||||||
|
setSortOrder('asc');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderSortIcon = (column: string) => {
|
||||||
|
if (sortBy === column) {
|
||||||
|
if (sortOrder === 'asc') {
|
||||||
|
return <ChevronDownIcon className="h-4 w-4 ml-2 -mr-1" />;
|
||||||
|
} else {
|
||||||
|
return <ChevronUpIcon className="h-4 w-4 ml-2 -mr-1" />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Title className="mb-4 block">Shared with me</Title>
|
||||||
|
{/* Filters */}
|
||||||
|
<div className="flex items-center space-x-4 mb-6">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Menu menu={() => onBuildFileTypeContextMenu()}>
|
||||||
|
<Button theme="secondary" className="flex items-center">
|
||||||
|
<span>File Type</span>
|
||||||
|
<ChevronDownIcon className="h-4 w-4 ml-2 -mr-1" />
|
||||||
|
</Button>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Menu menu={() => onBuildPeopleContextMenu()}>
|
||||||
|
<Button theme="secondary" className="flex items-center">
|
||||||
|
<span>People</span>
|
||||||
|
<ChevronDownIcon className="h-4 w-4 ml-2 -mr-1" />
|
||||||
|
</Button>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Menu menu={() => onBuildDateContextMenu()}>
|
||||||
|
<Button theme="secondary" className="flex items-center">
|
||||||
|
<span>Last Modified</span>
|
||||||
|
<ChevronDownIcon className="h-4 w-4 ml-2 -mr-1" />
|
||||||
|
</Button>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Title className="mb-4 block">Documents:</Title>
|
||||||
|
<div className="relative overflow-x-auto shadow-md sm:rounded-lg">
|
||||||
|
<table className="w-full text-sm text-left text-gray-500 dark:text-gray-400">
|
||||||
|
<thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-blue-500 dark:text-white">
|
||||||
|
<tr>
|
||||||
|
<th scope="col" className="px-6 py-3">
|
||||||
|
<span className="flex" onClick={() => handleSort('name')}>
|
||||||
|
Name {renderSortIcon('name')}
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th scope="col" className="px-6 py-3">
|
||||||
|
<span className="flex" onClick={() => handleSort('name')}>
|
||||||
|
Shared By {renderSortIcon('name')}
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th scope="col" className="px-6 py-3">
|
||||||
|
<span className="flex" onClick={() => handleSort('name')}>
|
||||||
|
Shared Date {renderSortIcon('name')}
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th scope="col" className="px-6 py-3">
|
||||||
|
<span className="sr-only">Edit</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{!loading &&
|
||||||
|
driveItems
|
||||||
|
.sort((a: any, b: any) => {
|
||||||
|
// Perform sorting based on the selected column and order
|
||||||
|
console.log("a is: ", a);
|
||||||
|
console.log("b is: ", b);
|
||||||
|
if (sortBy === 'name') {
|
||||||
|
return sortOrder === 'asc'
|
||||||
|
? a.name.localeCompare(b.name)
|
||||||
|
: b.name.localeCompare(a.name);
|
||||||
|
}
|
||||||
|
return 0; // No sorting by default
|
||||||
|
})
|
||||||
|
.map((file: any, index: any) => (
|
||||||
|
<tr
|
||||||
|
key={index}
|
||||||
|
className="bg-white border-b dark:bg-gray-800 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
scope="row"
|
||||||
|
className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white"
|
||||||
|
>
|
||||||
|
{file.name}
|
||||||
|
</th>
|
||||||
|
<td className="px-6 py-4">Dwho</td>
|
||||||
|
<td className="px-6 py-4">2023-05-05</td>
|
||||||
|
<td className="px-6 py-4 text-right">
|
||||||
|
<Menu menu={onBuildFileContextMenu(file.id)}>
|
||||||
|
<Button
|
||||||
|
theme={'secondary'}
|
||||||
|
size="sm"
|
||||||
|
className={'!rounded-full '}
|
||||||
|
icon={DotsHorizontalIcon}
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -7,7 +7,10 @@ import {
|
|||||||
ShareIcon,
|
ShareIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
UserIcon,
|
UserIcon,
|
||||||
|
UserGroupIcon,
|
||||||
} from '@heroicons/react/outline';
|
} from '@heroicons/react/outline';
|
||||||
|
import useRouterCompany from '@features/router/hooks/use-router-company';
|
||||||
|
import useRouterWorkspace from '@features/router/hooks/use-router-workspace';
|
||||||
import { useCurrentUser } from 'app/features/users/hooks/use-current-user';
|
import { useCurrentUser } from 'app/features/users/hooks/use-current-user';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
import { Title } from '../../../atoms/text';
|
import { Title } from '../../../atoms/text';
|
||||||
@@ -17,8 +20,14 @@ import Account from '../common/account';
|
|||||||
import AppGrid from '../common/app-grid';
|
import AppGrid from '../common/app-grid';
|
||||||
import DiskUsage from '../common/disk-usage';
|
import DiskUsage from '../common/disk-usage';
|
||||||
import Actions from './actions';
|
import Actions from './actions';
|
||||||
|
import { useHistory, useLocation } from 'react-router-dom';
|
||||||
|
import RouterServices from '@features/router/services/router-service';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
|
const history = useHistory();
|
||||||
|
const location = useLocation();
|
||||||
|
const company = useRouterCompany();
|
||||||
|
const workspace = useRouterWorkspace();
|
||||||
const [parentId, setParentId] = useRecoilState(
|
const [parentId, setParentId] = useRecoilState(
|
||||||
DriveCurrentFolderAtom({ initialFolderId: 'root' }),
|
DriveCurrentFolderAtom({ initialFolderId: 'root' }),
|
||||||
);
|
);
|
||||||
@@ -30,6 +39,7 @@ export default () => {
|
|||||||
let folderType = 'home';
|
let folderType = 'home';
|
||||||
if ((path || [])[0]?.id === 'user_' + user?.id) folderType = 'personal';
|
if ((path || [])[0]?.id === 'user_' + user?.id) folderType = 'personal';
|
||||||
if (inTrash) folderType = 'trash';
|
if (inTrash) folderType = 'trash';
|
||||||
|
const { viewId } = RouterServices.getStateFromRoute();
|
||||||
return (
|
return (
|
||||||
<div className="grow flex flex-col overflow-auto -m-4 p-4 relative">
|
<div className="grow flex flex-col overflow-auto -m-4 p-4 relative">
|
||||||
<div className="grow">
|
<div className="grow">
|
||||||
@@ -50,21 +60,29 @@ export default () => {
|
|||||||
<div className="mt-4" />
|
<div className="mt-4" />
|
||||||
<Title>Drive</Title>
|
<Title>Drive</Title>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setParentId('root')}
|
onClick={() => {history.push(RouterServices.generateRouteFromState({companyId: company, viewId: ""})); setParentId('root')}}
|
||||||
size="lg"
|
size="lg"
|
||||||
theme="white"
|
theme="white"
|
||||||
className={'w-full mt-2 mb-1 ' + (folderType === 'home' ? activeClass : '')}
|
className={'w-full mt-2 mb-1 ' + (folderType === 'home' && viewId == '' ? activeClass : '')}
|
||||||
>
|
>
|
||||||
<CloudIcon className="w-5 h-5 mr-4" /> Home
|
<CloudIcon className="w-5 h-5 mr-4" /> Home
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setParentId('user_' + user?.id)}
|
onClick={() => {history.push(RouterServices.generateRouteFromState({companyId: company, viewId: ""})); setParentId('user_' + user?.id)}}
|
||||||
size="lg"
|
size="lg"
|
||||||
theme="white"
|
theme="white"
|
||||||
className={'w-full mb-1 ' + (folderType === 'personal' ? activeClass : '')}
|
className={'w-full mb-1 ' + (folderType === 'personal' && viewId == '' ? activeClass : '')}
|
||||||
>
|
>
|
||||||
<UserIcon className="w-5 h-5 mr-4" /> My Drive
|
<UserIcon className="w-5 h-5 mr-4" /> My Drive
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => history.push(RouterServices.generateRouteFromState({companyId: company, viewId: "shared-with-me"}))}
|
||||||
|
size="lg"
|
||||||
|
theme="white"
|
||||||
|
className={'w-full mb-1 ' + (viewId === 'shared-with-me' ? activeClass : '')}
|
||||||
|
>
|
||||||
|
<UserGroupIcon className="w-5 h-5 mr-4" /> Shared with me
|
||||||
|
</Button>
|
||||||
{false && (
|
{false && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
@@ -85,10 +103,10 @@ export default () => {
|
|||||||
)}
|
)}
|
||||||
{rootAccess === 'manage' && (
|
{rootAccess === 'manage' && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setParentId('trash')}
|
onClick={() =>{history.push(RouterServices.generateRouteFromState({companyId: company, viewId: ""}));setParentId('trash')}}
|
||||||
size="lg"
|
size="lg"
|
||||||
theme="white"
|
theme="white"
|
||||||
className={'w-full mb-1 ' + (folderType === 'trash' ? activeClass : '')}
|
className={'w-full mb-1 ' + (folderType === 'trash' && viewId == ''? activeClass : '')}
|
||||||
>
|
>
|
||||||
<TrashIcon className="w-5 h-5 mr-4 text-rose-500" /> Trash
|
<TrashIcon className="w-5 h-5 mr-4 text-rose-500" /> Trash
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Reference in New Issue
Block a user