@protobase/schema 0.21.7 → 0.21.9
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/address-errors.js +1 -1
- package/dist/ai-prompt.d.ts +8 -0
- package/dist/ai-prompt.js +17 -0
- package/dist/app-address.d.ts +2 -0
- package/dist/app-address.js +22 -0
- package/dist/field.d.ts +12 -0
- package/dist/field.js +10 -0
- package/dist/index.d.ts +2 -2
- package/dist/internal.d.ts +3 -2
- package/dist/internal.js +4 -3
- package/dist/layout.d.ts +8 -0
- package/dist/layout.js +10 -0
- package/dist/model.d.ts +62 -6
- package/dist/resource-model.d.ts +1 -0
- package/dist/resource-model.js +18 -1
- package/dist/resource.d.ts +6 -0
- package/dist/resource.js +8 -0
- package/dist/view-actions.d.ts +4 -0
- package/dist/view-actions.js +14 -1
- package/dist/view-displays.d.ts +20 -1
- package/dist/view-displays.js +39 -2
- package/dist/view-field.d.ts +4 -1
- package/dist/view-field.js +4 -1
- package/dist/view.d.ts +1 -1
- package/dist/view.js +4 -2
- package/package.json +1 -1
package/dist/address-errors.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { addressPlaceholders } from "./address-template.js";
|
|
1
2
|
import { parseFilter } from "./filter/parse.js";
|
|
2
3
|
import { checkFilter } from "./filter/check.js";
|
|
3
4
|
import { checkOrderBy, parseOrderBy } from "./filter/order-by.js";
|
|
4
|
-
import { addressPlaceholders } from "./address-template.js";
|
|
5
5
|
//#region src/address-errors.ts
|
|
6
6
|
const nameErrors = [
|
|
7
7
|
"unknown-field",
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/ai-prompt.d.ts
|
|
2
|
+
/** The fields an AI prompt reads from the record, from its `{field}` placeholders. */
|
|
3
|
+
export declare const promptFields: (prompt: string) => string[];
|
|
4
|
+
/** `prompt` with every `{field}` replaced by the record's value as text; a missing value becomes empty, json as JSON. */
|
|
5
|
+
export declare const renderPrompt: (prompt: string, record: Record<string, unknown>) => string;
|
|
6
|
+
/** Why a prompt cannot be an AI field's or action's: it reads no field of the record. */
|
|
7
|
+
export declare const promptError: (prompt: string) => "its prompt must name at least one {field} of the record" | undefined;
|
|
8
|
+
//#endregion
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { hrefPlaceholders } from "./address-template.js";
|
|
2
|
+
//#region src/ai-prompt.ts
|
|
3
|
+
const placeholder = /\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
4
|
+
/** The fields an AI prompt reads from the record, from its `{field}` placeholders. */
|
|
5
|
+
const promptFields = (prompt) => hrefPlaceholders(prompt);
|
|
6
|
+
const asText = (value) => {
|
|
7
|
+
if (value === null || value === void 0) return "";
|
|
8
|
+
if (typeof value === "string") return value;
|
|
9
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
|
|
10
|
+
return JSON.stringify(value);
|
|
11
|
+
};
|
|
12
|
+
/** `prompt` with every `{field}` replaced by the record's value as text; a missing value becomes empty, json as JSON. */
|
|
13
|
+
const renderPrompt = (prompt, record) => prompt.replace(placeholder, (_, name) => asText(record[name]));
|
|
14
|
+
/** Why a prompt cannot be an AI field's or action's: it reads no field of the record. */
|
|
15
|
+
const promptError = (prompt) => promptFields(prompt).length === 0 ? "its prompt must name at least one {field} of the record" : void 0;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { promptError, promptFields, renderPrompt };
|
package/dist/app-address.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export declare const addressWords: {
|
|
|
15
15
|
readonly filterLayout: "layout";
|
|
16
16
|
/** Where a list's map display looks: `latitude,longitude,zoom`. */
|
|
17
17
|
readonly map: "map";
|
|
18
|
+
/** The month or week a list's calendar display shows: `month,YYYY-MM-DD` or `week,YYYY-MM-DD`. */
|
|
19
|
+
readonly calendar: "calendar";
|
|
18
20
|
/** The saved search a list was opened from, by its key. */
|
|
19
21
|
readonly savedSearch: "saved_search";
|
|
20
22
|
/** The second path segment that opens a resource's create form instead of a record. */
|
package/dist/app-address.js
CHANGED
|
@@ -15,6 +15,8 @@ const addressWords = {
|
|
|
15
15
|
filterLayout: "layout",
|
|
16
16
|
/** Where a list's map display looks: `latitude,longitude,zoom`. */
|
|
17
17
|
map: "map",
|
|
18
|
+
/** The month or week a list's calendar display shows: `month,YYYY-MM-DD` or `week,YYYY-MM-DD`. */
|
|
19
|
+
calendar: "calendar",
|
|
18
20
|
/** The saved search a list was opened from, by its key. */
|
|
19
21
|
savedSearch: "saved_search",
|
|
20
22
|
/** The second path segment that opens a resource's create form instead of a record. */
|
|
@@ -41,6 +43,23 @@ const readMap = (text) => {
|
|
|
41
43
|
zoom
|
|
42
44
|
} : void 0;
|
|
43
45
|
};
|
|
46
|
+
const calendarText = (view) => `${view.scale},${view.day}`;
|
|
47
|
+
const isDay = (text) => {
|
|
48
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text);
|
|
49
|
+
if (!match) return false;
|
|
50
|
+
const [year, month, day] = match.slice(1).map(Number);
|
|
51
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
52
|
+
return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
|
|
53
|
+
};
|
|
54
|
+
const readCalendar = (text) => {
|
|
55
|
+
const parts = text?.split(",");
|
|
56
|
+
if (parts?.length !== 2) return void 0;
|
|
57
|
+
const [scale, day] = parts;
|
|
58
|
+
return (scale === "month" || scale === "week") && isDay(day) ? {
|
|
59
|
+
scale,
|
|
60
|
+
day
|
|
61
|
+
} : void 0;
|
|
62
|
+
};
|
|
44
63
|
const listQuery = (address) => {
|
|
45
64
|
const query = new URLSearchParams();
|
|
46
65
|
for (const name of listWords) {
|
|
@@ -48,6 +67,7 @@ const listQuery = (address) => {
|
|
|
48
67
|
if (value !== void 0 && value !== "") query.set(addressWords[name], value);
|
|
49
68
|
}
|
|
50
69
|
if (address.map) query.set(addressWords.map, mapText(address.map));
|
|
70
|
+
if (address.calendar) query.set(addressWords.calendar, calendarText(address.calendar));
|
|
51
71
|
const text = query.toString();
|
|
52
72
|
return text ? `?${text}` : "";
|
|
53
73
|
};
|
|
@@ -107,6 +127,7 @@ const readAppPath = (pathAndQuery, pages) => {
|
|
|
107
127
|
const orderBy = params.get(addressWords.orderBy) || void 0;
|
|
108
128
|
const layout = filterLayout(params.get(addressWords.filterLayout));
|
|
109
129
|
const map = readMap(params.get(addressWords.map));
|
|
130
|
+
const calendar = readCalendar(params.get(addressWords.calendar));
|
|
110
131
|
const savedSearch = params.get(addressWords.savedSearch) || void 0;
|
|
111
132
|
return {
|
|
112
133
|
kind: "list",
|
|
@@ -116,6 +137,7 @@ const readAppPath = (pathAndQuery, pages) => {
|
|
|
116
137
|
...orderBy && { orderBy },
|
|
117
138
|
...layout && { filterLayout: layout },
|
|
118
139
|
...map && { map },
|
|
140
|
+
...calendar && { calendar },
|
|
119
141
|
...savedSearch && { savedSearch }
|
|
120
142
|
};
|
|
121
143
|
};
|
package/dist/field.d.ts
CHANGED
|
@@ -57,6 +57,11 @@ export type FieldMeta = {
|
|
|
57
57
|
};
|
|
58
58
|
/** A file field's settings (`f.file()`). */
|
|
59
59
|
file?: FileOptions;
|
|
60
|
+
/** How a job fills it (`.ai()`). */
|
|
61
|
+
ai?: {
|
|
62
|
+
/** The prompt the job fills it from. */
|
|
63
|
+
prompt: string;
|
|
64
|
+
};
|
|
60
65
|
};
|
|
61
66
|
/** A field type: its name, the constraints it takes and how it builds the schema that checks a value. */
|
|
62
67
|
export type FieldTypeDef = {
|
|
@@ -115,6 +120,13 @@ export declare class Field<V> {
|
|
|
115
120
|
min(value: number): Field<V>;
|
|
116
121
|
/** The greatest value of an integer, or the longest length of a text. Only `f.integer()` and `f.text()` take it. */
|
|
117
122
|
max(value: number): Field<V>;
|
|
123
|
+
/**
|
|
124
|
+
* A job fills the field from `prompt`, `{field}` replaced by the record's values, after a create, and after an update
|
|
125
|
+
* that changes a field the prompt names, as the user who wrote; see [AI fields and actions](website/src/content/docs/reference/ai.mdx).
|
|
126
|
+
*/
|
|
127
|
+
ai(o: {
|
|
128
|
+
prompt: string;
|
|
129
|
+
}): Field<V>;
|
|
118
130
|
/** A copy of the field with `patch` applied, which every option returns. */
|
|
119
131
|
protected with(patch: Partial<FieldMeta>): Field<V>;
|
|
120
132
|
private constrain;
|
package/dist/field.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { promptError } from "./ai-prompt.js";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
//#region src/field.ts
|
|
3
4
|
const fieldRegistry = z.registry();
|
|
@@ -85,6 +86,15 @@ var Field = class Field {
|
|
|
85
86
|
max(value) {
|
|
86
87
|
return this.constrain("max", { max: value });
|
|
87
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* A job fills the field from `prompt`, `{field}` replaced by the record's values, after a create, and after an update
|
|
91
|
+
* that changes a field the prompt names, as the user who wrote; see [AI fields and actions](website/src/content/docs/reference/ai.mdx).
|
|
92
|
+
*/
|
|
93
|
+
ai(o) {
|
|
94
|
+
const error = promptError(o.prompt);
|
|
95
|
+
if (error) throw new Error(`.ai(): ${error}`);
|
|
96
|
+
return this.with({ ai: { prompt: o.prompt } });
|
|
97
|
+
}
|
|
88
98
|
/** A copy of the field with `patch` applied, which every option returns. */
|
|
89
99
|
with(patch) {
|
|
90
100
|
return new Field(this.def, {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FileOptions, FileProcessor, FileProcessorInput, ImageFormat, ImageVariant, ImageVariantOptions, imageFormats } from "./files/file-options.js";
|
|
2
|
-
import { ActionInputKind, ActionInputModel, ActionModel, ActionRun, AppAddress, BadgeShape, BottomBarItemModel, BottomBarModel, CardFavoriteModel, CardPhotosModel, ChartGranularity, ChartModel, ChartRange, CheckedFilter, CompareOp, CreateModel, CustomSectionModel, DatePreset, DurationLiteral, DurationUnit, EventModel, EventOperation, ExportModel, FieldFormat, FieldModel, FieldPath, FieldType, FieldViewModel, FieldWidgetModel, FileFieldModel, FilterError, FilterErrorCode, FilterExpr, FilterLayout, FilterOf, FilterValue, FilterWidgetModel, FormModel, GalleryModel, GroupChartModel, HelpWhen, HistorySectionModel, JsonValue, KeyValue, LayoutItemModel, ListDisplayModel, ListModel, ListQuery, Literal, MapSectionModel, MapView, NavModel, NavRecentModel, NowValue, OrderByItem, PeriodChartModel, RecordMode, RelatedColumnModel, RelatedModel, ResourceModel, SaveFeedback, SaveSearchModel, SearchMatch, SearchResultModel, SearchesOfModel, SectionPresentation, ShowWhen, SignInPromptModel, SignInViewModel, SortSpec, Span, StatusTone, UserMenuItemModel, UserMenuModel, ViewModel } from "./model.js";
|
|
2
|
+
import { ActionInputKind, ActionInputModel, ActionModel, ActionRun, AppAddress, BadgeShape, BottomBarItemModel, BottomBarModel, CalendarView, CardFavoriteModel, CardPhotosModel, ChartGranularity, ChartModel, ChartRange, CheckedFilter, CommentsSectionModel, CompareOp, CreateModel, CustomSectionModel, DatePreset, DurationLiteral, DurationUnit, EventModel, EventOperation, ExportModel, FieldFormat, FieldModel, FieldPath, FieldType, FieldViewModel, FieldWidgetModel, FileFieldModel, FilterError, FilterErrorCode, FilterExpr, FilterLayout, FilterOf, FilterValue, FilterWidgetModel, FormModel, GalleryModel, GroupChartModel, HelpWhen, HistorySectionModel, JsonValue, KeyValue, LayoutItemModel, ListDisplayModel, ListModel, ListQuery, Literal, MapSectionModel, MapView, NavModel, NavRecentModel, NowValue, OrderByItem, PeriodChartModel, RecordMode, RelatedColumnModel, RelatedModel, ResourceModel, SaveFeedback, SaveSearchModel, SearchMatch, SearchResultModel, SearchesOfModel, SectionPresentation, ShowWhen, SignInPromptModel, SignInViewModel, SortSpec, Span, StatusTone, UserMenuItemModel, UserMenuModel, ViewModel } from "./model.js";
|
|
3
3
|
import { AccessAction, AccessContext, AccessFn, AccessResult, AccessUser, FieldAccess, FieldRule } from "./access/types.js";
|
|
4
4
|
import { AnyField, Constraint, Field, FieldMeta, FieldTypeDef } from "./field.js";
|
|
5
5
|
import { RelationField } from "./relation-field.js";
|
|
@@ -41,4 +41,4 @@ import { AccessSource, ResolveOptions, ResolvedAccess, RowFilterOperation, acces
|
|
|
41
41
|
import { restrictModel } from "./access/restrict.js";
|
|
42
42
|
import "./access/index.js";
|
|
43
43
|
import { AssistantAction, AssistantActionRequest, AssistantCardPart, AssistantDiff, AssistantEvent, AssistantMessage, AssistantMessageRequest, AssistantPart, AssistantState, AssistantStep, AssistantStepState, AssistantTablePart, AssistantTextPart, AssistantTone, AssistantWidgetPart } from "./assistant/protocol.js";
|
|
44
|
-
export { type Access, type AccessAction, type AccessContext, type AccessFn, type AccessResult, type AccessSource, type AccessUser, type ActionInputKind, type ActionInputModel, type ActionModel, type ActionRun, type AnyField, type AppAddress, type AssistantAction, type AssistantActionRequest, type AssistantCardPart, type AssistantDiff, type AssistantEvent, type AssistantMessage, type AssistantMessageRequest, type AssistantPart, type AssistantState, type AssistantStep, type AssistantStepState, type AssistantTablePart, type AssistantTextPart, type AssistantTone, type AssistantWidgetPart, type BadgeShape, type BottomBarItemModel, type BottomBarModel, type BottomBarNames, type BottomBarSource, type Capability, type CapabilityAction, type CapabilityLimit, type CapabilityScope, type CardFavoriteModel, type CardPhotosModel, type ChartGranularity, type ChartInput, type ChartModel, type ChartRange, type CheckResult, type CheckedFilter, type CompareOp, type ComponentRef, type Constraint, type CreateModel, type CreatedOptions, type CustomSectionModel, type DatePreset, type DeletedOptions, type DurationLiteral, type DurationUnit, type EvaluateOptions, type EventActor, type EventContext, type EventHelpers, type EventMatch, type EventModel, type EventOperation, type EventSpec, type ExportModel, type Field, type FieldAccess, type FieldFormat, type FieldMeta, type FieldModel, type FieldPath, type FieldRefValue, type FieldRefs, type FieldRule, type FieldType, type FieldTypeDef, type FieldView, type FieldViewModel, type FieldViews, type FieldWidgetModel, type FieldsInput, type FileField, type FileFieldModel, type FileOptions, type FileProcessor, type FileProcessorInput, type FilterError, type FilterErrorCode, type FilterExpr, type FilterLayout, type FilterLimits, type FilterOf, type FilterParse, type FilterValue, type FilterWidgetModel, type FormAddress, type FormInput, type FormModel, type GalleryModel, type Grant, type GroupChartInput, type GroupChartModel, type HeldCapability, type HelpWhen, type HistorySectionModel, type ImageFormat, type ImageVariant, type ImageVariantOptions, type InferRecord, type Issue, type JsonValue, type KeyOf, type KeyRefs, type KeyType, type KeyValue, type LayoutItemModel, type ListColumn, type ListDisplayModel, type ListModel, type ListQuery, type Literal, type MapSectionModel, type MapView, type NavModel, type NavRecentModel, type NowValue, type OrderByCheck, type OrderByItem, type OrderByParse, type PeriodChartInput, type PeriodChartModel, type RecordKey, type RecordMode, type RecordOf, type Ref, type Refs, type RelatedColumnModel, type RelatedModel, type RelationField, type ResolveOptions, type ResolvedAccess, type ResourceBuilder, type ResourceModel, type RoleEntry, type RoleLimit, type Roles, type RolesOptions, type RowFilterOperation, type Rule, type SaveFeedback, type SaveSearchModel, type SearchMatch, type SearchRef, type SearchRefs, type SearchResultModel, type SearchesOfModel, type SectionPresentation, type ShowWhen, type SignInAddress, type SignInPromptModel, type SignInStep, type SignInViewModel, type SortSpec, type Span, type StatusTone, type UndeletedOptions, type UpdatedOptions, type UserMenuItemModel, type UserMenuModel, type UserMenuSource, type Validator, type ViewBuilder, type ViewModel, type ViewProps, accessWarnings, addressWords, anonymousRole, appPath, bottomBar, bottomBarErrors, checkFilter, checkOrderBy, checkRecord, createWhere, defineRoles, enumLabel, evaluateFilter, f, fieldRef, fieldsUsedBy, fitsKeyType, formPath, formRoute, imageFormats, keyTypes, layout as l, maxBottomBarItems, parseFilter, parseOrderBy, pickView, printFilter, printOrderBy, readAppPath, readFormAddress, readSignInAddress, resolveAccess, resource, restrictModel, rule, signInParams, signInPath, signInRoute, signInSteps, userMenu, view, where };
|
|
44
|
+
export { type Access, type AccessAction, type AccessContext, type AccessFn, type AccessResult, type AccessSource, type AccessUser, type ActionInputKind, type ActionInputModel, type ActionModel, type ActionRun, type AnyField, type AppAddress, type AssistantAction, type AssistantActionRequest, type AssistantCardPart, type AssistantDiff, type AssistantEvent, type AssistantMessage, type AssistantMessageRequest, type AssistantPart, type AssistantState, type AssistantStep, type AssistantStepState, type AssistantTablePart, type AssistantTextPart, type AssistantTone, type AssistantWidgetPart, type BadgeShape, type BottomBarItemModel, type BottomBarModel, type BottomBarNames, type BottomBarSource, type CalendarView, type Capability, type CapabilityAction, type CapabilityLimit, type CapabilityScope, type CardFavoriteModel, type CardPhotosModel, type ChartGranularity, type ChartInput, type ChartModel, type ChartRange, type CheckResult, type CheckedFilter, type CommentsSectionModel, type CompareOp, type ComponentRef, type Constraint, type CreateModel, type CreatedOptions, type CustomSectionModel, type DatePreset, type DeletedOptions, type DurationLiteral, type DurationUnit, type EvaluateOptions, type EventActor, type EventContext, type EventHelpers, type EventMatch, type EventModel, type EventOperation, type EventSpec, type ExportModel, type Field, type FieldAccess, type FieldFormat, type FieldMeta, type FieldModel, type FieldPath, type FieldRefValue, type FieldRefs, type FieldRule, type FieldType, type FieldTypeDef, type FieldView, type FieldViewModel, type FieldViews, type FieldWidgetModel, type FieldsInput, type FileField, type FileFieldModel, type FileOptions, type FileProcessor, type FileProcessorInput, type FilterError, type FilterErrorCode, type FilterExpr, type FilterLayout, type FilterLimits, type FilterOf, type FilterParse, type FilterValue, type FilterWidgetModel, type FormAddress, type FormInput, type FormModel, type GalleryModel, type Grant, type GroupChartInput, type GroupChartModel, type HeldCapability, type HelpWhen, type HistorySectionModel, type ImageFormat, type ImageVariant, type ImageVariantOptions, type InferRecord, type Issue, type JsonValue, type KeyOf, type KeyRefs, type KeyType, type KeyValue, type LayoutItemModel, type ListColumn, type ListDisplayModel, type ListModel, type ListQuery, type Literal, type MapSectionModel, type MapView, type NavModel, type NavRecentModel, type NowValue, type OrderByCheck, type OrderByItem, type OrderByParse, type PeriodChartInput, type PeriodChartModel, type RecordKey, type RecordMode, type RecordOf, type Ref, type Refs, type RelatedColumnModel, type RelatedModel, type RelationField, type ResolveOptions, type ResolvedAccess, type ResourceBuilder, type ResourceModel, type RoleEntry, type RoleLimit, type Roles, type RolesOptions, type RowFilterOperation, type Rule, type SaveFeedback, type SaveSearchModel, type SearchMatch, type SearchRef, type SearchRefs, type SearchResultModel, type SearchesOfModel, type SectionPresentation, type ShowWhen, type SignInAddress, type SignInPromptModel, type SignInStep, type SignInViewModel, type SortSpec, type Span, type StatusTone, type UndeletedOptions, type UpdatedOptions, type UserMenuItemModel, type UserMenuModel, type UserMenuSource, type Validator, type ViewBuilder, type ViewModel, type ViewProps, accessWarnings, addressWords, anonymousRole, appPath, bottomBar, bottomBarErrors, checkFilter, checkOrderBy, checkRecord, createWhere, defineRoles, enumLabel, evaluateFilter, f, fieldRef, fieldsUsedBy, fitsKeyType, formPath, formRoute, imageFormats, keyTypes, layout as l, maxBottomBarItems, parseFilter, parseOrderBy, pickView, printFilter, printOrderBy, readAppPath, readFormAddress, readSignInAddress, resolveAccess, resource, restrictModel, rule, signInParams, signInPath, signInRoute, signInSteps, userMenu, view, where };
|
package/dist/internal.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { checkedAccept, checkedProvider, parseSize } from "./files/file-options.js";
|
|
2
2
|
import { actionInputErrors, shownInputs } from "./action-inputs.js";
|
|
3
3
|
import { chartFields, isGroupChart } from "./view-chart.js";
|
|
4
|
-
import { boardErrors, cardPhotoLimit, displayFields, tableDisplay } from "./view-displays.js";
|
|
4
|
+
import { boardErrors, calendarErrors, cardPhotoLimit, displayFields, tableDisplay } from "./view-displays.js";
|
|
5
5
|
import { fieldRefsIn, isFieldRef, markFieldRefs, unmarkFieldRefs } from "./field-ref.js";
|
|
6
6
|
import { bottomBarFor } from "./bottom-bar.js";
|
|
7
7
|
import { decodeKey, encodeKey } from "./keys.js";
|
|
@@ -13,7 +13,8 @@ import { defaultRecentLimit, navRecentErrors, statusTones } from "./nav-recent.j
|
|
|
13
13
|
import { PluginHost, PluginRequirement, inRange, pluginProblem } from "./plugin-version.js";
|
|
14
14
|
import { redactSecrets } from "./redact-secrets.js";
|
|
15
15
|
import { addressPlaceholders, appHrefError, fillAddress, hrefField, hrefPlaceholders, leavesApp, linkActionError } from "./address-template.js";
|
|
16
|
+
import { promptError, promptFields, renderPrompt } from "./ai-prompt.js";
|
|
16
17
|
import { AddressCheck, addressErrors } from "./address-errors.js";
|
|
17
18
|
import { applyAssistantEvent, emptyAssistantState } from "./assistant/apply-event.js";
|
|
18
19
|
import { readEventStream } from "./assistant/event-stream.js";
|
|
19
|
-
export { type AddressCheck, type PluginHost, type PluginRequirement, type PropShapes, type RefShape, type SearchTerm, actionInputErrors, addressErrors, addressPlaceholders, appHrefError, applyAssistantEvent, boardErrors, bottomBarFor, cardPhotoLimit, chartFields, checkEventFilter, checkedAccept, checkedProvider, decodeKey, defaultRecentLimit, displayFields, emptyAssistantState, encodeKey, fieldRefsIn, fillAddress, hrefField, hrefPlaceholders, inRange, isFieldRef, isGroupChart, leavesApp, linkActionError, markFieldRefs, matchedSearchField, matchesSearch, navRecentErrors, parseSize, pluginProblem, pruneByShape, readEventStream, redactSecrets, searchTerms, shapes, shownInputs, statusTones, tableDisplay, termMatches, unmarkFieldRefs, webhookResources };
|
|
20
|
+
export { type AddressCheck, type PluginHost, type PluginRequirement, type PropShapes, type RefShape, type SearchTerm, actionInputErrors, addressErrors, addressPlaceholders, appHrefError, applyAssistantEvent, boardErrors, bottomBarFor, calendarErrors, cardPhotoLimit, chartFields, checkEventFilter, checkedAccept, checkedProvider, decodeKey, defaultRecentLimit, displayFields, emptyAssistantState, encodeKey, fieldRefsIn, fillAddress, hrefField, hrefPlaceholders, inRange, isFieldRef, isGroupChart, leavesApp, linkActionError, markFieldRefs, matchedSearchField, matchesSearch, navRecentErrors, parseSize, pluginProblem, promptError, promptFields, pruneByShape, readEventStream, redactSecrets, renderPrompt, searchTerms, shapes, shownInputs, statusTones, tableDisplay, termMatches, unmarkFieldRefs, webhookResources };
|
package/dist/internal.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import { addressPlaceholders, appHrefError, fillAddress, hrefField, hrefPlaceholders, leavesApp, linkActionError } from "./address-template.js";
|
|
2
|
+
import { promptError, promptFields, renderPrompt } from "./ai-prompt.js";
|
|
1
3
|
import { checkedAccept, checkedProvider, parseSize } from "./files/file-options.js";
|
|
2
4
|
import { matchedSearchField, matchesSearch, searchTerms, termMatches } from "./search-match.js";
|
|
3
5
|
import { checkEventFilter } from "./events/event-models.js";
|
|
4
6
|
import { defaultRecentLimit, navRecentErrors, statusTones } from "./nav-recent.js";
|
|
5
7
|
import { actionInputErrors, shownInputs } from "./action-inputs.js";
|
|
6
|
-
import { addressPlaceholders, appHrefError, fillAddress, hrefField, hrefPlaceholders, leavesApp, linkActionError } from "./address-template.js";
|
|
7
8
|
import { chartFields, isGroupChart } from "./view-chart.js";
|
|
8
9
|
import { fieldRefsIn, isFieldRef, markFieldRefs, unmarkFieldRefs } from "./field-ref.js";
|
|
9
|
-
import { boardErrors, cardPhotoLimit, displayFields, tableDisplay } from "./view-displays.js";
|
|
10
|
+
import { boardErrors, calendarErrors, cardPhotoLimit, displayFields, tableDisplay } from "./view-displays.js";
|
|
10
11
|
import { bottomBarFor } from "./bottom-bar.js";
|
|
11
12
|
import { decodeKey, encodeKey } from "./keys.js";
|
|
12
13
|
import { webhookResources } from "./webhooks/webhook-resources.js";
|
|
@@ -16,4 +17,4 @@ import { redactSecrets } from "./redact-secrets.js";
|
|
|
16
17
|
import { addressErrors } from "./address-errors.js";
|
|
17
18
|
import { applyAssistantEvent, emptyAssistantState } from "./assistant/apply-event.js";
|
|
18
19
|
import { readEventStream } from "./assistant/event-stream.js";
|
|
19
|
-
export { actionInputErrors, addressErrors, addressPlaceholders, appHrefError, applyAssistantEvent, boardErrors, bottomBarFor, cardPhotoLimit, chartFields, checkEventFilter, checkedAccept, checkedProvider, decodeKey, defaultRecentLimit, displayFields, emptyAssistantState, encodeKey, fieldRefsIn, fillAddress, hrefField, hrefPlaceholders, inRange, isFieldRef, isGroupChart, leavesApp, linkActionError, markFieldRefs, matchedSearchField, matchesSearch, navRecentErrors, parseSize, pluginProblem, pruneByShape, readEventStream, redactSecrets, searchTerms, shapes, shownInputs, statusTones, tableDisplay, termMatches, unmarkFieldRefs, webhookResources };
|
|
20
|
+
export { actionInputErrors, addressErrors, addressPlaceholders, appHrefError, applyAssistantEvent, boardErrors, bottomBarFor, calendarErrors, cardPhotoLimit, chartFields, checkEventFilter, checkedAccept, checkedProvider, decodeKey, defaultRecentLimit, displayFields, emptyAssistantState, encodeKey, fieldRefsIn, fillAddress, hrefField, hrefPlaceholders, inRange, isFieldRef, isGroupChart, leavesApp, linkActionError, markFieldRefs, matchedSearchField, matchesSearch, navRecentErrors, parseSize, pluginProblem, promptError, promptFields, pruneByShape, readEventStream, redactSecrets, renderPrompt, searchTerms, shapes, shownInputs, statusTones, tableDisplay, termMatches, unmarkFieldRefs, webhookResources };
|
package/dist/layout.d.ts
CHANGED
|
@@ -73,6 +73,14 @@ export declare const layout: {
|
|
|
73
73
|
limit?: number;
|
|
74
74
|
when?: ShowWhen;
|
|
75
75
|
}) => LayoutItemModel;
|
|
76
|
+
/**
|
|
77
|
+
* The record's comments, oldest first, with who wrote each and when, and a box to write one; `@` mentions someone who
|
|
78
|
+
* may read the record. Needs `.comments()` on the resource; a view without a layout shows it last by itself. See
|
|
79
|
+
* [Record comments](website/src/content/docs/reference/comments.mdx).
|
|
80
|
+
*/
|
|
81
|
+
comments: (title?: string, o?: {
|
|
82
|
+
when?: ShowWhen;
|
|
83
|
+
}) => LayoutItemModel;
|
|
76
84
|
/**
|
|
77
85
|
* A component of the project, registered in `protobase.ui.tsx` under its name, drawn where it is listed: in the main
|
|
78
86
|
* column, or in the sidebar with `sidebar: true`. The record page stays around it, with its title, breadcrumbs,
|
package/dist/layout.js
CHANGED
|
@@ -94,6 +94,16 @@ const layout = {
|
|
|
94
94
|
};
|
|
95
95
|
},
|
|
96
96
|
/**
|
|
97
|
+
* The record's comments, oldest first, with who wrote each and when, and a box to write one; `@` mentions someone who
|
|
98
|
+
* may read the record. Needs `.comments()` on the resource; a view without a layout shows it last by itself. See
|
|
99
|
+
* [Record comments](website/src/content/docs/reference/comments.mdx).
|
|
100
|
+
*/
|
|
101
|
+
comments: (title = "Comments", o = {}) => ({
|
|
102
|
+
kind: "comments",
|
|
103
|
+
title,
|
|
104
|
+
...o.when !== void 0 && { when: o.when }
|
|
105
|
+
}),
|
|
106
|
+
/**
|
|
97
107
|
* A component of the project, registered in `protobase.ui.tsx` under its name, drawn where it is listed: in the main
|
|
98
108
|
* column, or in the sidebar with `sidebar: true`. The record page stays around it, with its title, breadcrumbs,
|
|
99
109
|
* actions and save; the component reads the record with `useLayoutRecord()`.
|
package/dist/model.d.ts
CHANGED
|
@@ -44,6 +44,11 @@ export type FieldModel = {
|
|
|
44
44
|
};
|
|
45
45
|
/** A file field's allowed types (empty: any), largest upload in bytes, provider, and what each upload writes. */
|
|
46
46
|
file?: FileFieldModel;
|
|
47
|
+
/** A job fills it from the prompt after a create or an update of a field the prompt names (`.ai()`). */
|
|
48
|
+
ai?: {
|
|
49
|
+
/** The prompt, with `{field}` placeholders. */
|
|
50
|
+
prompt: string;
|
|
51
|
+
};
|
|
47
52
|
};
|
|
48
53
|
/** A file field's settings, as `/meta` gives them; see [Files](website/src/content/docs/reference/files.mdx). */
|
|
49
54
|
export type FileFieldModel = {
|
|
@@ -105,6 +110,8 @@ export type ResourceModel = {
|
|
|
105
110
|
events?: EventModel[];
|
|
106
111
|
/** Whether every change of its records is kept, with who made it (`.history()`). */
|
|
107
112
|
history?: true;
|
|
113
|
+
/** Whether people who may read its records comment on them (`.comments()`). */
|
|
114
|
+
comments?: true;
|
|
108
115
|
};
|
|
109
116
|
/** What happened to a record: `delete` and `undelete` include setting and clearing a soft delete field. */
|
|
110
117
|
export type EventOperation = 'create' | 'update' | 'delete' | 'undelete';
|
|
@@ -164,11 +171,18 @@ export type MapView = {
|
|
|
164
171
|
/** MapLibre's zoom, from 0 to 22. */
|
|
165
172
|
zoom: number;
|
|
166
173
|
};
|
|
174
|
+
/** Which month or week a list's calendar display shows: the grid's `scale`, and a UTC day (`YYYY-MM-DD`) in it. */
|
|
175
|
+
export type CalendarView = {
|
|
176
|
+
/** A month of whole weeks, or one week. */
|
|
177
|
+
scale: 'month' | 'week';
|
|
178
|
+
/** A day in the month or week, as `YYYY-MM-DD`. */
|
|
179
|
+
day: string;
|
|
180
|
+
};
|
|
167
181
|
/**
|
|
168
182
|
* A place in the app: a resource's list, one of its records, its create form, or a composed page. A record's `key` is
|
|
169
183
|
* its one value or its parts, as the record API takes them; `readAppPath` gives it back as the API's text form. A create
|
|
170
184
|
* form's `values` fill in fields of the new record by name, as text (`{ listing_id: '42' }`). A list's `map` is where its
|
|
171
|
-
* map display looks. See [Addresses](website/src/content/docs/reference/layouts.md#addresses).
|
|
185
|
+
* map display looks, and its `calendar` the month or week its calendar display shows. See [Addresses](website/src/content/docs/reference/layouts.md#addresses).
|
|
172
186
|
*/
|
|
173
187
|
export type AppAddress = {
|
|
174
188
|
/** A resource's list. */
|
|
@@ -185,6 +199,8 @@ export type AppAddress = {
|
|
|
185
199
|
filterLayout?: FilterLayout;
|
|
186
200
|
/** Where its map display looks. */
|
|
187
201
|
map?: MapView;
|
|
202
|
+
/** The month or week its calendar display shows. */
|
|
203
|
+
calendar?: CalendarView;
|
|
188
204
|
/**
|
|
189
205
|
* The saved search the list was opened from, by its key in the text form the record API takes: while the list's
|
|
190
206
|
* filter differs from the search's, the list offers to update the search.
|
|
@@ -216,10 +232,11 @@ export type AppAddress = {
|
|
|
216
232
|
page: string;
|
|
217
233
|
};
|
|
218
234
|
/**
|
|
219
|
-
* How a value is shown: `badge` as a coloured badge, `code` in monospace, `percent` with a % sign
|
|
235
|
+
* How a value is shown: `badge` as a coloured badge, `code` in monospace, `percent` with a % sign; a text field's
|
|
236
|
+
* `email`, `phone` and `url` as a link that writes, calls or opens it, edited with the keyboard for it. `relative`,
|
|
220
237
|
* `absolute` and `compact` are carried in `/meta` for apps that render their own lists.
|
|
221
238
|
*/
|
|
222
|
-
export type FieldFormat = 'relative' | 'absolute' | 'compact' | 'percent' | 'badge' | 'code';
|
|
239
|
+
export type FieldFormat = 'relative' | 'absolute' | 'compact' | 'percent' | 'badge' | 'code' | 'email' | 'phone' | 'url';
|
|
223
240
|
/** A view's hints for one field (`.fields((r) => ...)`); see [Names and columns](website/src/content/docs/reference/ui-config.mdx#names-and-columns). */
|
|
224
241
|
export type FieldViewModel = {
|
|
225
242
|
/** What the field is called, instead of its name in words. */
|
|
@@ -365,6 +382,21 @@ export type ListDisplayModel = {
|
|
|
365
382
|
badge?: string;
|
|
366
383
|
/** A few small fields, such as dates, each shown when it has a value. */
|
|
367
384
|
meta?: string[];
|
|
385
|
+
} | {
|
|
386
|
+
/** The records on the days they cover, in a month grid or a week grid, moved to another day by dragging. */
|
|
387
|
+
kind: 'calendar';
|
|
388
|
+
/** What the address names (`?display=calendar`). */
|
|
389
|
+
name: string;
|
|
390
|
+
/** The display's name in the switch. */
|
|
391
|
+
label?: string;
|
|
392
|
+
/** The display's icon in the switch. */
|
|
393
|
+
icon?: string;
|
|
394
|
+
/** The filterable date or timestamp field a record starts on. */
|
|
395
|
+
start: string;
|
|
396
|
+
/** The filterable field of `start`'s type a record ends on; a record is on its start day alone when omitted. */
|
|
397
|
+
end?: string;
|
|
398
|
+
/** The event's text; the view's title when omitted. */
|
|
399
|
+
title?: string;
|
|
368
400
|
} | {
|
|
369
401
|
/** The rows drawn by a project component. */
|
|
370
402
|
kind: 'custom';
|
|
@@ -597,6 +629,11 @@ export type RelatedModel = {
|
|
|
597
629
|
sort?: string;
|
|
598
630
|
/** Fields of `resource`, through relations too (`unitId.propertyId`). */
|
|
599
631
|
columns?: string[];
|
|
632
|
+
/**
|
|
633
|
+
* The relation field of `resource` whose record a row opens, for rows that are links between this record and another
|
|
634
|
+
* (a join resource); by default the one relation in `resource`'s primary key besides `field`, when there is exactly one.
|
|
635
|
+
*/
|
|
636
|
+
open?: string;
|
|
600
637
|
/** How many records it shows, the first in `sort` order; with more, it says so ("The first 10 of 37.") beside "View all", which opens the rest in the list. */
|
|
601
638
|
limit?: number;
|
|
602
639
|
/** The display of `resource`'s list it opens in, such as `cards`; the list's default when omitted. */
|
|
@@ -659,6 +696,18 @@ export type HistorySectionModel = {
|
|
|
659
696
|
/** Whether it shows on the form, the read view or both. */
|
|
660
697
|
when?: ShowWhen;
|
|
661
698
|
};
|
|
699
|
+
/**
|
|
700
|
+
* The record's comments, oldest first, each with its author and time, and a box to write a new one that mentions people
|
|
701
|
+
* with `@`. Needs `.comments()` on the resource. See [Record comments](website/src/content/docs/reference/comments.mdx).
|
|
702
|
+
*/
|
|
703
|
+
export type CommentsSectionModel = {
|
|
704
|
+
/** A comments section. */
|
|
705
|
+
kind: 'comments';
|
|
706
|
+
/** The section's title. */
|
|
707
|
+
title: string;
|
|
708
|
+
/** Whether it shows on the form, the read view or both. */
|
|
709
|
+
when?: ShowWhen;
|
|
710
|
+
};
|
|
662
711
|
/** How a section shows its fields on the read view: `facts` as a row of key facts; label/value rows when omitted. */
|
|
663
712
|
export type SectionPresentation = 'facts';
|
|
664
713
|
/** An item of the record page (`.layout((r) => ...)` with `l`); see [The record page](website/src/content/docs/reference/ui-config.mdx#the-record-page). */
|
|
@@ -687,7 +736,7 @@ export type LayoutItemModel = {
|
|
|
687
736
|
fields: string[];
|
|
688
737
|
/** Whether it shows on the form, the read view or both. */
|
|
689
738
|
when?: ShowWhen;
|
|
690
|
-
} | RelatedModel | CustomSectionModel | MapSectionModel | GalleryModel | HistorySectionModel;
|
|
739
|
+
} | RelatedModel | CustomSectionModel | MapSectionModel | GalleryModel | HistorySectionModel | CommentsSectionModel;
|
|
691
740
|
/**
|
|
692
741
|
* A record's photos as tiles in the main column under `title`, one large and the next few smaller, opening a lightbox
|
|
693
742
|
* that steps through them all: first the record's own file field `image`, then the `image` of the records of
|
|
@@ -744,8 +793,8 @@ export type RecordMode = 'edit' | 'read' | 'locked';
|
|
|
744
793
|
export type SaveFeedback = 'toast' | 'button';
|
|
745
794
|
/**
|
|
746
795
|
* What a named action does in the app: set fields of the record (`update`), delete it (`delete`), go to an address in
|
|
747
|
-
* the app (`link` with `to`)
|
|
748
|
-
* values. Without it the app runs a handler it registered by name.
|
|
796
|
+
* the app (`link` with `to`), open a link that leaves it (`link` with `href`), `{field}` replaced by the record's
|
|
797
|
+
* values, or have the model write a field (`ai`). Without it the app runs a handler it registered by name.
|
|
749
798
|
*/
|
|
750
799
|
export type ActionRun = {
|
|
751
800
|
/** Updates the record. */
|
|
@@ -768,6 +817,13 @@ export type ActionRun = {
|
|
|
768
817
|
* the `https:` or `http:` URL the record holds, without which the action does not show.
|
|
769
818
|
*/
|
|
770
819
|
href: string;
|
|
820
|
+
} | {
|
|
821
|
+
/** Has the model write one field of the record from `prompt`, `{field}` replaced by the record's values, as the user. */
|
|
822
|
+
kind: 'ai';
|
|
823
|
+
/** The prompt, with `{field}` placeholders; `/meta` leaves it out. */
|
|
824
|
+
prompt?: string;
|
|
825
|
+
/** The field it writes. */
|
|
826
|
+
writes: string;
|
|
771
827
|
};
|
|
772
828
|
/** A named action of a view (`.actions((a) => ...)`); see [Named actions](website/src/content/docs/reference/layouts.md#named-actions). */
|
|
773
829
|
export type ActionModel = {
|
package/dist/resource-model.d.ts
CHANGED
package/dist/resource-model.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { promptFields } from "./ai-prompt.js";
|
|
1
2
|
import { buildEventModels } from "./events/event-models.js";
|
|
2
3
|
import { snakeCase } from "./naming.js";
|
|
3
4
|
//#region src/resource-model.ts
|
|
@@ -40,7 +41,8 @@ const fieldModel = (name, field) => {
|
|
|
40
41
|
...meta.file.variants && Object.keys(meta.file.variants).length > 0 && { variants: structuredClone(meta.file.variants) },
|
|
41
42
|
...meta.file.original && { original: { ...meta.file.original } },
|
|
42
43
|
...meta.file.external && { external: true }
|
|
43
|
-
} }
|
|
44
|
+
} },
|
|
45
|
+
...meta.ai && { ai: { prompt: meta.ai.prompt } }
|
|
44
46
|
};
|
|
45
47
|
};
|
|
46
48
|
const assembleModel = (state) => {
|
|
@@ -59,6 +61,7 @@ const assembleModel = (state) => {
|
|
|
59
61
|
...state.searchMatch && Object.keys(state.searchMatch).length > 0 && { searchMatch: { ...state.searchMatch } },
|
|
60
62
|
...state.owner && { owner: state.owner },
|
|
61
63
|
...state.history && { history: true },
|
|
64
|
+
...state.comments && { comments: true },
|
|
62
65
|
...state.position && { position: { ...state.position } }
|
|
63
66
|
};
|
|
64
67
|
};
|
|
@@ -118,6 +121,19 @@ const positionProblems = (model) => {
|
|
|
118
121
|
...parent && !parent.filterable ? [`position "${name}" is within "${within}", which is not filterable, and each parent's records are found by it`] : []
|
|
119
122
|
];
|
|
120
123
|
};
|
|
124
|
+
const aiProblems = (model) => Object.values(model.fields).filter((field) => field.ai).flatMap((field) => [
|
|
125
|
+
...field.type === "text" || field.type === "enum" ? [] : [`is ${field.type}, not text or an enum`],
|
|
126
|
+
...field.nullable ? [] : ["must be optional(), empty until the job fills it"],
|
|
127
|
+
...field.readOnly ? ["cannot be readOnly(), as the job writes it through the API"] : [],
|
|
128
|
+
...field.sensitive ? ["cannot be sensitive"] : [],
|
|
129
|
+
...promptFields(field.ai.prompt).flatMap((name) => {
|
|
130
|
+
const read = Object.hasOwn(model.fields, name) ? model.fields[name] : void 0;
|
|
131
|
+
if (!read) return [`reads "${name}", which is not a field`];
|
|
132
|
+
if (name === field.name) return [`reads "${name}", itself`];
|
|
133
|
+
if (read.ai) return [`reads "${name}", which is an AI field too`];
|
|
134
|
+
return read.sensitive ? [`reads "${name}", which is sensitive`] : [];
|
|
135
|
+
})
|
|
136
|
+
].map((problem) => `AI field "${field.name}" ${problem}`));
|
|
121
137
|
const buildResourceModel = (state) => {
|
|
122
138
|
const model = assembleModel(state);
|
|
123
139
|
const events = state.events && buildEventModels(model, state.events);
|
|
@@ -125,6 +141,7 @@ const buildResourceModel = (state) => {
|
|
|
125
141
|
...sensitiveProblems(model),
|
|
126
142
|
...fileProblems(model),
|
|
127
143
|
...positionProblems(model),
|
|
144
|
+
...aiProblems(model),
|
|
128
145
|
...events?.problems ?? []
|
|
129
146
|
];
|
|
130
147
|
if (problems.length > 0) throw new Error(`Resource "${model.name}": ${problems.join("; ")}`);
|
package/dist/resource.d.ts
CHANGED
|
@@ -103,6 +103,12 @@ export declare class ResourceBuilder<F extends FieldsInput = {}, K = never> {
|
|
|
103
103
|
* [Record history](website/src/content/docs/reference/history.mdx).
|
|
104
104
|
*/
|
|
105
105
|
history(): ResourceBuilder<F, K>;
|
|
106
|
+
/**
|
|
107
|
+
* Lets the people who may read a record comment on it, and mention others who may read it too, who get an email. The
|
|
108
|
+
* record page shows the thread with `l.comments()`, and admins list every comment as Comments. See
|
|
109
|
+
* [Record comments](website/src/content/docs/reference/comments.mdx).
|
|
110
|
+
*/
|
|
111
|
+
comments(): ResourceBuilder<F, K>;
|
|
106
112
|
/** The code predicates (`match`) of its events, by event name. */
|
|
107
113
|
eventMatches(): Record<string, EventMatch<any>>;
|
|
108
114
|
/**
|
package/dist/resource.js
CHANGED
|
@@ -120,6 +120,14 @@ var ResourceBuilder = class ResourceBuilder {
|
|
|
120
120
|
history() {
|
|
121
121
|
return this.next({ history: true });
|
|
122
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Lets the people who may read a record comment on it, and mention others who may read it too, who get an email. The
|
|
125
|
+
* record page shows the thread with `l.comments()`, and admins list every comment as Comments. See
|
|
126
|
+
* [Record comments](website/src/content/docs/reference/comments.mdx).
|
|
127
|
+
*/
|
|
128
|
+
comments() {
|
|
129
|
+
return this.next({ comments: true });
|
|
130
|
+
}
|
|
123
131
|
/** The code predicates (`match`) of its events, by event name. */
|
|
124
132
|
eventMatches() {
|
|
125
133
|
return eventMatches(this.toModel(), this.state.events ?? []);
|
package/dist/view-actions.d.ts
CHANGED
package/dist/view-actions.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { actionInputErrors, inputs } from "./action-inputs.js";
|
|
2
1
|
import { linkActionError } from "./address-template.js";
|
|
2
|
+
import { promptError } from "./ai-prompt.js";
|
|
3
|
+
import { actionInputErrors, inputs } from "./action-inputs.js";
|
|
3
4
|
//#region src/view-actions.ts
|
|
4
5
|
const named = (name, o) => ({
|
|
5
6
|
name,
|
|
@@ -64,6 +65,18 @@ const actions = {
|
|
|
64
65
|
href: o.href
|
|
65
66
|
}
|
|
66
67
|
};
|
|
68
|
+
},
|
|
69
|
+
ai: (name, o) => {
|
|
70
|
+
const error = promptError(o.prompt) ?? (o.writes === "" ? "it writes no field" : void 0);
|
|
71
|
+
if (error) throw new Error(`Action "${name}": ${error}`);
|
|
72
|
+
return {
|
|
73
|
+
...named(name, o),
|
|
74
|
+
run: {
|
|
75
|
+
kind: "ai",
|
|
76
|
+
prompt: o.prompt,
|
|
77
|
+
writes: o.writes
|
|
78
|
+
}
|
|
79
|
+
};
|
|
67
80
|
}
|
|
68
81
|
};
|
|
69
82
|
//#endregion
|
package/dist/view-displays.d.ts
CHANGED
|
@@ -92,6 +92,21 @@ export declare const displays: {
|
|
|
92
92
|
badge?: Ref;
|
|
93
93
|
meta?: Ref[];
|
|
94
94
|
}, o?: Naming) => ListDisplayModel;
|
|
95
|
+
/**
|
|
96
|
+
* The records on the days from `start` to `end` (or on their start day alone), in a month grid of whole weeks or a
|
|
97
|
+
* week grid, with weeks from Monday and days in UTC. It loads only the days shown through the list API, with the
|
|
98
|
+
* list's filter and search, in start order. An event shows `title` (default: the view's title), after its time for a
|
|
99
|
+
* timestamp, and opens the record. Dragging an event to another day, or picking it up with Space and moving it with
|
|
100
|
+
* the arrow keys, saves the record with `start` and `end` moved by the same days, as one update with its ETag, so the
|
|
101
|
+
* resource's access rules and `validate` decide, and a refusal puts the event back and says why. `start` must be a
|
|
102
|
+
* filterable date or timestamp field and `end` a filterable field of the same type. Named `calendar` unless `name`
|
|
103
|
+
* says otherwise. See [Calendars](website/src/content/docs/reference/ui-config.mdx#calendars).
|
|
104
|
+
*/
|
|
105
|
+
calendar: (fields: {
|
|
106
|
+
start: Ref;
|
|
107
|
+
end?: Ref;
|
|
108
|
+
title?: Ref;
|
|
109
|
+
}, o?: Naming) => ListDisplayModel;
|
|
95
110
|
/**
|
|
96
111
|
* The list drawn by a component of the project, registered in `protobase.ui.tsx` under its name. It gets `props` and
|
|
97
112
|
* reads the rows with `useListDisplay()`; `fields` are the fields it shows, fetched with relation names.
|
|
@@ -107,6 +122,10 @@ export declare const displays: {
|
|
|
107
122
|
export declare const boardErrors: (display: Extract<ListDisplayModel, {
|
|
108
123
|
kind: "board";
|
|
109
124
|
}>, model: ResourceModel) => string[];
|
|
110
|
-
/**
|
|
125
|
+
/** What makes a calendar unable to place the records on days: a `start` that is no filterable date or timestamp, or an `end` that is no filterable field of its type. */
|
|
126
|
+
export declare const calendarErrors: (display: Extract<ListDisplayModel, {
|
|
127
|
+
kind: "calendar";
|
|
128
|
+
}>, model: ResourceModel) => string[];
|
|
129
|
+
/** Every field a display names: what it shows, a map's position and the fields saying how exact it is, a board's columns, a calendar's dates, and the field references in a custom display's props. */
|
|
111
130
|
export declare const displayFields: (display: ListDisplayModel) => string[];
|
|
112
131
|
//#endregion
|
package/dist/view-displays.js
CHANGED
|
@@ -92,6 +92,24 @@ const displays = {
|
|
|
92
92
|
...fields.meta && { meta: fields.meta.map((ref) => ref.name) }
|
|
93
93
|
}),
|
|
94
94
|
/**
|
|
95
|
+
* The records on the days from `start` to `end` (or on their start day alone), in a month grid of whole weeks or a
|
|
96
|
+
* week grid, with weeks from Monday and days in UTC. It loads only the days shown through the list API, with the
|
|
97
|
+
* list's filter and search, in start order. An event shows `title` (default: the view's title), after its time for a
|
|
98
|
+
* timestamp, and opens the record. Dragging an event to another day, or picking it up with Space and moving it with
|
|
99
|
+
* the arrow keys, saves the record with `start` and `end` moved by the same days, as one update with its ETag, so the
|
|
100
|
+
* resource's access rules and `validate` decide, and a refusal puts the event back and says why. `start` must be a
|
|
101
|
+
* filterable date or timestamp field and `end` a filterable field of the same type. Named `calendar` unless `name`
|
|
102
|
+
* says otherwise. See [Calendars](website/src/content/docs/reference/ui-config.mdx#calendars).
|
|
103
|
+
*/
|
|
104
|
+
calendar: (fields, o = {}) => ({
|
|
105
|
+
kind: "calendar",
|
|
106
|
+
name: o.name ?? "calendar",
|
|
107
|
+
...naming(o),
|
|
108
|
+
start: fields.start.name,
|
|
109
|
+
...fields.end && { end: fields.end.name },
|
|
110
|
+
...fields.title && { title: fields.title.name }
|
|
111
|
+
}),
|
|
112
|
+
/**
|
|
95
113
|
* The list drawn by a component of the project, registered in `protobase.ui.tsx` under its name. It gets `props` and
|
|
96
114
|
* reads the rows with `useListDisplay()`; `fields` are the fields it shows, fetched with relation names.
|
|
97
115
|
*/
|
|
@@ -131,7 +149,21 @@ const boardErrors = (display, model) => {
|
|
|
131
149
|
if (field.type !== "enum") return [`"${display.by}" on ${model.name} is ${field.type}, not an enum, whose values are the board's columns`];
|
|
132
150
|
return field.filterable ? [] : [`"${display.by}" on ${model.name} is not filterable, and each column of the board filters by its value`];
|
|
133
151
|
};
|
|
134
|
-
/**
|
|
152
|
+
/** What makes a calendar unable to place the records on days: a `start` that is no filterable date or timestamp, or an `end` that is no filterable field of its type. */
|
|
153
|
+
const calendarErrors = (display, model) => {
|
|
154
|
+
const known = (name) => Object.hasOwn(model.fields, name) ? model.fields[name] : void 0;
|
|
155
|
+
const start = known(display.start);
|
|
156
|
+
const end = display.end === void 0 ? void 0 : known(display.end);
|
|
157
|
+
if (!start) return [];
|
|
158
|
+
if (start.type !== "date" && start.type !== "timestamp") return [`start "${display.start}" on ${model.name} is ${start.type}, not a date or timestamp`];
|
|
159
|
+
const unfilterable = (name) => `${name === display.start ? "start" : "end"} "${name}" on ${model.name} is not filterable, and the calendar filters by it for the days it shows`;
|
|
160
|
+
return [
|
|
161
|
+
...start.filterable ? [] : [unfilterable(display.start)],
|
|
162
|
+
...end && end.type !== start.type ? [`end "${display.end}" on ${model.name} is ${end.type}, not ${start.type} like start "${display.start}"`] : [],
|
|
163
|
+
...end && end.type === start.type && !end.filterable ? [unfilterable(display.end)] : []
|
|
164
|
+
];
|
|
165
|
+
};
|
|
166
|
+
/** Every field a display names: what it shows, a map's position and the fields saying how exact it is, a board's columns, a calendar's dates, and the field references in a custom display's props. */
|
|
135
167
|
const displayFields = (display) => {
|
|
136
168
|
if (display.kind === "map") return [
|
|
137
169
|
display.latitude,
|
|
@@ -149,6 +181,11 @@ const displayFields = (display) => {
|
|
|
149
181
|
display.badge,
|
|
150
182
|
...display.meta ?? []
|
|
151
183
|
].filter((name) => name !== void 0);
|
|
184
|
+
if (display.kind === "calendar") return [
|
|
185
|
+
display.start,
|
|
186
|
+
display.end,
|
|
187
|
+
display.title
|
|
188
|
+
].filter((name) => name !== void 0);
|
|
152
189
|
return [
|
|
153
190
|
display.image,
|
|
154
191
|
display.title,
|
|
@@ -161,4 +198,4 @@ const displayFields = (display) => {
|
|
|
161
198
|
].filter((name) => name !== void 0);
|
|
162
199
|
};
|
|
163
200
|
//#endregion
|
|
164
|
-
export { boardErrors, cardPhotoLimit, customComponent, displayErrors, displayFields, displays, tableDisplay };
|
|
201
|
+
export { boardErrors, calendarErrors, cardPhotoLimit, customComponent, displayErrors, displayFields, displays, tableDisplay };
|
package/dist/view-field.d.ts
CHANGED
|
@@ -26,7 +26,10 @@ export declare class FieldView<N extends string = string, V = unknown> {
|
|
|
26
26
|
* view and locked form; `always` when omitted. Its lists, cards and filters show it either way.
|
|
27
27
|
*/
|
|
28
28
|
when(when: ShowWhen): FieldView<N, V>;
|
|
29
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* How the value shows: `badge` for statuses, `code` in monospace, `percent` with a % sign; for a text field, `email`,
|
|
31
|
+
* `phone` and `url` as a mail, call or open link, edited with an email, phone or address keyboard on a mobile screen.
|
|
32
|
+
*/
|
|
30
33
|
format(format: FieldFormat): FieldView<N, V>;
|
|
31
34
|
/**
|
|
32
35
|
* A text of several lines, such as a description: forms edit it in a box that grows with the text, and the record
|
package/dist/view-field.js
CHANGED
|
@@ -30,7 +30,10 @@ var FieldView = class FieldView {
|
|
|
30
30
|
when(when) {
|
|
31
31
|
return this.with({ when });
|
|
32
32
|
}
|
|
33
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* How the value shows: `badge` for statuses, `code` in monospace, `percent` with a % sign; for a text field, `email`,
|
|
35
|
+
* `phone` and `url` as a mail, call or open link, edited with an email, phone or address keyboard on a mobile screen.
|
|
36
|
+
*/
|
|
34
37
|
format(format) {
|
|
35
38
|
return this.with({ format });
|
|
36
39
|
}
|
package/dist/view.d.ts
CHANGED
|
@@ -69,7 +69,7 @@ export declare class ViewBuilder<R extends {
|
|
|
69
69
|
fields(build: (r: FieldViews<R>) => Record<string, FieldView>): ViewBuilder<R>;
|
|
70
70
|
/**
|
|
71
71
|
* The list: its table's `columns`, fields or columns of related records (`c.related`), `sort` and `search`, the
|
|
72
|
-
* columns that are `editable` in their cell, and other `displays` to switch to (`d.cards`, `d.map`, `d.board`, `d.custom`), one of which
|
|
72
|
+
* columns that are `editable` in their cell, and other `displays` to switch to (`d.cards`, `d.map`, `d.board`, `d.calendar`, `d.custom`), one of which
|
|
73
73
|
* may be the `default` the list opens in. Throws when display or column names collide, `default` names none or an
|
|
74
74
|
* editable field is not among the columns. See
|
|
75
75
|
* [Editing in the list](website/src/content/docs/reference/ui-config.mdx#editing-in-the-list).
|
package/dist/view.js
CHANGED
|
@@ -5,7 +5,7 @@ import { actions } from "./view-actions.js";
|
|
|
5
5
|
import { chartModel } from "./view-chart.js";
|
|
6
6
|
import { formModel } from "./view-form.js";
|
|
7
7
|
import { columns, isListColumn } from "./view-columns.js";
|
|
8
|
-
import { boardErrors, displayErrors, displayFields, displays } from "./view-displays.js";
|
|
8
|
+
import { boardErrors, calendarErrors, displayErrors, displayFields, displays } from "./view-displays.js";
|
|
9
9
|
import { FieldView } from "./view-field.js";
|
|
10
10
|
import { widgets } from "./view-widgets.js";
|
|
11
11
|
//#region src/view.ts
|
|
@@ -76,7 +76,7 @@ var ViewBuilder = class ViewBuilder {
|
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
78
|
* The list: its table's `columns`, fields or columns of related records (`c.related`), `sort` and `search`, the
|
|
79
|
-
* columns that are `editable` in their cell, and other `displays` to switch to (`d.cards`, `d.map`, `d.board`, `d.custom`), one of which
|
|
79
|
+
* columns that are `editable` in their cell, and other `displays` to switch to (`d.cards`, `d.map`, `d.board`, `d.calendar`, `d.custom`), one of which
|
|
80
80
|
* may be the `default` the list opens in. Throws when display or column names collide, `default` names none or an
|
|
81
81
|
* editable field is not among the columns. See
|
|
82
82
|
* [Editing in the list](website/src/content/docs/reference/ui-config.mdx#editing-in-the-list).
|
|
@@ -255,6 +255,8 @@ var ViewBuilder = class ViewBuilder {
|
|
|
255
255
|
if (flag !== void 0) throw new Error(`View "${this.state.resource}": display "${display.name}" shows "${flag}" as a flag, which is not a boolean field`);
|
|
256
256
|
const board = display.kind === "board" ? boardErrors(display, resource) : [];
|
|
257
257
|
if (board.length > 0) throw new Error(`View "${this.state.resource}": display "${display.name}": ${board.join("; ")}`);
|
|
258
|
+
const calendar = display.kind === "calendar" ? calendarErrors(display, resource) : [];
|
|
259
|
+
if (calendar.length > 0) throw new Error(`View "${this.state.resource}": display "${display.name}": ${calendar.join("; ")}`);
|
|
258
260
|
}
|
|
259
261
|
const clash = resource ? list?.relatedColumns?.find((column) => Object.hasOwn(resource.fields, column.name)) : void 0;
|
|
260
262
|
if (clash) throw new Error(`View "${this.state.resource}": related column "${clash.name}" has the name of a field of the resource`);
|