mirror of
https://github.com/colanode/colanode.git
synced 2025-12-16 11:47:47 +01:00
Use tanstackdb for temp files
This commit is contained in:
@@ -1,75 +0,0 @@
|
||||
import { SelectAccount } from '@colanode/client/databases/app';
|
||||
import { mapAccount } from '@colanode/client/lib/mappers';
|
||||
import { ChangeCheckResult, QueryHandler } from '@colanode/client/lib/types';
|
||||
import { AccountGetQueryInput } from '@colanode/client/queries/accounts/account-get';
|
||||
import { AppService } from '@colanode/client/services/app-service';
|
||||
import { Account } from '@colanode/client/types/accounts';
|
||||
import { Event } from '@colanode/client/types/events';
|
||||
|
||||
export class AccountGetQueryHandler
|
||||
implements QueryHandler<AccountGetQueryInput>
|
||||
{
|
||||
private readonly app: AppService;
|
||||
|
||||
constructor(app: AppService) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public async handleQuery(
|
||||
input: AccountGetQueryInput
|
||||
): Promise<Account | null> {
|
||||
const row = await this.fetchAccount(input.accountId);
|
||||
if (!row) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return mapAccount(row);
|
||||
}
|
||||
|
||||
public async checkForChanges(
|
||||
event: Event,
|
||||
input: AccountGetQueryInput
|
||||
): Promise<ChangeCheckResult<AccountGetQueryInput>> {
|
||||
if (
|
||||
event.type === 'account.created' &&
|
||||
event.account.id === input.accountId
|
||||
) {
|
||||
return {
|
||||
hasChanges: true,
|
||||
result: event.account,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
event.type === 'account.updated' &&
|
||||
event.account.id === input.accountId
|
||||
) {
|
||||
return {
|
||||
hasChanges: true,
|
||||
result: event.account,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
event.type === 'account.deleted' &&
|
||||
event.account.id === input.accountId
|
||||
) {
|
||||
return {
|
||||
hasChanges: true,
|
||||
result: null,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
hasChanges: false,
|
||||
};
|
||||
}
|
||||
|
||||
private fetchAccount(accountId: string): Promise<SelectAccount | undefined> {
|
||||
return this.app.database
|
||||
.selectFrom('accounts')
|
||||
.selectAll()
|
||||
.where('id', '=', accountId)
|
||||
.executeTakeFirst();
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
import { mapTempFile } from '@colanode/client/lib';
|
||||
import { ChangeCheckResult, QueryHandler } from '@colanode/client/lib/types';
|
||||
import { TempFileGetQueryInput } from '@colanode/client/queries';
|
||||
import { AppService } from '@colanode/client/services';
|
||||
import { Event } from '@colanode/client/types/events';
|
||||
import { TempFile } from '@colanode/client/types/files';
|
||||
|
||||
export class TempFileGetQueryHandler
|
||||
implements QueryHandler<TempFileGetQueryInput>
|
||||
{
|
||||
private readonly app: AppService;
|
||||
|
||||
public constructor(app: AppService) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public async handleQuery(
|
||||
input: TempFileGetQueryInput
|
||||
): Promise<TempFile | null> {
|
||||
return await this.fetchTempFile(input);
|
||||
}
|
||||
|
||||
public async checkForChanges(
|
||||
event: Event,
|
||||
input: TempFileGetQueryInput,
|
||||
_: TempFile | null
|
||||
): Promise<ChangeCheckResult<TempFileGetQueryInput>> {
|
||||
if (event.type === 'temp.file.created' && event.tempFile.id === input.id) {
|
||||
return {
|
||||
hasChanges: true,
|
||||
result: event.tempFile,
|
||||
};
|
||||
}
|
||||
|
||||
if (event.type === 'temp.file.deleted' && event.tempFile.id === input.id) {
|
||||
return {
|
||||
hasChanges: true,
|
||||
result: null,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
hasChanges: false,
|
||||
};
|
||||
}
|
||||
|
||||
private async fetchTempFile(
|
||||
input: TempFileGetQueryInput
|
||||
): Promise<TempFile | null> {
|
||||
const tempFile = await this.app.database
|
||||
.selectFrom('temp_files')
|
||||
.selectAll()
|
||||
.where('id', '=', input.id)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!tempFile) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const url = await this.app.fs.url(tempFile.path);
|
||||
return mapTempFile(tempFile, url);
|
||||
}
|
||||
}
|
||||
65
packages/client/src/handlers/queries/files/temp-file-list.ts
Normal file
65
packages/client/src/handlers/queries/files/temp-file-list.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { mapTempFile } from '@colanode/client/lib';
|
||||
import { ChangeCheckResult, QueryHandler } from '@colanode/client/lib/types';
|
||||
import { TempFileListQueryInput } from '@colanode/client/queries';
|
||||
import { AppService } from '@colanode/client/services';
|
||||
import { Event } from '@colanode/client/types/events';
|
||||
import { TempFile } from '@colanode/client/types/files';
|
||||
|
||||
export class TempFileListQueryHandler
|
||||
implements QueryHandler<TempFileListQueryInput>
|
||||
{
|
||||
private readonly app: AppService;
|
||||
|
||||
public constructor(app: AppService) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public async handleQuery(_: TempFileListQueryInput): Promise<TempFile[]> {
|
||||
return await this.fetchTempFiles();
|
||||
}
|
||||
|
||||
public async checkForChanges(
|
||||
event: Event,
|
||||
input: TempFileListQueryInput,
|
||||
output: TempFile[]
|
||||
): Promise<ChangeCheckResult<TempFileListQueryInput>> {
|
||||
if (event.type === 'temp.file.created') {
|
||||
const newResult = [...output, event.tempFile];
|
||||
|
||||
return {
|
||||
hasChanges: true,
|
||||
result: newResult,
|
||||
};
|
||||
}
|
||||
|
||||
if (event.type === 'temp.file.deleted') {
|
||||
const newResult = output.filter(
|
||||
(tempFile) => tempFile.id !== event.tempFile.id
|
||||
);
|
||||
|
||||
return {
|
||||
hasChanges: true,
|
||||
result: newResult,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
hasChanges: false,
|
||||
};
|
||||
}
|
||||
|
||||
private async fetchTempFiles(): Promise<TempFile[]> {
|
||||
const tempFiles = await this.app.database
|
||||
.selectFrom('temp_files')
|
||||
.selectAll()
|
||||
.execute();
|
||||
|
||||
const result: TempFile[] = [];
|
||||
for (const tempFile of tempFiles) {
|
||||
const url = await this.app.fs.url(tempFile.path);
|
||||
result.push(mapTempFile(tempFile, url));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import { QueryHandler } from '@colanode/client/lib/types';
|
||||
import { QueryMap } from '@colanode/client/queries';
|
||||
import { AppService } from '@colanode/client/services/app-service';
|
||||
|
||||
import { AccountGetQueryHandler } from './accounts/account-get';
|
||||
import { AccountListQueryHandler } from './accounts/accounts-list';
|
||||
import { MetadataListQueryHandler } from './apps/metadata-list';
|
||||
import { TabsListQueryHandler } from './apps/tabs-list';
|
||||
@@ -23,7 +22,7 @@ import { DownloadListQueryHandler } from './files/download-list';
|
||||
import { FileDownloadRequestGetQueryHandler } from './files/file-download-request-get';
|
||||
import { FileListQueryHandler } from './files/file-list';
|
||||
import { LocalFileGetQueryHandler } from './files/local-file-get';
|
||||
import { TempFileGetQueryHandler } from './files/temp-file-get';
|
||||
import { TempFileListQueryHandler } from './files/temp-file-list';
|
||||
import { UploadListQueryHandler } from './files/upload-list';
|
||||
import { IconCategoryListQueryHandler } from './icons/icon-category-list';
|
||||
import { IconListQueryHandler } from './icons/icon-list';
|
||||
@@ -77,7 +76,6 @@ export const buildQueryHandlerMap = (app: AppService): QueryHandlerMap => {
|
||||
'icon.category.list': new IconCategoryListQueryHandler(app),
|
||||
'node.children.get': new NodeChildrenGetQueryHandler(app),
|
||||
'radar.data.get': new RadarDataGetQueryHandler(app),
|
||||
'account.get': new AccountGetQueryHandler(app),
|
||||
'database.list': new DatabaseListQueryHandler(app),
|
||||
'database.view.list': new DatabaseViewListQueryHandler(app),
|
||||
'record.search': new RecordSearchQueryHandler(app),
|
||||
@@ -92,7 +90,7 @@ export const buildQueryHandlerMap = (app: AppService): QueryHandlerMap => {
|
||||
'workspace.storage.get': new WorkspaceStorageGetQueryHandler(app),
|
||||
'upload.list': new UploadListQueryHandler(app),
|
||||
'download.list': new DownloadListQueryHandler(app),
|
||||
'temp.file.get': new TempFileGetQueryHandler(app),
|
||||
'temp.file.list': new TempFileListQueryHandler(app),
|
||||
'icon.svg.get': new IconSvgGetQueryHandler(app),
|
||||
'emoji.svg.get': new EmojiSvgGetQueryHandler(app),
|
||||
'tabs.list': new TabsListQueryHandler(app),
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { Account } from '@colanode/client/types/accounts';
|
||||
|
||||
export type AccountGetQueryInput = {
|
||||
type: 'account.get';
|
||||
accountId: string;
|
||||
};
|
||||
|
||||
declare module '@colanode/client/queries' {
|
||||
interface QueryMap {
|
||||
'account.get': {
|
||||
input: AccountGetQueryInput;
|
||||
output: Account | null;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { TempFile } from '@colanode/client/types';
|
||||
|
||||
export type TempFileGetQueryInput = {
|
||||
type: 'temp.file.get';
|
||||
id: string;
|
||||
};
|
||||
|
||||
declare module '@colanode/client/queries' {
|
||||
interface QueryMap {
|
||||
'temp.file.get': {
|
||||
input: TempFileGetQueryInput;
|
||||
output: TempFile | null;
|
||||
};
|
||||
}
|
||||
}
|
||||
14
packages/client/src/queries/files/temp-file-list.ts
Normal file
14
packages/client/src/queries/files/temp-file-list.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { TempFile } from '@colanode/client/types';
|
||||
|
||||
export type TempFileListQueryInput = {
|
||||
type: 'temp.file.list';
|
||||
};
|
||||
|
||||
declare module '@colanode/client/queries' {
|
||||
interface QueryMap {
|
||||
'temp.file.list': {
|
||||
input: TempFileListQueryInput;
|
||||
output: TempFile[];
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { sha256 } from 'js-sha256';
|
||||
|
||||
export * from './accounts/account-get';
|
||||
export * from './accounts/account-list';
|
||||
export * from './apps/metadata-list';
|
||||
export * from './chats/chat-list';
|
||||
@@ -39,7 +38,7 @@ export * from './records/record-field-value-count';
|
||||
export * from './workspaces/workspace-storage-get';
|
||||
export * from './files/upload-list';
|
||||
export * from './files/download-list';
|
||||
export * from './files/temp-file-get';
|
||||
export * from './files/temp-file-list';
|
||||
export * from './icons/icon-svg-get';
|
||||
export * from './emojis/emoji-svg-get';
|
||||
export * from './apps/tabs-list';
|
||||
|
||||
@@ -6,6 +6,7 @@ import { createDownloadsCollection } from '@colanode/ui/data/downloads';
|
||||
import { createMetadataCollection } from '@colanode/ui/data/metadata';
|
||||
import { createServersCollection } from '@colanode/ui/data/servers';
|
||||
import { createTabsCollection } from '@colanode/ui/data/tabs';
|
||||
import { createTempFilesCollection } from '@colanode/ui/data/temp-files';
|
||||
import { createUploadsCollection } from '@colanode/ui/data/uploads';
|
||||
import { createUsersCollection } from '@colanode/ui/data/users';
|
||||
import { createWorkspacesCollection } from '@colanode/ui/data/workspaces';
|
||||
@@ -31,6 +32,7 @@ class AppDatabase {
|
||||
public readonly tabs = createTabsCollection();
|
||||
public readonly metadata = createMetadataCollection();
|
||||
public readonly workspaces = createWorkspacesCollection();
|
||||
public readonly tempFiles = createTempFilesCollection();
|
||||
|
||||
private readonly workspaceDatabases: Map<string, WorkspaceDatabase> =
|
||||
new Map();
|
||||
@@ -54,6 +56,7 @@ class AppDatabase {
|
||||
this.metadata.preload(),
|
||||
this.tabs.preload(),
|
||||
this.workspaces.preload(),
|
||||
this.tempFiles.preload(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
47
packages/ui/src/data/temp-files.ts
Normal file
47
packages/ui/src/data/temp-files.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { createCollection } from '@tanstack/react-db';
|
||||
|
||||
import { TempFile } from '@colanode/client/types';
|
||||
|
||||
export const createTempFilesCollection = () => {
|
||||
return createCollection<TempFile, string>({
|
||||
getKey(item) {
|
||||
return item.id;
|
||||
},
|
||||
sync: {
|
||||
sync({ begin, write, commit, markReady }) {
|
||||
window.colanode
|
||||
.executeQuery({
|
||||
type: 'temp.file.list',
|
||||
})
|
||||
.then((tempFiles) => {
|
||||
begin();
|
||||
|
||||
for (const tempFile of tempFiles) {
|
||||
write({ type: 'insert', value: tempFile });
|
||||
}
|
||||
|
||||
commit();
|
||||
markReady();
|
||||
});
|
||||
|
||||
const subscriptionId = window.eventBus.subscribe((event) => {
|
||||
if (event.type === 'temp.file.created') {
|
||||
begin();
|
||||
write({ type: 'insert', value: event.tempFile });
|
||||
commit();
|
||||
} else if (event.type === 'temp.file.deleted') {
|
||||
begin();
|
||||
write({ type: 'delete', value: event.tempFile });
|
||||
commit();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
cleanup: () => {
|
||||
window.eventBus.unsubscribe(subscriptionId);
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,49 +1,65 @@
|
||||
import { eq, useLiveQuery } from '@tanstack/react-db';
|
||||
import { type NodeViewProps } from '@tiptap/core';
|
||||
import { NodeViewWrapper } from '@tiptap/react';
|
||||
import { X } from 'lucide-react';
|
||||
|
||||
import { TempFile } from '@colanode/client/types';
|
||||
import { FileSubtype } from '@colanode/core';
|
||||
import { FileNoPreview } from '@colanode/ui/components/files/file-no-preview';
|
||||
import { FilePreviewAudio } from '@colanode/ui/components/files/previews/file-preview-audio';
|
||||
import { FilePreviewImage } from '@colanode/ui/components/files/previews/file-preview-image';
|
||||
import { FilePreviewVideo } from '@colanode/ui/components/files/previews/file-preview-video';
|
||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||
import { database } from '@colanode/ui/data';
|
||||
import { canPreviewFile } from '@colanode/ui/lib/files';
|
||||
|
||||
const TempFilePreview = ({ file }: { file: TempFile }) => {
|
||||
if (file.subtype === 'image') {
|
||||
return <FilePreviewImage url={file.url} name={file.name} />;
|
||||
interface TempFilePreviewProps {
|
||||
name: string;
|
||||
mimeType: string;
|
||||
subtype: FileSubtype;
|
||||
url: string;
|
||||
}
|
||||
|
||||
const TempFilePreview = ({
|
||||
name,
|
||||
mimeType,
|
||||
subtype,
|
||||
url,
|
||||
}: TempFilePreviewProps) => {
|
||||
if (subtype === 'image') {
|
||||
return <FilePreviewImage url={url} name={name} />;
|
||||
}
|
||||
|
||||
if (file.subtype === 'video') {
|
||||
return <FilePreviewVideo url={file.url} />;
|
||||
if (subtype === 'video') {
|
||||
return <FilePreviewVideo url={url} />;
|
||||
}
|
||||
|
||||
if (file.subtype === 'audio') {
|
||||
return <FilePreviewAudio url={file.url} />;
|
||||
if (subtype === 'audio') {
|
||||
return <FilePreviewAudio url={url} />;
|
||||
}
|
||||
|
||||
return <FileNoPreview mimeType={file.mimeType} />;
|
||||
return <FileNoPreview mimeType={mimeType} />;
|
||||
};
|
||||
|
||||
export const TempFileNodeView = ({ node, deleteNode }: NodeViewProps) => {
|
||||
const fileId = node.attrs.id;
|
||||
|
||||
const tempFileQuery = useLiveQuery(
|
||||
{
|
||||
type: 'temp.file.get',
|
||||
id: fileId,
|
||||
},
|
||||
{
|
||||
enabled: !!fileId,
|
||||
}
|
||||
const tempFileQuery = useLiveQuery((q) =>
|
||||
q
|
||||
.from({ tempFiles: database.tempFiles })
|
||||
.where(({ tempFiles }) => eq(tempFiles.id, fileId))
|
||||
.select(({ tempFiles }) => ({
|
||||
name: tempFiles.name,
|
||||
mimeType: tempFiles.mimeType,
|
||||
subtype: tempFiles.subtype,
|
||||
url: tempFiles.url,
|
||||
}))
|
||||
.findOne()
|
||||
);
|
||||
|
||||
if (!fileId || tempFileQuery.isPending || !tempFileQuery.data) {
|
||||
const tempFile = tempFileQuery.data;
|
||||
if (!fileId || !tempFile) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tempFile = tempFileQuery.data;
|
||||
const mimeType = tempFile.mimeType;
|
||||
const subtype = tempFile.subtype;
|
||||
const canPreview = canPreviewFile(subtype);
|
||||
@@ -61,7 +77,12 @@ export const TempFileNodeView = ({ node, deleteNode }: NodeViewProps) => {
|
||||
<X className="size-4" />
|
||||
</button>
|
||||
{canPreview ? (
|
||||
<TempFilePreview file={tempFile} />
|
||||
<TempFilePreview
|
||||
name={tempFile.name}
|
||||
mimeType={tempFile.mimeType}
|
||||
subtype={tempFile.subtype}
|
||||
url={tempFile.url}
|
||||
/>
|
||||
) : (
|
||||
<FileNoPreview mimeType={mimeType} />
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user