UNPKG

@aire-ux/aire-overlay

Version:
1,423 lines (1,324 loc) 52 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; import { css, html, LitElement, nothing } from 'lit'; import { property, query, state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import { copy } from './copy-to-clipboard.js'; import { licenseCheckFailed, licenseCheckNoKey, licenseCheckOk, licenseInit } from './License'; var ConnectionStatus; (function (ConnectionStatus) { ConnectionStatus["ACTIVE"] = "active"; ConnectionStatus["INACTIVE"] = "inactive"; ConnectionStatus["UNAVAILABLE"] = "unavailable"; ConnectionStatus["ERROR"] = "error"; })(ConnectionStatus || (ConnectionStatus = {})); export class Connection extends Object { constructor(url) { super(); this.status = ConnectionStatus.UNAVAILABLE; if (url) { this.webSocket = new WebSocket(url); this.webSocket.onmessage = (msg) => this.handleMessage(msg); this.webSocket.onerror = (err) => this.handleError(err); this.webSocket.onclose = (_) => { if (this.status !== ConnectionStatus.ERROR) { this.setStatus(ConnectionStatus.UNAVAILABLE); } this.webSocket = undefined; }; } setInterval(() => { if (this.webSocket && self.status !== ConnectionStatus.ERROR && this.status !== ConnectionStatus.UNAVAILABLE) { this.webSocket.send(''); } }, Connection.HEARTBEAT_INTERVAL); } onHandshake() { // Intentionally empty } onReload() { // Intentionally empty } onConnectionError(_) { // Intentionally empty } onStatusChange(_) { // Intentionally empty } onMessage(message) { // eslint-disable-next-line no-console console.error('Unknown message received from the live reload server:', message); } handleMessage(msg) { let json; try { json = JSON.parse(msg.data); } catch (e) { this.handleError(`[${e.name}: ${e.message}`); return; } if (json.command === 'hello') { this.setStatus(ConnectionStatus.ACTIVE); this.onHandshake(); } else if (json.command === 'reload') { if (this.status === ConnectionStatus.ACTIVE) { this.onReload(); } } else if (json.command === 'license-check-ok') { licenseCheckOk(json.data); } else if (json.command === 'license-check-failed') { licenseCheckFailed(json.data); } else if (json.command === 'license-check-nokey') { licenseCheckNoKey(json.data); } else { this.onMessage(json); } } handleError(msg) { // eslint-disable-next-line no-console console.error(msg); this.setStatus(ConnectionStatus.ERROR); if (msg instanceof Event && this.webSocket) { this.onConnectionError(`Error in WebSocket connection to ${this.webSocket.url}`); } else { this.onConnectionError(msg); } } setActive(yes) { if (!yes && this.status === ConnectionStatus.ACTIVE) { this.setStatus(ConnectionStatus.INACTIVE); } else if (yes && this.status === ConnectionStatus.INACTIVE) { this.setStatus(ConnectionStatus.ACTIVE); } } setStatus(status) { if (this.status !== status) { this.status = status; this.onStatusChange(status); } } send(command, data) { const message = JSON.stringify({ command, data }); if (!this.webSocket) { // eslint-disable-next-line no-console console.error(`Unable to send message ${command}. No websocket is available`); } else if (this.webSocket.readyState !== WebSocket.OPEN) { this.webSocket.addEventListener('open', () => this.webSocket.send(message)); } else { this.webSocket.send(message); } } setFeature(featureId, enabled) { this.send('setFeature', { featureId, enabled }); } sendTelemetry(browserData) { this.send('reportTelemetry', { browserData }); } sendLicenseCheck(product) { this.send('checkLicense', product); } } Connection.HEARTBEAT_INTERVAL = 180000; var MessageType; (function (MessageType) { MessageType["LOG"] = "log"; MessageType["INFORMATION"] = "information"; MessageType["WARNING"] = "warning"; MessageType["ERROR"] = "error"; })(MessageType || (MessageType = {})); export class VaadinDevTools extends LitElement { constructor() { super(...arguments); this.expanded = false; this.messages = []; this.notifications = []; this.frontendStatus = ConnectionStatus.UNAVAILABLE; this.javaStatus = ConnectionStatus.UNAVAILABLE; this.tabs = [ { id: 'log', title: 'Log', render: this.renderLog, activate: this.activateLog }, { id: 'info', title: 'Info', render: this.renderInfo }, { id: 'features', title: 'Experimental Features', render: this.renderFeatures } ]; this.activeTab = 'log'; this.serverInfo = { flowVersion: '', vaadinVersion: '', javaVersion: '', osVersion: '', productName: '' }; this.features = []; this.unreadErrors = false; this.nextMessageId = 1; this.transitionDuration = 0; } static get styles() { return css ` :host { --dev-tools-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif; --dev-tools-font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; --dev-tools-font-size: 0.8125rem; --dev-tools-font-size-small: 0.75rem; --dev-tools-text-color: rgba(255, 255, 255, 0.8); --dev-tools-text-color-secondary: rgba(255, 255, 255, 0.65); --dev-tools-text-color-emphasis: rgba(255, 255, 255, 0.95); --dev-tools-text-color-active: rgba(255, 255, 255, 1); --dev-tools-background-color-inactive: rgba(45, 45, 45, 0.25); --dev-tools-background-color-active: rgba(45, 45, 45, 0.98); --dev-tools-background-color-active-blurred: rgba(45, 45, 45, 0.85); --dev-tools-border-radius: 0.5rem; --dev-tools-box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.05), 0 4px 12px -2px rgba(0, 0, 0, 0.4); --dev-tools-blue-hsl: ${this.BLUE_HSL}; --dev-tools-blue-color: hsl(var(--dev-tools-blue-hsl)); --dev-tools-green-hsl: ${this.GREEN_HSL}; --dev-tools-green-color: hsl(var(--dev-tools-green-hsl)); --dev-tools-grey-hsl: ${this.GREY_HSL}; --dev-tools-grey-color: hsl(var(--dev-tools-grey-hsl)); --dev-tools-yellow-hsl: ${this.YELLOW_HSL}; --dev-tools-yellow-color: hsl(var(--dev-tools-yellow-hsl)); --dev-tools-red-hsl: ${this.RED_HSL}; --dev-tools-red-color: hsl(var(--dev-tools-red-hsl)); /* Needs to be in ms, used in JavaScript as well */ --dev-tools-transition-duration: 180ms; all: initial; direction: ltr; cursor: default; font: normal 400 var(--dev-tools-font-size) / 1.125rem var(--dev-tools-font-family); color: var(--dev-tools-text-color); -webkit-user-select: none; -moz-user-select: none; user-select: none; position: fixed; z-index: 20000; pointer-events: none; bottom: 0; right: 0; width: 100%; height: 100%; display: flex; flex-direction: column-reverse; align-items: flex-end; } .dev-tools { pointer-events: auto; display: flex; align-items: center; position: fixed; z-index: inherit; right: 0.5rem; bottom: 0.5rem; min-width: 1.75rem; height: 1.75rem; max-width: 1.75rem; border-radius: 0.5rem; padding: 0.375rem; box-sizing: border-box; background-color: var(--dev-tools-background-color-inactive); box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.05); color: var(--dev-tools-text-color); transition: var(--dev-tools-transition-duration); white-space: nowrap; line-height: 1rem; } .dev-tools:hover, .dev-tools.active { background-color: var(--dev-tools-background-color-active); box-shadow: var(--dev-tools-box-shadow); } .dev-tools.active { max-width: calc(100% - 1rem); } .dev-tools .dev-tools-icon { flex: none; pointer-events: none; display: inline-block; width: 1rem; height: 1rem; fill: #fff; transition: var(--dev-tools-transition-duration); margin: 0; } .dev-tools.active .dev-tools-icon { opacity: 0; position: absolute; transform: scale(0.5); } .dev-tools .status-blip { flex: none; display: block; width: 6px; height: 6px; border-radius: 50%; z-index: 20001; background: var(--dev-tools-grey-color); position: absolute; top: -1px; right: -1px; } .dev-tools .status-description { overflow: hidden; text-overflow: ellipsis; padding: 0 0.25rem; } .dev-tools.error { background-color: hsla(var(--dev-tools-red-hsl), 0.15); animation: bounce 0.5s; animation-iteration-count: 2; } .switch { display: inline-flex; align-items: center; } .switch input { opacity: 0; width: 0; height: 0; position: absolute; } .switch .slider { display: block; flex: none; width: 28px; height: 18px; border-radius: 9px; background-color: rgba(255, 255, 255, 0.3); transition: var(--dev-tools-transition-duration); margin-right: 0.5rem; } .switch:focus-within .slider, .switch .slider:hover { background-color: rgba(255, 255, 255, 0.35); transition: none; } .switch input:focus-visible ~ .slider { box-shadow: 0 0 0 2px var(--dev-tools-background-color-active), 0 0 0 4px var(--dev-tools-blue-color); } .switch .slider::before { content: ''; display: block; margin: 2px; width: 14px; height: 14px; background-color: #fff; transition: var(--dev-tools-transition-duration); border-radius: 50%; } .switch input:checked + .slider { background-color: var(--dev-tools-green-color); } .switch input:checked + .slider::before { transform: translateX(10px); } .switch input:disabled + .slider::before { background-color: var(--dev-tools-grey-color); } .window.hidden { opacity: 0; transform: scale(0); position: absolute; } .window.visible { transform: none; opacity: 1; pointer-events: auto; } .window.visible ~ .dev-tools { opacity: 0; pointer-events: none; } .window.visible ~ .dev-tools .dev-tools-icon, .window.visible ~ .dev-tools .status-blip { transition: none; opacity: 0; } .window { border-radius: var(--dev-tools-border-radius); overflow: hidden; margin: 0.5rem; width: 30rem; max-width: calc(100% - 1rem); max-height: calc(100vh - 1rem); flex-shrink: 1; background-color: var(--dev-tools-background-color-active); color: var(--dev-tools-text-color); transition: var(--dev-tools-transition-duration); transform-origin: bottom right; display: flex; flex-direction: column; box-shadow: var(--dev-tools-box-shadow); outline: none; } .window-toolbar { display: flex; flex: none; align-items: center; padding: 0.375rem; white-space: nowrap; order: 1; background-color: rgba(0, 0, 0, 0.2); gap: 0.5rem; } .tab { color: var(--dev-tools-text-color-secondary); font: inherit; font-size: var(--dev-tools-font-size-small); font-weight: 500; line-height: 1; padding: 0.25rem 0.375rem; background: none; border: none; margin: 0; border-radius: 0.25rem; transition: var(--dev-tools-transition-duration); } .tab:hover, .tab.active { color: var(--dev-tools-text-color-active); } .tab.active { background-color: rgba(255, 255, 255, 0.12); } .tab.unreadErrors::after { content: '•'; color: hsl(var(--dev-tools-red-hsl)); font-size: 1.5rem; position: absolute; transform: translate(0, -50%); } .ahreflike { font-weight: 500; color: var(--dev-tools-text-color-secondary); text-decoration: underline; cursor: pointer; } .ahreflike:hover { color: var(--dev-tools-text-color-emphasis); } .button { all: initial; font-family: inherit; font-size: var(--dev-tools-font-size-small); line-height: 1; white-space: nowrap; background-color: rgba(0, 0, 0, 0.2); color: inherit; font-weight: 600; padding: 0.25rem 0.375rem; border-radius: 0.25rem; } .button:focus, .button:hover { color: var(--dev-tools-text-color-emphasis); } .minimize-button { flex: none; width: 1rem; height: 1rem; color: inherit; background-color: transparent; border: 0; padding: 0; margin: 0 0 0 auto; opacity: 0.8; } .minimize-button:hover { opacity: 1; } .minimize-button svg { max-width: 100%; } .message.information { --dev-tools-notification-color: var(--dev-tools-blue-color); } .message.warning { --dev-tools-notification-color: var(--dev-tools-yellow-color); } .message.error { --dev-tools-notification-color: var(--dev-tools-red-color); } .message { display: flex; padding: 0.1875rem 0.75rem 0.1875rem 2rem; background-clip: padding-box; } .message.log { padding-left: 0.75rem; } .message-content { margin-right: 0.5rem; -webkit-user-select: text; -moz-user-select: text; user-select: text; } .message-heading { position: relative; display: flex; align-items: center; margin: 0.125rem 0; } .message.log { color: var(--dev-tools-text-color-secondary); } .message:not(.log) .message-heading { font-weight: 500; } .message.has-details .message-heading { color: var(--dev-tools-text-color-emphasis); font-weight: 600; } .message-heading::before { position: absolute; margin-left: -1.5rem; display: inline-block; text-align: center; font-size: 0.875em; font-weight: 600; line-height: calc(1.25em - 2px); width: 14px; height: 14px; box-sizing: border-box; border: 1px solid transparent; border-radius: 50%; } .message.information .message-heading::before { content: 'i'; border-color: currentColor; color: var(--dev-tools-notification-color); } .message.warning .message-heading::before, .message.error .message-heading::before { content: '!'; color: var(--dev-tools-background-color-active); background-color: var(--dev-tools-notification-color); } .features-tray { padding: 0.75rem; flex: auto; overflow: auto; animation: fade-in var(--dev-tools-transition-duration) ease-in; user-select: text; } .features-tray p { margin-top: 0; color: var(--dev-tools-text-color-secondary); } .features-tray .feature { display: flex; align-items: center; gap: 1rem; padding-bottom: 0.5em; } .message .message-details { font-weight: 400; color: var(--dev-tools-text-color-secondary); margin: 0.25rem 0; } .message .message-details[hidden] { display: none; } .message .message-details p { display: inline; margin: 0; margin-right: 0.375em; word-break: break-word; } .message .persist { color: var(--dev-tools-text-color-secondary); white-space: nowrap; margin: 0.375rem 0; display: flex; align-items: center; position: relative; -webkit-user-select: none; -moz-user-select: none; user-select: none; } .message .persist::before { content: ''; width: 1em; height: 1em; border-radius: 0.2em; margin-right: 0.375em; background-color: rgba(255, 255, 255, 0.3); } .message .persist:hover::before { background-color: rgba(255, 255, 255, 0.4); } .message .persist.on::before { background-color: rgba(255, 255, 255, 0.9); } .message .persist.on::after { content: ''; order: -1; position: absolute; width: 0.75em; height: 0.25em; border: 2px solid var(--dev-tools-background-color-active); border-width: 0 0 2px 2px; transform: translate(0.05em, -0.05em) rotate(-45deg) scale(0.8, 0.9); } .message .dismiss-message { font-weight: 600; align-self: stretch; display: flex; align-items: center; padding: 0 0.25rem; margin-left: 0.5rem; color: var(--dev-tools-text-color-secondary); } .message .dismiss-message:hover { color: var(--dev-tools-text-color); } .notification-tray { display: flex; flex-direction: column-reverse; align-items: flex-end; margin: 0.5rem; flex: none; } .window.hidden + .notification-tray { margin-bottom: 3rem; } .notification-tray .message { pointer-events: auto; background-color: var(--dev-tools-background-color-active); color: var(--dev-tools-text-color); max-width: 30rem; box-sizing: border-box; border-radius: var(--dev-tools-border-radius); margin-top: 0.5rem; transition: var(--dev-tools-transition-duration); transform-origin: bottom right; animation: slideIn var(--dev-tools-transition-duration); box-shadow: var(--dev-tools-box-shadow); padding-top: 0.25rem; padding-bottom: 0.25rem; } .notification-tray .message.animate-out { animation: slideOut forwards var(--dev-tools-transition-duration); } .notification-tray .message .message-details { max-height: 10em; overflow: hidden; } .message-tray { flex: auto; overflow: auto; max-height: 20rem; user-select: text; } .message-tray .message { animation: fade-in var(--dev-tools-transition-duration) ease-in; padding-left: 2.25rem; } .message-tray .message.warning { background-color: hsla(var(--dev-tools-yellow-hsl), 0.09); } .message-tray .message.error { background-color: hsla(var(--dev-tools-red-hsl), 0.09); } .message-tray .message.error .message-heading { color: hsl(var(--dev-tools-red-hsl)); } .message-tray .message.warning .message-heading { color: hsl(var(--dev-tools-yellow-hsl)); } .message-tray .message + .message { border-top: 1px solid rgba(255, 255, 255, 0.07); } .message-tray .dismiss-message, .message-tray .persist { display: none; } .info-tray { padding: 0.75rem; position: relative; flex: auto; overflow: auto; animation: fade-in var(--dev-tools-transition-duration) ease-in; user-select: text; } .info-tray dl { margin: 0; display: grid; grid-template-columns: max-content 1fr; column-gap: 0.75rem; position: relative; } .info-tray dt { grid-column: 1; color: var(--dev-tools-text-color-emphasis); } .info-tray dt:not(:first-child)::before { content: ''; width: 100%; position: absolute; height: 1px; background-color: rgba(255, 255, 255, 0.1); margin-top: -0.375rem; } .info-tray dd { grid-column: 2; margin: 0; } .info-tray :is(dt, dd):not(:last-child) { margin-bottom: 0.75rem; } .info-tray dd + dd { margin-top: -0.5rem; } .info-tray .live-reload-status::before { content: '•'; color: var(--status-color); width: 0.75rem; display: inline-block; font-size: 1rem; line-height: 0.5rem; } .info-tray .copy { position: fixed; z-index: 1; top: 0.5rem; right: 0.5rem; } .info-tray .switch { vertical-align: -4px; } @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0%); opacity: 1; } } @keyframes slideOut { from { transform: translateX(0%); opacity: 1; } to { transform: translateX(100%); opacity: 0; } } @keyframes fade-in { 0% { opacity: 0; } } @keyframes bounce { 0% { transform: scale(0.8); } 50% { transform: scale(1.5); background-color: hsla(var(--dev-tools-red-hsl), 1); } 100% { transform: scale(1); } } @supports (backdrop-filter: blur(1px)) { .dev-tools, .window, .notification-tray .message { backdrop-filter: blur(8px); } .dev-tools:hover, .dev-tools.active, .window, .notification-tray .message { background-color: var(--dev-tools-background-color-active-blurred); } } `; } static get isActive() { const active = window.sessionStorage.getItem(VaadinDevTools.ACTIVE_KEY_IN_SESSION_STORAGE); return active === null || active !== 'false'; } static notificationDismissed(persistentId) { const shown = window.localStorage.getItem(VaadinDevTools.DISMISSED_NOTIFICATIONS_IN_LOCAL_STORAGE); return shown !== null && shown.includes(persistentId); } elementTelemetry() { let data = {}; try { // localstorage data is collected by vaadin-usage-statistics.js const localStorageStatsString = localStorage.getItem('vaadin.statistics.basket'); if (!localStorageStatsString) { // Do not send empty data return; } data = JSON.parse(localStorageStatsString); } catch (e) { // In case of parse errors don't send anything return; } if (this.frontendConnection) { this.frontendConnection.sendTelemetry(data); } } openWebSocketConnection() { this.frontendStatus = ConnectionStatus.UNAVAILABLE; this.javaStatus = ConnectionStatus.UNAVAILABLE; const onConnectionError = (msg) => this.log(MessageType.ERROR, msg); const onReload = () => { if (this.liveReloadDisabled) { return; } this.showSplashMessage('Reloading…'); const lastReload = window.sessionStorage.getItem(VaadinDevTools.TRIGGERED_COUNT_KEY_IN_SESSION_STORAGE); const nextReload = lastReload ? parseInt(lastReload, 10) + 1 : 1; window.sessionStorage.setItem(VaadinDevTools.TRIGGERED_COUNT_KEY_IN_SESSION_STORAGE, nextReload.toString()); window.sessionStorage.setItem(VaadinDevTools.TRIGGERED_KEY_IN_SESSION_STORAGE, 'true'); window.location.reload(); }; const frontendConnection = new Connection(this.getDedicatedWebSocketUrl()); frontendConnection.onHandshake = () => { this.log(MessageType.LOG, 'Vaadin development mode initialized'); if (!VaadinDevTools.isActive) { frontendConnection.setActive(false); } this.elementTelemetry(); }; frontendConnection.onConnectionError = onConnectionError; frontendConnection.onReload = onReload; frontendConnection.onStatusChange = (status) => { this.frontendStatus = status; }; frontendConnection.onMessage = (message) => { if ((message === null || message === void 0 ? void 0 : message.command) === 'serverInfo') { this.serverInfo = message.data; } else if ((message === null || message === void 0 ? void 0 : message.command) === 'featureFlags') { this.features = message.data.features; } else { // eslint-disable-next-line no-console console.error('Unknown message from front-end connection:', JSON.stringify(message)); } }; this.frontendConnection = frontendConnection; let javaConnection; if (this.backend === VaadinDevTools.SPRING_BOOT_DEVTOOLS && this.springBootLiveReloadPort) { javaConnection = new Connection(this.getSpringBootWebSocketUrl(window.location)); javaConnection.onHandshake = () => { if (!VaadinDevTools.isActive) { javaConnection.setActive(false); } }; javaConnection.onReload = onReload; javaConnection.onConnectionError = onConnectionError; } else if (this.backend === VaadinDevTools.JREBEL || this.backend === VaadinDevTools.HOTSWAP_AGENT) { javaConnection = frontendConnection; } else { javaConnection = new Connection(undefined); } const prevOnStatusChange = javaConnection.onStatusChange; javaConnection.onStatusChange = (status) => { prevOnStatusChange(status); this.javaStatus = status; }; const prevOnHandshake = javaConnection.onHandshake; javaConnection.onHandshake = () => { prevOnHandshake(); if (this.backend) { this.log(MessageType.INFORMATION, `Java live reload available: ${VaadinDevTools.BACKEND_DISPLAY_NAME[this.backend]}`); } }; this.javaConnection = javaConnection; if (!this.backend) { this.showNotification(MessageType.WARNING, 'Java live reload unavailable', 'Live reload for Java changes is currently not set up. Find out how to make use of this functionality to boost your workflow.', 'https://vaadin.com/docs/latest/flow/configuration/live-reload', 'liveReloadUnavailable'); } } getDedicatedWebSocketUrl() { function getAbsoluteUrl(relative) { // Use innerHTML to obtain an absolute URL const div = document.createElement('div'); div.innerHTML = `<a href="${relative}"/>`; return div.firstChild.href; } if (this.url === undefined) { return undefined; } const connectionBaseUrl = getAbsoluteUrl(this.url); if (!connectionBaseUrl.startsWith('http://') && !connectionBaseUrl.startsWith('https://')) { // eslint-disable-next-line no-console console.error('The protocol of the url should be http or https for live reload to work.'); return undefined; } return `${connectionBaseUrl.replace(/^http/, 'ws')}?v-r=push&debug_window`; } getSpringBootWebSocketUrl(location) { const { hostname } = location; const wsProtocol = location.protocol === 'https:' ? 'wss' : 'ws'; if (hostname.endsWith('gitpod.io')) { // Gitpod uses `port-url` instead of `url:port` const hostnameWithoutPort = hostname.replace(/.*?-/, ''); return `${wsProtocol}://${this.springBootLiveReloadPort}-${hostnameWithoutPort}`; } else { return `${wsProtocol}://${hostname}:${this.springBootLiveReloadPort}`; } } connectedCallback() { super.connectedCallback(); this.catchErrors(); // when focus or clicking anywhere, move the splash message to the message tray this.disableEventListener = (_) => this.demoteSplashMessage(); document.body.addEventListener('focus', this.disableEventListener); document.body.addEventListener('click', this.disableEventListener); this.openWebSocketConnection(); const lastReload = window.sessionStorage.getItem(VaadinDevTools.TRIGGERED_KEY_IN_SESSION_STORAGE); if (lastReload) { const now = new Date(); const reloaded = `${`0${now.getHours()}`.slice(-2)}:${`0${now.getMinutes()}`.slice(-2)}:${`0${now.getSeconds()}`.slice(-2)}`; this.showSplashMessage(`Page reloaded at ${reloaded}`); window.sessionStorage.removeItem(VaadinDevTools.TRIGGERED_KEY_IN_SESSION_STORAGE); } this.transitionDuration = parseInt(window.getComputedStyle(this).getPropertyValue('--dev-tools-transition-duration'), 10); const windowAny = window; windowAny.Vaadin = windowAny.Vaadin || {}; windowAny.Vaadin.devTools = Object.assign(this, windowAny.Vaadin.devTools); licenseInit(); } format(o) { return o.toString(); } catchErrors() { // Process stored messages const queue = window.Vaadin.ConsoleErrors; if (queue) { queue.forEach((args) => { this.log(MessageType.ERROR, args.map((o) => this.format(o)).join(' ')); }); } // Install new handler that immediately processes messages window.Vaadin.ConsoleErrors = { push: (args) => { this.log(MessageType.ERROR, args.map((o) => this.format(o)).join(' ')); } }; } disconnectedCallback() { if (this.disableEventListener) { document.body.removeEventListener('focus', this.disableEventListener); document.body.removeEventListener('click', this.disableEventListener); } super.disconnectedCallback(); } toggleExpanded() { this.notifications.slice().forEach((notification) => this.dismissNotification(notification.id)); this.expanded = !this.expanded; if (this.expanded) { this.root.focus(); } } showSplashMessage(msg) { this.splashMessage = msg; if (this.splashMessage) { if (this.expanded) { this.demoteSplashMessage(); } else { // automatically move notification to message tray after a certain amount of time setTimeout(() => { this.demoteSplashMessage(); }, VaadinDevTools.AUTO_DEMOTE_NOTIFICATION_DELAY); } } } demoteSplashMessage() { if (this.splashMessage) { this.log(MessageType.LOG, this.splashMessage); } this.showSplashMessage(undefined); } checkLicense(productInfo) { if (this.frontendConnection) { this.frontendConnection.sendLicenseCheck(productInfo); } else { licenseCheckFailed({ message: 'Internal error: no connection', product: productInfo }); } } log(type, message, details, link) { const id = this.nextMessageId; this.nextMessageId += 1; this.messages.push({ id, type, message, details, link, dontShowAgain: false, deleted: false }); while (this.messages.length > VaadinDevTools.MAX_LOG_ROWS) { this.messages.shift(); } this.requestUpdate(); this.updateComplete.then(() => { // Scroll into view const lastMessage = this.renderRoot.querySelector('.message-tray .message:last-child'); if (this.expanded && lastMessage) { setTimeout(() => lastMessage.scrollIntoView({ behavior: 'smooth' }), this.transitionDuration); this.unreadErrors = false; } else if (type === MessageType.ERROR) { this.unreadErrors = true; } }); } showNotification(type, message, details, link, persistentId) { if (persistentId === undefined || !VaadinDevTools.notificationDismissed(persistentId)) { // Do not open persistent message if another is already visible with the same persistentId const matchingVisibleNotifications = this.notifications .filter((notification) => notification.persistentId === persistentId) .filter((notification) => !notification.deleted); if (matchingVisibleNotifications.length > 0) { return; } const id = this.nextMessageId; this.nextMessageId += 1; this.notifications.push({ id, type, message, details, link, persistentId, dontShowAgain: false, deleted: false }); // automatically move notification to message tray after a certain amount of time unless it contains a link if (link === undefined) { setTimeout(() => { this.dismissNotification(id); }, VaadinDevTools.AUTO_DEMOTE_NOTIFICATION_DELAY); } this.requestUpdate(); } else { this.log(type, message, details, link); } } dismissNotification(id) { const index = this.findNotificationIndex(id); if (index !== -1 && !this.notifications[index].deleted) { const notification = this.notifications[index]; // user is explicitly dismissing a notification---after that we won't bug them with it if (notification.dontShowAgain && notification.persistentId && !VaadinDevTools.notificationDismissed(notification.persistentId)) { let dismissed = window.localStorage.getItem(VaadinDevTools.DISMISSED_NOTIFICATIONS_IN_LOCAL_STORAGE); dismissed = dismissed === null ? notification.persistentId : `${dismissed},${notification.persistentId}`; window.localStorage.setItem(VaadinDevTools.DISMISSED_NOTIFICATIONS_IN_LOCAL_STORAGE, dismissed); } notification.deleted = true; this.log(notification.type, notification.message, notification.details, notification.link); // give some time for the animation setTimeout(() => { const idx = this.findNotificationIndex(id); if (idx !== -1) { this.notifications.splice(idx, 1); this.requestUpdate(); } }, this.transitionDuration); } } findNotificationIndex(id) { let index = -1; this.notifications.some((notification, idx) => { if (notification.id === id) { index = idx; return true; } else { return false; } }); return index; } toggleDontShowAgain(id) { const index = this.findNotificationIndex(id); if (index !== -1 && !this.notifications[index].deleted) { const notification = this.notifications[index]; notification.dontShowAgain = !notification.dontShowAgain; this.requestUpdate(); } } setActive(yes) { var _a, _b; (_a = this.frontendConnection) === null || _a === void 0 ? void 0 : _a.setActive(yes); (_b = this.javaConnection) === null || _b === void 0 ? void 0 : _b.setActive(yes); window.sessionStorage.setItem(VaadinDevTools.ACTIVE_KEY_IN_SESSION_STORAGE, yes ? 'true' : 'false'); } getStatusColor(status) { if (status === ConnectionStatus.ACTIVE) { return css `hsl(${VaadinDevTools.GREEN_HSL})`; } else if (status === ConnectionStatus.INACTIVE) { return css `hsl(${VaadinDevTools.GREY_HSL})`; } else if (status === ConnectionStatus.UNAVAILABLE) { return css `hsl(${VaadinDevTools.YELLOW_HSL})`; } else if (status === ConnectionStatus.ERROR) { return css `hsl(${VaadinDevTools.RED_HSL})`; } else { return css `none`; } } /* eslint-disable lit/no-template-arrow */ renderMessage(messageObject) { return html ` <div class="message ${messageObject.type} ${messageObject.deleted ? 'animate-out' : ''} ${messageObject.details || messageObject.link ? 'has-details' : ''}" > <div class="message-content"> <div class="message-heading">${messageObject.message}</div> <div class="message-details" ?hidden="${!messageObject.details && !messageObject.link}"> ${messageObject.details ? html `<p>${messageObject.details}</p>` : ''} ${messageObject.link ? html `<a class="ahreflike" href="${messageObject.link}" target="_blank">Learn more</a>` : ''} </div> ${messageObject.persistentId ? html `<div class="persist ${messageObject.dontShowAgain ? 'on' : 'off'}" @click=${() => this.toggleDontShowAgain(messageObject.id)} > Don’t show again </div>` : ''} </div> <div class="dismiss-message" @click=${() => this.dismissNotification(messageObject.id)}>Dismiss</div> </div> `; } /* eslint-disable lit/no-template-map */ render() { return html ` <div class="window ${this.expanded ? 'visible' : 'hidden'}" tabindex="0" @keydown=${(e) => e.key === 'Escape' && this.toggleExpanded()} > <div class="window-toolbar"> ${this.tabs.map((tab) => html `<button class=${classMap({ tab: true, active: this.activeTab === tab.id, unreadErrors: tab.id === 'log' && this.unreadErrors })} id="${tab.id}" @click=${() => { this.activeTab = tab.id; if (tab.activate) tab.activate.call(this); }} > ${tab.title} </button> `)} <button class="minimize-button" title="Minimize" @click=${() => this.toggleExpanded()}> <svg fill="none" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"> <g fill="#fff" opacity=".8"> <path d="m7.25 1.75c0-.41421.33579-.75.75-.75h3.25c2.0711 0 3.75 1.67893 3.75 3.75v6.5c0 2.0711-1.6789 3.75-3.75 3.75h-6.5c-2.07107 0-3.75-1.6789-3.75-3.75v-3.25c0-.41421.33579-.75.75-.75s.75.33579.75.75v3.25c0 1.2426 1.00736 2.25 2.25 2.25h6.5c1.2426 0 2.25-1.0074 2.25-2.25v-6.5c0-1.24264-1.0074-2.25-2.25-2.25h-3.25c-.41421 0-.75-.33579-.75-.75z" /> <path d="m2.96967 2.96967c.29289-.29289.76777-.29289 1.06066 0l5.46967 5.46967v-2.68934c0-.41421.33579-.75.75-.75.4142 0 .75.33579.75.75v4.5c0 .4142-.3358.75-.75.75h-4.5c-.41421 0-.75-.3358-.75-.75 0-.41421.33579-.75.75-.75h2.68934l-5.46967-5.46967c-.29289-.29289-.29289-.76777 0-1.06066z" /> </g> </svg> </button> </div> ${this.tabs.map((tab) => (this.activeTab === tab.id ? tab.render.call(this) : nothing))} </div> <div class="notification-tray">${this.notifications.map((msg) => this.renderMessage(msg))}</div> <div class="dev-tools ${this.splashMessage ? 'active' : ''}${this.unreadErrors ? ' error' : ''}" @click=${() => this.toggleExpanded()} > ${this.unreadErrors ? html `<svg fill="none" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="dev-tools-icon error" > <clipPath id="a"><path d="m0 0h16v16h-16z" /></clipPath> <g clip-path="url(#a)"> <path d="m6.25685 2.09894c.76461-1.359306 2.72169-1.359308 3.4863 0l5.58035 9.92056c.7499 1.3332-.2135 2.9805-1.7432 2.9805h-11.1606c-1.529658 0-2.4930857-1.6473-1.743156-2.9805z" fill="#ff5c69" /> <path d="m7.99699 4c-.45693 0-.82368.37726-.81077.834l.09533 3.37352c.01094.38726.32803.69551.71544.69551.38741 0 .70449-.30825.71544-.69551l.09533-3.37352c.0129-.45674-.35384-.834-.81077-.834zm.00301 8c.60843 0 1-.3879 1-.979 0-.5972-.39157-.9851-1-.9851s-1 .3879-1 .9851c0 .5911.39157.979 1 .979z" fill="#fff" /> </g> </svg>` : html `<svg fill="none" height="17" viewBox="0 0 16 17" width="16" xmlns="http://www.w3.org/2000/svg" class="dev-tools-icon logo" > <g fill="#fff"> <path d="m8.88273 5.97926c0 .04401-.0032.08898-.00801.12913-.02467.42848-.37813.76767-.8117.76767-.43358 0-.78704-.34112-.81171-.76928-.00481-.04015-.00801-.08351-.00801-.12752 0-.42784-.10255-.87656-1.14434-.87656h-3.48364c-1.57118 0-2.315271-.72849-2.315271-2.21758v-1.26683c0-.42431.324618-.768314.748261-.768314.42331 0 .74441.344004.74441.768314v.42784c0 .47924.39576.81265 1.11293.81265h3.41538c1.5542 0 1.67373 1.156 1.725 1.7679h.03429c.05095-.6119.17048-1.7679 1.72468-1.7679h3.4154c.7172 0 1.0145-.32924 1.0145-.80847l-.0067-.43202c0-.42431.3227-.768314.7463-.768314.4234 0 .7255.344004.7255.768314v1.26683c0 1.48909-.6181 2.21758-2.1893 2.21758h-3.4836c-1.04182 0-1.14437.44872-1.14437.87656z" /> <path d="m8.82577 15.1648c-.14311.3144-.4588.5335-.82635.5335-.37268 0-.69252-.2249-.83244-.5466-.00206-.0037-.00412-.0073-.00617-.0108-.00275-.0047-.00549-.0094-.00824-.0145l-3.16998-5.87318c-.08773-.15366-.13383-.32816-.13383-.50395 0-.56168.45592-1.01879 1.01621-1.01879.45048 0 .75656.22069.96595.6993l2.16882 4.05042 2.17166-4.05524c.2069-.47379.513-.69448.9634-.69448.5603 0 1.0166.45711 1.0166 1.01879 0 .17579-.0465.35029-.1348.50523l-3.1697 5.8725c-.00503.0096-.01006.0184-.01509.0272-.00201.0036-.00402.0071-.00604.0106z" /> </g> </svg>`} <span class="status-blip" style="background: linear-gradient(to right, ${this.getStatusColor(this.frontendStatus)} 50%, ${this.getStatusColor(this.javaStatus)} 50%)" ></span> ${this.splashMessage ? html `<span class="status-description">${this.splashMessage}</span></div>` : nothing} </div>`; } renderLog() { return html `<div class="message-tray">${this.messages.map((msg) => this.renderMessage(msg))}</div>`; } activateLog() { this.unreadErrors = false; this.updateComplete.then(() => { const lastMessage = this.renderRoot.querySelector('.message-tray .message:last-child'); if (lastMessage) { lastMessage.scrollIntoView(); } }); } renderInfo() { return html `<div class="info-tray"> <button class="button copy" @click=${this.copyInfoToClipboard}>Copy</button> <dl> <dt>${this.serverInfo.productName}</dt> <dd>${this.serverInfo.vaadinVersion}</dd> <dt>Flow</dt> <dd>${this.serverInfo.flowVersion}</dd> <dt>Java</dt> <dd>${this.serverInfo.javaVersion}</dd> <dt>OS</dt> <dd>${this.serverInfo.osVersion}</dd> <dt>Browser</dt> <dd>${navigator.userAgent}</dd> <dt> Live reload <label class="switch"> <input id="toggle" type="checkbox" ?disabled=${this.liveReloadDisabled || ((this.frontendStatus === ConnectionStatus.UNAVAILABLE || this.frontendStatus === ConnectionStatus.ERROR) && (this.javaStatus === ConnectionStatus.UNAVAILABLE || this.javaStatus === ConnectionStatus.ERROR))} ?checked="${this.frontendStatus === ConnectionStatus.ACTIVE || this.javaStatus === ConnectionStatus.ACTIVE}" @change=${(e) => this.setActive(e.target.checked)} /> <span class="slider"></span> </label> </dt> <dd class="live-reload-status" style="--status-color: ${this.getStatusColor(this.javaStatus)}"> Java ${this.javaStatus} ${this.backend ? `(${VaadinDevTools.BACKEND_DISPLAY_NAME[this.backend]})` : ''} </dd> <dd class="live-reload-status" style="--status-color: ${this.getStatusColor(this.frontendStatus)}"> Front end ${this.frontendStatus} </dd> </dl> </div>`; } renderFeatures() { return html `<div class="features-tray"> <p> These features are work in progress. The behavior, API, look and feel can still change significantly before (and if) they become part of a stable release. </p> ${this.features.map((feature) => html `<div class="feature"> <label class="switch"> <input class="feature-toggle" id="feature-toggle-${feature.id}" type="checkbox" ?checked=${feature.enabled} @change=${(e) => this.toggleFeatureFlag(e, feature)} /> <span class="slider"></span> ${feature.title} </label> <a class="ahreflike" href="${feature.moreInfoLink}" target="_blank">Learn more</a> </div>`)} </div>`; } copyInfoToClipboard() { const items = this.renderRoot.querySelectorAll('.info-tray dt, .info-tray dd'); const text = Array.from(items) .map((message) => (message.localName === 'dd' ? ': ' : '\n') + message.textContent.trim()) .join('') .replace(/^\n/, ''); copy(text); this.showNotification(MessageType.INFORMATION, 'Environment information copied to clipboard', undefined, undefined, 'versionInfoCopied'); } toggleFeatureFlag(e, feature) { const enabled = e.target.checked; if (this.frontendConnection) { this.frontendConnection.setFeature(feature.id, enabled); this.showNotification(MessageType.INFORMATION, `“${feature.title}” ${enabled ? 'enabled' : 'disabled'}`, feature.requiresServerRestart ? 'This feature requires a server restart' : undefined, undefined, `feature${feature.id}${enabled ? 'Enabled' : 'Disabled'}`); } else { this.log(MessageType.ERROR, `Unable to toggle feature ${feature.title}: No server connection available`); } } } VaadinDevTools.BLUE_HSL = css `206, 100%, 70%`; VaadinDevTools.GREEN_HSL = css `145, 80%, 42%`; VaadinDevTools.GREY_HSL = css `0, 0%, 50%`; VaadinDevTools.YELLOW_HSL = css `38, 98%, 64%`; VaadinDevTools.RED_HSL = css `355, 100%, 68%`; VaadinDevTools.MAX_LOG_ROWS = 1000; VaadinDevTools.DISMISSED_NOTIFICATIONS_IN_LOCAL_STORAGE = 'vaadin.live-reload.dismissedNotifications'; VaadinDevTools.ACTIVE_KEY_IN_SESSION_STORAGE = 'vaadin.live-reload.active'; VaadinDevTools.TRIGGERED_KEY_IN_SESSION_STORAGE = 'vaadin.live-reload.triggered'; VaadinDevTools.TRIGGERED_COUNT_KEY_IN_SESSION_STORAGE = 'vaadin.live-reload.triggeredCount'; VaadinDevTools.AUTO_DEMOTE_NOTIFICATION_DELAY = 5000; VaadinDevTools.HOTSWAP_AGENT = 'HOTSWAP_AGENT'; VaadinDevTools.J