Skip to content

Commit

Permalink
feat: gallery view (#1051)
Browse files Browse the repository at this point in the history
* feat: gallery view

* fix: lint type

* chore: update i18n

* fix: presort interaction

* fix: the rendering for group by date field validation

* fix: display tooltip when text ellipsis is activated

* fix: rendering of card title
  • Loading branch information
Sky-FE authored Nov 4, 2024
1 parent d59e99b commit d1dbfb6
Show file tree
Hide file tree
Showing 72 changed files with 1,169 additions and 78 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ Visualize and interact with data in various ways best suited for their specific
- Grid View: The default view of the table, which displays data in a spreadsheet-like format.
- Form View: Input data in a form format, which is useful for collecting data.
- Kanban View: Displays data in a Kanban board, which is a visual representation of data in columns and cards.
- Gallery View: Displays data in a gallery format, which is useful for displaying images and other media.
- Calendar View: Displays data in a calendar format, which is useful for tracking dates and events. (coming soon)
- Gallery View: Displays data in a gallery format, which is useful for displaying images and other media. (coming soon)
- Gantt View: Displays data in a Gantt chart, which is useful for tracking project schedules. (coming soon)
- Timeline View: Displays data in a timeline format, which is useful for tracking events over time. (coming soon)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ export class AggregationService {
where: {
tableId,
...(withView?.viewId ? { id: withView.viewId } : {}),
type: { in: [ViewType.Grid, ViewType.Gantt, ViewType.Kanban] },
type: { in: [ViewType.Grid, ViewType.Gantt, ViewType.Kanban, ViewType.Gallery] },
deletedTime: null,
},
})
Expand Down
2 changes: 1 addition & 1 deletion apps/nestjs-backend/src/features/share/share.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ export class ShareService {
return this.getViewAllCollaborators(shareInfo);
}

