Skip to content

Commit dc5d5f5

Browse files
authored
Merge pull request #473 from circuitpython/beta
Pull in Beta Features
2 parents 2db1006 + b58df41 commit dc5d5f5

13 files changed

Lines changed: 539 additions & 137 deletions

File tree

index.html

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
</ul>
9191
</nav>
9292
</div>
93-
<div id="editor"></div>
93+
<div id="editor" tabindex="0"></div>
9494
</div>
9595
<div id="page-separator" class=""></div>
9696
<div id="serial-page" class="">
@@ -113,13 +113,14 @@
113113
</select>
114114
<canvas id="plotter-canvas"></canvas>
115115
</div>
116-
<div id="terminal"></div>
116+
<div id="terminal" tabindex="1"></div>
117117
</div>
118118
</div>
119119
<div id="footer-bar">
120120
<button id="btn-mode-editor" class="mode-button active">Editor</button>
121121
<button id="btn-mode-serial" class="mode-button">Serial</button>
122122
<div class="spacer"></div>
123+
<button class="purple-button btn-settings">Settings<i class="fa-solid fa-gear"></i></button>
123124
<button class="purple-button btn-info" disabled>Info<i class="fa-solid fa-info-circle"></i></button>
124125
</div>
125126
</div>
@@ -153,6 +154,14 @@
153154
<button class="purple-button cancel-button">Cancel</button>
154155
</div>
155156
</div>
157+
<div class="popup-modal shadow settings-dialog closable" data-popup-modal="settings">
158+
<i class="fa-solid fa-2x fa-xmark text-white bg-primary p-3 popup-modal__close"></i>
159+
<div id="settings-content"></div>
160+
<div class="buttons centered">
161+
<button class="purple-button ok-button">Save</button>
162+
<button class="purple-button cancel-button">Cancel</button>
163+
</div>
164+
</div>
156165
<div class="popup-modal shadow file-dialog closable" data-popup-modal="folder-select">
157166
<span id="current-path"></span>
158167
<i class="fa-solid fa-2x fa-xmark text-white bg-primary p-3 popup-modal__close"></i>
@@ -186,12 +195,27 @@
186195
<button id="usb-workflow" class="purple-button files-button" value="usb">USB<i class="fa-brands fa-usb"></i></button>
187196
</div>
188197
</div>
198+
<div class="popup-modal shadow prompt closable" data-popup-modal="ok-cancel">
199+
<div id="message"></div>
200+
<div class="buttons centered">
201+
<button class="purple-button ok-button" value="ok">OK</button>
202+
<button class="purple-button cancel-button" value="cancel">Cancel</button>
203+
</div>
204+
</div>
189205
<div class="popup-modal shadow prompt closable" data-popup-modal="message">
190206
<div id="message"></div>
191207
<div class="buttons centered">
192208
<button class="purple-button ok-button">Ok</button>
193209
</div>
194210
</div>
211+
<div class="popup-modal shadow prompt" data-popup-modal="input">
212+
<div id="message"></div>
213+
<input type="text" id="inputvalue" />
214+
<div class="buttons centered">
215+
<button class="purple-button ok-button">Ok</button>
216+
<button class="purple-button cancel-button">Cancel</button>
217+
</div>
218+
</div>
195219
<div class="popup-modal shadow prompt" data-popup-modal="progress" data-tabbable="false">
196220
<div class="label centered" id="status"></div>
197221
<div class="label centered" id="percentage"></div>

js/common/dialogs.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,26 @@ class MessageModal extends GenericModal {
241241
}
242242
}
243243

