@neurodyn/room-viewer 0.0.33 → 0.1.1
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/contract.d.ts +168 -0
- package/dist/contract.js +230 -0
- package/dist/index.d.ts +1 -0
- package/dist/room-viewer.embed.min.js +94 -499
- package/dist/room-viewer.global.min.js +94 -499
- package/dist/room-viewer.js +185 -25
- package/package.json +10 -3
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
import type { RoomViewerDialog } from '@neurodyn/react-room-viewer';
|
|
3
|
+
import type { RoomViewerResourceType } from '@neurodyn/react-room-viewer';
|
|
4
|
+
import type { RoomViewerResourceTypeInput } from '@neurodyn/react-room-viewer';
|
|
5
|
+
|
|
6
|
+
/** What the reader hands a parser besides the raw value. */
|
|
7
|
+
declare interface AttributeParseContext {
|
|
8
|
+
/** Full attribute name, e.g. `data-neurodyn-room-viewer-api-key`. */
|
|
9
|
+
attribute: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Whether an attribute must be set: always, never, or only while another
|
|
14
|
+
* attribute holds one of the listed values (`src` while `type` is `wallart`).
|
|
15
|
+
* `when` names a field of the same contract.
|
|
16
|
+
*/
|
|
17
|
+
declare type AttributeRequirement
|
|
18
|
+
= | boolean
|
|
19
|
+
| { readonly when: string, readonly is: readonly string[] }
|
|
20
|
+
|
|
21
|
+
/** The tag that loads the self-initializing embed build from `src`. */
|
|
22
|
+
export declare function buildEmbedScript(src: string): string;
|
|
23
|
+
|
|
24
|
+
/** A copy-paste trigger button with the given attributes, in contract order and escaped. */
|
|
25
|
+
export declare function buildRoomViewerTrigger(values: RoomViewerTriggerValues, options: TriggerSnippetOptions): string;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The rows describing a trigger built from the same values, for the table
|
|
29
|
+
* beside the snippet; `extra` adds attributes the snippet leaves out.
|
|
30
|
+
*/
|
|
31
|
+
export declare function describeRoomViewerTrigger(values: RoomViewerTriggerValues, extra?: readonly (keyof RoomViewerTriggerValues)[]): RoomViewerTriggerRow[];
|
|
32
|
+
|
|
33
|
+
/** A widget option that has no attribute: callbacks, controlled state, sessions. */
|
|
34
|
+
declare interface OptionOnly {
|
|
35
|
+
readonly kind: 'option'
|
|
36
|
+
/** Why the option is programmatic only. */
|
|
37
|
+
readonly reason: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** What an attribute's parser produces. */
|
|
41
|
+
declare type ParsedAttributeValue<Field> = Field extends TriggerAttribute<infer Value> ? Value : never
|
|
42
|
+
|
|
43
|
+
export declare const ROOM_VIEWER_DIMENSION_UNITS: readonly ["m", "cm", "in"];
|
|
44
|
+
|
|
45
|
+
export declare const ROOM_VIEWER_DISPLAY_UNITS: readonly ["cm", "in"];
|
|
46
|
+
|
|
47
|
+
export declare const ROOM_VIEWER_EMBED_SCRIPT_URL = "https://cdn.neurodyn.ai/@neurodyn/room-viewer/dist/room-viewer.embed.min.js";
|
|
48
|
+
|
|
49
|
+
export declare const ROOM_VIEWER_LANGUAGES: readonly ["en", "ru"];
|
|
50
|
+
|
|
51
|
+
export declare const ROOM_VIEWER_RESOURCE_TYPES: readonly ["wallart", "rugs", "flooring", "wall-panel"];
|
|
52
|
+
|
|
53
|
+
export declare const ROOM_VIEWER_SOURCES: readonly ["default", "shopify"];
|
|
54
|
+
|
|
55
|
+
/** The wall-art trigger the docs and Connect show as an example. */
|
|
56
|
+
export declare const ROOM_VIEWER_TRIGGER_EXAMPLE: {
|
|
57
|
+
readonly type: "wallart";
|
|
58
|
+
readonly src: "https://placehold.co/800x1200.png";
|
|
59
|
+
readonly name: "Sample Print";
|
|
60
|
+
readonly width: 1;
|
|
61
|
+
readonly height: 1.5;
|
|
62
|
+
readonly apiKey: "...";
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export declare const roomViewerContract: TriggerContract<RoomViewerDialogProps, {
|
|
66
|
+
readonly type: TriggerAttribute<RoomViewerResourceTypeInput | undefined>;
|
|
67
|
+
readonly src: TriggerAttribute<string | undefined>;
|
|
68
|
+
readonly tinuuid: TriggerAttribute<string | undefined>;
|
|
69
|
+
readonly roomId: TriggerAttribute<string | undefined>;
|
|
70
|
+
readonly name: TriggerAttribute<string | undefined>;
|
|
71
|
+
readonly width: TriggerAttribute<number | undefined>;
|
|
72
|
+
readonly height: TriggerAttribute<number | undefined>;
|
|
73
|
+
readonly dimensionUnit: TriggerAttribute<"m" | "cm" | "in" | undefined>;
|
|
74
|
+
readonly displayUnit: TriggerAttribute<"cm" | "in" | undefined>;
|
|
75
|
+
readonly language: TriggerAttribute<"en" | "ru" | undefined>;
|
|
76
|
+
readonly apiKey: TriggerAttribute<string | undefined>;
|
|
77
|
+
readonly source: TriggerAttribute<"default" | "shopify" | undefined>;
|
|
78
|
+
readonly disableClose: TriggerAttribute<boolean>;
|
|
79
|
+
readonly widgetSession: OptionOnly;
|
|
80
|
+
readonly open: OptionOnly;
|
|
81
|
+
readonly defaultOpen: OptionOnly;
|
|
82
|
+
readonly onOpenChange: OptionOnly;
|
|
83
|
+
}>;
|
|
84
|
+
|
|
85
|
+
/** Options of the room viewer dialog. */
|
|
86
|
+
export declare interface RoomViewerDialogProps extends Omit<ComponentProps<typeof RoomViewerDialog>, 'rooms'> {
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export declare type RoomViewerTriggerRow = TriggerSnippetRow<keyof RoomViewerTriggerValues>;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Values for a copy-paste trigger. Snippets only use canonical Resource Types:
|
|
93
|
+
* the legacy aliases keep working on merchant pages but are never generated.
|
|
94
|
+
*/
|
|
95
|
+
export declare type RoomViewerTriggerValues = Omit<TriggerSnippetValues<typeof roomViewerContract.fields>, 'type'> & {
|
|
96
|
+
type?: RoomViewerResourceType;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/** A widget option a merchant can set with a `data-*` attribute on the trigger. */
|
|
100
|
+
declare interface TriggerAttribute<Value> {
|
|
101
|
+
readonly kind: 'attribute'
|
|
102
|
+
/** Attribute name without the widget prefix, e.g. `api-key`. */
|
|
103
|
+
readonly name: string
|
|
104
|
+
readonly parse: (raw: string | null, context: AttributeParseContext) => Value
|
|
105
|
+
/** The accepted values, listed in the docs. */
|
|
106
|
+
readonly values?: readonly string[]
|
|
107
|
+
/** What the docs and the cabinets' snippets ask for; the reader does not enforce it. */
|
|
108
|
+
readonly required: AttributeRequirement
|
|
109
|
+
/** The default as Markdown for the docs, e.g. `` `cm` `` or `User locale`. */
|
|
110
|
+
readonly defaultValue?: string
|
|
111
|
+
/** One or two sentences for the docs; accepted values are appended from `values`. */
|
|
112
|
+
readonly description: string
|
|
113
|
+
/** Full attribute names read, in order, while the primary one is absent or blank. */
|
|
114
|
+
readonly legacyNames?: readonly string[]
|
|
115
|
+
/** Runs an explicitly passed programmatic option through `parse` as well. */
|
|
116
|
+
readonly normalizesOptions?: boolean
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Keys of the fields that are attributes. */
|
|
120
|
+
declare type TriggerAttributeKey<Fields> = {
|
|
121
|
+
[Key in keyof Fields]: Fields[Key] extends TriggerAttribute<any> ? Key : never
|
|
122
|
+
}[keyof Fields] & string
|
|
123
|
+
|
|
124
|
+
declare interface TriggerContract<Options, Fields extends TriggerFields<Options> = TriggerFields<Options>> {
|
|
125
|
+
/** Prefix shared by the widget's option attributes, e.g. `data-neurodyn-room-viewer-`. */
|
|
126
|
+
readonly prefix: string
|
|
127
|
+
/** The first marker is the one snippets use. */
|
|
128
|
+
readonly markers: readonly [TriggerMarker, ...TriggerMarker[]]
|
|
129
|
+
readonly fields: Fields
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* One entry per widget option, none optional: an option added to the widget's
|
|
134
|
+
* props without an entry here fails type-checking until it is given an
|
|
135
|
+
* attribute or marked option-only.
|
|
136
|
+
*/
|
|
137
|
+
declare type TriggerFields<Options> = {
|
|
138
|
+
readonly [Key in keyof Options]-?: TriggerAttribute<Options[Key] | undefined> | OptionOnly
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** A marker attribute that selects trigger elements, e.g. `data-neurodyn-room-viewer-dialog`. */
|
|
142
|
+
declare interface TriggerMarker {
|
|
143
|
+
readonly name: string
|
|
144
|
+
readonly description: string
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export declare interface TriggerSnippetOptions {
|
|
148
|
+
/** The button text. */
|
|
149
|
+
label: string
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** One attribute of a snippet, for the table next to it. */
|
|
153
|
+
declare interface TriggerSnippetRow<Key extends string> {
|
|
154
|
+
/** The field key, or `null` for the marker attribute. */
|
|
155
|
+
key: Key | null
|
|
156
|
+
attribute: string
|
|
157
|
+
required: boolean
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Values for a copy-paste trigger: only the contract's attributes, each typed
|
|
162
|
+
* as its option. Placeholders such as `PUBLIC_WIDGET_KEY` fit string options.
|
|
163
|
+
*/
|
|
164
|
+
declare type TriggerSnippetValues<Fields> = {
|
|
165
|
+
[Key in TriggerAttributeKey<Fields>]?: Exclude<ParsedAttributeValue<Fields[Key]>, undefined | null>
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export { }
|
package/dist/contract.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
function contract_attribute(name, parser, details) {
|
|
2
|
+
return {
|
|
3
|
+
kind: 'attribute',
|
|
4
|
+
name,
|
|
5
|
+
parse: parser.parse,
|
|
6
|
+
values: parser.values,
|
|
7
|
+
...details
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function optionOnly(reason) {
|
|
11
|
+
return {
|
|
12
|
+
kind: 'option',
|
|
13
|
+
reason
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function defineTriggerContract() {
|
|
17
|
+
return (contract)=>contract;
|
|
18
|
+
}
|
|
19
|
+
function contract_attributeEntries(contract) {
|
|
20
|
+
return Object.entries(contract.fields).filter((entry)=>'attribute' === entry[1].kind);
|
|
21
|
+
}
|
|
22
|
+
function escapeHtml(value) {
|
|
23
|
+
return value.replaceAll('&', '&').replaceAll('"', '"').replaceAll('<', '<').replaceAll('>', '>');
|
|
24
|
+
}
|
|
25
|
+
function isAttributeRequired(requirement, values) {
|
|
26
|
+
if ('boolean' == typeof requirement) return requirement;
|
|
27
|
+
const current = values[requirement.when];
|
|
28
|
+
return null != current && requirement.is.includes(String(current));
|
|
29
|
+
}
|
|
30
|
+
function snippetEntries(contract, values) {
|
|
31
|
+
return contract_attributeEntries(contract).flatMap(([key, field])=>{
|
|
32
|
+
const value = values[key];
|
|
33
|
+
return null == value ? [] : [
|
|
34
|
+
{
|
|
35
|
+
key,
|
|
36
|
+
field,
|
|
37
|
+
value
|
|
38
|
+
}
|
|
39
|
+
];
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function snippet_buildTriggerSnippet(contract, values, { label }) {
|
|
43
|
+
const lines = [
|
|
44
|
+
'<button',
|
|
45
|
+
' type="button"',
|
|
46
|
+
` ${contract.markers[0].name}`
|
|
47
|
+
];
|
|
48
|
+
for (const { field, value } of snippetEntries(contract, values))lines.push(` ${contract.prefix}${field.name}="${escapeHtml(String(value))}"`);
|
|
49
|
+
lines.push('>', ` ${escapeHtml(label)}`, '</button>');
|
|
50
|
+
return lines.join('\n');
|
|
51
|
+
}
|
|
52
|
+
function describeTriggerSnippet(contract, values, extra = []) {
|
|
53
|
+
const present = snippetEntries(contract, values);
|
|
54
|
+
const presentKeys = new Set(present.map(({ key })=>key));
|
|
55
|
+
const fields = contract.fields;
|
|
56
|
+
const row = (key, field)=>({
|
|
57
|
+
key,
|
|
58
|
+
attribute: `${contract.prefix}${field.name}`,
|
|
59
|
+
required: isAttributeRequired(field.required, values)
|
|
60
|
+
});
|
|
61
|
+
return [
|
|
62
|
+
{
|
|
63
|
+
key: null,
|
|
64
|
+
attribute: contract.markers[0].name,
|
|
65
|
+
required: true
|
|
66
|
+
},
|
|
67
|
+
...present.map(({ key, field })=>row(key, field)),
|
|
68
|
+
...extra.filter((key)=>!presentKeys.has(key)).map((key)=>row(key, fields[key]))
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
function snippet_buildEmbedScript(src) {
|
|
72
|
+
return `<script defer src="${escapeHtml(src)}"></script>`;
|
|
73
|
+
}
|
|
74
|
+
function parsers_text() {
|
|
75
|
+
return {
|
|
76
|
+
parse: (raw)=>raw ?? void 0
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function number() {
|
|
80
|
+
return {
|
|
81
|
+
parse: (raw)=>raw ? Number(raw) : void 0
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function flag() {
|
|
85
|
+
return {
|
|
86
|
+
parse: (raw)=>'true' === raw
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function oneOf(values) {
|
|
90
|
+
return {
|
|
91
|
+
parse: (raw)=>values.includes(raw ?? '') ? raw : void 0,
|
|
92
|
+
values
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function unchecked(values) {
|
|
96
|
+
return {
|
|
97
|
+
parse: (raw)=>raw ?? void 0,
|
|
98
|
+
values
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function listOf() {
|
|
102
|
+
return (values)=>values;
|
|
103
|
+
}
|
|
104
|
+
const ROOM_VIEWER_TRIGGER_ATTRIBUTE = 'data-neurodyn-room-viewer-dialog';
|
|
105
|
+
const ROOM_VIEWER_RESOURCE_TYPES = listOf()([
|
|
106
|
+
'wallart',
|
|
107
|
+
'rugs',
|
|
108
|
+
'flooring',
|
|
109
|
+
'wall-panel'
|
|
110
|
+
]);
|
|
111
|
+
const ROOM_VIEWER_DIMENSION_UNITS = listOf()([
|
|
112
|
+
'm',
|
|
113
|
+
'cm',
|
|
114
|
+
'in'
|
|
115
|
+
]);
|
|
116
|
+
const ROOM_VIEWER_DISPLAY_UNITS = listOf()([
|
|
117
|
+
'cm',
|
|
118
|
+
'in'
|
|
119
|
+
]);
|
|
120
|
+
const ROOM_VIEWER_LANGUAGES = listOf()([
|
|
121
|
+
'en',
|
|
122
|
+
'ru'
|
|
123
|
+
]);
|
|
124
|
+
const ROOM_VIEWER_SOURCES = listOf()([
|
|
125
|
+
'default',
|
|
126
|
+
'shopify'
|
|
127
|
+
]);
|
|
128
|
+
const ROOM_VIEWER_EMBED_SCRIPT_URL = 'https://cdn.neurodyn.ai/@neurodyn/room-viewer/dist/room-viewer.embed.min.js';
|
|
129
|
+
const roomViewerContract = defineTriggerContract()({
|
|
130
|
+
prefix: 'data-neurodyn-room-viewer-',
|
|
131
|
+
markers: [
|
|
132
|
+
{
|
|
133
|
+
name: ROOM_VIEWER_TRIGGER_ATTRIBUTE,
|
|
134
|
+
description: 'Marks an element for the embed build.'
|
|
135
|
+
}
|
|
136
|
+
],
|
|
137
|
+
fields: {
|
|
138
|
+
type: contract_attribute('type', unchecked(ROOM_VIEWER_RESOURCE_TYPES), {
|
|
139
|
+
required: true,
|
|
140
|
+
description: 'Resource type.'
|
|
141
|
+
}),
|
|
142
|
+
src: contract_attribute('src', parsers_text(), {
|
|
143
|
+
required: {
|
|
144
|
+
when: 'type',
|
|
145
|
+
is: [
|
|
146
|
+
'wallart'
|
|
147
|
+
]
|
|
148
|
+
},
|
|
149
|
+
description: 'Wall-art image URL.'
|
|
150
|
+
}),
|
|
151
|
+
tinuuid: contract_attribute('tinuuid', parsers_text(), {
|
|
152
|
+
required: {
|
|
153
|
+
when: 'type',
|
|
154
|
+
is: [
|
|
155
|
+
'rugs',
|
|
156
|
+
'flooring',
|
|
157
|
+
'wall-panel'
|
|
158
|
+
]
|
|
159
|
+
},
|
|
160
|
+
description: 'The object\'s tinuuid.'
|
|
161
|
+
}),
|
|
162
|
+
roomId: contract_attribute('room-id', parsers_text(), {
|
|
163
|
+
required: false,
|
|
164
|
+
description: 'Initial Room ID from the available catalog. Skips the initial interior chooser; unavailable Rooms open the chooser. Empty values do nothing.'
|
|
165
|
+
}),
|
|
166
|
+
name: contract_attribute('name', parsers_text(), {
|
|
167
|
+
required: true,
|
|
168
|
+
description: 'Title shown in the dialog header.'
|
|
169
|
+
}),
|
|
170
|
+
width: contract_attribute('width', number(), {
|
|
171
|
+
required: false,
|
|
172
|
+
description: 'Wall-art width. Used when `type` is `wallart`.'
|
|
173
|
+
}),
|
|
174
|
+
height: contract_attribute('height', number(), {
|
|
175
|
+
required: false,
|
|
176
|
+
description: 'Wall-art height. Used when `type` is `wallart`.'
|
|
177
|
+
}),
|
|
178
|
+
dimensionUnit: contract_attribute('dimension-unit', oneOf(ROOM_VIEWER_DIMENSION_UNITS), {
|
|
179
|
+
required: false,
|
|
180
|
+
defaultValue: '`m`',
|
|
181
|
+
description: 'Unit used by wall-art width and height.'
|
|
182
|
+
}),
|
|
183
|
+
displayUnit: contract_attribute('display-unit', oneOf(ROOM_VIEWER_DISPLAY_UNITS), {
|
|
184
|
+
required: false,
|
|
185
|
+
defaultValue: '`cm`',
|
|
186
|
+
description: 'Unit used to display UI measurements.'
|
|
187
|
+
}),
|
|
188
|
+
language: contract_attribute('language', oneOf(ROOM_VIEWER_LANGUAGES), {
|
|
189
|
+
required: false,
|
|
190
|
+
defaultValue: 'User locale',
|
|
191
|
+
description: 'UI language override.'
|
|
192
|
+
}),
|
|
193
|
+
apiKey: contract_attribute('api-key', parsers_text(), {
|
|
194
|
+
required: true,
|
|
195
|
+
description: 'Public API key sent as `x-api-key`. Not needed when `source` is `shopify`.'
|
|
196
|
+
}),
|
|
197
|
+
source: contract_attribute('source', oneOf(ROOM_VIEWER_SOURCES), {
|
|
198
|
+
required: false,
|
|
199
|
+
defaultValue: '`default`',
|
|
200
|
+
description: 'Backend source mode.'
|
|
201
|
+
}),
|
|
202
|
+
disableClose: contract_attribute('disable-close', flag(), {
|
|
203
|
+
required: false,
|
|
204
|
+
defaultValue: '`false`',
|
|
205
|
+
description: 'Set to `true` to hide close controls and prevent dismissal.'
|
|
206
|
+
}),
|
|
207
|
+
widgetSession: optionOnly('An existing Widget Session handed over by an embedding surface such as Shopify.'),
|
|
208
|
+
open: optionOnly('The embed build owns the open state.'),
|
|
209
|
+
defaultOpen: optionOnly('The embed build owns the open state.'),
|
|
210
|
+
onOpenChange: optionOnly('The embed build owns the open state.')
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
const ROOM_VIEWER_TRIGGER_EXAMPLE = {
|
|
214
|
+
type: 'wallart',
|
|
215
|
+
src: 'https://placehold.co/800x1200.png',
|
|
216
|
+
name: 'Sample Print',
|
|
217
|
+
width: 1,
|
|
218
|
+
height: 1.5,
|
|
219
|
+
apiKey: '...'
|
|
220
|
+
};
|
|
221
|
+
function buildRoomViewerTrigger(values, options) {
|
|
222
|
+
return snippet_buildTriggerSnippet(roomViewerContract, values, options);
|
|
223
|
+
}
|
|
224
|
+
function describeRoomViewerTrigger(values, extra = []) {
|
|
225
|
+
return describeTriggerSnippet(roomViewerContract, values, extra);
|
|
226
|
+
}
|
|
227
|
+
function contract_buildEmbedScript(src) {
|
|
228
|
+
return snippet_buildEmbedScript(src);
|
|
229
|
+
}
|
|
230
|
+
export { ROOM_VIEWER_DIMENSION_UNITS, ROOM_VIEWER_DISPLAY_UNITS, ROOM_VIEWER_EMBED_SCRIPT_URL, ROOM_VIEWER_LANGUAGES, ROOM_VIEWER_RESOURCE_TYPES, ROOM_VIEWER_SOURCES, ROOM_VIEWER_TRIGGER_EXAMPLE, contract_buildEmbedScript as buildEmbedScript, buildRoomViewerTrigger, describeRoomViewerTrigger, roomViewerContract };
|
package/dist/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export declare class RoomViewerDialog {
|
|
|
14
14
|
declare interface RoomViewerDialogOptions extends Partial<RoomViewerDialogProps> {
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/** Options of the room viewer dialog. */
|
|
17
18
|
declare interface RoomViewerDialogProps extends Omit<ComponentProps<typeof RoomViewerDialog_2>, 'rooms'> {
|
|
18
19
|
}
|
|
19
20
|
|