@tulip-systems/drive 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +662 -0
- package/package.json +113 -0
- package/src/components/content.tsx +13 -0
- package/src/components/context.client.tsx +12 -0
- package/src/components/dnd.client.tsx +47 -0
- package/src/components/grid-card.client.tsx +252 -0
- package/src/components/grid.client.tsx +96 -0
- package/src/components/navigation/breadcrumbs.client.tsx +125 -0
- package/src/components/navigation/header.client.tsx +45 -0
- package/src/components/navigation/toolbar.client.tsx +35 -0
- package/src/components/navigation/view-switcher.client.tsx +32 -0
- package/src/components/selection.client.tsx +48 -0
- package/src/components/view.client.tsx +67 -0
- package/src/config/filters.ts +14 -0
- package/src/config/types.tsx +90 -0
- package/src/entry.client.ts +7 -0
- package/src/entry.server.ts +4 -0
- package/src/entry.ts +10 -0
- package/src/lib/constants.ts +19 -0
- package/src/lib/contracts.ts +121 -0
- package/src/lib/dto.ts +83 -0
- package/src/lib/helpers.server.ts +14 -0
- package/src/lib/helpers.ts +32 -0
- package/src/lib/search-params.ts +5 -0
- package/src/lib/validators.ts +89 -0
- package/src/providers/google/components/command-file-update.tsx +100 -0
- package/src/providers/google/components/command-folder-create.tsx +104 -0
- package/src/providers/google/components/command-folder-update.tsx +100 -0
- package/src/providers/google/components/content.client.tsx +6 -0
- package/src/providers/google/components/navigation.client.tsx +21 -0
- package/src/providers/google/components/provider.client.tsx +60 -0
- package/src/providers/google/components/view.client.tsx +158 -0
- package/src/providers/google/config/columns-data.tsx +81 -0
- package/src/providers/google/config/filters.ts +3 -0
- package/src/providers/google/entry.client.ts +10 -0
- package/src/providers/google/entry.server.ts +5 -0
- package/src/providers/google/entry.ts +12 -0
- package/src/providers/google/lib/constants.ts +10 -0
- package/src/providers/google/lib/dto.ts +104 -0
- package/src/providers/google/lib/helpers.ts +37 -0
- package/src/providers/google/lib/router.server.ts +62 -0
- package/src/providers/google/lib/schema.ts +9 -0
- package/src/providers/google/lib/search-params.ts +7 -0
- package/src/providers/google/lib/service.server.ts +792 -0
- package/src/providers/google/lib/validators.ts +148 -0
- package/src/providers/local/components/command-file-update.tsx +93 -0
- package/src/providers/local/components/command-file-upload.tsx +29 -0
- package/src/providers/local/components/command-folder-create.tsx +100 -0
- package/src/providers/local/components/command-folder-update.tsx +93 -0
- package/src/providers/local/components/content.client.tsx +3 -0
- package/src/providers/local/components/navigation.client.tsx +23 -0
- package/src/providers/local/components/provider.client.tsx +90 -0
- package/src/providers/local/components/upload-zone-context.client.tsx +43 -0
- package/src/providers/local/components/upload-zone.client.tsx +182 -0
- package/src/providers/local/components/view.client.tsx +145 -0
- package/src/providers/local/config/columns-data.tsx +81 -0
- package/src/providers/local/config/filters.ts +14 -0
- package/src/providers/local/entry.client.ts +18 -0
- package/src/providers/local/entry.server.ts +7 -0
- package/src/providers/local/entry.ts +14 -0
- package/src/providers/local/lib/constants.ts +23 -0
- package/src/providers/local/lib/helpers.ts +105 -0
- package/src/providers/local/lib/route-handler.server.ts +153 -0
- package/src/providers/local/lib/router.server.ts +137 -0
- package/src/providers/local/lib/schema.ts +104 -0
- package/src/providers/local/lib/search-params.ts +4 -0
- package/src/providers/local/lib/service.server.ts +1116 -0
- package/src/providers/local/lib/upload.client.ts +177 -0
- package/src/providers/local/lib/validators.ts +154 -0
- package/src/styles.css +1 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { generateDefaultUUID } from "@tulip-systems/core/config";
|
|
2
|
+
import type { BulkActionSchema } from "@tulip-systems/core/router";
|
|
3
|
+
import type { StorageAsset } from "@tulip-systems/core/storage";
|
|
4
|
+
import type {
|
|
5
|
+
LocalDriveFileNode,
|
|
6
|
+
LocalDriveNode,
|
|
7
|
+
PresignLocalDriveFileInput,
|
|
8
|
+
UpdateLocalDriveNodeInput,
|
|
9
|
+
} from "./validators";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Upload request
|
|
13
|
+
*/
|
|
14
|
+
export type PrepareLocalDriveUploadInput = Omit<
|
|
15
|
+
LocalDriveUploadFileRequest,
|
|
16
|
+
"name" | "size" | "contentType"
|
|
17
|
+
>;
|
|
18
|
+
|
|
19
|
+
export type LocalDriveUploadFileRequest = PresignLocalDriveFileInput & { file: File };
|
|
20
|
+
export type LocalDriveUploadHooks = {
|
|
21
|
+
beforePresign?: (input: LocalDriveUploadFileRequest) => Promise<void> | void;
|
|
22
|
+
afterPresign?: (presignResult: {
|
|
23
|
+
asset: Omit<StorageAsset, "presignedUrl">;
|
|
24
|
+
node: LocalDriveFileNode;
|
|
25
|
+
uploadId: string;
|
|
26
|
+
presignedUrl: string;
|
|
27
|
+
}) => Promise<void> | void;
|
|
28
|
+
beforeConfirm?: (presignResult: {
|
|
29
|
+
asset: Omit<StorageAsset, "presignedUrl">;
|
|
30
|
+
node: LocalDriveFileNode;
|
|
31
|
+
uploadId: string;
|
|
32
|
+
presignedUrl: string;
|
|
33
|
+
}) => Promise<void> | void;
|
|
34
|
+
afterConfirm?: (node: LocalDriveFileNode) => Promise<void> | void;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Upload client
|
|
39
|
+
*/
|
|
40
|
+
export type LocalDriveUploadClient = {
|
|
41
|
+
prepareUpload: (input: PrepareLocalDriveUploadInput) => LocalDriveUploadFileRequest;
|
|
42
|
+
upload: (
|
|
43
|
+
input: LocalDriveUploadFileRequest,
|
|
44
|
+
hooks?: LocalDriveUploadHooks,
|
|
45
|
+
) => Promise<LocalDriveFileNode>;
|
|
46
|
+
deleteFiles: (ids: string[]) => Promise<void>;
|
|
47
|
+
updateNode: (id: string, data: UpdateLocalDriveNodeInput) => Promise<LocalDriveNode>;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Create upload client
|
|
52
|
+
*/
|
|
53
|
+
type CreateLocalDriveUploadClientProps = {
|
|
54
|
+
endpoints: {
|
|
55
|
+
presignUpload: (input: PresignLocalDriveFileInput) => Promise<{
|
|
56
|
+
asset: Omit<StorageAsset, "presignedUrl">;
|
|
57
|
+
node: LocalDriveFileNode;
|
|
58
|
+
uploadId: string;
|
|
59
|
+
presignedUrl: string;
|
|
60
|
+
}>;
|
|
61
|
+
confirmUpload: (input: { uploadId: string }) => Promise<LocalDriveFileNode>;
|
|
62
|
+
deleteNodes: (input: BulkActionSchema) => Promise<void>;
|
|
63
|
+
updateNode: (input: { id: string; data: UpdateLocalDriveNodeInput }) => Promise<LocalDriveNode>;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export function createLocalDriveUploadClient(
|
|
68
|
+
props: CreateLocalDriveUploadClientProps,
|
|
69
|
+
): LocalDriveUploadClient {
|
|
70
|
+
/**
|
|
71
|
+
* Create input schema for the upload method
|
|
72
|
+
* @param {PrepareLocalDriveUploadInput} input
|
|
73
|
+
* @returns {LocalDriveUploadFileRequest}
|
|
74
|
+
*/
|
|
75
|
+
function prepareUpload(input: PrepareLocalDriveUploadInput): LocalDriveUploadFileRequest {
|
|
76
|
+
return {
|
|
77
|
+
...input,
|
|
78
|
+
uploadId: input.uploadId ?? generateDefaultUUID(),
|
|
79
|
+
name: input.file.name,
|
|
80
|
+
size: input.file.size,
|
|
81
|
+
contentType: input.file.type,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Upload file to the server
|
|
87
|
+
* @param {LocalDriveUploadFileRequest} input
|
|
88
|
+
* @returns {Promise<LocalDriveFileNode>}
|
|
89
|
+
*/
|
|
90
|
+
async function upload(
|
|
91
|
+
input: LocalDriveUploadFileRequest,
|
|
92
|
+
hooks?: LocalDriveUploadHooks,
|
|
93
|
+
): Promise<LocalDriveFileNode> {
|
|
94
|
+
let presignResult: {
|
|
95
|
+
asset: Omit<StorageAsset, "presignedUrl">;
|
|
96
|
+
node: LocalDriveFileNode;
|
|
97
|
+
uploadId: string;
|
|
98
|
+
presignedUrl: string;
|
|
99
|
+
} | null = null;
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
/**
|
|
103
|
+
* Presign
|
|
104
|
+
*/
|
|
105
|
+
await hooks?.beforePresign?.(input);
|
|
106
|
+
const { file, ...presignInput } = input;
|
|
107
|
+
presignResult = await props.endpoints.presignUpload(presignInput);
|
|
108
|
+
await hooks?.afterPresign?.(presignResult);
|
|
109
|
+
/**
|
|
110
|
+
* Upload the file
|
|
111
|
+
*/
|
|
112
|
+
const uploadResponse = await fetch(presignResult.presignedUrl, {
|
|
113
|
+
method: "PUT",
|
|
114
|
+
headers: { "Content-Type": input.file.type },
|
|
115
|
+
body: input.file,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (!uploadResponse.ok) {
|
|
119
|
+
const message = await uploadResponse.text();
|
|
120
|
+
throw new Error(`Upload failed: ${message}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Confirm
|
|
125
|
+
*/
|
|
126
|
+
await hooks?.beforeConfirm?.(presignResult);
|
|
127
|
+
const node = await props.endpoints.confirmUpload({ uploadId: presignResult.uploadId });
|
|
128
|
+
await hooks?.afterConfirm?.(node);
|
|
129
|
+
/**
|
|
130
|
+
* Return
|
|
131
|
+
*/
|
|
132
|
+
return node;
|
|
133
|
+
} catch (err) {
|
|
134
|
+
console.error("Upload error: ", err);
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Delete if upload failed
|
|
138
|
+
* */
|
|
139
|
+
try {
|
|
140
|
+
if (presignResult?.node.id) {
|
|
141
|
+
await deleteFiles([presignResult.node.id]);
|
|
142
|
+
}
|
|
143
|
+
} catch (cleanupErr) {
|
|
144
|
+
console.error("Cleanup delete failed:", cleanupErr);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
throw err;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Delete files
|
|
153
|
+
* @param {string[]} ids
|
|
154
|
+
*/
|
|
155
|
+
async function deleteFiles(ids: string[]) {
|
|
156
|
+
await props.endpoints.deleteNodes({ ids });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Update node
|
|
161
|
+
* @param {string} id
|
|
162
|
+
* @param {UpdateLocalDriveNodeInput} data
|
|
163
|
+
*/
|
|
164
|
+
async function updateNode(id: string, data: UpdateLocalDriveNodeInput) {
|
|
165
|
+
return props.endpoints.updateNode({ id, data });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Upload client
|
|
170
|
+
*/
|
|
171
|
+
return {
|
|
172
|
+
prepareUpload,
|
|
173
|
+
upload,
|
|
174
|
+
deleteFiles,
|
|
175
|
+
updateNode,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { resolveFiltersSchema, tableQuerySchema } from "@tulip-systems/core/data-tables";
|
|
2
|
+
import {
|
|
3
|
+
getAssetURLSchema,
|
|
4
|
+
type StorageAsset,
|
|
5
|
+
storageAssetVisibilitySchema,
|
|
6
|
+
} from "@tulip-systems/core/storage";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { type DriveNodeChild, driveNodeSchema } from "@/lib/dto";
|
|
9
|
+
import {
|
|
10
|
+
createDriveFolderInputSchema,
|
|
11
|
+
presignDriveFileInputSchema,
|
|
12
|
+
updateDriveNodeInputSchema,
|
|
13
|
+
uploadDriveFileInputSchema,
|
|
14
|
+
} from "@/lib/validators";
|
|
15
|
+
import { localDriveFilters } from "../config/filters";
|
|
16
|
+
import { imageVariants } from "./constants";
|
|
17
|
+
import type { nodes } from "./schema";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Nodes
|
|
21
|
+
*/
|
|
22
|
+
export type LocalNode = typeof nodes.$inferSelect;
|
|
23
|
+
|
|
24
|
+
export const localDriveNodeSchema = driveNodeSchema.extend({
|
|
25
|
+
provider: z.literal("local"),
|
|
26
|
+
assetId: z.string().nullable(),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export type LocalDriveNode = z.infer<typeof localDriveNodeSchema>;
|
|
30
|
+
export type LocalDriveFileNode = LocalDriveNode & { type: "file" };
|
|
31
|
+
export type LocalDriveFolderNode = LocalDriveNode & { type: "folder" };
|
|
32
|
+
export type LocalDriveNodeChild = DriveNodeChild<LocalDriveNode>;
|
|
33
|
+
export type LocalDriveNodeWithAsset = LocalDriveNode & { asset: StorageAsset | null };
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Node with children
|
|
37
|
+
*/
|
|
38
|
+
export const localDriveNodeWithChildrenSchema = localDriveNodeSchema.extend({
|
|
39
|
+
children: z.array(localDriveNodeSchema),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export type LocalDriveNodeWithChildren = z.infer<typeof localDriveNodeWithChildrenSchema>;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Create folder
|
|
46
|
+
*/
|
|
47
|
+
export const createLocalDriveFolderInputSchema = createDriveFolderInputSchema.extend({
|
|
48
|
+
hidden: z.boolean().optional(),
|
|
49
|
+
readonly: z.boolean().optional(),
|
|
50
|
+
});
|
|
51
|
+
export type CreateLocalDriveFolderInput = z.input<typeof createLocalDriveFolderInputSchema>;
|
|
52
|
+
export type CreateLocalDriveFolderSchema = z.infer<typeof createLocalDriveFolderInputSchema>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Update node
|
|
56
|
+
*/
|
|
57
|
+
export const updateLocalDriveNodeInputSchema = updateDriveNodeInputSchema.extend({
|
|
58
|
+
parentId: z.string().nullable().optional(),
|
|
59
|
+
hidden: z.boolean().optional(),
|
|
60
|
+
readonly: z.boolean().optional(),
|
|
61
|
+
archivedAt: z.date().nullable().optional(),
|
|
62
|
+
});
|
|
63
|
+
export type UpdateLocalDriveNodeInput = z.input<typeof updateLocalDriveNodeInputSchema>;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Upload file
|
|
67
|
+
*/
|
|
68
|
+
export const uploadLocalDriveFileInputSchema = uploadDriveFileInputSchema.extend({
|
|
69
|
+
hidden: z.boolean().optional(),
|
|
70
|
+
readonly: z.boolean().optional(),
|
|
71
|
+
});
|
|
72
|
+
export type UploadLocalDriveFileSchema = z.infer<typeof uploadLocalDriveFileInputSchema>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Presign file
|
|
76
|
+
*/
|
|
77
|
+
export const presignLocalDriveFileInputSchema = presignDriveFileInputSchema.extend({
|
|
78
|
+
hidden: z.boolean().optional(),
|
|
79
|
+
readonly: z.boolean().optional(),
|
|
80
|
+
uploadId: z.uuid().optional(),
|
|
81
|
+
visibility: storageAssetVisibilitySchema.optional().default("private"),
|
|
82
|
+
});
|
|
83
|
+
export type PresignLocalDriveFileInput = z.input<typeof presignLocalDriveFileInputSchema>;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get file url schema
|
|
87
|
+
*/
|
|
88
|
+
export const getLocalFileURLSchemaDefaults = { variant: "main", disposition: "inline" } as const;
|
|
89
|
+
|
|
90
|
+
export const getLocalFileURLSchema = getAssetURLSchema.extend({
|
|
91
|
+
variant: z.enum(imageVariants).optional().default(getLocalFileURLSchemaDefaults.variant),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
export type GetLocalFileURLSchema = z.infer<typeof getLocalFileURLSchema>;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Tree filters
|
|
98
|
+
*/
|
|
99
|
+
export const localDriveTreeFiltersSchema = resolveFiltersSchema(localDriveFilters)
|
|
100
|
+
.partial()
|
|
101
|
+
.extend({
|
|
102
|
+
namespace: z.string(),
|
|
103
|
+
parentId: z.string().nullable(),
|
|
104
|
+
})
|
|
105
|
+
.transform((input) => ({
|
|
106
|
+
...input,
|
|
107
|
+
hidden: input?.hidden ?? false,
|
|
108
|
+
isArchived: input?.isArchived ?? false,
|
|
109
|
+
}));
|
|
110
|
+
export type LocalDriveTableFilters = z.input<typeof localDriveTreeFiltersSchema>;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Flat filters
|
|
114
|
+
*/
|
|
115
|
+
export const localDriveFlatFiltersSchema = resolveFiltersSchema(localDriveFilters)
|
|
116
|
+
.partial()
|
|
117
|
+
.extend({
|
|
118
|
+
namespace: z.string().optional(),
|
|
119
|
+
})
|
|
120
|
+
.transform((input) => ({
|
|
121
|
+
...input,
|
|
122
|
+
hidden: input?.hidden ?? false,
|
|
123
|
+
isArchived: input?.isArchived ?? false,
|
|
124
|
+
}));
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* List tree input schema
|
|
128
|
+
*/
|
|
129
|
+
export const listLocalDriveTreeInputSchema = tableQuerySchema.extend({
|
|
130
|
+
filters: localDriveTreeFiltersSchema,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
export type ListLocalDriveTreeInput = z.input<typeof listLocalDriveTreeInputSchema>;
|
|
134
|
+
export type ListLocalDriveTreeSchema = z.infer<typeof listLocalDriveTreeInputSchema>;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* List flat input schema
|
|
138
|
+
*/
|
|
139
|
+
export const listLocalDriveFlatInputSchema = tableQuerySchema.extend({
|
|
140
|
+
filters: localDriveFlatFiltersSchema,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
export type ListLocalDriveFlatSchema = z.infer<typeof listLocalDriveFlatInputSchema>;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get by parent id input
|
|
147
|
+
*/
|
|
148
|
+
export const getLocalDriveNodesByParentIdInputSchema = tableQuerySchema
|
|
149
|
+
.pick({ order: true, sort: true, search: true })
|
|
150
|
+
.extend({ filters: localDriveTreeFiltersSchema });
|
|
151
|
+
|
|
152
|
+
export type GetLocalDriveNodesByParentIdInput = z.input<
|
|
153
|
+
typeof getLocalDriveNodesByParentIdInputSchema
|
|
154
|
+
>;
|
package/src/styles.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@source ".";
|