244+
class InputModal extends GenericModal {
245+
_handleOkButton(event) {
246+
this._returnValue(this._getElement('inputValueField').value);
247+
}
248+
249+
async open(message, defaultValue="") {
250+
let p = super.open();
251+
const cancelButton = this._currentModal.querySelector("button.cancel-button");
252+
this._addDialogElement('cancelButton', cancelButton, 'click', this._closeModal);
253+
const okButton = this._currentModal.querySelector("button.ok-button");
254+
this._addDialogElement('okButton', okButton, 'click', this._handleOkButton);
255+
const inputValueField = this._currentModal.querySelector("#inputvalue");
256+
this._addDialogElement('inputValueField', inputValueField);
257+
this._setElementValue('inputValueField', defaultValue);
258+
this._currentModal.querySelector("#message").innerHTML = message;
259+
260+
return p;
261+
}
262+
}
263+
244264
class ProgressDialog extends GenericModal {
245265
async open() {
246266
let p = super.open();
@@ -424,5 +444,6 @@ export {
424444
UnsavedDialog,
425445
DiscoveryModal,
426446
ProgressDialog,
427-
DeviceInfoModal
428-
};
447+
DeviceInfoModal,
448+
InputModal
449+
};

js/common/file_dialog.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {GenericModal, ProgressDialog, ButtonValueDialog} from './dialogs.js';
1+
import {GenericModal, ProgressDialog, ButtonValueDialog, InputModal} from './dialogs.js';
22
import {readUploadedFileAsArrayBuffer} from './utilities.js';
33
import {saveAs} from 'file-saver';
44
import JSZip from 'jszip';
@@ -321,6 +321,11 @@ class FileDialog extends GenericModal {
321321
return selectedItems > 1;
322322
}
323323

324+
async _prompt(message, defaultValue="") {
325+
const inputModal = new InputModal("input");
326+
return await inputModal.open(message, defaultValue);
327+
}
328+
324329
_updateToolbar() {
325330
this._setElementEnabled('delButton', this._canPerformWritableFileOperation());
326331
this._setElementEnabled('renameButton', !this._multipleItemsSelected() && this._canPerformWritableFileOperation());
@@ -653,7 +658,8 @@ class FileDialog extends GenericModal {
653658
return;
654659
}
655660
oldName = oldName[0];
656-
let newName = prompt("Enter a new folder name", oldName);
661+
let newName = await this._prompt("Enter a new name", oldName);
662+
657663
// If cancelled, do nothing
658664
if (!newName) {
659665
return;
@@ -688,7 +694,7 @@ class FileDialog extends GenericModal {
688694
async _handleNewFolderButton() {
689695
if (this._readOnlyMode) return;
690696
// prompt for new folder name
691-
let folderName = prompt("Enter a new folder name");
697+
let folderName = await this._prompt("Enter a new folder name");
692698
// If cancelled, do nothing
693699
if (!folderName) {
694700
return;

js/common/settings.js

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import {GenericModal} from './dialogs.js';
2+
3+
class SettingsDialog extends GenericModal {
4+
constructor(modalId, settingsData) {
5+
super(modalId);
6+
this._settingsData = settingsData;
7+
this._settings = {}
8+
}
9+
10+
async open(settings) {
11+
let p = super.open();
12+
const cancelButton = this._currentModal.querySelector("button.cancel-button");
13+
this._addDialogElement('cancelButton', cancelButton, 'click', this._closeModal);
14+
const okButton = this._currentModal.querySelector("button.ok-button");
15+
this._addDialogElement('okButton', okButton, 'click', this._handleOkButton);
16+
17+
const contentDiv = this._currentModal.querySelector("#settings-content");
18+
contentDiv.innerHTML = '';
19+
20+
for (const setting of this._settingsData) {
21+
const label = document.createElement('label');
22+
label.textContent = setting.label;
23+
if (setting.icon) {
24+
const icon = document.createElement('i');
25+
icon.className = `fa-solid fa-${setting.icon} setting-item-icon`;
26+
label.prepend(icon);
27+
}
28+
label.htmlFor = `setting-${setting.key}`;
29+
contentDiv.appendChild(label);
30+
31+
const control = await this._createControl(setting);
32+
control.value = settings[setting.key];
33+
contentDiv.appendChild(control);
34+
}
35+
36+
return p;
37+
}
38+
39+
async _handleOkButton() {
40+
let settings = {}
41+
for (const setting of this._settingsData) {
42+
const control = this._currentModal.querySelector(`#setting-${setting.key}`);
43+
settings[setting.key] = control.value;
44+
}
45+
this._returnValue(settings);
46+
}
47+
48+
async _createControl(settingData) {
49+
// Return the created control
50+
let control;
51+
if (settingData.type === 'select') {
52+
control = document.createElement('select');
53+
for (const optionValue of settingData.options) {
54+
const option = document.createElement('option');
55+
option.value = optionValue;
56+
option.textContent = optionValue.charAt(0).toUpperCase() + optionValue.slice(1);
57+
control.appendChild(option);
58+
}
59+
}
60+
control.id = `setting-${settingData.key}`;
61+
62+
// this will also call this._addDialogElement to add event listeners as needed
63+
this._addDialogElement(`setting-${settingData.key}`, control);
64+
return control;
65+
}
66+
}
67+
68+
class Settings {
69+
// This is a class that handles loading/saving settings as well as providing a settings dialog
70+
constructor() {
71+
// This will hold the layout/save data for the settings
72+
this._settingsData = [
73+
{ key: 'theme', type: 'select', label: 'Editor Theme', icon: 'palette', options: ['dark', 'light'], default: 'dark' }
74+
];
75+
this._settings = {};
76+
this._loadSettings();
77+
78+
this._settingsDialog = new SettingsDialog('settings', this._settingsData);
79+
}
80+
81+
_loadSettings() {
82+
// Load all saved settings or defaults
83+
for (const setting of this._settingsData) {
84+
this._settings[setting.key] = this._loadSetting(setting.key, setting.default);
85+
}
86+
}
87+
88+
_saveSettings() {
89+
// Save all settings
90+
for (const key in this._settings) {
91+
this._saveSetting(key, this._settings[key]);
92+
}
93+
}
94+
95+
_loadSetting(setting, defaultValue) {
96+
let value = JSON.parse(window.localStorage.getItem(setting));
97+
if (value == null) {
98+
return defaultValue;
99+
}
100+
101+
return value;
102+
}
103+
104+
_saveSetting(setting, value) {
105+
window.localStorage.setItem(setting, JSON.stringify(value));
106+
}
107+
108+
getSetting(key) {
109+
return this._settings[key];
110+
}
111+
112+
async showDialog() {
113+
this._settings = await this._settingsDialog.open(this._settings);
114+
if (this._settings) {
115+
this._saveSettings();
116+
return true;
117+
}
118+
return false;
119+
}
120+
}
121+
122+
123+
export {
124+
Settings
125+
};

js/common/utilities.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ function isIp() {
5353

5454
// Check if the current url is a Web Workflow, IP, or Test Address and current path is /code/
5555
function isLocal() {
56-
return (isMdns() || location.hostname == "localhost" || isIp()) && (location.pathname == "/code/");
56+
return location.hostname == "localhost" || ((isMdns() || isIp()) && location.pathname == "/code/");
5757
}
5858

5959
// Test to see if browser is running on Microsoft Windows OS
@@ -67,7 +67,7 @@ function isMicrosoftWindows() {
6767
return false;
6868
}
6969

70-
// Test to see if browser is running on Microsoft Windows OS
70+
// Test to see if browser is running on Chrome OS
7171
function isChromeOs() {
7272
if (navigator.userAgent.includes("CrOS")) {
7373
return true;

js/layout.js

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,49 @@ function isSerialVisible() {
2626
return serialPage.classList.contains('active');
2727
}
2828

29+
function setupPanelFocusHandlers() {
30+
// Removing existing handlers to avoid duplicates
31+
serialPage.removeEventListener('click', handleActivePanel);
32+
editorPage.removeEventListener('click', handleActivePanel);
33+
34+
// Adding new handlers
35+
serialPage.addEventListener('click', handleActivePanel);
36+
editorPage.addEventListener('click', handleActivePanel);
37+
}
38+
39+
function handleActivePanel(event) {
40+
const panel = event.currentTarget;
41+
setActivePanel(panel);
42+
}
43+
44+
function setActivePanel(panel) {
45+
editorPage.classList.remove('focused-panel');
46+
serialPage.classList.remove('focused-panel');
47+
48+
if (panel === serialPage && isSerialVisible()) {
49+
// Serial panel requested and visible
50+
serialPage.classList.add('focused-panel');
51+
} else if (panel === editorPage && isEditorVisible()) {
52+
// Editor panel requested and visible
53+
editorPage.classList.add('focused-panel');
54+
} else {
55+
// Requested panel is not visible, set other panel as focused
56+
if (isEditorVisible()) {
57+
editorPage.classList.add('focused-panel');
58+
} else {
59+
serialPage.classList.add('focused-panel');
60+
}
61+
}
62+
}
63+
2964
async function toggleEditor() {
3065
if (isSerialVisible()) {
3166
editorPage.classList.toggle('active');
3267
saveSetting(SETTING_EDITOR_VISIBLE, isEditorVisible());
3368
updatePageLayout(UPDATE_TYPE_EDITOR);
3469
}
70+
setupPanelFocusHandlers();
71+
setActivePanel(editorPage);
3572
}
3673

3774
async function toggleSerial() {
@@ -40,6 +77,8 @@ async function toggleSerial() {
4077
saveSetting(SETTING_TERMINAL_VISIBLE, isSerialVisible());
4178
updatePageLayout(UPDATE_TYPE_SERIAL);
4279
}
80+
setupPanelFocusHandlers();
81+
setActivePanel(serialPage);
4382
}
4483

4584
btnModeEditor.removeEventListener('click', toggleEditor);
@@ -229,4 +268,6 @@ pageSeparator.addEventListener('mousedown', async function (e) {
229268

230269
fixViewportHeight();
231270
window.addEventListener("resize", fixViewportHeight);
232-
loadPanelSettings();
271+
loadPanelSettings();
272+
setupPanelFocusHandlers();
273+
setActivePanel(editorPage);

0 commit comments

Comments
 (0)