1const True = true; 2const False = false; 3const None = undefined; 4 5let app = undefined; 6let mounted_app = undefined; 7 8const loaded_libraries = new Set(); 9const loaded_components = new Set(); 10 11function parseElements(raw_elements) { 12 return JSON.parse( 13 raw_elements 14 .replace(/$/g, "$") 15 .replace(/`/g, "`") 16 .replace(/>/g, ">") 17 .replace(/</g, "<") 18 .replace(/&/g, "&") 19 ); 20} 21 22function replaceUndefinedAttributes(element) { 23 element.class ??= []; 24 element.style ??= {}; 25 element.props ??= {}; 26 element.text ??= null; 27 element.events ??= []; 28 element.update_method ??= null; 29 element.component ??= null; 30 element.libraries ??= []; 31 element.slots = { 32 default: { ids: element.children || [] }, 33 ...(element.slots ?? {}), 34 }; 35} 36 37function getElement(id) { 38 const _id = id instanceof Element ? id.id.slice(1) : id; 39 return mounted_app.$refs["r" + _id]; 40} 41 42function getHtmlElement(id) { 43 let id_as_a_string = id.toString(); 44 if (!id_as_a_string.startsWith("c")) { 45 id_as_a_string = "c" + id_as_a_string; 46 } 47 return document.getElementById(id_as_a_string); 48} 49 50function runMethod(target, method_name, args) { 51 if (typeof target === "object") { 52 if (method_name in target) { 53 return target[method_name](...args); 54 } else { 55 return eval(method_name)(target, ...args); 56 } 57 } 58 const element = getElement(target); 59 if (element === null || element === undefined) return; 60 if (method_name in element) { 61 return element[method_name](...args); 62 } else if (method_name in (element.$refs.qRef || [])) { 63 return element.$refs.qRef[method_name](...args); 64 } else { 65 return eval(method_name)(element, ...args); 66 } 67} 68 69function getComputedProp(target, prop_name) { 70 if (typeof target === "object" && prop_name in target) { 71 return target[prop_name]; 72 } 73 const element = getElement(target); 74 if (element === null || element === undefined) return; 75 if (prop_name in element) { 76 return element[prop_name]; 77 } else if (prop_name in (element.$refs.qRef || [])) { 78 return element.$refs.qRef[prop_name]; 79 } 80} 81 82function emitEvent(event_name, ...args) { 83 getElement(0).$emit(event_name, ...args); 84} 85 86function stringifyEventArgs(args, event_args) { 87 const result = []; 88 args.forEach((arg, i) => { 89 if (event_args !== null && i >= event_args.length) return; 90 let filtered = {}; 91 if (typeof arg !== "object" || arg === null || Array.isArray(arg)) { 92 filtered = arg; 93 } else { 94 for (let k in arg) { 95 // ignore "Restricted" fields in Firefox (see #2469) 96 if (k == "originalTarget") { 97 try { 98 arg[k].toString(); 99 } catch (e) { 100 continue; 101 } 102 } 103 if (event_args === null || event_args[i] === null || event_args[i].includes(k)) { 104 filtered[k] = arg[k]; 105 } 106 } 107 } 108 result.push(JSON.stringify(filtered, (k, v) => (v instanceof Node || v instanceof Window ? undefined : v))); 109 }); 110 return result; 111} 112 113const waitingCallbacks = new Map(); 114function throttle(callback, time, leading, trailing, id) { 115 if (time <= 0) { 116 // execute callback immediately and return 117 callback(); 118 return; 119 } 120 if (waitingCallbacks.has(id)) { 121 if (trailing) { 122 // update trailing callback 123 waitingCallbacks.set(id, callback); 124 } 125 } else { 126 if (leading) { 127 // execute leading callback and set timeout to block more leading callbacks 128 callback(); 129 waitingCallbacks.set(id, null); 130 } else if (trailing) { 131 // set trailing callback and set timeout to execute it 132 waitingCallbacks.set(id, callback); 133 } 134 if (leading || trailing) { 135 // set timeout to remove block and to execute trailing callback 136 setTimeout(() => { 137 const trailingCallback = waitingCallbacks.get(id); 138 if (trailingCallback) trailingCallback(); 139 waitingCallbacks.delete(id); 140 }, 1000 * time); 141 } 142 } 143} 144function renderRecursively(elements, id) { 145 const element = elements[id]; 146 if (element === undefined) { 147 return; 148 } 149 150 // @todo: Try avoid this with better handling of initial page load. 151 if (element.component) loaded_components.add(element.component.name);
152 element.libraries.forEach((library) => loaded_libraries.add(library.name)); 153 154 const props = { 155 id: "c" + id, 156 ref: "r" + id, 157 key: id, // HACK: workaround for #600 and #898 158 class: element.class.join(" ") || undefined, 159 style: Object.entries(element.style).reduce((str, [p, val]) => `${str}${p}:${val};`, "") || undefined, 160 ...element.props, 161 }; 162 Object.entries(props).forEach(([key, value]) => { 163 if (key.startsWith(":")) { 164 try { 165 try { 166 props[key.substring(1)] = new Function(`return (${value})`)(); 167 } catch (e) { 168 props[key.substring(1)] = eval(value); 169 } 170 delete props[key]; 171 } catch (e) { 172 console.error(`Error while converting ${key} attribute to function:`, e); 173 } 174 } 175 }); 176 element.events.forEach((event) => { 177 let event_name = "on" + event.type[0].toLocaleUpperCase() + event.type.substring(1); 178 event.specials.forEach((s) => (event_name += s[0].toLocaleUpperCase() + s.substring(1))); 179 180 const emit = (...args) => { 181 const emitter = () => 182 window.socket?.emit("event", { 183 id: id, 184 client_id: window.clientId, 185 listener_id: event.listener_id, 186 args: stringifyEventArgs(args, event.args), 187 }); 188 const delayed_emitter = () => { 189 if (window.did_handshake) emitter(); 190 else setTimeout(delayed_emitter, 10); 191 }; 192 throttle(delayed_emitter, event.throttle, event.leading_events, event.trailing_events, event.listener_id); 193 if (element.props["loopback"] === False && event.type == "update:modelValue") { 194 element.props["model-value"] = args; 195 } 196 }; 197 198 let handler; 199 if (event.js_handler) { 200 handler = eval(event.js_handler); 201 } else { 202 handler = emit; 203 } 204 205 handler = Vue.withModifiers(handler, event.modifiers); 206 handler = event.keys.length ? Vue.withKeys(handler, event.keys) : handler; 207 if (props[event_name]) { 208 props[event_name].push(handler); 209 } else { 210 props[event_name] = [handler]; 211 } 212 }); 213 const slots = {}; 214 const element_slots = { 215 default: { ids: element.children || [] }, 216 ...element.slots, 217 }; 218 Object.entries(element_slots).forEach(([name, data]) => { 219 slots[name] = (props) => { 220 const rendered = []; 221 if (data.template) { 222 rendered.push( 223 Vue.h( 224 { 225 props: { props: { type: Object, default: {} } }, 226 template: data.template, 227 }, 228 { 229 props: props, 230 } 231 ) 232 ); 233 } 234 const children = data.ids.map((id) => renderRecursively(elements, id)); 235 if (name === "default" && element.text !== null) { 236 children.unshift(element.text); 237 } 238 return [...rendered, ...children]; 239 }; 240 }); 241 return Vue.h(Vue.resolveComponent(element.tag), props, slots); 242} 243 244function runJavascript(code, request_id) { 245 new Promise((resolve) => resolve(eval(code))) 246 .catch((reason) => { 247 if (reason instanceof SyntaxError) return eval(`(async() => {${code}})()`); 248 else throw reason; 249 }) 250 .then((result) => { 251 if (request_id) { 252 window.socket.emit("javascript_response", { request_id, client_id: window.clientId, result }); 253 } 254 }); 255} 256 257function download(src, filename, mediaType, prefix) { 258 const anchor = document.createElement("a"); 259 if (typeof src === "string") { 260 anchor.href = src.startsWith("/") ? prefix + src : src; 261 } else { 262 anchor.href = URL.createObjectURL(new Blob([src], { type: mediaType })); 263 } 264 anchor.target = "_blank"; 265 anchor.download = filename || ""; 266 document.body.appendChild(anchor); 267 anchor.click(); 268 document.body.removeChild(anchor); 269 if (typeof src !== "string") { 270 URL.revokeObjectURL(anchor.href); 271 } 272} 273 274function ack() { 275 if (window.ackedMessageId >= window.nextMessageId) return; 276 window.socket.emit("ack", { 277 client_id: window.clientId, 278 next_message_id: window.nextMessageId, 279 }); 280 window.ackedMessageId = window.nextMessageId; 281} 282 283async function loadDependencies(element, prefix, version) { 284 if (element.component) { 285 const { name, key, tag } = element.component; 286 if (!loaded_components.has(name) && !key.endsWith(".vue")) { 287 const component = await import(`${prefix}/_nicegui/${version}/components/${key}`); 288 app.component(tag, component.default); 289 loaded_components.add(name); 290 } 291 } 292 if (element.libraries) { 293 for (const { name, key } of element.libraries) { 294 if (loaded_libraries.has(name)) continue; 295 await import(`${prefix}/_nicegui/${version}/libraries/${key}`); 296 loaded_libraries.add(name); 297 } 298 } 299} 300 301function createRandomUUID() { 302 try { 303 return crypto.randomUUID(); 304 } catch (e) { 305 // https://stackoverflow.com/a/2117523/3419103 306 return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (c) => 307 (+c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (+c / 4)))).toString(16) 308 ); 309 } 310} 311
312const OLD_TAB_ID = sessionStorage.__nicegui_tab_closed === "false" ? sessionStorage.__nicegui_tab_id : null; 313const TAB_ID = 314 !sessionStorage.__nicegui_tab_id || sessionStorage.__nicegui_tab_closed === "false" 315 ? (sessionStorage.__nicegui_tab_id = createRandomUUID()) 316 : sessionStorage.__nicegui_tab_id; 317sessionStorage.__nicegui_tab_closed = "false"; 318window.onbeforeunload = function () { 319 sessionStorage.__nicegui_tab_closed = "true"; 320}; 321 322function createApp(elements, options) { 323 Object.entries(elements).forEach(([_, element]) => replaceUndefinedAttributes(element)); 324 setInterval(() => ack(), 3000); 325 return (app = Vue.createApp({ 326 data() { 327 return { 328 elements, 329 }; 330 }, 331 render() { 332 return renderRecursively(this.elements, 0); 333 }, 334 mounted() { 335 mounted_app = this; 336 window.documentId = createRandomUUID(); 337 window.clientId = options.query.client_id; 338 const url = window.location.protocol === "https:" ? "wss://" : "ws://" + window.location.host; 339 window.path_prefix = options.prefix; 340 window.nextMessageId = options.query.next_message_id; 341 window.ackedMessageId = -1; 342 window.socket = io(url, { 343 path: `${options.prefix}/_nicegui_ws/socket.io`, 344 query: options.query, 345 extraHeaders: options.extraHeaders, 346 transports: options.transports, 347 }); 348 window.did_handshake = false; 349 const messageHandlers = { 350 connect: () => { 351 const args = { 352 client_id: window.clientId, 353 document_id: window.documentId, 354 tab_id: TAB_ID, 355 old_tab_id: OLD_TAB_ID, 356 next_message_id: window.nextMessageId, 357 }; 358 window.socket.emit("handshake", args, (ok) => { 359 if (!ok) { 360 console.log("reloading because handshake failed for clientId " + window.clientId); 361 window.location.reload(); 362 } 363 window.did_handshake = true; 364 document.getElementById("popup").ariaHidden = true; 365 }); 366 }, 367 connect_error: (err) => { 368 if (err.message == "timeout") { 369 console.log("reloading because connection timed out"); 370 window.location.reload(); // see https://github.com/zauberzeug/nicegui/issues/198 371 } 372 }, 373 try_reconnect: async () => { 374 document.getElementById("popup").ariaHidden = false; 375 await fetch(window.location.href, { headers: { "NiceGUI-Check": "try_reconnect" } }); 376 console.log("reloading because reconnect was requested"); 377 window.location.reload(); 378 }, 379 disconnect: () => { 380 document.getElementById("popup").ariaHidden = false; 381 }, 382 update: async (msg) => { 383 const loadPromises = Object.entries(msg) 384 .filter(([_, element]) => element && (element.component || element.libraries)) 385 .map(([_, element]) => loadDependencies(element, options.prefix, options.version)); 386 await Promise.all(loadPromises); 387 388 for (const [id, element] of Object.entries(msg)) { 389 if (element === null) { 390 delete this.elements[id]; 391 continue; 392 } 393 replaceUndefinedAttributes(element); 394 this.elements[id] = element; 395 } 396 397 await this.$nextTick(); 398 for (const [id, element] of Object.entries(msg)) { 399 if (element?.update_method) { 400 getElement(id)[element.update_method](); 401 } 402 } 403 }, 404 run_javascript: (msg) => runJavascript(msg.code, msg.request_id), 405 open: (msg) => { 406 const url = msg.path.startsWith("/") ? options.prefix + msg.path : msg.path; 407 const target = msg.new_tab ? "_blank" : "_self"; 408 window.open(url, target); 409 }, 410 download: (msg) => download(msg.src, msg.filename, msg.media_type, options.prefix), 411 notify: (msg) => Quasar.Notify.create(msg), 412 }; 413 const socketMessageQueue = []; 414 let isProcessingSocketMessage = false; 415 for (const [event, handler] of Object.entries(messageHandlers)) { 416 window.socket.on(event, async (...args) => { 417 if (args.length > 0 && args[0]._id !== undefined) { 418 const message_id = args[0]._id; 419 if (message_id < window.nextMessageId) return; 420 window.nextMessageId = message_id + 1; 421 delete args[0]._id; 422 } 423 socketMessageQueue.push(() => handler(...args)); 424 if (!isProcessingSocketMessage) { 425 while (socketMessageQueue.length > 0) { 426 const handler = socketMessageQueue.shift(); 427 isProcessingSocketMessage = true; 428 try { 429 await handler(); 430 } catch (e) { 431 console.error(e); 432 } 433 isProcessingSocketMessage = false;
434 } 435 } 436 }); 437 } 438 }, 439 })); 440} 441 442// HACK: remove Quasar's rules for divs in QCard (#2265, #2301) 443for (let sheet of document.styleSheets) { 444 if (/\/quasar(?:\.prod)?\.css$/.test(sheet.href)) { 445 for (let rule of sheet.cssRules) { 446 if (/\.q-card > div/.test(rule.selectorText)) rule.selectorText = ".nicegui-card-tight" + rule.selectorText; 447 } 448 } 449}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.