mirror of
https://github.com/toeverything/AFFiNE.git
synced 2025-01-02 07:24:14 +03:00
refactor(core): desktop project struct (#8334)
This commit is contained in:
parent
89d09fd5e9
commit
902635e60f
@ -4,9 +4,6 @@ import type { FrameworkProvider, Scope, Service } from '../core';
|
||||
import { ComponentNotFoundError, Framework } from '../core';
|
||||
import { parseIdentifier } from '../core/identifier';
|
||||
import type { GeneralIdentifier, IdentifierType, Type } from '../core/types';
|
||||
import { MountPoint } from './scope-root-components';
|
||||
|
||||
export { useMount } from './scope-root-components';
|
||||
|
||||
export const FrameworkStackContext = React.createContext<FrameworkProvider[]>([
|
||||
Framework.EMPTY.provider(),
|
||||
@ -129,7 +126,7 @@ export const FrameworkScope = ({
|
||||
|
||||
return (
|
||||
<FrameworkStackContext.Provider value={nextStack}>
|
||||
<MountPoint>{children}</MountPoint>
|
||||
{children}
|
||||
</FrameworkStackContext.Provider>
|
||||
);
|
||||
};
|
||||
|
@ -1,74 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
type NodesMap = Map<
|
||||
number,
|
||||
{
|
||||
node: React.ReactNode;
|
||||
debugKey?: string;
|
||||
}
|
||||
>;
|
||||
|
||||
const ScopeRootComponentsContext = React.createContext<{
|
||||
nodes: NodesMap;
|
||||
setNodes: React.Dispatch<React.SetStateAction<NodesMap>>;
|
||||
}>({ nodes: new Map(), setNodes: () => {} });
|
||||
|
||||
let _id = 0;
|
||||
/**
|
||||
* A hook to add nodes to the nearest scope's root
|
||||
*/
|
||||
export const useMount = (debugKey?: string) => {
|
||||
const [id] = React.useState(_id++);
|
||||
const { setNodes } = React.useContext(ScopeRootComponentsContext);
|
||||
|
||||
const unmount = React.useCallback(() => {
|
||||
setNodes(prev => {
|
||||
if (!prev.has(id)) {
|
||||
return prev;
|
||||
}
|
||||
const next = new Map(prev);
|
||||
next.delete(id);
|
||||
return next;
|
||||
});
|
||||
}, [id, setNodes]);
|
||||
|
||||
const mount = React.useCallback(
|
||||
(node: React.ReactNode) => {
|
||||
setNodes(prev => new Map(prev).set(id, { node, debugKey }));
|
||||
return unmount;
|
||||
},
|
||||
[setNodes, id, debugKey, unmount]
|
||||
);
|
||||
|
||||
return React.useMemo(() => {
|
||||
return {
|
||||
/**
|
||||
* Add a node to the nearest scope root
|
||||
* ```tsx
|
||||
* const { mount } = useMount();
|
||||
* useEffect(() => {
|
||||
* const unmount = mount(<div>Node</div>);
|
||||
* return unmount;
|
||||
* }, [])
|
||||
* ```
|
||||
* @return A function to unmount the added node.
|
||||
*/
|
||||
mount,
|
||||
};
|
||||
}, [mount]);
|
||||
};
|
||||
|
||||
export const MountPoint = ({ children }: React.PropsWithChildren) => {
|
||||
const [nodes, setNodes] = React.useState<NodesMap>(new Map());
|
||||
|
||||
return (
|
||||
<ScopeRootComponentsContext.Provider value={{ nodes, setNodes }}>
|
||||
{children}
|
||||
{Array.from(nodes.entries()).map(([id, { node, debugKey }]) => (
|
||||
<div data-testid={debugKey} key={id} style={{ display: 'contents' }}>
|
||||
{node}
|
||||
</div>
|
||||
))}
|
||||
</ScopeRootComponentsContext.Provider>
|
||||
);
|
||||
};
|
@ -1,5 +1,4 @@
|
||||
import { AffineContext } from '@affine/core/components/context';
|
||||
import { Telemetry } from '@affine/core/components/telemetry';
|
||||
import { AppFallback } from '@affine/core/mobile/components';
|
||||
import { configureMobileModules } from '@affine/core/mobile/modules';
|
||||
import { router } from '@affine/core/mobile/router';
|
||||
@ -47,7 +46,6 @@ export function App() {
|
||||
<FrameworkRoot framework={frameworkProvider}>
|
||||
<I18nProvider>
|
||||
<AffineContext store={getCurrentStore()}>
|
||||
<Telemetry />
|
||||
<RouterProvider
|
||||
fallbackElement={<AppFallback />}
|
||||
router={router}
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { GlobalLoading } from '@affine/component/global-loading';
|
||||
import { AppFallback } from '@affine/core/components/affine/app-container';
|
||||
import { AffineContext } from '@affine/core/components/context';
|
||||
import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls';
|
||||
import { Telemetry } from '@affine/core/components/telemetry';
|
||||
import { AppContainer } from '@affine/core/desktop/components/app-container';
|
||||
import { router } from '@affine/core/desktop/router';
|
||||
import { configureCommonModules } from '@affine/core/modules';
|
||||
import { configureAppTabsHeaderModule } from '@affine/core/modules/app-tabs-header';
|
||||
@ -11,27 +9,35 @@ import {
|
||||
configureDesktopApiModule,
|
||||
DesktopApiService,
|
||||
} from '@affine/core/modules/desktop-api';
|
||||
import { GlobalDialogService } from '@affine/core/modules/dialogs';
|
||||
import { EditorSettingService } from '@affine/core/modules/editor-setting';
|
||||
import { configureFindInPageModule } from '@affine/core/modules/find-in-page';
|
||||
import { I18nProvider } from '@affine/core/modules/i18n';
|
||||
import { configureElectronStateStorageImpls } from '@affine/core/modules/storage';
|
||||
import { CustomThemeModifier } from '@affine/core/modules/theme-editor';
|
||||
import {
|
||||
ClientSchemeProvider,
|
||||
PopupWindowProvider,
|
||||
} from '@affine/core/modules/url';
|
||||
import { configureSqliteUserspaceStorageProvider } from '@affine/core/modules/userspace';
|
||||
import { configureDesktopWorkbenchModule } from '@affine/core/modules/workbench';
|
||||
import {
|
||||
configureDesktopWorkbenchModule,
|
||||
WorkbenchService,
|
||||
} from '@affine/core/modules/workbench';
|
||||
import {
|
||||
configureBrowserWorkspaceFlavours,
|
||||
configureSqliteWorkspaceEngineStorageProvider,
|
||||
} from '@affine/core/modules/workspace-engine';
|
||||
import createEmotionCache from '@affine/core/utils/create-emotion-cache';
|
||||
import { apis, events } from '@affine/electron-api';
|
||||
import { CacheProvider } from '@emotion/react';
|
||||
import {
|
||||
DocsService,
|
||||
Framework,
|
||||
FrameworkRoot,
|
||||
getCurrentStore,
|
||||
GlobalContextService,
|
||||
LifecycleService,
|
||||
WorkspacesService,
|
||||
} from '@toeverything/infra';
|
||||
import { Suspense } from 'react';
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
@ -111,6 +117,49 @@ window.addEventListener('focus', () => {
|
||||
});
|
||||
frameworkProvider.get(LifecycleService).applicationStart();
|
||||
|
||||
events?.applicationMenu.openAboutPageInSettingModal(() =>
|
||||
frameworkProvider.get(GlobalDialogService).open('setting', {
|
||||
activeTab: 'about',
|
||||
})
|
||||
);
|
||||
events?.applicationMenu.onNewPageAction(() => {
|
||||
const currentWorkspaceId = frameworkProvider
|
||||
.get(GlobalContextService)
|
||||
.globalContext.workspaceId.get();
|
||||
const workspacesService = frameworkProvider.get(WorkspacesService);
|
||||
const workspaceMetadata = currentWorkspaceId
|
||||
? workspacesService.list.workspace$(currentWorkspaceId).value
|
||||
: null;
|
||||
const workspaceRef =
|
||||
workspaceMetadata &&
|
||||
workspacesService.open({ metadata: workspaceMetadata });
|
||||
if (!workspaceRef) {
|
||||
return;
|
||||
}
|
||||
const { workspace, dispose } = workspaceRef;
|
||||
const editorSettingService = frameworkProvider.get(EditorSettingService);
|
||||
const docsService = workspace.scope.get(DocsService);
|
||||
const editorSetting = editorSettingService.editorSetting;
|
||||
|
||||
const docProps = {
|
||||
note: editorSetting.get('affine:note'),
|
||||
};
|
||||
apis?.ui
|
||||
.isActiveTab()
|
||||
.then(isActive => {
|
||||
if (!isActive) {
|
||||
return;
|
||||
}
|
||||
const page = docsService.createDoc({ docProps });
|
||||
workspace.scope.get(WorkbenchService).workbench.openDoc(page.id);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
dispose();
|
||||
});
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<Suspense>
|
||||
@ -119,11 +168,8 @@ export function App() {
|
||||
<I18nProvider>
|
||||
<AffineContext store={getCurrentStore()}>
|
||||
<DesktopThemeSync />
|
||||
<Telemetry />
|
||||
<CustomThemeModifier />
|
||||
<GlobalLoading />
|
||||
<RouterProvider
|
||||
fallbackElement={<AppFallback />}
|
||||
fallbackElement={<AppContainer fallback />}
|
||||
router={router}
|
||||
future={future}
|
||||
/>
|
||||
|
@ -16,18 +16,17 @@ export const root = style({
|
||||
},
|
||||
});
|
||||
|
||||
export const body = style({
|
||||
flex: 1,
|
||||
paddingTop: 52,
|
||||
});
|
||||
|
||||
export const appTabsHeader = style({
|
||||
zIndex: 1,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
});
|
||||
|
||||
export const fallbackRoot = style({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
paddingTop: 52,
|
||||
});
|
||||
|
||||
export const splitViewFallback = style({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ShellAppFallback } from '@affine/core/components/affine/app-container';
|
||||
import { useAppSettingHelper } from '@affine/core/components/hooks/affine/use-app-setting-helper';
|
||||
import { ThemeProvider } from '@affine/core/components/theme-provider';
|
||||
import { configureAppSidebarModule } from '@affine/core/modules/app-sidebar';
|
||||
import { ShellAppSidebarFallback } from '@affine/core/modules/app-sidebar/views';
|
||||
import {
|
||||
AppTabsHeader,
|
||||
configureAppTabsHeaderModule,
|
||||
@ -10,7 +10,6 @@ import { configureDesktopApiModule } from '@affine/core/modules/desktop-api';
|
||||
import { configureI18nModule, I18nProvider } from '@affine/core/modules/i18n';
|
||||
import { configureElectronStateStorageImpls } from '@affine/core/modules/storage';
|
||||
import { configureAppThemeModule } from '@affine/core/modules/theme';
|
||||
import { SplitViewFallback } from '@affine/core/modules/workbench/view/split-view/split-view';
|
||||
import {
|
||||
configureGlobalStorageModule,
|
||||
Framework,
|
||||
@ -41,9 +40,9 @@ export function App() {
|
||||
<I18nProvider>
|
||||
<div className={styles.root} data-translucent={translucent}>
|
||||
<AppTabsHeader mode="shell" className={styles.appTabsHeader} />
|
||||
<ShellAppFallback className={styles.fallbackRoot}>
|
||||
<SplitViewFallback className={styles.splitViewFallback} />
|
||||
</ShellAppFallback>
|
||||
<div className={styles.body}>
|
||||
<ShellAppSidebarFallback />
|
||||
</div>
|
||||
</div>
|
||||
</I18nProvider>
|
||||
</ThemeProvider>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { DesktopApiService } from '@affine/core/modules/desktop-api/service';
|
||||
import { DesktopApiService } from '@affine/core/modules/desktop-api';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { useRef } from 'react';
|
||||
|
@ -9,11 +9,8 @@ import { storeWorkspaceMeta } from '../workspace';
|
||||
import { getWorkspaceDBPath, getWorkspacesBasePath } from '../workspace/meta';
|
||||
|
||||
export type ErrorMessage =
|
||||
| 'DB_FILE_ALREADY_LOADED'
|
||||
| 'DB_FILE_PATH_INVALID'
|
||||
| 'DB_FILE_INVALID'
|
||||
| 'DB_FILE_MIGRATION_FAILED'
|
||||
| 'FILE_ALREADY_EXISTS'
|
||||
| 'UNKNOWN_ERROR';
|
||||
|
||||
export interface LoadDBFileResult {
|
||||
|
@ -1,10 +0,0 @@
|
||||
import type { NamespaceHandlers } from '../type';
|
||||
import { savePDFFileAs } from './pdf';
|
||||
|
||||
export const exportHandlers = {
|
||||
savePDFFileAs: async (_, title: string) => {
|
||||
return savePDFFileAs(title);
|
||||
},
|
||||
} satisfies NamespaceHandlers;
|
||||
|
||||
export * from './pdf';
|
@ -1,90 +0,0 @@
|
||||
import { BrowserWindow, dialog } from 'electron';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import { logger } from '../logger';
|
||||
import type { ErrorMessage } from './utils';
|
||||
import { getFakedResult } from './utils';
|
||||
|
||||
export interface SavePDFFileResult {
|
||||
filePath?: string;
|
||||
canceled?: boolean;
|
||||
error?: ErrorMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called when the user clicks the "Export to PDF" button in the electron.
|
||||
*
|
||||
* It will just copy the file to the given path
|
||||
*/
|
||||
export async function savePDFFileAs(
|
||||
pageTitle: string
|
||||
): Promise<SavePDFFileResult> {
|
||||
try {
|
||||
const ret =
|
||||
getFakedResult() ??
|
||||
(await dialog.showSaveDialog({
|
||||
properties: ['showOverwriteConfirmation'],
|
||||
title: 'Save PDF',
|
||||
showsTagField: false,
|
||||
buttonLabel: 'Save',
|
||||
defaultPath: `${pageTitle}.pdf`,
|
||||
message: 'Save Page as a PDF file',
|
||||
filters: [{ name: 'PDF Files', extensions: ['pdf'] }],
|
||||
}));
|
||||
const filePath = ret.filePath;
|
||||
if (ret.canceled || !filePath) {
|
||||
return {
|
||||
canceled: true,
|
||||
};
|
||||
}
|
||||
|
||||
await BrowserWindow.getFocusedWindow()
|
||||
?.webContents.printToPDF({
|
||||
pageSize: 'A4',
|
||||
margins: {
|
||||
bottom: 0.5,
|
||||
},
|
||||
printBackground: true,
|
||||
landscape: false,
|
||||
displayHeaderFooter: true,
|
||||
headerTemplate: '<div></div>',
|
||||
footerTemplate: getFootTemple(),
|
||||
})
|
||||
.then(data => {
|
||||
fs.writeFile(filePath, data, error => {
|
||||
if (error) throw error;
|
||||
logger.log(`Wrote PDF successfully to ${filePath}`);
|
||||
});
|
||||
});
|
||||
return { filePath };
|
||||
} catch (err) {
|
||||
logger.error('savePDFFileAs', err);
|
||||
return {
|
||||
error: 'UNKNOWN_ERROR',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getFootTemple(): string {
|
||||
const logo = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="53" height="12" viewBox="0 0 53 12" fill="none">
|
||||
<path d="M18.9256 0.709372C18.8749 0.504937 18.6912 0.361572 18.4807 0.361572H17.77C17.5595 0.361572 17.3758 0.504937 17.3252 0.709372L14.9153 10.4283C14.8438 10.7172 15.0621 10.9965 15.3601 10.9965H15.6052C15.8183 10.9965 16.0033 10.8497 16.0513 10.6423L16.5646 8.43721C16.6127 8.22974 16.7976 8.08291 17.0107 8.08291H19.2396C19.4527 8.08291 19.6376 8.22974 19.6857 8.43721L20.199 10.6423C20.247 10.8497 20.432 10.9965 20.6451 10.9965H20.8902C21.1878 10.9965 21.4065 10.7172 21.335 10.4283L18.9251 0.709372H18.9256ZM18.7891 7.0629H17.4616C17.1666 7.0629 16.9483 6.7883 17.0155 6.50113L17.9025 2.23181C17.9575 1.99576 18.2936 1.99576 18.3486 2.23181L19.2357 6.50113C19.3024 6.7883 19.0845 7.0629 18.7896 7.0629H18.7891Z" fill="black" fill-opacity="0.1"/>
|
||||
<path d="M36.2654 5.00861H30.766C30.5131 5.00861 30.3078 4.8033 30.3078 4.55036V2.25132C30.3078 1.77055 30.6976 1.38074 31.1783 1.38074H33.8997C34.1526 1.38074 34.3579 1.17544 34.3579 0.922494V0.818977C34.3579 0.566031 34.1526 0.36073 33.8997 0.36073H30.8539C29.8924 0.36073 29.1132 1.14036 29.1132 2.10146V5.00774H24.2171C23.9642 5.00774 23.7589 4.80244 23.7589 4.54949V2.25046C23.7589 1.76969 24.1487 1.37988 24.6295 1.37988H27.3508C27.6038 1.37988 27.8091 1.17457 27.8091 0.921628V0.818111C27.8091 0.565165 27.6038 0.359863 27.3508 0.359863H24.3051C23.3435 0.359863 22.5643 1.13949 22.5643 2.1006V10.5366C22.5643 10.7895 22.7696 10.9948 23.0226 10.9948H23.3011C23.554 10.9948 23.7593 10.7895 23.7593 10.5366V6.48513C23.7593 6.23219 23.9646 6.02689 24.2176 6.02689H29.1136V10.5366C29.1136 10.7895 29.3189 10.9948 29.5719 10.9948H29.8504C30.1033 10.9948 30.3086 10.7895 30.3086 10.5366V6.48513C30.3086 6.23219 30.5139 6.02689 30.7669 6.02689H35.9713C36.4521 6.02689 36.8419 6.4167 36.8419 6.89747V10.5418C36.8419 10.7947 37.0472 11 37.3001 11H37.5492C37.8021 11 38.0074 10.7947 38.0074 10.5418V6.74804C38.0074 5.7865 37.2278 5.00731 36.2667 5.00731L36.2654 5.00861Z" fill="black" fill-opacity="0.1"/>
|
||||
<path d="M45.2871 0.361517H45.0363C44.7838 0.361517 44.5789 0.565953 44.5781 0.818032L44.5504 9.53946L42.0512 0.695024C41.9954 0.497519 41.8156 0.361517 41.6103 0.361517H40.521C40.268 0.361517 40.0627 0.566819 40.0627 0.819765V10.5387C40.0627 10.7916 40.268 10.9969 40.521 10.9969H40.7718C41.0243 10.9969 41.2292 10.7925 41.23 10.5404L41.2577 1.81899L43.7569 10.6634C43.8128 10.8609 43.9925 10.9969 44.1978 10.9969H45.2871C45.5401 10.9969 45.7454 10.7916 45.7454 10.5387V0.819331C45.7454 0.566386 45.5401 0.361084 45.2871 0.361084V0.361517Z" fill="black" fill-opacity="0.1"/>
|
||||
<path d="M49.2307 1.3811H51.8212C52.0741 1.3811 52.2794 1.17579 52.2794 0.922849V0.819331C52.2794 0.566386 52.0741 0.361084 51.8212 0.361084H48.9214C47.9599 0.361084 47.1807 1.14071 47.1807 2.10182V9.25489C47.1807 10.2164 47.9603 10.9956 48.9214 10.9956H51.8212C52.0741 10.9956 52.2794 10.7903 52.2794 10.5374V10.4339C52.2794 10.1809 52.0741 9.97562 51.8212 9.97562H49.2307C48.7499 9.97562 48.3601 9.5858 48.3601 9.10503V6.33996C48.3601 6.08701 48.5654 5.88171 48.8183 5.88171H51.6752C51.9282 5.88171 52.1335 5.67641 52.1335 5.42346V5.31994C52.1335 5.067 51.9282 4.8617 51.6752 4.8617H48.8183C48.5654 4.8617 48.3601 4.65639 48.3601 4.40345V2.24995C48.3601 1.76918 48.7499 1.37936 49.2307 1.37936V1.3811Z" fill="black" fill-opacity="0.1"/>
|
||||
<path d="M37.3088 1.65787C37.1052 1.4543 36.7583 1.54742 36.6838 1.82549L36.3473 3.08199C36.2728 3.35962 36.527 3.61387 36.8051 3.5398L38.0616 3.20326C38.3396 3.12876 38.4323 2.7814 38.2292 2.57826L37.3097 1.65873L37.3088 1.65787Z" fill="black" fill-opacity="0.1"/>
|
||||
<path d="M11.9043 9.92891C11.8624 9.85125 11.3139 8.91339 11.2674 8.82602C9.92288 6.49855 7.98407 3.13718 6.64195 0.814557C6.37775 0.29657 5.6448 0.286862 5.36882 0.795141C3.92685 3.2932 1.71414 7.12436 0.274242 9.6193C0.214607 9.72505 0.118915 9.88037 0.0617076 9.99687C-0.035025 10.2063 -0.0163025 10.4677 0.10574 10.6608C0.238877 10.8858 0.502032 11.0165 0.759985 11.0027H0.8269C2.06986 11.0009 9.86983 11.0048 11.2844 11.0027C11.8329 11.0041 12.1779 10.4022 11.904 9.92926L11.9043 9.92891ZM6.09068 1.66053C6.91793 3.09661 7.8967 4.79099 8.85535 6.45036C8.47016 6.21355 8.05792 6.02875 7.6169 5.91711C7.49763 5.89007 7.04136 5.83529 6.94289 5.83113C6.9207 5.82939 6.89851 5.82835 6.87598 5.82835H5.12474C4.97288 5.82835 4.82622 5.86753 4.69655 5.93757C4.53325 5.14845 4.68199 4.26468 4.8914 3.51683C4.90978 3.45269 4.92954 3.38889 4.9493 3.3251C5.29636 2.7239 5.62366 2.15737 5.91073 1.65984C5.95095 1.5905 6.0508 1.59084 6.09068 1.65984V1.66053ZM6.15412 8.25707C6.15412 8.25707 6.12327 8.30492 6.09692 8.34583C6.07161 8.37079 6.03728 8.38535 5.99984 8.38535C5.94956 8.38535 5.90484 8.35935 5.87988 8.31601L5.03841 6.85809C5.03841 6.85809 5.0124 6.80747 4.98987 6.76413C4.98085 6.72946 4.98571 6.69271 5.00408 6.66046C5.02905 6.61712 5.07412 6.59112 5.12404 6.59112H6.80733C6.80733 6.59112 6.86419 6.59389 6.91308 6.59632C6.9474 6.60568 6.97722 6.62822 6.99559 6.66046C7.02056 6.7038 7.02056 6.75581 6.99559 6.79915L6.15378 8.25707H6.15412ZM1.12681 9.94521C1.30502 9.63733 1.53766 9.23653 1.5914 9.14084C2.1971 8.09169 3.04585 6.62163 3.89252 5.15539C3.88004 5.60715 3.92616 6.05684 4.04993 6.49473C4.08599 6.61158 4.26697 7.03422 4.31274 7.12159C4.32591 7.14967 4.34256 7.17914 4.35851 7.20619L5.21939 8.69739C5.29532 8.8288 5.40245 8.93628 5.52796 9.01359C4.92607 9.54961 4.08634 9.86269 3.33397 10.0551C3.27191 10.0707 3.20916 10.0849 3.14675 10.0988C2.34827 10.0988 1.66837 10.0995 1.21695 10.1009C1.13651 10.1009 1.08659 10.0142 1.12681 9.94486V9.94521ZM10.7834 10.1016C9.65661 10.1026 7.37212 10.1005 5.25475 10.0995C5.65139 9.88453 6.01683 9.62034 6.33337 9.29478C6.41624 9.20498 6.69222 8.83712 6.74492 8.75391C6.7626 8.72825 6.77994 8.69913 6.79519 8.67208L7.65608 7.18088C7.73201 7.04947 7.77153 6.90281 7.77569 6.75546C8.54089 7.00856 9.23188 7.57925 9.77449 8.13468C9.82129 8.18322 9.86706 8.23245 9.91248 8.28203C10.2464 8.86035 10.5695 9.41994 10.8729 9.9459C10.9127 10.0152 10.8632 10.1019 10.7831 10.1019L10.7834 10.1016Z" fill="black" fill-opacity="0.1"/>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
const footerTemp = `
|
||||
<div style="font-size: 14px; width: 100%; display: flex; justify-content: flex-end; margin-right: 40px;">
|
||||
<a href="https://affine.pro" style="display: flex; text-decoration: none; color: rgba(0, 0, 0, 0.1);">
|
||||
<span>Created with</span>
|
||||
<div style="display: flex; align-items: center;">${logo}</div>
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return footerTemp;
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
// provide a backdoor to set dialog path for testing in playwright
|
||||
interface FakeDialogResult {
|
||||
canceled?: boolean;
|
||||
filePath?: string;
|
||||
filePaths?: string[];
|
||||
}
|
||||
// result will be used in the next call to showOpenDialog
|
||||
// if it is being read once, it will be reset to undefined
|
||||
let fakeDialogResult: FakeDialogResult | undefined = undefined;
|
||||
export function getFakedResult() {
|
||||
const result = fakeDialogResult;
|
||||
fakeDialogResult = undefined;
|
||||
return result;
|
||||
}
|
||||
|
||||
export function setFakeDialogResult(result: FakeDialogResult | undefined) {
|
||||
fakeDialogResult = result;
|
||||
// for convenience, we will fill filePaths with filePath if it is not set
|
||||
if (result?.filePaths === undefined && result?.filePath !== undefined) {
|
||||
result.filePaths = [result.filePath];
|
||||
}
|
||||
}
|
||||
const ErrorMessages = ['FILE_ALREADY_EXISTS', 'UNKNOWN_ERROR'] as const;
|
||||
export type ErrorMessage = (typeof ErrorMessages)[number];
|
@ -3,7 +3,6 @@ import { ipcMain } from 'electron';
|
||||
import { AFFINE_API_CHANNEL_NAME } from '../shared/type';
|
||||
import { clipboardHandlers } from './clipboard';
|
||||
import { configStorageHandlers } from './config-storage';
|
||||
import { exportHandlers } from './export';
|
||||
import { findInPageHandlers } from './find-in-page';
|
||||
import { getLogFilePath, logger, revealLogFile } from './logger';
|
||||
import { sharedStorageHandlers } from './shared-storage';
|
||||
@ -24,7 +23,6 @@ export const allHandlers = {
|
||||
debug: debugHandlers,
|
||||
ui: uiHandlers,
|
||||
clipboard: clipboardHandlers,
|
||||
export: exportHandlers,
|
||||
updater: updaterHandlers,
|
||||
configStorage: configStorageHandlers,
|
||||
findInPage: findInPageHandlers,
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { AffineContext } from '@affine/core/components/context';
|
||||
import { Telemetry } from '@affine/core/components/telemetry';
|
||||
import { AppFallback } from '@affine/core/mobile/components';
|
||||
import { configureMobileModules } from '@affine/core/mobile/modules';
|
||||
import { router } from '@affine/core/mobile/router';
|
||||
@ -123,7 +122,6 @@ export function App() {
|
||||
<FrameworkRoot framework={frameworkProvider}>
|
||||
<I18nProvider>
|
||||
<AffineContext store={getCurrentStore()}>
|
||||
<Telemetry />
|
||||
<RouterProvider
|
||||
fallbackElement={<AppFallback />}
|
||||
router={router}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { AffineContext } from '@affine/core/components/context';
|
||||
import { Telemetry } from '@affine/core/components/telemetry';
|
||||
import { AppFallback } from '@affine/core/mobile/components';
|
||||
import { configureMobileModules } from '@affine/core/mobile/modules';
|
||||
import { router } from '@affine/core/mobile/router';
|
||||
@ -67,7 +66,6 @@ export function App() {
|
||||
<FrameworkRoot framework={frameworkProvider}>
|
||||
<I18nProvider>
|
||||
<AffineContext store={getCurrentStore()}>
|
||||
<Telemetry />
|
||||
<RouterProvider
|
||||
fallbackElement={<AppFallback />}
|
||||
router={router}
|
||||
|
@ -1,16 +1,10 @@
|
||||
import { GlobalLoading } from '@affine/component/global-loading';
|
||||
import { AppFallback } from '@affine/core/components/affine/app-container';
|
||||
import { AffineContext } from '@affine/core/components/context';
|
||||
import { Telemetry } from '@affine/core/components/telemetry';
|
||||
import { AppContainer } from '@affine/core/desktop/components/app-container';
|
||||
import { router } from '@affine/core/desktop/router';
|
||||
import { configureCommonModules } from '@affine/core/modules';
|
||||
import { I18nProvider } from '@affine/core/modules/i18n';
|
||||
import {
|
||||
configureOpenInApp,
|
||||
OpenInAppGuard,
|
||||
} from '@affine/core/modules/open-in-app';
|
||||
import { OpenInAppGuard } from '@affine/core/modules/open-in-app';
|
||||
import { configureLocalStorageStateStorageImpls } from '@affine/core/modules/storage';
|
||||
import { CustomThemeModifier } from '@affine/core/modules/theme-editor';
|
||||
import { PopupWindowProvider } from '@affine/core/modules/url';
|
||||
import { configureIndexedDBUserspaceStorageProvider } from '@affine/core/modules/userspace';
|
||||
import { configureBrowserWorkbenchModule } from '@affine/core/modules/workbench';
|
||||
@ -42,7 +36,6 @@ configureLocalStorageStateStorageImpls(framework);
|
||||
configureBrowserWorkspaceFlavours(framework);
|
||||
configureIndexedDBWorkspaceEngineStorageProvider(framework);
|
||||
configureIndexedDBUserspaceStorageProvider(framework);
|
||||
configureOpenInApp(framework);
|
||||
framework.impl(PopupWindowProvider, {
|
||||
open: (target: string) => {
|
||||
const targetUrl = new URL(target);
|
||||
@ -77,12 +70,9 @@ export function App() {
|
||||
<CacheProvider value={cache}>
|
||||
<I18nProvider>
|
||||
<AffineContext store={getCurrentStore()}>
|
||||
<Telemetry />
|
||||
<CustomThemeModifier />
|
||||
<GlobalLoading />
|
||||
<OpenInAppGuard>
|
||||
<RouterProvider
|
||||
fallbackElement={<AppFallback key="RouterFallback" />}
|
||||
fallbackElement={<AppContainer fallback />}
|
||||
router={router}
|
||||
future={future}
|
||||
/>
|
||||
|
@ -1,35 +1,5 @@
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { Loading } from '../../ui/loading';
|
||||
import * as styles from './index.css';
|
||||
import { globalLoadingEventsAtom } from './index.jotai';
|
||||
|
||||
export {
|
||||
type GlobalLoadingEvent,
|
||||
pushGlobalLoadingEventAtom,
|
||||
resolveGlobalLoadingEventAtom,
|
||||
} from './index.jotai';
|
||||
|
||||
export function GlobalLoading(): ReactNode {
|
||||
const globalLoadingEvents = useAtomValue(globalLoadingEventsAtom);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (globalLoadingEvents.length) {
|
||||
setLoading(true);
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [globalLoadingEvents]);
|
||||
|
||||
if (!globalLoadingEvents.length) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className={styles.globalLoadingWrapperStyle} data-loading={loading}>
|
||||
<Loading size={20} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
export * from './confirm-modal';
|
||||
export * from './modal';
|
||||
export * from './overlay-modal';
|
||||
export * from './prompt-modal';
|
||||
|
@ -229,6 +229,7 @@ export const ModalInner = forwardRef<HTMLDivElement, ModalProps>(
|
||||
styles.modalContentWrapper,
|
||||
contentWrapperClassName
|
||||
)}
|
||||
data-mobile={BUILD_CONFIG.isMobileEdition ? '' : undefined}
|
||||
style={contentWrapperStyle}
|
||||
>
|
||||
<Dialog.Content
|
||||
|
109
packages/frontend/component/src/ui/modal/prompt-modal.css.ts
Normal file
109
packages/frontend/component/src/ui/modal/prompt-modal.css.ts
Normal file
@ -0,0 +1,109 @@
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
// desktop
|
||||
export const desktopStyles = {
|
||||
container: style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}),
|
||||
description: style({}),
|
||||
header: style({}),
|
||||
content: style({
|
||||
height: '100%',
|
||||
overflowY: 'auto',
|
||||
padding: '12px 4px 20px 4px',
|
||||
}),
|
||||
label: style({
|
||||
color: cssVar('textSecondaryColor'),
|
||||
fontSize: 14,
|
||||
lineHeight: '22px',
|
||||
padding: '8px 0',
|
||||
}),
|
||||
input: style({}),
|
||||
inputContainer: style({}),
|
||||
footer: style({
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
paddingTop: '40px',
|
||||
marginTop: 'auto',
|
||||
gap: '20px',
|
||||
selectors: {
|
||||
'&.modalFooterWithChildren': {
|
||||
paddingTop: '20px',
|
||||
},
|
||||
'&.reverse': {
|
||||
flexDirection: 'row-reverse',
|
||||
justifyContent: 'flex-start',
|
||||
},
|
||||
},
|
||||
}),
|
||||
action: style({}),
|
||||
};
|
||||
|
||||
// mobile
|
||||
export const mobileStyles = {
|
||||
container: style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: '12px 0 !important',
|
||||
borderRadius: 22,
|
||||
}),
|
||||
description: style({
|
||||
padding: '11px 22px',
|
||||
fontSize: 17,
|
||||
fontWeight: 400,
|
||||
letterSpacing: -0.43,
|
||||
lineHeight: '22px',
|
||||
}),
|
||||
label: style({
|
||||
color: cssVar('textSecondaryColor'),
|
||||
fontSize: 14,
|
||||
lineHeight: '22px',
|
||||
padding: '8px 16px',
|
||||
}),
|
||||
header: style({
|
||||
padding: '10px 16px',
|
||||
marginBottom: '0px !important',
|
||||
fontSize: 17,
|
||||
fontWeight: 600,
|
||||
letterSpacing: -0.43,
|
||||
lineHeight: '22px',
|
||||
}),
|
||||
inputContainer: style({
|
||||
padding: '0 16px',
|
||||
}),
|
||||
input: style({
|
||||
height: 44,
|
||||
fontSize: 17,
|
||||
lineHeight: '22px',
|
||||
}),
|
||||
content: style({
|
||||
padding: '11px 22px',
|
||||
fontSize: 17,
|
||||
fontWeight: 400,
|
||||
letterSpacing: -0.43,
|
||||
lineHeight: '22px',
|
||||
}),
|
||||
footer: style({
|
||||
padding: '8px 16px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 16,
|
||||
selectors: {
|
||||
'&.reverse': {
|
||||
flexDirection: 'column-reverse',
|
||||
},
|
||||
},
|
||||
}),
|
||||
action: style({
|
||||
width: '100%',
|
||||
height: 44,
|
||||
borderRadius: 8,
|
||||
fontSize: 17,
|
||||
fontWeight: 400,
|
||||
letterSpacing: -0.43,
|
||||
lineHeight: '22px',
|
||||
}),
|
||||
};
|
235
packages/frontend/component/src/ui/modal/prompt-modal.tsx
Normal file
235
packages/frontend/component/src/ui/modal/prompt-modal.tsx
Normal file
@ -0,0 +1,235 @@
|
||||
import { DialogTrigger } from '@radix-ui/react-dialog';
|
||||
import clsx from 'clsx';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { createContext, useCallback, useContext, useState } from 'react';
|
||||
|
||||
import type { ButtonProps } from '../button';
|
||||
import { Button } from '../button';
|
||||
import Input, { type InputProps } from '../input';
|
||||
import type { ModalProps } from './modal';
|
||||
import { Modal } from './modal';
|
||||
import { desktopStyles, mobileStyles } from './prompt-modal.css';
|
||||
|
||||
const styles = BUILD_CONFIG.isMobileEdition ? mobileStyles : desktopStyles;
|
||||
|
||||
export interface PromptModalProps extends ModalProps {
|
||||
confirmButtonOptions?: Omit<ButtonProps, 'children'>;
|
||||
onConfirm?: ((text: string) => void) | ((text: string) => Promise<void>);
|
||||
onCancel?: () => void;
|
||||
confirmText?: React.ReactNode;
|
||||
cancelText?: React.ReactNode;
|
||||
|
||||
label?: React.ReactNode;
|
||||
defaultValue?: string;
|
||||
required?: boolean;
|
||||
cancelButtonOptions?: Omit<ButtonProps, 'children'>;
|
||||
|
||||
inputOptions?: Omit<InputProps, 'value' | 'onChange'>;
|
||||
reverseFooter?: boolean;
|
||||
/**
|
||||
* Auto focus on confirm button when modal opened
|
||||
* @default true
|
||||
*/
|
||||
autoFocusConfirm?: boolean;
|
||||
}
|
||||
|
||||
export const PromptModal = ({
|
||||
children,
|
||||
confirmButtonOptions,
|
||||
// FIXME: we need i18n
|
||||
confirmText,
|
||||
cancelText = 'Cancel',
|
||||
cancelButtonOptions,
|
||||
reverseFooter,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
label,
|
||||
required = true,
|
||||
inputOptions,
|
||||
defaultValue,
|
||||
width = 480,
|
||||
autoFocusConfirm = true,
|
||||
headerClassName,
|
||||
descriptionClassName,
|
||||
...props
|
||||
}: PromptModalProps) => {
|
||||
const [value, setValue] = useState(defaultValue ?? '');
|
||||
const onConfirmClick = useCallback(() => {
|
||||
Promise.resolve(onConfirm?.(value))
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
})
|
||||
.finally(() => {
|
||||
setValue('');
|
||||
});
|
||||
}, [onConfirm, value]);
|
||||
|
||||
const onKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Escape') {
|
||||
if (value) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
} else {
|
||||
e.currentTarget.blur();
|
||||
}
|
||||
}
|
||||
},
|
||||
[value]
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
contentOptions={{
|
||||
className: styles.container,
|
||||
onPointerDownOutside: e => {
|
||||
e.stopPropagation();
|
||||
onCancel?.();
|
||||
},
|
||||
}}
|
||||
width={width}
|
||||
closeButtonOptions={{
|
||||
onClick: onCancel,
|
||||
}}
|
||||
headerClassName={clsx(styles.header, headerClassName)}
|
||||
descriptionClassName={clsx(styles.description, descriptionClassName)}
|
||||
{...props}
|
||||
>
|
||||
<div className={styles.label}>{label}</div>
|
||||
<div className={styles.inputContainer}>
|
||||
<Input
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
autoFocus
|
||||
className={styles.input}
|
||||
onKeyDown={onKeyDown}
|
||||
data-testid="prompt-modal-input"
|
||||
{...inputOptions}
|
||||
/>
|
||||
</div>
|
||||
{children ? <div className={styles.content}>{children}</div> : null}
|
||||
<div
|
||||
className={clsx(styles.footer, {
|
||||
modalFooterWithChildren: !!children,
|
||||
reverse: reverseFooter,
|
||||
})}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
className={styles.action}
|
||||
onClick={onCancel}
|
||||
data-testid="prompt-modal-cancel"
|
||||
{...cancelButtonOptions}
|
||||
>
|
||||
{cancelText}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<Button
|
||||
className={styles.action}
|
||||
onClick={onConfirmClick}
|
||||
disabled={required && !value}
|
||||
data-testid="prompt-modal-confirm"
|
||||
autoFocus={autoFocusConfirm}
|
||||
{...confirmButtonOptions}
|
||||
>
|
||||
{confirmText}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
interface OpenPromptModalOptions {
|
||||
autoClose?: boolean;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
interface PromptModalContextProps {
|
||||
modalProps: PromptModalProps;
|
||||
openPromptModal: (
|
||||
props?: PromptModalProps,
|
||||
options?: OpenPromptModalOptions
|
||||
) => void;
|
||||
closePromptModal: () => void;
|
||||
}
|
||||
const PromptModalContext = createContext<PromptModalContextProps>({
|
||||
modalProps: { open: false },
|
||||
openPromptModal: () => {},
|
||||
closePromptModal: () => {},
|
||||
});
|
||||
export const PromptModalProvider = ({ children }: PropsWithChildren) => {
|
||||
const [modalProps, setModalProps] = useState<PromptModalProps>({
|
||||
open: false,
|
||||
});
|
||||
|
||||
const setLoading = useCallback((value: boolean) => {
|
||||
setModalProps(prev => ({
|
||||
...prev,
|
||||
confirmButtonOptions: {
|
||||
...prev.confirmButtonOptions,
|
||||
loading: value,
|
||||
},
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const closePromptModal = useCallback(() => {
|
||||
setModalProps({ open: false });
|
||||
}, []);
|
||||
|
||||
const openPromptModal = useCallback(
|
||||
(props?: PromptModalProps, options?: OpenPromptModalOptions) => {
|
||||
const { autoClose = true, onSuccess } = options ?? {};
|
||||
if (!props) {
|
||||
setModalProps({ open: true });
|
||||
return;
|
||||
}
|
||||
|
||||
const { onConfirm: _onConfirm, ...otherProps } = props;
|
||||
|
||||
const onConfirm = (text: string) => {
|
||||
setLoading(true);
|
||||
return Promise.resolve(_onConfirm?.(text))
|
||||
.then(() => onSuccess?.())
|
||||
.catch(console.error)
|
||||
.finally(() => setLoading(false))
|
||||
.finally(() => autoClose && closePromptModal());
|
||||
};
|
||||
setModalProps({ ...otherProps, onConfirm, open: true });
|
||||
},
|
||||
[closePromptModal, setLoading]
|
||||
);
|
||||
|
||||
const onOpenChange = useCallback(
|
||||
(open: boolean) => {
|
||||
modalProps.onOpenChange?.(open);
|
||||
setModalProps(props => ({ ...props, open }));
|
||||
},
|
||||
[modalProps]
|
||||
);
|
||||
|
||||
return (
|
||||
<PromptModalContext.Provider
|
||||
value={{
|
||||
openPromptModal: openPromptModal,
|
||||
closePromptModal: closePromptModal,
|
||||
modalProps,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
{/* TODO(@catsjuice): multi-instance support(unnecessary for now) */}
|
||||
<PromptModal {...modalProps} onOpenChange={onOpenChange} />
|
||||
</PromptModalContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const usePromptModal = () => {
|
||||
const context = useContext(PromptModalContext);
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useConfirmModal must be used within a ConfirmModalProvider'
|
||||
);
|
||||
}
|
||||
return {
|
||||
openPromptModal: context.openPromptModal,
|
||||
closePromptModal: context.closePromptModal,
|
||||
};
|
||||
};
|
@ -79,15 +79,12 @@ export const modalContentWrapper = style({
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
zIndex: cssVar('zIndexModal'),
|
||||
'@media': {
|
||||
'screen and (width <= 640px)': {
|
||||
// todo: adjust animation
|
||||
|
||||
selectors: {
|
||||
'&[data-mobile]': {
|
||||
alignItems: 'flex-end',
|
||||
paddingBottom: 'env(safe-area-inset-bottom, 20px)',
|
||||
},
|
||||
},
|
||||
|
||||
selectors: {
|
||||
'&[data-full-screen="true"]': {
|
||||
padding: '0 !important',
|
||||
},
|
||||
|
@ -4,17 +4,17 @@ import type { DocMode } from '@blocksuite/affine/blocks';
|
||||
import { ImportIcon, PlusIcon } from '@blocksuite/icons/rc';
|
||||
|
||||
import type { usePageHelper } from '../components/blocksuite/block-suite-page-list/utils';
|
||||
import type { CreateWorkspaceDialogService } from '../modules/create-workspace';
|
||||
import type { GlobalDialogService } from '../modules/dialogs';
|
||||
import { registerAffineCommand } from './registry';
|
||||
|
||||
export function registerAffineCreationCommands({
|
||||
pageHelper,
|
||||
t,
|
||||
createWorkspaceDialogService,
|
||||
globalDialogService,
|
||||
}: {
|
||||
t: ReturnType<typeof useI18n>;
|
||||
pageHelper: ReturnType<typeof usePageHelper>;
|
||||
createWorkspaceDialogService: CreateWorkspaceDialogService;
|
||||
globalDialogService: GlobalDialogService;
|
||||
}) {
|
||||
const unsubs: Array<() => void> = [];
|
||||
unsubs.push(
|
||||
@ -62,7 +62,7 @@ export function registerAffineCreationCommands({
|
||||
run() {
|
||||
track.$.cmdk.workspace.createWorkspace();
|
||||
|
||||
createWorkspaceDialogService.dialog.open('new');
|
||||
globalDialogService.open('create-workspace', undefined);
|
||||
},
|
||||
})
|
||||
);
|
||||
@ -80,7 +80,7 @@ export function registerAffineCreationCommands({
|
||||
control: 'import',
|
||||
});
|
||||
|
||||
createWorkspaceDialogService.dialog.open('add');
|
||||
globalDialogService.open('import-workspace', undefined);
|
||||
},
|
||||
})
|
||||
);
|
||||
|
@ -1,20 +1,19 @@
|
||||
import type { useI18n } from '@affine/i18n';
|
||||
import { track } from '@affine/track';
|
||||
import { ContactWithUsIcon, NewIcon } from '@blocksuite/icons/rc';
|
||||
import type { createStore } from 'jotai';
|
||||
|
||||
import { openSettingModalAtom } from '../components/atoms';
|
||||
import type { GlobalDialogService } from '../modules/dialogs';
|
||||
import type { UrlService } from '../modules/url';
|
||||
import { registerAffineCommand } from './registry';
|
||||
|
||||
export function registerAffineHelpCommands({
|
||||
t,
|
||||
store,
|
||||
urlService,
|
||||
globalDialogService,
|
||||
}: {
|
||||
t: ReturnType<typeof useI18n>;
|
||||
store: ReturnType<typeof createStore>;
|
||||
urlService: UrlService;
|
||||
globalDialogService: GlobalDialogService;
|
||||
}) {
|
||||
const unsubs: Array<() => void> = [];
|
||||
unsubs.push(
|
||||
@ -37,8 +36,7 @@ export function registerAffineHelpCommands({
|
||||
label: t['com.affine.cmdk.affine.contact-us'](),
|
||||
run() {
|
||||
track.$.cmdk.help.contactUs();
|
||||
store.set(openSettingModalAtom, {
|
||||
open: true,
|
||||
globalDialogService.open('setting', {
|
||||
activeTab: 'about',
|
||||
workspaceMetadata: null,
|
||||
});
|
||||
|
@ -4,11 +4,9 @@ import type { DocCollection } from '@blocksuite/affine/store';
|
||||
import { ArrowRightBigIcon } from '@blocksuite/icons/rc';
|
||||
import type { createStore } from 'jotai';
|
||||
|
||||
import {
|
||||
openSettingModalAtom,
|
||||
openWorkspaceListModalAtom,
|
||||
} from '../components/atoms';
|
||||
import { openWorkspaceListModalAtom } from '../components/atoms';
|
||||
import type { useNavigateHelper } from '../components/hooks/use-navigate-helper';
|
||||
import type { GlobalDialogService } from '../modules/dialogs';
|
||||
import { registerAffineCommand } from './registry';
|
||||
|
||||
export function registerAffineNavigationCommands({
|
||||
@ -16,11 +14,13 @@ export function registerAffineNavigationCommands({
|
||||
store,
|
||||
docCollection,
|
||||
navigationHelper,
|
||||
globalDialogService,
|
||||
}: {
|
||||
t: ReturnType<typeof useI18n>;
|
||||
store: ReturnType<typeof createStore>;
|
||||
navigationHelper: ReturnType<typeof useNavigateHelper>;
|
||||
docCollection: DocCollection;
|
||||
globalDialogService: GlobalDialogService;
|
||||
}) {
|
||||
const unsubs: Array<() => void> = [];
|
||||
unsubs.push(
|
||||
@ -96,10 +96,9 @@ export function registerAffineNavigationCommands({
|
||||
keyBinding: '$mod+,',
|
||||
run() {
|
||||
track.$.cmdk.settings.openSettings();
|
||||
store.set(openSettingModalAtom, s => ({
|
||||
globalDialogService.open('setting', {
|
||||
activeTab: 'appearance',
|
||||
open: !s.open,
|
||||
}));
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
@ -112,10 +111,9 @@ export function registerAffineNavigationCommands({
|
||||
label: t['com.affine.cmdk.affine.navigation.open-account-settings'](),
|
||||
run() {
|
||||
track.$.cmdk.settings.openSettings({ to: 'account' });
|
||||
store.set(openSettingModalAtom, s => ({
|
||||
globalDialogService.open('setting', {
|
||||
activeTab: 'account',
|
||||
open: !s.open,
|
||||
}));
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { Button, FlexWrapper, notify } from '@affine/component';
|
||||
import { openSettingModalAtom } from '@affine/core/components/atoms';
|
||||
import { SubscriptionService } from '@affine/core/modules/cloud';
|
||||
import { GlobalDialogService } from '@affine/core/modules/dialogs';
|
||||
import { EditorService } from '@affine/core/modules/editor';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { track } from '@affine/track';
|
||||
import { AiIcon } from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useServices } from '@toeverything/infra';
|
||||
import { useLiveData, useService, useServices } from '@toeverything/infra';
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import Lottie from 'lottie-react';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
@ -51,24 +50,20 @@ export const AIOnboardingEdgeless = () => {
|
||||
const notifyId = useLiveData(edgelessNotifyId$);
|
||||
const generalAIOnboardingOpened = useLiveData(showAIOnboardingGeneral$);
|
||||
const aiSubscription = useLiveData(subscriptionService.subscription.ai$);
|
||||
const settingModalOpen = useAtomValue(openSettingModalAtom);
|
||||
const globalDialogService = useService(GlobalDialogService);
|
||||
const timeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
||||
|
||||
const setSettingModal = useSetAtom(openSettingModalAtom);
|
||||
|
||||
const mode = useLiveData(editorService.editor.mode$);
|
||||
|
||||
const goToPricingPlans = useCallback(() => {
|
||||
track.$.aiOnboarding.dialog.viewPlans();
|
||||
setSettingModal({
|
||||
open: true,
|
||||
globalDialogService.open('setting', {
|
||||
activeTab: 'plans',
|
||||
scrollAnchor: 'aiPricingPlan',
|
||||
});
|
||||
}, [setSettingModal]);
|
||||
}, [globalDialogService]);
|
||||
|
||||
useEffect(() => {
|
||||
if (settingModalOpen.open) return;
|
||||
if (generalAIOnboardingOpened) return;
|
||||
if (notifyId) return;
|
||||
if (mode !== 'edgeless') return;
|
||||
@ -128,7 +123,6 @@ export const AIOnboardingEdgeless = () => {
|
||||
goToPricingPlans,
|
||||
mode,
|
||||
notifyId,
|
||||
settingModalOpen,
|
||||
t,
|
||||
]);
|
||||
|
||||
|
@ -1,12 +1,11 @@
|
||||
import { Button, IconButton, Modal } from '@affine/component';
|
||||
import { openSettingModalAtom } from '@affine/core/components/atoms';
|
||||
import { useBlurRoot } from '@affine/core/components/hooks/use-blur-root';
|
||||
import { AuthService, SubscriptionService } from '@affine/core/modules/cloud';
|
||||
import { GlobalDialogService } from '@affine/core/modules/dialogs';
|
||||
import { Trans, useI18n } from '@affine/i18n';
|
||||
import { track } from '@affine/track';
|
||||
import { ArrowLeftSmallIcon } from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useServices } from '@toeverything/infra';
|
||||
import { useAtom } from 'jotai';
|
||||
import { useLiveData, useService, useServices } from '@toeverything/infra';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
@ -96,8 +95,8 @@ export const AIOnboardingGeneral = () => {
|
||||
const aiSubscription = useLiveData(subscriptionService.subscription.ai$);
|
||||
const [index, setIndex] = useState(0);
|
||||
const list = useMemo(() => getPlayList(t), [t]);
|
||||
const [settingModal, setSettingModal] = useAtom(openSettingModalAtom);
|
||||
const readyToOpen = isLoggedIn && !settingModal.open;
|
||||
const globalDialogService = useService(GlobalDialogService);
|
||||
const readyToOpen = isLoggedIn;
|
||||
useBlurRoot(open && readyToOpen);
|
||||
|
||||
const isFirst = index === 0;
|
||||
@ -111,14 +110,13 @@ export const AIOnboardingGeneral = () => {
|
||||
toggleGeneralAIOnboarding(false);
|
||||
}, []);
|
||||
const goToPricingPlans = useCallback(() => {
|
||||
setSettingModal({
|
||||
open: true,
|
||||
globalDialogService.open('setting', {
|
||||
activeTab: 'plans',
|
||||
scrollAnchor: 'aiPricingPlan',
|
||||
});
|
||||
track.$.aiOnboarding.dialog.viewPlans();
|
||||
closeAndDismiss();
|
||||
}, [closeAndDismiss, setSettingModal]);
|
||||
}, [closeAndDismiss, globalDialogService]);
|
||||
const onPrev = useCallback(() => {
|
||||
setIndex(i => Math.max(0, i - 1));
|
||||
}, []);
|
||||
|
@ -1,5 +0,0 @@
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const electronFallback = style({
|
||||
paddingTop: 52,
|
||||
});
|
@ -1,59 +0,0 @@
|
||||
import {
|
||||
AppSidebarFallback,
|
||||
ShellAppSidebarFallback,
|
||||
} from '@affine/core/modules/app-sidebar/views';
|
||||
import clsx from 'clsx';
|
||||
import type { PropsWithChildren, ReactElement } from 'react';
|
||||
|
||||
import { useAppSettingHelper } from '../../components/hooks/affine/use-app-setting-helper';
|
||||
import type { WorkspaceRootProps } from '../workspace';
|
||||
import {
|
||||
AppContainer as AppContainerWithoutSettings,
|
||||
MainContainerFallback,
|
||||
} from '../workspace';
|
||||
import * as styles from './app-container.css';
|
||||
|
||||
export const AppContainer = (props: WorkspaceRootProps) => {
|
||||
const { appSettings } = useAppSettingHelper();
|
||||
|
||||
return (
|
||||
<AppContainerWithoutSettings
|
||||
useNoisyBackground={appSettings.enableNoisyBackground}
|
||||
useBlurBackground={appSettings.enableBlurBackground}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const AppFallback = ({
|
||||
className,
|
||||
children,
|
||||
}: PropsWithChildren<{
|
||||
className?: string;
|
||||
}>): ReactElement => {
|
||||
return (
|
||||
<AppContainer
|
||||
className={clsx(
|
||||
className,
|
||||
BUILD_CONFIG.isElectron && styles.electronFallback
|
||||
)}
|
||||
>
|
||||
<AppSidebarFallback />
|
||||
<MainContainerFallback>{children}</MainContainerFallback>
|
||||
</AppContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export const ShellAppFallback = ({
|
||||
className,
|
||||
children,
|
||||
}: PropsWithChildren<{
|
||||
className?: string;
|
||||
}>): ReactElement => {
|
||||
return (
|
||||
<AppContainer className={className}>
|
||||
<ShellAppSidebarFallback />
|
||||
<MainContainerFallback>{children}</MainContainerFallback>
|
||||
</AppContainer>
|
||||
);
|
||||
};
|
@ -1,16 +1,15 @@
|
||||
import { Tooltip } from '@affine/component/ui/tooltip';
|
||||
import { useCatchEventCallback } from '@affine/core/components/hooks/use-catch-event-hook';
|
||||
import { GlobalDialogService } from '@affine/core/modules/dialogs';
|
||||
import { SubscriptionPlan } from '@affine/graphql';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { useLiveData, useServices } from '@toeverything/infra';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useLiveData, useService, useServices } from '@toeverything/infra';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import {
|
||||
ServerConfigService,
|
||||
SubscriptionService,
|
||||
} from '../../../modules/cloud';
|
||||
import { openSettingModalAtom } from '../../atoms';
|
||||
import * as styles from './style.css';
|
||||
|
||||
export const UserPlanButton = () => {
|
||||
@ -35,14 +34,13 @@ export const UserPlanButton = () => {
|
||||
subscriptionService.subscription.revalidate();
|
||||
}, [subscriptionService]);
|
||||
|
||||
const setSettingModalAtom = useSetAtom(openSettingModalAtom);
|
||||
const globalDialogService = useService(GlobalDialogService);
|
||||
const handleClick = useCatchEventCallback(() => {
|
||||
setSettingModalAtom({
|
||||
open: true,
|
||||
globalDialogService.open('setting', {
|
||||
activeTab: 'plans',
|
||||
scrollAnchor: 'cloudPricingPlan',
|
||||
});
|
||||
}, [setSettingModalAtom]);
|
||||
}, [globalDialogService]);
|
||||
|
||||
const t = useI18n();
|
||||
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||
import { CollectionService } from '@affine/core/modules/collection';
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { AllDocsIcon, FilterIcon } from '@blocksuite/icons/rc';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useEditCollection } from '../../page-list';
|
||||
import { ActionButton } from './action-button';
|
||||
import collectionDetailDark from './assets/collection-detail.dark.png';
|
||||
import collectionDetailLight from './assets/collection-detail.light.png';
|
||||
@ -41,18 +40,21 @@ export const EmptyCollectionDetail = ({
|
||||
|
||||
const Actions = ({ collection }: { collection: Collection }) => {
|
||||
const t = useI18n();
|
||||
const collectionService = useService(CollectionService);
|
||||
const { open } = useEditCollection();
|
||||
const workspaceDialogService = useService(WorkspaceDialogService);
|
||||
|
||||
const openAddDocs = useAsyncCallback(async () => {
|
||||
const ret = await open({ ...collection }, 'page');
|
||||
collectionService.updateCollection(ret.id, () => ret);
|
||||
}, [open, collection, collectionService]);
|
||||
const openAddDocs = useCallback(() => {
|
||||
workspaceDialogService.open('collection-editor', {
|
||||
collectionId: collection.id,
|
||||
mode: 'page',
|
||||
});
|
||||
}, [collection, workspaceDialogService]);
|
||||
|
||||
const openAddRules = useAsyncCallback(async () => {
|
||||
const ret = await open({ ...collection }, 'rule');
|
||||
collectionService.updateCollection(ret.id, () => ret);
|
||||
}, [collection, open, collectionService]);
|
||||
const openAddRules = useCallback(() => {
|
||||
workspaceDialogService.open('collection-editor', {
|
||||
collectionId: collection.id,
|
||||
mode: 'rule',
|
||||
});
|
||||
}, [collection, workspaceDialogService]);
|
||||
|
||||
return (
|
||||
<div className={actionGroup}>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { usePromptModal } from '@affine/component';
|
||||
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
|
||||
import { CollectionService } from '@affine/core/modules/collection';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
@ -6,7 +7,7 @@ import { useService, WorkspaceService } from '@toeverything/infra';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { createEmptyCollection, useEditCollectionName } from '../../page-list';
|
||||
import { createEmptyCollection } from '../../page-list';
|
||||
import { ActionButton } from './action-button';
|
||||
import collectionListDark from './assets/collection-list.dark.png';
|
||||
import collectionListLight from './assets/collection-list.light.png';
|
||||
@ -19,24 +20,36 @@ export const EmptyCollections = (props: UniversalEmptyProps) => {
|
||||
const currentWorkspace = useService(WorkspaceService).workspace;
|
||||
|
||||
const navigateHelper = useNavigateHelper();
|
||||
const { open } = useEditCollectionName({
|
||||
title: t['com.affine.editCollection.createCollection'](),
|
||||
showTips: true,
|
||||
});
|
||||
const { openPromptModal } = usePromptModal();
|
||||
|
||||
const showAction = true;
|
||||
|
||||
const handleCreateCollection = useCallback(() => {
|
||||
open('')
|
||||
.then(name => {
|
||||
openPromptModal({
|
||||
title: t['com.affine.editCollection.saveCollection'](),
|
||||
label: t['com.affine.editCollectionName.name'](),
|
||||
inputOptions: {
|
||||
placeholder: t['com.affine.editCollectionName.name.placeholder'](),
|
||||
},
|
||||
children: t['com.affine.editCollectionName.createTips'](),
|
||||
confirmText: t['com.affine.editCollection.save'](),
|
||||
cancelText: t['com.affine.editCollection.button.cancel'](),
|
||||
confirmButtonOptions: {
|
||||
variant: 'primary',
|
||||
},
|
||||
onConfirm(name) {
|
||||
const id = nanoid();
|
||||
collectionService.addCollection(createEmptyCollection(id, { name }));
|
||||
navigateHelper.jumpToCollection(currentWorkspace.id, id);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}, [collectionService, currentWorkspace, navigateHelper, open]);
|
||||
},
|
||||
});
|
||||
}, [
|
||||
collectionService,
|
||||
currentWorkspace.id,
|
||||
navigateHelper,
|
||||
openPromptModal,
|
||||
t,
|
||||
]);
|
||||
|
||||
return (
|
||||
<EmptyLayout
|
||||
|
@ -1,10 +0,0 @@
|
||||
import { HelpIsland } from '../../pure/help-island';
|
||||
import { ToolContainer } from '../../workspace';
|
||||
|
||||
export const HubIsland = () => {
|
||||
return (
|
||||
<ToolContainer>
|
||||
<HelpIsland />
|
||||
</ToolContainer>
|
||||
);
|
||||
};
|
@ -2,8 +2,8 @@ import { Loading, Scrollable } from '@affine/component';
|
||||
import { EditorLoading } from '@affine/component/page-detail-skeleton';
|
||||
import { Button, IconButton } from '@affine/component/ui/button';
|
||||
import { Modal, useConfirmModal } from '@affine/component/ui/modal';
|
||||
import { openSettingModalAtom } from '@affine/core/components/atoms';
|
||||
import { useDocCollectionPageTitle } from '@affine/core/components/hooks/use-block-suite-workspace-page-title';
|
||||
import { GlobalDialogService } from '@affine/core/modules/dialogs';
|
||||
import { EditorService } from '@affine/core/modules/editor';
|
||||
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
||||
import { WorkspaceQuotaService } from '@affine/core/modules/quota';
|
||||
@ -18,7 +18,7 @@ import { CloseIcon, ToggleCollapseIcon } from '@blocksuite/icons/rc';
|
||||
import * as Collapsible from '@radix-ui/react-collapsible';
|
||||
import type { DialogContentProps } from '@radix-ui/react-dialog';
|
||||
import { useLiveData, useService, WorkspaceService } from '@toeverything/infra';
|
||||
import { atom, useAtom, useSetAtom } from 'jotai';
|
||||
import { atom, useAtom } from 'jotai';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import {
|
||||
Fragment,
|
||||
@ -188,21 +188,19 @@ const PlanPrompt = () => {
|
||||
permissionService.permission.revalidate();
|
||||
}, [permissionService]);
|
||||
|
||||
const setSettingModalAtom = useSetAtom(openSettingModalAtom);
|
||||
const [planPromptClosed, setPlanPromptClosed] = useAtom(planPromptClosedAtom);
|
||||
|
||||
const globalDialogService = useService(GlobalDialogService);
|
||||
const closeFreePlanPrompt = useCallback(() => {
|
||||
setPlanPromptClosed(true);
|
||||
}, [setPlanPromptClosed]);
|
||||
|
||||
const onClickUpgrade = useCallback(() => {
|
||||
setSettingModalAtom({
|
||||
open: true,
|
||||
globalDialogService.open('setting', {
|
||||
activeTab: 'plans',
|
||||
scrollAnchor: 'cloudPricingPlan',
|
||||
});
|
||||
track.$.docHistory.$.viewPlans();
|
||||
}, [setSettingModalAtom]);
|
||||
}, [globalDialogService]);
|
||||
|
||||
const t = useI18n();
|
||||
|
||||
|
@ -1,16 +1,14 @@
|
||||
import { ConfirmModal } from '@affine/component/ui/modal';
|
||||
import {
|
||||
openQuotaModalAtom,
|
||||
openSettingModalAtom,
|
||||
} from '@affine/core/components/atoms';
|
||||
import { openQuotaModalAtom } from '@affine/core/components/atoms';
|
||||
import { UserQuotaService } from '@affine/core/modules/cloud';
|
||||
import { GlobalDialogService } from '@affine/core/modules/dialogs';
|
||||
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
||||
import { WorkspaceQuotaService } from '@affine/core/modules/quota';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { track } from '@affine/track';
|
||||
import { useLiveData, useService, WorkspaceService } from '@toeverything/infra';
|
||||
import bytes from 'bytes';
|
||||
import { useAtom, useSetAtom } from 'jotai';
|
||||
import { useAtom } from 'jotai';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
|
||||
export const CloudQuotaModal = () => {
|
||||
@ -45,17 +43,16 @@ export const CloudQuotaModal = () => {
|
||||
return isOwner && userQuota?.name === 'free';
|
||||
}, [isOwner, userQuota]);
|
||||
|
||||
const setSettingModalAtom = useSetAtom(openSettingModalAtom);
|
||||
const globalDialogService = useService(GlobalDialogService);
|
||||
const handleUpgradeConfirm = useCallback(() => {
|
||||
setSettingModalAtom({
|
||||
open: true,
|
||||
globalDialogService.open('setting', {
|
||||
activeTab: 'plans',
|
||||
scrollAnchor: 'cloudPricingPlan',
|
||||
});
|
||||
|
||||
track.$.paywall.storage.viewPlans();
|
||||
setOpen(false);
|
||||
}, [setOpen, setSettingModalAtom]);
|
||||
}, [globalDialogService, setOpen]);
|
||||
|
||||
const description = useMemo(() => {
|
||||
if (userQuota && isFreePlanOwner) {
|
||||
|
@ -1,3 +0,0 @@
|
||||
import { atom } from 'jotai';
|
||||
|
||||
export const settingModalScrollContainerAtom = atom<HTMLElement | null>(null);
|
@ -1,20 +0,0 @@
|
||||
export const GeneralSettingKeys = [
|
||||
'shortcuts',
|
||||
'appearance',
|
||||
'about',
|
||||
'plans',
|
||||
'billing',
|
||||
'experimental-features',
|
||||
'editor',
|
||||
] as const;
|
||||
|
||||
export const WorkspaceSubTabs = ['preference', 'properties'] as const;
|
||||
|
||||
export type GeneralSettingKey = (typeof GeneralSettingKeys)[number];
|
||||
|
||||
export type WorkspaceSubTab = (typeof WorkspaceSubTabs)[number];
|
||||
|
||||
export type ActiveTab =
|
||||
| GeneralSettingKey
|
||||
| 'account'
|
||||
| `workspace:${WorkspaceSubTab}`;
|
@ -1,22 +0,0 @@
|
||||
import type { WorkspaceMetadata } from '@toeverything/infra';
|
||||
|
||||
import type { WorkspaceSubTab } from '../types';
|
||||
import { WorkspaceSettingDetail } from './new-workspace-setting-detail';
|
||||
import { WorkspaceSettingProperties } from './properties';
|
||||
|
||||
export const WorkspaceSetting = ({
|
||||
workspaceMetadata,
|
||||
subTab,
|
||||
}: {
|
||||
workspaceMetadata: WorkspaceMetadata;
|
||||
subTab: WorkspaceSubTab;
|
||||
}) => {
|
||||
switch (subTab) {
|
||||
case 'preference':
|
||||
return <WorkspaceSettingDetail workspaceMetadata={workspaceMetadata} />;
|
||||
case 'properties':
|
||||
return (
|
||||
<WorkspaceSettingProperties workspaceMetadata={workspaceMetadata} />
|
||||
);
|
||||
}
|
||||
};
|
@ -1,13 +1,13 @@
|
||||
import { notify, Skeleton } from '@affine/component';
|
||||
import { Button } from '@affine/component/ui/button';
|
||||
import { Menu, MenuItem, MenuTrigger } from '@affine/component/ui/menu';
|
||||
import { openSettingModalAtom } from '@affine/core/components/atoms';
|
||||
import {
|
||||
getSelectedNodes,
|
||||
useSharingUrl,
|
||||
} from '@affine/core/components/hooks/affine/use-share-url';
|
||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||
import { ServerConfigService } from '@affine/core/modules/cloud';
|
||||
import { GlobalDialogService } from '@affine/core/modules/dialogs';
|
||||
import { EditorService } from '@affine/core/modules/editor';
|
||||
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
||||
import { ShareInfoService } from '@affine/core/modules/share-doc';
|
||||
@ -28,7 +28,6 @@ import {
|
||||
} from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { Suspense, useCallback, useEffect, useMemo } from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
|
||||
@ -83,15 +82,14 @@ export const AFFiNESharePage = (props: ShareMenuProps) => {
|
||||
|
||||
const permissionService = useService(WorkspacePermissionService);
|
||||
const isOwner = useLiveData(permissionService.permission.isOwner$);
|
||||
const setSettingModalAtom = useSetAtom(openSettingModalAtom);
|
||||
const globalDialogService = useService(GlobalDialogService);
|
||||
|
||||
const onOpenWorkspaceSettings = useCallback(() => {
|
||||
setSettingModalAtom({
|
||||
open: true,
|
||||
globalDialogService.open('setting', {
|
||||
activeTab: 'workspace:preference',
|
||||
workspaceMetadata: props.workspaceMetadata,
|
||||
});
|
||||
}, [props.workspaceMetadata, setSettingModalAtom]);
|
||||
}, [globalDialogService, props.workspaceMetadata]);
|
||||
|
||||
const onClickAnyoneReadOnlyShare = useAsyncCallback(async () => {
|
||||
if (isSharedPage) {
|
||||
|
@ -49,47 +49,6 @@ const SubscriptionChangedNotifyFooter = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const useUpgradeNotify = () => {
|
||||
const t = useI18n();
|
||||
const prevNotifyIdRef = useRef<string | number | null>(null);
|
||||
|
||||
return useCallback(
|
||||
(link: string) => {
|
||||
prevNotifyIdRef.current && notify.dismiss(prevNotifyIdRef.current);
|
||||
const id = notify(
|
||||
{
|
||||
title: (
|
||||
<span className={notifyHeader}>
|
||||
{t['com.affine.payment.upgrade-success-notify.title']()}
|
||||
</span>
|
||||
),
|
||||
message: t['com.affine.payment.upgrade-success-notify.content'](),
|
||||
alignMessage: 'title',
|
||||
icon: null,
|
||||
footer: (
|
||||
<SubscriptionChangedNotifyFooter
|
||||
to={link}
|
||||
okText={
|
||||
BUILD_CONFIG.isElectron
|
||||
? t['com.affine.payment.upgrade-success-notify.ok-client']()
|
||||
: t['com.affine.payment.upgrade-success-notify.ok-web']()
|
||||
}
|
||||
cancelText={t[
|
||||
'com.affine.payment.upgrade-success-notify.later'
|
||||
]()}
|
||||
onCancel={() => notify.dismiss(id)}
|
||||
onConfirm={() => notify.dismiss(id)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{ duration: 24 * 60 * 60 * 1000 }
|
||||
);
|
||||
prevNotifyIdRef.current = id;
|
||||
},
|
||||
[t]
|
||||
);
|
||||
};
|
||||
|
||||
export const useDowngradeNotify = () => {
|
||||
const t = useI18n();
|
||||
const prevNotifyIdRef = useRef<string | number | null>(null);
|
||||
|
@ -1,40 +1,13 @@
|
||||
import { atom } from 'jotai';
|
||||
|
||||
import type { SettingProps } from '../affine/setting-modal';
|
||||
import type { ActiveTab } from '../affine/setting-modal/types';
|
||||
// modal atoms
|
||||
export const openWorkspacesModalAtom = atom(false);
|
||||
/**
|
||||
* @deprecated use `useSignOut` hook instated
|
||||
*/
|
||||
export const openSignOutModalAtom = atom(false);
|
||||
export const openQuotaModalAtom = atom(false);
|
||||
export const openStarAFFiNEModalAtom = atom(false);
|
||||
export const openIssueFeedbackModalAtom = atom(false);
|
||||
export const openHistoryTipsModalAtom = atom(false);
|
||||
|
||||
export const rightSidebarWidthAtom = atom(320);
|
||||
|
||||
export type PlansScrollAnchor =
|
||||
| 'aiPricingPlan'
|
||||
| 'cloudPricingPlan'
|
||||
| 'lifetimePricingPlan';
|
||||
export type SettingAtom = {
|
||||
open: boolean;
|
||||
workspaceMetadata?: SettingProps['workspaceMetadata'];
|
||||
} & (
|
||||
| {
|
||||
activeTab: 'plans';
|
||||
scrollAnchor?: PlansScrollAnchor;
|
||||
}
|
||||
| { activeTab: Exclude<ActiveTab, 'plans'> }
|
||||
);
|
||||
|
||||
export const openSettingModalAtom = atom<SettingAtom>({
|
||||
activeTab: 'appearance',
|
||||
open: false,
|
||||
});
|
||||
|
||||
export const openImportModalAtom = atom(false);
|
||||
|
||||
export type AuthAtomData =
|
||||
|
@ -1,12 +1,11 @@
|
||||
import { AIProvider } from '@affine/core/blocksuite/presets/ai';
|
||||
import { toggleGeneralAIOnboarding } from '@affine/core/components/affine/ai-onboarding/apis';
|
||||
import { authAtom, openSettingModalAtom } from '@affine/core/components/atoms';
|
||||
import { authAtom } from '@affine/core/components/atoms';
|
||||
import {
|
||||
getBaseUrl,
|
||||
type getCopilotHistoriesQuery,
|
||||
type RequestOptions,
|
||||
} from '@affine/graphql';
|
||||
import { track } from '@affine/track';
|
||||
import { UnauthorizedError } from '@blocksuite/affine/blocks';
|
||||
import { assertExists } from '@blocksuite/affine/global/utils';
|
||||
import { getCurrentStore } from '@toeverything/infra';
|
||||
@ -463,14 +462,6 @@ Could you make a new website based on these notes and send back just the html fi
|
||||
return forkCopilotSession(options);
|
||||
});
|
||||
|
||||
AIProvider.slots.requestUpgradePlan.on(() => {
|
||||
getCurrentStore().set(openSettingModalAtom, {
|
||||
activeTab: 'billing',
|
||||
open: true,
|
||||
});
|
||||
track.$.paywall.aiAction.viewPlans();
|
||||
});
|
||||
|
||||
AIProvider.slots.requestLogin.on(() => {
|
||||
getCurrentStore().set(authAtom, s => ({
|
||||
...s,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { IconButton } from '@affine/component';
|
||||
import { DocInfoService } from '@affine/core/modules/doc-info';
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { track } from '@affine/track';
|
||||
import { InformationIcon } from '@blocksuite/icons/rc';
|
||||
@ -7,13 +7,13 @@ import { useService } from '@toeverything/infra';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const InfoButton = ({ docId }: { docId: string }) => {
|
||||
const modal = useService(DocInfoService).modal;
|
||||
const workspaceDialogService = useService(WorkspaceDialogService);
|
||||
const t = useI18n();
|
||||
|
||||
const onOpenInfoModal = useCallback(() => {
|
||||
track.$.header.actions.openDocInfo();
|
||||
modal.open(docId);
|
||||
}, [docId, modal]);
|
||||
workspaceDialogService.open('doc-info', { docId });
|
||||
}, [docId, workspaceDialogService]);
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
|
@ -1,17 +1,20 @@
|
||||
import { OverlayModal } from '@affine/component';
|
||||
import { openHistoryTipsModalAtom } from '@affine/core/components/atoms';
|
||||
import { useEnableCloud } from '@affine/core/components/hooks/affine/use-enable-cloud';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { useService, WorkspaceService } from '@toeverything/infra';
|
||||
import { useAtom } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import TopSvg from './top-svg';
|
||||
|
||||
export const HistoryTipsModal = () => {
|
||||
export const HistoryTipsModal = ({
|
||||
open,
|
||||
setOpen,
|
||||
}: {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
const currentWorkspace = useService(WorkspaceService).workspace;
|
||||
const [open, setOpen] = useAtom(openHistoryTipsModalAtom);
|
||||
const confirmEnableCloud = useEnableCloud();
|
||||
|
||||
const handleConfirm = useCallback(() => {
|
@ -1,4 +1,4 @@
|
||||
import { notify } from '@affine/component';
|
||||
import { notify, useConfirmModal } from '@affine/component';
|
||||
import {
|
||||
Menu,
|
||||
MenuItem,
|
||||
@ -7,14 +7,9 @@ import {
|
||||
} from '@affine/component/ui/menu';
|
||||
import { PageHistoryModal } from '@affine/core/components/affine/page-history-modal';
|
||||
import { ShareMenuContent } from '@affine/core/components/affine/share-page-modal/share-menu';
|
||||
import {
|
||||
openHistoryTipsModalAtom,
|
||||
openImportModalAtom,
|
||||
} from '@affine/core/components/atoms';
|
||||
import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/use-block-suite-meta-helper';
|
||||
import { useEnableCloud } from '@affine/core/components/hooks/affine/use-enable-cloud';
|
||||
import { useExportPage } from '@affine/core/components/hooks/affine/use-export-page';
|
||||
import { useTrashModalHelper } from '@affine/core/components/hooks/affine/use-trash-modal-helper';
|
||||
import { useDocMetaHelper } from '@affine/core/components/hooks/use-block-suite-page-meta';
|
||||
import {
|
||||
Export,
|
||||
@ -23,7 +18,10 @@ import {
|
||||
} from '@affine/core/components/page-list';
|
||||
import { IsFavoriteIcon } from '@affine/core/components/pure/icons';
|
||||
import { useDetailPageHeaderResponsive } from '@affine/core/desktop/pages/workspace/detail-page/use-header-responsive';
|
||||
import { DocInfoService } from '@affine/core/modules/doc-info';
|
||||
import {
|
||||
GlobalDialogService,
|
||||
WorkspaceDialogService,
|
||||
} from '@affine/core/modules/dialogs';
|
||||
import { EditorService } from '@affine/core/modules/editor';
|
||||
import { OpenInAppService } from '@affine/core/modules/open-in-app/services';
|
||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||
@ -55,11 +53,11 @@ import {
|
||||
useServiceOptional,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { HeaderDropDownButton } from '../../../pure/header-drop-down-button';
|
||||
import { useFavorite } from '../favorite';
|
||||
import { HistoryTipsModal } from './history-tips-modal';
|
||||
|
||||
type PageMenuProps = {
|
||||
rename?: () => void;
|
||||
@ -81,6 +79,7 @@ export const PageHeaderMenuButton = ({
|
||||
|
||||
const workspace = useService(WorkspaceService).workspace;
|
||||
|
||||
const globalDialogService = useService(GlobalDialogService);
|
||||
const editorService = useService(EditorService);
|
||||
const isInTrash = useLiveData(
|
||||
editorService.editor.doc.meta$.map(meta => meta.trash)
|
||||
@ -98,7 +97,6 @@ export const PageHeaderMenuButton = ({
|
||||
const { favorite, toggleFavorite } = useFavorite(pageId);
|
||||
|
||||
const { duplicate } = useBlockSuiteMetaHelper();
|
||||
const { setTrashModal } = useTrashModalHelper();
|
||||
|
||||
const [isEditing, setEditing] = useState(!page.readonly);
|
||||
const { setDocReadonly } = useDocMetaHelper();
|
||||
@ -122,8 +120,7 @@ export const PageHeaderMenuButton = ({
|
||||
}, [openSidePanel]);
|
||||
|
||||
const [historyModalOpen, setHistoryModalOpen] = useState(false);
|
||||
const setOpenHistoryTipsModal = useSetAtom(openHistoryTipsModalAtom);
|
||||
const setOpenImportModalAtom = useSetAtom(openImportModalAtom);
|
||||
const [openHistoryTipsModal, setOpenHistoryTipsModal] = useState(false);
|
||||
|
||||
const openHistoryModal = useCallback(() => {
|
||||
track.$.header.history.open();
|
||||
@ -133,11 +130,11 @@ export const PageHeaderMenuButton = ({
|
||||
return setOpenHistoryTipsModal(true);
|
||||
}, [setOpenHistoryTipsModal, workspace.flavour]);
|
||||
|
||||
const docInfoModal = useService(DocInfoService).modal;
|
||||
const workspaceDialogService = useService(WorkspaceDialogService);
|
||||
const openInfoModal = useCallback(() => {
|
||||
track.$.header.pageInfo.open();
|
||||
docInfoModal.open(pageId);
|
||||
}, [docInfoModal, pageId]);
|
||||
workspaceDialogService.open('doc-info', { docId: pageId });
|
||||
}, [workspaceDialogService, pageId]);
|
||||
|
||||
const handleOpenInNewTab = useCallback(() => {
|
||||
workbench.openDoc(pageId, {
|
||||
@ -151,14 +148,22 @@ export const PageHeaderMenuButton = ({
|
||||
});
|
||||
}, [pageId, workbench]);
|
||||
|
||||
const { openConfirmModal } = useConfirmModal();
|
||||
|
||||
const handleOpenTrashModal = useCallback(() => {
|
||||
track.$.header.docOptions.deleteDoc();
|
||||
setTrashModal({
|
||||
open: true,
|
||||
pageIds: [pageId],
|
||||
pageTitles: [editorService.editor.doc.meta$.value.title ?? ''],
|
||||
openConfirmModal({
|
||||
title: t['com.affine.moveToTrash.confirmModal.title'](),
|
||||
description: t['com.affine.moveToTrash.confirmModal.description']({
|
||||
title: editorService.editor.doc.title$.value || t['Untitled'](),
|
||||
}),
|
||||
cancelText: t['com.affine.confirmModal.button.cancel'](),
|
||||
confirmText: t.Delete(),
|
||||
onConfirm: () => {
|
||||
editorService.editor.doc.moveToTrash();
|
||||
},
|
||||
});
|
||||
}, [editorService, pageId, setTrashModal]);
|
||||
}, [editorService.editor.doc, openConfirmModal, t]);
|
||||
|
||||
const handleRename = useCallback(() => {
|
||||
rename?.();
|
||||
@ -201,8 +206,8 @@ export const PageHeaderMenuButton = ({
|
||||
|
||||
const handleOpenImportModal = useCallback(() => {
|
||||
track.$.header.importModal.open();
|
||||
setOpenImportModalAtom(true);
|
||||
}, [setOpenImportModalAtom]);
|
||||
globalDialogService.open('import', undefined);
|
||||
}, [globalDialogService]);
|
||||
|
||||
const handleShareMenuOpenChange = useCallback((open: boolean) => {
|
||||
if (open) {
|
||||
@ -411,6 +416,10 @@ export const PageHeaderMenuButton = ({
|
||||
onOpenChange={setHistoryModalOpen}
|
||||
/>
|
||||
) : null}
|
||||
<HistoryTipsModal
|
||||
open={openHistoryTipsModal}
|
||||
setOpen={setOpenHistoryTipsModal}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ConfirmModalProvider } from '@affine/component';
|
||||
import { ConfirmModalProvider, PromptModalProvider } from '@affine/component';
|
||||
import { ProviderComposer } from '@affine/component/provider-composer';
|
||||
import { ThemeProvider } from '@affine/core/components/theme-provider';
|
||||
import type { createStore } from 'jotai';
|
||||
@ -19,6 +19,7 @@ export function AffineContext(props: AffineContextProps) {
|
||||
<Provider key="JotaiProvider" store={props.store} />,
|
||||
<ThemeProvider key="ThemeProvider" />,
|
||||
<ConfirmModalProvider key="ConfirmModalProvider" />,
|
||||
<PromptModalProvider key="PromptModalProvider" />,
|
||||
].filter(Boolean),
|
||||
[props.store]
|
||||
)}
|
||||
|
@ -1,2 +1 @@
|
||||
export * from './info-modal/info-modal';
|
||||
export * from './table';
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { toast } from '@affine/component';
|
||||
import { toast, useConfirmModal } from '@affine/component';
|
||||
import {
|
||||
PreconditionStrategy,
|
||||
registerAffineCommand,
|
||||
} from '@affine/core/commands';
|
||||
import { DocInfoService } from '@affine/core/modules/doc-info';
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import type { Editor } from '@affine/core/modules/editor';
|
||||
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
@ -22,7 +22,6 @@ import { useCallback, useEffect } from 'react';
|
||||
import { pageHistoryModalAtom } from '../../../components/atoms/page-history';
|
||||
import { useBlockSuiteMetaHelper } from './use-block-suite-meta-helper';
|
||||
import { useExportPage } from './use-export-page';
|
||||
import { useTrashModalHelper } from './use-trash-modal-helper';
|
||||
|
||||
export function useRegisterBlocksuiteEditorCommands(editor: Editor) {
|
||||
const doc = useService(DocService).doc;
|
||||
@ -36,7 +35,7 @@ export function useRegisterBlocksuiteEditorCommands(editor: Editor) {
|
||||
const trash = useLiveData(doc.trash$);
|
||||
|
||||
const setPageHistoryModalState = useSetAtom(pageHistoryModalAtom);
|
||||
const docInfoModal = useService(DocInfoService).modal;
|
||||
const workspaceDialogService = useService(WorkspaceDialogService);
|
||||
|
||||
const openHistoryModal = useCallback(() => {
|
||||
setPageHistoryModalState(() => ({
|
||||
@ -46,22 +45,25 @@ export function useRegisterBlocksuiteEditorCommands(editor: Editor) {
|
||||
}, [docId, setPageHistoryModalState]);
|
||||
|
||||
const openInfoModal = useCallback(() => {
|
||||
docInfoModal.open(docId);
|
||||
}, [docId, docInfoModal]);
|
||||
workspaceDialogService.open('doc-info', { docId });
|
||||
}, [docId, workspaceDialogService]);
|
||||
|
||||
const { duplicate } = useBlockSuiteMetaHelper();
|
||||
const exportHandler = useExportPage();
|
||||
const { setTrashModal } = useTrashModalHelper();
|
||||
const onClickDelete = useCallback(
|
||||
(title: string) => {
|
||||
setTrashModal({
|
||||
open: true,
|
||||
pageIds: [docId],
|
||||
pageTitles: [title],
|
||||
});
|
||||
},
|
||||
[docId, setTrashModal]
|
||||
);
|
||||
const { openConfirmModal } = useConfirmModal();
|
||||
const onClickDelete = useCallback(() => {
|
||||
openConfirmModal({
|
||||
title: t['com.affine.moveToTrash.confirmModal.title'](),
|
||||
description: t['com.affine.moveToTrash.confirmModal.description']({
|
||||
title: doc.title$.value || t['Untitled'](),
|
||||
}),
|
||||
cancelText: t['com.affine.confirmModal.button.cancel'](),
|
||||
confirmText: t.Delete(),
|
||||
onConfirm: () => {
|
||||
doc.moveToTrash();
|
||||
},
|
||||
});
|
||||
}, [doc, openConfirmModal, t]);
|
||||
|
||||
const isCloudWorkspace = workspace.flavour === WorkspaceFlavour.AFFINE_CLOUD;
|
||||
|
||||
@ -174,23 +176,6 @@ export function useRegisterBlocksuiteEditorCommands(editor: Editor) {
|
||||
})
|
||||
);
|
||||
|
||||
unsubs.push(
|
||||
registerAffineCommand({
|
||||
id: `editor:${mode}-export-to-pdf`,
|
||||
preconditionStrategy: () => mode === 'page' && !trash,
|
||||
category: `editor:${mode}`,
|
||||
icon: mode === 'page' ? <PageIcon /> : <EdgelessIcon />,
|
||||
label: t['Export to PDF'](),
|
||||
async run() {
|
||||
track.$.cmdk.editor.export({
|
||||
type: 'pdf',
|
||||
});
|
||||
|
||||
exportHandler('pdf');
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
unsubs.push(
|
||||
registerAffineCommand({
|
||||
id: `editor:${mode}-export-to-html`,
|
||||
@ -252,7 +237,7 @@ export function useRegisterBlocksuiteEditorCommands(editor: Editor) {
|
||||
run() {
|
||||
track.$.cmdk.editor.deleteDoc();
|
||||
|
||||
onClickDelete(doc.title$.value);
|
||||
onClickDelete();
|
||||
},
|
||||
})
|
||||
);
|
||||
|
@ -1,16 +1,10 @@
|
||||
import { useUpgradeNotify } from '@affine/core/components/affine/subscription-landing/notify';
|
||||
import { SubscriptionPlan, SubscriptionRecurring } from '@affine/graphql';
|
||||
import { track } from '@affine/track';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
import { type AuthAccountInfo } from '../../../modules/cloud';
|
||||
|
||||
const separator = '::';
|
||||
const recoverSeparator = nanoid();
|
||||
const localStorageKey = 'subscription-succeed-info';
|
||||
|
||||
const typeFormUrl = 'https://6dxre9ihosp.typeform.com/to';
|
||||
const typeFormUpgradeId = 'mUMGGQS8';
|
||||
const typeFormDowngradeId = 'RvD9AoRg';
|
||||
@ -69,80 +63,3 @@ export const generateSubscriptionCallbackLink = (
|
||||
|
||||
return `${baseUrl}?info=${encodeURIComponent(query)}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse subscription callback query.info
|
||||
* @returns
|
||||
*/
|
||||
export const parseSubscriptionCallbackLink = (query: string) => {
|
||||
const [plan, recurring, id, email, rawName] =
|
||||
decodeURIComponent(query).split(separator);
|
||||
const name = rawName.replaceAll(recoverSeparator, separator);
|
||||
|
||||
return {
|
||||
plan: plan as SubscriptionPlan,
|
||||
recurring: recurring as SubscriptionRecurring,
|
||||
account: {
|
||||
id,
|
||||
email,
|
||||
info: {
|
||||
name,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to parse subscription callback link, and save to local storage and delete the query
|
||||
*/
|
||||
export const useSubscriptionNotifyWriter = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
useEffect(() => {
|
||||
const query = searchParams.get('info');
|
||||
if (query) {
|
||||
localStorage.setItem(localStorageKey, query);
|
||||
searchParams.delete('info');
|
||||
}
|
||||
}, [searchParams]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to read and parse subscription info from localStorage
|
||||
*/
|
||||
export const useSubscriptionNotifyReader = () => {
|
||||
const upgradeNotify = useUpgradeNotify();
|
||||
|
||||
const readAndNotify = useCallback(() => {
|
||||
const query = localStorage.getItem(localStorageKey);
|
||||
if (!query) return;
|
||||
|
||||
try {
|
||||
const { plan, recurring, account } = parseSubscriptionCallbackLink(query);
|
||||
const link = getUpgradeQuestionnaireLink({
|
||||
id: account.id,
|
||||
email: account.email,
|
||||
name: account.info?.name ?? '',
|
||||
plan,
|
||||
recurring,
|
||||
});
|
||||
upgradeNotify(link);
|
||||
localStorage.removeItem(localStorageKey);
|
||||
|
||||
track.$.settingsPanel.plans.subscribe({
|
||||
plan,
|
||||
recurring,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Failed to parse subscription callback link', err);
|
||||
}
|
||||
}, [upgradeNotify]);
|
||||
|
||||
useEffect(() => {
|
||||
readAndNotify();
|
||||
window.addEventListener('focus', readAndNotify);
|
||||
return () => {
|
||||
window.removeEventListener('focus', readAndNotify);
|
||||
};
|
||||
}, [readAndNotify]);
|
||||
};
|
||||
|
@ -1,27 +0,0 @@
|
||||
import { toast } from '@affine/component';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { useAtom } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { trashModalAtom } from '../../../components/atoms/trash-modal';
|
||||
import { useBlockSuiteMetaHelper } from './use-block-suite-meta-helper';
|
||||
|
||||
export function useTrashModalHelper() {
|
||||
const t = useI18n();
|
||||
const [trashModal, setTrashModal] = useAtom(trashModalAtom);
|
||||
const { pageIds } = trashModal;
|
||||
const { removeToTrash } = useBlockSuiteMetaHelper();
|
||||
const handleOnConfirm = useCallback(() => {
|
||||
pageIds.forEach(pageId => {
|
||||
removeToTrash(pageId);
|
||||
});
|
||||
toast(t['com.affine.toastMessage.movedTrash']());
|
||||
setTrashModal({ ...trashModal, open: false });
|
||||
}, [pageIds, removeToTrash, setTrashModal, t, trashModal]);
|
||||
|
||||
return {
|
||||
trashModal,
|
||||
setTrashModal,
|
||||
handleOnConfirm,
|
||||
};
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import { AppSidebarService } from '@affine/core/modules/app-sidebar';
|
||||
import { DesktopApiService } from '@affine/core/modules/desktop-api/service';
|
||||
import { DesktopApiService } from '@affine/core/modules/desktop-api';
|
||||
import { GlobalDialogService } from '@affine/core/modules/dialogs';
|
||||
import { I18nService } from '@affine/core/modules/i18n';
|
||||
import { UrlService } from '@affine/core/modules/url';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
@ -25,7 +26,6 @@ import {
|
||||
registerAffineUpdatesCommands,
|
||||
} from '../../commands';
|
||||
import { usePageHelper } from '../../components/blocksuite/block-suite-page-list/utils';
|
||||
import { CreateWorkspaceDialogService } from '../../modules/create-workspace';
|
||||
import { EditorSettingService } from '../../modules/editor-setting';
|
||||
import { CMDKQuickSearchService } from '../../modules/quicksearch/services/cmdk';
|
||||
import { useActiveBlocksuiteEditor } from './use-block-suite-editor';
|
||||
@ -78,7 +78,7 @@ export function useRegisterWorkspaceCommands() {
|
||||
const [editor] = useActiveBlocksuiteEditor();
|
||||
const cmdkQuickSearchService = useService(CMDKQuickSearchService);
|
||||
const editorSettingService = useService(EditorSettingService);
|
||||
const createWorkspaceDialogService = useService(CreateWorkspaceDialogService);
|
||||
const globalDialogService = useService(GlobalDialogService);
|
||||
const appSidebarService = useService(AppSidebarService);
|
||||
const i18n = useService(I18nService).i18n;
|
||||
|
||||
@ -117,12 +117,19 @@ export function useRegisterWorkspaceCommands() {
|
||||
t,
|
||||
docCollection: currentWorkspace.docCollection,
|
||||
navigationHelper,
|
||||
globalDialogService,
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsub();
|
||||
};
|
||||
}, [store, t, currentWorkspace.docCollection, navigationHelper]);
|
||||
}, [
|
||||
store,
|
||||
t,
|
||||
currentWorkspace.docCollection,
|
||||
navigationHelper,
|
||||
globalDialogService,
|
||||
]);
|
||||
|
||||
// register AffineSettingsCommands
|
||||
useEffect(() => {
|
||||
@ -162,7 +169,7 @@ export function useRegisterWorkspaceCommands() {
|
||||
// register AffineCreationCommands
|
||||
useEffect(() => {
|
||||
const unsub = registerAffineCreationCommands({
|
||||
createWorkspaceDialogService,
|
||||
globalDialogService,
|
||||
pageHelper: pageHelper,
|
||||
t,
|
||||
});
|
||||
@ -170,18 +177,18 @@ export function useRegisterWorkspaceCommands() {
|
||||
return () => {
|
||||
unsub();
|
||||
};
|
||||
}, [store, pageHelper, t, createWorkspaceDialogService]);
|
||||
}, [store, pageHelper, t, globalDialogService]);
|
||||
|
||||
// register AffineHelpCommands
|
||||
useEffect(() => {
|
||||
const unsub = registerAffineHelpCommands({
|
||||
store,
|
||||
t,
|
||||
urlService,
|
||||
globalDialogService,
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsub();
|
||||
};
|
||||
}, [store, t, urlService]);
|
||||
}, [t, globalDialogService, urlService]);
|
||||
}
|
||||
|
@ -1,250 +0,0 @@
|
||||
import { toast } from '@affine/component';
|
||||
import {
|
||||
pushGlobalLoadingEventAtom,
|
||||
resolveGlobalLoadingEventAtom,
|
||||
} from '@affine/component/global-loading';
|
||||
import {
|
||||
OpenInAppCard,
|
||||
SidebarSwitch,
|
||||
} from '@affine/core/modules/app-sidebar/views';
|
||||
import { WorkspaceDesktopApiService } from '@affine/core/modules/desktop-api/service';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { type DocMode, ZipTransformer } from '@blocksuite/affine/blocks';
|
||||
import {
|
||||
DocsService,
|
||||
effect,
|
||||
fromPromise,
|
||||
LiveData,
|
||||
onStart,
|
||||
throwIfAborted,
|
||||
useLiveData,
|
||||
useService,
|
||||
useServices,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import {
|
||||
catchError,
|
||||
EMPTY,
|
||||
finalize,
|
||||
mergeMap,
|
||||
switchMap,
|
||||
timeout,
|
||||
} from 'rxjs';
|
||||
import { Map as YMap } from 'yjs';
|
||||
|
||||
import { AIProvider } from '../../blocksuite/presets/ai';
|
||||
import { AppTabsHeader } from '../../modules/app-tabs-header';
|
||||
import { EditorSettingService } from '../../modules/editor-setting';
|
||||
import { NavigationButtons } from '../../modules/navigation';
|
||||
import { useRegisterNavigationCommands } from '../../modules/navigation/view/use-register-navigation-commands';
|
||||
import { QuickSearchContainer } from '../../modules/quicksearch';
|
||||
import { WorkbenchService } from '../../modules/workbench';
|
||||
import { WorkspaceAIOnboarding } from '../affine/ai-onboarding';
|
||||
import { AppContainer } from '../affine/app-container';
|
||||
import { SyncAwareness } from '../affine/awareness';
|
||||
import { useRegisterFindInPageCommands } from '../hooks/affine/use-register-find-in-page-commands';
|
||||
import { useSubscriptionNotifyReader } from '../hooks/affine/use-subscription-notify';
|
||||
import { useRegisterWorkspaceCommands } from '../hooks/use-register-workspace-commands';
|
||||
import { OverCapacityNotification } from '../over-capacity';
|
||||
import { CurrentWorkspaceModals } from '../providers/modal-provider';
|
||||
import { SWRConfigProvider } from '../providers/swr-config-provider';
|
||||
import { AIIsland } from '../pure/ai-island';
|
||||
import { RootAppSidebar } from '../root-app-sidebar';
|
||||
import { MainContainer } from '../workspace';
|
||||
import { WorkspaceUpgrade } from '../workspace-upgrade';
|
||||
import * as styles from './styles.css';
|
||||
|
||||
export const WorkspaceLayout = function WorkspaceLayout({
|
||||
children,
|
||||
}: PropsWithChildren) {
|
||||
return (
|
||||
<SWRConfigProvider>
|
||||
{/* load all workspaces is costly, do not block the whole UI */}
|
||||
<CurrentWorkspaceModals />
|
||||
<WorkspaceLayoutInner>{children}</WorkspaceLayoutInner>
|
||||
{/* should show after workspace loaded */}
|
||||
<WorkspaceAIOnboarding />
|
||||
<AIIsland />
|
||||
</SWRConfigProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const WorkspaceLayoutProviders = ({ children }: PropsWithChildren) => {
|
||||
const t = useI18n();
|
||||
const pushGlobalLoadingEvent = useSetAtom(pushGlobalLoadingEventAtom);
|
||||
const resolveGlobalLoadingEvent = useSetAtom(resolveGlobalLoadingEventAtom);
|
||||
const { workspaceService, docsService } = useServices({
|
||||
WorkspaceService,
|
||||
DocsService,
|
||||
EditorSettingService,
|
||||
});
|
||||
const currentWorkspace = workspaceService.workspace;
|
||||
const docsList = docsService.list;
|
||||
|
||||
const workbench = useService(WorkbenchService).workbench;
|
||||
useEffect(() => {
|
||||
const insertTemplate = effect(
|
||||
switchMap(({ template, mode }: { template: string; mode: string }) => {
|
||||
return fromPromise(async abort => {
|
||||
const templateZip = await fetch(template, { signal: abort });
|
||||
const templateBlob = await templateZip.blob();
|
||||
throwIfAborted(abort);
|
||||
const [doc] = await ZipTransformer.importDocs(
|
||||
currentWorkspace.docCollection,
|
||||
templateBlob
|
||||
);
|
||||
if (doc) {
|
||||
doc.resetHistory();
|
||||
}
|
||||
|
||||
return { doc, mode };
|
||||
}).pipe(
|
||||
timeout(10000 /* 10s */),
|
||||
mergeMap(({ mode, doc }) => {
|
||||
if (doc) {
|
||||
docsList.setPrimaryMode(doc.id, mode as DocMode);
|
||||
workbench.openDoc(doc.id);
|
||||
}
|
||||
return EMPTY;
|
||||
}),
|
||||
onStart(() => {
|
||||
pushGlobalLoadingEvent({
|
||||
key: 'insert-template',
|
||||
});
|
||||
}),
|
||||
catchError(err => {
|
||||
console.error(err);
|
||||
toast(t['com.affine.ai.template-insert.failed']());
|
||||
return EMPTY;
|
||||
}),
|
||||
finalize(() => {
|
||||
resolveGlobalLoadingEvent('insert-template');
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
const disposable = AIProvider.slots.requestInsertTemplate.on(
|
||||
({ template, mode }) => {
|
||||
insertTemplate({ template, mode });
|
||||
}
|
||||
);
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
insertTemplate.unsubscribe();
|
||||
};
|
||||
}, [
|
||||
currentWorkspace.docCollection,
|
||||
docsList,
|
||||
pushGlobalLoadingEvent,
|
||||
resolveGlobalLoadingEvent,
|
||||
t,
|
||||
workbench,
|
||||
]);
|
||||
|
||||
useSubscriptionNotifyReader();
|
||||
useRegisterWorkspaceCommands();
|
||||
useRegisterNavigationCommands();
|
||||
useRegisterFindInPageCommands();
|
||||
|
||||
useEffect(() => {
|
||||
// hotfix for blockVersions
|
||||
// this is a mistake in the
|
||||
// 0.8.0 ~ 0.8.1
|
||||
// 0.8.0-beta.0 ~ 0.8.0-beta.3
|
||||
// 0.8.0-canary.17 ~ 0.9.0-canary.3
|
||||
const meta = currentWorkspace.docCollection.doc.getMap('meta');
|
||||
const blockVersions = meta.get('blockVersions');
|
||||
if (
|
||||
!(blockVersions instanceof YMap) &&
|
||||
blockVersions !== null &&
|
||||
blockVersions !== undefined &&
|
||||
typeof blockVersions === 'object'
|
||||
) {
|
||||
meta.set(
|
||||
'blockVersions',
|
||||
new YMap(Object.entries(blockVersions as Record<string, number>))
|
||||
);
|
||||
}
|
||||
}, [currentWorkspace.docCollection.doc]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* This DndContext is used for drag page from all-pages list into a folder in sidebar */}
|
||||
{children}
|
||||
<QuickSearchContainer />
|
||||
<SyncAwareness />
|
||||
<OverCapacityNotification />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const DesktopLayout = ({ children }: PropsWithChildren) => {
|
||||
// is there a better way to make sure service is always available even if it's not explicitly used?
|
||||
useService(WorkspaceDesktopApiService);
|
||||
return (
|
||||
<div className={styles.desktopAppViewContainer}>
|
||||
<div className={styles.desktopTabsHeader}>
|
||||
<AppTabsHeader
|
||||
left={
|
||||
<>
|
||||
<SidebarSwitch show />
|
||||
<NavigationButtons />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.desktopAppViewMain}>
|
||||
<RootAppSidebar />
|
||||
<MainContainer>{children}</MainContainer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const BrowserLayout = ({ children }: PropsWithChildren) => {
|
||||
return (
|
||||
<div className={styles.browserAppViewContainer}>
|
||||
<OpenInAppCard />
|
||||
<RootAppSidebar />
|
||||
<MainContainer>{children}</MainContainer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const LayoutComponent = BUILD_CONFIG.isElectron ? DesktopLayout : BrowserLayout;
|
||||
|
||||
/**
|
||||
* Wraps the workspace layout main router view
|
||||
*/
|
||||
const WorkspaceLayoutUIContainer = ({ children }: PropsWithChildren) => {
|
||||
const workbench = useService(WorkbenchService).workbench;
|
||||
const currentPath = useLiveData(
|
||||
LiveData.computed(get => {
|
||||
return get(workbench.basename$) + get(workbench.location$).pathname;
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<AppContainer data-current-path={currentPath}>
|
||||
<LayoutComponent>{children}</LayoutComponent>
|
||||
</AppContainer>
|
||||
);
|
||||
};
|
||||
export const WorkspaceLayoutInner = ({ children }: PropsWithChildren) => {
|
||||
const workspace = useService(WorkspaceService).workspace;
|
||||
|
||||
const upgrading = useLiveData(workspace.upgrade.upgrading$);
|
||||
const needUpgrade = useLiveData(workspace.upgrade.needUpgrade$);
|
||||
|
||||
return (
|
||||
<WorkspaceLayoutProviders>
|
||||
<WorkspaceLayoutUIContainer>
|
||||
{needUpgrade || upgrading ? <WorkspaceUpgrade /> : children}
|
||||
</WorkspaceLayoutUIContainer>
|
||||
</WorkspaceLayoutProviders>
|
||||
);
|
||||
};
|
@ -1,9 +1,8 @@
|
||||
import { notify } from '@affine/component';
|
||||
import { openSettingModalAtom } from '@affine/core/components/atoms';
|
||||
import { GlobalDialogService } from '@affine/core/modules/dialogs';
|
||||
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { useLiveData, useService, WorkspaceService } from '@toeverything/infra';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
@ -20,14 +19,13 @@ export const OverCapacityNotification = () => {
|
||||
permissionService.permission.revalidate();
|
||||
}, [permissionService]);
|
||||
|
||||
const setSettingModalAtom = useSetAtom(openSettingModalAtom);
|
||||
const globalDialogService = useService(GlobalDialogService);
|
||||
const jumpToPricePlan = useCallback(() => {
|
||||
setSettingModalAtom({
|
||||
open: true,
|
||||
globalDialogService.open('setting', {
|
||||
activeTab: 'plans',
|
||||
scrollAnchor: 'cloudPricingPlan',
|
||||
});
|
||||
}, [setSettingModalAtom]);
|
||||
}, [globalDialogService]);
|
||||
|
||||
// debounce sync engine status
|
||||
useEffect(() => {
|
||||
|
@ -1,4 +1,3 @@
|
||||
export * from './collection-list-header';
|
||||
export * from './collection-list-item';
|
||||
export * from './select-collection';
|
||||
export * from './virtualized-collection-list';
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
} from '@affine/component';
|
||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import type { Tag } from '@affine/core/modules/tag';
|
||||
import { TagService } from '@affine/core/modules/tag';
|
||||
import { isNewTabTrigger } from '@affine/core/utils';
|
||||
@ -28,18 +29,13 @@ import {
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { CollectionService } from '../../../modules/collection';
|
||||
import { usePageHelper } from '../../blocksuite/block-suite-page-list/utils';
|
||||
import { createTagFilter } from '../filter/utils';
|
||||
import { createEmptyCollection } from '../use-collection-manager';
|
||||
import {
|
||||
useEditCollection,
|
||||
useEditCollectionName,
|
||||
} from '../view/use-edit-collection';
|
||||
import { SaveAsCollectionButton } from '../view';
|
||||
import * as styles from './page-list-header.css';
|
||||
import { PageListNewPageButton } from './page-list-new-page-button';
|
||||
|
||||
@ -102,21 +98,22 @@ export const CollectionPageListHeader = ({
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
const { jumpToCollections } = useNavigateHelper();
|
||||
const { collectionService, workspaceService } = useServices({
|
||||
CollectionService,
|
||||
WorkspaceService,
|
||||
});
|
||||
const { collectionService, workspaceService, workspaceDialogService } =
|
||||
useServices({
|
||||
CollectionService,
|
||||
WorkspaceService,
|
||||
WorkspaceDialogService,
|
||||
});
|
||||
|
||||
const handleJumpToCollections = useCallback(() => {
|
||||
jumpToCollections(workspaceId);
|
||||
}, [jumpToCollections, workspaceId]);
|
||||
|
||||
const { open } = useEditCollection();
|
||||
|
||||
const handleEdit = useAsyncCallback(async () => {
|
||||
const ret = await open({ ...collection }, 'page');
|
||||
collectionService.updateCollection(collection.id, () => ret);
|
||||
}, [collection, collectionService, open]);
|
||||
const handleEdit = useCallback(() => {
|
||||
workspaceDialogService.open('collection-editor', {
|
||||
collectionId: collection.id,
|
||||
});
|
||||
}, [collection, workspaceDialogService]);
|
||||
|
||||
const workspace = workspaceService.workspace;
|
||||
const { createEdgeless, createPage } = usePageHelper(workspace.docCollection);
|
||||
@ -203,10 +200,6 @@ export const TagPageListHeader = ({
|
||||
const { jumpToTags, jumpToCollection } = useNavigateHelper();
|
||||
const collectionService = useService(CollectionService);
|
||||
const [openMenu, setOpenMenu] = useState(false);
|
||||
const { open } = useEditCollectionName({
|
||||
title: t['com.affine.editCollection.saveCollection'](),
|
||||
showTips: true,
|
||||
});
|
||||
|
||||
const handleJumpToTags = useCallback(() => {
|
||||
jumpToTags(workspaceId);
|
||||
@ -222,15 +215,6 @@ export const TagPageListHeader = ({
|
||||
},
|
||||
[collectionService, tag.id, jumpToCollection, workspaceId]
|
||||
);
|
||||
const handleClick = useCallback(() => {
|
||||
open('')
|
||||
.then(name => {
|
||||
return saveToCollection(createEmptyCollection(nanoid(), { name }));
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}, [open, saveToCollection]);
|
||||
|
||||
return (
|
||||
<div className={styles.docListHeader}>
|
||||
@ -267,9 +251,7 @@ export const TagPageListHeader = ({
|
||||
</div>
|
||||
</Menu>
|
||||
</div>
|
||||
<Button onClick={handleClick}>
|
||||
{t['com.affine.editCollection.saveCollection']()}
|
||||
</Button>
|
||||
<SaveAsCollectionButton onConfirm={saveToCollection} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,26 @@
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const pagesTab = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
});
|
||||
export const pagesTabContent = style({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
gap: 8,
|
||||
alignItems: 'center',
|
||||
padding: '16px 16px 8px 16px',
|
||||
});
|
||||
|
||||
export const pageList = style({
|
||||
width: '100%',
|
||||
});
|
||||
|
||||
export const ellipsis = style({
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
});
|
@ -13,18 +13,16 @@ import {
|
||||
} from '@toeverything/infra';
|
||||
import { type ReactNode, useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { FavoriteTag } from '../../components/favorite-tag';
|
||||
import { FilterList } from '../../filter';
|
||||
import { VariableSelect } from '../../filter/vars';
|
||||
import { usePageHeaderColsDef } from '../../header-col-def';
|
||||
import { PageListItemRenderer } from '../../page-group';
|
||||
import { ListTableHeader } from '../../page-header';
|
||||
import type { BaseSelectorDialogProps } from '../../selector';
|
||||
import { SelectorLayout } from '../../selector/selector-layout';
|
||||
import type { ListItem } from '../../types';
|
||||
import { VirtualizedList } from '../../virtualized-list';
|
||||
import { AffineShapeIcon } from '../affine-shape';
|
||||
import * as styles from './edit-collection.css';
|
||||
import { AffineShapeIcon, FavoriteTag } from '..';
|
||||
import { FilterList } from '../filter';
|
||||
import { VariableSelect } from '../filter/vars';
|
||||
import { usePageHeaderColsDef } from '../header-col-def';
|
||||
import { PageListItemRenderer } from '../page-group';
|
||||
import { ListTableHeader } from '../page-header';
|
||||
import { SelectorLayout } from '../selector/selector-layout';
|
||||
import type { ListItem } from '../types';
|
||||
import { VirtualizedList } from '../virtualized-list';
|
||||
import * as styles from './select-page.css';
|
||||
import { useFilter } from './use-filter';
|
||||
import { useSearch } from './use-search';
|
||||
|
||||
@ -40,7 +38,10 @@ export const SelectPage = ({
|
||||
confirmText?: ReactNode;
|
||||
header?: ReactNode;
|
||||
buttons?: ReactNode;
|
||||
} & BaseSelectorDialogProps<string[]>) => {
|
||||
init?: string[];
|
||||
onConfirm?: (data: string[]) => void;
|
||||
onCancel?: () => void;
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
const [value, setValue] = useState(init);
|
||||
const onChange = useCallback(
|
@ -2,8 +2,10 @@ import type { Filter } from '@affine/env/filter';
|
||||
import type { MouseEvent } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import type { PageDataForFilter } from '../../use-collection-manager';
|
||||
import { filterPageByRules } from '../../use-collection-manager';
|
||||
import {
|
||||
filterPageByRules,
|
||||
type PageDataForFilter,
|
||||
} from '../use-collection-manager';
|
||||
|
||||
export const useFilter = (list: PageDataForFilter[]) => {
|
||||
const [filters, changeFilters] = useState<Filter[]>([]);
|
@ -1,12 +1,11 @@
|
||||
import { toast } from '@affine/component';
|
||||
import { useTrashModalHelper } from '@affine/core/components/hooks/affine/use-trash-modal-helper';
|
||||
import { toast, useConfirmModal } from '@affine/component';
|
||||
import { useBlockSuiteDocMeta } from '@affine/core/components/hooks/use-block-suite-page-meta';
|
||||
import { CollectionService } from '@affine/core/modules/collection';
|
||||
import type { Tag } from '@affine/core/modules/tag';
|
||||
import type { Collection, Filter } from '@affine/env/filter';
|
||||
import { Trans, useI18n } from '@affine/i18n';
|
||||
import type { DocMeta } from '@blocksuite/affine/store';
|
||||
import { useService, WorkspaceService } from '@toeverything/infra';
|
||||
import { DocsService, useService, WorkspaceService } from '@toeverything/infra';
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { ListFloatingToolbar } from '../components/list-floating-toolbar';
|
||||
@ -62,10 +61,12 @@ export const VirtualizedPageList = ({
|
||||
listItem?: DocMeta[];
|
||||
setHideHeaderCreateNewPage?: (hide: boolean) => void;
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
const listRef = useRef<ItemListHandle>(null);
|
||||
const [showFloatingToolbar, setShowFloatingToolbar] = useState(false);
|
||||
const [selectedPageIds, setSelectedPageIds] = useState<string[]>([]);
|
||||
const currentWorkspace = useService(WorkspaceService).workspace;
|
||||
const docsService = useService(DocsService);
|
||||
const pageMetas = useBlockSuiteDocMeta(currentWorkspace.docCollection);
|
||||
const pageOperations = usePageOperationsRenderer();
|
||||
const pageHeaderColsDef = usePageHeaderColsDef();
|
||||
@ -122,26 +123,39 @@ export const VirtualizedPageList = ({
|
||||
return <PageListHeader />;
|
||||
}, [collection, currentWorkspace.id, tag]);
|
||||
|
||||
const { setTrashModal } = useTrashModalHelper();
|
||||
const { openConfirmModal } = useConfirmModal();
|
||||
|
||||
const handleMultiDelete = useCallback(() => {
|
||||
if (filteredSelectedPageIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
const pageNameMapping = Object.fromEntries(
|
||||
pageMetas.map(meta => [meta.id, meta.title])
|
||||
);
|
||||
|
||||
const pageNames = filteredSelectedPageIds.map(
|
||||
id => pageNameMapping[id] ?? ''
|
||||
);
|
||||
setTrashModal({
|
||||
open: true,
|
||||
pageIds: filteredSelectedPageIds,
|
||||
pageTitles: pageNames,
|
||||
openConfirmModal({
|
||||
title: t['com.affine.moveToTrash.confirmModal.title.multiple']({
|
||||
number: filteredSelectedPageIds.length.toString(),
|
||||
}),
|
||||
description: t[
|
||||
'com.affine.moveToTrash.confirmModal.description.multiple'
|
||||
]({
|
||||
number: filteredSelectedPageIds.length.toString(),
|
||||
}),
|
||||
cancelText: t['com.affine.confirmModal.button.cancel'](),
|
||||
confirmText: t.Delete(),
|
||||
onConfirm: () => {
|
||||
for (const docId of filteredSelectedPageIds) {
|
||||
const doc = docsService.list.doc$(docId).value;
|
||||
doc?.moveToTrash();
|
||||
}
|
||||
},
|
||||
});
|
||||
hideFloatingToolbar();
|
||||
}, [filteredSelectedPageIds, hideFloatingToolbar, pageMetas, setTrashModal]);
|
||||
}, [
|
||||
docsService.list,
|
||||
filteredSelectedPageIds,
|
||||
hideFloatingToolbar,
|
||||
openConfirmModal,
|
||||
t,
|
||||
]);
|
||||
|
||||
const group = usePageItemGroupDefinitions();
|
||||
|
||||
|
@ -4,11 +4,11 @@ import {
|
||||
MenuItem,
|
||||
toast,
|
||||
useConfirmModal,
|
||||
usePromptModal,
|
||||
} from '@affine/component';
|
||||
import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/use-block-suite-meta-helper';
|
||||
import { useTrashModalHelper } from '@affine/core/components/hooks/affine/use-trash-modal-helper';
|
||||
import { useCatchEventCallback } from '@affine/core/components/hooks/use-catch-event-hook';
|
||||
import { DocInfoService } from '@affine/core/modules/doc-info';
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import {
|
||||
CompatibleFavoriteItemsAdapter,
|
||||
FavoriteService,
|
||||
@ -33,6 +33,7 @@ import {
|
||||
SplitViewIcon,
|
||||
} from '@blocksuite/icons/rc';
|
||||
import {
|
||||
DocsService,
|
||||
FeatureFlagService,
|
||||
useLiveData,
|
||||
useService,
|
||||
@ -51,7 +52,6 @@ import { DisablePublicSharing, MoveToTrash } from './operation-menu-items';
|
||||
import { CreateOrEditTag } from './tags/create-tag';
|
||||
import type { TagMeta } from './types';
|
||||
import { ColWrapper } from './utils';
|
||||
import { useEditCollection, useEditCollectionName } from './view';
|
||||
|
||||
const tooltipSideTop = { side: 'top' as const };
|
||||
const tooltipSideTopAlignEnd = { side: 'top' as const, align: 'end' as const };
|
||||
@ -83,17 +83,19 @@ export const PageOperationCell = ({
|
||||
featureFlagService.flags.enable_multi_view.$
|
||||
);
|
||||
const currentWorkspace = workspaceService.workspace;
|
||||
const { setTrashModal } = useTrashModalHelper();
|
||||
const favourite = useLiveData(favAdapter.isFavorite$(page.id, 'doc'));
|
||||
const workbench = workbenchService.workbench;
|
||||
const { duplicate } = useBlockSuiteMetaHelper();
|
||||
const docRecord = useLiveData(useService(DocsService).list.doc$(page.id));
|
||||
const blocksuiteDoc = currentWorkspace.docCollection.getDoc(page.id);
|
||||
|
||||
const docInfoModal = useService(DocInfoService).modal;
|
||||
const workspaceDialogService = useService(WorkspaceDialogService);
|
||||
const onOpenInfoModal = useCallback(() => {
|
||||
track.$.docInfoPanel.$.open();
|
||||
docInfoModal.open(blocksuiteDoc?.id);
|
||||
}, [blocksuiteDoc?.id, docInfoModal]);
|
||||
if (blocksuiteDoc?.id) {
|
||||
track.$.docInfoPanel.$.open();
|
||||
workspaceDialogService.open('doc-info', { docId: blocksuiteDoc.id });
|
||||
}
|
||||
}, [blocksuiteDoc?.id, workspaceDialogService]);
|
||||
|
||||
const onDisablePublicSharing = useCallback(() => {
|
||||
// TODO(@EYHN): implement disable public sharing
|
||||
@ -102,15 +104,26 @@ export const PageOperationCell = ({
|
||||
});
|
||||
}, []);
|
||||
|
||||
const { openConfirmModal } = useConfirmModal();
|
||||
|
||||
const onRemoveToTrash = useCallback(() => {
|
||||
if (!docRecord) {
|
||||
return;
|
||||
}
|
||||
track.allDocs.list.docMenu.deleteDoc();
|
||||
|
||||
setTrashModal({
|
||||
open: true,
|
||||
pageIds: [page.id],
|
||||
pageTitles: [page.title],
|
||||
openConfirmModal({
|
||||
title: t['com.affine.moveToTrash.confirmModal.title'](),
|
||||
description: t['com.affine.moveToTrash.confirmModal.description']({
|
||||
title: docRecord.title$.value || t['Untitled'](),
|
||||
}),
|
||||
cancelText: t['com.affine.confirmModal.button.cancel'](),
|
||||
confirmText: t.Delete(),
|
||||
onConfirm: () => {
|
||||
docRecord.moveToTrash();
|
||||
},
|
||||
});
|
||||
}, [page.id, page.title, setTrashModal]);
|
||||
}, [docRecord, openConfirmModal, t]);
|
||||
|
||||
const onOpenInSplitView = useCallback(() => {
|
||||
track.allDocs.list.docMenu.openInSplitView();
|
||||
@ -297,11 +310,15 @@ export const CollectionOperationCell = ({
|
||||
info,
|
||||
}: CollectionOperationCellProps) => {
|
||||
const t = useI18n();
|
||||
const { compatibleFavoriteItemsAdapter: favAdapter, workspaceService } =
|
||||
useServices({
|
||||
CompatibleFavoriteItemsAdapter,
|
||||
WorkspaceService,
|
||||
});
|
||||
const {
|
||||
compatibleFavoriteItemsAdapter: favAdapter,
|
||||
workspaceService,
|
||||
workspaceDialogService,
|
||||
} = useServices({
|
||||
CompatibleFavoriteItemsAdapter,
|
||||
WorkspaceService,
|
||||
WorkspaceDialogService,
|
||||
});
|
||||
const docCollection = workspaceService.workspace.docCollection;
|
||||
const { createPage } = usePageHelper(docCollection);
|
||||
const { openConfirmModal } = useConfirmModal();
|
||||
@ -309,11 +326,7 @@ export const CollectionOperationCell = ({
|
||||
favAdapter.isFavorite$(collection.id, 'collection')
|
||||
);
|
||||
|
||||
const { open: openEditCollectionModal } = useEditCollection();
|
||||
|
||||
const { open: openEditCollectionNameModal } = useEditCollectionName({
|
||||
title: t['com.affine.editCollection.renameCollection'](),
|
||||
});
|
||||
const { openPromptModal } = usePromptModal();
|
||||
|
||||
const handlePropagation = useCallback((event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
@ -323,39 +336,36 @@ export const CollectionOperationCell = ({
|
||||
const handleEditName = useCallback(
|
||||
(event: MouseEvent) => {
|
||||
handlePropagation(event);
|
||||
// use openRenameModal if it is in the sidebar collection list
|
||||
openEditCollectionNameModal(collection.name)
|
||||
.then(name => {
|
||||
return service.updateCollection(collection.id, collection => ({
|
||||
openPromptModal({
|
||||
title: t['com.affine.editCollection.renameCollection'](),
|
||||
label: t['com.affine.editCollectionName.name'](),
|
||||
inputOptions: {
|
||||
placeholder: t['com.affine.editCollectionName.name.placeholder'](),
|
||||
},
|
||||
confirmText: t['com.affine.editCollection.save'](),
|
||||
cancelText: t['com.affine.editCollection.button.cancel'](),
|
||||
confirmButtonOptions: {
|
||||
variant: 'primary',
|
||||
},
|
||||
onConfirm(name) {
|
||||
service.updateCollection(collection.id, () => ({
|
||||
...collection,
|
||||
name,
|
||||
}));
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
[
|
||||
collection.id,
|
||||
collection.name,
|
||||
handlePropagation,
|
||||
openEditCollectionNameModal,
|
||||
service,
|
||||
]
|
||||
[collection, handlePropagation, openPromptModal, service, t]
|
||||
);
|
||||
|
||||
const handleEdit = useCallback(
|
||||
(event: MouseEvent) => {
|
||||
handlePropagation(event);
|
||||
openEditCollectionModal(collection)
|
||||
.then(collection => {
|
||||
return service.updateCollection(collection.id, () => collection);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
workspaceDialogService.open('collection-editor', {
|
||||
collectionId: collection.id,
|
||||
});
|
||||
},
|
||||
[handlePropagation, openEditCollectionModal, collection, service]
|
||||
[handlePropagation, workspaceDialogService, collection.id]
|
||||
);
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
|
@ -1,27 +0,0 @@
|
||||
import { SelectCollection } from '../collections';
|
||||
import { SelectTag } from '../tags';
|
||||
import { SelectPage } from '../view/edit-collection/select-page';
|
||||
import { useSelectDialog } from './use-select-dialog';
|
||||
|
||||
export * from './use-select-dialog';
|
||||
|
||||
/**
|
||||
* Return a `open` function to open the select collection dialog.
|
||||
*/
|
||||
export const useSelectCollection = () => {
|
||||
return useSelectDialog(SelectCollection, 'select-collection');
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a `open` function to open the select page dialog.
|
||||
*/
|
||||
export const useSelectDoc = () => {
|
||||
return useSelectDialog(SelectPage, 'select-doc-dialog');
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a `open` function to open the select tag dialog.
|
||||
*/
|
||||
export const useSelectTag = () => {
|
||||
return useSelectDialog(SelectTag, 'select-tag-dialog');
|
||||
};
|
@ -1,102 +0,0 @@
|
||||
import { Modal, type ModalProps } from '@affine/component';
|
||||
import { useMount } from '@toeverything/infra';
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
export interface BaseSelectorDialogProps<T> {
|
||||
init?: T;
|
||||
onConfirm?: (data: T) => void;
|
||||
onCancel?: () => void;
|
||||
}
|
||||
|
||||
const defaultModalProps: Partial<Omit<ModalProps, 'children'>> = {};
|
||||
export const useSelectDialog = function useSelectDialog<T, P>(
|
||||
Component: React.FC<BaseSelectorDialogProps<T> & P>,
|
||||
debugKey?: string,
|
||||
options?: {
|
||||
modalProps?: Partial<Omit<ModalProps, 'children'>>;
|
||||
}
|
||||
) {
|
||||
// to control whether the dialog is open, it's not equal to !!value
|
||||
// when closing the dialog, show will be `false` first, then after the animation, value turns to `undefined`
|
||||
const [show, setShow] = useState(false);
|
||||
const [value, setValue] = useState<{
|
||||
init?: T;
|
||||
onConfirm: (v: T) => void;
|
||||
}>();
|
||||
const [additionalProps, setAdditionalProps] = useState<P>();
|
||||
|
||||
const onOpenChanged = useCallback((open: boolean) => {
|
||||
if (!open) setValue(undefined);
|
||||
setShow(open);
|
||||
}, []);
|
||||
|
||||
const close = useCallback(() => setShow(false), []);
|
||||
|
||||
/**
|
||||
* Open a dialog to select items
|
||||
*/
|
||||
const open = useCallback(
|
||||
(ids?: T, additionalProps?: P) => {
|
||||
return new Promise<T>(resolve => {
|
||||
setShow(true);
|
||||
setAdditionalProps(additionalProps);
|
||||
setValue({
|
||||
init: ids,
|
||||
onConfirm: list => {
|
||||
close();
|
||||
resolve(list);
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
[close]
|
||||
);
|
||||
|
||||
const { mount } = useMount(debugKey);
|
||||
|
||||
useEffect(() => {
|
||||
const { contentOptions, ...otherModalProps } =
|
||||
options?.modalProps ?? defaultModalProps;
|
||||
|
||||
return mount(
|
||||
<Modal
|
||||
open={show}
|
||||
onOpenChange={onOpenChanged}
|
||||
withoutCloseButton
|
||||
width="calc(100% - 32px)"
|
||||
height="80%"
|
||||
contentOptions={{
|
||||
style: {
|
||||
padding: 0,
|
||||
maxWidth: 976,
|
||||
background: cssVar('backgroundPrimaryColor'),
|
||||
},
|
||||
...contentOptions,
|
||||
}}
|
||||
{...otherModalProps}
|
||||
>
|
||||
{value ? (
|
||||
<Component
|
||||
init={value.init}
|
||||
onCancel={close}
|
||||
onConfirm={value.onConfirm}
|
||||
{...(additionalProps as any)}
|
||||
/>
|
||||
) : null}
|
||||
</Modal>
|
||||
);
|
||||
}, [
|
||||
Component,
|
||||
additionalProps,
|
||||
close,
|
||||
debugKey,
|
||||
mount,
|
||||
onOpenChanged,
|
||||
options?.modalProps,
|
||||
show,
|
||||
value,
|
||||
]);
|
||||
|
||||
return open;
|
||||
};
|
@ -1,4 +1,3 @@
|
||||
export * from './select-tag';
|
||||
export * from './tag-list-header';
|
||||
export * from './tag-list-item';
|
||||
export * from './virtualized-tag-list';
|
||||
|
@ -1,6 +1,7 @@
|
||||
import type { MenuItemProps } from '@affine/component';
|
||||
import { Menu, MenuItem } from '@affine/component';
|
||||
import { Menu, MenuItem, usePromptModal } from '@affine/component';
|
||||
import { useDeleteCollectionInfo } from '@affine/core/components/hooks/affine/use-delete-collection-info';
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite';
|
||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
@ -25,10 +26,6 @@ import { useCallback, useMemo } from 'react';
|
||||
import { CollectionService } from '../../../modules/collection';
|
||||
import { IsFavoriteIcon } from '../../pure/icons';
|
||||
import * as styles from './collection-operations.css';
|
||||
import {
|
||||
useEditCollection,
|
||||
useEditCollectionName,
|
||||
} from './use-edit-collection';
|
||||
|
||||
export const CollectionOperations = ({
|
||||
collection,
|
||||
@ -44,18 +41,17 @@ export const CollectionOperations = ({
|
||||
collectionService: service,
|
||||
workbenchService,
|
||||
featureFlagService,
|
||||
workspaceDialogService,
|
||||
} = useServices({
|
||||
CollectionService,
|
||||
WorkbenchService,
|
||||
FeatureFlagService,
|
||||
WorkspaceDialogService,
|
||||
});
|
||||
const deleteInfo = useDeleteCollectionInfo();
|
||||
const workbench = workbenchService.workbench;
|
||||
const { open: openEditCollectionModal } = useEditCollection();
|
||||
const t = useI18n();
|
||||
const { open: openEditCollectionNameModal } = useEditCollectionName({
|
||||
title: t['com.affine.editCollection.renameCollection'](),
|
||||
});
|
||||
const { openPromptModal } = usePromptModal();
|
||||
const enableMultiView = useLiveData(
|
||||
featureFlagService.flags.enable_multi_view.$
|
||||
);
|
||||
@ -65,27 +61,31 @@ export const CollectionOperations = ({
|
||||
if (openRenameModal) {
|
||||
return openRenameModal();
|
||||
}
|
||||
openEditCollectionNameModal(collection.name)
|
||||
.then(name => {
|
||||
return service.updateCollection(collection.id, () => ({
|
||||
openPromptModal({
|
||||
title: t['com.affine.editCollection.renameCollection'](),
|
||||
label: t['com.affine.editCollectionName.name'](),
|
||||
inputOptions: {
|
||||
placeholder: t['com.affine.editCollectionName.name.placeholder'](),
|
||||
},
|
||||
confirmText: t['com.affine.editCollection.save'](),
|
||||
cancelText: t['com.affine.editCollection.button.cancel'](),
|
||||
confirmButtonOptions: {
|
||||
variant: 'primary',
|
||||
},
|
||||
onConfirm(name) {
|
||||
service.updateCollection(collection.id, () => ({
|
||||
...collection,
|
||||
name,
|
||||
}));
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}, [openRenameModal, openEditCollectionNameModal, collection, service]);
|
||||
},
|
||||
});
|
||||
}, [openRenameModal, openPromptModal, t, service, collection]);
|
||||
|
||||
const showEdit = useCallback(() => {
|
||||
openEditCollectionModal(collection)
|
||||
.then(collection => {
|
||||
return service.updateCollection(collection.id, () => collection);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}, [openEditCollectionModal, collection, service]);
|
||||
workspaceDialogService.open('collection-editor', {
|
||||
collectionId: collection.id,
|
||||
});
|
||||
}, [workspaceDialogService, collection.id]);
|
||||
|
||||
const openCollectionSplitView = useCallback(() => {
|
||||
workbench.openCollection(collection.id, { at: 'tail' });
|
||||
|
@ -2,6 +2,4 @@ export * from './affine-shape';
|
||||
export * from './collection-list';
|
||||
export * from './collection-operations';
|
||||
export * from './create-collection';
|
||||
export * from './edit-collection/edit-collection';
|
||||
export * from './save-as-collection-button';
|
||||
export * from './use-edit-collection';
|
||||
|
@ -8,3 +8,8 @@ export const button = style({
|
||||
fontWeight: 500,
|
||||
height: '28px',
|
||||
});
|
||||
export const createTips = style({
|
||||
color: cssVar('textSecondaryColor'),
|
||||
fontSize: 12,
|
||||
lineHeight: '20px',
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Button } from '@affine/component';
|
||||
import { Button, usePromptModal } from '@affine/component';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { SaveIcon } from '@blocksuite/icons/rc';
|
||||
@ -7,7 +7,6 @@ import { useCallback } from 'react';
|
||||
|
||||
import { createEmptyCollection } from '../use-collection-manager';
|
||||
import * as styles from './save-as-collection-button.css';
|
||||
import { useEditCollectionName } from './use-edit-collection';
|
||||
|
||||
interface SaveAsCollectionButtonProps {
|
||||
onConfirm: (collection: Collection) => void;
|
||||
@ -17,19 +16,29 @@ export const SaveAsCollectionButton = ({
|
||||
onConfirm,
|
||||
}: SaveAsCollectionButtonProps) => {
|
||||
const t = useI18n();
|
||||
const { open } = useEditCollectionName({
|
||||
title: t['com.affine.editCollection.saveCollection'](),
|
||||
showTips: true,
|
||||
});
|
||||
const { openPromptModal } = usePromptModal();
|
||||
const handleClick = useCallback(() => {
|
||||
open('')
|
||||
.then(name => {
|
||||
return onConfirm(createEmptyCollection(nanoid(), { name }));
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}, [open, onConfirm]);
|
||||
openPromptModal({
|
||||
title: t['com.affine.editCollection.saveCollection'](),
|
||||
label: t['com.affine.editCollectionName.name'](),
|
||||
inputOptions: {
|
||||
placeholder: t['com.affine.editCollectionName.name.placeholder'](),
|
||||
},
|
||||
children: (
|
||||
<div className={styles.createTips}>
|
||||
{t['com.affine.editCollectionName.createTips']()}
|
||||
</div>
|
||||
),
|
||||
confirmText: t['com.affine.editCollection.save'](),
|
||||
cancelText: t['com.affine.editCollection.button.cancel'](),
|
||||
confirmButtonOptions: {
|
||||
variant: 'primary',
|
||||
},
|
||||
onConfirm(name) {
|
||||
onConfirm(createEmptyCollection(nanoid(), { name }));
|
||||
},
|
||||
});
|
||||
}, [openPromptModal, t, onConfirm]);
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClick}
|
||||
|
@ -1,97 +0,0 @@
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import { useMount } from '@toeverything/infra';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { CreateCollectionModal } from './create-collection';
|
||||
import {
|
||||
EditCollectionModal,
|
||||
type EditCollectionMode,
|
||||
} from './edit-collection/edit-collection';
|
||||
|
||||
export const useEditCollection = () => {
|
||||
const [data, setData] = useState<{
|
||||
collection: Collection;
|
||||
mode?: 'page' | 'rule';
|
||||
onConfirm: (collection: Collection) => void;
|
||||
}>();
|
||||
const close = useCallback((open: boolean) => {
|
||||
if (!open) {
|
||||
setData(undefined);
|
||||
}
|
||||
}, []);
|
||||
const { mount } = useMount('useEditCollection');
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) return;
|
||||
return mount(
|
||||
<EditCollectionModal
|
||||
init={data?.collection}
|
||||
open={!!data}
|
||||
mode={data?.mode}
|
||||
onOpenChange={close}
|
||||
onConfirm={data?.onConfirm ?? (() => {})}
|
||||
/>
|
||||
);
|
||||
}, [close, data, mount]);
|
||||
|
||||
return {
|
||||
open: (
|
||||
collection: Collection,
|
||||
mode?: EditCollectionMode
|
||||
): Promise<Collection> =>
|
||||
new Promise<Collection>(res => {
|
||||
setData({
|
||||
collection,
|
||||
mode,
|
||||
onConfirm: collection => {
|
||||
res(collection);
|
||||
},
|
||||
});
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
export const useEditCollectionName = ({
|
||||
title,
|
||||
showTips,
|
||||
}: {
|
||||
title: string;
|
||||
showTips?: boolean;
|
||||
}) => {
|
||||
const [data, setData] = useState<{
|
||||
name: string;
|
||||
onConfirm: (name: string) => void;
|
||||
}>();
|
||||
const close = useCallback((open: boolean) => {
|
||||
if (!open) {
|
||||
setData(undefined);
|
||||
}
|
||||
}, []);
|
||||
const { mount } = useMount('useEditCollectionName');
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) return;
|
||||
return mount(
|
||||
<CreateCollectionModal
|
||||
showTips={showTips}
|
||||
title={title}
|
||||
init={data?.name ?? ''}
|
||||
open={!!data}
|
||||
onOpenChange={close}
|
||||
onConfirm={data?.onConfirm ?? (() => {})}
|
||||
/>
|
||||
);
|
||||
}, [close, data, mount, showTips, title]);
|
||||
|
||||
return {
|
||||
open: (name: string): Promise<string> =>
|
||||
new Promise<string>(res => {
|
||||
setData({
|
||||
name,
|
||||
onConfirm: collection => {
|
||||
res(collection);
|
||||
},
|
||||
});
|
||||
}),
|
||||
};
|
||||
};
|
@ -1,218 +0,0 @@
|
||||
import { NotificationCenter, notify } from '@affine/component';
|
||||
import { DesktopApiService } from '@affine/core/modules/desktop-api/service';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import {
|
||||
GlobalContextService,
|
||||
useLiveData,
|
||||
useService,
|
||||
useServiceOptional,
|
||||
WorkspaceService,
|
||||
WorkspacesService,
|
||||
} from '@toeverything/infra';
|
||||
import { useAtom } from 'jotai';
|
||||
import type { ReactElement } from 'react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
import { AuthService } from '../../modules/cloud/services/auth';
|
||||
import { CreateWorkspaceDialogProvider } from '../../modules/create-workspace';
|
||||
import { FindInPageModal } from '../../modules/find-in-page/view/find-in-page-modal';
|
||||
import { ImportTemplateDialogProvider } from '../../modules/import-template';
|
||||
import { PeekViewManagerModal } from '../../modules/peek-view';
|
||||
import { AuthModal } from '../affine/auth';
|
||||
import { AiLoginRequiredModal } from '../affine/auth/ai-login-required';
|
||||
import { HistoryTipsModal } from '../affine/history-tips-modal';
|
||||
import { ImportModal } from '../affine/import-modal';
|
||||
import { IssueFeedbackModal } from '../affine/issue-feedback-modal';
|
||||
import {
|
||||
CloudQuotaModal,
|
||||
LocalQuotaModal,
|
||||
} from '../affine/quota-reached-modal';
|
||||
import { SettingModal } from '../affine/setting-modal';
|
||||
import { SignOutModal } from '../affine/sign-out-modal';
|
||||
import { StarAFFiNEModal } from '../affine/star-affine-modal';
|
||||
import type { SettingAtom } from '../atoms';
|
||||
import {
|
||||
openImportModalAtom,
|
||||
openSettingModalAtom,
|
||||
openSignOutModalAtom,
|
||||
} from '../atoms';
|
||||
import { InfoModal } from '../doc-properties/info-modal/info-modal';
|
||||
import { useTrashModalHelper } from '../hooks/affine/use-trash-modal-helper';
|
||||
import { useAsyncCallback } from '../hooks/affine-async-hooks';
|
||||
import { useNavigateHelper } from '../hooks/use-navigate-helper';
|
||||
import { MoveToTrash } from '../page-list';
|
||||
|
||||
export const Setting = () => {
|
||||
const [{ open, workspaceMetadata, activeTab }, setOpenSettingModalAtom] =
|
||||
useAtom(openSettingModalAtom);
|
||||
|
||||
const onSettingClick = useCallback(
|
||||
({
|
||||
activeTab,
|
||||
workspaceMetadata,
|
||||
}: Pick<SettingAtom, 'activeTab' | 'workspaceMetadata'>) => {
|
||||
setOpenSettingModalAtom(prev => ({
|
||||
...prev,
|
||||
activeTab,
|
||||
workspaceMetadata,
|
||||
}));
|
||||
},
|
||||
[setOpenSettingModalAtom]
|
||||
);
|
||||
|
||||
const onOpenChange = useCallback(
|
||||
(open: boolean) => {
|
||||
setOpenSettingModalAtom(prev => ({ ...prev, open }));
|
||||
},
|
||||
[setOpenSettingModalAtom]
|
||||
);
|
||||
|
||||
const desktopApi = useServiceOptional(DesktopApiService);
|
||||
|
||||
useEffect(() => {
|
||||
return desktopApi?.events?.applicationMenu.openAboutPageInSettingModal(() =>
|
||||
setOpenSettingModalAtom({
|
||||
activeTab: 'about',
|
||||
open: true,
|
||||
})
|
||||
);
|
||||
}, [desktopApi?.events?.applicationMenu, setOpenSettingModalAtom]);
|
||||
|
||||
if (!open) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingModal
|
||||
open={open}
|
||||
activeTab={activeTab}
|
||||
workspaceMetadata={workspaceMetadata}
|
||||
onSettingClick={onSettingClick}
|
||||
onOpenChange={onOpenChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export function CurrentWorkspaceModals() {
|
||||
const currentWorkspace = useService(WorkspaceService).workspace;
|
||||
|
||||
const { trashModal, setTrashModal, handleOnConfirm } = useTrashModalHelper();
|
||||
const deletePageTitles = trashModal.pageTitles;
|
||||
const trashConfirmOpen = trashModal.open;
|
||||
const onTrashConfirmOpenChange = useCallback(
|
||||
(open: boolean) => {
|
||||
setTrashModal({
|
||||
...trashModal,
|
||||
open,
|
||||
});
|
||||
},
|
||||
[trashModal, setTrashModal]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<StarAFFiNEModal />
|
||||
<IssueFeedbackModal />
|
||||
{currentWorkspace ? <Setting /> : null}
|
||||
{currentWorkspace?.flavour === WorkspaceFlavour.LOCAL && (
|
||||
<>
|
||||
<LocalQuotaModal />
|
||||
<HistoryTipsModal />
|
||||
</>
|
||||
)}
|
||||
{currentWorkspace?.flavour === WorkspaceFlavour.AFFINE_CLOUD && (
|
||||
<CloudQuotaModal />
|
||||
)}
|
||||
<AiLoginRequiredModal />
|
||||
<PeekViewManagerModal />
|
||||
{BUILD_CONFIG.isElectron && <FindInPageModal />}
|
||||
<MoveToTrash.ConfirmModal
|
||||
open={trashConfirmOpen}
|
||||
onConfirm={handleOnConfirm}
|
||||
onOpenChange={onTrashConfirmOpenChange}
|
||||
titles={deletePageTitles}
|
||||
/>
|
||||
{currentWorkspace ? <InfoModal /> : null}
|
||||
<Import />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const SignOutConfirmModal = () => {
|
||||
const { openPage } = useNavigateHelper();
|
||||
const authService = useService(AuthService);
|
||||
const [open, setOpen] = useAtom(openSignOutModalAtom);
|
||||
const globalContextService = useService(GlobalContextService);
|
||||
const currentWorkspaceId = useLiveData(
|
||||
globalContextService.globalContext.workspaceId.$
|
||||
);
|
||||
const workspacesService = useService(WorkspacesService);
|
||||
const workspaces = useLiveData(workspacesService.list.workspaces$);
|
||||
const currentWorkspaceMetadata = useLiveData(
|
||||
currentWorkspaceId
|
||||
? workspacesService.list.workspace$(currentWorkspaceId)
|
||||
: undefined
|
||||
);
|
||||
|
||||
const onConfirm = useAsyncCallback(async () => {
|
||||
setOpen(false);
|
||||
try {
|
||||
await authService.signOut();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
// TODO(@eyhn): i18n
|
||||
notify.error({
|
||||
title: 'Failed to sign out',
|
||||
});
|
||||
}
|
||||
|
||||
// if current workspace is affine cloud, switch to local workspace
|
||||
if (currentWorkspaceMetadata?.flavour === WorkspaceFlavour.AFFINE_CLOUD) {
|
||||
const localWorkspace = workspaces.find(
|
||||
w => w.flavour === WorkspaceFlavour.LOCAL
|
||||
);
|
||||
if (localWorkspace) {
|
||||
openPage(localWorkspace.id, 'all');
|
||||
}
|
||||
}
|
||||
}, [
|
||||
authService,
|
||||
currentWorkspaceMetadata?.flavour,
|
||||
openPage,
|
||||
setOpen,
|
||||
workspaces,
|
||||
]);
|
||||
|
||||
return (
|
||||
<SignOutModal open={open} onOpenChange={setOpen} onConfirm={onConfirm} />
|
||||
);
|
||||
};
|
||||
|
||||
export const AllWorkspaceModals = (): ReactElement => {
|
||||
return (
|
||||
<>
|
||||
<NotificationCenter />
|
||||
<ImportTemplateDialogProvider />
|
||||
<CreateWorkspaceDialogProvider />
|
||||
<AuthModal />
|
||||
<SignOutConfirmModal />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const Import = () => {
|
||||
const [open, setOpenImportModalAtom] = useAtom(openImportModalAtom);
|
||||
|
||||
const onOpenChange = useCallback(
|
||||
(open: boolean) => {
|
||||
setOpenImportModalAtom(open);
|
||||
},
|
||||
[setOpenImportModalAtom]
|
||||
);
|
||||
|
||||
if (!open) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <ImportModal open={open} onOpenChange={onOpenChange} />;
|
||||
};
|
@ -0,0 +1,164 @@
|
||||
import { toast } from '@affine/component';
|
||||
import {
|
||||
pushGlobalLoadingEventAtom,
|
||||
resolveGlobalLoadingEventAtom,
|
||||
} from '@affine/component/global-loading';
|
||||
import { AIProvider } from '@affine/core/blocksuite/presets/ai';
|
||||
import { SyncAwareness } from '@affine/core/components/affine/awareness';
|
||||
import { useRegisterFindInPageCommands } from '@affine/core/components/hooks/affine/use-register-find-in-page-commands';
|
||||
import { useRegisterWorkspaceCommands } from '@affine/core/components/hooks/use-register-workspace-commands';
|
||||
import { OverCapacityNotification } from '@affine/core/components/over-capacity';
|
||||
import { GlobalDialogService } from '@affine/core/modules/dialogs';
|
||||
import { EditorSettingService } from '@affine/core/modules/editor-setting';
|
||||
import { useRegisterNavigationCommands } from '@affine/core/modules/navigation/view/use-register-navigation-commands';
|
||||
import { QuickSearchContainer } from '@affine/core/modules/quicksearch';
|
||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import track from '@affine/track';
|
||||
import { type DocMode, ZipTransformer } from '@blocksuite/affine/blocks';
|
||||
import {
|
||||
DocsService,
|
||||
effect,
|
||||
fromPromise,
|
||||
onStart,
|
||||
throwIfAborted,
|
||||
useService,
|
||||
useServices,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useEffect } from 'react';
|
||||
import {
|
||||
catchError,
|
||||
EMPTY,
|
||||
finalize,
|
||||
mergeMap,
|
||||
switchMap,
|
||||
timeout,
|
||||
} from 'rxjs';
|
||||
import { Map as YMap } from 'yjs';
|
||||
|
||||
/**
|
||||
* @deprecated just for legacy code, will be removed in the future
|
||||
*/
|
||||
export const WorkspaceSideEffects = () => {
|
||||
const t = useI18n();
|
||||
const pushGlobalLoadingEvent = useSetAtom(pushGlobalLoadingEventAtom);
|
||||
const resolveGlobalLoadingEvent = useSetAtom(resolveGlobalLoadingEventAtom);
|
||||
const { workspaceService, docsService } = useServices({
|
||||
WorkspaceService,
|
||||
DocsService,
|
||||
EditorSettingService,
|
||||
});
|
||||
const currentWorkspace = workspaceService.workspace;
|
||||
const docsList = docsService.list;
|
||||
|
||||
const workbench = useService(WorkbenchService).workbench;
|
||||
useEffect(() => {
|
||||
const insertTemplate = effect(
|
||||
switchMap(({ template, mode }: { template: string; mode: string }) => {
|
||||
return fromPromise(async abort => {
|
||||
const templateZip = await fetch(template, { signal: abort });
|
||||
const templateBlob = await templateZip.blob();
|
||||
throwIfAborted(abort);
|
||||
const [doc] = await ZipTransformer.importDocs(
|
||||
currentWorkspace.docCollection,
|
||||
templateBlob
|
||||
);
|
||||
if (doc) {
|
||||
doc.resetHistory();
|
||||
}
|
||||
|
||||
return { doc, mode };
|
||||
}).pipe(
|
||||
timeout(10000 /* 10s */),
|
||||
mergeMap(({ mode, doc }) => {
|
||||
if (doc) {
|
||||
docsList.setPrimaryMode(doc.id, mode as DocMode);
|
||||
workbench.openDoc(doc.id);
|
||||
}
|
||||
return EMPTY;
|
||||
}),
|
||||
onStart(() => {
|
||||
pushGlobalLoadingEvent({
|
||||
key: 'insert-template',
|
||||
});
|
||||
}),
|
||||
catchError(err => {
|
||||
console.error(err);
|
||||
toast(t['com.affine.ai.template-insert.failed']());
|
||||
return EMPTY;
|
||||
}),
|
||||
finalize(() => {
|
||||
resolveGlobalLoadingEvent('insert-template');
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
const disposable = AIProvider.slots.requestInsertTemplate.on(
|
||||
({ template, mode }) => {
|
||||
insertTemplate({ template, mode });
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
insertTemplate.unsubscribe();
|
||||
};
|
||||
}, [
|
||||
currentWorkspace.docCollection,
|
||||
docsList,
|
||||
pushGlobalLoadingEvent,
|
||||
resolveGlobalLoadingEvent,
|
||||
t,
|
||||
workbench,
|
||||
]);
|
||||
|
||||
const globalDialogService = useService(GlobalDialogService);
|
||||
|
||||
useEffect(() => {
|
||||
const disposable = AIProvider.slots.requestUpgradePlan.on(() => {
|
||||
globalDialogService.open('setting', {
|
||||
activeTab: 'billing',
|
||||
});
|
||||
track.$.paywall.aiAction.viewPlans();
|
||||
});
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
};
|
||||
}, [globalDialogService]);
|
||||
|
||||
useRegisterWorkspaceCommands();
|
||||
useRegisterNavigationCommands();
|
||||
useRegisterFindInPageCommands();
|
||||
|
||||
useEffect(() => {
|
||||
// hotfix for blockVersions
|
||||
// this is a mistake in the
|
||||
// 0.8.0 ~ 0.8.1
|
||||
// 0.8.0-beta.0 ~ 0.8.0-beta.3
|
||||
// 0.8.0-canary.17 ~ 0.9.0-canary.3
|
||||
const meta = currentWorkspace.docCollection.doc.getMap('meta');
|
||||
const blockVersions = meta.get('blockVersions');
|
||||
if (
|
||||
!(blockVersions instanceof YMap) &&
|
||||
blockVersions !== null &&
|
||||
blockVersions !== undefined &&
|
||||
typeof blockVersions === 'object'
|
||||
) {
|
||||
meta.set(
|
||||
'blockVersions',
|
||||
new YMap(Object.entries(blockVersions as Record<string, number>))
|
||||
);
|
||||
}
|
||||
}, [currentWorkspace.docCollection.doc]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<QuickSearchContainer />
|
||||
<SyncAwareness />
|
||||
<OverCapacityNotification />
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import { DesktopApiService } from '@affine/core/modules/desktop-api/service';
|
||||
import { DesktopApiService } from '@affine/core/modules/desktop-api';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
|
@ -1,17 +1,17 @@
|
||||
import { Tooltip } from '@affine/component/ui/tooltip';
|
||||
import { GlobalDialogService } from '@affine/core/modules/dialogs';
|
||||
import type { SettingTab } from '@affine/core/modules/dialogs/constant';
|
||||
import { UrlService } from '@affine/core/modules/url';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { CloseIcon, NewIcon } from '@blocksuite/icons/rc';
|
||||
import {
|
||||
GlobalContextService,
|
||||
useLiveData,
|
||||
useService,
|
||||
useServices,
|
||||
} from '@toeverything/infra';
|
||||
import { useSetAtom } from 'jotai/react';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import type { SettingProps } from '../../affine/setting-modal';
|
||||
import { openSettingModalAtom } from '../../atoms';
|
||||
import { ContactIcon, HelpIcon, KeyboardIcon } from './icons';
|
||||
import {
|
||||
StyledAnimateWrapper,
|
||||
@ -40,19 +40,18 @@ export const HelpIsland = () => {
|
||||
});
|
||||
const docId = useLiveData(globalContextService.globalContext.docId.$);
|
||||
const docMode = useLiveData(globalContextService.globalContext.docMode.$);
|
||||
const setOpenSettingModalAtom = useSetAtom(openSettingModalAtom);
|
||||
const globalDialogService = useService(GlobalDialogService);
|
||||
const [spread, setShowSpread] = useState(false);
|
||||
const t = useI18n();
|
||||
const openSettingModal = useCallback(
|
||||
(tab: SettingProps['activeTab']) => {
|
||||
(tab: SettingTab) => {
|
||||
setShowSpread(false);
|
||||
|
||||
setOpenSettingModalAtom({
|
||||
open: true,
|
||||
globalDialogService.open('setting', {
|
||||
activeTab: tab,
|
||||
});
|
||||
},
|
||||
[setOpenSettingModalAtom]
|
||||
[globalDialogService]
|
||||
);
|
||||
const openAbout = useCallback(
|
||||
() => openSettingModal('about'),
|
||||
|
@ -1,7 +1,3 @@
|
||||
import {
|
||||
openImportModalAtom,
|
||||
openSettingModalAtom,
|
||||
} from '@affine/core/components/atoms';
|
||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||
import {
|
||||
AddPageButton,
|
||||
@ -15,6 +11,7 @@ import {
|
||||
SidebarScrollableContainer,
|
||||
} from '@affine/core/modules/app-sidebar/views';
|
||||
import { ExternalMenuLinkItem } from '@affine/core/modules/app-sidebar/views/menu-item/external-menu-link-item';
|
||||
import { GlobalDialogService } from '@affine/core/modules/dialogs';
|
||||
import {
|
||||
ExplorerCollections,
|
||||
ExplorerFavorites,
|
||||
@ -37,10 +34,10 @@ import {
|
||||
import type { Workspace } from '@toeverything/infra';
|
||||
import {
|
||||
useLiveData,
|
||||
useService,
|
||||
useServices,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import type { MouseEvent, ReactElement } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
@ -86,6 +83,7 @@ export const RootAppSidebar = (): ReactElement => {
|
||||
});
|
||||
const currentWorkspace = workspaceService.workspace;
|
||||
const t = useI18n();
|
||||
const globalDialogService = useService(GlobalDialogService);
|
||||
const workbench = workbenchService.workbench;
|
||||
const currentPath = useLiveData(
|
||||
workbench.location$.map(location => location.pathname)
|
||||
@ -106,21 +104,17 @@ export const RootAppSidebar = (): ReactElement => {
|
||||
[pageHelper]
|
||||
);
|
||||
|
||||
const setOpenSettingModalAtom = useSetAtom(openSettingModalAtom);
|
||||
const setOpenImportModalAtom = useSetAtom(openImportModalAtom);
|
||||
|
||||
const onOpenSettingModal = useCallback(() => {
|
||||
setOpenSettingModalAtom({
|
||||
globalDialogService.open('setting', {
|
||||
activeTab: 'appearance',
|
||||
open: true,
|
||||
});
|
||||
track.$.navigationPanel.$.openSettings();
|
||||
}, [setOpenSettingModalAtom]);
|
||||
}, [globalDialogService]);
|
||||
|
||||
const onOpenImportModal = useCallback(() => {
|
||||
track.$.navigationPanel.importModal.open();
|
||||
setOpenImportModalAtom(true);
|
||||
}, [setOpenImportModalAtom]);
|
||||
globalDialogService.open('import', undefined);
|
||||
}, [globalDialogService]);
|
||||
|
||||
return (
|
||||
<AppSidebar>
|
||||
|
@ -8,11 +8,8 @@ import {
|
||||
type MenuProps,
|
||||
Skeleton,
|
||||
} from '@affine/component';
|
||||
import {
|
||||
authAtom,
|
||||
openSettingModalAtom,
|
||||
openSignOutModalAtom,
|
||||
} from '@affine/core/components/atoms';
|
||||
import { authAtom } from '@affine/core/components/atoms';
|
||||
import { GlobalDialogService } from '@affine/core/modules/dialogs';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { track } from '@affine/track';
|
||||
import { AccountIcon, SignOutIcon } from '@blocksuite/icons/rc';
|
||||
@ -32,6 +29,7 @@ import {
|
||||
UserQuotaService,
|
||||
} from '../../modules/cloud';
|
||||
import { UserPlanButton } from '../affine/auth/user-plan-button';
|
||||
import { useSignOut } from '../hooks/affine/use-sign-out';
|
||||
import * as styles from './index.css';
|
||||
import { UnknownUserIcon } from './unknow-user';
|
||||
|
||||
@ -78,21 +76,15 @@ const UnauthorizedUserInfo = () => {
|
||||
};
|
||||
|
||||
const AccountMenu = () => {
|
||||
const setSettingModalAtom = useSetAtom(openSettingModalAtom);
|
||||
const setOpenSignOutModalAtom = useSetAtom(openSignOutModalAtom);
|
||||
const globalDialogService = useService(GlobalDialogService);
|
||||
const openSignOutModal = useSignOut();
|
||||
|
||||
const onOpenAccountSetting = useCallback(() => {
|
||||
track.$.navigationPanel.profileAndBadge.openSettings({ to: 'account' });
|
||||
setSettingModalAtom(prev => ({
|
||||
...prev,
|
||||
open: true,
|
||||
globalDialogService.open('setting', {
|
||||
activeTab: 'account',
|
||||
}));
|
||||
}, [setSettingModalAtom]);
|
||||
|
||||
const onOpenSignOutModal = useCallback(() => {
|
||||
setOpenSignOutModalAtom(true);
|
||||
}, [setOpenSignOutModalAtom]);
|
||||
});
|
||||
}, [globalDialogService]);
|
||||
|
||||
const t = useI18n();
|
||||
|
||||
@ -108,7 +100,7 @@ const AccountMenu = () => {
|
||||
<MenuItem
|
||||
prefixIcon={<SignOutIcon />}
|
||||
data-testid="workspace-modal-sign-out-option"
|
||||
onClick={onOpenSignOutModal}
|
||||
onClick={openSignOutModal}
|
||||
>
|
||||
{t['com.affine.workspace.cloud.account.logout']()}
|
||||
</MenuItem>
|
||||
@ -193,22 +185,20 @@ const AIUsage = () => {
|
||||
const loading = copilotActionLimit === null || copilotActionUsed === null;
|
||||
const loadError = useLiveData(copilotQuotaService.copilotQuota.error$);
|
||||
|
||||
const setSettingModalAtom = useSetAtom(openSettingModalAtom);
|
||||
const globalDialogService = useService(GlobalDialogService);
|
||||
|
||||
const goToAIPlanPage = useCallback(() => {
|
||||
setSettingModalAtom({
|
||||
open: true,
|
||||
globalDialogService.open('setting', {
|
||||
activeTab: 'plans',
|
||||
scrollAnchor: 'aiPricingPlan',
|
||||
});
|
||||
}, [setSettingModalAtom]);
|
||||
}, [globalDialogService]);
|
||||
|
||||
const goToAccountSetting = useCallback(() => {
|
||||
setSettingModalAtom({
|
||||
open: true,
|
||||
globalDialogService.open('setting', {
|
||||
activeTab: 'account',
|
||||
});
|
||||
}, [setSettingModalAtom]);
|
||||
}, [globalDialogService]);
|
||||
|
||||
if (loading) {
|
||||
if (loadError) console.error(loadError);
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Menu, type MenuProps } from '@affine/component';
|
||||
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
|
||||
import type { CreateWorkspaceCallbackPayload } from '@affine/core/modules/create-workspace';
|
||||
import { track } from '@affine/track';
|
||||
import {
|
||||
GlobalContextService,
|
||||
@ -18,7 +17,10 @@ interface WorkspaceSelectorProps {
|
||||
open?: boolean;
|
||||
workspaceMetadata?: WorkspaceMetadata;
|
||||
onSelectWorkspace?: (workspaceMetadata: WorkspaceMetadata) => void;
|
||||
onCreatedWorkspace?: (payload: CreateWorkspaceCallbackPayload) => void;
|
||||
onCreatedWorkspace?: (payload: {
|
||||
metadata: WorkspaceMetadata;
|
||||
defaultDocId?: string;
|
||||
}) => void;
|
||||
showSettingsButton?: boolean;
|
||||
showEnableCloudButton?: boolean;
|
||||
showArrowDownIcon?: boolean;
|
||||
@ -140,14 +142,14 @@ export const WorkspaceNavigator = ({
|
||||
[onSelectWorkspace, jumpToPage]
|
||||
);
|
||||
const handleCreatedWorkspace = useCallback(
|
||||
(payload: CreateWorkspaceCallbackPayload) => {
|
||||
(payload: { metadata: WorkspaceMetadata; defaultDocId?: string }) => {
|
||||
onCreatedWorkspace?.(payload);
|
||||
if (document.startViewTransition) {
|
||||
document.startViewTransition(() => {
|
||||
if (payload.defaultDocId) {
|
||||
jumpToPage(payload.meta.id, payload.defaultDocId);
|
||||
jumpToPage(payload.metadata.id, payload.defaultDocId);
|
||||
} else {
|
||||
jumpToPage(payload.meta.id, 'all');
|
||||
jumpToPage(payload.metadata.id, 'all');
|
||||
}
|
||||
return new Promise(resolve =>
|
||||
setTimeout(resolve, 150)
|
||||
@ -155,9 +157,9 @@ export const WorkspaceNavigator = ({
|
||||
});
|
||||
} else {
|
||||
if (payload.defaultDocId) {
|
||||
jumpToPage(payload.meta.id, payload.defaultDocId);
|
||||
jumpToPage(payload.metadata.id, payload.defaultDocId);
|
||||
} else {
|
||||
jumpToPage(payload.meta.id, 'all');
|
||||
jumpToPage(payload.metadata.id, 'all');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -2,8 +2,7 @@ import { Divider } from '@affine/component/ui/divider';
|
||||
import { MenuItem } from '@affine/component/ui/menu';
|
||||
import { authAtom } from '@affine/core/components/atoms';
|
||||
import { AuthService } from '@affine/core/modules/cloud';
|
||||
import { CreateWorkspaceDialogService } from '@affine/core/modules/create-workspace';
|
||||
import type { CreateWorkspaceCallbackPayload } from '@affine/core/modules/create-workspace/types';
|
||||
import { GlobalDialogService } from '@affine/core/modules/dialogs';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { track } from '@affine/track';
|
||||
import { Logo1Icon } from '@blocksuite/icons/rc';
|
||||
@ -62,7 +61,10 @@ export const SignInItem = () => {
|
||||
interface UserWithWorkspaceListProps {
|
||||
onEventEnd?: () => void;
|
||||
onClickWorkspace?: (workspace: WorkspaceMetadata) => void;
|
||||
onCreatedWorkspace?: (payload: CreateWorkspaceCallbackPayload) => void;
|
||||
onCreatedWorkspace?: (payload: {
|
||||
metadata: WorkspaceMetadata;
|
||||
defaultDocId?: string;
|
||||
}) => void;
|
||||
showSettingsButton?: boolean;
|
||||
showEnableCloudButton?: boolean;
|
||||
}
|
||||
@ -74,7 +76,7 @@ const UserWithWorkspaceListInner = ({
|
||||
showSettingsButton,
|
||||
showEnableCloudButton,
|
||||
}: UserWithWorkspaceListProps) => {
|
||||
const createWorkspaceDialogService = useService(CreateWorkspaceDialogService);
|
||||
const globalDialogService = useService(GlobalDialogService);
|
||||
const session = useLiveData(useService(AuthService).session.session$);
|
||||
const featureFlagService = useService(FeatureFlagService);
|
||||
|
||||
@ -97,14 +99,14 @@ const UserWithWorkspaceListInner = ({
|
||||
return openSignInModal();
|
||||
}
|
||||
track.$.navigationPanel.workspaceList.createWorkspace();
|
||||
createWorkspaceDialogService.dialog.open('new', payload => {
|
||||
globalDialogService.open('create-workspace', undefined, payload => {
|
||||
if (payload) {
|
||||
onCreatedWorkspace?.(payload);
|
||||
}
|
||||
});
|
||||
onEventEnd?.();
|
||||
}, [
|
||||
createWorkspaceDialogService,
|
||||
globalDialogService,
|
||||
featureFlagService,
|
||||
isAuthenticated,
|
||||
onCreatedWorkspace,
|
||||
@ -116,13 +118,13 @@ const UserWithWorkspaceListInner = ({
|
||||
track.$.navigationPanel.workspaceList.createWorkspace({
|
||||
control: 'import',
|
||||
});
|
||||
createWorkspaceDialogService.dialog.open('add', payload => {
|
||||
globalDialogService.open('import-workspace', undefined, payload => {
|
||||
if (payload) {
|
||||
onCreatedWorkspace?.(payload);
|
||||
onCreatedWorkspace?.({ metadata: payload.workspace });
|
||||
}
|
||||
});
|
||||
onEventEnd?.();
|
||||
}, [createWorkspaceDialogService.dialog, onCreatedWorkspace, onEventEnd]);
|
||||
}, [globalDialogService, onCreatedWorkspace, onEventEnd]);
|
||||
|
||||
const workspaceManager = useService(WorkspacesService);
|
||||
const workspaces = useLiveData(workspaceManager.list.workspaces$);
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { ScrollableContainer } from '@affine/component';
|
||||
import { Divider } from '@affine/component/ui/divider';
|
||||
import { openSettingModalAtom } from '@affine/core/components/atoms';
|
||||
import { useEnableCloud } from '@affine/core/components/hooks/affine/use-enable-cloud';
|
||||
import { AuthService } from '@affine/core/modules/cloud';
|
||||
import { GlobalDialogService } from '@affine/core/modules/dialogs';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { CloudWorkspaceIcon, LocalWorkspaceIcon } from '@blocksuite/icons/rc';
|
||||
@ -14,7 +14,6 @@ import {
|
||||
WorkspaceService,
|
||||
WorkspacesService,
|
||||
} from '@toeverything/infra';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { WorkspaceCard } from '../../workspace-card';
|
||||
@ -100,11 +99,10 @@ export const AFFiNEWorkspaceList = ({
|
||||
}) => {
|
||||
const workspacesService = useService(WorkspacesService);
|
||||
const workspaces = useLiveData(workspacesService.list.workspaces$);
|
||||
const globalDialogService = useService(GlobalDialogService);
|
||||
|
||||
const confirmEnableCloud = useEnableCloud();
|
||||
|
||||
const setOpenSettingModalAtom = useSetAtom(openSettingModalAtom);
|
||||
|
||||
const session = useService(AuthService).session;
|
||||
const status = useLiveData(session.status$);
|
||||
|
||||
@ -128,14 +126,13 @@ export const AFFiNEWorkspaceList = ({
|
||||
|
||||
const onClickWorkspaceSetting = useCallback(
|
||||
(workspaceMetadata: WorkspaceMetadata) => {
|
||||
setOpenSettingModalAtom({
|
||||
open: true,
|
||||
globalDialogService.open('setting', {
|
||||
activeTab: 'workspace:preference',
|
||||
workspaceMetadata,
|
||||
});
|
||||
onEventEnd?.();
|
||||
},
|
||||
[onEventEnd, setOpenSettingModalAtom]
|
||||
[globalDialogService, onEventEnd]
|
||||
);
|
||||
|
||||
const onClickEnableCloud = useCallback(
|
||||
|
@ -1,93 +0,0 @@
|
||||
import { useAppSettingHelper } from '@affine/core/components/hooks/affine/use-app-setting-helper';
|
||||
import { AppSidebarService } from '@affine/core/modules/app-sidebar';
|
||||
import {
|
||||
DocsService,
|
||||
GlobalContextService,
|
||||
useLiveData,
|
||||
useService,
|
||||
} from '@toeverything/infra';
|
||||
import { clsx } from 'clsx';
|
||||
import type { HTMLAttributes, PropsWithChildren, ReactElement } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import { appStyle, mainContainerStyle, toolStyle } from './index.css';
|
||||
|
||||
export type WorkspaceRootProps = PropsWithChildren<{
|
||||
className?: string;
|
||||
useNoisyBackground?: boolean;
|
||||
useBlurBackground?: boolean;
|
||||
}>;
|
||||
|
||||
export const AppContainer = ({
|
||||
useNoisyBackground,
|
||||
useBlurBackground,
|
||||
children,
|
||||
className,
|
||||
...rest
|
||||
}: WorkspaceRootProps) => {
|
||||
const noisyBackground = BUILD_CONFIG.isElectron && useNoisyBackground;
|
||||
const blurBackground =
|
||||
BUILD_CONFIG.isElectron && environment.isMacOs && useBlurBackground;
|
||||
return (
|
||||
<div
|
||||
{...rest}
|
||||
className={clsx(appStyle, className, {
|
||||
'noisy-background': noisyBackground,
|
||||
'blur-background': blurBackground,
|
||||
})}
|
||||
data-noise-background={noisyBackground}
|
||||
data-blur-background={blurBackground}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export interface MainContainerProps extends HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
export const MainContainer = forwardRef<
|
||||
HTMLDivElement,
|
||||
PropsWithChildren<MainContainerProps>
|
||||
>(function MainContainer({ className, children, ...props }, ref): ReactElement {
|
||||
const { appSettings } = useAppSettingHelper();
|
||||
const appSidebarService = useService(AppSidebarService).sidebar;
|
||||
const open = useLiveData(appSidebarService.open$);
|
||||
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={clsx(mainContainerStyle, className)}
|
||||
data-is-desktop={BUILD_CONFIG.isElectron}
|
||||
data-transparent={false}
|
||||
data-client-border={appSettings.clientBorder}
|
||||
data-side-bar-open={open}
|
||||
data-testid="main-container"
|
||||
ref={ref}
|
||||
>
|
||||
<div className={mainContainerStyle}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
MainContainer.displayName = 'MainContainer';
|
||||
|
||||
export const MainContainerFallback = ({ children }: PropsWithChildren) => {
|
||||
// todo: default app fallback?
|
||||
return <MainContainer>{children}</MainContainer>;
|
||||
};
|
||||
|
||||
export const ToolContainer = (
|
||||
props: PropsWithChildren<{ className?: string }>
|
||||
): ReactElement => {
|
||||
const docId = useLiveData(
|
||||
useService(GlobalContextService).globalContext.docId.$
|
||||
);
|
||||
const docRecordList = useService(DocsService).list;
|
||||
const doc = useLiveData(docId ? docRecordList.doc$(docId) : undefined);
|
||||
const inTrash = useLiveData(doc?.meta$)?.trash;
|
||||
return (
|
||||
<div className={clsx(toolStyle, { trash: inTrash }, props.className)}>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,17 @@
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const islandContainer = style({
|
||||
position: 'absolute',
|
||||
right: 16,
|
||||
bottom: 16,
|
||||
zIndex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: '12px',
|
||||
selectors: {
|
||||
'&.trash': {
|
||||
bottom: '78px',
|
||||
},
|
||||
},
|
||||
});
|
@ -0,0 +1,26 @@
|
||||
import {
|
||||
DocsService,
|
||||
GlobalContextService,
|
||||
useLiveData,
|
||||
useService,
|
||||
} from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
import type { PropsWithChildren, ReactElement } from 'react';
|
||||
|
||||
import { islandContainer } from './container.css';
|
||||
|
||||
export const IslandContainer = (
|
||||
props: PropsWithChildren<{ className?: string }>
|
||||
): ReactElement => {
|
||||
const docId = useLiveData(
|
||||
useService(GlobalContextService).globalContext.docId.$
|
||||
);
|
||||
const docRecordList = useService(DocsService).list;
|
||||
const doc = useLiveData(docId ? docRecordList.doc$(docId) : undefined);
|
||||
const inTrash = useLiveData(doc?.meta$)?.trash;
|
||||
return (
|
||||
<div className={clsx(islandContainer, { trash: inTrash }, props.className)}>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
};
|
@ -3,7 +3,7 @@ import { useLiveData, useService } from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { ToolContainer } from '../../workspace';
|
||||
import { IslandContainer } from './container';
|
||||
import { AIIcon } from './icons';
|
||||
import { aiIslandBtn, aiIslandWrapper, toolStyle } from './styles.css';
|
||||
|
||||
@ -24,7 +24,7 @@ export const AIIsland = () => {
|
||||
}, [activeTab, haveChatTab, sidebarOpen]);
|
||||
|
||||
return (
|
||||
<ToolContainer className={clsx(toolStyle, { hide })}>
|
||||
<IslandContainer className={clsx(toolStyle, { hide })}>
|
||||
<div className={aiIslandWrapper} data-hide={hide}>
|
||||
<button
|
||||
className={aiIslandBtn}
|
||||
@ -38,6 +38,6 @@ export const AIIsland = () => {
|
||||
<AIIcon />
|
||||
</button>
|
||||
</div>
|
||||
</ToolContainer>
|
||||
</IslandContainer>
|
||||
);
|
||||
};
|
@ -0,0 +1,133 @@
|
||||
import { useAppSettingHelper } from '@affine/core/components/hooks/affine/use-app-setting-helper';
|
||||
import { RootAppSidebar } from '@affine/core/components/root-app-sidebar';
|
||||
import { AppSidebarService } from '@affine/core/modules/app-sidebar';
|
||||
import {
|
||||
AppSidebarFallback,
|
||||
OpenInAppCard,
|
||||
SidebarSwitch,
|
||||
} from '@affine/core/modules/app-sidebar/views';
|
||||
import { AppTabsHeader } from '@affine/core/modules/app-tabs-header';
|
||||
import { NavigationButtons } from '@affine/core/modules/navigation';
|
||||
import {
|
||||
useLiveData,
|
||||
useService,
|
||||
useServiceOptional,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
forwardRef,
|
||||
type HTMLAttributes,
|
||||
type PropsWithChildren,
|
||||
type ReactElement,
|
||||
} from 'react';
|
||||
|
||||
import * as styles from './styles.css';
|
||||
|
||||
export const AppContainer = ({
|
||||
children,
|
||||
className,
|
||||
fallback = false,
|
||||
...rest
|
||||
}: PropsWithChildren<{
|
||||
className?: string;
|
||||
fallback?: boolean;
|
||||
}>) => {
|
||||
const { appSettings } = useAppSettingHelper();
|
||||
|
||||
const noisyBackground =
|
||||
BUILD_CONFIG.isElectron && appSettings.enableNoisyBackground;
|
||||
const blurBackground =
|
||||
BUILD_CONFIG.isElectron &&
|
||||
environment.isMacOs &&
|
||||
appSettings.enableBlurBackground;
|
||||
return (
|
||||
<div
|
||||
{...rest}
|
||||
className={clsx(styles.appStyle, className, {
|
||||
'noisy-background': noisyBackground,
|
||||
'blur-background': blurBackground,
|
||||
})}
|
||||
data-noise-background={noisyBackground}
|
||||
data-blur-background={blurBackground}
|
||||
>
|
||||
<LayoutComponent fallback={fallback}>{children}</LayoutComponent>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const DesktopLayout = ({
|
||||
children,
|
||||
fallback = false,
|
||||
}: PropsWithChildren<{ fallback?: boolean }>) => {
|
||||
const workspaceService = useServiceOptional(WorkspaceService);
|
||||
const isInWorkspace = !!workspaceService;
|
||||
return (
|
||||
<div className={styles.desktopAppViewContainer}>
|
||||
<div className={styles.desktopTabsHeader}>
|
||||
<AppTabsHeader
|
||||
left={
|
||||
<>
|
||||
{isInWorkspace && <SidebarSwitch show />}
|
||||
{isInWorkspace && <NavigationButtons />}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.desktopAppViewMain}>
|
||||
{fallback ? (
|
||||
<AppSidebarFallback />
|
||||
) : (
|
||||
isInWorkspace && <RootAppSidebar />
|
||||
)}
|
||||
<MainContainer>{children}</MainContainer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const BrowserLayout = ({
|
||||
children,
|
||||
fallback = false,
|
||||
}: PropsWithChildren<{ fallback?: boolean }>) => {
|
||||
const workspaceService = useServiceOptional(WorkspaceService);
|
||||
const isInWorkspace = !!workspaceService;
|
||||
|
||||
return (
|
||||
<div className={styles.browserAppViewContainer}>
|
||||
<OpenInAppCard />
|
||||
{fallback ? <AppSidebarFallback /> : isInWorkspace && <RootAppSidebar />}
|
||||
<MainContainer>{children}</MainContainer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const LayoutComponent = BUILD_CONFIG.isElectron ? DesktopLayout : BrowserLayout;
|
||||
|
||||
const MainContainer = forwardRef<
|
||||
HTMLDivElement,
|
||||
PropsWithChildren<HTMLAttributes<HTMLDivElement>>
|
||||
>(function MainContainer({ className, children, ...props }, ref): ReactElement {
|
||||
const workspaceService = useServiceOptional(WorkspaceService);
|
||||
const isInWorkspace = !!workspaceService;
|
||||
const { appSettings } = useAppSettingHelper();
|
||||
const appSidebarService = useService(AppSidebarService).sidebar;
|
||||
const open = useLiveData(appSidebarService.open$);
|
||||
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={clsx(styles.mainContainerStyle, className)}
|
||||
data-is-desktop={BUILD_CONFIG.isElectron}
|
||||
data-transparent={false}
|
||||
data-client-border={appSettings.clientBorder}
|
||||
data-side-bar-open={open && isInWorkspace}
|
||||
data-testid="main-container"
|
||||
ref={ref}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
MainContainer.displayName = 'MainContainer';
|
@ -1,7 +1,5 @@
|
||||
import { cssVar, lightCssVariables } from '@toeverything/theme';
|
||||
import { createVar, globalStyle, style } from '@vanilla-extract/css';
|
||||
|
||||
export const panelWidthVar = createVar('panel-width');
|
||||
import { globalStyle, style } from '@vanilla-extract/css';
|
||||
|
||||
export const appStyle = style({
|
||||
width: '100%',
|
||||
@ -42,6 +40,38 @@ globalStyle(`html[data-theme="dark"] ${appStyle}`, {
|
||||
},
|
||||
});
|
||||
|
||||
export const browserAppViewContainer = style({
|
||||
display: 'flex',
|
||||
flexFlow: 'row',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
});
|
||||
|
||||
export const desktopAppViewContainer = style({
|
||||
display: 'flex',
|
||||
flexFlow: 'column',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
});
|
||||
|
||||
export const desktopAppViewMain = style({
|
||||
display: 'flex',
|
||||
flexFlow: 'row',
|
||||
width: '100%',
|
||||
height: 'calc(100% - 52px)',
|
||||
position: 'relative',
|
||||
});
|
||||
|
||||
export const desktopTabsHeader = style({
|
||||
display: 'flex',
|
||||
flexFlow: 'row',
|
||||
height: '52px',
|
||||
zIndex: 1,
|
||||
width: '100%',
|
||||
overflow: 'hidden',
|
||||
});
|
||||
|
||||
export const mainContainerStyle = style({
|
||||
position: 'relative',
|
||||
zIndex: 0,
|
||||
@ -83,26 +113,3 @@ export const mainContainerStyle = style({
|
||||
},
|
||||
},
|
||||
});
|
||||
export const toolStyle = style({
|
||||
position: 'absolute',
|
||||
right: 16,
|
||||
bottom: 16,
|
||||
zIndex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: '12px',
|
||||
selectors: {
|
||||
'&.trash': {
|
||||
bottom: '78px',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const fallbackRootStyle = style({
|
||||
paddingTop: 52,
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
});
|
@ -5,25 +5,6 @@ export const ellipsis = style({
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
});
|
||||
export const pagesTabContent = style({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
gap: 8,
|
||||
alignItems: 'center',
|
||||
padding: '16px 16px 8px 16px',
|
||||
});
|
||||
export const pagesTab = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
});
|
||||
export const pagesList = style({
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
});
|
||||
export const bottomLeft = style({
|
||||
display: 'flex',
|
||||
gap: 8,
|
||||
@ -130,9 +111,6 @@ export const confirmButton = style({
|
||||
export const resultPages = style({
|
||||
width: '100%',
|
||||
});
|
||||
export const pageList = style({
|
||||
width: '100%',
|
||||
});
|
||||
export const previewCountTipsHighlight = style({
|
||||
color: cssVar('primaryColor'),
|
||||
});
|
@ -1,80 +1,16 @@
|
||||
import { Button, Modal, RadioGroup } from '@affine/component';
|
||||
import { Button, RadioGroup } from '@affine/component';
|
||||
import { useAllPageListConfig } from '@affine/core/components/hooks/affine/use-all-page-list-config';
|
||||
import { SelectPage } from '@affine/core/components/page-list/docs/select-page';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import type { DialogContentProps } from '@radix-ui/react-dialog';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import * as styles from './edit-collection.css';
|
||||
import { RulesMode } from './rules-mode';
|
||||
import { SelectPage } from './select-page';
|
||||
|
||||
export type EditCollectionMode = 'page' | 'rule';
|
||||
|
||||
export interface EditCollectionModalProps {
|
||||
init?: Collection;
|
||||
title?: string;
|
||||
open: boolean;
|
||||
mode?: EditCollectionMode;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onConfirm: (view: Collection) => void;
|
||||
}
|
||||
|
||||
const contentOptions: DialogContentProps = {
|
||||
style: {
|
||||
padding: 0,
|
||||
maxWidth: 944,
|
||||
backgroundColor: 'var(--affine-background-primary-color)',
|
||||
},
|
||||
};
|
||||
export const EditCollectionModal = ({
|
||||
init,
|
||||
onConfirm,
|
||||
open,
|
||||
onOpenChange,
|
||||
title,
|
||||
mode,
|
||||
}: EditCollectionModalProps) => {
|
||||
const t = useI18n();
|
||||
const onConfirmOnCollection = useCallback(
|
||||
(view: Collection) => {
|
||||
onConfirm(view);
|
||||
onOpenChange(false);
|
||||
},
|
||||
[onConfirm, onOpenChange]
|
||||
);
|
||||
const onCancel = useCallback(() => {
|
||||
onOpenChange(false);
|
||||
}, [onOpenChange]);
|
||||
|
||||
if (!(open && init)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
withoutCloseButton
|
||||
width="calc(100% - 64px)"
|
||||
height="80%"
|
||||
contentOptions={contentOptions}
|
||||
persistent
|
||||
>
|
||||
<EditCollection
|
||||
title={title}
|
||||
onConfirmText={t['com.affine.editCollection.save']()}
|
||||
init={init}
|
||||
mode={mode}
|
||||
onCancel={onCancel}
|
||||
onConfirm={onConfirmOnCollection}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export interface EditCollectionProps {
|
||||
title?: string;
|
||||
onConfirmText?: string;
|
||||
init: Collection;
|
||||
mode?: EditCollectionMode;
|
@ -0,0 +1,60 @@
|
||||
import { Modal } from '@affine/component';
|
||||
import { CollectionService } from '@affine/core/modules/collection';
|
||||
import type { DialogComponentProps } from '@affine/core/modules/dialogs';
|
||||
import type { WORKSPACE_DIALOG_SCHEMA } from '@affine/core/modules/dialogs/constant';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { EditCollection } from './edit-collection';
|
||||
|
||||
export const CollectionEditorDialog = ({
|
||||
close,
|
||||
collectionId,
|
||||
mode,
|
||||
}: DialogComponentProps<WORKSPACE_DIALOG_SCHEMA['collection-editor']>) => {
|
||||
const t = useI18n();
|
||||
const collectionService = useService(CollectionService);
|
||||
const collection = useLiveData(collectionService.collection$(collectionId));
|
||||
const onConfirmOnCollection = useCallback(
|
||||
(collection: Collection) => {
|
||||
collectionService.updateCollection(collection.id, () => collection);
|
||||
close();
|
||||
},
|
||||
[close, collectionService]
|
||||
);
|
||||
const onCancel = useCallback(() => {
|
||||
close();
|
||||
}, [close]);
|
||||
|
||||
if (!collection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open
|
||||
onOpenChange={onCancel}
|
||||
withoutCloseButton
|
||||
width="calc(100% - 64px)"
|
||||
height="80%"
|
||||
contentOptions={{
|
||||
style: {
|
||||
padding: 0,
|
||||
maxWidth: 944,
|
||||
backgroundColor: 'var(--affine-background-primary-color)',
|
||||
},
|
||||
}}
|
||||
persistent
|
||||
>
|
||||
<EditCollection
|
||||
onConfirmText={t['com.affine.editCollection.save']()}
|
||||
init={collection}
|
||||
mode={mode}
|
||||
onCancel={onCancel}
|
||||
onConfirm={onConfirmOnCollection}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
};
|
@ -1,4 +1,13 @@
|
||||
import { Button, IconButton, Tooltip } from '@affine/component';
|
||||
import type { AllPageListConfig } from '@affine/core/components/hooks/affine/use-all-page-list-config';
|
||||
import {
|
||||
AffineShapeIcon,
|
||||
FilterList,
|
||||
filterPageByRules,
|
||||
List,
|
||||
type ListItem,
|
||||
ListScrollContainer,
|
||||
} from '@affine/core/components/page-list';
|
||||
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import { Trans, useI18n } from '@affine/i18n';
|
||||
@ -15,12 +24,6 @@ import clsx from 'clsx';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import type { AllPageListConfig } from '../../../hooks/affine/use-all-page-list-config';
|
||||
import { FilterList } from '../../filter';
|
||||
import { List, ListScrollContainer } from '../../list';
|
||||
import type { ListItem } from '../../types';
|
||||
import { filterPageByRules } from '../../use-collection-manager';
|
||||
import { AffineShapeIcon } from '../affine-shape';
|
||||
import * as styles from './edit-collection.css';
|
||||
|
||||
export const RulesMode = ({
|
@ -1,9 +1,13 @@
|
||||
import { Avatar, ConfirmModal, Input, Switch, toast } from '@affine/component';
|
||||
import { Avatar, ConfirmModal, Input, Switch } from '@affine/component';
|
||||
import type { ConfirmModalProps } from '@affine/component/ui/modal';
|
||||
import { CloudSvg } from '@affine/core/components/affine/share-page-modal/cloud-svg';
|
||||
import { authAtom } from '@affine/core/components/atoms';
|
||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import { AuthService } from '@affine/core/modules/cloud';
|
||||
import {
|
||||
type DialogComponentProps,
|
||||
type GLOBAL_DIALOG_SCHEMA,
|
||||
} from '@affine/core/modules/dialogs';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { track } from '@affine/track';
|
||||
@ -11,21 +15,14 @@ import {
|
||||
FeatureFlagService,
|
||||
useLiveData,
|
||||
useService,
|
||||
useServiceOptional,
|
||||
WorkspacesService,
|
||||
} from '@toeverything/infra';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useCallback, useLayoutEffect, useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { AuthService } from '../../../modules/cloud';
|
||||
import { _addLocalWorkspace } from '../../../modules/workspace-engine';
|
||||
import { buildShowcaseWorkspace } from '../../../utils/first-app-data';
|
||||
import { DesktopApiService } from '../../desktop-api';
|
||||
import { CreateWorkspaceDialogService } from '../services/dialog';
|
||||
import * as styles from './dialog.css';
|
||||
|
||||
const logger = new DebugLogger('CreateWorkspaceModal');
|
||||
|
||||
interface NameWorkspaceContentProps extends ConfirmModalProps {
|
||||
loading: boolean;
|
||||
onConfirmName: (
|
||||
@ -157,53 +154,11 @@ const NameWorkspaceContent = ({
|
||||
);
|
||||
};
|
||||
|
||||
const CreateWorkspaceDialog = () => {
|
||||
const createWorkspaceDialogService = useService(CreateWorkspaceDialogService);
|
||||
const mode = useLiveData(createWorkspaceDialogService.dialog.mode$);
|
||||
const t = useI18n();
|
||||
export const CreateWorkspaceDialog = ({
|
||||
close,
|
||||
}: DialogComponentProps<GLOBAL_DIALOG_SCHEMA['create-workspace']>) => {
|
||||
const workspacesService = useService(WorkspacesService);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const electronApi = useServiceOptional(DesktopApiService);
|
||||
|
||||
// TODO(@Peng): maybe refactor using xstate?
|
||||
useLayoutEffect(() => {
|
||||
let canceled = false;
|
||||
// if mode changed, reset step
|
||||
if (mode === 'add') {
|
||||
// a hack for now
|
||||
// when adding a workspace, we will immediately let user select a db file
|
||||
// after it is done, it will effectively add a new workspace to app-data folder
|
||||
// so after that, we will be able to load it via importLocalWorkspace
|
||||
(async () => {
|
||||
if (!electronApi) {
|
||||
return;
|
||||
}
|
||||
logger.info('load db file');
|
||||
const result = await electronApi.handler.dialog.loadDBFile();
|
||||
if (result.workspaceId && !canceled) {
|
||||
_addLocalWorkspace(result.workspaceId);
|
||||
workspacesService.list.revalidate();
|
||||
createWorkspaceDialogService.dialog.callback({
|
||||
meta: {
|
||||
flavour: WorkspaceFlavour.LOCAL,
|
||||
id: result.workspaceId,
|
||||
},
|
||||
});
|
||||
} else if (result.error || result.canceled) {
|
||||
if (result.error) {
|
||||
toast(t[result.error]());
|
||||
}
|
||||
createWorkspaceDialogService.dialog.callback(undefined);
|
||||
createWorkspaceDialogService.dialog.close();
|
||||
}
|
||||
})().catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
canceled = true;
|
||||
};
|
||||
}, [createWorkspaceDialogService, electronApi, mode, t, workspacesService]);
|
||||
|
||||
const onConfirmName = useAsyncCallback(
|
||||
async (name: string, workspaceFlavour: WorkspaceFlavour) => {
|
||||
@ -218,40 +173,28 @@ const CreateWorkspaceDialog = () => {
|
||||
workspaceFlavour,
|
||||
name
|
||||
);
|
||||
createWorkspaceDialogService.dialog.callback({ meta, defaultDocId });
|
||||
|
||||
createWorkspaceDialogService.dialog.close();
|
||||
close({ metadata: meta, defaultDocId });
|
||||
setLoading(false);
|
||||
},
|
||||
[createWorkspaceDialogService.dialog, loading, workspacesService]
|
||||
[loading, workspacesService, close]
|
||||
);
|
||||
|
||||
const onOpenChange = useCallback(
|
||||
(open: boolean) => {
|
||||
if (!open) {
|
||||
createWorkspaceDialogService.dialog.close();
|
||||
close();
|
||||
}
|
||||
},
|
||||
[createWorkspaceDialogService]
|
||||
[close]
|
||||
);
|
||||
|
||||
if (mode === 'new') {
|
||||
return (
|
||||
<NameWorkspaceContent
|
||||
loading={loading}
|
||||
open
|
||||
onOpenChange={onOpenChange}
|
||||
onConfirmName={onConfirmName}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const CreateWorkspaceDialogProvider = () => {
|
||||
const createWorkspaceDialogService = useService(CreateWorkspaceDialogService);
|
||||
const isOpen = useLiveData(createWorkspaceDialogService.dialog.isOpen$);
|
||||
|
||||
return isOpen ? <CreateWorkspaceDialog /> : null;
|
||||
return (
|
||||
<NameWorkspaceContent
|
||||
loading={loading}
|
||||
open
|
||||
onOpenChange={onOpenChange}
|
||||
onConfirmName={onConfirmName}
|
||||
/>
|
||||
);
|
||||
};
|
@ -0,0 +1,62 @@
|
||||
import { Modal, Scrollable } from '@affine/component';
|
||||
import { BlocksuiteHeaderTitle } from '@affine/core/components/blocksuite/block-suite-header/title';
|
||||
import type { DialogComponentProps } from '@affine/core/modules/dialogs';
|
||||
import type { WORKSPACE_DIALOG_SCHEMA } from '@affine/core/modules/dialogs/constant';
|
||||
import type { Doc } from '@toeverything/infra';
|
||||
import { DocsService, FrameworkScope, useService } from '@toeverything/infra';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { InfoTable } from './info-modal';
|
||||
import * as styles from './styles.css';
|
||||
|
||||
export const DocInfoDialog = ({
|
||||
close,
|
||||
docId,
|
||||
}: DialogComponentProps<WORKSPACE_DIALOG_SCHEMA['doc-info']>) => {
|
||||
const docsService = useService(DocsService);
|
||||
|
||||
const [doc, setDoc] = useState<Doc | null>(null);
|
||||
useEffect(() => {
|
||||
if (!docId) return;
|
||||
const docRef = docsService.open(docId);
|
||||
setDoc(docRef.doc);
|
||||
return () => {
|
||||
docRef.release();
|
||||
setDoc(null);
|
||||
};
|
||||
}, [docId, docsService]);
|
||||
|
||||
if (!doc || !docId) return null;
|
||||
|
||||
return (
|
||||
<FrameworkScope scope={doc.scope}>
|
||||
<Modal
|
||||
contentOptions={{
|
||||
className: styles.container,
|
||||
}}
|
||||
open
|
||||
onOpenChange={() => close()}
|
||||
withoutCloseButton
|
||||
>
|
||||
<Scrollable.Root>
|
||||
<Scrollable.Viewport
|
||||
className={styles.viewport}
|
||||
data-testid="info-modal"
|
||||
>
|
||||
<div
|
||||
className={styles.titleContainer}
|
||||
data-testid="info-modal-title"
|
||||
>
|
||||
<BlocksuiteHeaderTitle
|
||||
docId={docId}
|
||||
className={styles.titleStyle}
|
||||
/>
|
||||
</div>
|
||||
<InfoTable docId={docId} onClose={() => close()} />
|
||||
</Scrollable.Viewport>
|
||||
<Scrollable.Scrollbar className={styles.scrollBar} />
|
||||
</Scrollable.Root>
|
||||
</Modal>
|
||||
</FrameworkScope>
|
||||
);
|
||||
};
|
@ -1,99 +1,27 @@
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
type InlineEditHandle,
|
||||
Menu,
|
||||
Modal,
|
||||
PropertyCollapsibleContent,
|
||||
PropertyCollapsibleSection,
|
||||
Scrollable,
|
||||
} from '@affine/component';
|
||||
import {
|
||||
DocDatabaseBacklinkInfo,
|
||||
DocInfoService,
|
||||
} from '@affine/core/modules/doc-info';
|
||||
import { CreatePropertyMenuItems } from '@affine/core/components/doc-properties/menu/create-doc-property';
|
||||
import { DocPropertyRow } from '@affine/core/components/doc-properties/table';
|
||||
import { DocDatabaseBacklinkInfo } from '@affine/core/modules/doc-info';
|
||||
import { DocsSearchService } from '@affine/core/modules/docs-search';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { PlusIcon } from '@blocksuite/icons/rc';
|
||||
import type { Doc } from '@toeverything/infra';
|
||||
import {
|
||||
DocsService,
|
||||
FrameworkScope,
|
||||
LiveData,
|
||||
useLiveData,
|
||||
useService,
|
||||
useServices,
|
||||
} from '@toeverything/infra';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import { BlocksuiteHeaderTitle } from '../../blocksuite/block-suite-header/title';
|
||||
import { CreatePropertyMenuItems } from '../menu/create-doc-property';
|
||||
import { DocPropertyRow } from '../table';
|
||||
import * as styles from './info-modal.css';
|
||||
import { LinksRow } from './links-row';
|
||||
|
||||
export const InfoModal = () => {
|
||||
const modal = useService(DocInfoService).modal;
|
||||
const docId = useLiveData(modal.docId$);
|
||||
const docsService = useService(DocsService);
|
||||
|
||||
const [doc, setDoc] = useState<Doc | null>(null);
|
||||
useEffect(() => {
|
||||
if (!docId) return;
|
||||
const docRef = docsService.open(docId);
|
||||
setDoc(docRef.doc);
|
||||
return () => {
|
||||
docRef.release();
|
||||
setDoc(null);
|
||||
};
|
||||
}, [docId, docsService]);
|
||||
|
||||
if (!doc || !docId) return null;
|
||||
|
||||
return (
|
||||
<FrameworkScope scope={doc.scope}>
|
||||
<InfoModalOpened docId={docId} />
|
||||
</FrameworkScope>
|
||||
);
|
||||
};
|
||||
|
||||
const InfoModalOpened = ({ docId }: { docId: string }) => {
|
||||
const modal = useService(DocInfoService).modal;
|
||||
|
||||
const titleInputHandleRef = useRef<InlineEditHandle>(null);
|
||||
const handleClose = useCallback(() => {
|
||||
modal.close();
|
||||
}, [modal]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
contentOptions={{
|
||||
className: styles.container,
|
||||
}}
|
||||
open
|
||||
onOpenChange={v => modal.onOpenChange(v)}
|
||||
withoutCloseButton
|
||||
>
|
||||
<Scrollable.Root>
|
||||
<Scrollable.Viewport
|
||||
className={styles.viewport}
|
||||
data-testid="info-modal"
|
||||
>
|
||||
<div className={styles.titleContainer} data-testid="info-modal-title">
|
||||
<BlocksuiteHeaderTitle
|
||||
docId={docId}
|
||||
className={styles.titleStyle}
|
||||
inputHandleRef={titleInputHandleRef}
|
||||
/>
|
||||
</div>
|
||||
<InfoTable docId={docId} onClose={handleClose} />
|
||||
</Scrollable.Viewport>
|
||||
<Scrollable.Scrollbar className={styles.scrollBar} />
|
||||
</Scrollable.Root>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export const InfoTable = ({
|
||||
onClose,
|
||||
docId,
|
@ -1,8 +1,8 @@
|
||||
import { PropertyCollapsibleSection } from '@affine/component';
|
||||
import { AffinePageReference } from '@affine/core/components/affine/reference-link';
|
||||
import type { Backlink, Link } from '@affine/core/modules/doc-link';
|
||||
import type { MouseEvent } from 'react';
|
||||
|
||||
import { AffinePageReference } from '../../affine/reference-link';
|
||||
import * as styles from './links-row.css';
|
||||
|
||||
export const LinksRow = ({
|
@ -0,0 +1,84 @@
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import { globalStyle, style } from '@vanilla-extract/css';
|
||||
|
||||
export const container = style({
|
||||
maxWidth: 480,
|
||||
minWidth: 360,
|
||||
padding: '20px 0',
|
||||
alignSelf: 'start',
|
||||
marginTop: '120px',
|
||||
});
|
||||
|
||||
export const titleContainer = style({
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
flexDirection: 'column',
|
||||
});
|
||||
|
||||
export const titleStyle = style({
|
||||
fontSize: cssVar('fontH6'),
|
||||
fontWeight: '600',
|
||||
});
|
||||
|
||||
export const rowNameContainer = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: 6,
|
||||
padding: 6,
|
||||
width: '160px',
|
||||
});
|
||||
|
||||
export const viewport = style({
|
||||
maxHeight: 'calc(100vh - 220px)',
|
||||
padding: '0 24px',
|
||||
});
|
||||
|
||||
export const scrollBar = style({
|
||||
width: 6,
|
||||
transform: 'translateX(-4px)',
|
||||
});
|
||||
|
||||
export const hiddenInput = style({
|
||||
width: '0',
|
||||
height: '0',
|
||||
position: 'absolute',
|
||||
});
|
||||
|
||||
export const timeRow = style({
|
||||
marginTop: 20,
|
||||
borderBottom: 4,
|
||||
});
|
||||
|
||||
export const tableBodyRoot = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
position: 'relative',
|
||||
});
|
||||
|
||||
export const addPropertyButton = style({
|
||||
alignSelf: 'flex-start',
|
||||
fontSize: cssVar('fontSm'),
|
||||
color: `${cssVarV2('text/secondary')}`,
|
||||
padding: '0 4px',
|
||||
height: 36,
|
||||
fontWeight: 400,
|
||||
gap: 6,
|
||||
'@media': {
|
||||
print: {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
selectors: {
|
||||
[`[data-property-collapsed="true"] &`]: {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
});
|
||||
globalStyle(`${addPropertyButton} svg`, {
|
||||
fontSize: 16,
|
||||
color: cssVarV2('icon/secondary'),
|
||||
});
|
||||
globalStyle(`${addPropertyButton}:hover svg`, {
|
||||
color: cssVarV2('icon/primary'),
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user