@weaver-apps/calendar 0.1.3 → 0.3.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/README.md CHANGED
@@ -7,10 +7,15 @@ and holds no key.
7
7
  ## Compose it
8
8
 
9
9
  ```ts
10
- import { createCalendarPlugin, createHttpCalendarGateway, provideCalendarGateway } from '@weaver-apps/calendar';
11
-
12
- provideTranslationNamespaces('calendar'),
13
- provideCapabilityGrants({ calendar: ['contributions', 'navigation', 'ui'] }),
10
+ import {
11
+ calendarPluginId,
12
+ createCalendarPlugin,
13
+ createHttpCalendarGateway,
14
+ provideCalendarGateway,
15
+ } from '@weaver-apps/calendar';
16
+
17
+ provideTranslationNamespaces(calendarPluginId),
18
+ provideCapabilityGrants({ [calendarPluginId]: ['contributions', 'navigation', 'ui'] }),
14
19
  ...providePlugins(createCalendarPlugin({ railItem: true })),
15
20
  provideCalendarGateway(createHttpCalendarGateway({ baseUrl: '/weaver-apps' })),
16
21
  ```
@@ -18,17 +23,36 @@ provideCalendarGateway(createHttpCalendarGateway({ baseUrl: '/weaver-apps' })),
18
23
  - `baseUrl` is a same-origin path the host's backend forwards to the server, adding the forwarding key and
19
24
  the signed-in person's tenant (`X-API-Key`, `X-Act-For-Tenant`). The gateway appends the server's own
20
25
  routes, such as `/calendar/calendars/{id}/appointments`.
