@protobase/ui 0.55.4 → 0.55.6
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/dist/app/current-address.d.ts +1 -0
- package/dist/app/current-address.js +2 -1
- package/dist/app/full-area-record.js +33 -0
- package/dist/app/record-page.js +25 -3
- package/dist/chat/composer-files.js +13 -0
- package/dist/chat/composer-send.js +8 -0
- package/dist/chat/composer.d.ts +12 -3
- package/dist/chat/composer.js +58 -24
- package/dist/package.js +1 -1
- package/dist/shell/assets/app-Co8P2yd_.js +9 -0
- package/dist/shell/assets/index-mZM1MZhj.css +2 -0
- package/dist/shell/assets/{index-BIUTidjX.js → index-s_7wHErR.js} +2 -2
- package/dist/shell/assets/{records-map-BwX2z-di.js → records-map-CB1CggIx.js} +2 -2
- package/dist/shell/assets/{shared-protobase-ui-BD_5D_Ot.js → shared-protobase-ui-MiSbBekN.js} +2 -2
- package/dist/shell/index.html +4 -4
- package/dist/shell/protobase.shell.json +1 -1
- package/dist/shell/protobase.sw.js +2 -2
- package/dist/shell-source-maps/assets/{app-NiljOxYe.js.map → app-Co8P2yd_.js.map} +1 -1
- package/dist/shell-source-maps/assets/{index-BIUTidjX.js.map → index-s_7wHErR.js.map} +1 -1
- package/dist/shell-source-maps/assets/{records-map-BwX2z-di.js.map → records-map-CB1CggIx.js.map} +1 -1
- package/dist/shell-source-maps/assets/{shared-protobase-ui-BD_5D_Ot.js.map → shared-protobase-ui-MiSbBekN.js.map} +1 -1
- package/package.json +4 -4
- package/dist/shell/assets/app-NiljOxYe.js +0 -9
- package/dist/shell/assets/index-BRYcNA4H.css +0 -2
|
@@ -36,7 +36,8 @@ const currentAddress = (read, defaults) => {
|
|
|
36
36
|
key: encodeKey(read.key),
|
|
37
37
|
...read.edit && { edit: true },
|
|
38
38
|
...read.open && { open: read.open },
|
|
39
|
-
...read.tab && { tab: read.tab }
|
|
39
|
+
...read.tab && { tab: read.tab },
|
|
40
|
+
...read.standard && { standard: true }
|
|
40
41
|
};
|
|
41
42
|
if (read?.kind !== "list") return read;
|
|
42
43
|
return {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useProjectUi } from "./pages/project-ui.js";
|
|
2
|
+
import { CustomBoundary } from "./pages/custom-boundary.js";
|
|
3
|
+
import { RecordScope } from "./pages/record-scope.js";
|
|
4
|
+
import { usePageScroll } from "../app-shell/page-scroll.js";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
//#region src/app/full-area-record.tsx
|
|
7
|
+
/**
|
|
8
|
+
* A record page that is a component of the app's own (`.record({ component })`), with the record around it as
|
|
9
|
+
* `useLayoutRecord()` gives it. It fills the content area, which does not scroll while it shows; `standard`, the
|
|
10
|
+
* record's standard page, shows instead while the component is missing or when it throws.
|
|
11
|
+
*/
|
|
12
|
+
const FullAreaRecord = ({ component, scope, standard }) => {
|
|
13
|
+
const { components } = useProjectUi();
|
|
14
|
+
const Component = components?.[component];
|
|
15
|
+
if (!Component) return standard;
|
|
16
|
+
return /* @__PURE__ */ jsx(CustomBoundary, {
|
|
17
|
+
name: component,
|
|
18
|
+
fallback: standard,
|
|
19
|
+
children: /* @__PURE__ */ jsx(FullArea, { children: /* @__PURE__ */ jsx(RecordScope, {
|
|
20
|
+
value: scope,
|
|
21
|
+
children: /* @__PURE__ */ jsx(Component, {})
|
|
22
|
+
}) })
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
const FullArea = ({ children }) => {
|
|
26
|
+
usePageScroll(false);
|
|
27
|
+
return /* @__PURE__ */ jsx("div", {
|
|
28
|
+
className: "grid min-h-0 flex-1 grid-cols-[minmax(0,1fr)] grid-rows-[minmax(0,1fr)]",
|
|
29
|
+
children
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
//#endregion
|
|
33
|
+
export { FullAreaRecord };
|
package/dist/app/record-page.js
CHANGED
|
@@ -47,6 +47,7 @@ import { useDeleteFlow } from "./delete/use-delete-flow.js";
|
|
|
47
47
|
import { useRestore } from "./delete/use-restore.js";
|
|
48
48
|
import { auditField } from "../live/audit-fields.js";
|
|
49
49
|
import { CustomSection } from "./custom-section.js";
|
|
50
|
+
import { FullAreaRecord } from "./full-area-record.js";
|
|
50
51
|
import { MapSection } from "./map/map-section.js";
|
|
51
52
|
import { RecordChartSection } from "./record-chart/record-chart-section.js";
|
|
52
53
|
import { RecordActions } from "./pages/record-actions.js";
|
|
@@ -101,7 +102,8 @@ const layoutFor = (model, view, read) => {
|
|
|
101
102
|
];
|
|
102
103
|
};
|
|
103
104
|
const RecordPage = ({ resource, recordKey }) => {
|
|
104
|
-
const { resources } = useAdminMeta();
|
|
105
|
+
const { resources, views } = useAdminMeta();
|
|
106
|
+
const { address } = useAppAddress();
|
|
105
107
|
const model = resources[resource];
|
|
106
108
|
const record = useOpenedRecord(resource, recordKey);
|
|
107
109
|
if (!model) return /* @__PURE__ */ jsx(Notice, {
|
|
@@ -122,12 +124,30 @@ const RecordPage = ({ resource, recordKey }) => {
|
|
|
122
124
|
error: record.error
|
|
123
125
|
})
|
|
124
126
|
});
|
|
125
|
-
|
|
127
|
+
const standard = /* @__PURE__ */ jsx(LoadedRecord, {
|
|
126
128
|
model,
|
|
127
129
|
recordKey,
|
|
128
130
|
stored: record.data,
|
|
129
131
|
preview: record.isPlaceholderData
|
|
130
132
|
}, record.isPlaceholderData ? "preview" : "loaded");
|
|
133
|
+
const fullArea = views[resource]?.record;
|
|
134
|
+
if (!fullArea || address?.kind === "record" && address.standard) return standard;
|
|
135
|
+
if (record.isPlaceholderData) return /* @__PURE__ */ jsx(Notice, {
|
|
136
|
+
title: text$1("loading"),
|
|
137
|
+
children: text$1("fetching")
|
|
138
|
+
});
|
|
139
|
+
const { record: fields, etag, permissions } = record.data;
|
|
140
|
+
return /* @__PURE__ */ jsx(FullAreaRecord, {
|
|
141
|
+
component: fullArea.component,
|
|
142
|
+
scope: {
|
|
143
|
+
model,
|
|
144
|
+
record: fields,
|
|
145
|
+
key: recordKey,
|
|
146
|
+
etag,
|
|
147
|
+
permissions
|
|
148
|
+
},
|
|
149
|
+
standard
|
|
150
|
+
});
|
|
131
151
|
};
|
|
132
152
|
const RecordNotFound = ({ model, recordKey }) => {
|
|
133
153
|
const elsewhere = useRecordElsewhere(model, recordKey);
|
|
@@ -178,13 +198,15 @@ const LoadedRecord = ({ model, recordKey, stored, preview }) => {
|
|
|
178
198
|
const openGroups = address?.kind === "record" ? address.open : void 0;
|
|
179
199
|
const atEdit = address?.kind === "record" && address.edit === true;
|
|
180
200
|
const addressedTab = address?.kind === "record" ? address.tab : void 0;
|
|
201
|
+
const standard = address?.kind === "record" && address.standard === true;
|
|
181
202
|
const recordAt = (open = [], edit = false, tab) => ({
|
|
182
203
|
kind: "record",
|
|
183
204
|
resource: model.name,
|
|
184
205
|
key: recordKey,
|
|
185
206
|
...edit && { edit },
|
|
186
207
|
...open.length > 0 && { open },
|
|
187
|
-
...tab && { tab }
|
|
208
|
+
...tab && { tab },
|
|
209
|
+
...standard && { standard }
|
|
188
210
|
});
|
|
189
211
|
const readAddress = recordAt(openGroups, false, addressedTab);
|
|
190
212
|
const editAddress = recordAt(openGroups, true, addressedTab);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/chat/composer-files.ts
|
|
2
|
+
/** Whether a drag carries files, which the composer takes, rather than text, which the box takes as usual. */
|
|
3
|
+
const carriesFiles = (transfer) => transfer.types.includes("Files");
|
|
4
|
+
/**
|
|
5
|
+
* The files a paste gives, or none when it also holds text: a copy from a spreadsheet or a web page can hold an image
|
|
6
|
+
* of its text too, and text pasted into the box stays text.
|
|
7
|
+
*/
|
|
8
|
+
const pastedFiles = (transfer) => {
|
|
9
|
+
if (transfer.types.includes("text/plain")) return [];
|
|
10
|
+
return [...transfer.files];
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { carriesFiles, pastedFiles };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/chat/composer-send.ts
|
|
2
|
+
/** Whether the composer may send `message`, its trimmed text: never while busy, and empty text only when allowed, such as for a message of files alone. */
|
|
3
|
+
const canSend = (message, { busy, canSendEmpty }) => {
|
|
4
|
+
if (busy) return false;
|
|
5
|
+
return message !== "" || canSendEmpty === true;
|
|
6
|
+
};
|
|
7
|
+
//#endregion
|
|
8
|
+
export { canSend };
|
package/dist/chat/composer.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PeopleSearch } from "../mentions/use-people-search.js";
|
|
2
2
|
import "../mentions/index.js";
|
|
3
|
-
import { Ref } from "react";
|
|
3
|
+
import { ReactNode, Ref } from "react";
|
|
4
4
|
//#region src/chat/composer.d.ts
|
|
5
5
|
export type ComposerProps = {
|
|
6
6
|
/** Gets the trimmed text and the ids of the people it mentions, each once. */
|
|
@@ -11,10 +11,19 @@ export type ComposerProps = {
|
|
|
11
11
|
inputRef?: Ref<HTMLTextAreaElement>;
|
|
12
12
|
/** Finds the people `@` lists by the name typed after it, such as a channel's members; without it, `@` is plain text. */
|
|
13
13
|
searchPeople?: PeopleSearch;
|
|
14
|
+
/** Drawn inside the composer above the box, such as a tray of the files picked for the message. */
|
|
15
|
+
attachments?: ReactNode;
|
|
16
|
+
/** Drawn left of the box, such as a button that picks files. */
|
|
17
|
+
actions?: ReactNode;
|
|
18
|
+
/** Enter and the button send even with no text, `onSend('', [])`, such as when files are picked. */
|
|
19
|
+
canSendEmpty?: boolean;
|
|
20
|
+
/** Gets the files pasted into the box or dropped on the composer; the composer itself uploads and sends nothing. A paste that holds text stays text. */
|
|
21
|
+
onFiles?: (files: File[]) => void;
|
|
14
22
|
};
|
|
15
23
|
/**
|
|
16
24
|
* The text box under a chat, which grows with its text. Enter or the button sends the trimmed text and clears the box,
|
|
17
|
-
* Shift+Enter starts a new line; while the list of people to mention is open, Enter picks from it instead.
|
|
25
|
+
* Shift+Enter starts a new line; while the list of people to mention is open, Enter picks from it instead. With `canSendEmpty`,
|
|
26
|
+
* such as while files are picked, it sends with no text too.
|
|
18
27
|
*/
|
|
19
|
-
export declare const Composer: ({ onSend, busy, placeholder, inputRef, searchPeople }: ComposerProps) => import("react").JSX.Element;
|
|
28
|
+
export declare const Composer: ({ onSend, busy, placeholder, inputRef, searchPeople, attachments, actions, canSendEmpty, onFiles }: ComposerProps) => import("react").JSX.Element;
|
|
20
29
|
//#endregion
|
package/dist/chat/composer.js
CHANGED
|
@@ -2,19 +2,26 @@ import { Button } from "../primitives/button.js";
|
|
|
2
2
|
import { text } from "./messages.js";
|
|
3
3
|
import { mentionedIds } from "../mentions/mention-text.js";
|
|
4
4
|
import { MentionBox, emptyDraft } from "../mentions/mention-box.js";
|
|
5
|
+
import { carriesFiles, pastedFiles } from "./composer-files.js";
|
|
6
|
+
import { canSend } from "./composer-send.js";
|
|
5
7
|
import { useState } from "react";
|
|
6
8
|
import { SendHorizontal } from "lucide-react";
|
|
7
9
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
10
|
//#region src/chat/composer.tsx
|
|
9
11
|
/**
|
|
10
12
|
* The text box under a chat, which grows with its text. Enter or the button sends the trimmed text and clears the box,
|
|
11
|
-
* Shift+Enter starts a new line; while the list of people to mention is open, Enter picks from it instead.
|
|
13
|
+
* Shift+Enter starts a new line; while the list of people to mention is open, Enter picks from it instead. With `canSendEmpty`,
|
|
14
|
+
* such as while files are picked, it sends with no text too.
|
|
12
15
|
*/
|
|
13
|
-
const Composer = ({ onSend, busy, placeholder, inputRef, searchPeople }) => {
|
|
16
|
+
const Composer = ({ onSend, busy, placeholder, inputRef, searchPeople, attachments, actions, canSendEmpty, onFiles }) => {
|
|
14
17
|
const [draft, setDraft] = useState(emptyDraft);
|
|
15
18
|
const message = draft.text.trim();
|
|
19
|
+
const sendable = canSend(message, {
|
|
20
|
+
busy,
|
|
21
|
+
canSendEmpty
|
|
22
|
+
});
|
|
16
23
|
const send = () => {
|
|
17
|
-
if (!
|
|
24
|
+
if (!sendable) return;
|
|
18
25
|
onSend?.(message, mentionedIds(message, draft.picked));
|
|
19
26
|
setDraft(emptyDraft);
|
|
20
27
|
};
|
|
@@ -23,33 +30,60 @@ const Composer = ({ onSend, busy, placeholder, inputRef, searchPeople }) => {
|
|
|
23
30
|
event.preventDefault();
|
|
24
31
|
send();
|
|
25
32
|
};
|
|
33
|
+
const onPaste = (event) => {
|
|
34
|
+
if (!onFiles) return;
|
|
35
|
+
const files = pastedFiles(event.clipboardData);
|
|
36
|
+
if (files.length === 0) return;
|
|
37
|
+
event.preventDefault();
|
|
38
|
+
onFiles(files);
|
|
39
|
+
};
|
|
40
|
+
const onDragOver = (event) => {
|
|
41
|
+
if (!onFiles || !carriesFiles(event.dataTransfer)) return;
|
|
42
|
+
event.preventDefault();
|
|
43
|
+
};
|
|
44
|
+
const onDrop = (event) => {
|
|
45
|
+
if (!onFiles || !carriesFiles(event.dataTransfer)) return;
|
|
46
|
+
event.preventDefault();
|
|
47
|
+
onFiles([...event.dataTransfer.files]);
|
|
48
|
+
};
|
|
26
49
|
return /* @__PURE__ */ jsxs("form", {
|
|
27
|
-
className: "flex items-end gap-2",
|
|
50
|
+
className: "flex flex-wrap items-end gap-2",
|
|
28
51
|
onSubmit: (event) => {
|
|
29
52
|
event.preventDefault();
|
|
30
53
|
send();
|
|
31
54
|
},
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
55
|
+
onDragOver,
|
|
56
|
+
onDrop,
|
|
57
|
+
children: [
|
|
58
|
+
attachments && /* @__PURE__ */ jsx("div", {
|
|
59
|
+
className: "basis-full",
|
|
60
|
+
children: attachments
|
|
61
|
+
}),
|
|
62
|
+
actions,
|
|
63
|
+
/* @__PURE__ */ jsx("div", {
|
|
64
|
+
className: "min-w-0 flex-1",
|
|
65
|
+
children: /* @__PURE__ */ jsx(MentionBox, {
|
|
66
|
+
ref: inputRef,
|
|
67
|
+
rows: 1,
|
|
68
|
+
"aria-label": text("message"),
|
|
69
|
+
placeholder,
|
|
70
|
+
value: draft,
|
|
71
|
+
onChange: setDraft,
|
|
72
|
+
searchPeople,
|
|
73
|
+
listAt: "above",
|
|
74
|
+
onKeyDown,
|
|
75
|
+
onPaste
|
|
76
|
+
})
|
|
77
|
+
}),
|
|
78
|
+
/* @__PURE__ */ jsx(Button, {
|
|
79
|
+
type: "submit",
|
|
80
|
+
variant: "primary",
|
|
81
|
+
className: "w-8 px-0",
|
|
82
|
+
"aria-label": text("send"),
|
|
83
|
+
disabled: !sendable,
|
|
84
|
+
children: /* @__PURE__ */ jsx(SendHorizontal, { className: "size-4" })
|
|
44
85
|
})
|
|
45
|
-
|
|
46
|
-
type: "submit",
|
|
47
|
-
variant: "primary",
|
|
48
|
-
className: "w-8 px-0",
|
|
49
|
-
"aria-label": text("send"),
|
|
50
|
-
disabled: !message || busy,
|
|
51
|
-
children: /* @__PURE__ */ jsx(SendHorizontal, { className: "size-4" })
|
|
52
|
-
})]
|
|
86
|
+
]
|
|
53
87
|
});
|
|
54
88
|
};
|
|
55
89
|
//#endregion
|
package/dist/package.js
CHANGED