@roomle/embedding-lib 0.0.0-dev-a40887e → 0.0.0-dev-9d077e2
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/configurator.d.ts +2 -0
- package/configurator.js +119 -0
- package/configurator.min.js +117 -0
- package/hi.js +40 -138
- package/hi.min.js +39 -122
- package/package.json +8 -1
- package/packages/embedding-lib/src/configurator-agent/chat-recovery.d.ts +9 -0
- package/packages/embedding-lib/src/configurator-agent/configurator-mcp-tools.d.ts +11 -0
- package/packages/embedding-lib/src/configurator-agent/configurator-planning-assistant.d.ts +8 -0
- package/packages/embedding-lib/src/configurator.d.ts +3 -0
- package/packages/embedding-lib/src/hi.d.ts +1 -3
- package/packages/embedding-lib/src/homag-intelligence/hi-mcp-tools.d.ts +11 -11
- package/packages/embedding-lib/src/homag-intelligence/hi-planning-assistant.d.ts +5 -41
- package/packages/embedding-lib/src/planning-assistant/chat-model.d.ts +33 -0
- package/packages/embedding-lib/src/{homag-intelligence/hi-mcp/mcp-tool.d.ts → planning-assistant/mcp-tools.d.ts} +10 -27
- package/packages/embedding-lib/src/planning-assistant/planning-assistant.d.ts +23 -0
- package/planning-assistant-BW9fNGZO.mjs +158 -0
- package/planning-assistant-CyVA2uUX.mjs +143 -0
- package/roomle-embedding-lib.js +64 -55
- package/roomle-embedding-lib.min.js +9 -0
- package/hi-mcp-server-B4BAKDmw.mjs +0 -5288
- package/hi-mcp-server-DxIvpJmq.mjs +0 -4879
- package/packages/embedding-lib/src/homag-intelligence/hi-mcp/agent-facing.d.ts +0 -35
- package/packages/embedding-lib/src/homag-intelligence/hi-mcp/hi-mcp-server.d.ts +0 -3
- package/packages/embedding-lib/src/homag-intelligence/hi-mcp/plan-history.d.ts +0 -33
- package/packages/embedding-lib/src/homag-intelligence/hi-mcp/planner-api.d.ts +0 -32
- package/packages/embedding-lib/src/homag-intelligence/hi-mcp/skills.d.ts +0 -23
- package/packages/embedding-lib/src/homag-intelligence/hi-mcp/tool-executors.d.ts +0 -4
- /package/{comlink-B2Kj_5Ge.mjs → comlink-BJnhpJa4.mjs} +0 -0
- /package/{comlink-B5lV2OWp.mjs → comlink-lSsogKgb.mjs} +0 -0
package/roomle-embedding-lib.js
CHANGED
|
@@ -97,6 +97,14 @@ var t = class {
|
|
|
97
97
|
}, c = /* @__PURE__ */ function(e) {
|
|
98
98
|
return e.BOTTOM_BAR = "bottom_bar", e.PARTLIST_BOUNDS = "partlist_bounds", e.INTERACTION_NOTES = "interaction_notes", e.PARAMETER_GROUPS = "parameter_groups", e;
|
|
99
99
|
}({}), l = (e) => JSON.parse(JSON.stringify(e)), u = {
|
|
100
|
+
staging: !1,
|
|
101
|
+
theme: "scandinavian",
|
|
102
|
+
density: "balanced",
|
|
103
|
+
light: "soft",
|
|
104
|
+
birdsEyeView: "adjust",
|
|
105
|
+
debug: !1,
|
|
106
|
+
stage: "alpha"
|
|
107
|
+
}, d = {
|
|
100
108
|
mobileLandscape: !0,
|
|
101
109
|
floorMaterialRootTag: "materials_root",
|
|
102
110
|
buttons: {
|
|
@@ -124,35 +132,36 @@ var t = class {
|
|
|
124
132
|
openCloseAnimation: !0,
|
|
125
133
|
localExport3d: !1
|
|
126
134
|
},
|
|
135
|
+
imageGenConfig: u,
|
|
127
136
|
configuratorZoom: !1,
|
|
128
137
|
rotationSnapDegrees: 10,
|
|
129
138
|
interactionsCollapsed: !1
|
|
130
|
-
},
|
|
139
|
+
}, f = (e) => (m(e), e?.customApiUrl && (e.customApiUrl = decodeURIComponent(e.customApiUrl)), e.shareUrl && (e.deeplink = e.shareUrl.replace(g, _)), e), p = (e) => e.trim() !== "" && Number.isFinite(Number(e.trim())), m = (e) => {
|
|
131
140
|
if (!e || typeof e != "object") return;
|
|
132
141
|
let t = Object.keys(e);
|
|
133
142
|
for (let n of t) {
|
|
134
143
|
let t = e[n];
|
|
135
|
-
if (!Array.isArray(t) && typeof t == "object" && t &&
|
|
136
|
-
t === "true" || t === "false" ? e[n] = t === "true" : typeof t == "string" &&
|
|
144
|
+
if (!Array.isArray(t) && typeof t == "object" && t && m(t), Array.isArray(t)) for (let e of t) m(e);
|
|
145
|
+
t === "true" || t === "false" ? e[n] = t === "true" : typeof t == "string" && p(t) && (e[n] = Number(t));
|
|
137
146
|
}
|
|
138
|
-
},
|
|
139
|
-
let t = l(
|
|
147
|
+
}, h = () => {
|
|
148
|
+
let t = l(d);
|
|
140
149
|
t.locale ||= e(null, !0), t.id === "(idle)" && delete t.id;
|
|
141
150
|
let n = i();
|
|
142
151
|
return n && a(n) && (t.configuratorId = "demoConfigurator"), t.customApiUrl = void 0, t.emails = !1, t;
|
|
143
|
-
},
|
|
152
|
+
}, g = "<CONF_ID>", _ = "#CONFIGURATIONID#", v = (e) => {
|
|
144
153
|
let t = e.featureFlags;
|
|
145
154
|
if (!t || typeof t != "object" || Array.isArray(t)) return;
|
|
146
155
|
let n = t;
|
|
147
156
|
"enableTwoLevelCatalog" in n && typeof n.enableTwoLevelCatalog == "boolean" && (e.enableTwoLevelCatalog === !1 || e.enableTwoLevelCatalog === void 0) && (e.enableTwoLevelCatalog = n.enableTwoLevelCatalog), "webGpu" in n && typeof n.webGpu == "boolean" && (e.webGpu === !1 || e.webGpu === void 0) && (e.webGpu = n.webGpu);
|
|
148
|
-
},
|
|
157
|
+
}, y = (e) => {
|
|
149
158
|
e.featureFlags ||= {}, typeof e.featureFlags.realPartList != "boolean" && (e.featureFlags.realPartList = !0), typeof e.featureFlags.globalCallbacks != "boolean" && (e.featureFlags.globalCallbacks = !0), typeof e.featureFlags.mocAr != "boolean" && (e.featureFlags.mocAr = !1);
|
|
150
|
-
},
|
|
151
|
-
let n = l(t), r =
|
|
152
|
-
|
|
159
|
+
}, b = (e, t) => {
|
|
160
|
+
let n = l(t), r = h();
|
|
161
|
+
y(r);
|
|
153
162
|
let i = o(o(r, e.settings || {}), n);
|
|
154
|
-
return i.configuratorId = e.id, !i.overrideTenant && e.tenant && (i.overrideTenant = e.tenant),
|
|
155
|
-
},
|
|
163
|
+
return i.configuratorId = e.id, !i.overrideTenant && e.tenant && (i.overrideTenant = e.tenant), v(i), i;
|
|
164
|
+
}, x = () => /(android)/i.test(navigator.userAgent), S = class {
|
|
156
165
|
_messageHandler = null;
|
|
157
166
|
isSetupDone = !1;
|
|
158
167
|
viewName = "main";
|
|
@@ -216,7 +225,7 @@ var t = class {
|
|
|
216
225
|
}
|
|
217
226
|
}));
|
|
218
227
|
}
|
|
219
|
-
},
|
|
228
|
+
}, C = () => {
|
|
220
229
|
let e, t;
|
|
221
230
|
return {
|
|
222
231
|
promise: new Promise((n, r) => {
|
|
@@ -229,7 +238,7 @@ var t = class {
|
|
|
229
238
|
//#endregion
|
|
230
239
|
//#region packages/common/src/utils/rapi-version.ts
|
|
231
240
|
(/* @__PURE__ */ "/items./items/{id}./items/{id}/renderings./items/{id}/conversions./items/{id}/3d-exports./items/{id}/perspectiveImage./items/{id}/perspectiveImageHD./items/{id}/topImage./items/{id}/tags./items/{id}/tagConnections./items/{id}/additionalContents./items/{id}/assetProcessings./items/{id}/3dAssets/{format}./items/{id}/3dAssets/{format}/{dummyFileName}./configurations./configurations/{id}./configurations/{id}/shares./configurations/{id}/renderings./configurations/{id}/3d-exports./configurations/{id}/3dAssets/{format}./configurations/{id}/3dAssets/{format}/{dummyFileName}./auth/signin./auth/signout./auth/reset./auth/reset/confirm./auth/change/pwd./auth/context./assets/analyze./version".split(".")).map((e) => RegExp("^" + e.replace(/\{[^}]+\}/g, "[^/]+") + "/?$"));
|
|
232
|
-
var
|
|
241
|
+
var w = /\/v[23]\/?$/, T = (e, t) => w.test(e) ? e.replace(w, "/v2") + t : e + t, E = {
|
|
233
242
|
SDK: "extended",
|
|
234
243
|
CONFIGURATOR_SDK: "configurator",
|
|
235
244
|
CALLBACKS: "callbacks",
|
|
@@ -237,22 +246,22 @@ var C = /\/v[23]\/?$/, w = (e, t) => C.test(e) ? e.replace(C, "/v2") + t : e + t
|
|
|
237
246
|
ANALYTICS: "analytics",
|
|
238
247
|
GLOBAL: "global",
|
|
239
248
|
RAPI: "rapi"
|
|
240
|
-
},
|
|
249
|
+
}, D = ".", O = E.SDK + "." + E.CALLBACKS, k = {
|
|
241
250
|
REQUEST_BOOT: "requestBoot",
|
|
242
251
|
SETUP: "setup",
|
|
243
252
|
WEBSITE_READY: "websiteReady"
|
|
244
|
-
},
|
|
253
|
+
}, A = {
|
|
245
254
|
GET_METHODS: "getMethods",
|
|
246
255
|
RETURN_METHODS: "returnMethods",
|
|
247
256
|
REGISTER_CUSTOM_VIEW: "registerCustomView",
|
|
248
257
|
REGISTER_CUSTOM_VIEW_DONE: "registerCustomViewDone"
|
|
249
|
-
},
|
|
258
|
+
}, j = ["constructor", "callbacks"], M = (e) => e[0] !== "_" && !j.includes(e), N = (e, t, n = !1) => {
|
|
250
259
|
if (!e) return [];
|
|
251
|
-
let r = Object.getOwnPropertyNames(e), i = n ?
|
|
252
|
-
return r.filter(
|
|
253
|
-
},
|
|
260
|
+
let r = Object.getOwnPropertyNames(e), i = n ? E.CALLBACKS + "." : "";
|
|
261
|
+
return r.filter(M).map((e) => t + "." + i + e);
|
|
262
|
+
}, P = async (e, t) => {
|
|
254
263
|
if (typeof e != "string") throw Error("Configurator ID is not a string type: \"" + typeof e + "\"");
|
|
255
|
-
let n = t.customApiUrl ? t.customApiUrl : "https://api.roomle.com/v2", r = t.overrideTenant || 9, i =
|
|
264
|
+
let n = t.customApiUrl ? t.customApiUrl : "https://api.roomle.com/v2", r = t.overrideTenant || 9, i = T(n, "/configurators/" + e), a = "03-" + window.btoa((/* @__PURE__ */ new Date()).toISOString() + ";anonymous;roomle_portal_v2"), o = new Request(i, {
|
|
256
265
|
method: "GET",
|
|
257
266
|
headers: new Headers({
|
|
258
267
|
apiKey: "roomle_portal_v2",
|
|
@@ -267,7 +276,7 @@ var C = /\/v[23]\/?$/, w = (e, t) => C.test(e) ? e.replace(C, "/v2") + t : e + t
|
|
|
267
276
|
cache: "default"
|
|
268
277
|
}), { configurator: s } = await (await fetch(o)).json();
|
|
269
278
|
return s;
|
|
270
|
-
},
|
|
279
|
+
}, F = "data-rml-interaction-container", I = (e, t, n) => {
|
|
271
280
|
let r = null;
|
|
272
281
|
Object.defineProperty(e, t, {
|
|
273
282
|
get() {
|
|
@@ -277,28 +286,28 @@ var C = /\/v[23]\/?$/, w = (e, t) => C.test(e) ? e.replace(C, "/v2") + t : e + t
|
|
|
277
286
|
e?.mute ? r = e.value : (console.warn("You override Roomle defined behaviour. To disalbe this warning pass in an object with the following properties"), console.warn("{ mute: true, value: () => void }"), r = e);
|
|
278
287
|
}
|
|
279
288
|
});
|
|
280
|
-
},
|
|
281
|
-
e && setTimeout(() => e.style.setProperty(
|
|
282
|
-
},
|
|
289
|
+
}, L = () => window.innerHeight * .01 + "px", R = (e) => {
|
|
290
|
+
e && setTimeout(() => e.style.setProperty(V, L()), 0);
|
|
291
|
+
}, z = "rml-styles", B = 450, V = "--rml-full-height", H = {
|
|
283
292
|
CONTAINER: "rml-container",
|
|
284
293
|
FILL: "rml-fill",
|
|
285
294
|
POSITION: "rml-pos",
|
|
286
295
|
TRANSITION: "rml-transition",
|
|
287
296
|
ANDROID_HEIGHT: "rml-android-height",
|
|
288
297
|
OVERFLOW_HIDDEN: "rml-overflow-hidden"
|
|
289
|
-
},
|
|
298
|
+
}, U = /* @__PURE__ */ new Map(), W = class extends S {
|
|
290
299
|
static createPlanner(e, t, n, r = []) {
|
|
291
300
|
return this._create(e, t, n, r);
|
|
292
301
|
}
|
|
293
302
|
static async connect(e, n = []) {
|
|
294
|
-
let r = new
|
|
303
|
+
let r = new S();
|
|
295
304
|
r.viewName = e;
|
|
296
|
-
let { resolve: i, promise: a } =
|
|
305
|
+
let { resolve: i, promise: a } = C(), { resolve: o, promise: s } = C(), c = new t("custom-view-" + e, window, window.parent, ({ message: e, args: t }) => {
|
|
297
306
|
switch (e) {
|
|
298
|
-
case
|
|
307
|
+
case A.REGISTER_CUSTOM_VIEW_DONE:
|
|
299
308
|
i();
|
|
300
309
|
break;
|
|
301
|
-
case
|
|
310
|
+
case A.RETURN_METHODS:
|
|
302
311
|
r.handleSetup(t[0]), o();
|
|
303
312
|
break;
|
|
304
313
|
default: if (r.isSetupDone) return r.executeMessage({
|
|
@@ -309,7 +318,7 @@ var C = /\/v[23]\/?$/, w = (e, t) => C.test(e) ? e.replace(C, "/v2") + t : e + t
|
|
|
309
318
|
});
|
|
310
319
|
r.setMessageHandler(c);
|
|
311
320
|
let l = [e];
|
|
312
|
-
return c.sendMessage(
|
|
321
|
+
return c.sendMessage(A.REGISTER_CUSTOM_VIEW, l), await a, c.sendMessage(A.GET_METHODS, l), await s, r.setupPlugins(n, document.body, "custom-view"), await Promise.allSettled(r.pluginsLoaded), r;
|
|
313
322
|
}
|
|
314
323
|
static createConfigurator(e, t, n, r = []) {
|
|
315
324
|
return this._create(e, t, n, r);
|
|
@@ -323,10 +332,10 @@ var C = /\/v[23]\/?$/, w = (e, t) => C.test(e) ? e.replace(C, "/v2") + t : e + t
|
|
|
323
332
|
static async _create(e, t, n, r) {
|
|
324
333
|
return new Promise(async (i, a) => {
|
|
325
334
|
try {
|
|
326
|
-
let a =
|
|
327
|
-
|
|
328
|
-
let c = await
|
|
329
|
-
n =
|
|
335
|
+
let a = f(n), s = h();
|
|
336
|
+
y(s);
|
|
337
|
+
let c = await P(e, o(s, a));
|
|
338
|
+
n = b(c, a);
|
|
330
339
|
let l = new this(c, t, n, r, i);
|
|
331
340
|
return await Promise.allSettled(l.pluginsLoaded), l;
|
|
332
341
|
} catch (e) {
|
|
@@ -341,51 +350,51 @@ var C = /\/v[23]\/?$/, w = (e, t) => C.test(e) ? e.replace(C, "/v2") + t : e + t
|
|
|
341
350
|
_iframe;
|
|
342
351
|
constructor(e, n, r, i, a) {
|
|
343
352
|
if (super(), !e || typeof e.id != "string") throw Error("Please provide a correct configuratorId, you get the correct ID from your Roomle Contact Person");
|
|
344
|
-
if (
|
|
345
|
-
if (!document.getElementById(
|
|
353
|
+
if (U.has(n)) throw Error("There is already an instance on this DOM element");
|
|
354
|
+
if (!document.getElementById(z)) {
|
|
346
355
|
let e = r.zIndex || 9999999, t = document.createElement("style");
|
|
347
|
-
t.type = "text/css", t.id =
|
|
348
|
-
let n = "transition:all ease-in-out " +
|
|
356
|
+
t.type = "text/css", t.id = z;
|
|
357
|
+
let n = "transition:all ease-in-out " + B + "ms;", i = ["-webkit-", "-o-"].reduce((e, t) => e += t + n, "") + n, a = L();
|
|
349
358
|
t.innerHTML = `
|
|
350
|
-
.${
|
|
351
|
-
.${
|
|
352
|
-
.${
|
|
353
|
-
.${
|
|
354
|
-
.${
|
|
355
|
-
.${
|
|
359
|
+
.${H.CONTAINER}{${V}:${a};}
|
|
360
|
+
.${H.POSITION}{position:fixed;top:0;left:0;z-index:${e};opacity:0}
|
|
361
|
+
.${H.TRANSITION}{${i}}
|
|
362
|
+
.${H.FILL}{width:100%;height:100%;opacity:1}
|
|
363
|
+
.${H.ANDROID_HEIGHT}{height:calc(var(${V},1vh)*100)}
|
|
364
|
+
.${H.OVERFLOW_HIDDEN}{overflow:hidden}
|
|
356
365
|
`, document.head.appendChild(t);
|
|
357
366
|
}
|
|
358
367
|
this._executeMessage = this._executeMessage.bind(this);
|
|
359
368
|
let o = new t("website", window, null, this._executeMessage);
|
|
360
|
-
this.setMessageHandler(o), this._onResize = this._onResize.bind(this),
|
|
369
|
+
this.setMessageHandler(o), this._onResize = this._onResize.bind(this), x() && window.addEventListener("resize", this._onResize), this._container = n, this._initData = r, this._configuratorSettings = e;
|
|
361
370
|
let s = this._createIframe();
|
|
362
|
-
this._onUseFullPage = this._onUseFullPage.bind(this), this._onBackToWebsite = this._onBackToWebsite.bind(this), this._waitForIframe = a, this._container.appendChild(s), this._iframe = s, this.setupPlugins(i, this._iframe),
|
|
371
|
+
this._onUseFullPage = this._onUseFullPage.bind(this), this._onBackToWebsite = this._onBackToWebsite.bind(this), this._waitForIframe = a, this._container.appendChild(s), this._iframe = s, this.setupPlugins(i, this._iframe), U.set(n, !0);
|
|
363
372
|
}
|
|
364
373
|
teardown() {
|
|
365
|
-
this._container &&
|
|
374
|
+
this._container && U.delete(this._container);
|
|
366
375
|
let e = this._container.querySelector("iframe");
|
|
367
376
|
e && this._container.removeChild(e), window.removeEventListener("resize", this._onResize);
|
|
368
377
|
}
|
|
369
378
|
_createIframe() {
|
|
370
379
|
let e = document.createElement("iframe"), t = this._configuratorSettings?.url || "https://www.roomle.com/t/cp/";
|
|
371
|
-
return this._initData.useLocalRoomle && (t = location.href.replace("embedding.html", "")), location.href.includes("roomle.gitlab.io") && (t = location.href.replace("embedding.html", "index.html")), this._initData.overrideServerUrl && (t = this._initData.overrideServerUrl), e.src = t, e.allow = "microphone", e.classList.add(
|
|
380
|
+
return this._initData.useLocalRoomle && (t = location.href.replace("embedding.html", "")), location.href.includes("roomle.gitlab.io") && (t = location.href.replace("embedding.html", "index.html")), this._initData.overrideServerUrl && (t = this._initData.overrideServerUrl), e.src = t, e.allow = "microphone", e.classList.add(H.CONTAINER), e.classList.add(H.FILL), e;
|
|
372
381
|
}
|
|
373
382
|
_onResize() {
|
|
374
|
-
|
|
383
|
+
R(this._iframe);
|
|
375
384
|
}
|
|
376
385
|
_onUseFullPage() {
|
|
377
|
-
this._iframe.classList.add(
|
|
386
|
+
this._iframe.classList.add(H.POSITION), document.documentElement.classList.add(H.OVERFLOW_HIDDEN), window.document.body.classList.add(H.OVERFLOW_HIDDEN), x() && (R(this._iframe), this._iframe.classList.add(H.ANDROID_HEIGHT));
|
|
378
387
|
}
|
|
379
388
|
_onBackToWebsite() {
|
|
380
|
-
this._iframe.classList.remove(
|
|
389
|
+
this._iframe.classList.remove(H.POSITION), this._iframe.classList.remove(H.ANDROID_HEIGHT), document.documentElement.classList.remove(H.OVERFLOW_HIDDEN), window.document.body.classList.remove(H.OVERFLOW_HIDDEN);
|
|
381
390
|
}
|
|
382
391
|
_executeMessage({ message: e, args: t }, n) {
|
|
383
|
-
if (n.source && n.source === this._iframe?.contentWindow) return e ===
|
|
392
|
+
if (n.source && n.source === this._iframe?.contentWindow) return e === k.REQUEST_BOOT ? this._messageHandler ? (this._messageHandler.setOutgoingMessageBus(n.source), Promise.resolve({ result: this._initData })) : (console.error("MessageHandler not set"), Promise.resolve({ error: "MessageHandler not set" })) : e === k.SETUP ? (this.handleSetup(t[0]), I(this.ui.callbacks, "onUseFullPage", this._onUseFullPage), I(this.ui.callbacks, "onBackToWebsite", this._onBackToWebsite), this._waitForIframe(this), setTimeout(() => {
|
|
384
393
|
if (!this._messageHandler) {
|
|
385
394
|
console.error("MessageHandler not set");
|
|
386
395
|
return;
|
|
387
396
|
}
|
|
388
|
-
this._messageHandler.sendMessage(
|
|
397
|
+
this._messageHandler.sendMessage(k.WEBSITE_READY);
|
|
389
398
|
}, 0), Promise.resolve({ result: null })) : this.executeMessage({
|
|
390
399
|
message: e,
|
|
391
400
|
args: t
|
|
@@ -393,4 +402,4 @@ var C = /\/v[23]\/?$/, w = (e, t) => C.test(e) ? e.replace(C, "/v2") + t : e + t
|
|
|
393
402
|
}
|
|
394
403
|
};
|
|
395
404
|
//#endregion
|
|
396
|
-
export {
|
|
405
|
+
export { k as HANDSHAKE_MESSAGES, F as INTERACTION_CONTAINER_SELECTOR, E as NAMESPACE, D as NAMESPACE_SEPARATOR, O as SDK_CALLBACK, A as WELL_KNOWN_MESSAGES, W as default, P as getConfiguratorSettings, N as getMethodNames, M as isExposable };
|
|
@@ -102,6 +102,15 @@ var t = class {
|
|
|
102
102
|
openCloseAnimation: !0,
|
|
103
103
|
localExport3d: !1
|
|
104
104
|
},
|
|
105
|
+
imageGenConfig: {
|
|
106
|
+
staging: !1,
|
|
107
|
+
theme: "scandinavian",
|
|
108
|
+
density: "balanced",
|
|
109
|
+
light: "soft",
|
|
110
|
+
birdsEyeView: "adjust",
|
|
111
|
+
debug: !1,
|
|
112
|
+
stage: "alpha"
|
|
113
|
+
},
|
|
105
114
|
configuratorZoom: !1,
|
|
106
115
|
rotationSnapDegrees: 10,
|
|
107
116
|
interactionsCollapsed: !1
|