public/js/index.js

//////////////////////////////////////////////////////////////////////
//	Copyright (C) Hiroshi SUGIMURA 2013.09.27.
//	Last updated: 2022.08.24
//////////////////////////////////////////////////////////////////////
/**
 * @namespace window
 * @desc index.js
 */

/**
 * @namespace window.ipc
 * @desc index.js
 */
'use strict'

////////////////////////////////////////////////////////////////////////////////
/**
 * @Func isObjEmpty
 * @Desc 内部
 * @Param {Void}
 * @Return {Void}
 */
function isObjEmpty(obj) {
	return Object.keys(obj).length === 0;
}


////////////////////////////////////////////////////////////////////////////////
window.addEventListener('load', onLoad);

/**
 * @Func window.onLoad
 * @Desc ドキュメントがロードされたら呼ばれる
 * HTMLがロードされたら実行,EventListenerとしてはDOMContentLoadedのあとloadする。
 * このシステムとしてはindex.jsが最後実行してほしいのでloadとし、
 * 他のサブモジュールをDOMContentLoadedにする
 * @Param {Void}
 * @Return {Void}
 */
function onLoad() {
	console.log('## onLoad index.js');

	// 内部変数
	let licenses;      // 使用モジュールのライセンス
	let halData;       // HALのデータ

	// ライセンス
	let divLicenses = document.getElementById('divLicenses'); // ライセンス表記

	// debug
	let myIPaddr = document.getElementById('myIPaddr');
	let txtErrLog = document.getElementById('txtErrLog');

	let syncBtn = document.getElementById('syncBtn');

	// 検索ダイアログ
	const searchBox = document.getElementById('searchBox');
	const searchInput = document.getElementById('searchInput');
	const searchResult = document.getElementById('searchResult');

	//////////////////////////////////////////////////////////////////
	/**
	 * @event
	 * @name window.ipc.to-renderer
	 * @Desc MainProcessからのメッセージ振り分け
	 * @Param {Void}
	 * @Return {Void}
	 */
	window.ipc.on('to-renderer', (event, obj) => {
		// console.log( '->', obj );
		// console.log('to-renderer, event:', event); eventは 'to-renderer' が入ってる
		let c = JSON.parse(obj);    // obj = {cmd, arg} の形式でくる
		// console.log(c);

		switch (c.cmd) {
			//----------------------------------------------
			// システム関連
			case "renewSystemConfigView":  // システム設定の画面表示変更
				console.log('main -> renewSystemConfigView:', c.arg);
				window.renewSystemConfigView(c.arg);
				break;

			case 'openSearch':
				console.log('main -> openSearch:');
				if (searchBox.style.display === 'block') {
					searchBox.style.display = 'none';
				} else {
					searchBox.style.display = 'block';
					searchInput.focus();  // 検索ボックスにフォーカスする
				}
				break;

			case 'foundResultShow':
				console.log('main -> foundResultShow:');
				searchResult.innerHTML = `${c.arg.activeMatchOrdinal} / ${c.arg.matches}`;
				break;

			//----------------------------------------------
			// ユーザプロファイル関連
			case "renewUserConfigView":  // user profileの画面表示変更
				console.log('main -> renewUserConfigView:', c.arg);
				window.renewUserConfigView(c.arg);
				break;

			//----------------------------------------------
			// HAL関連
			case "renewHALConfigView": // HAL情報(APIKey)
				console.log('main -> renewHALConfigView:', c.arg);
				window.renewHALConfigView(c.arg);
				break;

			case "HALRenewResponse": // HALのデータをもらった(評価値)
				console.log('main -> HALRenewResponse:', c.arg);
				halData = c.arg;
				window.HALRedraw(halData.MajorResults, halData.MinorResults, halData.MinorkeyMeans);
				break;

			case "HALgetApiTokenResponse": // HAL API トークン取得の応答
				console.log('main -> HALgetApiTokenResponse:', c.arg);
				getHalApiTokenCallback(c.arg);
				break;

			case "HALsetApiTokenResponse": // HAL API トークン設定の応答
				console.log('main -> HALsetApiTokenResponse:', c.arg);
				window.HALsetApiTokenResponse(c.arg);
				break;

			case "HALdeleteApiTokenResponse": // HAL API トークン設定削除の応答
				console.log('main -> HALdeleteApiTokenResponse:', c.arg);
				window.HALdeleteApiTokenResponse();
				break;

			case "HALgetUserProfileResponse": // HAL ユーザープロファイル取得の応答 c.arg.profile or c.arg.error
				console.log('main -> HALgetUserProfileResponse:', c.arg);
				window.HALgetUserProfileResponse(c.arg);
				break;

			case "HALSyncResponse":  // HAL cloud: 同期の応答、同期処理終了
				console.log('main -> HALSyncResponse:', c.arg);
				window.HALSyncResponse(c.arg);
				break;

			// Garmin
			case "showGarmin":  // Garminデータ表示
				console.log('main -> showGarmin:', c.arg);
				window.showGarminData(c.arg);
				break;


			//----------------------------------------------
			// EL関連
			case "fclEL":
				console.log('main -> fclEL:', c.arg);
				window.renewFacilitiesEL(c.arg);
				break;

			case "renewELConfigView":
				console.log('main -> renewELConfigView');
				window.renewELConfigView(c.arg);
				break;


			//----------------------------------------------
			// 電力スマメ関連
			case "fclESM":
				// console.log('main -> fclESM:', c.arg);
				window.renewESM(c.arg);
				break;

			case "ESMLinked":
				console.log('main -> ESMLinked:');
				window.addToast('Info', '電力スマートメータとLinkしました');
				break;

			case "renewESMConfigView":
				console.log('main -> renewESMConfigView', c.arg);
				window.renewESMConfigView(c.arg);
				break;

			case "renewTodayElectricEnergy":  // WI-SUNのスマートメータ
				window.renewEnergy(c.arg);
				break;

			//----------------------------------------------
			// Philips hue関連
			case "fclHue":
				console.log('main -> fclHue:', c.arg);
				window.renewHueLog(JSON.stringify(c.arg, null, '  '));
				window.renewFacilitiesHue(c.arg);
				break;

			case "HueLinked": // HueとLinkできた
				console.log('main -> HueLinked:', c.arg);
				window.hueLinked(c.arg);
				break;

			case "renewHueConfigView":
				console.log('main -> renewHueConfigView:', c.arg);
				window.renewHueConfigView(c.arg);
				break;


			//----------------------------------------------
			// Ikea関連
			case "fclIkea":
				console.log('main -> fclIkea', c.arg);
				window.renewFacilitiesIkea(c.arg);
				break;

			case "renewIkeaConfigView":
				console.log('main -> renewIkeaConfigView', c.arg);
				window.renewIkeaConfigView(c.arg);
				break;



			//----------------------------------------------
			// OpenWeatherMap関連
			case "renewOwm": // OpenWeatherMapのデータをもらった
				console.log('main -> renewOwm:', c.arg);
				window.renewOwm(c.arg);
				break;

			case "renewOwmConfigView":  // Configを画面に表示
				console.log('main -> renewOwmConfigView:', c.arg);
				window.renewOwmConfigView(c.arg);
				break;


			//----------------------------------------------
			// 気象庁関連
			case "renewJmaAbst": // JMAのデータをもらった
				console.log('main -> renewJmaAbst:', c.arg);
				window.renewJmaAbst(c.arg);
				break;

			case "renewJmaDetail":
				console.log('main -> renewJmaDetail:', c.arg);
				window.renewJmaDetail(c.arg);
				break;

			case "renewJmaConfigView":
				console.log('main -> renewJmaConfigView', c.arg);
				window.renewJmaConfigView(c.arg);
				break;


			//----------------------------------------------
			// Netatmo関連
			case "renewNetatmo": // Netatmoのデータをもらった
				// console.log( 'main -> renewNetatmo:', c.arg );  // ログ多すぎる
				window.renewNetatmo(c.arg);
				break;

			case "renewNetatmoConfigView": // Netatmoの設定データをもらった
				console.log('main -> renewNetatmoConfigView:', c.arg);
				window.renewNetatmoConfigView(c.arg);
				break;


			//----------------------------------------------
			// Omron関連
			case "renewOmron": // Omronのデータをもらった
				// console.log( 'main -> renewOmron:', c.arg );  // ログ多過ぎるので必要な時だけ有効にする
				window.renewOmron(c.arg);
				break;

			case "omronDisconnected": // Omron切断
				console.log('main -> omronDisconnected:');  // ログ多過ぎるので必要な時だけ有効にする
				window.disconnectedOmron();
				break;

			case "renewOmronConfigView": // Omronの設定データをもらった
				console.log('main -> renewOmronConfigView:', c.arg);
				window.renewOmronConfigView(c.arg);
				break;


			//----------------------------------------------
			// UD-CO2S関連
			case "renewCo2s": // Co2sのデータをもらった
				// console.log( 'main -> renewCo2s:', c.arg );  // ログ多過ぎるので必要な時だけ有効にする
				window.renewCo2s(c.arg);
				break;

			case "co2sDisconnected": // Co2s切断
				console.log('main -> co2sDisconnected:');  // ログ多過ぎるので必要な時だけ有効にする
				window.disconnectedCo2s();
				break;

			case "renewCo2sConfigView": // Co2sの設定データをもらった
				console.log('main -> renewCo2sConfigView:', c.arg);
				window.renewCo2sConfigView(c.arg);
				break;


			//----------------------------------------------
			// SwitchBot関連
			case "fclSwitchBot":
				// console.log('main -> fclSwitchBot:', c.arg);
				window.renewFacilitiesSwitchBot(c.arg);
				break;

			case "renewSwitchBotConfigView":
				console.log('main -> renewSwitchBotConfigView:', c.arg);
				window.renewSwitchBotConfigView(c.arg);
				break;

			case "renewRoomEnvSwitchBot":
				// console.log( 'main -> renewRoomEnvSwitchBot:', c.arg );
				console.log('main -> renewRoomEnvSwitchBot');
				window.renewRoomEnvSwitchBot(c.arg);
				break;


			//----------------------------------------------
			// 部屋環境グラフ
			case "renewRoomEnvNetatmo":
				// console.log( 'main -> newRoomEnvNetatmo:', c.arg);   // ログ多すぎる
				console.log('main -> newRoomEnvNetatmo');
				window.renewRoomEnvNetatmo(c.arg);
				break;

			case "renewRoomEnvOmron":
				// console.log( 'main -> newRoomEnvOmron:', c.arg);   // ログ多すぎる
				console.log('main -> newRoomEnvOmron');
				window.renewRoomEnvOmron(c.arg);
				break;

			case "renewRoomEnvCo2s":
				// console.log( 'main -> renewRoomEnvCo2s:', c.arg);   // ログ多すぎる
				console.log('main -> renewRoomEnvCo2s');
				window.renewRoomEnvCo2s(c.arg);
				break;

			case "renewTodayElectricEnergy_submeter":  // Ether サブメータ
				window.renewEnergySubmeter(c.arg);
				break;


			//----------------------------------------------
			// カレンダー
			case "createCalendar":
				console.log('main -> createCalendar:');
				// console.log('main -> renewCalendar:', c.arg);
				window.createCalendar(c.arg);
				break;

			case "renewCalendar":
				console.log('main -> renewCalendar:');
				window.renewCalendar();
				break;

			//----------------------------------------------
			// PLIS全体
			case "myIPaddr":
				console.log('main -> myIPaddr:', c.arg);
				myIPaddr.innerHTML = 'My IP address list: ' + c.arg;
				break;

			case "configSaved": // 設定保存の応答
				console.log('main -> configSaved:', c.arg);
				if (c.arg.error) {
					alert(c.arg.error);
				}
				switch (c.arg) {
					case "System": window.SystemConfigSaved(); break;
					case "User": window.UserConfigSaved(); break;
					case "OWM": window.OwmConfigSaved(); break;
					case "JMA": window.JmaConfigSaved(); break;
					case "Hue": window.HueConfigSaved(); break;
					case "Ikea": window.IkeaConfigSaved(); break;
					case "Netatmo": window.NetatmoConfigSaved(); break;
					case "Omron": window.OmronConfigSaved(); break;
					case "EL": window.ELConfigSaved(); break;
					case "ESM": window.ESMConfigSaved(); break;
					case "SwitchBot": window.SwitchBotConfigSaved(); break;

					default:
						// window.alert('設定を保存しました。');
						window.addToast('Info', '${c.arg} 設定を保存しました。');
						break;
				}
				break;

			case "renewLicenses": // ライセンス表示の更新
				licenses = c.arg;
				renewLicenses();
				break;

			case "Info":
				console.log('main -> Info:', c.arg);
				window.addToast('Info', c.arg);
				break;

			case "Error":
				console.log('main -> Error:', c.arg);
				// c.arg = {datetime, moduleName, stackLog}
				window.addError(c.arg);  // このメソド内でToastする
				break;

			default:
				txtErrLog.value = JSON.stringify(c, null, '  ');
				console.log('main -> unknown cmd:', c.cmd, "arg:", c.arg);
				break;
		}
	});

	////////////////////////////////////////////////////////////////////////////////
	// user profile関係

	/**
	 * @Func renewLicenses
	 * @Desc ライセンス
	 * @Param {Void}
	 * @Return {Void}
	 */
	let renewLicenses = function () {
		// console.log( licenses );
		let doc = `<table class="sort_table" id="tblLicenses">`
			+ `<thead> <tr><th>Name</th> <th>Licenses</th> <th>Publisher</th> <th>Repository</th> <tr></thead><tbody>`;

		let keys = Object.keys(licenses);
		for (let k of keys) {
			doc += `<tr class="item"><td>${k}</td><td>${licenses[k].licenses}</td><td>${licenses[k].publisher}</td><td><a href='${licenses[k].repository}'>${licenses[k].repository}</a></td><tr>`;
		}

		doc += '</tbody></table>';

		divLicenses.innerHTML = doc;
	};


	//////////////////////////////////////////////////////////////////////
	// ボタン

	/**
	 * @Func window.pushHideButton
	 * @Desc テキストエリアを見せたり隠したり
	 * @Param {Void}
	 * @Return {Void}
	 */
	window.pushHideButton = function (field) {
		let txtPass = document.getElementById(field);
		let btnEye = document.getElementById(field + "ButtonEye");
		if (txtPass.type === "text") {
			txtPass.type = "password";
			btnEye.classList.remove("fa-eye-slash");
			btnEye.classList.add("fa-eye");
		} else {
			txtPass.type = "text";
			btnEye.classList.add("fa-eye-slash");
			btnEye.classList.remove("fa-eye");
		}
	};

	// tableをsortできるように準備
	let column_no = 0; //今回クリックされた列番号
	let column_no_prev = 0; //前回クリックされた列番号
	function sortReady() {
		document.querySelectorAll('table.sort_table thead th').forEach(elm => {  // classがsort_tableのものを改変
			elm.onclick = function () {
				column_no = this.cellIndex; //クリックされた列番号
				let table = this.parentNode.parentNode.parentNode;
				let sortType = 0; //0:数値 1:文字
				let sortArray = new Array; //クリックした列のデータを全て格納する配列
				for (let r = 1; r < table.rows.length; r++) {
					//行番号と値を配列に格納
					let column = new Object;
					column.row = table.rows[r];
					column.value = table.rows[r].cells[column_no].textContent;
					sortArray.push(column);
					//数値判定
					if (isNaN(Number(column.value))) {
						sortType = 1; //値が数値変換できなかった場合は文字列ソート
					}
				}
				if (sortType == 0) { //数値ソート
					if (column_no_prev == column_no) { //同じ列が2回クリックされた場合は降順ソート
						sortArray.sort(compareNumberDesc);
					} else {
						sortArray.sort(compareNumber);
					}
				} else { //文字列ソート
					if (column_no_prev == column_no) { //同じ列が2回クリックされた場合は降順ソート
						sortArray.sort(compareStringDesc);
					} else {
						sortArray.sort(compareString);
					}
				}
				//ソート後のTRオブジェクトを順番にtbodyへ追加(移動)
				let tbody = this.parentNode.parentNode;
				for (let i = 0; i < sortArray.length; i++) {
					tbody.appendChild(sortArray[i].row);
				}
				//昇順/降順ソート切り替えのために列番号を保存
				if (column_no_prev == column_no) {
					column_no_prev = -1; //降順ソート
				} else {
					column_no_prev = column_no;
				}
			};
		});
	}
	sortReady();

	//数値ソート(昇順)
	function compareNumber(a, b) {
		return a.value - b.value;
	}

	//数値ソート(降順)
	function compareNumberDesc(a, b) {
		return b.value - a.value;
	}

	//文字列ソート(昇順)
	function compareString(a, b) {
		if (a.value < b.value) {
			return -1;
		} else {
			return 1;
		}
		return 0;
	}

	//文字列ソート(降順)
	function compareStringDesc(a, b) {
		if (a.value > b.value) {
			return -1;
		} else {
			return 1;
		}
		return 0;
	}

	//////////////////////////////////////////////////////////////////////
	// ページ内検索
	// 検索開始
	window.btnSearchStart_Click = function () {
		window.ipc.PageInSearch(searchInput.value);
	}

	// 順検索
	window.btnSearchNext_Click = function () {
		window.ipc.PageInSearchNext(searchInput.value);
	}

	// 逆検索
	window.btnSearchPrev_Click = function () {
		window.ipc.PageInSearchPrev(searchInput.value);
	}

	// 検索終了
	window.btnSearchStop_Click = function () {
		searchBox.style.display = 'none';
		searchResult.innerHTML = '0 / 0';
		window.ipc.PageInSearchStop();
	}

	// escape key
	searchInput.addEventListener("keydown", (key) => {
		// console.log(key.code);
		if (key.code == 'Escape') {
			window.btnSearchStop_Click();
		}
	});

	//////////////////////////////////////////////////////////////////////
	// この関数の最後に呼ぶ
	// 準備できたことをmainプロセスに伝える
	window.ipc.already();
};