Skip to content

Commit

Permalink
feat: embed view (#775)
Browse files Browse the repository at this point in the history
* chore: update build script

* build: add docker sha tag

* feat: display app version

* feat: set theme for share page

* feat: embed view

* chore: publish 1.3.1-beta.0 release

* chore: publish 1.3.1 release

* fix: lint
  • Loading branch information
tea-artist authored Jul 30, 2024
1 parent d6c2f05 commit 218ecdf
Show file tree
Hide file tree
Showing 61 changed files with 619 additions and 489 deletions.
1 change: 1 addition & 0 deletions .github/workflows/docker-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ jobs:
ghcr.io/teableio/${{ matrix.image }}
docker.io/teableio/${{ matrix.image }}
tags: |
type=sha,format=long
type=raw,value=latest
- name: ⚙️ Set up QEMU
Expand Down
2 changes: 1 addition & 1 deletion apps/nestjs-backend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@teable/backend",
"version": "1.3.0-beta.0",
"version": "1.3.1",
"license": "AGPL-3.0",
"private": true,
"main": "dist/index.js",
Expand Down
1 change: 1 addition & 0 deletions apps/nextjs-app/.env
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ NEXT_BUILD_ENV_SOURCEMAPS=false
NEXT_BUILD_ENV_CSP=true
NEXT_BUILD_ENV_SENTRY_ENABLED=true
NEXT_BUILD_ENV_SENTRY_UPLOAD_DRY_RUN=true
NEXT_PUBLIC_BUILD_VERSION=develop
#NEXT_BUILD_ENV_SENTRY_DEBUG=false
#NEXT_BUILD_ENV_SENTRY_TRACING=false
#######################################################################################
Expand Down
6 changes: 1 addition & 5 deletions apps/nextjs-app/config/tests/AppTestProviders.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import type { IAppContext } from '@teable/sdk/context';
import { AppContext, FieldContext, ThemeKey, ViewContext } from '@teable/sdk/context';
import { AppContext, FieldContext, ViewContext } from '@teable/sdk/context';
import { defaultLocale } from '@teable/sdk/context/app/i18n';
import type { IFieldInstance, IViewInstance } from '@teable/sdk/model';
import { noop } from 'lodash';
import type { FC, PropsWithChildren } from 'react';
import { I18nextTestStubProvider } from './I18nextTestStubProvider';

export const createAppContext = (context: Partial<IAppContext> = {}) => {
const defaultContext: IAppContext = {
connected: false,
theme: ThemeKey.Dark,
isAutoTheme: false,
setTheme: noop,
locale: defaultLocale,
};
// eslint-disable-next-line react/display-name
Expand Down
3 changes: 2 additions & 1 deletion apps/nextjs-app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@teable/app",
"version": "1.3.0-beta.0",
"version": "1.3.1",
"license": "AGPL-3.0",
"private": true,
"main": "main/index.js",
Expand Down Expand Up @@ -140,6 +140,7 @@
"next-i18next": "15.2.0",
"next-secure-headers": "2.2.0",
"next-seo": "6.5.0",
"@teable/next-themes": "0.3.3",
"next-transpile-modules": "10.0.1",
"nprogress": "0.2.0",
"picocolors": "1.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ export const SettingPage = (props: ISettingPageProps) => {
</div>

<div className="grow" />

<p className="p-4 text-right text-xs">
{t('settings.setting.version')}: {process.env.NEXT_PUBLIC_BUILD_VERSION}
</p>
<CopyInstance instanceId={instanceId} />
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { LaptopIcon } from '@radix-ui/react-icons';
import { Moon, Settings, Sun, Table2 } from '@teable/icons';
import { ThemeKey } from '@teable/sdk/context';
import { useBase, useTables, useTheme } from '@teable/sdk/hooks';
import { useTheme } from '@teable/next-themes';
import { useBase, useTables } from '@teable/sdk/hooks';
import {
CommandDialog,
CommandInput,
Expand Down Expand Up @@ -81,7 +81,7 @@ export const QuickAction = ({ children }: React.PropsWithChildren) => {
className="flex gap-2"
onSelect={() => {
setOpen(false);
theme.setTheme(ThemeKey.Light);
theme.setTheme('light');
}}
value={t('common:settings.setting.light')}
>
Expand All @@ -92,7 +92,7 @@ export const QuickAction = ({ children }: React.PropsWithChildren) => {
className="flex gap-2"
onSelect={() => {
setOpen(false);
theme.setTheme(ThemeKey.Dark);
theme.setTheme('dark');
}}
value={t('common:settings.setting.dark')}
>
Expand All @@ -103,7 +103,7 @@ export const QuickAction = ({ children }: React.PropsWithChildren) => {
className="flex gap-2"
onSelect={() => {
setOpen(false);
theme.setTheme(null);
theme.setTheme('system');
}}
value={t('common:settings.setting.system')}
>
Expand Down
11 changes: 5 additions & 6 deletions apps/nextjs-app/src/features/app/blocks/graph/useGraph.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import type { GraphData, Graph as IGraph } from '@antv/g6';
import G6 from '@antv/g6';
import { ThemeKey } from '@teable/sdk/context';
import { useTheme } from '@teable/sdk/hooks';
import { useTheme } from '@teable/next-themes';
import type { RefObject } from 'react';
import { useCallback, useEffect, useRef } from 'react';
export const useGraph = (ref: RefObject<HTMLDivElement>) => {
const graphRef = useRef<IGraph>();
const { theme } = useTheme();
const { resolvedTheme } = useTheme();

useEffect(() => {
if (!ref.current) {
Expand All @@ -19,14 +18,14 @@ export const useGraph = (ref: RefObject<HTMLDivElement>) => {
'absolute flex gap-2 right-2 bottom-2 border rounded bg-background shadow p-1 pointer cursor-pointer',
}),
];
if (theme === ThemeKey.Light) {
if (resolvedTheme === 'light') {
plugins.push(
new G6.Grid({
follow: true,
})
);
}
const textColor = theme === ThemeKey.Light ? '#000' : '#fff';
const textColor = resolvedTheme === 'light' ? '#000' : '#fff';
const graph = new G6.Graph({
plugins,
container: element,
Expand Down Expand Up @@ -93,7 +92,7 @@ export const useGraph = (ref: RefObject<HTMLDivElement>) => {
console.error(e);
}
};
}, [ref, theme]);
}, [ref, resolvedTheme]);

const updateGraph = useCallback(async (data?: GraphData) => {
const graph = graphRef.current;
Expand Down
11 changes: 6 additions & 5 deletions apps/nextjs-app/src/features/app/blocks/share/view/AuthPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { shareViewAuth } from '@teable/openapi';
import { Button, Input, Label } from '@teable/ui-lib';
import { Spin } from '@teable/ui-lib/base';
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
import { useState } from 'react';
import { fromZodError } from 'zod-validation-error';
import { shareConfig } from '@/features/i18n/share.config';

export const AuthPage = () => {
const [error, setError] = useState('');
Expand All @@ -14,6 +16,7 @@ export const AuthPage = () => {
const { mutateAsync: authShareView, isLoading } = useMutation({
mutationFn: shareViewAuth,
});
const { t } = useTranslation(shareConfig.i18nNamespaces);

const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
Expand Down Expand Up @@ -41,14 +44,12 @@ export const AuthPage = () => {
return (
<div className="flex min-h-screen items-center justify-center px-4 py-12 sm:px-6 lg:px-8">
<div className="w-full max-w-md space-y-8">
<h2 className="text-center text-3xl font-extrabold">
Enter your password to view this page
</h2>
<h2 className="text-center text-3xl font-extrabold">{t('share:auth.title')}</h2>
<form className="relative space-y-6" onSubmit={onSubmit}>
<div className="-space-y-px rounded-md shadow-sm">
<div>
<Label className="sr-only" htmlFor="password">
Password
{t('share:auth.password')}
</Label>
<Input
id="password"
Expand All @@ -63,7 +64,7 @@ export const AuthPage = () => {
</div>
<Button className="w-full" type="submit" disabled={isLoading}>
{isLoading && <Spin />}
Submit
{t('share:auth.submit')}
</Button>
{error && (
<div className="absolute -bottom-1 w-full translate-y-full text-center text-sm text-destructive">
Expand Down
37 changes: 37 additions & 0 deletions apps/nextjs-app/src/features/app/blocks/share/view/EmbedFooter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ArrowUpRight, TeableNew } from '@teable/icons';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
import { shareConfig } from '@/features/i18n/share.config';

export const EmbedFooter = ({
hideBranding,
hideNewPage,
}: {
hideBranding?: boolean;
hideNewPage?: boolean;
}) => {
const router = useRouter();
const { t } = useTranslation(shareConfig.i18nNamespaces);
const fullPath = router.asPath;
const url = new URL(fullPath, 'http://example.com'); // Use a dummy base URL
url.searchParams.delete('embed');
const pathWithoutEmbed = `${url.pathname}${url.search}`;

return (
<div className="flex items-center justify-between border-t px-2 py-1 text-xs">
{!hideBranding && (
<Link href="/" className="flex items-center gap-1" target="_blank">
<TeableNew className="size-4 text-black" />
Teable
</Link>
)}
{!hideNewPage && (
<Link className="flex gap-1" href={pathWithoutEmbed} target="_blank">
<ArrowUpRight className="size-4" />
{t('share:openOnNewPage')}
</Link>
)}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ViewType } from '@teable/core';
import { useContext } from 'react';
import { FormView } from './component/FormView';
import { FormView } from './component/form/FormView';
import { GridView } from './component/grid/GridView';
import { KanbanView } from './component/kanban/KanbanView';
import { ShareViewPageContext } from './ShareViewPageContext';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from '@teable/sdk/context';
import { getWsPath } from '@teable/sdk/context/app/useConnection';
import Head from 'next/head';
import { useRouter } from 'next/router';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useSdkLocale } from '@/features/app/hooks/useSdkLocale';
Expand All @@ -29,6 +30,8 @@ export const ShareViewPage = (props: IShareViewPageProps) => {
const sdkLocale = useSdkLocale();
const { i18n } = useTranslation();

const { query } = useRouter();

const wsPath = useMemo(() => {
if (typeof window === 'object') {
return addQueryParamsToWebSocketUrl(getWsPath(), { shareId });
Expand All @@ -37,12 +40,17 @@ export const ShareViewPage = (props: IShareViewPageProps) => {
}, [shareId]);

return (
<ShareViewPageContext.Provider value={props.shareViewData}>
<Head>
<title>{view?.name ?? 'Teable'}</title>
</Head>
<AppLayout>
<AppProvider lang={i18n.language} wsPath={wsPath} locale={sdkLocale}>
<AppProvider
lang={i18n.language}
wsPath={wsPath}
locale={sdkLocale}
forcedTheme={query.theme as string}
>
<ShareViewPageContext.Provider value={props.shareViewData}>
<Head>
<title>{view?.name ?? 'Teable'}</title>
</Head>
<AppLayout>
<SessionProvider
user={{
id: ANONYMOUS_USER_ID,
Expand Down Expand Up @@ -70,8 +78,8 @@ export const ShareViewPage = (props: IShareViewPageProps) => {
</ViewProvider>
</AnchorContext.Provider>
</SessionProvider>
</AppProvider>
</AppLayout>
</ShareViewPageContext.Provider>
</AppLayout>
</ShareViewPageContext.Provider>
</AppProvider>
);
};
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
import { useMutation } from '@tanstack/react-query';
import { shareViewFormSubmit } from '@teable/openapi';
import { useRouter } from 'next/router';
import { useContext } from 'react';
import { FormPreviewer } from '../../../view/form/components';
import { ShareViewPageContext } from '../ShareViewPageContext';
import { FormPreviewer } from '@/features/app/blocks/view/form/components';
import { ShareViewPageContext } from '../../ShareViewPageContext';
import { FormViewBase } from './FormViewBase';

export const FormView = () => {
const { shareId } = useContext(ShareViewPageContext);
const { mutateAsync } = useMutation({
mutationFn: shareViewFormSubmit,
});

const {
query: { embed },
} = useRouter();

const onSubmit = async (fields: Record<string, unknown>) => {
await mutateAsync({ shareId, fields });
};
return (
<div className="flex size-full">
<FormPreviewer submit={onSubmit} />
{embed ? <FormViewBase /> : <FormPreviewer submit={onSubmit} />}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { FormBody } from '@/features/app/blocks/view/form/components/FromBody';
import { EmbedFooter } from '../../EmbedFooter';

export const FormViewBase = () => {
return (
<div className="flex grow flex-col border">
<FormBody className="grow overflow-auto pb-8" />
<EmbedFooter hideNewPage />
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { TeableNew } from '@teable/icons';
import { RecordProvider } from '@teable/sdk/context';
import { SearchProvider } from '@teable/sdk/context/query';
import { useIsHydrated } from '@teable/sdk/hooks';
import { useRouter } from 'next/router';
import { useContext } from 'react';
import { EmbedFooter } from '../../EmbedFooter';
import { ShareViewPageContext } from '../../ShareViewPageContext';
import { AggregationProvider, RowCountProvider, GroupPointProvider } from './aggregation';
import { GridViewBase } from './GridViewBase';
Expand All @@ -12,26 +14,32 @@ import { Toolbar } from './toolbar';
export const GridView = () => {
const { records, view } = useContext(ShareViewPageContext);
const isHydrated = useIsHydrated();
const {
query: { hideToolBar, embed },
} = useRouter();

return (
<div className="flex size-full flex-col md:px-3 md:pb-3">
<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>
{!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 serverRecords={records}>
<AggregationProvider>
<RowCountProvider>
<GroupPointProvider>
<Toolbar />
{!hideToolBar && <Toolbar />}
<div className="w-full grow overflow-hidden">
{isHydrated && <GridViewBase />}
</div>
{embed && <EmbedFooter />}
</GroupPointProvider>
</RowCountProvider>
</AggregationProvider>
Expand Down
Loading

0 comments on commit 218ecdf

Please sign in to comment.