@api-client/ui 0.6.5 → 0.6.7
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/build/src/core/Activity.d.ts +7 -5
- package/build/src/core/Activity.d.ts.map +1 -1
- package/build/src/core/Activity.js +6 -4
- package/build/src/core/Activity.js.map +1 -1
- package/build/src/core/ActivityManager.d.ts +35 -2
- package/build/src/core/ActivityManager.d.ts.map +1 -1
- package/build/src/core/ActivityManager.js +11 -0
- package/build/src/core/ActivityManager.js.map +1 -1
- package/build/src/core/Fragment.d.ts +12 -2
- package/build/src/core/Fragment.d.ts.map +1 -1
- package/build/src/core/Fragment.js +11 -2
- package/build/src/core/Fragment.js.map +1 -1
- package/build/src/elements/setup/internals/OrganizationSelector.js +2 -2
- package/build/src/elements/setup/internals/OrganizationSelector.js.map +1 -1
- package/build/src/md/button/internals/base.d.ts +4 -1
- package/build/src/md/button/internals/base.d.ts.map +1 -1
- package/build/src/md/button/internals/base.js +4 -0
- package/build/src/md/button/internals/base.js.map +1 -1
- package/build/src/services/OrganizationService.d.ts +44 -6
- package/build/src/services/OrganizationService.d.ts.map +1 -1
- package/build/src/services/OrganizationService.js +79 -24
- package/build/src/services/OrganizationService.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/core/Activity.ts +8 -6
- package/src/core/ActivityManager.ts +41 -6
- package/src/core/Fragment.ts +14 -4
- package/src/elements/setup/internals/OrganizationSelector.ts +2 -2
- package/src/md/button/internals/base.ts +5 -1
- package/src/services/OrganizationService.ts +84 -25
package/package.json
CHANGED
package/src/core/Activity.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
2
|
import { nothing, TemplateResult } from 'lit'
|
|
3
|
-
import { ActivityLifecycle, IntentResult, type Intent } from './ActivityManager.js'
|
|
3
|
+
import { ActivityLifecycle, IntentResult, type Intent, type ResolvedIntent } from './ActivityManager.js'
|
|
4
4
|
import { FragmentManager } from './FragmentManager.js'
|
|
5
5
|
import type { Fragment } from './Fragment.js'
|
|
6
6
|
import type { Application, UpdateRequest } from './Application.js'
|
|
@@ -301,17 +301,19 @@ export class Activity extends EventTarget {
|
|
|
301
301
|
|
|
302
302
|
/**
|
|
303
303
|
* Starts a new activity for a result.
|
|
304
|
+
* @template T The type of the result data expected.
|
|
304
305
|
* @param intent The intent to start.
|
|
305
|
-
* @returns A promise that resolves when the started activity finishes, returning the result Intent
|
|
306
|
+
* @returns A promise that resolves when the started activity finishes, returning the result Intent with
|
|
307
|
+
* data of type T.
|
|
306
308
|
* @example
|
|
307
309
|
* ```typescript
|
|
308
|
-
* const resultIntent = await this.startActivityForResult({ action: '
|
|
310
|
+
* const resultIntent = await this.startActivityForResult<{ userId: string }>({ action: 'pickUser' });
|
|
309
311
|
* if (resultIntent.resultCode === IntentResult.RESULT_OK) {
|
|
310
|
-
*
|
|
312
|
+
* console.log(resultIntent.data?.userId);
|
|
311
313
|
* }
|
|
312
314
|
* ```
|
|
313
315
|
*/
|
|
314
|
-
async startActivityForResult(intent: Intent): Promise<
|
|
316
|
+
async startActivityForResult<T>(intent: Intent): Promise<ResolvedIntent<T | undefined>> {
|
|
315
317
|
const { manager } = this.getApplication()
|
|
316
318
|
return manager.startActivityForResult(intent)
|
|
317
319
|
}
|
|
@@ -355,7 +357,7 @@ export class Activity extends EventTarget {
|
|
|
355
357
|
if (event.type === EventTypes.Intent.startActivityForResult) {
|
|
356
358
|
const info = event.detail as ActivityWithResultDetail
|
|
357
359
|
const result = await this.startActivityForResult(info.intent)
|
|
358
|
-
info.onResult(
|
|
360
|
+
info.onResult(result.result, result)
|
|
359
361
|
} else if (event.type === EventTypes.Intent.startActivity) {
|
|
360
362
|
await this.startActivity(event.detail.intent)
|
|
361
363
|
} else {
|
|
@@ -59,9 +59,22 @@ export enum IntentFlags {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
export interface Intent<T = unknown> {
|
|
62
|
+
/**
|
|
63
|
+
* The action name the activity is registered for.
|
|
64
|
+
*/
|
|
62
65
|
action: string
|
|
66
|
+
/**
|
|
67
|
+
* The data passed to the activity.
|
|
68
|
+
*/
|
|
63
69
|
data?: T
|
|
70
|
+
/**
|
|
71
|
+
* The category of the activity.
|
|
72
|
+
* Optional and currently not used.
|
|
73
|
+
*/
|
|
64
74
|
category?: string[]
|
|
75
|
+
/**
|
|
76
|
+
* The flags that control the behavior of the activity.
|
|
77
|
+
*/
|
|
65
78
|
flags?: number
|
|
66
79
|
/**
|
|
67
80
|
* The request code used to distinguish between different activities.
|
|
@@ -69,6 +82,17 @@ export interface Intent<T = unknown> {
|
|
|
69
82
|
requestCode?: number
|
|
70
83
|
}
|
|
71
84
|
|
|
85
|
+
/**
|
|
86
|
+
* An intent returned by an activity after the activity result is ready.
|
|
87
|
+
* This object is only created when the activity is started with the `ForResult` flag.
|
|
88
|
+
*/
|
|
89
|
+
export interface ResolvedIntent<T = unknown> extends Intent<T> {
|
|
90
|
+
/**
|
|
91
|
+
* The result code returned by the activity.
|
|
92
|
+
*/
|
|
93
|
+
result: IntentResult
|
|
94
|
+
}
|
|
95
|
+
|
|
72
96
|
export enum IntentResult {
|
|
73
97
|
RESULT_CANCELED,
|
|
74
98
|
RESULT_OK,
|
|
@@ -137,7 +161,7 @@ export class ActivityManager {
|
|
|
137
161
|
/**
|
|
138
162
|
* Stores the resolvers for activities started for result.
|
|
139
163
|
*/
|
|
140
|
-
private resultResolvers = new Map<Intent, (intent:
|
|
164
|
+
private resultResolvers = new Map<Intent, (intent: ResolvedIntent) => void>()
|
|
141
165
|
|
|
142
166
|
/**
|
|
143
167
|
* Represents the activity stack. It is used to manage
|
|
@@ -291,9 +315,19 @@ export class ActivityManager {
|
|
|
291
315
|
/**
|
|
292
316
|
* Starts an activity that should return a result to the calling activity.
|
|
293
317
|
*
|
|
318
|
+
* @template T The type of the result data expected.
|
|
294
319
|
* @param intent The intent to start the activity.
|
|
295
|
-
|
|
296
|
-
|
|
320
|
+
* @returns A promise that resolves when the started activity finishes, returning the result Intent with
|
|
321
|
+
* data of type T.
|
|
322
|
+
* @example
|
|
323
|
+
* ```typescript
|
|
324
|
+
* const resultIntent = await mgr.startActivityForResult<{ userId: string }>({ action: 'pickUser' });
|
|
325
|
+
* if (resultIntent.resultCode === IntentResult.RESULT_OK) {
|
|
326
|
+
* console.log(resultIntent.data?.userId);
|
|
327
|
+
* }
|
|
328
|
+
* ```
|
|
329
|
+
*/
|
|
330
|
+
async startActivityForResult<T>(intent: Intent): Promise<ResolvedIntent<T | undefined>> {
|
|
297
331
|
const byReference = (intent.flags ?? 0) & IntentFlags.ByReference
|
|
298
332
|
const shallowCopy = { ...intent }
|
|
299
333
|
const data = shallowCopy.data
|
|
@@ -310,8 +344,8 @@ export class ActivityManager {
|
|
|
310
344
|
deepCopy.flags = IntentFlags.ForResult
|
|
311
345
|
}
|
|
312
346
|
|
|
313
|
-
return new Promise<
|
|
314
|
-
this.resultResolvers.set(deepCopy, resolve)
|
|
347
|
+
return new Promise<ResolvedIntent<T>>((resolve, reject) => {
|
|
348
|
+
this.resultResolvers.set(deepCopy, resolve as (intent: ResolvedIntent<unknown>) => void)
|
|
315
349
|
this.startActivity(deepCopy).catch((e) => {
|
|
316
350
|
this.resultResolvers.delete(deepCopy)
|
|
317
351
|
reject(e)
|
|
@@ -374,9 +408,10 @@ export class ActivityManager {
|
|
|
374
408
|
this.resultResolvers.delete(originalIntent)
|
|
375
409
|
// We construct the result intent.
|
|
376
410
|
// Ideally we would like to preserve the original intent structure but with new data.
|
|
377
|
-
const resultIntent:
|
|
411
|
+
const resultIntent: ResolvedIntent = {
|
|
378
412
|
...originalIntent,
|
|
379
413
|
data: activity.getResult(),
|
|
414
|
+
result: activity.resultCode,
|
|
380
415
|
}
|
|
381
416
|
resolver(resultIntent)
|
|
382
417
|
}
|
package/src/core/Fragment.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Activity } from './Activity.js'
|
|
|
3
3
|
import { FragmentState, type FragmentOptions, FragmentManager } from './FragmentManager.js'
|
|
4
4
|
import type { Application, UpdateRequest } from './Application.js'
|
|
5
5
|
import { FragmentRenderer } from './renderer/FragmentRenderer.js'
|
|
6
|
-
import { Intent,
|
|
6
|
+
import { Intent, ResolvedIntent } from './ActivityManager.js'
|
|
7
7
|
import { bound } from '../decorators/bound.js'
|
|
8
8
|
import type { ActivityDetail, ActivityWithResultDetail } from '../events/IntentEvents.js'
|
|
9
9
|
import { EventTypes } from '../events/EventTypes.js'
|
|
@@ -250,14 +250,24 @@ export class Fragment extends EventTarget {
|
|
|
250
250
|
|
|
251
251
|
/**
|
|
252
252
|
* Starts an activity for result.
|
|
253
|
+
* @template T The type of the result data expected.
|
|
253
254
|
* @param intent The intent to start.
|
|
255
|
+
* @returns A promise that resolves when the started activity finishes, returning the result Intent with
|
|
256
|
+
* data of type T.
|
|
257
|
+
* @example
|
|
258
|
+
* ```typescript
|
|
259
|
+
* const resultIntent = await this.startActivityForResult<{ userId: string }>({ action: 'pickUser' });
|
|
260
|
+
* if (resultIntent.resultCode === IntentResult.RESULT_OK) {
|
|
261
|
+
* console.log(resultIntent.data?.userId);
|
|
262
|
+
* }
|
|
263
|
+
* ```
|
|
254
264
|
*/
|
|
255
|
-
async startActivityForResult(intent: Intent): Promise<
|
|
265
|
+
async startActivityForResult<T>(intent: Intent): Promise<ResolvedIntent<T | undefined>> {
|
|
256
266
|
const activity = this.getActivity()
|
|
257
267
|
if (!activity) {
|
|
258
268
|
throw new Error('Fragment is not attached to an activity')
|
|
259
269
|
}
|
|
260
|
-
return activity.startActivityForResult(intent)
|
|
270
|
+
return activity.startActivityForResult<T>(intent)
|
|
261
271
|
}
|
|
262
272
|
|
|
263
273
|
/**
|
|
@@ -296,7 +306,7 @@ export class Fragment extends EventTarget {
|
|
|
296
306
|
if (event.type === EventTypes.Intent.startActivityForResult) {
|
|
297
307
|
const info = event.detail as ActivityWithResultDetail
|
|
298
308
|
const result = await this.startActivityForResult(info.intent)
|
|
299
|
-
info.onResult(
|
|
309
|
+
info.onResult(result.result, result)
|
|
300
310
|
} else if (event.type === EventTypes.Intent.startActivity) {
|
|
301
311
|
await activity.startActivity(event.detail.intent)
|
|
302
312
|
} else {
|
|
@@ -85,7 +85,7 @@ export default class OrganizationSelector extends LitElement {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
protected override render(): unknown {
|
|
88
|
-
return html
|
|
88
|
+
return html`${this.renderTrigger()} ${this.renderMenu()}`
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
protected renderTrigger(): TemplateResult {
|
|
@@ -97,7 +97,7 @@ export default class OrganizationSelector extends LitElement {
|
|
|
97
97
|
aria-label="Current organization: ${orgName}"
|
|
98
98
|
trailingicon
|
|
99
99
|
color="tonal"
|
|
100
|
-
|
|
100
|
+
popovertarget="org-selector-menu"
|
|
101
101
|
>
|
|
102
102
|
${orgName}
|
|
103
103
|
<ui-icon slot="icon">arrow_drop_down</ui-icon>
|
|
@@ -131,6 +131,9 @@ export default class BaseButton extends UiElement {
|
|
|
131
131
|
/**
|
|
132
132
|
* Turns a `<ui-button>` element into a popover control button; takes the ID
|
|
133
133
|
* of the popover element to control as its value.
|
|
134
|
+
*
|
|
135
|
+
* Note: this is required for now as the spec only allows to control popovers
|
|
136
|
+
* via buttons. Custom elements are not allowed to control popovers.
|
|
134
137
|
* @attribute
|
|
135
138
|
*/
|
|
136
139
|
@property({ type: String, reflect: true }) accessor popoverTarget: string | undefined
|
|
@@ -146,7 +149,7 @@ export default class BaseButton extends UiElement {
|
|
|
146
149
|
* it will be shown; if the popover is showing, it will be hidden. If popoverTargetAction is omitted,
|
|
147
150
|
* "toggle" is the default action that will be performed by the control button.
|
|
148
151
|
*/
|
|
149
|
-
@property({ type: String, reflect: true }) accessor popoverTargetAction:
|
|
152
|
+
@property({ type: String, reflect: true }) accessor popoverTargetAction: 'hide' | 'show' | 'toggle' | undefined
|
|
150
153
|
/**
|
|
151
154
|
* When true, the focus ring effect will be constrained to the inside of the button's bounds.
|
|
152
155
|
* @attribute
|
|
@@ -312,6 +315,7 @@ export default class BaseButton extends UiElement {
|
|
|
312
315
|
} else if (action === 'show') {
|
|
313
316
|
element.showPopover()
|
|
314
317
|
} else {
|
|
318
|
+
// default to toggle
|
|
315
319
|
element.togglePopover()
|
|
316
320
|
}
|
|
317
321
|
element.focus()
|
|
@@ -56,9 +56,74 @@ export default class OrganizationService extends Service {
|
|
|
56
56
|
protected override async onCreate(): Promise<void> {
|
|
57
57
|
const url = new URL(window.location.href)
|
|
58
58
|
this.pendingOid = url.searchParams.get('oid')
|
|
59
|
+
this.revalidate()
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
/**
|
|
63
|
+
* @deprecated Use #setOrganization instead
|
|
64
|
+
*/
|
|
65
|
+
setupUserOrganization(org: IOrganization): Promise<void> {
|
|
66
|
+
return this.setOrganization(org)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Sets an org as the current user org.
|
|
71
|
+
* @param org The organization to add.
|
|
72
|
+
*/
|
|
73
|
+
async setOrganization(org: IOrganization): Promise<void> {
|
|
74
|
+
const hasOrg = this.organizations.some((i) => i.key === org.key)
|
|
75
|
+
if (!hasOrg) {
|
|
76
|
+
this.organizations.push(org)
|
|
77
|
+
}
|
|
78
|
+
const config = await services.get('config')
|
|
79
|
+
await config.local.set(CurrentOrganizationKey, org.key)
|
|
80
|
+
this.organizationId = org.key
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Adds an organization to the list of organizations.
|
|
85
|
+
* It also updated the cache. Used when creating a new org.
|
|
86
|
+
* @param org The organization to add.
|
|
87
|
+
*/
|
|
88
|
+
async addOrganization(org: IOrganization): Promise<void> {
|
|
89
|
+
const hasOrg = this.organizations.some((i) => i.key === org.key)
|
|
90
|
+
if (!hasOrg) {
|
|
91
|
+
this.organizations.push(org)
|
|
92
|
+
await this.#saveCache()
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Removes an organization from the list of organizations.
|
|
98
|
+
* It also updated the cache. Used when deleting an org.
|
|
99
|
+
* @param org The organization to remove.
|
|
100
|
+
*/
|
|
101
|
+
async removeOrganization(org: IOrganization): Promise<void> {
|
|
102
|
+
const idx = this.organizations.findIndex((i) => i.key === org.key)
|
|
103
|
+
if (idx !== -1) {
|
|
104
|
+
this.organizations.splice(idx, 1)
|
|
105
|
+
await this.#saveCache()
|
|
106
|
+
if (this.organizationId === org.key) {
|
|
107
|
+
this.organizationId = undefined
|
|
108
|
+
const config = await services.get('config')
|
|
109
|
+
await config.local.delete(CurrentOrganizationKey)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @deprecated Use #readOrganization instead
|
|
116
|
+
*/
|
|
117
|
+
getUserOrganization(id: string): Promise<IOrganization> {
|
|
118
|
+
return this.readOrganization(id)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Reads an organization from the list of organizations.
|
|
123
|
+
* @param id The organization ID.
|
|
124
|
+
* @returns The organization.
|
|
125
|
+
*/
|
|
126
|
+
async readOrganization(id: string): Promise<IOrganization> {
|
|
62
127
|
await this.#updateComplete
|
|
63
128
|
const org = this.organizations.find((i) => i.key === id)
|
|
64
129
|
if (!org) {
|
|
@@ -67,6 +132,10 @@ export default class OrganizationService extends Service {
|
|
|
67
132
|
return org
|
|
68
133
|
}
|
|
69
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Gets the current user organization.
|
|
137
|
+
* @returns The current organization or undefined if not set.
|
|
138
|
+
*/
|
|
70
139
|
async currentOrganization(): Promise<IOrganization | undefined> {
|
|
71
140
|
if (this.state === State.Checking) {
|
|
72
141
|
await this.#updateComplete
|
|
@@ -74,11 +143,14 @@ export default class OrganizationService extends Service {
|
|
|
74
143
|
if (!this.organizationId) {
|
|
75
144
|
return undefined
|
|
76
145
|
}
|
|
77
|
-
// TODO: we will store the orgs list in the local/session store
|
|
78
|
-
// so we won't be pinging server with every page reload.
|
|
79
146
|
return this.organizations.find((i) => i.key === this.organizationId)
|
|
80
147
|
}
|
|
81
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Checks if the user has a specific grant type in the current organization.
|
|
151
|
+
* @param grantType The grant type to check.
|
|
152
|
+
* @returns `true` if the user has the specified grant type, `false` otherwise.
|
|
153
|
+
*/
|
|
82
154
|
protected async hasGrantType(grantType: UserOrganizationGrantType): Promise<boolean> {
|
|
83
155
|
const org = await this.currentOrganization()
|
|
84
156
|
if (!org) {
|
|
@@ -87,6 +159,11 @@ export default class OrganizationService extends Service {
|
|
|
87
159
|
return org.grantType === grantType
|
|
88
160
|
}
|
|
89
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Checks if the user has any of the specified grant types in the current organization.
|
|
164
|
+
* @param types The grant types to check.
|
|
165
|
+
* @returns `true` if the user has any of the specified grant types, `false` otherwise.
|
|
166
|
+
*/
|
|
90
167
|
async hasRoles(types: UserOrganizationGrantType[]): Promise<boolean> {
|
|
91
168
|
const org = await this.currentOrganization()
|
|
92
169
|
if (!org) {
|
|
@@ -135,9 +212,6 @@ export default class OrganizationService extends Service {
|
|
|
135
212
|
if (this.state === State.Checking) {
|
|
136
213
|
return this.#updateComplete as Promise<CompletionState>
|
|
137
214
|
}
|
|
138
|
-
if (this.organizationId) {
|
|
139
|
-
return Promise.resolve(CompletionState.OK)
|
|
140
|
-
}
|
|
141
215
|
this.state = State.Checking
|
|
142
216
|
this.#updateComplete = new Promise<CompletionState>((resolver) => {
|
|
143
217
|
this.#updateResolver = resolver
|
|
@@ -175,12 +249,12 @@ export default class OrganizationService extends Service {
|
|
|
175
249
|
return false
|
|
176
250
|
}
|
|
177
251
|
|
|
178
|
-
async #saveCache(
|
|
252
|
+
async #saveCache(): Promise<void> {
|
|
179
253
|
const cache = await services.get('cache')
|
|
180
|
-
if (
|
|
254
|
+
if (this.organizations.length === 0) {
|
|
181
255
|
await cache.delete(OrgsCacheKey)
|
|
182
256
|
} else {
|
|
183
|
-
await cache.set<IOrganization[]>(OrgsCacheKey,
|
|
257
|
+
await cache.set<IOrganization[]>(OrgsCacheKey, this.organizations, cache.TTL.OneHour)
|
|
184
258
|
}
|
|
185
259
|
}
|
|
186
260
|
|
|
@@ -188,7 +262,7 @@ export default class OrganizationService extends Service {
|
|
|
188
262
|
const sdk = await services.get('sdk')
|
|
189
263
|
const orgs = await sdk.organizations.list()
|
|
190
264
|
this.organizations = orgs.items || []
|
|
191
|
-
await this.#saveCache(
|
|
265
|
+
await this.#saveCache()
|
|
192
266
|
}
|
|
193
267
|
|
|
194
268
|
#resolve(state: CompletionState): void {
|
|
@@ -220,19 +294,4 @@ export default class OrganizationService extends Service {
|
|
|
220
294
|
}
|
|
221
295
|
return result
|
|
222
296
|
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* When the user has no organizations, it set the organization as the only one
|
|
226
|
-
* and persists the organization id as the selected organization.
|
|
227
|
-
* @param org The organization to add.
|
|
228
|
-
*/
|
|
229
|
-
async setupUserOrganization(org: IOrganization): Promise<void> {
|
|
230
|
-
const hasOrg = this.organizations.some((i) => i.key === org.key)
|
|
231
|
-
if (!hasOrg) {
|
|
232
|
-
this.organizations.push(org)
|
|
233
|
-
}
|
|
234
|
-
const config = await services.get('config')
|
|
235
|
-
await config.local.set(CurrentOrganizationKey, org.key)
|
|
236
|
-
this.organizationId = org.key
|
|
237
|
-
}
|
|
238
297
|
}
|