mirror of
https://github.com/colanode/colanode.git
synced 2025-12-16 11:47:47 +01:00
Remove react router
This commit is contained in:
@@ -104,7 +104,6 @@
|
|||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-hook-form": "^7.53.2",
|
"react-hook-form": "^7.53.2",
|
||||||
"react-intersection-observer": "^9.16.0",
|
"react-intersection-observer": "^9.16.0",
|
||||||
"react-router-dom": "^7.5.0",
|
|
||||||
"semver": "^7.7.1",
|
"semver": "^7.7.1",
|
||||||
"tailwind-merge": "^3.2.0",
|
"tailwind-merge": "^3.2.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { Migration } from 'kysely';
|
||||||
|
|
||||||
|
export const createMetadataTable: Migration = {
|
||||||
|
up: async (db) => {
|
||||||
|
await db.schema
|
||||||
|
.createTable('metadata')
|
||||||
|
.addColumn('key', 'text', (col) => col.notNull().primaryKey())
|
||||||
|
.addColumn('value', 'text', (col) => col.notNull())
|
||||||
|
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||||
|
.addColumn('updated_at', 'text')
|
||||||
|
.execute();
|
||||||
|
},
|
||||||
|
down: async (db) => {
|
||||||
|
await db.schema.dropTable('metadata').execute();
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Migration } from 'kysely';
|
import { Migration } from 'kysely';
|
||||||
|
|
||||||
import { createWorkspacesTable } from './00001-create-workspaces-table';
|
import { createWorkspacesTable } from './00001-create-workspaces-table';
|
||||||
|
import { createMetadataTable } from './00002-create-metadata-table';
|
||||||
|
|
||||||
export const accountDatabaseMigrations: Record<string, Migration> = {
|
export const accountDatabaseMigrations: Record<string, Migration> = {
|
||||||
'00001-create-workspaces-table': createWorkspacesTable,
|
'00001-create-workspaces-table': createWorkspacesTable,
|
||||||
|
'00002-create-metadata-table': createMetadataTable,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,6 +18,18 @@ export type SelectWorkspace = Selectable<WorkspaceTable>;
|
|||||||
export type CreateWorkspace = Insertable<WorkspaceTable>;
|
export type CreateWorkspace = Insertable<WorkspaceTable>;
|
||||||
export type UpdateWorkspace = Updateable<WorkspaceTable>;
|
export type UpdateWorkspace = Updateable<WorkspaceTable>;
|
||||||
|
|
||||||
|
interface AccountMetadataTable {
|
||||||
|
key: ColumnType<string, string, never>;
|
||||||
|
value: ColumnType<string, string, string>;
|
||||||
|
created_at: ColumnType<string, string, never>;
|
||||||
|
updated_at: ColumnType<string | null, string | null, string | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SelectAccountMetadata = Selectable<AccountMetadataTable>;
|
||||||
|
export type CreateAccountMetadata = Insertable<AccountMetadataTable>;
|
||||||
|
export type UpdateAccountMetadata = Updateable<AccountMetadataTable>;
|
||||||
|
|
||||||
export interface AccountDatabaseSchema {
|
export interface AccountDatabaseSchema {
|
||||||
workspaces: WorkspaceTable;
|
workspaces: WorkspaceTable;
|
||||||
|
metadata: AccountMetadataTable;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ import {
|
|||||||
} from '@/main/databases/app';
|
} from '@/main/databases/app';
|
||||||
import { SelectEmoji } from '@/main/databases/emojis';
|
import { SelectEmoji } from '@/main/databases/emojis';
|
||||||
import { SelectIcon } from '@/main/databases/icons';
|
import { SelectIcon } from '@/main/databases/icons';
|
||||||
import { SelectWorkspace } from '@/main/databases/account';
|
import {
|
||||||
|
SelectAccountMetadata,
|
||||||
|
SelectWorkspace,
|
||||||
|
} from '@/main/databases/account';
|
||||||
import {
|
import {
|
||||||
SelectFileState,
|
SelectFileState,
|
||||||
SelectMutation,
|
SelectMutation,
|
||||||
@@ -20,7 +23,11 @@ import {
|
|||||||
SelectDocumentState,
|
SelectDocumentState,
|
||||||
SelectDocumentUpdate,
|
SelectDocumentUpdate,
|
||||||
} from '@/main/databases/workspace';
|
} from '@/main/databases/workspace';
|
||||||
import { Account } from '@/shared/types/accounts';
|
import {
|
||||||
|
Account,
|
||||||
|
AccountMetadata,
|
||||||
|
AccountMetadataKey,
|
||||||
|
} from '@/shared/types/accounts';
|
||||||
import { Server } from '@/shared/types/servers';
|
import { Server } from '@/shared/types/servers';
|
||||||
import { User } from '@/shared/types/users';
|
import { User } from '@/shared/types/users';
|
||||||
import { FileState } from '@/shared/types/files';
|
import { FileState } from '@/shared/types/files';
|
||||||
@@ -223,6 +230,17 @@ export const mapAppMetadata = (row: SelectAppMetadata): AppMetadata => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const mapAccountMetadata = (
|
||||||
|
row: SelectAccountMetadata
|
||||||
|
): AccountMetadata => {
|
||||||
|
return {
|
||||||
|
key: row.key as AccountMetadataKey,
|
||||||
|
value: JSON.parse(row.value),
|
||||||
|
createdAt: row.created_at,
|
||||||
|
updatedAt: row.updated_at,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const mapWorkspaceMetadata = (
|
export const mapWorkspaceMetadata = (
|
||||||
row: SelectWorkspaceMetadata
|
row: SelectWorkspaceMetadata
|
||||||
): WorkspaceMetadata => {
|
): WorkspaceMetadata => {
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { MutationHandler } from '@/main/lib/types';
|
||||||
|
import { eventBus } from '@/shared/lib/event-bus';
|
||||||
|
import { mapAccountMetadata } from '@/main/lib/mappers';
|
||||||
|
import { appService } from '@/main/services/app-service';
|
||||||
|
import {
|
||||||
|
AccountMetadataDeleteMutationInput,
|
||||||
|
AccountMetadataDeleteMutationOutput,
|
||||||
|
} from '@/shared/mutations/accounts/account-metadata-delete';
|
||||||
|
|
||||||
|
export class AccountMetadataDeleteMutationHandler
|
||||||
|
implements MutationHandler<AccountMetadataDeleteMutationInput>
|
||||||
|
{
|
||||||
|
async handleMutation(
|
||||||
|
input: AccountMetadataDeleteMutationInput
|
||||||
|
): Promise<AccountMetadataDeleteMutationOutput> {
|
||||||
|
const account = appService.getAccount(input.accountId);
|
||||||
|
|
||||||
|
if (!account) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletedMetadata = await account.database
|
||||||
|
.deleteFrom('metadata')
|
||||||
|
.where('key', '=', input.key)
|
||||||
|
.returningAll()
|
||||||
|
.executeTakeFirst();
|
||||||
|
|
||||||
|
if (!deletedMetadata) {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
eventBus.publish({
|
||||||
|
type: 'account_metadata_deleted',
|
||||||
|
accountId: input.accountId,
|
||||||
|
metadata: mapAccountMetadata(deletedMetadata),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { MutationHandler } from '@/main/lib/types';
|
||||||
|
import { eventBus } from '@/shared/lib/event-bus';
|
||||||
|
import { mapAccountMetadata } from '@/main/lib/mappers';
|
||||||
|
import { appService } from '@/main/services/app-service';
|
||||||
|
import {
|
||||||
|
AccountMetadataSaveMutationInput,
|
||||||
|
AccountMetadataSaveMutationOutput,
|
||||||
|
} from '@/shared/mutations/accounts/account-metadata-save';
|
||||||
|
|
||||||
|
export class AccountMetadataSaveMutationHandler
|
||||||
|
implements MutationHandler<AccountMetadataSaveMutationInput>
|
||||||
|
{
|
||||||
|
async handleMutation(
|
||||||
|
input: AccountMetadataSaveMutationInput
|
||||||
|
): Promise<AccountMetadataSaveMutationOutput> {
|
||||||
|
const account = appService.getAccount(input.accountId);
|
||||||
|
|
||||||
|
if (!account) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdMetadata = await account.database
|
||||||
|
.insertInto('metadata')
|
||||||
|
.returningAll()
|
||||||
|
.values({
|
||||||
|
key: input.key,
|
||||||
|
value: JSON.stringify(input.value),
|
||||||
|
created_at: new Date().toISOString(),
|
||||||
|
})
|
||||||
|
.onConflict((cb) =>
|
||||||
|
cb.columns(['key']).doUpdateSet({
|
||||||
|
value: JSON.stringify(input.value),
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.executeTakeFirst();
|
||||||
|
|
||||||
|
if (!createdMetadata) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
eventBus.publish({
|
||||||
|
type: 'account_metadata_saved',
|
||||||
|
accountId: input.accountId,
|
||||||
|
metadata: mapAccountMetadata(createdMetadata),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
37
apps/desktop/src/main/mutations/apps/app-metadata-delete.ts
Normal file
37
apps/desktop/src/main/mutations/apps/app-metadata-delete.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { MutationHandler } from '@/main/lib/types';
|
||||||
|
import { eventBus } from '@/shared/lib/event-bus';
|
||||||
|
import { mapAppMetadata } from '@/main/lib/mappers';
|
||||||
|
import { appService } from '@/main/services/app-service';
|
||||||
|
import {
|
||||||
|
AppMetadataDeleteMutationInput,
|
||||||
|
AppMetadataDeleteMutationOutput,
|
||||||
|
} from '@/shared/mutations/apps/app-metadata-delete';
|
||||||
|
|
||||||
|
export class AppMetadataDeleteMutationHandler
|
||||||
|
implements MutationHandler<AppMetadataDeleteMutationInput>
|
||||||
|
{
|
||||||
|
async handleMutation(
|
||||||
|
input: AppMetadataDeleteMutationInput
|
||||||
|
): Promise<AppMetadataDeleteMutationOutput> {
|
||||||
|
const deletedMetadata = await appService.database
|
||||||
|
.deleteFrom('metadata')
|
||||||
|
.where('key', '=', input.key)
|
||||||
|
.returningAll()
|
||||||
|
.executeTakeFirst();
|
||||||
|
|
||||||
|
if (!deletedMetadata) {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
eventBus.publish({
|
||||||
|
type: 'app_metadata_deleted',
|
||||||
|
metadata: mapAppMetadata(deletedMetadata),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
47
apps/desktop/src/main/mutations/apps/app-metadata-save.ts
Normal file
47
apps/desktop/src/main/mutations/apps/app-metadata-save.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { MutationHandler } from '@/main/lib/types';
|
||||||
|
import {
|
||||||
|
AppMetadataSaveMutationInput,
|
||||||
|
AppMetadataSaveMutationOutput,
|
||||||
|
} from '@/shared/mutations/apps/app-metadata-save';
|
||||||
|
import { eventBus } from '@/shared/lib/event-bus';
|
||||||
|
import { mapAppMetadata } from '@/main/lib/mappers';
|
||||||
|
import { appService } from '@/main/services/app-service';
|
||||||
|
|
||||||
|
export class AppMetadataSaveMutationHandler
|
||||||
|
implements MutationHandler<AppMetadataSaveMutationInput>
|
||||||
|
{
|
||||||
|
async handleMutation(
|
||||||
|
input: AppMetadataSaveMutationInput
|
||||||
|
): Promise<AppMetadataSaveMutationOutput> {
|
||||||
|
const createdMetadata = await appService.database
|
||||||
|
.insertInto('metadata')
|
||||||
|
.returningAll()
|
||||||
|
.values({
|
||||||
|
key: input.key,
|
||||||
|
value: JSON.stringify(input.value),
|
||||||
|
created_at: new Date().toISOString(),
|
||||||
|
})
|
||||||
|
.onConflict((cb) =>
|
||||||
|
cb.columns(['key']).doUpdateSet({
|
||||||
|
value: JSON.stringify(input.value),
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.executeTakeFirst();
|
||||||
|
|
||||||
|
if (!createdMetadata) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
eventBus.publish({
|
||||||
|
type: 'app_metadata_saved',
|
||||||
|
metadata: mapAppMetadata(createdMetadata),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -59,6 +59,10 @@ import { UsersInviteMutationHandler } from '@/main/mutations/users/users-invite'
|
|||||||
import { WorkspaceMetadataSaveMutationHandler } from '@/main/mutations/workspaces/workspace-metadata-save';
|
import { WorkspaceMetadataSaveMutationHandler } from '@/main/mutations/workspaces/workspace-metadata-save';
|
||||||
import { WorkspaceMetadataDeleteMutationHandler } from '@/main/mutations/workspaces/workspace-metadata-delete';
|
import { WorkspaceMetadataDeleteMutationHandler } from '@/main/mutations/workspaces/workspace-metadata-delete';
|
||||||
import { DocumentUpdateMutationHandler } from '@/main/mutations/documents/document-update';
|
import { DocumentUpdateMutationHandler } from '@/main/mutations/documents/document-update';
|
||||||
|
import { AppMetadataSaveMutationHandler } from '@/main/mutations/apps/app-metadata-save';
|
||||||
|
import { AppMetadataDeleteMutationHandler } from '@/main/mutations/apps/app-metadata-delete';
|
||||||
|
import { AccountMetadataSaveMutationHandler } from '@/main/mutations/accounts/account-metadata-save';
|
||||||
|
import { AccountMetadataDeleteMutationHandler } from '@/main/mutations/accounts/account-metadata-delete';
|
||||||
import { MutationHandler } from '@/main/lib/types';
|
import { MutationHandler } from '@/main/lib/types';
|
||||||
import { MutationMap } from '@/shared/mutations';
|
import { MutationMap } from '@/shared/mutations';
|
||||||
|
|
||||||
@@ -128,4 +132,8 @@ export const mutationHandlerMap: MutationHandlerMap = {
|
|||||||
workspace_metadata_save: new WorkspaceMetadataSaveMutationHandler(),
|
workspace_metadata_save: new WorkspaceMetadataSaveMutationHandler(),
|
||||||
workspace_metadata_delete: new WorkspaceMetadataDeleteMutationHandler(),
|
workspace_metadata_delete: new WorkspaceMetadataDeleteMutationHandler(),
|
||||||
document_update: new DocumentUpdateMutationHandler(),
|
document_update: new DocumentUpdateMutationHandler(),
|
||||||
|
app_metadata_save: new AppMetadataSaveMutationHandler(),
|
||||||
|
app_metadata_delete: new AppMetadataDeleteMutationHandler(),
|
||||||
|
account_metadata_save: new AccountMetadataSaveMutationHandler(),
|
||||||
|
account_metadata_delete: new AccountMetadataDeleteMutationHandler(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ export class WorkspaceMetadataSaveMutationHandler
|
|||||||
.returningAll()
|
.returningAll()
|
||||||
.values({
|
.values({
|
||||||
key: input.key,
|
key: input.key,
|
||||||
value: input.value,
|
value: JSON.stringify(input.value),
|
||||||
created_at: new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
})
|
})
|
||||||
.onConflict((cb) =>
|
.onConflict((cb) =>
|
||||||
cb.columns(['key']).doUpdateSet({
|
cb.columns(['key']).doUpdateSet({
|
||||||
value: input.value,
|
value: JSON.stringify(input.value),
|
||||||
updated_at: new Date().toISOString(),
|
updated_at: new Date().toISOString(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -38,7 +38,7 @@ export class WorkspaceMetadataSaveMutationHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
eventBus.publish({
|
eventBus.publish({
|
||||||
type: 'workspace_metadata_updated',
|
type: 'workspace_metadata_saved',
|
||||||
accountId: input.accountId,
|
accountId: input.accountId,
|
||||||
workspaceId: input.workspaceId,
|
workspaceId: input.workspaceId,
|
||||||
metadata: mapWorkspaceMetadata(createdMetadata),
|
metadata: mapWorkspaceMetadata(createdMetadata),
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import { ChangeCheckResult, QueryHandler } from '@/main/lib/types';
|
||||||
|
import { mapAccountMetadata } from '@/main/lib/mappers';
|
||||||
|
import { AccountMetadataListQueryInput } from '@/shared/queries/accounts/account-metadata-list';
|
||||||
|
import { Event } from '@/shared/types/events';
|
||||||
|
import { AccountMetadata } from '@/shared/types/accounts';
|
||||||
|
import { SelectAccountMetadata } from '@/main/databases/account/schema';
|
||||||
|
import { appService } from '@/main/services/app-service';
|
||||||
|
|
||||||
|
export class AccountMetadataListQueryHandler
|
||||||
|
implements QueryHandler<AccountMetadataListQueryInput>
|
||||||
|
{
|
||||||
|
public async handleQuery(
|
||||||
|
input: AccountMetadataListQueryInput
|
||||||
|
): Promise<AccountMetadata[]> {
|
||||||
|
const rows = await this.getAccountMetadata(input.accountId);
|
||||||
|
if (!rows) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows.map(mapAccountMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async checkForChanges(
|
||||||
|
event: Event,
|
||||||
|
input: AccountMetadataListQueryInput,
|
||||||
|
output: AccountMetadata[]
|
||||||
|
): Promise<ChangeCheckResult<AccountMetadataListQueryInput>> {
|
||||||
|
if (
|
||||||
|
event.type === 'account_created' &&
|
||||||
|
event.account.id === input.accountId
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
hasChanges: true,
|
||||||
|
result: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
event.type === 'account_metadata_saved' &&
|
||||||
|
event.accountId === input.accountId
|
||||||
|
) {
|
||||||
|
const newOutput = [
|
||||||
|
...output.filter((metadata) => metadata.key !== event.metadata.key),
|
||||||
|
event.metadata,
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasChanges: true,
|
||||||
|
result: newOutput,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
event.type === 'account_metadata_deleted' &&
|
||||||
|
event.accountId === input.accountId
|
||||||
|
) {
|
||||||
|
const newOutput = output.filter(
|
||||||
|
(metadata) => metadata.key !== event.metadata.key
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasChanges: true,
|
||||||
|
result: newOutput,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasChanges: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getAccountMetadata(
|
||||||
|
accountId: string
|
||||||
|
): Promise<SelectAccountMetadata[] | undefined> {
|
||||||
|
const account = appService.getAccount(accountId);
|
||||||
|
|
||||||
|
if (!account) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = await account.database
|
||||||
|
.selectFrom('metadata')
|
||||||
|
.selectAll()
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,13 +25,11 @@ export class AppMetadataListQueryHandler
|
|||||||
_: AppMetadataListQueryInput,
|
_: AppMetadataListQueryInput,
|
||||||
output: AppMetadata[]
|
output: AppMetadata[]
|
||||||
): Promise<ChangeCheckResult<AppMetadataListQueryInput>> {
|
): Promise<ChangeCheckResult<AppMetadataListQueryInput>> {
|
||||||
if (event.type === 'app_metadata_updated') {
|
if (event.type === 'app_metadata_saved') {
|
||||||
const newOutput = output.map((metadata) => {
|
const newOutput = [
|
||||||
if (metadata.key === event.metadata.key) {
|
...output.filter((metadata) => metadata.key !== event.metadata.key),
|
||||||
return event.metadata;
|
event.metadata,
|
||||||
}
|
];
|
||||||
return metadata;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hasChanges: true,
|
hasChanges: true,
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import { ChatListQueryHandler } from '@/main/queries/chats/chat-list';
|
|||||||
import { DocumentGetQueryHandler } from '@/main/queries/documents/document-get';
|
import { DocumentGetQueryHandler } from '@/main/queries/documents/document-get';
|
||||||
import { DocumentStateGetQueryHandler } from '@/main/queries/documents/document-state-get';
|
import { DocumentStateGetQueryHandler } from '@/main/queries/documents/document-state-get';
|
||||||
import { DocumentUpdatesListQueryHandler } from '@/main/queries/documents/document-update-list';
|
import { DocumentUpdatesListQueryHandler } from '@/main/queries/documents/document-update-list';
|
||||||
|
import { AccountMetadataListQueryHandler } from '@/main/queries/accounts/account-metadata-list';
|
||||||
import { WorkspaceMetadataListQueryHandler } from '@/main/queries/workspaces/workspace-metadata-list';
|
import { WorkspaceMetadataListQueryHandler } from '@/main/queries/workspaces/workspace-metadata-list';
|
||||||
import { QueryHandler } from '@/main/lib/types';
|
import { QueryHandler } from '@/main/lib/types';
|
||||||
import { QueryMap } from '@/shared/queries';
|
import { QueryMap } from '@/shared/queries';
|
||||||
@@ -80,4 +81,5 @@ export const queryHandlerMap: QueryHandlerMap = {
|
|||||||
document_get: new DocumentGetQueryHandler(),
|
document_get: new DocumentGetQueryHandler(),
|
||||||
document_state_get: new DocumentStateGetQueryHandler(),
|
document_state_get: new DocumentStateGetQueryHandler(),
|
||||||
document_updates_list: new DocumentUpdatesListQueryHandler(),
|
document_updates_list: new DocumentUpdatesListQueryHandler(),
|
||||||
|
account_metadata_list: new AccountMetadataListQueryHandler(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,16 +41,14 @@ export class WorkspaceMetadataListQueryHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
event.type === 'workspace_metadata_updated' &&
|
event.type === 'workspace_metadata_saved' &&
|
||||||
event.accountId === input.accountId &&
|
event.accountId === input.accountId &&
|
||||||
event.workspaceId === input.workspaceId
|
event.workspaceId === input.workspaceId
|
||||||
) {
|
) {
|
||||||
const newOutput = output.map((metadata) => {
|
const newOutput = [
|
||||||
if (metadata.key === event.metadata.key) {
|
...output.filter((metadata) => metadata.key !== event.metadata.key),
|
||||||
return event.metadata;
|
event.metadata,
|
||||||
}
|
];
|
||||||
return metadata;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hasChanges: true,
|
hasChanges: true,
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export class MetadataService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
eventBus.publish({
|
eventBus.publish({
|
||||||
type: 'app_metadata_updated',
|
type: 'app_metadata_saved',
|
||||||
metadata: mapAppMetadata(createdMetadata),
|
metadata: mapAppMetadata(createdMetadata),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,32 @@
|
|||||||
import { Outlet } from 'react-router-dom';
|
import { useState, useEffect } from 'react';
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { AppContext } from './contexts/app';
|
|
||||||
|
|
||||||
|
import { AppContext } from '@/renderer/contexts/app';
|
||||||
import { DelayedComponent } from '@/renderer/components/ui/delayed-component';
|
import { DelayedComponent } from '@/renderer/components/ui/delayed-component';
|
||||||
import { AppLoader } from '@/renderer/app-loader';
|
import { AppLoader } from '@/renderer/app-loader';
|
||||||
import { useQuery } from '@/renderer/hooks/use-query';
|
import { useQuery } from '@/renderer/hooks/use-query';
|
||||||
import { RadarProvider } from '@/renderer/radar-provider';
|
import { RadarProvider } from '@/renderer/radar-provider';
|
||||||
|
import { Account } from '@/renderer/components/accounts/account';
|
||||||
|
import { Login } from '@/renderer/components/accounts/login';
|
||||||
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
const [initialized, setInitialized] = React.useState(false);
|
const [initialized, setInitialized] = useState(false);
|
||||||
|
const [openLogin, setOpenLogin] = useState(false);
|
||||||
|
|
||||||
const { data, isPending } = useQuery({
|
const { data: metadata, isPending: isPendingMetadata } = useQuery({
|
||||||
type: 'app_metadata_list',
|
type: 'app_metadata_list',
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
const { data: accounts, isPending: isPendingAccounts } = useQuery({
|
||||||
|
type: 'account_list',
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
window.colanode.init().then(() => {
|
window.colanode.init().then(() => {
|
||||||
setInitialized(true);
|
setInitialized(true);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!initialized || isPending) {
|
if (!initialized || isPendingMetadata || isPendingAccounts) {
|
||||||
return (
|
return (
|
||||||
<DelayedComponent>
|
<DelayedComponent>
|
||||||
<AppLoader />
|
<AppLoader />
|
||||||
@@ -29,16 +34,51 @@ export const App = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const accountMetadata = metadata?.find(
|
||||||
|
(metadata) => metadata.key === 'account'
|
||||||
|
);
|
||||||
|
|
||||||
|
const account =
|
||||||
|
accounts?.find((account) => account.id === accountMetadata?.value) ||
|
||||||
|
accounts?.[0];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppContext.Provider
|
<AppContext.Provider
|
||||||
value={{
|
value={{
|
||||||
getMetadata: (key: string) => {
|
getMetadata: (key) => {
|
||||||
return data?.find((metadata) => metadata.key === key)?.value;
|
return metadata?.find((metadata) => metadata.key === key)?.value;
|
||||||
|
},
|
||||||
|
setMetadata: (key, value) => {
|
||||||
|
window.colanode.executeMutation({
|
||||||
|
type: 'app_metadata_save',
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deleteMetadata: (key: string) => {
|
||||||
|
window.colanode.executeMutation({
|
||||||
|
type: 'app_metadata_delete',
|
||||||
|
key,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
openLogin: () => setOpenLogin(true),
|
||||||
|
closeLogin: () => setOpenLogin(false),
|
||||||
|
openAccount: (id: string) => {
|
||||||
|
setOpenLogin(false);
|
||||||
|
window.colanode.executeMutation({
|
||||||
|
type: 'app_metadata_save',
|
||||||
|
key: 'account',
|
||||||
|
value: id,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RadarProvider>
|
<RadarProvider>
|
||||||
<Outlet />
|
{!openLogin && account ? (
|
||||||
|
<Account key={account.id} account={account} />
|
||||||
|
) : (
|
||||||
|
<Login />
|
||||||
|
)}
|
||||||
</RadarProvider>
|
</RadarProvider>
|
||||||
</AppContext.Provider>
|
</AppContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,106 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Mail } from 'lucide-react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { Avatar } from '@/renderer/components/avatars/avatar';
|
|
||||||
import { Button } from '@/renderer/components/ui/button';
|
|
||||||
import { useQuery } from '@/renderer/hooks/use-query';
|
|
||||||
import { AccountContext } from '@/renderer/contexts/account';
|
|
||||||
|
|
||||||
export const AccountNotFound = () => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const { data } = useQuery({
|
|
||||||
type: 'account_list',
|
|
||||||
});
|
|
||||||
|
|
||||||
const accounts = data ?? [];
|
|
||||||
return (
|
|
||||||
<div className="grid h-screen min-h-screen w-full grid-cols-5">
|
|
||||||
<div className="col-span-2 flex items-center justify-center bg-zinc-950">
|
|
||||||
<h1 className="font-neotrax text-8xl text-white">404</h1>
|
|
||||||
</div>
|
|
||||||
<div className="col-span-3 flex items-center justify-center py-12">
|
|
||||||
<div className="mx-auto grid w-96 gap-6">
|
|
||||||
<div className="grid gap-4 text-center">
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<h1 className="text-2xl font-semibold tracking-tight">
|
|
||||||
Account not found
|
|
||||||
</h1>
|
|
||||||
<p className="text-sm font-semibold tracking-tight">
|
|
||||||
You have been logged out or your login has expired.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
{accounts.length > 0 ? (
|
|
||||||
<React.Fragment>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Continue with one of your existing accounts
|
|
||||||
</p>
|
|
||||||
<div className="flex flex-row items-center justify-center gap-4">
|
|
||||||
{accounts.map((account) => (
|
|
||||||
<AccountContext.Provider
|
|
||||||
key={account.id}
|
|
||||||
value={{
|
|
||||||
...account,
|
|
||||||
openSettings: () => {},
|
|
||||||
openLogout: () => {},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="w-full flex items-center gap-2 text-left text-sm border border-gray-100 rounded-lg p-2 hover:bg-gray-100 hover:cursor-pointer"
|
|
||||||
onClick={() => navigate(`/${account.id}`)}
|
|
||||||
>
|
|
||||||
<Avatar
|
|
||||||
className="size-8 rounded-lg"
|
|
||||||
id={account.id}
|
|
||||||
name={account.name}
|
|
||||||
avatar={account.avatar}
|
|
||||||
/>
|
|
||||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
||||||
<span className="truncate font-semibold">
|
|
||||||
{account.name}
|
|
||||||
</span>
|
|
||||||
<span className="truncate text-xs">
|
|
||||||
{account.email}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AccountContext.Provider>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Or login with your email
|
|
||||||
</p>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
variant="outline"
|
|
||||||
className="w-full"
|
|
||||||
onClick={() => navigate('/login')}
|
|
||||||
>
|
|
||||||
<Mail className="mr-2 size-4" />
|
|
||||||
Login
|
|
||||||
</Button>
|
|
||||||
</React.Fragment>
|
|
||||||
) : (
|
|
||||||
<React.Fragment>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
You need to login to continue
|
|
||||||
</p>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
variant="outline"
|
|
||||||
className="w-full"
|
|
||||||
onClick={() => navigate('/login')}
|
|
||||||
>
|
|
||||||
<Mail className="mr-2 size-4" />
|
|
||||||
Login
|
|
||||||
</Button>
|
|
||||||
</React.Fragment>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
export const AccountRedirect = (): React.ReactNode => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
window.colanode
|
|
||||||
.executeQuery({
|
|
||||||
type: 'account_list',
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
const firstAccount = data?.[0];
|
|
||||||
|
|
||||||
if (firstAccount) {
|
|
||||||
navigate(`/${firstAccount.id}`);
|
|
||||||
} else {
|
|
||||||
navigate('/login');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [navigate]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
@@ -1,42 +1,86 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Outlet, useNavigate, useParams } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { AccountLogout } from '@/renderer/components/accounts/account-logout';
|
import { AccountLogout } from '@/renderer/components/accounts/account-logout';
|
||||||
import { AccountSettingsDialog } from '@/renderer/components/accounts/account-settings-dialog';
|
import { AccountSettingsDialog } from '@/renderer/components/accounts/account-settings-dialog';
|
||||||
import { AccountContext } from '@/renderer/contexts/account';
|
import { AccountContext } from '@/renderer/contexts/account';
|
||||||
import { AccountNotFound } from '@/renderer/components/accounts/account-not-found';
|
import { Account as AccountType } from '@/shared/types/accounts';
|
||||||
import { useQuery } from '@/renderer/hooks/use-query';
|
import { useQuery } from '@/renderer/hooks/use-query';
|
||||||
|
import { WorkspaceCreate } from '@/renderer/components/workspaces/workspace-create';
|
||||||
|
import { Workspace } from '@/renderer/components/workspaces/workspace';
|
||||||
|
|
||||||
export const Account = () => {
|
interface AccountProps {
|
||||||
const { accountId } = useParams<{ accountId: string }>();
|
account: AccountType;
|
||||||
const navigate = useNavigate();
|
}
|
||||||
|
|
||||||
|
export const Account = ({ account }: AccountProps) => {
|
||||||
const [openSettings, setOpenSettings] = React.useState(false);
|
const [openSettings, setOpenSettings] = React.useState(false);
|
||||||
const [openLogout, setOpenLogout] = React.useState(false);
|
const [openLogout, setOpenLogout] = React.useState(false);
|
||||||
|
const [openCreateWorkspace, setOpenCreateWorkspace] = React.useState(false);
|
||||||
|
|
||||||
const { data, isPending } = useQuery({
|
const { data: metadata, isPending: isPendingMetadata } = useQuery({
|
||||||
type: 'account_get',
|
type: 'account_metadata_list',
|
||||||
accountId: accountId!,
|
accountId: account.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isPending) {
|
const { data: workspaces, isPending: isPendingWorkspaces } = useQuery({
|
||||||
|
type: 'workspace_list',
|
||||||
|
accountId: account.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isPendingMetadata || isPendingWorkspaces) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data) {
|
const workspaceMetadata = metadata?.find(
|
||||||
return <AccountNotFound />;
|
(metadata) => metadata.key === 'workspace'
|
||||||
}
|
);
|
||||||
|
|
||||||
|
const workspace =
|
||||||
|
workspaces?.find(
|
||||||
|
(workspace) => workspace.id === workspaceMetadata?.value
|
||||||
|
) || workspaces?.[0];
|
||||||
|
|
||||||
|
const handleWorkspaceCreateSuccess = (id: string) => {
|
||||||
|
setOpenCreateWorkspace(false);
|
||||||
|
window.colanode.executeMutation({
|
||||||
|
type: 'account_metadata_save',
|
||||||
|
accountId: account.id,
|
||||||
|
key: 'workspace',
|
||||||
|
value: id,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleWorkspaceCreateCancel =
|
||||||
|
(workspaces?.length || 0) > 0
|
||||||
|
? () => setOpenCreateWorkspace(false)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const account = data;
|
|
||||||
return (
|
return (
|
||||||
<AccountContext.Provider
|
<AccountContext.Provider
|
||||||
value={{
|
value={{
|
||||||
...account,
|
...account,
|
||||||
openSettings: () => setOpenSettings(true),
|
openSettings: () => setOpenSettings(true),
|
||||||
openLogout: () => setOpenLogout(true),
|
openLogout: () => setOpenLogout(true),
|
||||||
|
openWorkspaceCreate: () => setOpenCreateWorkspace(true),
|
||||||
|
openWorkspace: (id) => {
|
||||||
|
setOpenCreateWorkspace(false);
|
||||||
|
window.colanode.executeMutation({
|
||||||
|
type: 'account_metadata_save',
|
||||||
|
accountId: account.id,
|
||||||
|
key: 'workspace',
|
||||||
|
value: id,
|
||||||
|
});
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Outlet />
|
{!openCreateWorkspace && workspace ? (
|
||||||
|
<Workspace workspace={workspace} />
|
||||||
|
) : (
|
||||||
|
<WorkspaceCreate
|
||||||
|
onSuccess={handleWorkspaceCreateSuccess}
|
||||||
|
onCancel={handleWorkspaceCreateCancel}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{openSettings && (
|
{openSettings && (
|
||||||
<AccountSettingsDialog
|
<AccountSettingsDialog
|
||||||
open={true}
|
open={true}
|
||||||
@@ -48,7 +92,6 @@ export const Account = () => {
|
|||||||
onCancel={() => setOpenLogout(false)}
|
onCancel={() => setOpenLogout(false)}
|
||||||
onLogout={() => {
|
onLogout={() => {
|
||||||
setOpenLogout(false);
|
setOpenLogout(false);
|
||||||
navigate('/');
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
|
import { useApp } from '@/renderer/contexts/app';
|
||||||
import { EmailLogin } from '@/renderer/components/accounts/email-login';
|
import { EmailLogin } from '@/renderer/components/accounts/email-login';
|
||||||
import { EmailRegister } from '@/renderer/components/accounts/email-register';
|
import { EmailRegister } from '@/renderer/components/accounts/email-register';
|
||||||
import { EmailVerify } from '@/renderer/components/accounts/email-verify';
|
import { EmailVerify } from '@/renderer/components/accounts/email-verify';
|
||||||
@@ -31,8 +31,7 @@ type VerifyPanelState = {
|
|||||||
type PanelState = LoginPanelState | RegisterPanelState | VerifyPanelState;
|
type PanelState = LoginPanelState | RegisterPanelState | VerifyPanelState;
|
||||||
|
|
||||||
export const LoginForm = ({ accounts, servers }: LoginFormProps) => {
|
export const LoginForm = ({ accounts, servers }: LoginFormProps) => {
|
||||||
const navigate = useNavigate();
|
const app = useApp();
|
||||||
|
|
||||||
const [server, setServer] = React.useState<Server>(servers[0]!);
|
const [server, setServer] = React.useState<Server>(servers[0]!);
|
||||||
const [panel, setPanel] = React.useState<PanelState>({
|
const [panel, setPanel] = React.useState<PanelState>({
|
||||||
type: 'login',
|
type: 'login',
|
||||||
@@ -52,8 +51,7 @@ export const LoginForm = ({ accounts, servers }: LoginFormProps) => {
|
|||||||
server={server}
|
server={server}
|
||||||
onSuccess={(output) => {
|
onSuccess={(output) => {
|
||||||
if (output.type === 'success') {
|
if (output.type === 'success') {
|
||||||
const userId = output.workspaces[0]?.id ?? '';
|
app.openAccount(output.account.id);
|
||||||
navigate(`/${output.account.id}/${userId}`);
|
|
||||||
} else if (output.type === 'verify') {
|
} else if (output.type === 'verify') {
|
||||||
setPanel({
|
setPanel({
|
||||||
type: 'verify',
|
type: 'verify',
|
||||||
@@ -81,8 +79,7 @@ export const LoginForm = ({ accounts, servers }: LoginFormProps) => {
|
|||||||
server={server}
|
server={server}
|
||||||
onSuccess={(output) => {
|
onSuccess={(output) => {
|
||||||
if (output.type === 'success') {
|
if (output.type === 'success') {
|
||||||
const userId = output.workspaces[0]?.id ?? '';
|
app.openAccount(output.account.id);
|
||||||
navigate(`/${output.account.id}/${userId}`);
|
|
||||||
} else if (output.type === 'verify') {
|
} else if (output.type === 'verify') {
|
||||||
setPanel({
|
setPanel({
|
||||||
type: 'verify',
|
type: 'verify',
|
||||||
@@ -113,8 +110,7 @@ export const LoginForm = ({ accounts, servers }: LoginFormProps) => {
|
|||||||
expiresAt={panel.expiresAt}
|
expiresAt={panel.expiresAt}
|
||||||
onSuccess={(output) => {
|
onSuccess={(output) => {
|
||||||
if (output.type === 'success') {
|
if (output.type === 'success') {
|
||||||
const userId = output.workspaces[0]?.id ?? '';
|
app.openAccount(output.account.id);
|
||||||
navigate(`/${output.account.id}/${userId}`);
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -137,7 +133,7 @@ export const LoginForm = ({ accounts, servers }: LoginFormProps) => {
|
|||||||
<p
|
<p
|
||||||
className="text-center text-sm text-muted-foreground hover:cursor-pointer hover:underline"
|
className="text-center text-sm text-muted-foreground hover:cursor-pointer hover:underline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(-1);
|
app.closeLogin();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Check, LogOut, Plus, Settings } from 'lucide-react';
|
import { Check, LogOut, Plus, Settings } from 'lucide-react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
|
import { useApp } from '@/renderer/contexts/app';
|
||||||
import { Avatar } from '@/renderer/components/avatars/avatar';
|
import { Avatar } from '@/renderer/components/avatars/avatar';
|
||||||
import { NotificationBadge } from '@/renderer/components/ui/notification-badge';
|
import { NotificationBadge } from '@/renderer/components/ui/notification-badge';
|
||||||
import {
|
import {
|
||||||
@@ -18,8 +18,8 @@ import { useQuery } from '@/renderer/hooks/use-query';
|
|||||||
import { AccountReadState } from '@/shared/types/radars';
|
import { AccountReadState } from '@/shared/types/radars';
|
||||||
|
|
||||||
export function SidebarMenuFooter() {
|
export function SidebarMenuFooter() {
|
||||||
|
const app = useApp();
|
||||||
const account = useAccount();
|
const account = useAccount();
|
||||||
const navigate = useNavigate();
|
|
||||||
const radar = useRadar();
|
const radar = useRadar();
|
||||||
|
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
@@ -112,7 +112,7 @@ export function SidebarMenuFooter() {
|
|||||||
key={accountItem.id}
|
key={accountItem.id}
|
||||||
className="p-0"
|
className="p-0"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`/${accountItem.id}`);
|
app.openAccount(accountItem.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AccountContext.Provider
|
<AccountContext.Provider
|
||||||
@@ -120,6 +120,8 @@ export function SidebarMenuFooter() {
|
|||||||
...accountItem,
|
...accountItem,
|
||||||
openSettings: () => {},
|
openSettings: () => {},
|
||||||
openLogout: () => {},
|
openLogout: () => {},
|
||||||
|
openWorkspace: () => {},
|
||||||
|
openWorkspaceCreate: () => {},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="w-full flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
<div className="w-full flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||||
@@ -157,7 +159,7 @@ export function SidebarMenuFooter() {
|
|||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="flex items-center gap-2 text-muted-foreground hover:text-foreground"
|
className="flex items-center gap-2 text-muted-foreground hover:text-foreground"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`/login`);
|
app.openLogin();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Plus className="size-4" />
|
<Plus className="size-4" />
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Bell, Check, Plus, Settings } from 'lucide-react';
|
import { Bell, Check, Plus, Settings } from 'lucide-react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { Avatar } from '@/renderer/components/avatars/avatar';
|
import { Avatar } from '@/renderer/components/avatars/avatar';
|
||||||
import { NotificationBadge } from '@/renderer/components/ui/notification-badge';
|
import { NotificationBadge } from '@/renderer/components/ui/notification-badge';
|
||||||
@@ -20,7 +19,6 @@ import { useQuery } from '@/renderer/hooks/use-query';
|
|||||||
export const SidebarMenuHeader = () => {
|
export const SidebarMenuHeader = () => {
|
||||||
const workspace = useWorkspace();
|
const workspace = useWorkspace();
|
||||||
const account = useAccount();
|
const account = useAccount();
|
||||||
const navigate = useNavigate();
|
|
||||||
const radar = useRadar();
|
const radar = useRadar();
|
||||||
|
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
@@ -104,7 +102,7 @@ export const SidebarMenuHeader = () => {
|
|||||||
key={workspaceItem.id}
|
key={workspaceItem.id}
|
||||||
className="p-0"
|
className="p-0"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`/${account.id}/${workspaceItem.id}`);
|
account.openWorkspace(workspaceItem.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="w-full flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
<div className="w-full flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||||
@@ -135,7 +133,7 @@ export const SidebarMenuHeader = () => {
|
|||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="gap-2 p-2 text-muted-foreground hover:text-foreground"
|
className="gap-2 p-2 text-muted-foreground hover:text-foreground"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`/${account.id}/create`);
|
account.openWorkspaceCreate();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Plus className="size-4" />
|
<Plus className="size-4" />
|
||||||
|
|||||||
@@ -1,25 +1,20 @@
|
|||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { WorkspaceForm } from '@/renderer/components/workspaces/workspace-form';
|
import { WorkspaceForm } from '@/renderer/components/workspaces/workspace-form';
|
||||||
import { useAccount } from '@/renderer/contexts/account';
|
import { useAccount } from '@/renderer/contexts/account';
|
||||||
import { useMutation } from '@/renderer/hooks/use-mutation';
|
import { useMutation } from '@/renderer/hooks/use-mutation';
|
||||||
import { useQuery } from '@/renderer/hooks/use-query';
|
|
||||||
import { toast } from '@/renderer/hooks/use-toast';
|
import { toast } from '@/renderer/hooks/use-toast';
|
||||||
|
|
||||||
export const WorkspaceCreate = () => {
|
interface WorkspaceCreateProps {
|
||||||
|
onSuccess: (id: string) => void;
|
||||||
|
onCancel: (() => void) | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WorkspaceCreate = ({
|
||||||
|
onSuccess,
|
||||||
|
onCancel,
|
||||||
|
}: WorkspaceCreateProps) => {
|
||||||
const account = useAccount();
|
const account = useAccount();
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const { mutate, isPending } = useMutation();
|
const { mutate, isPending } = useMutation();
|
||||||
|
|
||||||
const { data } = useQuery({
|
|
||||||
type: 'workspace_list',
|
|
||||||
accountId: account.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const workspaces = data ?? [];
|
|
||||||
const handleCancel = workspaces.length > 0 ? () => navigate('/') : undefined;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container flex flex-row justify-center">
|
<div className="container flex flex-row justify-center">
|
||||||
<div className="w-full max-w-[700px]">
|
<div className="w-full max-w-[700px]">
|
||||||
@@ -39,7 +34,7 @@ export const WorkspaceCreate = () => {
|
|||||||
avatar: values.avatar ?? null,
|
avatar: values.avatar ?? null,
|
||||||
},
|
},
|
||||||
onSuccess(output) {
|
onSuccess(output) {
|
||||||
navigate(`/${account.id}/${output.id}`);
|
onSuccess(output.id);
|
||||||
},
|
},
|
||||||
onError(error) {
|
onError(error) {
|
||||||
toast({
|
toast({
|
||||||
@@ -51,7 +46,7 @@ export const WorkspaceCreate = () => {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
isSaving={isPending}
|
isSaving={isPending}
|
||||||
onCancel={handleCancel}
|
onCancel={onCancel}
|
||||||
saveText="Create"
|
saveText="Create"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Plus } from 'lucide-react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { Avatar } from '@/renderer/components/avatars/avatar';
|
|
||||||
import { Button } from '@/renderer/components/ui/button';
|
|
||||||
import { useQuery } from '@/renderer/hooks/use-query';
|
|
||||||
import { useAccount } from '@/renderer/contexts/account';
|
|
||||||
|
|
||||||
export const WorkspaceNotFound = () => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const account = useAccount();
|
|
||||||
|
|
||||||
const { data } = useQuery({
|
|
||||||
type: 'workspace_list',
|
|
||||||
accountId: account.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const workspaces = data ?? [];
|
|
||||||
return (
|
|
||||||
<div className="grid h-screen min-h-screen w-full grid-cols-5">
|
|
||||||
<div className="col-span-2 flex items-center justify-center bg-zinc-950">
|
|
||||||
<h1 className="font-neotrax text-8xl text-white">404</h1>
|
|
||||||
</div>
|
|
||||||
<div className="col-span-3 flex items-center justify-center py-12">
|
|
||||||
<div className="mx-auto grid w-96 gap-6">
|
|
||||||
<div className="grid gap-4 text-center">
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<h1 className="text-2xl font-semibold tracking-tight">
|
|
||||||
Workspace not found
|
|
||||||
</h1>
|
|
||||||
<p className="text-sm font-semibold tracking-tight">
|
|
||||||
It may have been deleted or your acces has been removed.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
{workspaces.length > 0 ? (
|
|
||||||
<React.Fragment>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Continue with one of your existing workspaces
|
|
||||||
</p>
|
|
||||||
<div className="flex flex-row items-center justify-center gap-4">
|
|
||||||
{workspaces.map((workspace) => (
|
|
||||||
<div
|
|
||||||
key={workspace.id}
|
|
||||||
className="w-full flex items-center gap-2 text-left text-sm border border-gray-100 rounded-lg p-2 hover:bg-gray-100 hover:cursor-pointer"
|
|
||||||
onClick={() => navigate(`/${account.id}/${workspace.id}`)}
|
|
||||||
>
|
|
||||||
<Avatar
|
|
||||||
className="size-8 rounded-lg"
|
|
||||||
id={workspace.id}
|
|
||||||
name={workspace.name}
|
|
||||||
avatar={workspace.avatar}
|
|
||||||
/>
|
|
||||||
<p className="grid flex-1 text-left text-sm leading-tight truncate font-semibold">
|
|
||||||
{workspace.name}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Or create a new workspace
|
|
||||||
</p>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
variant="outline"
|
|
||||||
className="w-full"
|
|
||||||
onClick={() => navigate(`/${account.id}/create`)}
|
|
||||||
>
|
|
||||||
<Plus className="mr-2 size-4" />
|
|
||||||
Create new workspace
|
|
||||||
</Button>
|
|
||||||
</React.Fragment>
|
|
||||||
) : (
|
|
||||||
<React.Fragment>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Create a new workspace
|
|
||||||
</p>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
variant="outline"
|
|
||||||
className="w-full"
|
|
||||||
onClick={() => navigate(`/${account.id}/create`)}
|
|
||||||
>
|
|
||||||
<Plus className="mr-2 size-4" />
|
|
||||||
Create new workspace
|
|
||||||
</Button>
|
|
||||||
</React.Fragment>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { useAccount } from '@/renderer/contexts/account';
|
|
||||||
|
|
||||||
export const WorkspaceRedirect = (): React.ReactNode => {
|
|
||||||
const account = useAccount();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
window.colanode
|
|
||||||
.executeQuery({
|
|
||||||
type: 'workspace_list',
|
|
||||||
accountId: account.id,
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
const workspaces = data ?? [];
|
|
||||||
const firstWorkspace = workspaces[0];
|
|
||||||
if (firstWorkspace) {
|
|
||||||
navigate(`/${account.id}/${firstWorkspace.id}`);
|
|
||||||
} else {
|
|
||||||
navigate(`/${account.id}/create`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [navigate]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
@@ -1,47 +1,34 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { Layout } from '@/renderer/components/layouts/layout';
|
import { Layout } from '@/renderer/components/layouts/layout';
|
||||||
import { WorkspaceSettingsDialog } from '@/renderer/components/workspaces/workspace-settings-dialog';
|
import { WorkspaceSettingsDialog } from '@/renderer/components/workspaces/workspace-settings-dialog';
|
||||||
import { WorkspaceNotFound } from '@/renderer/components/workspaces/workspace-not-found';
|
|
||||||
import { useAccount } from '@/renderer/contexts/account';
|
import { useAccount } from '@/renderer/contexts/account';
|
||||||
import { WorkspaceContext } from '@/renderer/contexts/workspace';
|
import { WorkspaceContext } from '@/renderer/contexts/workspace';
|
||||||
import { useQuery } from '@/renderer/hooks/use-query';
|
import { useQuery } from '@/renderer/hooks/use-query';
|
||||||
import {
|
import {
|
||||||
WorkspaceMetadataKey,
|
WorkspaceMetadataKey,
|
||||||
WorkspaceMetadataMap,
|
WorkspaceMetadataMap,
|
||||||
|
Workspace as WorkspaceType,
|
||||||
} from '@/shared/types/workspaces';
|
} from '@/shared/types/workspaces';
|
||||||
|
|
||||||
export const Workspace = () => {
|
interface WorkspaceProps {
|
||||||
const { workspaceId } = useParams<{ workspaceId: string }>();
|
workspace: WorkspaceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Workspace = ({ workspace }: WorkspaceProps) => {
|
||||||
const account = useAccount();
|
const account = useAccount();
|
||||||
const [openSettings, setOpenSettings] = React.useState(false);
|
const [openSettings, setOpenSettings] = React.useState(false);
|
||||||
|
|
||||||
const { data: workspace, isPending: isPendingWorkspace } = useQuery({
|
|
||||||
type: 'workspace_get',
|
|
||||||
accountId: account.id,
|
|
||||||
workspaceId: workspaceId!,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { data: metadata, isPending: isPendingMetadata } = useQuery({
|
const { data: metadata, isPending: isPendingMetadata } = useQuery({
|
||||||
type: 'workspace_metadata_list',
|
type: 'workspace_metadata_list',
|
||||||
accountId: account.id,
|
accountId: account.id,
|
||||||
workspaceId: workspaceId!,
|
workspaceId: workspace.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isPendingWorkspace || isPendingMetadata) {
|
if (isPendingMetadata) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!workspace) {
|
|
||||||
return <WorkspaceNotFound />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!workspace) {
|
|
||||||
return <WorkspaceNotFound />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkspaceContext.Provider
|
<WorkspaceContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@@ -68,22 +55,22 @@ export const Workspace = () => {
|
|||||||
window.colanode.executeMutation({
|
window.colanode.executeMutation({
|
||||||
type: 'workspace_metadata_save',
|
type: 'workspace_metadata_save',
|
||||||
accountId: account.id,
|
accountId: account.id,
|
||||||
workspaceId: workspaceId!,
|
workspaceId: workspace.id,
|
||||||
key,
|
key,
|
||||||
value: JSON.stringify(value),
|
value,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
deleteMetadata(key: string) {
|
deleteMetadata(key: string) {
|
||||||
window.colanode.executeMutation({
|
window.colanode.executeMutation({
|
||||||
type: 'workspace_metadata_delete',
|
type: 'workspace_metadata_delete',
|
||||||
accountId: account.id,
|
accountId: account.id,
|
||||||
workspaceId: workspaceId!,
|
workspaceId: workspace.id,
|
||||||
key,
|
key,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Layout key={workspaceId} />
|
<Layout key={workspace.id} />
|
||||||
{openSettings && (
|
{openSettings && (
|
||||||
<WorkspaceSettingsDialog
|
<WorkspaceSettingsDialog
|
||||||
open={openSettings}
|
open={openSettings}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { Account } from '@/shared/types/accounts';
|
|||||||
interface AccountContext extends Account {
|
interface AccountContext extends Account {
|
||||||
openSettings: () => void;
|
openSettings: () => void;
|
||||||
openLogout: () => void;
|
openLogout: () => void;
|
||||||
|
openWorkspace: (id: string) => void;
|
||||||
|
openWorkspaceCreate: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AccountContext = createContext<AccountContext>(
|
export const AccountContext = createContext<AccountContext>(
|
||||||
|
|||||||
@@ -6,6 +6,14 @@ interface AppContext {
|
|||||||
getMetadata: <K extends AppMetadataKey>(
|
getMetadata: <K extends AppMetadataKey>(
|
||||||
key: K
|
key: K
|
||||||
) => AppMetadataMap[K]['value'] | undefined;
|
) => AppMetadataMap[K]['value'] | undefined;
|
||||||
|
setMetadata: <K extends AppMetadataKey>(
|
||||||
|
key: K,
|
||||||
|
value: AppMetadataMap[K]['value']
|
||||||
|
) => void;
|
||||||
|
deleteMetadata: <K extends AppMetadataKey>(key: K) => void;
|
||||||
|
openLogin: () => void;
|
||||||
|
closeLogin: () => void;
|
||||||
|
openAccount: (id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AppContext = createContext<AppContext>({} as AppContext);
|
export const AppContext = createContext<AppContext>({} as AppContext);
|
||||||
|
|||||||
@@ -4,56 +4,14 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { DndProvider } from 'react-dnd';
|
import { DndProvider } from 'react-dnd';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import { createHashRouter, RouterProvider } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { HTML5Backend } from '@/shared/lib/dnd-backend';
|
import { HTML5Backend } from '@/shared/lib/dnd-backend';
|
||||||
import { App } from '@/renderer/app';
|
import { App } from '@/renderer/app';
|
||||||
import { Account } from '@/renderer/components/accounts/account';
|
|
||||||
import { AccountRedirect } from '@/renderer/components/accounts/account-redirect';
|
|
||||||
import { Login } from '@/renderer/components/accounts/login';
|
|
||||||
import { Toaster } from '@/renderer/components/ui/toaster';
|
import { Toaster } from '@/renderer/components/ui/toaster';
|
||||||
import { TooltipProvider } from '@/renderer/components/ui/tooltip';
|
import { TooltipProvider } from '@/renderer/components/ui/tooltip';
|
||||||
import { Workspace } from '@/renderer/components/workspaces/workspace';
|
|
||||||
import { WorkspaceCreate } from '@/renderer/components/workspaces/workspace-create';
|
|
||||||
import { WorkspaceRedirect } from '@/renderer/components/workspaces/workspace-redirect';
|
|
||||||
import { useEventBus } from '@/renderer/hooks/use-event-bus';
|
import { useEventBus } from '@/renderer/hooks/use-event-bus';
|
||||||
import { Event } from '@/shared/types/events';
|
import { Event } from '@/shared/types/events';
|
||||||
|
|
||||||
const router = createHashRouter([
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
element: <App />,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
element: <AccountRedirect />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: ':accountId',
|
|
||||||
element: <Account />,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
element: <WorkspaceRedirect />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'create',
|
|
||||||
element: <WorkspaceCreate />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: ':workspaceId',
|
|
||||||
element: <Workspace />,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/login',
|
|
||||||
element: <Login />,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const queryClient = new QueryClient({
|
export const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
queries: {
|
queries: {
|
||||||
@@ -102,7 +60,7 @@ const Root = () => {
|
|||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<DndProvider backend={HTML5Backend}>
|
<DndProvider backend={HTML5Backend}>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<RouterProvider router={router} />
|
<App />
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</DndProvider>
|
</DndProvider>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
export type AccountMetadataDeleteMutationInput = {
|
||||||
|
type: 'account_metadata_delete';
|
||||||
|
accountId: string;
|
||||||
|
key: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AccountMetadataDeleteMutationOutput = {
|
||||||
|
success: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
declare module '@/shared/mutations' {
|
||||||
|
interface MutationMap {
|
||||||
|
account_metadata_delete: {
|
||||||
|
input: AccountMetadataDeleteMutationInput;
|
||||||
|
output: AccountMetadataDeleteMutationOutput;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import {
|
||||||
|
AccountMetadataKey,
|
||||||
|
AccountMetadataMap,
|
||||||
|
} from '@/shared/types/accounts';
|
||||||
|
|
||||||
|
export type AccountMetadataSaveMutationInput = {
|
||||||
|
type: 'account_metadata_save';
|
||||||
|
accountId: string;
|
||||||
|
key: AccountMetadataKey;
|
||||||
|
value: AccountMetadataMap[AccountMetadataKey]['value'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AccountMetadataSaveMutationOutput = {
|
||||||
|
success: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
declare module '@/shared/mutations' {
|
||||||
|
interface MutationMap {
|
||||||
|
account_metadata_save: {
|
||||||
|
input: AccountMetadataSaveMutationInput;
|
||||||
|
output: AccountMetadataSaveMutationOutput;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
export type AppMetadataDeleteMutationInput = {
|
||||||
|
type: 'app_metadata_delete';
|
||||||
|
key: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AppMetadataDeleteMutationOutput = {
|
||||||
|
success: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
declare module '@/shared/mutations' {
|
||||||
|
interface MutationMap {
|
||||||
|
app_metadata_delete: {
|
||||||
|
input: AppMetadataDeleteMutationInput;
|
||||||
|
output: AppMetadataDeleteMutationOutput;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
20
apps/desktop/src/shared/mutations/apps/app-metadata-save.ts
Normal file
20
apps/desktop/src/shared/mutations/apps/app-metadata-save.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { AppMetadataKey, AppMetadataMap } from '@/shared/types/apps';
|
||||||
|
|
||||||
|
export type AppMetadataSaveMutationInput = {
|
||||||
|
type: 'app_metadata_save';
|
||||||
|
key: AppMetadataKey;
|
||||||
|
value: AppMetadataMap[AppMetadataKey]['value'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AppMetadataSaveMutationOutput = {
|
||||||
|
success: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
declare module '@/shared/mutations' {
|
||||||
|
interface MutationMap {
|
||||||
|
app_metadata_save: {
|
||||||
|
input: AppMetadataSaveMutationInput;
|
||||||
|
output: AppMetadataSaveMutationOutput;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
|
import {
|
||||||
|
WorkspaceMetadataMap,
|
||||||
|
WorkspaceMetadataKey,
|
||||||
|
} from '@/shared/types/workspaces';
|
||||||
|
|
||||||
export type WorkspaceMetadataSaveMutationInput = {
|
export type WorkspaceMetadataSaveMutationInput = {
|
||||||
type: 'workspace_metadata_save';
|
type: 'workspace_metadata_save';
|
||||||
accountId: string;
|
accountId: string;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
key: string;
|
key: WorkspaceMetadataKey;
|
||||||
value: string;
|
value: WorkspaceMetadataMap[WorkspaceMetadataKey]['value'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkspaceMetadataSaveMutationOutput = {
|
export type WorkspaceMetadataSaveMutationOutput = {
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { AccountMetadata } from '@/shared/types/accounts';
|
||||||
|
|
||||||
|
export type AccountMetadataListQueryInput = {
|
||||||
|
type: 'account_metadata_list';
|
||||||
|
accountId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
declare module '@/shared/queries' {
|
||||||
|
interface QueryMap {
|
||||||
|
account_metadata_list: {
|
||||||
|
input: AccountMetadataListQueryInput;
|
||||||
|
output: AccountMetadata[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,3 +10,18 @@ export type Account = {
|
|||||||
updatedAt: string | null;
|
updatedAt: string | null;
|
||||||
syncedAt: string | null;
|
syncedAt: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type AccountWorkspaceMetadata = {
|
||||||
|
key: 'workspace';
|
||||||
|
value: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AccountMetadata = AccountWorkspaceMetadata;
|
||||||
|
|
||||||
|
export type AccountMetadataKey = AccountMetadata['key'];
|
||||||
|
|
||||||
|
export type AccountMetadataMap = {
|
||||||
|
workspace: AccountWorkspaceMetadata;
|
||||||
|
};
|
||||||
|
|||||||
@@ -34,10 +34,18 @@ export type AppWindowSizeMetadata = {
|
|||||||
updatedAt: string | null;
|
updatedAt: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type AppAccountMetadata = {
|
||||||
|
key: 'account';
|
||||||
|
value: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
export type AppMetadata =
|
export type AppMetadata =
|
||||||
| AppPlatformMetadata
|
| AppPlatformMetadata
|
||||||
| AppVersionMetadata
|
| AppVersionMetadata
|
||||||
| AppWindowSizeMetadata;
|
| AppWindowSizeMetadata
|
||||||
|
| AppAccountMetadata;
|
||||||
|
|
||||||
export type AppMetadataKey = AppMetadata['key'];
|
export type AppMetadataKey = AppMetadata['key'];
|
||||||
|
|
||||||
@@ -45,4 +53,5 @@ export type AppMetadataMap = {
|
|||||||
platform: AppPlatformMetadata;
|
platform: AppPlatformMetadata;
|
||||||
version: AppVersionMetadata;
|
version: AppVersionMetadata;
|
||||||
window_size: AppWindowSizeMetadata;
|
window_size: AppWindowSizeMetadata;
|
||||||
|
account: AppAccountMetadata;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Message } from '@colanode/core';
|
import { Message } from '@colanode/core';
|
||||||
|
|
||||||
import { AppMetadata } from '@/shared/types/apps';
|
import { AppMetadata } from '@/shared/types/apps';
|
||||||
import { Account } from '@/shared/types/accounts';
|
import { Account, AccountMetadata } from '@/shared/types/accounts';
|
||||||
import { Server } from '@/shared/types/servers';
|
import { Server } from '@/shared/types/servers';
|
||||||
import { Workspace, WorkspaceMetadata } from '@/shared/types/workspaces';
|
import { Workspace, WorkspaceMetadata } from '@/shared/types/workspaces';
|
||||||
import { User } from '@/shared/types/users';
|
import { User } from '@/shared/types/users';
|
||||||
@@ -169,8 +169,8 @@ export type AccountConnectionMessageEvent = {
|
|||||||
message: Message;
|
message: Message;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AppMetadataUpdatedEvent = {
|
export type AppMetadataSavedEvent = {
|
||||||
type: 'app_metadata_updated';
|
type: 'app_metadata_saved';
|
||||||
metadata: AppMetadata;
|
metadata: AppMetadata;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -179,8 +179,20 @@ export type AppMetadataDeletedEvent = {
|
|||||||
metadata: AppMetadata;
|
metadata: AppMetadata;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkspaceMetadataUpdatedEvent = {
|
export type AccountMetadataSavedEvent = {
|
||||||
type: 'workspace_metadata_updated';
|
type: 'account_metadata_saved';
|
||||||
|
accountId: string;
|
||||||
|
metadata: AccountMetadata;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AccountMetadataDeletedEvent = {
|
||||||
|
type: 'account_metadata_deleted';
|
||||||
|
accountId: string;
|
||||||
|
metadata: AccountMetadata;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WorkspaceMetadataSavedEvent = {
|
||||||
|
type: 'workspace_metadata_saved';
|
||||||
accountId: string;
|
accountId: string;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
metadata: WorkspaceMetadata;
|
metadata: WorkspaceMetadata;
|
||||||
@@ -256,9 +268,11 @@ export type Event =
|
|||||||
| AccountConnectionOpenedEvent
|
| AccountConnectionOpenedEvent
|
||||||
| AccountConnectionClosedEvent
|
| AccountConnectionClosedEvent
|
||||||
| AccountConnectionMessageEvent
|
| AccountConnectionMessageEvent
|
||||||
| AppMetadataUpdatedEvent
|
| AppMetadataSavedEvent
|
||||||
| AppMetadataDeletedEvent
|
| AppMetadataDeletedEvent
|
||||||
| WorkspaceMetadataUpdatedEvent
|
| AccountMetadataSavedEvent
|
||||||
|
| AccountMetadataDeletedEvent
|
||||||
|
| WorkspaceMetadataSavedEvent
|
||||||
| WorkspaceMetadataDeletedEvent
|
| WorkspaceMetadataDeletedEvent
|
||||||
| DocumentUpdatedEvent
|
| DocumentUpdatedEvent
|
||||||
| DocumentDeletedEvent
|
| DocumentDeletedEvent
|
||||||
|
|||||||
68
package-lock.json
generated
68
package-lock.json
generated
@@ -102,7 +102,6 @@
|
|||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-hook-form": "^7.53.2",
|
"react-hook-form": "^7.53.2",
|
||||||
"react-intersection-observer": "^9.16.0",
|
"react-intersection-observer": "^9.16.0",
|
||||||
"react-router-dom": "^7.5.0",
|
|
||||||
"semver": "^7.7.1",
|
"semver": "^7.7.1",
|
||||||
"tailwind-merge": "^3.2.0",
|
"tailwind-merge": "^3.2.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
@@ -6922,12 +6921,6 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/cookie": {
|
|
||||||
"version": "0.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
|
|
||||||
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@types/cors": {
|
"node_modules/@types/cors": {
|
||||||
"version": "2.8.17",
|
"version": "2.8.17",
|
||||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
|
||||||
@@ -16669,55 +16662,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-router": {
|
|
||||||
"version": "7.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.0.tgz",
|
|
||||||
"integrity": "sha512-estOHrRlDMKdlQa6Mj32gIks4J+AxNsYoE0DbTTxiMy2mPzZuWSDU+N85/r1IlNR7kGfznF3VCUlvc5IUO+B9g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/cookie": "^0.6.0",
|
|
||||||
"cookie": "^1.0.1",
|
|
||||||
"set-cookie-parser": "^2.6.0",
|
|
||||||
"turbo-stream": "2.4.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": ">=18",
|
|
||||||
"react-dom": ">=18"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"react-dom": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-router-dom": {
|
|
||||||
"version": "7.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.5.0.tgz",
|
|
||||||
"integrity": "sha512-fFhGFCULy4vIseTtH5PNcY/VvDJK5gvOWcwJVHQp8JQcWVr85ENhJ3UpuF/zP1tQOIFYNRJHzXtyhU1Bdgw0RA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"react-router": "7.5.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": ">=18",
|
|
||||||
"react-dom": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-router/node_modules/cookie": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-style-singleton": {
|
"node_modules/react-style-singleton": {
|
||||||
"version": "2.2.3",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
|
||||||
@@ -17462,12 +17406,6 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/set-cookie-parser": {
|
|
||||||
"version": "2.7.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
|
||||||
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/set-function-length": {
|
"node_modules/set-function-length": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||||
@@ -19097,12 +19035,6 @@
|
|||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/turbo-stream": {
|
|
||||||
"version": "2.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
|
|
||||||
"integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==",
|
|
||||||
"license": "ISC"
|
|
||||||
},
|
|
||||||
"node_modules/turbo-windows-64": {
|
"node_modules/turbo-windows-64": {
|
||||||
"version": "2.5.0",
|
"version": "2.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-2.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-2.5.0.tgz",
|
||||||
|
|||||||
Reference in New Issue
Block a user