diff --git a/dashboard/dashboard.js b/dashboard/dashboard.js
index e1d7981..c1b7f47 100644
--- a/dashboard/dashboard.js
+++ b/dashboard/dashboard.js
@@ -11,10 +11,12 @@ namespace("com.subnodal.nanoplay.website.dashboard", function(exports) {
var subElements = require("com.subnodal.subelements");
var resources = require("com.subnodal.nanoplay.website.resources");
+ var dialogs = require("com.subnodal.nanoplay.website.dialogs");
var darkThemeEnabled = false;
window.dashboard = exports;
+ window.dialogs = dialogs;
exports.toggleTheme = function() {
darkThemeEnabled = !darkThemeEnabled;
diff --git a/dashboard/editor.html b/dashboard/editor.html
index 9c5f1ed..3191166 100644
--- a/dashboard/editor.html
+++ b/dashboard/editor.html
@@ -57,10 +57,10 @@
{{ editor.getAppName() }}
settings
-
+
-
+
diff --git a/dashboard/editor.js b/dashboard/editor.js
index 8450c2c..b91a9a9 100644
--- a/dashboard/editor.js
+++ b/dashboard/editor.js
@@ -19,11 +19,9 @@ namespace("com.subnodal.nanoplay.website.editor", function(exports) {
var resources = require("com.subnodal.nanoplay.website.resources");
var dialogs = require("com.subnodal.nanoplay.website.dialogs");
var communications = require("com.subnodal.nanoplay.website.communications");
- var dashboard = require("com.subnodal.nanoplay.website.dashboard");
var simulator = require("com.subnodal.nanoplay.website.simulator");
window.editor = exports;
- window.dialogs = dialogs;
const SUPPORTED_LANGUAGES = ["en_GB", "fr_FR"];
const DEFAULT_APP_ICON = "AAAAAAAAA////AAAf///YAAH///+AABAAAAgAAX/vgIAAEAAACAABe79wgAAQAAAIAAF+94CAABAAAAgAAW+/4IAAEAAACAABfvv4gAAQAAAIAAD///8AAAAAAAAAA==";
diff --git a/dashboard/manager.html b/dashboard/manager.html
index e23d93a..57afbab 100644
--- a/dashboard/manager.html
+++ b/dashboard/manager.html
@@ -52,8 +52,60 @@
-
Manager coming soon!
-
Our NanoPlay manager is still in development! Come back later when we have something to show.
+
+
+ manager_connect
+ manager_connectDescription
+
+
+
+
+
+ manager_noBluetooth
+
+
+
+
diff --git a/dashboard/manager.js b/dashboard/manager.js
index 90f2e73..5a0f0bc 100644
--- a/dashboard/manager.js
+++ b/dashboard/manager.js
@@ -8,5 +8,125 @@
*/
namespace("com.subnodal.nanoplay.website.manager", function(exports) {
+ var subElements = require("com.subnodal.subelements");
+ var nanoplay = require("com.subnodal.nanoplay.webapi");
+ window.manager = exports;
+
+ exports.MAXIMUM_FREE_STORAGE = 8_712; // Bytes
+
+ exports.pages = {
+ GENERAL: 0,
+ APPS: 1
+ };
+
+ var currentPage = exports.pages.GENERAL;
+ var currentNanoplay = new nanoplay.NanoPlay();
+ var loadedAppsList = {};
+ var loadedFreeStorage = 0;
+
+ exports.getCurrentPage = function() {
+ return currentPage;
+ }
+
+ exports.setCurrentPage = function(page) {
+ currentPage = page;
+
+ subElements.render();
+ };
+
+ exports.getCurrentNanoplay = function() {
+ return currentNanoplay;
+ };
+
+ exports.showLoading = function(loadingMessage = _("manager_applyingSetting")) {
+ document.querySelector("#loadingMessage").textContent = loadingMessage;
+
+ document.querySelector("#loading").removeAttribute("hidden");
+ };
+
+ exports.hideLoading = function() {
+ document.querySelector("#loading").setAttribute("hidden", "");
+ };
+
+ exports.getAppsList = function() {
+ exports.showLoading(_("manager_gettingApps"));
+
+ return currentNanoplay.getApps().then(function(apps) {
+ loadedAppsList = apps;
+
+ manager.setCurrentPage(manager.pages.APPS);
+
+ exports.hideLoading();
+
+ return Promise.resolve();
+ });
+ };
+
+ exports.getLoadedAppsList = function() {
+ return loadedAppsList;
+ };
+
+ exports.getFreeStorage = function() {
+ return currentNanoplay.getFreeStorage().then(function(data) {
+ loadedFreeStorage = data;
+
+ subElements.render();
+
+ return Promise.resolve();
+ });
+ };
+
+ exports.getLoadedFreeStorage = function() {
+ return loadedFreeStorage;
+ };
+
+ exports.deleteApp = function(appId) {
+ exports.showLoading(_("manager_gettingApps"));
+
+ return currentNanoplay.removeApp(appId).then(function() {
+ delete loadedAppsList[appId];
+
+ return exports.getFreeStorage().then(function() {
+ exports.hideLoading();
+
+ return Promise.resolve();
+ });
+ });
+ };
+
+ exports.connect = function() {
+ document.querySelector("#connectButton").textContent = _("connecting");
+ document.querySelector("#connectButton").disabled = true;
+
+ return currentNanoplay.connect().then(function() {
+ document.querySelector("#connectButton").textContent = _("connect");
+ document.querySelector("#connectButton").disabled = false;
+
+ document.querySelector("#connectError").textContent = "";
+
+ return exports.getFreeStorage();
+ }).catch(function(error) {
+ document.querySelector("#connectButton").textContent = _("connect");
+ document.querySelector("#connectButton").disabled = false;
+
+ document.querySelector("#connectError").textContent = _("manager_connectionError");
+
+ console.error(error);
+
+ return Promise.resolve();
+ });
+ };
+
+ subElements.ready(function() {
+ document.querySelectorAll("#managerOptions, #managerOptions div button, #managerOptions div label").forEach(function(element) {
+ element.addEventListener("mouseover", function(event) {
+ var previewScreen = element.getAttribute("data-preview") || "home";
+
+ document.querySelector("#managerPreview .device .screen").style.backgroundImage = `url("/media/manager/${previewScreen}.png")`;
+
+ event.stopPropagation();
+ });
+ });
+ });
});
\ No newline at end of file
diff --git a/dialogs.js b/dialogs.js
index 8ed9624..fdd638d 100644
--- a/dialogs.js
+++ b/dialogs.js
@@ -13,7 +13,7 @@ namespace("com.subnodal.nanoplay.website.dialogs", function(exports) {
exports.open = function(dialogId) {
document.getElementById(dialogId).setAttribute("open", "");
- document.querySelector("dialog[open] a, dialog[open] input, dialog[open] button").focus();
+ document.querySelector("dialog[open] a, dialog[open] input, dialog[open] button")?.focus();
};
exports.close = function(dialogId) {
diff --git a/locale/en_GB.json b/locale/en_GB.json
index 1c2abf1..61b7616 100644
--- a/locale/en_GB.json
+++ b/locale/en_GB.json
@@ -10,6 +10,18 @@
"shop": "Shop",
"dashboard": "Dashboard",
+ "ok": "OK",
+ "cancel": "Cancel",
+ "done": "Done",
+ "open": "Open",
+ "edit": "Edit",
+ "delete": "Delete",
+ "connect": "Connect",
+ "connecting": "Connecting...",
+ "upload": "Upload",
+ "uploading": "Uploading...",
+ "untitled": "Untitled",
+
"signIn_title": "Let's go!",
"signIn_description": "Sign in with your Subnodal Account to get coding! Your code, apps, settings and more will all be stored with your Subnodal Account for you to access from anywhere.",
"signIn_signUpHint": "Don't have a Subnodal Account yet? Signing up is free and easy!",
@@ -32,8 +44,6 @@
"editor_noAppsYet": "No apps yet!",
"editor_noAppsYetMessage": "Once you've coded some apps, they'll appear here for you to open later.",
"editor_sendToNanoplay": "Send to NanoPlay",
- "editor_connecting": "Connecting...",
- "editor_uploading": "Uploading...",
"editor_noErrorsYet": "No errors yet!",
"editor_noBluetooth": "No Bluetooth? Don't worry! Visit the NanoPlay editor on your mobile phone to connect to your NanoPlay.",
"editor_noBluetoothQrCode": "Point your phone's camera at this QR code to quickly visit the NanoPlay editor",
@@ -62,13 +72,17 @@
"editor_logSource_simulator": "Simulator",
"manager": "Manager",
+ "manager_connect": "Connect to your NanoPlay",
+ "manager_connectDescription": "To access your NanoPlay's settings, you'll need to connect to your NanoPlay using Bluetooth.",
+ "manager_connectionError": "Looks like we couldsn't connect to your NanoPlay! Try connecting again.",
+ "manager_noBluetooth": "Unfortunately, this device doesn't have Bluetooth. Try using another device (such as a smartphone) instead.",
+ "manager_storageInfo": "Storage {percentage}% full · {bytesFree} bytes free",
+ "manager_noApps": "You have no apps on this NanoPlay.",
+ "manager_applyingSetting": "Applying setting...",
+ "manager_gettingApps": "Getting apps...",
+ "manager_deletingApp": "Deleting app...",
"toggleDarkMode": "Toggle dark mode",
- "backToDashboard": "Back to dashboard",
-
- "ok": "OK",
- "cancel": "Cancel",
- "done": "Done",
- "open": "Open"
+ "backToDashboard": "Back to dashboard"
}
}
\ No newline at end of file
diff --git a/style.css b/style.css
index 43f0a4c..69b0356 100644
--- a/style.css
+++ b/style.css
@@ -25,6 +25,7 @@
--simulatorBlueButton: #dceffd;
--simulatorOrangeButton: #fdf5e3;
--simulatorScreen: #7fc5f7;
+ --overlayBackground: rgba(255, 255, 255, 0.8);
}
* {
@@ -68,6 +69,7 @@ body.dark {
--blueText: white;
--orange: #f5b342;
--orangeText: white;
+ --overlayBackground: rgba(12, 36, 53, 0.8);
}
nav {
@@ -254,7 +256,7 @@ button.orange {
color: var(--orangeText);
}
-input {
+input, select {
padding: 10px;
padding-left: 1em;
padding-right: 1em;
@@ -287,7 +289,7 @@ label span:first-child {
vertical-align: middle;
}
-label input {
+label input, label select {
width: calc(100% - 210px);
vertical-align: middle;
}
@@ -319,57 +321,43 @@ pre strong.error {
color: var(--red);
}
+progress {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ height: 0.8em;
+ padding: 0;
+ overflow: hidden;
+}
+
+progress::-webkit-progress-bar {
+ background-color: var(--shade);
+ border-radius: 10px;
+ overflow: hidden;
+}
+
+progress::-webkit-progress-value {
+ background-color: var(--blue);
+ border-radius: 10px;
+ transition: width 0.5s;
+}
+
+progress::-moz-progress-bar {
+ background-color: var(--blue);
+ border-radius: 10px;
+}
+
loader {
position: relative;
display: block;
- width: calc(2em * 2);
- height: calc(2em * 2);
- margin: calc(0.5em + 5px) auto;
+ width: calc(2rem * 2);
+ height: calc(2rem * 2);
+ margin: calc(0.5rem + 5px) auto;
+ background-color: inherit;
color: var(--blue);
border-radius: 50%;
box-shadow: inset 0 0 0 10px;
- -moz-transform: translateZ(0);
- -webkit-transform: translateZ(0);
- transform: translateZ(0);
-}
-
-loader::before, loader::after {
- position: absolute;
- content: "";
-}
-
-loader::before {
- left: -2.5px;
- width: calc(2em + 5px);
- height: calc(calc(2em * 2) + 5px);
- background-color: var(--background);
- border-radius: calc(calc(2em * 2) + 5px) 0 0 calc(calc(2em * 2) + 5px);
- -moz-transform-origin: calc(2em + 5px) calc(2em + 1px);
- -webkit-transform-origin: calc(2em + 5px) calc(2em + 1px);
- transform-origin: calc(2em + 5px) calc(2em + 1px);
- -moz-transform: rotate(-180deg);
- -webkit-transform: rotate(-180deg);
- transform: rotate(-180deg);
- -moz-animation: spinner 2s infinite ease 1.5s;
- -webkit-animation: spinner 2s infinite ease 1.5s;
- animation: spinner 2s infinite ease 1.5s;
-}
-
-loader::after {
- left: calc(2em - 2.5px);
- width: calc(2em + 5px);
- height: calc(calc(2em * 2) + 5px);
- background-color: var(--background);
- border-radius: 0 calc(calc(2em * 2) + 5px) calc(calc(2em * 2) + 5px) 0;
- -moz-transform-origin: 0 calc(2em + 1px);
- -webkit-transform-origin: 0 calc(2em + 1px);
- transform-origin: 0 calc(2em + 1px);
- -moz-transform: rotate(-180deg);
- -webkit-transform: rotate(-180deg);
- transform: rotate(-180deg);
- -moz-animation: spinner 2s infinite ease;
- -webkit-animation: spinner 2s infinite ease;
- animation: spinner 2s infinite ease;
+ animation: loader 2s infinite linear;
}
loader.entireScreen {
@@ -804,16 +792,118 @@ body.dark .qrCode {
#managerOptions {
display: flex;
+ position: relative;
+ align-items: center;
+}
+
+#managerOptions #loading {
+ display: flex;
+ position: absolute;
align-items: center;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ width: 100%;
+ height: 100%;
+ max-height: unset;
+ margin: 0;
+ padding: 0;
+ background-color: var(--overlayBackground);
+ transition: 0.5s opacity;
+}
+
+#managerOptions #loading[hidden] {
+ opacity: 0;
+ pointer-events: none;
+}
+
+#managerOptions #loading div {
+ text-align: center;
+}
+
+#managerOptions #loading div p {
+ font-size: 1em;
}
#managerOptions div {
width: 100%;
- max-height: 60vh;
- margin-inline-end: 5vw;
+ max-height: 80vh;
+ margin-inline-end: calc(5vw - 1em);
+ padding-inline-end: 1em;
overflow: auto;
}
+#managerOptions .menu h1 {
+ font-size: 1.2em;
+}
+
+#managerOptions .menu h2 {
+ font-size: 1em;
+}
+
+#managerOptions .menu p {
+ font-size: 0.8em;
+}
+
+#managerOptions .menu button:not(.back):not(.app *), #managerOptions .menu .app {
+ display: block;
+ width: 100%;
+ padding: 10px;
+ padding-left: 1em;
+ padding-right: 1em;
+ margin-top: 5px;
+ margin-bottom: 5px;
+ background-color: var(--shade);
+ color: var(--shadeText);
+ border-radius: 10px;
+ text-align: left;
+}
+
+#managerOptions .menu button.back {
+ display: block;
+}
+
+#managerOptions .menu label {
+ display: block;
+ padding: 10px;
+ background-color: var(--shade);
+ color: var(--shadeText);
+ border-radius: 10px;
+}
+
+#managerOptions .menu label span {
+ display: block;
+ width: calc(100% - 2rem - 10px);
+ margin-left: calc(1rem - 5px);
+ margin-right: calc(1rem - 5px);
+ margin-bottom: 10px;
+ font-size: 0.8em;
+}
+
+#managerOptions .menu label input, #managerOptions .menu label select {
+ width: 100%;
+ padding-left: 10px;
+ padding-right: 10px;
+ background-color: var(--background);
+ color: var(--foreground);
+}
+
+#managerOptions .menu progress {
+ width: 100%;
+}
+
+#managerOptions .menu .app strong, #managerOptions .menu .app span {
+ display: block;
+}
+
+#managerOptions .menu .app .options {
+ margin-top: 5px;
+ margin-bottom: 5px;
+ padding-inline-end: 0;
+ text-align: end;
+}
+
@media (max-width: 800px) {
.simulator {
width: 30vw;
@@ -884,7 +974,7 @@ body.dark .qrCode {
align-items: flex-start;
}
- #managerOptions div {
+ #managerOptions .menu {
max-height: calc(100vh - 60vw - 5em);
margin-inline-end: 0;
}
@@ -901,45 +991,28 @@ body.dark .qrCode {
}
}
-@-moz-keyframes spinner {
+@keyframes loader {
0% {
- -moz-transform: rotate(0deg);
- -webkit-transform: rotate(0deg);
- transform: rotate(0deg);
- }
-
- 100% {
- -moz-transform: rotate(360deg);
- -webkit-transform: rotate(360deg);
- transform: rotate(360deg);
+ clip-path: polygon(50% 50%, 50% 0%, 0% 0, 0% 100%, 100% 100%, 100% 0%);
+ transform: rotateZ(0deg);
}
-}
-@-webkit-keyframes spinner {
- 0% {
- -moz-transform: rotate(0deg);
- -webkit-transform: rotate(0deg);
- transform: rotate(0deg);
+ 25% {
+ clip-path: polygon(50% 50%, 50% 0%, 0% 0, 0% 100%, 100% 100%, 100% 100%);
}
- 100% {
- -moz-transform: rotate(360deg);
- -webkit-transform: rotate(360deg);
- transform: rotate(360deg);
+ 50% {
+ clip-path: polygon(50% 50%, 50% 0%, 0% 0, 0% 100%, 100% 100%, 0% 100%);
+ transform: rotateZ(360deg);
}
-}
-@keyframes spinner {
- 0% {
- -moz-transform: rotate(0deg);
- -webkit-transform: rotate(0deg);
- transform: rotate(-180deg);
+ 75% {
+ clip-path: polygon(50% 50%, 50% 0%, 0% 0, 0% 100%, 100% 100%, 100% 100%);
}
100% {
- -moz-transform: rotate(360deg);
- -webkit-transform: rotate(360deg);
- transform: rotate(180deg);
+ clip-path: polygon(50% 50%, 50% 0%, 0% 0, 0% 100%, 100% 100%, 100% 0%);
+ transform: rotateZ(720deg);
}
}