@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.
Files changed (70) hide show
  1. package/LICENSE +662 -0
  2. package/package.json +113 -0
  3. package/src/components/content.tsx +13 -0
  4. package/src/components/context.client.tsx +12 -0
  5. package/src/components/dnd.client.tsx +47 -0
  6. package/src/components/grid-card.client.tsx +252 -0
  7. package/src/components/grid.client.tsx +96 -0
  8. package/src/components/navigation/breadcrumbs.client.tsx +125 -0
  9. package/src/components/navigation/header.client.tsx +45 -0
  10. package/src/components/navigation/toolbar.client.tsx +35 -0
  11. package/src/components/navigation/view-switcher.client.tsx +32 -0
  12. package/src/components/selection.client.tsx +48 -0
  13. package/src/components/view.client.tsx +67 -0
  14. package/src/config/filters.ts +14 -0
  15. package/src/config/types.tsx +90 -0
  16. package/src/entry.client.ts +7 -0
  17. package/src/entry.server.ts +4 -0
  18. package/src/entry.ts +10 -0
  19. package/src/lib/constants.ts +19 -0
  20. package/src/lib/contracts.ts +121 -0
  21. package/src/lib/dto.ts +83 -0
  22. package/src/lib/helpers.server.ts +14 -0
  23. package/src/lib/helpers.ts +32 -0
  24. package/src/lib/search-params.ts +5 -0
  25. package/src/lib/validators.ts +89 -0
  26. package/src/providers/google/components/command-file-update.tsx +100 -0
  27. package/src/providers/google/components/command-folder-create.tsx +104 -0
  28. package/src/providers/google/components/command-folder-update.tsx +100 -0
  29. package/src/providers/google/components/content.client.tsx +6 -0
  30. package/src/providers/google/components/navigation.client.tsx +21 -0
  31. package/src/providers/google/components/provider.client.tsx +60 -0
  32. package/src/providers/google/components/view.client.tsx +158 -0
  33. package/src/providers/google/config/columns-data.tsx +81 -0
  34. package/src/providers/google/config/filters.ts +3 -0
  35. package/src/providers/google/entry.client.ts +10 -0
  36. package/src/providers/google/entry.server.ts +5 -0
  37. package/src/providers/google/entry.ts +12 -0
  38. package/src/providers/google/lib/constants.ts +10 -0
  39. package/src/providers/google/lib/dto.ts +104 -0
  40. package/src/providers/google/lib/helpers.ts +37 -0
  41. package/src/providers/google/lib/router.server.ts +62 -0
  42. package/src/providers/google/lib/schema.ts +9 -0
  43. package/src/providers/google/lib/search-params.ts +7 -0
  44. package/src/providers/google/lib/service.server.ts +792 -0
  45. package/src/providers/google/lib/validators.ts +148 -0
  46. package/src/providers/local/components/command-file-update.tsx +93 -0
  47. package/src/providers/local/components/command-file-upload.tsx +29 -0
  48. package/src/providers/local/components/command-folder-create.tsx +100 -0
  49. package/src/providers/local/components/command-folder-update.tsx +93 -0
  50. package/src/providers/local/components/content.client.tsx +3 -0
  51. package/src/providers/local/components/navigation.client.tsx +23 -0
  52. package/src/providers/local/components/provider.client.tsx +90 -0
  53. package/src/providers/local/components/upload-zone-context.client.tsx +43 -0
  54. package/src/providers/local/components/upload-zone.client.tsx +182 -0
  55. package/src/providers/local/components/view.client.tsx +145 -0
  56. package/src/providers/local/config/columns-data.tsx +81 -0
  57. package/src/providers/local/config/filters.ts +14 -0
  58. package/src/providers/local/entry.client.ts +18 -0
  59. package/src/providers/local/entry.server.ts +7 -0
  60. package/src/providers/local/entry.ts +14 -0
  61. package/src/providers/local/lib/constants.ts +23 -0
  62. package/src/providers/local/lib/helpers.ts +105 -0
  63. package/src/providers/local/lib/route-handler.server.ts +153 -0
  64. package/src/providers/local/lib/router.server.ts +137 -0
  65. package/src/providers/local/lib/schema.ts +104 -0
  66. package/src/providers/local/lib/search-params.ts +4 -0
  67. package/src/providers/local/lib/service.server.ts +1116 -0
  68. package/src/providers/local/lib/upload.client.ts +177 -0
  69. package/src/providers/local/lib/validators.ts +154 -0
  70. 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 ".";