21
- - Serve the translations: the package carries them under `i18n/calendar`; add an assets glob that copies
22
- `node_modules/@weaver-apps/calendar/i18n/calendar` to `i18n/calendar`.
23
- - If the host compiles the shell's theme with Tailwind, add the package as an `@source`.
24
-
25
- ## Surfaces
26
-
27
- | Path | What it shows |
28
- |---|---|
29
- | `calendar` | the tenant's calendars |
30
- | `calendar/:calendarId` | one calendar's appointments, a week at a time |
31
-
32
- `calendarPath(id)` builds the second; a host that shows one entry per calendar navigates there.
33
- `createCalendarPlugin({ railItem: true })` adds a rail item that opens the list; without it the host decides
34
- where the calendar is reached from.
26
+ - Serve the translations: the package carries them under `i18n/weaver-apps-calendar`; add an assets glob
27
+ that copies `node_modules/@weaver-apps/calendar/i18n/weaver-apps-calendar` to `i18n/weaver-apps-calendar`.
28
+ - Install AG Grid, a peer dependency the list is drawn with: `npm i ag-grid-community@^36.2 ag-grid-angular@^36.2
29
+ @ag-grid-community/locale@^36.2`. `@weaver-apps/data-grid`, the grid both Weaver Apps weavers share, comes along as a
30
+ dependency; the host composes nothing for it.
31
+ - If the host compiles the shell's theme with Tailwind, add the package and `@weaver-apps/data-grid` as an `@source`.
32
+
33
+ ## Names in the host
34
+
35
+ The plugin id is `weaver-apps-calendar` (`calendarPluginId`). Every id the plugin registers and every icon
36
+ it contributes starts with `weaver-apps-calendar.`, its routes live under `weaver-apps-calendar/`, and its
37
+ translations are the namespace `weaver-apps-calendar`. The ids are exported as `calendarContributionIds`.
38
+
39
+ | Id | Path | What it shows |
40
+ |---|---|---|
41
+ | `weaver-apps-calendar.calendars` | `weaver-apps-calendar` | the tenant's calendars |
42
+ | `weaver-apps-calendar.calendar` | `weaver-apps-calendar/:calendarId` | one calendar's appointments, a week at a time |
43
+ | `weaver-apps-calendar.rail` | | the rail item, only with `railItem: true` |
44
+
45
+ A calendar's week is a grid: a click on a column header sorts it (earliest first by default), and the status
46
+ column filters by any number of statuses.
47
+
48
+ `calendarPath(id)` builds the address of one calendar; a host that shows one entry per calendar navigates
49
+ there. `createCalendarPlugin({ railItem: true })` adds a rail item that opens the list; without it the host
50
+ decides where the calendar is reached from.
51
+
52
+ ## Moving from 0.1.x
53
+
54
+ 0.2.0 draws the list with AG Grid, so a host installs it (see above). It also renamed everything the plugin puts
55
+ into a host; a host composing it changes three places: the key of
56
+ its capability grant (`calendar` becomes `weaver-apps-calendar`), the translation namespace it declares, and
57
+ the folder it serves the translations from (`i18n/calendar` becomes `i18n/weaver-apps-calendar`). Addresses
58
+ saved under `calendar/...` no longer resolve; build them with `calendarPath`.
@@ -0,0 +1,35 @@
1
+ import { InjectionToken } from "@angular/core";
2
+ var CalendarHost = class {
3
+ ctx;
4
+ bind(ctx) {
5
+ this.ctx = ctx;
6
+ }
7
+ unbind() {
8
+ this.ctx = void 0;
9
+ }
10
+ get ui() {
11
+ if (!this.ctx) throw new Error("The calendar plugin is not active.");
12
+ return this.ctx.ui;
13
+ }
14
+ navigate(path) {
15
+ this.ctx?.navigateContent(path);
16
+ }
17
+ };
18
+ const calendarHost = new CalendarHost();
19
+ var CalendarGatewayError = class extends Error {
20
+ status;
21
+ constructor(status) {
22
+ super(`The calendar answered ${status}.`);
23
+ this.status = status;
24
+ }
25
+ };
26
+ const CALENDAR_GATEWAY = new InjectionToken("weaver-apps.calendar.gateway");
27
+ function provideCalendarGateway(gateway) {
28
+ return {
29
+ provide: CALENDAR_GATEWAY,
30
+ useValue: gateway
31
+ };
32
+ }
33
+ export { calendarHost as i, CalendarGatewayError as n, provideCalendarGateway as r, CALENDAR_GATEWAY as t };
34
+
35
+ //# sourceMappingURL=weaver-apps-calendar-calendar-gateway-DUo797bn.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"weaver-apps-calendar-calendar-gateway-DUo797bn.mjs","names":[],"sources":["../../../../libs/calendar/src/lib/plugin/calendar-host.ts","../../../../libs/calendar/src/lib/gateway/calendar-gateway.ts"],"sourcesContent":["import type { PluginContext } from '@loomweaver/plugin-sdk';\n\ntype HostContext = Pick<PluginContext, 'ui' | 'navigateContent'>;\n\nclass CalendarHost {\n private ctx?: HostContext;\n\n bind(ctx: HostContext): void {\n this.ctx = ctx;\n }\n\n unbind(): void {\n this.ctx = undefined;\n }\n\n get ui(): HostContext['ui'] {\n if (!this.ctx) {\n throw new Error('The calendar plugin is not active.');\n }\n return this.ctx.ui;\n }\n\n navigate(path: string): void {\n this.ctx?.navigateContent(path);\n }\n}\n\nexport const calendarHost = new CalendarHost();\n","import { InjectionToken, Provider } from '@angular/core';\nimport type { AppointmentRequest, AppointmentResponse, AppointmentStatus, CalendarResponse } from '../api/models';\n\nexport interface CalendarRange {\n readonly from: Date;\n readonly until: Date;\n}\n\nexport interface ConflictingAppointment {\n readonly id: string;\n readonly start: string;\n readonly end: string;\n}\n\nexport type AppointmentOutcome =\n | { readonly kind: 'saved'; readonly appointment: AppointmentResponse }\n | { readonly kind: 'conflict'; readonly conflicts: readonly ConflictingAppointment[] }\n | { readonly kind: 'closed' }\n | { readonly kind: 'changed' }\n | { readonly kind: 'invalid' }\n | { readonly kind: 'missing' };\n\nexport interface CalendarGateway {\n calendars(): Promise<readonly CalendarResponse[]>;\n calendar(calendarId: string): Promise<CalendarResponse | null>;\n saveCalendar(calendarId: string, name: string): Promise<CalendarResponse>;\n appointments(calendarId: string, range: CalendarRange, contact?: string, signal?: AbortSignal): Promise<readonly AppointmentResponse[]>;\n book(calendarId: string, request: AppointmentRequest): Promise<AppointmentOutcome>;\n update(calendarId: string, appointmentId: string, request: AppointmentRequest): Promise<AppointmentOutcome>;\n cancel(calendarId: string, appointmentId: string, reason: string | null): Promise<AppointmentOutcome>;\n setStatus(calendarId: string, appointmentId: string, status: AppointmentStatus): Promise<AppointmentOutcome>;\n remove(calendarId: string, appointmentId: string): Promise<void>;\n}\n\nexport class CalendarGatewayError extends Error {\n constructor(readonly status: number) {\n super(`The calendar answered ${status}.`);\n }\n}\n\nexport const CALENDAR_GATEWAY = new InjectionToken<CalendarGateway>('weaver-apps.calendar.gateway');\n\nexport function provideCalendarGateway(gateway: CalendarGateway): Provider {\n return { provide: CALENDAR_GATEWAY, useValue: gateway };\n}\n"],"mappings":";AAIA,IAAM,eAAN,MAAkB;CACR;CAER,KAAK,KAAgB;EACnB,KAAK,MAAM;CACb;CAEA,SAAM;EACJ,KAAK,MAAM;CACb;CAEA,IAAI,KAAE;EACJ,IAAI,CAAC,KAAK,KACR,MAAM,IAAI,MAAM,oCAAoC;EAEtD,OAAO,KAAK,IAAI;CAClB;CAEA,SAAS,MAAY;EACnB,KAAK,KAAK,gBAAgB,IAAI;CAChC;AACD;AAED,MAAa,eAAe,IAAI,aAAY;ACO5C,IAAa,uBAAb,cAA0C,MAAK;CACxB;CAArB,YAAqB,QAAc;EACjC,MAAM,yBAAyB,OAAM,EAAG;EADrB,KAAA,SAAA;CAErB;AACD;AAED,MAAa,mBAAmB,IAAI,eAAgC,8BAA8B;AAElG,SAAgB,uBAAuB,SAAwB;CAC7D,OAAO;EAAE,SAAS;EAAkB,UAAU;CAAO;AACvD"}
@@ -0,0 +1,74 @@
1
+ import { i as calendarHost, t as CALENDAR_GATEWAY } from "./weaver-apps-calendar-calendar-gateway-DUo797bn.mjs";
2
+ import { n as calendarPath } from "./weaver-apps-calendar-calendar-paths-qzIR-lDU.mjs";
3
+ import * as i0 from "@angular/core";
4
+ import { CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy, Component, inject, resource } from "@angular/core";
5
+ import { TranslocoPipe } from "@jsverse/transloco";
6
+ var CalendarListPage = class CalendarListPage {
7
+ gateway = inject(CALENDAR_GATEWAY);
8
+ calendars = resource({
9
+ ...ngDevMode ? { debugName: "calendars" } : /* istanbul ignore next */ {},
10
+ loader: () => this.gateway.calendars()
11
+ });
12
+ open(calendarId) {
13
+ calendarHost.navigate(calendarPath(calendarId));
14
+ }
15
+ async create() {
16
+ const name = await calendarHost.ui.prompt({
17
+ title: "weaver-apps-calendar.calendars.create",
18
+ message: "weaver-apps-calendar.calendars.createMessage",
19
+ confirmLabel: "weaver-apps-calendar.calendars.createConfirm"
20
+ });
21
+ if (!name?.trim()) return;
22
+ try {
23
+ const calendar = await this.gateway.saveCalendar(crypto.randomUUID(), name.trim());
24
+ this.open(calendar.id);
25
+ } catch {
26
+ calendarHost.ui.toast({
27
+ message: "weaver-apps-calendar.errors.create",
28
+ kind: "error"
29
+ });
30
+ }
31
+ }
32
+ static ɵfac = i0.ɵɵngDeclareFactory({
33
+ minVersion: "12.0.0",
34
+ version: "22.2.3",
35
+ ngImport: i0,
36
+ type: CalendarListPage,
37
+ deps: [],
38
+ target: i0.ɵɵFactoryTarget.Component
39
+ });
40
+ static ɵcmp = i0.ɵɵngDeclareComponent({
41
+ minVersion: "17.0.0",
42
+ version: "22.2.3",
43
+ type: CalendarListPage,
44
+ isStandalone: true,
45
+ selector: "wa-calendar-list-page",
46
+ ngImport: i0,
47
+ template: "<div class=\"flex h-full flex-col gap-4 overflow-auto p-6\">\n <div class=\"flex items-center justify-between gap-4\">\n <h2 class=\"text-lg font-semibold text-content\">{{ 'weaver-apps-calendar.calendars.title' | transloco }}</h2>\n <button type=\"button\" class=\"lw-btn lw-btn--primary\" (click)=\"create()\">{{ 'weaver-apps-calendar.calendars.create' | transloco }}</button>\n </div>\n @if (calendars.error()) {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ 'weaver-apps-calendar.errors.load' | transloco }}</p>\n } @else if (calendars.hasValue()) {\n <ul role=\"list\" class=\"divide-y divide-border rounded-xl border border-border bg-surface\">\n @for (calendar of calendars.value(); track calendar.id) {\n <li>\n <button type=\"button\" class=\"flex w-full items-center gap-3 px-4 py-3 text-left text-sm text-content hover:bg-surface-overlay\" (click)=\"open(calendar.id)\">\n <lw-icon name=\"weaver-apps-calendar.calendar\" size=\"1.25rem\"></lw-icon>\n <span>{{ calendar.name }}</span>\n </button>\n </li>\n } @empty {\n <li class=\"px-4 py-8 text-center text-sm text-content-faint\">{{ 'weaver-apps-calendar.calendars.empty' | transloco }}</li>\n }\n </ul>\n }\n</div>\n",
48
+ dependencies: [{
49
+ kind: "pipe",
50
+ type: TranslocoPipe,
51
+ name: "transloco"
52
+ }],
53
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
54
+ });
55
+ };
56
+ i0.ɵɵngDeclareClassMetadata({
57
+ minVersion: "12.0.0",
58
+ version: "22.2.3",
59
+ ngImport: i0,
60
+ type: CalendarListPage,
61
+ decorators: [{
62
+ type: Component,
63
+ args: [{
64
+ selector: "wa-calendar-list-page",
65
+ imports: [TranslocoPipe],
66
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
67
+ changeDetection: ChangeDetectionStrategy.OnPush,
68
+ template: "<div class=\"flex h-full flex-col gap-4 overflow-auto p-6\">\n <div class=\"flex items-center justify-between gap-4\">\n <h2 class=\"text-lg font-semibold text-content\">{{ 'weaver-apps-calendar.calendars.title' | transloco }}</h2>\n <button type=\"button\" class=\"lw-btn lw-btn--primary\" (click)=\"create()\">{{ 'weaver-apps-calendar.calendars.create' | transloco }}</button>\n </div>\n @if (calendars.error()) {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ 'weaver-apps-calendar.errors.load' | transloco }}</p>\n } @else if (calendars.hasValue()) {\n <ul role=\"list\" class=\"divide-y divide-border rounded-xl border border-border bg-surface\">\n @for (calendar of calendars.value(); track calendar.id) {\n <li>\n <button type=\"button\" class=\"flex w-full items-center gap-3 px-4 py-3 text-left text-sm text-content hover:bg-surface-overlay\" (click)=\"open(calendar.id)\">\n <lw-icon name=\"weaver-apps-calendar.calendar\" size=\"1.25rem\"></lw-icon>\n <span>{{ calendar.name }}</span>\n </button>\n </li>\n } @empty {\n <li class=\"px-4 py-8 text-center text-sm text-content-faint\">{{ 'weaver-apps-calendar.calendars.empty' | transloco }}</li>\n }\n </ul>\n }\n</div>\n"
69
+ }]
70
+ }]
71
+ });
72
+ export { CalendarListPage };
73
+
74
+ //# sourceMappingURL=weaver-apps-calendar-calendar-list-page-dgF-nnQF.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"weaver-apps-calendar-calendar-list-page-dgF-nnQF.mjs","names":[],"sources":["../../../../libs/calendar/src/lib/views/calendar-list-page.ts","../../../../libs/calendar/src/lib/views/calendar-list-page.html"],"sourcesContent":["import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, inject, resource } from '@angular/core';\nimport { TranslocoPipe } from '@jsverse/transloco';\nimport { CALENDAR_GATEWAY } from '../gateway/calendar-gateway';\nimport { calendarHost } from '../plugin/calendar-host';\nimport { calendarPath } from '../plugin/calendar-paths';\n\n@Component({\n selector: 'wa-calendar-list-page',\n imports: [TranslocoPipe],\n schemas: [CUSTOM_ELEMENTS_SCHEMA],\n templateUrl: './calendar-list-page.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class CalendarListPage {\n private readonly gateway = inject(CALENDAR_GATEWAY);\n\n protected readonly calendars = resource({ loader: () => this.gateway.calendars() });\n\n protected open(calendarId: string): void {\n calendarHost.navigate(calendarPath(calendarId));\n }\n\n protected async create(): Promise<void> {\n const name = await calendarHost.ui.prompt({\n title: 'weaver-apps-calendar.calendars.create',\n message: 'weaver-apps-calendar.calendars.createMessage',\n confirmLabel: 'weaver-apps-calendar.calendars.createConfirm',\n });\n if (!name?.trim()) {\n return;\n }\n try {\n const calendar = await this.gateway.saveCalendar(crypto.randomUUID(), name.trim());\n this.open(calendar.id);\n } catch {\n calendarHost.ui.toast({ message: 'weaver-apps-calendar.errors.create', kind: 'error' });\n }\n }\n}\n","<div class=\"flex h-full flex-col gap-4 overflow-auto p-6\">\n <div class=\"flex items-center justify-between gap-4\">\n <h2 class=\"text-lg font-semibold text-content\">{{ 'weaver-apps-calendar.calendars.title' | transloco }}</h2>\n <button type=\"button\" class=\"lw-btn lw-btn--primary\" (click)=\"create()\">{{ 'weaver-apps-calendar.calendars.create' | transloco }}</button>\n </div>\n @if (calendars.error()) {\n <p class=\"text-sm text-negative\" role=\"alert\">{{ 'weaver-apps-calendar.errors.load' | transloco }}</p>\n } @else if (calendars.hasValue()) {\n <ul role=\"list\" class=\"divide-y divide-border rounded-xl border border-border bg-surface\">\n @for (calendar of calendars.value(); track calendar.id) {\n <li>\n <button type=\"button\" class=\"flex w-full items-center gap-3 px-4 py-3 text-left text-sm text-content hover:bg-surface-overlay\" (click)=\"open(calendar.id)\">\n <lw-icon name=\"weaver-apps-calendar.calendar\" size=\"1.25rem\"></lw-icon>\n <span>{{ calendar.name }}</span>\n </button>\n </li>\n } @empty {\n <li class=\"px-4 py-8 text-center text-sm text-content-faint\">{{ 'weaver-apps-calendar.calendars.empty' | transloco }}</li>\n }\n </ul>\n }\n</div>\n"],"mappings":";;;;;AAaA,IAAa,mBAAb,MAAa,iBAAgB;CACV,UAAU,OAAO,gBAAgB;CAE/B,YAAY,SAAQ;EAAA,GAAA,YAAA,EAAA,WAAA,YAAA,+BAAA,CAAA;EAAG,cAAc,KAAK,QAAQ,UAAS;CAAE,CAAA;CAEtE,KAAK,YAAkB;EAC/B,aAAa,SAAS,aAAa,UAAU,CAAC;CAChD;CAEU,MAAM,SAAM;EACpB,MAAM,OAAO,MAAM,aAAa,GAAG,OAAO;GACxC,OAAO;GACP,SAAS;GACT,cAAc;EACf,CAAA;EACD,IAAI,CAAC,MAAM,KAAI,GACb;EAEF,IAAI;GACF,MAAM,WAAW,MAAM,KAAK,QAAQ,aAAa,OAAO,WAAU,GAAI,KAAK,KAAI,CAAE;GACjF,KAAK,KAAK,SAAS,EAAE;EACvB,QAAQ;GACN,aAAa,GAAG,MAAM;IAAE,SAAS;IAAsC,MAAM;GAAO,CAAE;EACxF;CACF;CAxBW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAgB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAhB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAgB,cAAA;EAAA,UAAA;EAAA,UAAA;EAAA,UCb7B;EAsBA,cAAA,CAAA;GAAA,MAAA;GAAA,MDdY;GAAa,MAAA;EAAA,CAAA;EAAA,iBAAA,GAAA,wBAAA;CAAA,CAAA;;AAKZ,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAgB,YAAA,CAAA;EAP5B,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAuB,SACxB,CAAC,aAAa;GAAC,SACf,CAAC,sBAAsB;GAAC,iBAEhB,wBAAwB;GAAM,UAAA;EAAA,CAAA"}