agentic-drive 0.5.0 → 0.6.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/CHANGELOG.md +7 -0
- package/README.md +88 -15
- package/dist/{chunk-B7E2Q3RB.js → chunk-BXFK3QXK.js} +1 -1
- package/dist/{chunk-WQHJNHAR.js → chunk-ES3NZ7SX.js} +1 -1
- package/dist/chunk-S4AGCKXW.js +241 -0
- package/dist/{chunk-T6C5Y2WA.js → chunk-WJZNXLTR.js} +202 -201
- package/dist/cli.js +2 -2
- package/dist/client.d.ts +1 -1
- package/dist/client.js +4 -236
- package/dist/{drive-Cn3mpuDW.d.ts → drive-BtRyexUP.d.ts} +1 -1
- package/dist/embed.css +154 -0
- package/dist/embed.d.ts +21 -0
- package/dist/embed.js +412 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +2 -2
- package/dist/worker.d.ts +23 -4
- package/dist/worker.js +48 -23
- package/llms.txt +5 -0
- package/package.json +23 -3
- package/ui-dist/assets/{index-xUEd8lQS.css → index-B9nizMSQ.css} +1 -1
- package/ui-dist/assets/index-DQzn7NQP.js +49 -0
- package/ui-dist/index.html +2 -2
- package/ui-dist/assets/index-BiU4TGuq.js +0 -49
package/dist/client.js
CHANGED
|
@@ -1,239 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
this.code = code;
|
|
6
|
-
this.status = status;
|
|
7
|
-
this.name = "DriveApiError";
|
|
8
|
-
}
|
|
9
|
-
code;
|
|
10
|
-
status;
|
|
11
|
-
};
|
|
12
|
-
var BASE = "/api/v1/drive";
|
|
13
|
-
var CODES = {
|
|
14
|
-
NOT_FOUND: true,
|
|
15
|
-
NAME_TAKEN: true,
|
|
16
|
-
BLOB_EXISTS: true,
|
|
17
|
-
INVALID_NAME: true,
|
|
18
|
-
INVALID_PATH: true,
|
|
19
|
-
INVALID_REF: true,
|
|
20
|
-
NOT_A_FOLDER: true,
|
|
21
|
-
NOT_A_FILE: true,
|
|
22
|
-
MOVE_INTO_SELF: true,
|
|
23
|
-
TOO_DEEP: true,
|
|
24
|
-
PARENT_GONE: true,
|
|
25
|
-
TRASHED: true,
|
|
26
|
-
NOT_TRASHED: true,
|
|
27
|
-
IS_ROOT: true,
|
|
28
|
-
FORBIDDEN: true,
|
|
29
|
-
BLOB_MISSING: true,
|
|
30
|
-
TOO_LARGE: true,
|
|
31
|
-
INVALID_EXPIRY: true,
|
|
32
|
-
BUSY: true,
|
|
33
|
-
CROSS_DRIVE: true,
|
|
34
|
-
INVALID_SHARE: true,
|
|
35
|
-
TOO_MANY_ITEMS: true,
|
|
36
|
-
UNAUTHORIZED: true,
|
|
37
|
-
NOT_CONFIGURED: true,
|
|
38
|
-
INVALID_LINK: true,
|
|
39
|
-
INTERNAL: true
|
|
40
|
-
};
|
|
41
|
-
var KNOWN_CODES = new Set(Object.keys(CODES));
|
|
42
|
-
var record = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
43
|
-
var text = (v) => typeof v === "string";
|
|
44
|
-
var orNull = (test) => (v) => v === null || test(v);
|
|
45
|
-
var num = (v) => typeof v === "number";
|
|
46
|
-
var ACTOR_KINDS = { human: true, agent_of_human: true, agent_of_agent: true, agent: true, user_token: true };
|
|
47
|
-
var ROLES = ["viewer", "commenter", "editor", "owner"];
|
|
48
|
-
var oneOf = (list) => (v) => typeof v === "string" && list.includes(v);
|
|
49
|
-
var actor = (v) => record(v) && text(v.id) && oneOf(Object.keys(ACTOR_KINDS))(v.kind);
|
|
50
|
-
var isItem = (v) => record(v) && text(v.id) && text(v.owner) && text(v.parentId) && text(v.name) && (v.kind === "folder" || v.kind === "file" || v.kind === "ref") && num(v.size) && orNull(text)(v.mime) && orNull(text)(v.hash) && orNull(text)(v.ref) && num(v.createdAt) && num(v.updatedAt) && actor(v.createdBy) && actor(v.updatedBy) && text(v.createdById) && text(v.updatedById) && typeof v.trashed === "boolean" && orNull(num)(v.trashedAt) && typeof v.machineMade === "boolean";
|
|
51
|
-
var isPathItem = (v) => isItem(v) && typeof v.path === "string";
|
|
52
|
-
var listOf = (check) => (v) => Array.isArray(v) && v.every(check);
|
|
53
|
-
var shape = (test) => (v) => record(v) && test(v);
|
|
54
|
-
var numbers = (...keys) => shape((v) => keys.every((k) => typeof v[k] === "number"));
|
|
55
|
-
var isOk = shape((v) => v.ok === true);
|
|
56
|
-
var RETRY_STATUS = /* @__PURE__ */ new Set([502, 503, 504]);
|
|
57
|
-
function createClient(options) {
|
|
58
|
-
let origin;
|
|
59
|
-
try {
|
|
60
|
-
const url = new URL(options.baseUrl);
|
|
61
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error("not http");
|
|
62
|
-
origin = url.origin;
|
|
63
|
-
} catch {
|
|
64
|
-
throw new TypeError("baseUrl must be an http or https URL");
|
|
65
|
-
}
|
|
66
|
-
const { token } = options;
|
|
67
|
-
if (token !== void 0 && (typeof token !== "string" || !/^[A-Za-z0-9_-]{1,512}$/.test(token))) throw new TypeError("token must be 1 to 512 letters, digits, - or _");
|
|
68
|
-
const retries = options.retries ?? 0;
|
|
69
|
-
if (!Number.isInteger(retries) || retries < 0 || retries > 5) throw new RangeError("retries must be an integer from 0 to 5");
|
|
70
|
-
const doFetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
71
|
-
const query = (params) => {
|
|
72
|
-
const search = new URLSearchParams();
|
|
73
|
-
for (const [key, value] of Object.entries(params)) if (value !== void 0) search.set(key, String(value));
|
|
74
|
-
const text2 = search.toString();
|
|
75
|
-
return text2 === "" ? "" : `?${text2}`;
|
|
76
|
-
};
|
|
77
|
-
async function send(method, path, init) {
|
|
78
|
-
if (init.auth !== false && token === void 0) throw new TypeError("this call needs a token");
|
|
79
|
-
const headers = { ...token === void 0 ? {} : { authorization: `Bearer ${token}` }, ...init.headers };
|
|
80
|
-
let body = init.body;
|
|
81
|
-
if (init.json !== void 0) {
|
|
82
|
-
headers["content-type"] = "application/json";
|
|
83
|
-
body = JSON.stringify(init.json);
|
|
84
|
-
}
|
|
85
|
-
const attempts = method === "GET" ? retries + 1 : 1;
|
|
86
|
-
for (let attempt = 1; ; attempt++) {
|
|
87
|
-
let response;
|
|
88
|
-
try {
|
|
89
|
-
response = await doFetch(`${origin}${BASE}${path}`, { method, headers, body, signal: init.signal, redirect: "manual", ...body instanceof ReadableStream ? { duplex: "half" } : {} });
|
|
90
|
-
} catch (error) {
|
|
91
|
-
if (init.signal?.aborted || error instanceof Error && error.name === "AbortError") throw error;
|
|
92
|
-
if (attempt < attempts) {
|
|
93
|
-
await pause(attempt, init.signal);
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
const refused = body instanceof ReadableStream ? " (a stream body may be refused by this runtime: send bytes)" : "";
|
|
97
|
-
throw new DriveApiError("NETWORK_ERROR", 0, `${error instanceof Error ? `no answer: ${error.message}` : "no answer"}${refused}`);
|
|
98
|
-
}
|
|
99
|
-
if (attempt < attempts && RETRY_STATUS.has(response.status)) {
|
|
100
|
-
await response.body?.cancel();
|
|
101
|
-
await pause(attempt, init.signal);
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
if (response.type === "opaqueredirect" || response.status >= 300 && response.status < 400) {
|
|
105
|
-
await response.body?.cancel();
|
|
106
|
-
throw new DriveApiError("BAD_RESPONSE", response.status, "the server redirected. Set baseUrl to the final URL.");
|
|
107
|
-
}
|
|
108
|
-
if (response.ok) return response;
|
|
109
|
-
throw await errorOf(response, init.signal);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
async function pause(attempt, signal) {
|
|
113
|
-
signal?.throwIfAborted();
|
|
114
|
-
await new Promise((resolve, reject) => {
|
|
115
|
-
const timer = setTimeout(() => {
|
|
116
|
-
signal?.removeEventListener("abort", stop);
|
|
117
|
-
resolve();
|
|
118
|
-
}, Math.min(100 * 2 ** (attempt - 1), 1e3));
|
|
119
|
-
const stop = () => {
|
|
120
|
-
clearTimeout(timer);
|
|
121
|
-
reject(signal?.reason);
|
|
122
|
-
};
|
|
123
|
-
signal?.addEventListener("abort", stop, { once: true });
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
async function errorOf(response, signal) {
|
|
127
|
-
let code = "BAD_RESPONSE";
|
|
128
|
-
let message = `the server answered ${response.status}`;
|
|
129
|
-
try {
|
|
130
|
-
const parsed = await response.json();
|
|
131
|
-
if (typeof parsed.error?.code === "string" && KNOWN_CODES.has(parsed.error.code)) code = parsed.error.code;
|
|
132
|
-
else if (typeof parsed.error?.code === "string") message = `the server answered ${response.status} with an unknown code, ${JSON.stringify(parsed.error.code.slice(0, 40))}`;
|
|
133
|
-
if (code !== "BAD_RESPONSE" && typeof parsed.error?.message === "string") message = parsed.error.message;
|
|
134
|
-
} catch (error) {
|
|
135
|
-
if (signal?.aborted) throw error;
|
|
136
|
-
}
|
|
137
|
-
return new DriveApiError(code, response.status, message);
|
|
138
|
-
}
|
|
139
|
-
async function json(method, path, init, check) {
|
|
140
|
-
const response = await send(method, path, init);
|
|
141
|
-
let value;
|
|
142
|
-
try {
|
|
143
|
-
value = await response.json();
|
|
144
|
-
} catch (error) {
|
|
145
|
-
if (init.signal?.aborted) throw error;
|
|
146
|
-
if (!(error instanceof SyntaxError)) throw new DriveApiError("NETWORK_ERROR", 0, "the connection dropped while the body came in");
|
|
147
|
-
throw new DriveApiError("BAD_RESPONSE", response.status, "the answer is not JSON");
|
|
148
|
-
}
|
|
149
|
-
if (!check(value)) throw new DriveApiError("BAD_RESPONSE", response.status, "the answer is not the shape that the API sends");
|
|
150
|
-
return value;
|
|
151
|
-
}
|
|
152
|
-
async function bytes(path, init, verify = false) {
|
|
153
|
-
const response = await send("GET", path, init);
|
|
154
|
-
let data;
|
|
155
|
-
try {
|
|
156
|
-
data = new Uint8Array(await response.arrayBuffer());
|
|
157
|
-
} catch (error) {
|
|
158
|
-
if (init.signal?.aborted) throw error;
|
|
159
|
-
throw new DriveApiError("NETWORK_ERROR", 0, "the connection dropped while the body came in");
|
|
160
|
-
}
|
|
161
|
-
const sha256 = response.headers.get("x-content-sha256") ?? "";
|
|
162
|
-
if (verify) {
|
|
163
|
-
if (!/^[0-9a-f]{64}$/.test(sha256)) throw new DriveApiError("BAD_RESPONSE", response.status, "the answer has no valid x-content-sha256");
|
|
164
|
-
if (typeof crypto === "undefined" || !crypto.subtle) throw new DriveApiError("BAD_RESPONSE", response.status, "this runtime has no WebCrypto, so the bytes cannot be checked. Use https.");
|
|
165
|
-
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", data));
|
|
166
|
-
if (Array.from(digest, (b) => b.toString(16).padStart(2, "0")).join("") !== sha256) throw new DriveApiError("BAD_RESPONSE", response.status, "the bytes do not match their hash");
|
|
167
|
-
}
|
|
168
|
-
return { data, contentType: response.headers.get("content-type") ?? "application/octet-stream", sha256 };
|
|
169
|
-
}
|
|
170
|
-
const link = (token2) => {
|
|
171
|
-
if (token2 === void 0) return void 0;
|
|
172
|
-
if (!/^[A-Za-z0-9_-]{1,512}$/.test(token2)) throw new TypeError("a link token has 1 to 512 letters, digits, - or _");
|
|
173
|
-
return { "x-link-token": token2 };
|
|
174
|
-
};
|
|
175
|
-
const id = (value) => {
|
|
176
|
-
if (typeof value !== "string" || !/^[A-Za-z0-9_-][A-Za-z0-9_.-]*$/.test(value) || value === "." || value === "..") throw new TypeError("an item id has letters, digits, -, _ and . (not first), and is not . or ..");
|
|
177
|
-
return value;
|
|
178
|
-
};
|
|
179
|
-
const isUrl = shape((v) => typeof v.url === "string" && /^https?:\/\//.test(v.url) && URL.canParse(v.url) && typeof v.expiresInSeconds === "number");
|
|
180
|
-
const isShares = shape(
|
|
181
|
-
(v) => listOf(shape((g) => (g.subjectKind === "human" || g.subjectKind === "agent") && text(g.subjectId) && oneOf(ROLES)(g.role)))(v.grants) && listOf(shape((l) => text(l.id) && oneOf(ROLES)(l.role) && typeof l.signedInOnly === "boolean" && orNull(num)(l.expiresAt) && typeof l.revoked === "boolean"))(v.links) && listOf(shape((i) => text(i.id) && oneOf(ROLES)(i.role) && orNull(text)(i.email) && orNull(text)(i.handle) && orNull(num)(i.expiresAt) && typeof i.accepted === "boolean"))(v.invites)
|
|
182
|
-
);
|
|
183
|
-
const isHome = shape((v) => record(v.apps) && Object.values(v.apps).every(num) && num(v.files) && listOf(shape((i) => isPathItem(i) && orNull(text)(i.app)))(v.items));
|
|
184
|
-
const isId = shape((v) => typeof v.id === "string");
|
|
185
|
-
const isLink = shape((v) => typeof v.id === "string" && typeof v.token === "string");
|
|
186
|
-
return {
|
|
187
|
-
/** The live items of a folder. */
|
|
188
|
-
list: (path = "/", o = {}) => json("GET", `/items${query({ path })}`, o, listOf(isItem)),
|
|
189
|
-
stat: (path, o = {}) => json("GET", `/stat${query({ path })}`, o, isItem),
|
|
190
|
-
mkdir: (path, opts = {}, o = {}) => json("POST", "/folders", { json: { path, parents: opts.parents === true }, ...o }, isItem),
|
|
191
|
-
/** Writes a file. A `ReadableStream` body streams in runtimes that allow it. */
|
|
192
|
-
upload: (path, body, opts = {}, o = {}) => json("PUT", `/files${query({ path, mime: opts.mime, parents: opts.parents === true ? true : void 0, onConflict: opts.onConflict })}`, {
|
|
193
|
-
body: typeof body === "string" ? new TextEncoder().encode(body) : body,
|
|
194
|
-
headers: { "content-type": "application/octet-stream" },
|
|
195
|
-
...o
|
|
196
|
-
}, isItem),
|
|
197
|
-
/** The bytes. They are checked against the hash that the server sends. */
|
|
198
|
-
download: (path, o = {}) => bytes(`/files${query({ path })}`, o, true),
|
|
199
|
-
/** A signed link that works without a token, for `expires` seconds. You fetch the link yourself. */
|
|
200
|
-
url: (path, expires, o = {}) => json("GET", `/url${query({ path, expires })}`, o, isUrl),
|
|
201
|
-
move: (src, folder, opts = {}, o = {}) => json("POST", "/move", { json: { src, folder, ...opts }, ...o }, isItem),
|
|
202
|
-
trash: (path, o = {}) => json("POST", "/trash", { json: { path }, ...o }, isItem),
|
|
203
|
-
listTrash: (o = {}) => json("GET", "/trash", o, listOf(isItem)),
|
|
204
|
-
restore: (itemId, opts = {}, o = {}) => json("POST", "/restore", { json: { id: itemId, ...opts }, ...o }, isItem),
|
|
205
|
-
/** Deletes a trashed item for good. */
|
|
206
|
-
purge: (itemId, o = {}) => json("POST", "/purge", { json: { id: itemId }, ...o }, numbers("removed")),
|
|
207
|
-
search: (q = {}, o = {}) => json("GET", `/search${query(q)}`, o, listOf(isPathItem)),
|
|
208
|
-
/** Everything the caller has, as a zip. */
|
|
209
|
-
export: async (o = {}) => {
|
|
210
|
-
const { data } = await bytes("/export", o);
|
|
211
|
-
if (data[0] !== 80 || data[1] !== 75) throw new DriveApiError("BAD_RESPONSE", 200, "the answer is not a zip");
|
|
212
|
-
return data;
|
|
213
|
-
},
|
|
214
|
-
/** Erases the caller for good. The client sends `confirm=yes`. */
|
|
215
|
-
deleteAccount: (o = {}) => json("DELETE", "/account?confirm=yes", o, numbers("removed", "grants", "invites", "anonymized")),
|
|
216
|
-
home: (q = {}, o = {}) => json("GET", `/home${query(q)}`, o, isHome),
|
|
217
|
-
registerDocument: (input, o = {}) => json("POST", "/documents", { json: input, ...o }, isItem),
|
|
218
|
-
forgetDocument: (ref, o = {}) => json("DELETE", `/documents${query({ ref })}`, o, numbers("trashed")),
|
|
219
|
-
shares: (path, o = {}) => json("GET", `/shares${query({ path })}`, o, isShares),
|
|
220
|
-
share: (path, subject, role, o = {}) => json("POST", "/shares", { json: { path, subjectId: subject.id, subjectKind: subject.kind, role }, ...o }, isOk),
|
|
221
|
-
unshare: (path, subject, o = {}) => json("DELETE", `/shares${query({ path, subjectId: subject.id, subjectKind: subject.kind })}`, o, isOk),
|
|
222
|
-
/** Invite by `email` (a person) or `handle` (an agent). It sends no message. */
|
|
223
|
-
invite: (path, input, o = {}) => json("POST", "/invites", { json: { path, ...input }, ...o }, isId),
|
|
224
|
-
acceptInvite: (inviteId, o = {}) => json("POST", "/invites/accept", { json: { id: inviteId }, ...o }, isOk),
|
|
225
|
-
/** The token of a link comes back once. */
|
|
226
|
-
createLink: (path, input, o = {}) => json("POST", "/links", { json: { path, ...input }, ...o }, isLink),
|
|
227
|
-
revokeLink: (linkId, o = {}) => json("DELETE", `/links${query({ id: linkId })}`, o, isOk),
|
|
228
|
-
/** The next three read a shared item by id. Pass a link token, a bearer token, or both. */
|
|
229
|
-
openShared: async (itemId, opts = {}) => json("GET", `/shared/${id(itemId)}`, { auth: false, headers: link(opts.linkToken), signal: opts.signal }, isItem),
|
|
230
|
-
listShared: async (itemId, opts = {}) => json("GET", `/shared/${id(itemId)}/items`, { auth: false, headers: link(opts.linkToken), signal: opts.signal }, listOf(isItem)),
|
|
231
|
-
readShared: async (itemId, opts = {}) => {
|
|
232
|
-
const { data, contentType } = await bytes(`/shared/${id(itemId)}/content`, { auth: false, headers: link(opts.linkToken), signal: opts.signal });
|
|
233
|
-
return { data, contentType };
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
DriveApiError,
|
|
3
|
+
createClient
|
|
4
|
+
} from "./chunk-S4AGCKXW.js";
|
|
237
5
|
export {
|
|
238
6
|
DriveApiError,
|
|
239
7
|
createClient
|
|
@@ -200,7 +200,7 @@ declare function createDrive(deps: DriveDeps): {
|
|
|
200
200
|
exportPerson(ctx: Ctx): Promise<{
|
|
201
201
|
subject: {
|
|
202
202
|
id: string;
|
|
203
|
-
kind: "human" | "
|
|
203
|
+
kind: "human" | "agent_of_human" | "agent_of_agent" | "agent" | "user_token" | null;
|
|
204
204
|
};
|
|
205
205
|
partial: boolean;
|
|
206
206
|
authored: {
|
package/dist/embed.css
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/* ui/app.css */
|
|
2
|
+
.drive-main {
|
|
3
|
+
max-width: 72rem;
|
|
4
|
+
}
|
|
5
|
+
.drive-muted {
|
|
6
|
+
color: var(--ad-color-text-muted);
|
|
7
|
+
}
|
|
8
|
+
.drive-status {
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
gap: var(--ad-space-2);
|
|
12
|
+
color: var(--ad-color-text-muted);
|
|
13
|
+
}
|
|
14
|
+
.drive-signin {
|
|
15
|
+
display: grid;
|
|
16
|
+
gap: var(--ad-space-4);
|
|
17
|
+
max-width: 24rem;
|
|
18
|
+
}
|
|
19
|
+
.drive-form {
|
|
20
|
+
display: grid;
|
|
21
|
+
gap: var(--ad-space-4);
|
|
22
|
+
}
|
|
23
|
+
.drive-form__actions {
|
|
24
|
+
display: flex;
|
|
25
|
+
justify-content: flex-end;
|
|
26
|
+
gap: var(--ad-space-2);
|
|
27
|
+
}
|
|
28
|
+
.drive-drop {
|
|
29
|
+
min-height: 60vh;
|
|
30
|
+
border: 2px dashed transparent;
|
|
31
|
+
border-radius: var(--ad-radius-md);
|
|
32
|
+
padding: var(--ad-space-2);
|
|
33
|
+
}
|
|
34
|
+
.drive-drop--over {
|
|
35
|
+
border-color: var(--ad-color-accent);
|
|
36
|
+
background: var(--ad-color-accent-subtle);
|
|
37
|
+
}
|
|
38
|
+
.drive-bar {
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
justify-content: space-between;
|
|
42
|
+
gap: var(--ad-space-4);
|
|
43
|
+
flex-wrap: wrap;
|
|
44
|
+
margin-bottom: var(--ad-space-4);
|
|
45
|
+
}
|
|
46
|
+
.drive-bar h1 {
|
|
47
|
+
margin: 0;
|
|
48
|
+
font-size: var(--ad-font-size-xl);
|
|
49
|
+
overflow-wrap: anywhere;
|
|
50
|
+
}
|
|
51
|
+
.drive-crumbs ol {
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-wrap: wrap;
|
|
54
|
+
gap: var(--ad-space-2);
|
|
55
|
+
list-style: none;
|
|
56
|
+
margin: 0 0 var(--ad-space-2);
|
|
57
|
+
padding: 0;
|
|
58
|
+
color: var(--ad-color-text-muted);
|
|
59
|
+
}
|
|
60
|
+
.drive-crumbs li + li::before {
|
|
61
|
+
content: "/";
|
|
62
|
+
margin-right: var(--ad-space-2);
|
|
63
|
+
}
|
|
64
|
+
.drive-crumbs [aria-current=page] {
|
|
65
|
+
color: var(--ad-color-text);
|
|
66
|
+
font-weight: var(--ad-font-weight-semibold);
|
|
67
|
+
}
|
|
68
|
+
.drive-split {
|
|
69
|
+
display: grid;
|
|
70
|
+
grid-template-columns: 14rem minmax(0, 1fr);
|
|
71
|
+
gap: var(--ad-space-6);
|
|
72
|
+
}
|
|
73
|
+
@media (max-width: 48rem) {
|
|
74
|
+
.drive-split {
|
|
75
|
+
grid-template-columns: 1fr;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
.drive-tree {
|
|
79
|
+
list-style: none;
|
|
80
|
+
margin: 0;
|
|
81
|
+
padding: 0;
|
|
82
|
+
}
|
|
83
|
+
.drive-tree .drive-tree {
|
|
84
|
+
padding-left: var(--ad-space-4);
|
|
85
|
+
}
|
|
86
|
+
.drive-tree a {
|
|
87
|
+
display: flex;
|
|
88
|
+
align-items: center;
|
|
89
|
+
gap: var(--ad-space-2);
|
|
90
|
+
padding: var(--ad-space-1) var(--ad-space-2);
|
|
91
|
+
border-radius: var(--ad-radius-sm);
|
|
92
|
+
color: var(--ad-color-text);
|
|
93
|
+
text-decoration: none;
|
|
94
|
+
}
|
|
95
|
+
.drive-tree a:hover {
|
|
96
|
+
background: var(--ad-color-surface-hover);
|
|
97
|
+
}
|
|
98
|
+
.drive-tree a[aria-current=page] {
|
|
99
|
+
background: var(--ad-color-accent-subtle);
|
|
100
|
+
color: var(--ad-color-accent-text);
|
|
101
|
+
font-weight: var(--ad-font-weight-semibold);
|
|
102
|
+
}
|
|
103
|
+
.drive-tree svg {
|
|
104
|
+
width: 1rem;
|
|
105
|
+
height: 1rem;
|
|
106
|
+
flex: none;
|
|
107
|
+
}
|
|
108
|
+
.drive-grid {
|
|
109
|
+
display: grid;
|
|
110
|
+
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
|
|
111
|
+
gap: var(--ad-space-4);
|
|
112
|
+
list-style: none;
|
|
113
|
+
margin: 0;
|
|
114
|
+
padding: 0;
|
|
115
|
+
}
|
|
116
|
+
.drive-card {
|
|
117
|
+
position: relative;
|
|
118
|
+
display: grid;
|
|
119
|
+
gap: var(--ad-space-2);
|
|
120
|
+
padding: var(--ad-space-4);
|
|
121
|
+
border: 1px solid var(--ad-color-border);
|
|
122
|
+
border-radius: var(--ad-radius-md);
|
|
123
|
+
background: var(--ad-color-surface);
|
|
124
|
+
}
|
|
125
|
+
.drive-card__icon svg {
|
|
126
|
+
width: 2rem;
|
|
127
|
+
height: 2rem;
|
|
128
|
+
}
|
|
129
|
+
.drive-card__name {
|
|
130
|
+
overflow-wrap: anywhere;
|
|
131
|
+
font-weight: var(--ad-font-weight-medium);
|
|
132
|
+
color: var(--ad-color-text);
|
|
133
|
+
}
|
|
134
|
+
.drive-card__meta {
|
|
135
|
+
font-size: var(--ad-font-size-sm);
|
|
136
|
+
color: var(--ad-color-text-muted);
|
|
137
|
+
}
|
|
138
|
+
.drive-card__actions {
|
|
139
|
+
display: flex;
|
|
140
|
+
align-items: center;
|
|
141
|
+
justify-content: space-between;
|
|
142
|
+
gap: var(--ad-space-2);
|
|
143
|
+
}
|
|
144
|
+
.drive-root {
|
|
145
|
+
display: grid;
|
|
146
|
+
grid-template-columns: 12rem minmax(0, 1fr);
|
|
147
|
+
gap: var(--ad-space-6);
|
|
148
|
+
color: var(--ad-color-text);
|
|
149
|
+
}
|
|
150
|
+
@media (max-width: 48rem) {
|
|
151
|
+
.drive-root {
|
|
152
|
+
grid-template-columns: 1fr;
|
|
153
|
+
}
|
|
154
|
+
}
|
package/dist/embed.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
interface DriveUiOptions {
|
|
4
|
+
/** The address of the drive server, for example `https://drive.example.com`. Only its origin is used. */
|
|
5
|
+
baseUrl: string;
|
|
6
|
+
/** Returns the bearer token for the next request. It runs before every request, so it can refresh a token. No token sends no `authorization` header. */
|
|
7
|
+
getToken: () => string | undefined | Promise<string | undefined>;
|
|
8
|
+
}
|
|
9
|
+
/** The folders and trash of the signed-in caller, with upload, share, and the machine-made label. It needs the tooltip and toast providers above it. `mount` adds them.
|
|
10
|
+
* It shows the root again when `baseUrl` changes. A bad `baseUrl` or `getToken` throws. Pass a stable `getToken`, because a new function makes a new client. */
|
|
11
|
+
declare function DriveBrowser({ baseUrl, getToken }: DriveUiOptions): react.JSX.Element | null;
|
|
12
|
+
interface MountedDrive {
|
|
13
|
+
/** Show the drive with another address or token callback. The folder the person has open resets. */
|
|
14
|
+
update(options: DriveUiOptions): void;
|
|
15
|
+
/** Remove the UI. A request that runs is aborted, and no later request starts or calls `getToken`. Calling it again does nothing. */
|
|
16
|
+
unmount(): void;
|
|
17
|
+
}
|
|
18
|
+
/** Render the drive into an element, for an app that does not use React itself. A bad `baseUrl` or `getToken` throws a `TypeError`. */
|
|
19
|
+
declare function mount(element: HTMLElement, options: DriveUiOptions): MountedDrive;
|
|
20
|
+
|
|
21
|
+
export { DriveBrowser, type DriveUiOptions, type MountedDrive, mount };
|