// only form and kanban view can get all records
// only form, kanban and plugin view can get all collaborators
if ([ViewType.Form, ViewType.Kanban, ViewType.Plugin].includes(view.type)) {
return this.getViewAllCollaborators(shareInfo);
}
Expand Down
4 changes: 3 additions & 1 deletion apps/nestjs-backend/src/features/view/model/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { assertNever, ViewType } from '@teable/core';
import type { View } from '@teable/db-main-prisma';
import { plainToInstance } from 'class-transformer';
import { FormViewDto } from './form-view.dto';
import { GalleryViewDto } from './gallery-view.dto';
import { GridViewDto } from './grid-view.dto';
import { KanbanViewDto } from './kanban-view.dto';
import { PluginViewDto } from './plugin-view.dto';
Expand All @@ -15,11 +16,12 @@ export function createViewInstanceByRaw(viewRaw: View) {
return plainToInstance(GridViewDto, viewVo);
case ViewType.Kanban:
return plainToInstance(KanbanViewDto, viewVo);
case ViewType.Gallery:
return plainToInstance(GalleryViewDto, viewVo);
case ViewType.Form:
return plainToInstance(FormViewDto, viewVo);
case ViewType.Plugin:
return plainToInstance(PluginViewDto, viewVo);
case ViewType.Gallery:
case ViewType.Gantt:
case ViewType.Calendar:
throw new Error('did not implement yet');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { IShareViewMeta } from '@teable/core';
import { GalleryViewCore } from '@teable/core';

export class GalleryViewDto extends GalleryViewCore {
defaultShareMeta: IShareViewMeta = {
includeRecords: true,
};
}
2 changes: 1 addition & 1 deletion apps/nestjs-backend/src/features/view/view.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export class ViewService implements IReadonlyAdapterService {
// create view compensation data
const innerViewRo = { ...viewRo };
// primary field set visible default
if (viewRo.type === ViewType.Kanban) {
if ([ViewType.Kanban, ViewType.Gallery].includes(viewRo.type)) {
const primaryField = await this.prismaService.txClient().field.findFirstOrThrow({
where: { tableId, isPrimary: true, deletedTime: null },
select: { id: true },
Expand Down
9 changes: 8 additions & 1 deletion apps/nestjs-backend/src/utils/is-not-hidden-field.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { IKanbanViewOptions, IViewVo } from '@teable/core';
import type { IGalleryViewOptions, IKanbanViewOptions, IViewVo } from '@teable/core';
import { ViewType } from '@teable/core';

export const isNotHiddenField = (
Expand All @@ -16,6 +16,13 @@ export const isNotHiddenField = (
);
}

if (viewType === ViewType.Gallery) {
const { coverFieldId } = (options ?? {}) as IGalleryViewOptions;
return (
fieldId === coverFieldId || Boolean((columnMeta[fieldId] as { visible?: boolean })?.visible)
);
}

if ([ViewType.Form].includes(viewType)) {
return Boolean((columnMeta[fieldId] as { visible?: boolean })?.visible);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ export const VIEW_DEFAULT_SHARE_META: {
includeRecords: true,
},
},
{
viewType: ViewType.Gallery,
defaultShareMeta: {
includeRecords: true,
},
},
{
viewType: ViewType.Grid,
defaultShareMeta: {
Expand Down
1 change: 1 addition & 0 deletions apps/nextjs-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
"@tailwindcss/container-queries": "0.1.1",
"@tanstack/react-query": "4.36.1",
"@tanstack/react-table": "8.11.7",
"@tanstack/react-virtual": "3.2.0",
"@teable/common-i18n": "workspace:^",
"@teable/core": "workspace:^",
"@teable/icons": "workspace:^",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ViewType } from '@teable/core';
import { ShareViewContext } from '@teable/sdk/context';
import { useContext } from 'react';
import { FormView } from './component/form/FormView';
import { GalleryView } from './component/gallery/GalleryView';
import { GridView } from './component/grid/GridView';
import { KanbanView } from './component/kanban/KanbanView';
import { PluginView } from './component/plugin/SharePluginView';
Expand All @@ -18,6 +19,8 @@ export const ShareView = () => {
return <GridView />;
case ViewType.Kanban:
return <KanbanView />;
case ViewType.Gallery:
return <GalleryView />;
case ViewType.Plugin:
return <PluginView shareId={shareId} plugin={extra?.plugin} />;
default:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/* eslint-disable @next/next/no-html-link-for-pages */
import { TeableNew } from '@teable/icons';
import { RecordProvider, RowCountProvider, ShareViewContext } from '@teable/sdk/context';
import { SearchProvider } from '@teable/sdk/context/query';
import { useIsHydrated } from '@teable/sdk/hooks';
import { cn } from '@teable/ui-lib/shadcn';
import { useRouter } from 'next/router';
import { useContext } from 'react';
import { GalleryProvider } from '@/features/app/blocks/view/gallery/context';
import { GalleryViewBase } from '@/features/app/blocks/view/gallery/GalleryViewBase';
import { GalleryToolbar } from './toolbar';

export const GalleryView = () => {
const { view } = useContext(ShareViewContext);
const isHydrated = useIsHydrated();
const {
query: { hideToolBar, embed },
} = useRouter();

return (
<div className={cn('flex size-full flex-col', embed ? '' : 'md:px-3 md:pb-3')}>
{!embed && (
<div className="flex w-full justify-between px-1 py-2 md:px-0 md:py-3">
<h1 className="font-semibold md:text-lg">{view?.name}</h1>
<a href="/" className="flex items-center">
<TeableNew className="text-black md:text-2xl" />
<p className="ml-1 font-semibold">Teable</p>
</a>
</div>
)}
<div className="flex w-full grow flex-col overflow-hidden border md:rounded md:shadow-md">
<SearchProvider>
<RecordProvider>
<RowCountProvider>
{!hideToolBar && <GalleryToolbar />}
<GalleryProvider>
<div className="w-full grow overflow-hidden">
{isHydrated && <GalleryViewBase />}
</div>
</GalleryProvider>
</RowCountProvider>
</RecordProvider>
</SearchProvider>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { ArrowUpDown, Filter as FilterIcon } from '@teable/icons';
import type { GalleryView } from '@teable/sdk';
import { useView } from '@teable/sdk/hooks/use-view';
import { cn } from '@teable/ui-lib/shadcn';
import { useToolbarChange } from '@/features/app/blocks/view/hooks/useToolbarChange';
import { SearchButton } from '@/features/app/blocks/view/search/SearchButton';
import { ToolBarButton } from '@/features/app/blocks/view/tool-bar/ToolBarButton';
import { Sort } from '../../grid/toolbar/Sort';
import { ShareViewFilter } from '../../share-view-filter';

export const GalleryToolbar: React.FC<{ disabled?: boolean }> = (props) => {
const { disabled } = props;
const view = useView() as GalleryView | undefined;
const { onFilterChange, onSortChange } = useToolbarChange();

if (!view) return null;

return (
<div className="flex w-full items-center justify-between gap-2 border-b px-4 py-2 @container/toolbar">
<ShareViewFilter filters={view?.filter || null} onChange={onFilterChange}>
{(text, isActive) => (
<ToolBarButton
disabled={disabled}
isActive={isActive}
text={text}
className={cn(
'max-w-xs',
isActive &&
'bg-violet-100 dark:bg-violet-600/30 hover:bg-violet-200 dark:hover:bg-violet-500/30'
)}
textClassName="@2xl/toolbar:inline"
>
<FilterIcon className="size-4 text-sm" />
</ToolBarButton>
)}
</ShareViewFilter>
<Sort sorts={view?.sort || null} onChange={onSortChange}>
{(text: string, isActive) => (
<ToolBarButton
isActive={isActive}
text={text}
className={cn(
'max-w-xs',
isActive &&
'bg-orange-100 dark:bg-orange-600/30 hover:bg-orange-200 dark:hover:bg-orange-500/30'
)}
textClassName="@2xl/toolbar:inline"
>
<ArrowUpDown className="size-4 text-sm" />
</ToolBarButton>
)}
</Sort>
<div className="flex w-10 flex-1 justify-end">
<SearchButton />
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Toolbar';
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ export const AddView: React.FC = () => {
type: ViewType.Grid,
Icon: VIEW_ICON_MAP[ViewType.Grid],
},
{
name: t('view.category.gallery'),
type: ViewType.Gallery,
Icon: VIEW_ICON_MAP[ViewType.Gallery],
},
{
name: t('view.category.kanban'),
type: ViewType.Kanban,
Expand Down
3 changes: 3 additions & 0 deletions apps/nextjs-app/src/features/app/blocks/view/View.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ViewType } from '@teable/core';
import { useView } from '@teable/sdk';
import { FormView } from './form/FormView';
import { GalleryView } from './gallery/GalleryView';
import { GridView } from './grid/GridView';
import { KanbanView } from './kanban/KanbanView';
import { PluginView } from './plugin/PluginView';
Expand All @@ -18,6 +19,8 @@ export const View = (props: IViewBaseProps) => {
return <FormView />;
case ViewType.Kanban:
return <KanbanView />;
case ViewType.Gallery:
return <GalleryView />;
case ViewType.Plugin:
return <PluginView />;
default:
Expand Down
10 changes: 8 additions & 2 deletions apps/nextjs-app/src/features/app/blocks/view/constant.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { ViewType } from '@teable/core';
import { Sheet, ClipboardList as Form, Kanban, Component } from '@teable/icons';
import {
Sheet,
ClipboardList as Form,
LayoutGrid as Gallery,
Kanban,
Component,
} from '@teable/icons';

export const VIEW_ICON_MAP = {
[ViewType.Grid]: Sheet,
[ViewType.Gantt]: Sheet,
[ViewType.Kanban]: Kanban,
[ViewType.Gallery]: Sheet,
[ViewType.Gallery]: Gallery,
[ViewType.Calendar]: Sheet,
[ViewType.Form]: Form,
[ViewType.Plugin]: Component,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { RecordProvider, RowCountProvider } from '@teable/sdk/context';
import { SearchProvider } from '@teable/sdk/context/query';
import { useIsHydrated } from '@teable/sdk/hooks';
import { GalleryToolBar } from '../tool-bar/GalleryToolBar';
import { GalleryProvider } from './context';
import { GalleryViewBase } from './GalleryViewBase';

export const GalleryView = () => {
const isHydrated = useIsHydrated();

return (
<SearchProvider>
<RecordProvider>
<RowCountProvider>
<GalleryToolBar />
<GalleryProvider>
<div className="w-full grow overflow-hidden">{isHydrated && <GalleryViewBase />}</div>
</GalleryProvider>
</RowCountProvider>
</RecordProvider>
</SearchProvider>
);
};
Loading

0 comments on commit d1dbfb6

Please sign in to comment.