diff --git a/main-dev.js b/main-dev.js
new file mode 100644
index 0000000..4289812
--- /dev/null
+++ b/main-dev.js
@@ -0,0 +1,608 @@
+class ARMMane{
+ constructor(address = "localhost", port = "8000", protocol = "http", serverList = []) {
+ if (serverList.length == 0) {
+ serverList = [
+ {
+ "name": "Nicezki Pi (For Dev)",
+ "address": "pi.nicezki.com",
+ "port": "",
+ "protocol": "https"
+ },
+ {
+ "name": "Nicezki HTTPS (Deprecated)",
+ "address": "miri.network.nicezki.com",
+ "port": "5000",
+ "protocol": "https"
+ },
+ {
+ "name": "Local Server",
+ "address": "localhost",
+ "port": "8000",
+ "protocol": "http"
+ },
+ ];
+ }
+
+
+ this.appStatus = {
+ "connected" : false,
+ "isDisconnecting" : false,
+ "serverList" : serverList,
+ "server" : {
+ "fullURL" : protocol + "://" + address + (port ? ":" + port : ""),
+ "address" : address,
+ "port" : port,
+ "protocol" : protocol,
+ },
+ "currentScreen" : "screen_connect",
+ }
+
+
+ this.elements = {
+ "mode" : ["btn", "form", "ui", "screen", "template"],
+ "screen" : {
+ "errorloading" : document.querySelector(".scr-errload"),
+ "loading" : document.querySelector(".scr-loading"),
+ "connect" : document.querySelector(".scr-connect"),
+ "main" : document.querySelector(".scr-main"),
+ },
+ "ui" : {
+ "statusarea" : document.querySelector(".main-statusarea"),
+ "controlarea" : document.querySelector(".main-controlarea"),
+ "settingarea" : document.querySelector(".main-settingarea"),
+ "log_area" : document.querySelector(".log-area"),
+ "log_disconnect" : document.querySelector(".log-disconnect"),
+ },
+ "btn" : {
+ "status_toggle" : document.querySelector(".btn-status-toggle"),
+ "main_info" : document.querySelector(".btn-main-info"),
+ "main_control" : document.querySelector(".btn-main-control"),
+ "main_config" : document.querySelector(".btn-config"),
+ },
+ "form" : {
+ "servo_00" : document.querySelector("#form-field-s0"),
+ "servo_01" : document.querySelector("#form-field-s1"),
+ "servo_02" : document.querySelector("#form-field-s2"),
+ "servo_03" : document.querySelector("#form-field-s3"),
+ "servo_04" : document.querySelector("#form-field-s4"),
+ "servo_05" : document.querySelector("#form-field-s5"),
+ "conv_00" : document.querySelector("#form-field-conv1"),
+ "conv_01" : document.querySelector("#form-field-conv2"),
+ },
+ "template" : {
+ "btn_serverlist" : document.querySelector(".tp-btn-server"),
+ },
+ }
+
+ // this.uiElements = {
+ // "arm-status" : document.querySelector(".arm-status > div > h2"),
+ // "amn-icon" : document.querySelector(".amn-icon"),
+ // "amn-config-box" : document.querySelector(".amn-config-box"),
+ // "amn-info-box" : document.querySelector(".amn-info-box"),
+ // "btn-info" : document.querySelector(".btn-info"),
+ // "btn-control" : document.querySelector(".btn-control"),
+ // "btn-config" : document.querySelector(".btn-config"),
+ // "btn-config-inner" : document.querySelector(".btn-config > div > div > a"),
+ // "btn-preset-temp" : document.querySelector(".btn-preset-temp"),
+ // "btn-s-control" : document.querySelector(".btn-s-control"),
+ // "chip-servo-0" : document.querySelector(".chip-servo-0 div > h4"),
+ // "chip-servo-1" : document.querySelector(".chip-servo-1 div > h4"),
+ // "chip-servo-2" : document.querySelector(".chip-servo-2 div > h4"),
+ // "chip-servo-3" : document.querySelector(".chip-servo-3 div > h4"),
+ // "chip-servo-4" : document.querySelector(".chip-servo-4 div > h4"),
+ // "chip-servo-5" : document.querySelector(".chip-servo-5 div > h4"),
+ // "conf-cur-selmodel" : document.querySelector(".conf-cur-selmodel > div > h6"),
+ // "conf-cur-conv1" : document.querySelector(".conf-cur-conv1 > div > h6"),
+ // "conf-cur-conv2" : document.querySelector(".conf-cur-conv2 > div > h6"),
+ // "form-field-s0" : document.querySelector("#form-field-s0"),
+ // "form-field-s1" : document.querySelector("#form-field-s1"),
+ // "form-field-s2" : document.querySelector("#form-field-s2"),
+ // "form-field-s3" : document.querySelector("#form-field-s3"),
+ // "form-field-s4" : document.querySelector("#form-field-s4"),
+ // "form-field-s5" : document.querySelector("#form-field-s5"),
+ // "form-field-conv1" : document.querySelector("#form-field-conv1"),
+ // "form-field-conv2" : document.querySelector("#form-field-conv2"),
+ // "form-field-selmodel" : document.querySelector("#form-field-selmodel"),
+ // "list" : document.querySelectorAll(".list"),
+ // "swim-lane" : document.querySelectorAll(".swim-lane"),
+
+ // };
+ // this.config = [];
+ // this.controlMode = 0;
+ // this.convMode = ["หยุด", "เดินหน้า", "ถอยหลัง"];
+
+ this.init();
+ }
+
+
+
+
+
+ /**
+ * The init function sets up the initial state of the app.
+
+ *
+ *
+ * @return Nothing
+ *
+ */
+ init() {
+ this.consoleLog("「ARMMANE」 by Nattawut Manjai-araya v1.5.0");
+ // Hide all screen
+ this.hideAllScreen();
+
+ // Hide element log_disconnect
+ this.hideElement("log_disconnect");
+
+ // Show loading screen
+ this.showScreen("connect");
+
+ this.setupElementTrigger();
+ // this.clearDropdownItem("form-field-selmodel");
+ this.setupSSE();
+ }
+
+
+
+
+ /**
+ * The setTriggerEvent function is used to assign an event listener to a specific element.
+ *
+ *
+ * @param mode Determine which element is being used
+ * @param element_name Identify the element that will be assigned to the event
+ * @param event Determine which event to listen for
+ * @param func Define the function that will be executed when the event is triggered
+ *
+ * @return Nothing
+ *
+ */
+ setTriggerEvent(mode, element_name, event, func) {
+ if (!this.elements["mode"].includes(mode)) {
+ this.consoleLog("「ARMMANE」 Mode " + mode + " not found", "ERROR");
+ return;
+ }
+ this.elements[mode][element_name].addEventListener(event, func);
+ this.consoleLog("「ARMMANE」 Element " + element_name + " assigned to " + event + " event");
+ }
+
+
+
+
+ /**
+ * The setupElementTrigger function sets up the event listeners for each element.
+ *
+ */
+ setupElementTrigger() {
+ // status_toggle
+ this.setTriggerEvent("btn", "status_toggle", "click", () => {
+ this.toggleStatusInfo();
+ });
+
+ }
+
+
+ /**
+ * The toggleStatusInfo function toggles the display of the log area.
+ *
+ */
+ toggleStatusInfo() {
+ if (this.elements["ui"]["log_area"].style.display == "none") {
+ this.elements["ui"]["log_area"].style.display = "flex";
+ }else{
+ this.elements["ui"]["log_area"].style.display = "none";
+ }
+ }
+
+
+
+ /**
+ * The showScreen function shows the screen that was
+ * specified.
+ *
+ *
+ *
+ * @param screen_name Identify which screen to show
+ *
+ * @return Nothing
+ *
+ */
+ showScreen(screen_name) {
+ this.elements["screen"][screen_name].style.display = "flex";
+ this.elements["currentScreen"] = screen_name;
+ }
+
+
+
+ /**
+ * The hideScreen function hides the screen that was specified.
+ *
+ *
+ * @param screen_name Identify which screen to hide
+ *
+ * @return Nothing
+ *
+ */
+ hideScreen(screen_name) {
+ this.elements["screen"][screen_name].style.display = "none";
+ }
+
+
+
+
+ /**
+ * The hideAllScreen function hides all the screens from the UI.
+ *
+ *
+ * @return Nothing
+ *
+ */
+ hideAllScreen() {
+ for (var key in this.elements["screen"]) {
+ this.hideScreen(key);
+ }
+ }
+
+
+
+
+ /**
+ * The createServerSelectionButton function creates a button for each server in the server list.
+ * The buttons are added to the UI element with id "conn-serverlist-box".
+ *
+ *
+ *
+ */
+ createServerSelectionButton() {
+ for (var i = 0; i < this.server_list.length; i++) {
+ let newdiv = this.elements["template"]["btn_serverlist"].cloneNode(true);
+ // Add classnames to button
+ newdiv.classList.add("btn-server");
+ newdiv.classList.add("btn-serverlist-" + i);
+ // remove template class
+ newdiv.classList.remove("tp-btn-server");
+ // Change button text
+ newdiv.querySelector(".elementor-button-text").textContent = this.appStatus["serverList"][i]["name"];
+ newdiv.style.display = "flex";
+ // Add property to button for access server list
+ newdiv.address = this.appStatus["serverList"][i]["address"];
+ newdiv.port = this.server_list[i]["port"];
+ newdiv.protocol = this.server_list[i]["protocol"];
+ // Add event listener to button
+ newdiv.addEventListener("click", () => {
+ // Called function to change server
+ this.changeServer(newdiv.address, newdiv.port, newdiv.protocol);
+ });
+ this.uiElements["conn-serverlist-box"].appendChild(newdiv);
+ }
+ }
+
+
+
+ createPresetButton(){
+ this.getConfig();
+ let config = this.config;
+ array.forEach(element => {
+
+ });
+ }
+
+
+ addDropdownItem(element_name, item_name, item_value) {
+ let item = document.createElement("option");
+ item.value = item_value;
+ item.textContent = item_name;
+ this.uiElements[element_name].appendChild(item);
+ }
+
+
+
+ clearDropdownItem(element_name) {
+ this.uiElements[element_name].options.length = 0;
+ }
+
+ addModelItem() {
+ this.getDataFromAPI("model").then(data => {
+ for(let i = 0; i < data.length; i++) {
+ this.addDropdownItem("form-field-selmodel", data[i], data[i]);
+ }
+ });
+ }
+
+ getConfig() {
+ if (this.config.length == 0 || this.config == null || isempty(this.config)) {
+ this.updateConfig();
+ return this.config;
+ }
+ else {
+ return this.config;
+ }
+ }
+
+ async updateConfig() {
+ (this.getDataFromAPI("config")).then(data => {
+ console.log(data);
+ this.config = data;
+ return this.config;
+ });
+ }
+
+ controlServo(servo, value) {
+ // send POST api to server
+ fetch(this.serverURL + "/command/servo/" + servo + "/" + value, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json"
+ }
+ }).then(response => response.json())
+ .then(data => {
+ this.consoleLog("「ARMMANE」 Servo "+servo+" value changed to "+value);
+ })
+ .catch(err => {
+ console.log(err);
+ });
+ }
+
+ controlConv(conv, value) {
+ // send POST api to server
+ fetch(this.serverURL + "/command/conv/" + conv + "/" + value, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json"
+ }
+ }).then(response => response.json())
+ .then(data => {
+ this.consoleLog("「ARMMANE」 Conv "+conv+" value changed to "+value);
+ })
+ .catch(err => {
+ console.log(err);
+ });
+ }
+
+ getArmStatus() {
+ // send GET api to server
+ fetch(this.serverURL + "/status/arm", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json"
+ }
+ }).then(response => response.json())
+ .then(data => {
+ this.uiElements["form-field-s0"].value = data["status_arm"]["servo"][0];
+ this.uiElements["form-field-s1"].value = data["status_arm"]["servo"][1];
+ this.uiElements["form-field-s2"].value = data["status_arm"]["servo"][2];
+ this.uiElements["form-field-s3"].value = data["status_arm"]["servo"][3];
+ this.uiElements["form-field-s4"].value = data["status_arm"]["servo"][4];
+ this.uiElements["form-field-s5"].value = data["status_arm"]["servo"][5];
+ })
+ .catch(err => {
+ console.log(err);
+ });
+ }
+
+
+ async setupSSE(){
+ const source = new EventSource(this.serverURL + "/sse/status");
+ if (this.connected || this.source != null) {
+ // Disconnect first
+ this.disconnect();
+ this.connected = false;
+ }
+ this.source = source;
+ this.source.onopen = () => {
+ this.consoleLog("[INFO] SSE connected to " + this.serverAddress + ":" + this.serverPort,"WARN");
+ }
+ this.source.onerror = () => {
+ this.consoleLog("[INFO] SSE disconnected from " + this.serverAddress + ":" + this.serverPort,"WARN");
+ }
+ this.source.onmessage = (event) => {
+ let data = JSON.parse(event.data);
+ }
+ this.connected = true;
+ this.source.addEventListener("arm_status", (event) => {
+ let data = JSON.parse(event.data);
+ if (this.controlMode == 0) {
+ this.uiElements["form-field-s0"].value = data["servo"][0];
+ this.uiElements["form-field-s1"].value = data["servo"][1];
+ this.uiElements["form-field-s2"].value = data["servo"][2];
+ this.uiElements["form-field-s3"].value = data["servo"][3];
+ this.uiElements["form-field-s4"].value = data["servo"][4];
+ this.uiElements["form-field-s5"].value = data["servo"][5];
+ }
+ this.changeText("conf-cur-conv1", this.convMode[data["conv"][0]]);
+ this.changeText("conf-cur-conv2", this.convMode[data["conv"][1]]);
+ });
+ return true;
+ }
+
+ connect() {
+ let ssesource = this.setupSSE();
+ if (ssesource) {
+ this.consoleLog("Connected to server at " + this.serverAddress + ":" + this.serverPort,"SUCCESS");
+ this.connected = true;
+ }else{
+ this.consoleLog("Connection to server at " + this.serverAddress + ":" + this.serverPort + " failed", "ERROR");
+ this.connected = false;
+ }
+ }
+
+ disconnectSSE() {
+ this.source.close();
+ this.consoleLog("[INFO] SSE disconnected from " + this.serverAddress + ":" + this.serverPort,"WARN");
+ }
+
+
+ disconnect() {
+ this.connected = false;
+ this.consoleLog("[INFO] Disconnected from server at " + this.serverAddress + ":" + this.serverPort,"WARN");
+ }
+
+ async getDataFromAPI(path, value = "") {
+ if(value != "") {
+ this.consoleLog("「ARMMANE」 POST " + this.serverURL + "/" + path + "/" + value);
+ return fetch(this.serverURL + "/" + path + "/" + value, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json"
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ return data
+ })
+ .catch(err => {
+ console.log(err);
+ });
+ }
+ else {
+ this.consoleLog("「ARMMANE」 GET " + this.serverURL + "/" + path);
+ return fetch(this.serverURL + "/" + path, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json"
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ return data
+ })
+ .catch(err => {
+ console.log(err);
+ });
+ }
+ }
+
+ showElement(mode, element_name) {
+ if (!this.elements["mode"].includes(mode)) {
+ this.consoleLog("「ARMMANE」 Mode " + mode + " not found", "ERROR");
+ return;
+ }
+ this.elements[mode][element_name].style.display = "flex";
+ }
+
+ hideElement(mode, element_name) {
+ if (!this.elements["mode"].includes(mode)) {
+ this.consoleLog("「ARMMANE」 Mode " + mode + " not found", "ERROR");
+ return;
+ }
+ this.elements[mode][element_name].style.display = "none";
+ }
+
+ reshowElement(mode, element_name) {
+ if (!this.elements["mode"].includes(mode)) {
+ this.consoleLog("「ARMMANE」 Mode " + mode + " not found", "ERROR");
+ return;
+ }
+ this.elements[mode][element_name].style.display = "none";
+ setTimeout(() => {
+ this.elements[mode][element_name].style.display = "flex";
+ }, 100);
+ }
+
+ reshowElementByTime(mode, element_name, time) {
+ if (!this.elements["mode"].includes(mode)) {
+ this.consoleLog("「ARMMANE」 Mode " + mode + " not found", "ERROR");
+ return;
+ }
+ this.elements[mode][element_name].style.display = "none";
+ setTimeout(() => {
+ this.elements[mode][element_name].style.display = "flex";
+ }, time);
+ }
+
+ hideElementByTime(mode, element_name, time) {
+ if (!this.elements["mode"].includes(mode)) {
+ this.consoleLog("「ARMMANE」 Mode " + mode + " not found", "ERROR");
+ return;
+ }
+ this.elements[mode][element_name].style.display = "flex";
+ setTimeout(() => {
+ this.elements[mode][element_name].style.display = "none";
+ }, time);
+ }
+
+
+ changeText(element_name, text) {
+ this.uiElements[element_name].textContent = text;
+ }
+
+ getText(element_name) {
+ return this.uiElements[element_name].textContent;
+ }
+
+ changeIcon(element_name, icon) {
+ let icon_name = "fas fa-" + icon;
+ this.uiElements[element_name].className = icon_name;
+ }
+
+ getIcon(element_name) {
+ return this.uiElements[element_name].className;
+ }
+
+ changeProgress(element_name, progress) {
+ this.uiElements[element_name].style.width = progress + "%";
+ }
+
+ getProgress(element_name) {
+ return this.uiElements[element_name].style.width;
+ }
+
+
+
+
+ toggleArmConfig() {
+ if (this.uiElements["amn-config-box"].style.display == "none") {
+ this.uiElements["amn-config-box"].style.display = "flex";
+ this.uiElements["btn-config-inner"].style.color = "#FF006E";
+ this.uiElements["btn-config-inner"].style.backgroundColor = "#FFFFFF";
+ }
+ else {
+ this.uiElements["amn-config-box"].style.display = "none";
+ this.uiElements["btn-config-inner"].style.color = "#FFFFFF";
+ this.uiElements["btn-config-inner"].style.backgroundColor = "#FF006E";
+ }
+ }
+
+ consoleLog(Text, Type = "", Color = "", Bold = false, Italic = false) {
+ let logStyle = "";
+
+ // 「ARMMANE」
+ switch (Type.toUpperCase()) {
+ case "INFO":
+ logStyle = "background-color: #E60962; color: white;";
+ break;
+ case "SUCCESS":
+ logStyle = "background-color: green; color: white;";
+ break;
+ case "WARN":
+ logStyle = "background-color: orange; color: white;";
+ break;
+ case "ERROR":
+ logStyle = "background-color: red; color: white;";
+ break;
+ default:
+ break;
+ }
+
+ if (Color) {
+ logStyle = `background-color: ${Color}; color: white;`;
+ }
+
+ // Apply Bold and Italic styles if specified
+ if (Bold && Italic) {
+ Text = `${Text}`;
+ } else if (Bold) {
+ Text = `${Text}`;
+ } else if (Italic) {
+ Text = `${Text}`;
+ }
+
+ const logMessage = `%c${Type ? " [" + Type + "] " : ""}${Text}`;
+
+ console.log(logMessage+" ", logStyle);
+ }
+
+}
+
+
+var app = new ARMMane();