Some checks failed
Build and Release / build (x64, ubuntu-latest, linux) (push) Has been skipped
Build and Release / build (arm64, macos-latest, darwin) (push) Has been cancelled
Build and Release / build (x64, macos-latest, darwin) (push) Has been cancelled
Build and Release / build (x64, windows-latest, win32) (push) Has been cancelled
Build and Release / release (push) Has been cancelled
641 lines
23 KiB
JavaScript
641 lines
23 KiB
JavaScript
import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js';
|
|
import { AppHeader } from './AppHeader.js';
|
|
import { MainView } from '../views/MainView.js';
|
|
import { CustomizeView } from '../views/CustomizeView.js';
|
|
import { HelpView } from '../views/HelpView.js';
|
|
import { HistoryView } from '../views/HistoryView.js';
|
|
import { AssistantView } from '../views/AssistantView.js';
|
|
import { OnboardingView } from '../views/OnboardingView.js';
|
|
import { ScreenPickerDialog } from '../views/ScreenPickerDialog.js';
|
|
|
|
export class MastermindApp extends LitElement {
|
|
static styles = css`
|
|
* {
|
|
box-sizing: border-box;
|
|
font-family:
|
|
'Inter',
|
|
-apple-system,
|
|
BlinkMacSystemFont,
|
|
sans-serif;
|
|
margin: 0px;
|
|
padding: 0px;
|
|
cursor: default;
|
|
user-select: none;
|
|
}
|
|
|
|
:host {
|
|
display: block;
|
|
width: 100%;
|
|
height: 100vh;
|
|
background-color: var(--background-transparent);
|
|
color: var(--text-color);
|
|
}
|
|
|
|
.window-container {
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
background: var(--bg-primary);
|
|
}
|
|
|
|
.container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
}
|
|
|
|
.main-content {
|
|
flex: 1;
|
|
padding: var(--main-content-padding);
|
|
overflow-y: auto;
|
|
background: var(--main-content-background);
|
|
}
|
|
|
|
.main-content.with-border {
|
|
border-top: none;
|
|
}
|
|
|
|
.main-content.assistant-view {
|
|
padding: 12px;
|
|
}
|
|
|
|
.main-content.onboarding-view {
|
|
padding: 0;
|
|
background: transparent;
|
|
}
|
|
|
|
.main-content.settings-view,
|
|
.main-content.help-view,
|
|
.main-content.history-view {
|
|
padding: 0;
|
|
}
|
|
|
|
.view-container {
|
|
opacity: 1;
|
|
height: 100%;
|
|
}
|
|
|
|
.view-container.entering {
|
|
opacity: 0;
|
|
}
|
|
|
|
::-webkit-scrollbar {
|
|
width: 8px;
|
|
height: 8px;
|
|
}
|
|
|
|
::-webkit-scrollbar-track {
|
|
background: transparent;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb {
|
|
background: var(--scrollbar-thumb);
|
|
border-radius: 4px;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb:hover {
|
|
background: var(--scrollbar-thumb-hover);
|
|
}
|
|
`;
|
|
|
|
static properties = {
|
|
currentView: { type: String },
|
|
statusText: { type: String },
|
|
startTime: { type: Number },
|
|
isRecording: { type: Boolean },
|
|
sessionActive: { type: Boolean },
|
|
selectedProfile: { type: String },
|
|
selectedLanguage: { type: String },
|
|
responses: { type: Array },
|
|
currentResponseIndex: { type: Number },
|
|
selectedScreenshotInterval: { type: String },
|
|
selectedImageQuality: { type: String },
|
|
layoutMode: { type: String },
|
|
_viewInstances: { type: Object, state: true },
|
|
_isClickThrough: { state: true },
|
|
_awaitingNewResponse: { state: true },
|
|
shouldAnimateResponse: { type: Boolean },
|
|
_storageLoaded: { state: true },
|
|
aiProvider: { type: String },
|
|
modelInfo: { type: Object },
|
|
showScreenPicker: { type: Boolean },
|
|
screenSources: { type: Array },
|
|
};
|
|
|
|
constructor() {
|
|
super();
|
|
// Set defaults - will be overwritten by storage
|
|
this.currentView = 'main'; // Will check onboarding after storage loads
|
|
this.statusText = '';
|
|
this.startTime = null;
|
|
this.isRecording = false;
|
|
this.sessionActive = false;
|
|
this.selectedProfile = 'interview';
|
|
this.selectedLanguage = 'en-US';
|
|
this.selectedScreenshotInterval = '5';
|
|
this.selectedImageQuality = 'medium';
|
|
this.layoutMode = 'normal';
|
|
this.responses = [];
|
|
this.currentResponseIndex = -1;
|
|
this._viewInstances = new Map();
|
|
this._isClickThrough = false;
|
|
this._awaitingNewResponse = false;
|
|
this._currentResponseIsComplete = true;
|
|
this.shouldAnimateResponse = false;
|
|
this._storageLoaded = false;
|
|
this.aiProvider = 'gemini';
|
|
this.modelInfo = { model: '', visionModel: '', whisperModel: '' };
|
|
this.showScreenPicker = false;
|
|
this.screenSources = [];
|
|
|
|
// Load from storage
|
|
this._loadFromStorage();
|
|
}
|
|
|
|
async _loadFromStorage() {
|
|
try {
|
|
const [config, prefs, openaiSdkCreds] = await Promise.all([
|
|
mastermind.storage.getConfig(),
|
|
mastermind.storage.getPreferences(),
|
|
mastermind.storage.getOpenAISDKCredentials(),
|
|
]);
|
|
|
|
// Check onboarding status
|
|
this.currentView = config.onboarded ? 'main' : 'onboarding';
|
|
|
|
// Apply background appearance (color + transparency)
|
|
this.applyBackgroundAppearance(prefs.backgroundColor ?? '#1e1e1e', prefs.backgroundTransparency ?? 0.8);
|
|
|
|
// Load preferences
|
|
this.selectedProfile = prefs.selectedProfile || 'interview';
|
|
this.selectedLanguage = prefs.selectedLanguage || 'en-US';
|
|
this.selectedScreenshotInterval = prefs.selectedScreenshotInterval || '5';
|
|
this.selectedImageQuality = prefs.selectedImageQuality || 'medium';
|
|
this.layoutMode = config.layout || 'normal';
|
|
|
|
// Load AI provider and model info
|
|
this.aiProvider = prefs.aiProvider || 'gemini';
|
|
this.modelInfo = {
|
|
model: openaiSdkCreds.model || 'gpt-4o',
|
|
visionModel: openaiSdkCreds.visionModel || 'gpt-4o',
|
|
whisperModel: openaiSdkCreds.whisperModel || 'whisper-1',
|
|
};
|
|
|
|
this._storageLoaded = true;
|
|
this.updateLayoutMode();
|
|
this.requestUpdate();
|
|
} catch (error) {
|
|
console.error('Error loading from storage:', error);
|
|
this._storageLoaded = true;
|
|
this.requestUpdate();
|
|
}
|
|
}
|
|
|
|
hexToRgb(hex) {
|
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
return result
|
|
? {
|
|
r: parseInt(result[1], 16),
|
|
g: parseInt(result[2], 16),
|
|
b: parseInt(result[3], 16),
|
|
}
|
|
: { r: 30, g: 30, b: 30 };
|
|
}
|
|
|
|
lightenColor(rgb, amount) {
|
|
return {
|
|
r: Math.min(255, rgb.r + amount),
|
|
g: Math.min(255, rgb.g + amount),
|
|
b: Math.min(255, rgb.b + amount),
|
|
};
|
|
}
|
|
|
|
applyBackgroundAppearance(backgroundColor, alpha) {
|
|
const root = document.documentElement;
|
|
const baseRgb = this.hexToRgb(backgroundColor);
|
|
|
|
// Generate color variants based on the base color
|
|
const secondary = this.lightenColor(baseRgb, 7);
|
|
const tertiary = this.lightenColor(baseRgb, 15);
|
|
const hover = this.lightenColor(baseRgb, 20);
|
|
|
|
root.style.setProperty('--header-background', `rgba(${baseRgb.r}, ${baseRgb.g}, ${baseRgb.b}, ${alpha})`);
|
|
root.style.setProperty('--main-content-background', `rgba(${baseRgb.r}, ${baseRgb.g}, ${baseRgb.b}, ${alpha})`);
|
|
root.style.setProperty('--bg-primary', `rgba(${baseRgb.r}, ${baseRgb.g}, ${baseRgb.b}, ${alpha})`);
|
|
root.style.setProperty('--bg-secondary', `rgba(${secondary.r}, ${secondary.g}, ${secondary.b}, ${alpha})`);
|
|
root.style.setProperty('--bg-tertiary', `rgba(${tertiary.r}, ${tertiary.g}, ${tertiary.b}, ${alpha})`);
|
|
root.style.setProperty('--bg-hover', `rgba(${hover.r}, ${hover.g}, ${hover.b}, ${alpha})`);
|
|
root.style.setProperty('--input-background', `rgba(${tertiary.r}, ${tertiary.g}, ${tertiary.b}, ${alpha})`);
|
|
root.style.setProperty('--input-focus-background', `rgba(${tertiary.r}, ${tertiary.g}, ${tertiary.b}, ${alpha})`);
|
|
root.style.setProperty('--hover-background', `rgba(${hover.r}, ${hover.g}, ${hover.b}, ${alpha})`);
|
|
root.style.setProperty('--scrollbar-background', `rgba(${baseRgb.r}, ${baseRgb.g}, ${baseRgb.b}, ${alpha})`);
|
|
}
|
|
|
|
// Keep old function name for backwards compatibility
|
|
applyBackgroundTransparency(alpha) {
|
|
this.applyBackgroundAppearance('#1e1e1e', alpha);
|
|
}
|
|
|
|
connectedCallback() {
|
|
super.connectedCallback();
|
|
|
|
// Apply layout mode to document root
|
|
this.updateLayoutMode();
|
|
|
|
// Set up IPC listeners if needed
|
|
if (window.require) {
|
|
const { ipcRenderer } = window.require('electron');
|
|
ipcRenderer.on('new-response', (_, response) => {
|
|
this.addNewResponse(response);
|
|
});
|
|
ipcRenderer.on('update-response', (_, response) => {
|
|
this.updateCurrentResponse(response);
|
|
});
|
|
ipcRenderer.on('update-status', (_, status) => {
|
|
this.setStatus(status);
|
|
});
|
|
ipcRenderer.on('click-through-toggled', (_, isEnabled) => {
|
|
this._isClickThrough = isEnabled;
|
|
});
|
|
ipcRenderer.on('reconnect-failed', (_, data) => {
|
|
this.addNewResponse(data.message);
|
|
});
|
|
}
|
|
}
|
|
|
|
disconnectedCallback() {
|
|
super.disconnectedCallback();
|
|
if (window.require) {
|
|
const { ipcRenderer } = window.require('electron');
|
|
ipcRenderer.removeAllListeners('new-response');
|
|
ipcRenderer.removeAllListeners('update-response');
|
|
ipcRenderer.removeAllListeners('update-status');
|
|
ipcRenderer.removeAllListeners('click-through-toggled');
|
|
ipcRenderer.removeAllListeners('reconnect-failed');
|
|
}
|
|
}
|
|
|
|
setStatus(text) {
|
|
this.statusText = text;
|
|
|
|
// Mark response as complete when we get certain status messages
|
|
if (text.includes('Ready') || text.includes('Listening') || text.includes('Error')) {
|
|
this._currentResponseIsComplete = true;
|
|
console.log('[setStatus] Marked current response as complete');
|
|
}
|
|
}
|
|
|
|
addNewResponse(response) {
|
|
// Add a new response entry (first word of a new AI response)
|
|
this.responses = [...this.responses, response];
|
|
this.currentResponseIndex = this.responses.length - 1;
|
|
this._awaitingNewResponse = false;
|
|
console.log('[addNewResponse] Added:', response);
|
|
this.requestUpdate();
|
|
}
|
|
|
|
updateCurrentResponse(response) {
|
|
// Update the current response in place (streaming subsequent words)
|
|
if (this.responses.length > 0) {
|
|
this.responses = [...this.responses.slice(0, -1), response];
|
|
console.log('[updateCurrentResponse] Updated to:', response);
|
|
} else {
|
|
// Fallback: if no responses exist, add as new
|
|
this.addNewResponse(response);
|
|
}
|
|
this.requestUpdate();
|
|
}
|
|
|
|
// Header event handlers
|
|
handleCustomizeClick() {
|
|
this.currentView = 'customize';
|
|
this.requestUpdate();
|
|
}
|
|
|
|
handleHelpClick() {
|
|
this.currentView = 'help';
|
|
this.requestUpdate();
|
|
}
|
|
|
|
handleHistoryClick() {
|
|
this.currentView = 'history';
|
|
this.requestUpdate();
|
|
}
|
|
|
|
async handleClose() {
|
|
if (this.currentView === 'customize' || this.currentView === 'help' || this.currentView === 'history') {
|
|
this.currentView = 'main';
|
|
} else if (this.currentView === 'assistant') {
|
|
mastermind.stopCapture();
|
|
|
|
// Close the session
|
|
if (window.require) {
|
|
const { ipcRenderer } = window.require('electron');
|
|
await ipcRenderer.invoke('close-session');
|
|
}
|
|
this.sessionActive = false;
|
|
this.currentView = 'main';
|
|
console.log('Session closed');
|
|
} else {
|
|
// Quit the entire application
|
|
if (window.require) {
|
|
const { ipcRenderer } = window.require('electron');
|
|
await ipcRenderer.invoke('quit-application');
|
|
}
|
|
}
|
|
}
|
|
|
|
async handleHideToggle() {
|
|
if (window.require) {
|
|
const { ipcRenderer } = window.require('electron');
|
|
await ipcRenderer.invoke('toggle-window-visibility');
|
|
}
|
|
}
|
|
|
|
// Main view event handlers
|
|
async handleStart() {
|
|
// check if api key is empty do nothing
|
|
const apiKey = await mastermind.storage.getApiKey();
|
|
if (!apiKey || apiKey === '') {
|
|
// Trigger the red blink animation on the API key input
|
|
const mainView = this.shadowRoot.querySelector('main-view');
|
|
if (mainView && mainView.triggerApiKeyError) {
|
|
mainView.triggerApiKeyError();
|
|
}
|
|
return;
|
|
}
|
|
|
|
await mastermind.initializeGemini(this.selectedProfile, this.selectedLanguage);
|
|
// Pass the screenshot interval as string (including 'manual' option)
|
|
mastermind.startCapture(this.selectedScreenshotInterval, this.selectedImageQuality);
|
|
this.responses = [];
|
|
this.currentResponseIndex = -1;
|
|
this.startTime = Date.now();
|
|
this.currentView = 'assistant';
|
|
}
|
|
|
|
async handleAPIKeyHelp() {
|
|
if (window.require) {
|
|
const { ipcRenderer } = window.require('electron');
|
|
await ipcRenderer.invoke('open-external', 'https://cheatingdaddy.com/help/api-key');
|
|
}
|
|
}
|
|
|
|
// Customize view event handlers
|
|
async handleProfileChange(profile) {
|
|
this.selectedProfile = profile;
|
|
await mastermind.storage.updatePreference('selectedProfile', profile);
|
|
}
|
|
|
|
async handleLanguageChange(language) {
|
|
this.selectedLanguage = language;
|
|
await mastermind.storage.updatePreference('selectedLanguage', language);
|
|
}
|
|
|
|
async handleScreenshotIntervalChange(interval) {
|
|
this.selectedScreenshotInterval = interval;
|
|
await mastermind.storage.updatePreference('selectedScreenshotInterval', interval);
|
|
}
|
|
|
|
async handleImageQualityChange(quality) {
|
|
this.selectedImageQuality = quality;
|
|
await mastermind.storage.updatePreference('selectedImageQuality', quality);
|
|
}
|
|
|
|
handleBackClick() {
|
|
this.currentView = 'main';
|
|
this.requestUpdate();
|
|
}
|
|
|
|
// Help view event handlers
|
|
async handleExternalLinkClick(url) {
|
|
if (window.require) {
|
|
const { ipcRenderer } = window.require('electron');
|
|
await ipcRenderer.invoke('open-external', url);
|
|
}
|
|
}
|
|
|
|
// Assistant view event handlers
|
|
async handleSendText(message) {
|
|
const result = await window.mastermind.sendTextMessage(message);
|
|
|
|
if (!result.success) {
|
|
console.error('Failed to send message:', result.error);
|
|
this.setStatus('Error sending message: ' + result.error);
|
|
} else {
|
|
this.setStatus('Message sent...');
|
|
this._awaitingNewResponse = true;
|
|
}
|
|
}
|
|
|
|
handleResponseIndexChanged(e) {
|
|
this.currentResponseIndex = e.detail.index;
|
|
this.shouldAnimateResponse = false;
|
|
this.requestUpdate();
|
|
}
|
|
|
|
// Onboarding event handlers
|
|
handleOnboardingComplete() {
|
|
this.currentView = 'main';
|
|
}
|
|
|
|
updated(changedProperties) {
|
|
super.updated(changedProperties);
|
|
|
|
// Only notify main process of view change if the view actually changed
|
|
if (changedProperties.has('currentView') && window.require) {
|
|
const { ipcRenderer } = window.require('electron');
|
|
ipcRenderer.send('view-changed', this.currentView);
|
|
|
|
// Add a small delay to smooth out the transition
|
|
const viewContainer = this.shadowRoot?.querySelector('.view-container');
|
|
if (viewContainer) {
|
|
viewContainer.classList.add('entering');
|
|
requestAnimationFrame(() => {
|
|
viewContainer.classList.remove('entering');
|
|
});
|
|
}
|
|
}
|
|
|
|
if (changedProperties.has('layoutMode')) {
|
|
this.updateLayoutMode();
|
|
}
|
|
}
|
|
|
|
renderCurrentView() {
|
|
// Only re-render the view if it hasn't been cached or if critical properties changed
|
|
const viewKey = `${this.currentView}-${this.selectedProfile}-${this.selectedLanguage}`;
|
|
|
|
switch (this.currentView) {
|
|
case 'onboarding':
|
|
return html`
|
|
<onboarding-view .onComplete=${() => this.handleOnboardingComplete()} .onClose=${() => this.handleClose()}></onboarding-view>
|
|
`;
|
|
|
|
case 'main':
|
|
return html`
|
|
<main-view
|
|
.onStart=${() => this.handleStart()}
|
|
.onAPIKeyHelp=${() => this.handleAPIKeyHelp()}
|
|
.onLayoutModeChange=${layoutMode => this.handleLayoutModeChange(layoutMode)}
|
|
></main-view>
|
|
`;
|
|
|
|
case 'customize':
|
|
return html`
|
|
<customize-view
|
|
.selectedProfile=${this.selectedProfile}
|
|
.selectedLanguage=${this.selectedLanguage}
|
|
.selectedScreenshotInterval=${this.selectedScreenshotInterval}
|
|
.selectedImageQuality=${this.selectedImageQuality}
|
|
.layoutMode=${this.layoutMode}
|
|
.onProfileChange=${profile => this.handleProfileChange(profile)}
|
|
.onLanguageChange=${language => this.handleLanguageChange(language)}
|
|
.onScreenshotIntervalChange=${interval => this.handleScreenshotIntervalChange(interval)}
|
|
.onImageQualityChange=${quality => this.handleImageQualityChange(quality)}
|
|
.onLayoutModeChange=${layoutMode => this.handleLayoutModeChange(layoutMode)}
|
|
></customize-view>
|
|
`;
|
|
|
|
case 'help':
|
|
return html` <help-view .onExternalLinkClick=${url => this.handleExternalLinkClick(url)}></help-view> `;
|
|
|
|
case 'history':
|
|
return html` <history-view></history-view> `;
|
|
|
|
case 'assistant':
|
|
return html`
|
|
<assistant-view
|
|
.responses=${this.responses}
|
|
.currentResponseIndex=${this.currentResponseIndex}
|
|
.selectedProfile=${this.selectedProfile}
|
|
.aiProvider=${this.aiProvider}
|
|
.onSendText=${message => this.handleSendText(message)}
|
|
.shouldAnimateResponse=${this.shouldAnimateResponse}
|
|
@response-index-changed=${this.handleResponseIndexChanged}
|
|
@response-animation-complete=${() => {
|
|
this.shouldAnimateResponse = false;
|
|
this._currentResponseIsComplete = true;
|
|
console.log('[response-animation-complete] Marked current response as complete');
|
|
this.requestUpdate();
|
|
}}
|
|
></assistant-view>
|
|
`;
|
|
|
|
default:
|
|
return html`<div>Unknown view: ${this.currentView}</div>`;
|
|
}
|
|
}
|
|
|
|
render() {
|
|
const viewClassMap = {
|
|
assistant: 'assistant-view',
|
|
onboarding: 'onboarding-view',
|
|
customize: 'settings-view',
|
|
help: 'help-view',
|
|
history: 'history-view',
|
|
};
|
|
const mainContentClass = `main-content ${viewClassMap[this.currentView] || 'with-border'}`;
|
|
|
|
return html`
|
|
<div class="window-container">
|
|
<div class="container">
|
|
<app-header
|
|
.currentView=${this.currentView}
|
|
.statusText=${this.statusText}
|
|
.startTime=${this.startTime}
|
|
.aiProvider=${this.aiProvider}
|
|
.modelInfo=${this.modelInfo}
|
|
.onCustomizeClick=${() => this.handleCustomizeClick()}
|
|
.onHelpClick=${() => this.handleHelpClick()}
|
|
.onHistoryClick=${() => this.handleHistoryClick()}
|
|
.onCloseClick=${() => this.handleClose()}
|
|
.onBackClick=${() => this.handleBackClick()}
|
|
.onHideToggleClick=${() => this.handleHideToggle()}
|
|
?isClickThrough=${this._isClickThrough}
|
|
></app-header>
|
|
<div class="${mainContentClass}">
|
|
<div class="view-container">${this.renderCurrentView()}</div>
|
|
</div>
|
|
</div>
|
|
${this.showScreenPicker
|
|
? html`
|
|
<screen-picker-dialog
|
|
?visible=${this.showScreenPicker}
|
|
.sources=${this.screenSources}
|
|
@source-selected=${this.handleSourceSelected}
|
|
@cancelled=${this.handlePickerCancelled}
|
|
></screen-picker-dialog>
|
|
`
|
|
: ''}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
updateLayoutMode() {
|
|
// Apply or remove compact layout class to document root
|
|
if (this.layoutMode === 'compact') {
|
|
document.documentElement.classList.add('compact-layout');
|
|
} else {
|
|
document.documentElement.classList.remove('compact-layout');
|
|
}
|
|
}
|
|
|
|
async handleLayoutModeChange(layoutMode) {
|
|
this.layoutMode = layoutMode;
|
|
await mastermind.storage.updateConfig('layout', layoutMode);
|
|
this.updateLayoutMode();
|
|
|
|
// Notify main process about layout change for window resizing
|
|
if (window.require) {
|
|
try {
|
|
const { ipcRenderer } = window.require('electron');
|
|
await ipcRenderer.invoke('update-sizes');
|
|
} catch (error) {
|
|
console.error('Failed to update sizes in main process:', error);
|
|
}
|
|
}
|
|
|
|
this.requestUpdate();
|
|
}
|
|
|
|
async showScreenPickerDialog() {
|
|
const { ipcRenderer } = window.require('electron');
|
|
const result = await ipcRenderer.invoke('get-screen-sources');
|
|
|
|
if (result.success) {
|
|
this.screenSources = result.sources;
|
|
this.showScreenPicker = true;
|
|
return new Promise(resolve => {
|
|
this._screenPickerResolve = resolve;
|
|
});
|
|
} else {
|
|
console.error('Failed to get screen sources:', result.error);
|
|
return { cancelled: true };
|
|
}
|
|
}
|
|
|
|
async handleSourceSelected(event) {
|
|
const { source } = event.detail;
|
|
const { ipcRenderer } = window.require('electron');
|
|
|
|
// Tell main process which source was selected
|
|
await ipcRenderer.invoke('set-selected-source', source.id);
|
|
|
|
this.showScreenPicker = false;
|
|
if (this._screenPickerResolve) {
|
|
this._screenPickerResolve({ source });
|
|
this._screenPickerResolve = null;
|
|
}
|
|
}
|
|
|
|
handlePickerCancelled() {
|
|
this.showScreenPicker = false;
|
|
if (this._screenPickerResolve) {
|
|
this._screenPickerResolve({ cancelled: true });
|
|
this._screenPickerResolve = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
customElements.define('mastermind-app', MastermindApp);
|