diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3dce414 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..241edf9 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,5 @@ +node_modules +docs +dist +out +build diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..4a51dd8 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,16 @@ +module.exports = { + root: true, + env: { + es6: true, + node: true + }, + parserOptions: { + sourceType: 'module', + ecmaVersion: 2021 + }, + extends: ['eslint:recommended', '@electron-internal', '@electron-toolkit'], + rules: { + 'space-before-function-paren': 'off', + vendorPrefix: 'off' + } +}; diff --git a/.gitignore b/.gitignore index cd88a68..e3c7f9a 100644 --- a/.gitignore +++ b/.gitignore @@ -104,4 +104,9 @@ src/sounds/tts/* loquendoBot_backend.spec forge.config.js backend/* -src/backend/loquendoBot_backend.exe \ No newline at end of file +!backend/loquendoBot_backend.py +backend/loquendoBot_backend.exe +src/config/twitch-emotes.json +dist/* +src/config/betterttv-emotes.json +test.py diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 0000000..80c0f12 --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,5 @@ +singleQuote: true +semi: true +printWidth: 140 +trailingComma: none +arrowParens: avoid diff --git a/src/backend/loquendoBot_backend.py b/backend/loquendoBot_backend.py similarity index 74% rename from src/backend/loquendoBot_backend.py rename to backend/loquendoBot_backend.py index f539204..ba3c48b 100644 --- a/src/backend/loquendoBot_backend.py +++ b/backend/loquendoBot_backend.py @@ -1,10 +1,17 @@ from flask import Flask, Response, jsonify, request import gevent + +import re import gevent.monkey import json +from waitress import serve +import logging + +logger = logging.getLogger("waitress") +logger.setLevel(logging.INFO) gevent.monkey.patch_all() -import gevent.queue +# import gevent.queue import configparser import pyttsx3 @@ -21,8 +28,6 @@ from deep_translator import ( MyMemoryTranslator, ) -import emoji - from vosk import Model, KaldiRecognizer, SetLogLevel # global variables @@ -40,6 +45,7 @@ q = queue.Queue() # gobal functions + # classes class LanguageDetection: def __init__(self): @@ -55,20 +61,19 @@ class LanguageDetection: language_detection_model = os.path.join( resources_folder, "language_detection_model", f"lid.176.bin" ) - - language_detection_model = ( - rf"{language_detection_model}" - ) + + language_detection_model = rf"{language_detection_model}" self.model = fasttext.load_model(language_detection_model) def predict_lang(self, text): - predictions = self.model.predict(text, k=5) # returns top 2 matching languages + predictions = self.model.predict(text, k=3) # returns top 2 matching languages language_codes = [] for prediction in predictions[0]: language_codes.append(prediction.replace("__label__", "")) return language_codes + class STT: samplerate = None args = "" @@ -92,9 +97,7 @@ class STT: resources_folder, "speech_to_text_models", settings["STT"]["LANGUAGE"] ) - self.model = Model( - rf"{vosk_model}" - ) + self.model = Model(rf"{vosk_model}") self.dump_fn = None self.q = gevent.queue.Queue() @@ -132,8 +135,9 @@ class STT: def stop_recognition(self): self.is_running = False - -speech_recognition_service = STT() +settings.read(settingsPath) +if settings["STT"]["USE_STT"] and bool(settings["STT"]["LANGUAGE"]): + speech_recognition_service = STT() class TTS: @@ -151,16 +155,16 @@ class TTS: break self.engine.setProperty("voice", matching_id) + settings_folder = os.path.dirname(settingsPath) if environment == "dev": - settings_folder = os.path.dirname(settingsPath) src_folder = os.path.dirname(settings_folder) + bot_folder = os.path.dirname(src_folder) saveLocation = os.path.join( - src_folder, "sounds\\tts", f"Internal_{count}.mp3" + bot_folder, "sounds", f"Internal_{count}.mp3" ) else: - resources_folder = os.path.dirname(settingsPath) saveLocation = os.path.join( - resources_folder, "sounds\\tts", f"Internal_{count}.mp3" + settings_folder, "sounds", f"Internal_{count}.mp3" ) self.engine.save_to_file(message, saveLocation) @@ -175,11 +179,13 @@ class TTS: return [voice.name for voice in voices] - -text_to_speech_service = TTS() +settings.read(settingsPath) +if settings["TTS"]["USE_TTS"]: + text_to_speech_service = TTS() # endpoints + @app.route("/stream", methods=["GET"]) def stream_recognition(): def generate(): @@ -194,14 +200,6 @@ def stop_recording(): return Response("Speech recognition stopped", status=200) -# @app.before_request -# def custom_warning(): -# if environment == "dev": -# print( -# # "Running in internal development environment. This server is not for production use." -# ) - - @app.route("/terminate", methods=["GET"]) def terminate_processes(): shutdown_server() @@ -215,35 +213,55 @@ def shutdown_server(): func() -# @app.route("/detect", methods=["POST"]) -# def server_status(): -# try: -# request_data = request.json -# message = request_data.get("message", "") -# confidence_values = detector.compute_language_confidence_values(message) -# for language, value in confidence_values: -# print(f"{language.name}: {value:.2f}") -# message = request_data.get("message", "") -# except Exception as e: -# return jsonify({"error": "An error occurred"}), 500 -# return jsonify({"message": "Audio triggered"}), 200 - - @app.route("/status", methods=["GET"]) def server_status(): return jsonify({"status": "server is running"}) +@app.route("/detect", methods=["POST"]) +def get_language(): + try: + request_data = request.json + message = request_data.get("message", "") + lang = LanguageDetection().predict_lang(message) + except Exception as e: + return jsonify({"error": "An error occurred"}), 500 + return jsonify({"languages": lang}), 200 + + +@app.route("/translate", methods=["POST"]) +def get_translation(): + try: + settings.read(settingsPath) + request_data = request.json + message = request_data.get("message", "") + detectedLanguage = request_data.get("language", "") + try: + translated = MyMemoryTranslator( + source=detectedLanguage, target=settings["LANGUAGE"]["TRANSLATE_TO"] + ).translate(message) + except Exception as e: + return jsonify({"error": str(e), "code":429 }), 429 + except Exception as e: + return jsonify({"error": str(e), "code":500 }), 500 + return jsonify({"translation": translated}), 200 + + @app.route("/audio", methods=["POST"]) def trigger_backend_event(): try: request_data = request.json message = request_data.get("message", "") + filteredMessage = re.sub( + r"https?://(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)", + "a link", + message, + ) voice = request_data.get("voice") count = request_data.get("count") - text_to_speech_service.say(message, voice, count) + text_to_speech_service.say(filteredMessage, voice, count) except Exception as e: - return jsonify({"error": "An error occurred"}), 500 + return jsonify({"error": e}), 500 return jsonify({"message": "Audio triggered"}), 200 @@ -253,18 +271,10 @@ def get_voices(): voices = text_to_speech_service.voices() return jsonify({"voices": voices}), 200 except Exception as e: - return jsonify({"error": "An error occurred"}), 500 + return jsonify({"error": e}), 500 if __name__ == "__main__": - LANGUAGE = LanguageDetection() - lang = LANGUAGE.predict_lang("hola cómo estás") - print(lang) - text = "Keep it up. You are awesome" - translated = MyMemoryTranslator( - source="english", target="spanish latin america" - ).translate(text) - print(translated) if len(sys.argv) > 1: settings.read(settingsPath) port = int(settings["GENERAL"]["PORT"]) @@ -273,5 +283,4 @@ if __name__ == "__main__": port = 9000 stream_recognition() - app.run(host="127.0.0.1", port=port) - app.terminate() + serve(app, host="0.0.0.0", port=port) diff --git a/forge.config.js b/forge.config.js deleted file mode 100644 index 78ea629..0000000 --- a/forge.config.js +++ /dev/null @@ -1,36 +0,0 @@ -module.exports = { - packagerConfig: { - icon: './src/images/icon.ico', - asar: true, - extraResource: ['./src/config/loquendo.db', './src/sounds', './backend', './language_detection_model', './speech_to_text_models'], - }, - rebuildConfig: {}, - makers: [ - { - name: '@electron-forge/maker-squirrel', - config: { - setupIcon: './src/images/icon.ico', - }, - }, - { - name: '@electron-forge/maker-zip', - platforms: ['darwin'], - }, - { - name: '@electron-forge/maker-deb', - config: { - options: {}, - }, - }, - { - name: '@electron-forge/maker-rpm', - config: {}, - }, - ], - plugins: [ - { - name: '@electron-forge/plugin-auto-unpack-natives', - config: {}, - }, - ], -}; diff --git a/license b/license.md similarity index 96% rename from license rename to license.md index 7522c81..02cfa3d 100644 --- a/license +++ b/license.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 Khyretis +Copyright (c) 2021 Khyretos Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/package.json b/package.json index eb8957b..7eae0f4 100644 --- a/package.json +++ b/package.json @@ -1,43 +1,74 @@ { - "name": "loquendo-bot", - "version": "2.4.0", - "description": "Bot assistant for streamers over different platforms", - "main": "src/main.js", - "scripts": { - "start": "electron-forge start", - "package": "npm run backend && electron-forge package", - "make": "electron-forge make", - "publish": "electron-forge publish", - "backend": "pyinstaller --noconsole --onefile --collect-all vosk --distpath ./backend ./src/backend/loquendoBot_backend.py" + "name": "loquendo-bot", + "productName": "LoquendoBot", + "version": "2.6.0", + "description": "Bot assistant for streamers over different platforms", + "main": "src/main.js", + "scripts": { + "start": "electron-forge start", + "build": "npm run backend && electron-builder", + "publish": "electron-forge publish", + "backend": "pyinstaller --noconsole --onefile --collect-all vosk --distpath ./backend ./backend/loquendoBot_backend.py" + }, + "build": { + "appId": "LoquendoBot", + "win": { + "target": [ + "nsis" + ], + "icon": "./src/images/icon.ico" }, - "keywords": [], - "author": { - "name": "Khyretos", - "email": "khyretos@gmail.com" + "nsis": { + "oneClick": false, + "installerIcon": "./src/images/icon.ico", + "uninstallerIcon": "./src/images/icon.ico", + "uninstallDisplayName": "LoquendoBot-uninstaller", + "license": "license.md", + "allowToChangeInstallationDirectory": "true" }, - "license": "ISC", - "dependencies": { - "axios": "^1.4.0", - "electron-squirrel-startup": "^1.0.0", - "express": "^4.18.2", - "ini": "^2.0.0", - "kill-process-by-name": "^1.0.5", - "node-google-tts-api": "^1.1.1", - "querystring": "^0.2.1", - "socket.io": "^4.7.1", - "socket.io-client": "^4.7.1", - "tmi.js": "^1.8.5", - "url": "^0.11.1", - "winston": "^3.10.0", - "write-ini-file": "^4.0.1" - }, - "devDependencies": { - "@electron-forge/cli": "^6.2.1", - "@electron-forge/maker-deb": "^6.2.1", - "@electron-forge/maker-rpm": "^6.2.1", - "@electron-forge/maker-squirrel": "^6.2.1", - "@electron-forge/maker-zip": "^6.2.1", - "@electron-forge/plugin-auto-unpack-natives": "^6.2.1", - "electron": "^25.9.8" - } + "extraResources": [ + "speech_to_text_models/Where to get STT models.txt", + "backend/loquendoBot_backend.exe", + "language_detection_model", + "sounds" + ] + }, + "keywords": [], + "author": { + "name": "Khyretos", + "email": "khyretos@gmail.com" + }, + "license": "ISC", + "dependencies": { + "axios": "^1.4.0", + "emoji-picker-element": "^1.21.0", + "express": "^4.18.2", + "flag-icons": "^7.1.0", + "ini": "^2.0.0", + "kill-process-by-name": "^1.0.5", + "node-google-tts-api": "^1.1.1", + "querystring": "^0.2.1", + "socket.io": "^4.7.1", + "socket.io-client": "^4.7.1", + "sockette": "^2.0.6", + "tmi.js": "^1.8.5", + "url": "^0.11.1", + "winston": "^3.10.0", + "write-ini-file": "^4.0.1" + }, + "devDependencies": { + "@electron-forge/cli": "^6.2.1", + "@electron-forge/maker-deb": "^6.2.1", + "@electron-forge/maker-rpm": "^6.2.1", + "@electron-forge/maker-zip": "^6.2.1", + "@electron-forge/plugin-auto-unpack-natives": "^6.2.1", + "@electron-internal/eslint-config": "^1.0.1", + "@electron-toolkit/eslint-config": "^1.0.2", + "electron": "^25.9.8", + "electron-builder": "^24.9.1", + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.2", + "prettier": "^3.1.1" + } } diff --git a/src/sounds/notifications/Tip Sound.mp3 b/sounds/notifications/Tip Sound.mp3 similarity index 100% rename from src/sounds/notifications/Tip Sound.mp3 rename to sounds/notifications/Tip Sound.mp3 diff --git a/src/sounds/notifications/coin.mp3 b/sounds/notifications/coin.mp3 similarity index 100% rename from src/sounds/notifications/coin.mp3 rename to sounds/notifications/coin.mp3 diff --git a/src/sounds/notifications/dice.mp3 b/sounds/notifications/dice.mp3 similarity index 100% rename from src/sounds/notifications/dice.mp3 rename to sounds/notifications/dice.mp3 diff --git a/src/sounds/notifications/error.mp3 b/sounds/notifications/error.mp3 similarity index 100% rename from src/sounds/notifications/error.mp3 rename to sounds/notifications/error.mp3 diff --git a/src/sounds/notifications/fail.wav b/sounds/notifications/fail.wav similarity index 100% rename from src/sounds/notifications/fail.wav rename to sounds/notifications/fail.wav diff --git a/src/sounds/notifications/info.mp3 b/sounds/notifications/info.mp3 similarity index 100% rename from src/sounds/notifications/info.mp3 rename to sounds/notifications/info.mp3 diff --git a/src/sounds/notifications/pling.mp3 b/sounds/notifications/pling.mp3 similarity index 100% rename from src/sounds/notifications/pling.mp3 rename to sounds/notifications/pling.mp3 diff --git a/src/sounds/notifications/spinjump.mp3 b/sounds/notifications/spinjump.mp3 similarity index 100% rename from src/sounds/notifications/spinjump.mp3 rename to sounds/notifications/spinjump.mp3 diff --git a/src/sounds/notifications/success.wav b/sounds/notifications/success.wav similarity index 100% rename from src/sounds/notifications/success.wav rename to sounds/notifications/success.wav diff --git a/src/sounds/notifications/ting.mp3 b/sounds/notifications/ting.mp3 similarity index 100% rename from src/sounds/notifications/ting.mp3 rename to sounds/notifications/ting.mp3 diff --git a/speech_to_text_models/Where to get STT models.txt b/speech_to_text_models/Where to get STT models.txt index d41ac68..21a635e 100644 --- a/speech_to_text_models/Where to get STT models.txt +++ b/speech_to_text_models/Where to get STT models.txt @@ -1 +1,3 @@ -https://alphacephei.com/vosk/models +Download the model from here: https://alphacephei.com/vosk/models unzip it +and drop the folder in the 'speech_to_text_models' folder. Restart the app +to load the changes. diff --git a/src/css/chat.css b/src/css/chat.css index e65ea21..9685b95 100644 --- a/src/css/chat.css +++ b/src/css/chat.css @@ -1,517 +1,469 @@ @font-face { - font-family: 'FRAMDCN'; + font-family: 'FRAMDCN'; + src: url(../fonts/FRAMCDN/FRAMDCN.woff); } h1 { - font-family: 'FRAMDCN'; + font-family: 'FRAMDCN'; } .message-window { - height: calc(100% - 60px); - overflow: hidden; - overflow-y: auto; - display: flex; - align-items: center; - flex-direction: column; - background-color: var(--mid-section); - margin-left: 50px; - font-family: 'FRAMDCN'; - position: relative; - z-index: 1; + height: calc(100% - 60px); + overflow: hidden; + overflow-y: auto; + display: flex; + align-items: center; + flex-direction: column; + background-color: var(--mid-section); + padding-left: 50px; + padding-right: 50px; + font-family: 'FRAMDCN'; + position: relative; + z-index: 1; } .input-box { - display: flex; - border: none; - width: 100%; - height: 30px; - font-size: 16px; + display: flex; + border: none; + width: 100%; + height: 30px; + font-size: 16px; } .userText { - color: var(--chat-bubble-message); - font-family: Helvetica; - font-size: 16px; - text-align: right; - clear: both; + color: var(--chat-bubble-message); + font-family: Helvetica; + font-size: 16px; + text-align: right; + clear: both; } .userText span { - line-height: 1.5em; - display: inline-block; - background: #5ca6fa; - padding: 10px; - border-radius: 8px; - border-bottom-right-radius: 2px; - max-width: 80%; - margin-right: 10px; - animation: floatup 0.5s forwards; + line-height: 1.5em; + display: inline-block; + background: #5ca6fa; + padding: 10px; + border-radius: 8px; + border-bottom-right-radius: 2px; + max-width: 80%; + margin-right: 10px; + animation: floatup 0.5s forwards; } .botText { - color: #000; - font-family: Helvetica; - font-weight: normal; - font-size: 16px; - text-align: left; + color: #000; + font-family: Helvetica; + font-weight: normal; + font-size: 16px; + text-align: left; } .botText span { - line-height: 1.5em; - display: inline-block; - background: #e0e0e0; - padding: 10px; - border-radius: 8px; - border-bottom-left-radius: 2px; - max-width: 80%; - margin-left: 10px; - animation: floatup 0.5s forwards; + line-height: 1.5em; + display: inline-block; + background: #e0e0e0; + padding: 10px; + border-radius: 8px; + border-bottom-left-radius: 2px; + max-width: 80%; + margin-left: 10px; + animation: floatup 0.5s forwards; } @keyframes floatup { - from { - transform: translateY(14px); - opacity: 0; - } + from { + transform: translateY(14px); + opacity: 0; + } - to { - transform: translateY(0px); - opacity: 1; - } + to { + transform: translateY(0px); + opacity: 1; + } } @media screen and (max-width: 600px) { - .full-chat-block { - width: 100%; - border-radius: 0px; - } + .full-chat-block { + width: 100%; + border-radius: 0px; + } - .chat-bar-collapsible { - position: fixed; - bottom: 0; - right: 0; - width: 100%; - } + .chat-bar-collapsible { + position: fixed; + bottom: 0; + right: 0; + width: 100%; + } - .collapsible { - width: 100%; - border: 0px; - border-radius: 0px; - } + .collapsible { + width: 100%; + border: 0px; + border-radius: 0px; + } } ::-webkit-scrollbar { - width: 4px; + width: 4px; } ::-webkit-scrollbar-thumb { - background-color: #4c4c6a; - border-radius: 2px; + background-color: #4c4c6a; + border-radius: 2px; } .chatBox { - width: 300px; - height: 400px; - max-height: 400px; - display: flex; - flex-direction: column; - overflow: hidden; - box-shadow: 0 0 4px var(--main-color4); + width: 300px; + height: 400px; + max-height: 400px; + display: flex; + flex-direction: column; + overflow: hidden; + box-shadow: 0 0 4px var(--main-color4); } .chat-window { - flex: auto; - max-height: calc(100% - 60px); - background: #2f323b; - overflow: auto; + flex: auto; + max-height: calc(100% - 60px); + background: #2f323b; + overflow: auto; } .chat-input { - height: 30px; - display: flex; - flex: 0 0 auto; - height: 60px; - background: var(--main-color3); + height: 30px; + display: flex; + flex: 0 0 auto; + height: 60px; + background: var(--main-color3); } .chat-input input { - height: 59px; - line-height: 60px; - outline: 0 none; - border: none; - width: calc(100% - 60px); - color: var(--chat-bubble-message); - text-indent: 10px; - font-size: 12pt; - padding: 0; - background: var(--main-color3); + height: 59px; + line-height: 60px; + outline: 0 none; + border: none; + width: calc(100% - 60px); + color: var(--chat-bubble-message); + text-indent: 10px; + font-size: 12pt; + padding: 0; + background: var(--main-color3); } .chat-input button { - float: right; - outline: 0 none; - border: none; - background: var(--main-color3); - height: 40px; - width: 40px; - border-radius: 50%; - padding: 2px 0 0 0; - margin: 10px; + float: right; + outline: 0 none; + border: none; + background: var(--main-color3); + height: 40px; + width: 40px; + border-radius: 50%; + padding: 2px 0 0 0; + margin: 10px; } .chat-input input[good] + button { - box-shadow: 0 0 2px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.24); + box-shadow: + 0 0 2px rgba(0, 0, 0, 0.12), + 0 2px 4px rgba(0, 0, 0, 0.24); } .chat-input input[good] + button:hover { - box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); - /* filter: brightness(150%); */ + box-shadow: + 0 8px 17px 0 rgba(0, 0, 0, 0.2), + 0 6px 20px 0 rgba(0, 0, 0, 0.19); + /* filter: brightness(150%); */ } .chat-input input[good] + button path { - fill: var(--chat-bubble-message); + fill: var(--chat-bubble-message); } .msg-container { - direction: ltr; - position: static; - display: inline-block; - width: 100%; - padding: 0px 0px 10px 0px; + direction: ltr; + position: static; + width: 100%; + padding: 10px 0px 0px 0px; + display: grid; + grid-template: 1fr / 1fr; + align-self: start; } -.msg-container-user { - direction: rtl; - position: static; - display: inline-block; - width: 100%; - margin-top: 10px; +.msg-container > * { + grid-column: 1 / 1; + grid-row: 1 / 1; +} + +.msg-container.sender { + place-items: self-start; +} + +.msg-container.user { + place-items: self-end; } .msg-box { - background: var(--chat-bubble); - color: white; - min-width: 150px; - border-radius: 5px; - padding: 20px 5px 5px 25px; - margin: 20px 0px 0px 25px; - box-shadow: 0 0 2px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.24); - width: fit-content; + background: var(--chat-bubble); + color: white; + min-width: 100px; + border-radius: 5px; + padding: 18px 5px 5px 5px; + box-shadow: + 0 0 2px rgba(0, 0, 0, 0.12), + 0 2px 4px rgba(0, 0, 0, 0.24); + width: fit-content; + position: relative; + align-self: start; } -.msg-box-user { - background: var(--chat-bubble); - color: white; - text-align: -webkit-left; - min-width: 150px; - border-radius: 5px; - padding: 20px 15px 10px 5px; - margin: 0px 35px 0px 25px; - box-shadow: 0 0 2px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.24); - width: fit-content; +.msg-box.sender { + margin: 25px 25px 0px 35px; +} + +.msg-box.user { + text-align: left; + margin: 25px 35px 0px 0px; } .msg-box-user-temp { - background: var(--chat-bubble-temp); + background: var(--chat-bubble-temp); } .user-img { - display: inline-block; - border-radius: 50%; - height: 50px; - width: 50px; - z-index: 5; + display: inline-block; + position: relative; + border-radius: 50%; + height: 50px; + width: 50px; + z-index: 5; + align-self: start; } -.user-img-user { - display: inline-block; - border-radius: 50%; - height: 50px; - width: 50px; - position: absolute; - z-index: 5; -} - -.messages { - margin-left: 20px; -} - -.messages-user { - margin-right: 20px; +.messages.user { + margin-right: 20px; } .msg { - font-size: 12pt; - color: var(--chat-bubble-message); - margin: 0 0 0 0; + font-size: 12pt; + color: var(--chat-bubble-message); + margin: 0 0 0 0; } .msg-temp { - color: var(--chat-bubble-message-temp); + color: var(--chat-bubble-message-temp); } -/* .msg:first-of-type { - margin-top: 8px; -} */ - .timestamp { - color: var(--chat-bubble-header); - font-size: 10pt; - align-items: center; - font-family: 'xxii_avenmedium'; + color: var(--chat-bubble-header); + font-size: 10pt; + align-items: center; + font-family: 'xxii_avenmedium'; } .timestamp-temp { - color: var(--chat-bubble-header-temp); + color: var(--chat-bubble-header-temp); } .username { - float: left; - background-color: var(--main-color4); - margin-left: 25px; - color: white; - position: relative; - padding: 5px 5px 5px 30px; - border-radius: 5px; - top: 10px; - z-index: 1; + background-color: var(--main-color4); + color: white; + position: relative; + border-radius: 5px; + z-index: 3; + align-self: start; } -.username-user { - background-color: var(--main-color4); - margin-right: 25px; - color: white; - padding: 5px 40px 5px 15px; - border-radius: 5px; - margin: 0px 30px 5px 5px; - top: 15px; - position: relative; - z-index: 1; +.username.sender { + padding: 0px 5px 5px 30px; + margin: 20px 5px 5px 25px; +} + +.username.user { + padding: 0px 30px 5px 5px; + margin: 20px 30px 5px 5px; } .username-temp { - color: var(--chat-bubble-header-temp); + color: var(--chat-bubble-header-temp); } .post-time { - font-size: 8pt; - padding: 3px 5px 0px 15px; - color: white; - display: inline-block; - background-color: var(--main-color4); - right: 15px; - top: -19px; - position: relative; - z-index: 2; - border-radius: 5px; - text-align: center; + font-size: 8pt; + color: white; + display: inline-block; + background-color: var(--main-color4); + position: relative; + z-index: 2; + border-radius: 5px; + align-self: start; +} +.post-time.sender { + padding: 5px 5px 0px 15px; + margin: 0px 0px 0px 50px; } -.post-time-user { - font-size: 8pt; - padding: 3px 15px 0px 5px; - margin: 5px -15px 0px -10px; - color: white; - display: inline-block; - background-color: var(--main-color4); - right: 60px; - top: -19px; - position: relative; - z-index: 2; - border-radius: 5px; - text-align: center; +.post-time.user { + padding: 5px 15px 0px 5px; + margin: 0px 50px 0px 0px; } -/* .msg-self .msg-box { - border-radius: 6px 6px 6px 6px; - background: var(--main-color1); - float: right; -} - -.msg-self .user-img { - align-items: center; -} - -.msg-self .msg { - text-align: justify; - text-justify: inter-word; -} */ - .mmg { - display: flex; -} - -.icon-container { - height: 50px; - position: absolute; - left: 0; - display: flex; - align-items: center; -} - -.icon-container-user { - direction: ltr; - height: 50px; - position: absolute; - display: flex; - align-items: center; + display: flex; } .img { - height: 100%; - width: 100%; - border-radius: 50%; + height: 100%; + width: 100%; + border-radius: 50%; } .status-circle { - width: 20px; - height: 20px; - border-radius: 50%; - margin-left: -15px; - z-index: 6; - margin-top: -30px; + width: 20px; + border-radius: 50%; + z-index: 6; + position: relative; + align-self: start; } -.status-circle-user { - width: 20px; - height: 20px; - border-radius: 50%; - z-index: 6; - margin-top: -30px; +.status-circle.sender { + margin-left: 40px; +} + +.status-circle.user { + margin-right: 40px; } .menu-select { - font-size: 0.9rem; - height: 40px; - border-radius: 20px; - background-color: var(--main-color3); - color: var(--main-color2); - align-items: center; - border: 0px; - padding-left: 10px; - width: 300px; - font-size: 100%; - padding: 10px; - padding-right: 25px; - outline: none; - -webkit-appearance: none; - -moz-appearance: none; - background-image: url("data:image/svg+xml;utf8,"); - background-repeat: no-repeat; - background-position-x: 100%; - background-position-y: 5px; + font-size: 0.9rem; + height: 40px; + border-radius: 20px; + background-color: var(--main-color3); + color: var(--main-color2); + align-items: center; + border: 0px; + padding-left: 10px; + width: 300px; + font-size: 100%; + padding: 10px; + padding-right: 25px; + outline: none; + -webkit-appearance: none; + -moz-appearance: none; + background-image: url("data:image/svg+xml;utf8,"); + background-repeat: no-repeat; + background-position-x: 100%; + background-position-y: 5px; } .top-select { - width: auto; - height: 24px; - padding: 0px; - margin: 0px; - background-color: transparent; - color: white; - -webkit-appearance: none; - -moz-appearance: none; - border: none; + width: auto; + height: 24px; + padding: 0px; + margin: 0px; + background-color: transparent; + color: white; + -webkit-appearance: none; + -moz-appearance: none; + border: none; } .info-image { - width: 50px; - height: 50px; + width: 50px; + height: 50px; } .top-select option { - margin: 40px; - background: rgba(0, 0, 0, 0.3); - color: #fff; - text-shadow: 0 1px 0 rgba(0, 0, 0, 0.4); - background-color: var(--top-bar); + margin: 40px; + background: rgba(0, 0, 0, 0.3); + color: #fff; + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.4); + background-color: var(--top-bar); } .AdvancedMenu { - border: 1px var(--main-color2) solid; - margin-top: 10px; - min-width: 555px; - border-radius: 5px; - border-radius: 5px; + border: 1px var(--main-color2) solid; + margin-top: 10px; + min-width: 555px; + border-radius: 5px; + border-radius: 5px; } .legendStyle { - margin-left: 1em; - padding: 0.2em 0.8em; - display: flex; - align-items: center; + margin-left: 1em; + padding: 0.2em 0.8em; + display: flex; + align-items: center; } .AdvancedMenuRow { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - justify-content: left; - margin-bottom: 10px; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + justify-content: left; + margin-bottom: 10px; } .AdvancedMenuLabel { - font-size: 10pt; - padding-right: 5px; - margin-left: 10px; - width: 125px; + font-size: 10pt; + padding-right: 5px; + margin-left: 10px; + width: 125px; } .AdvancedMenuLabel2 { - font-size: 10pt; - padding-right: 5px; - margin-left: 10px; + font-size: 10pt; + padding-right: 5px; + margin-left: 10px; } .AdvancedMenuLabel3 { - font-size: 12pt; - padding-right: 5px; - margin-left: 10px; + font-size: 12pt; + padding-right: 5px; + margin-left: 10px; } #SaveAdvancedSettingsButton { - margin-left: 10px; + margin-left: 10px; } .toggle { - position: relative; - display: inline-block; - width: 60px; - height: 40px; - background-color: var(--main-color3); - border-radius: 20px; + position: relative; + display: inline-block; + width: 60px; + height: 40px; + background-color: var(--main-color3); + border-radius: 20px; } /* After slide changes */ .toggle:after { - content: ''; - position: absolute; - width: 30px; - height: 30px; - border-radius: 50%; - background-color: var(--main-color2); - left: 5px; - top: 5px; + content: ''; + position: absolute; + width: 30px; + height: 30px; + border-radius: 50%; + background-color: var(--main-color2); + left: 5px; + top: 5px; } /* Checkbox checked effect */ .checkbox:checked + .toggle::after { - left: 25px; + left: 25px; } /* Checkbox checked toggle label bg color */ .checkbox:checked + .toggle { - background-color: var(--main-color1); + background-color: var(--main-color1); } /* Checkbox vanished */ .checkbox { - display: none; + display: none; } /* Small toggle */ @@ -519,36 +471,105 @@ h1 { /* toggle in label designing */ .toggle-small { - position: relative; - display: inline-block; - width: 30px; - height: 20px; - background-color: var(--main-color3); - border-radius: 10px; - margin-left: 10px; + position: relative; + display: inline-block; + width: 30px; + height: 20px; + background-color: var(--main-color3); + border-radius: 10px; + margin-left: 10px; } /* After slide changes */ .toggle-small:after { - content: ''; - position: absolute; - width: 15px; - height: 15px; - border-radius: 50%; - background-color: white; - left: 2px; - top: 2px; + content: ''; + position: absolute; + width: 15px; + height: 15px; + border-radius: 50%; + background-color: white; + left: 2px; + top: 2px; } /* Checkbox checked effect */ .checkbox:checked + .toggle-small::after { - left: 13px; + left: 13px; } /* Checkbox checked toggle label bg color */ .checkbox:checked + .toggle-small { - background-color: var(--main-color1); + background-color: var(--main-color1); +} + +.emotes { + position: relative; + cursor: pointer; +} + +.dark { + display: none; + position: absolute; + z-index: 1; + top: -400px; +} + +.emotes:hover .dark { + display: block; +} + +.fi { + position: relative; + z-index: 5; + border-radius: 50%; +} + +.translation-header { + background-color: var(--main-color4); + border-radius: 5px; + width: fit-content; + padding: 5px; + margin: 10px 0px 5px -5px; + position: relative; +} + +.translation-message { + position: relative; + margin: 20px 0px 0px 0px; +} + +.translation-message.user { + margin: -20px 0px 0px 0px; +} + +.translation-icon { + position: relative; + padding: 0px 0px 0px 0px; + margin: -45px 0px 0px -40px; + top: 30px; +} + +.language-icon { + position: relative; + top: 45px; +} + +.flag-icon { + width: 20px !important; + height: 20px !important; + left: 18px; +} + +.flag-icon.user { + left: -18px; + top: -15px; +} + +.user-flag { + left: unset; + right: 18px; + top: -65px; } diff --git a/src/css/checkbox.css b/src/css/checkbox.css index 2a01657..15b2bdb 100644 --- a/src/css/checkbox.css +++ b/src/css/checkbox.css @@ -1,62 +1,62 @@ -input[type="radio"]:checked { - visibility: hidden; - position: absolute; +input[type='radio']:checked { + visibility: hidden; + position: absolute; } -input[type="radio"] { - visibility: hidden; - position: absolute; +input[type='radio'] { + visibility: hidden; + position: absolute; } label.btn span { - font-size: 1.5em; + font-size: 1.5em; } -label input[type="radio"]~i.fa.fa-square { - color: var(--main-color3); - display: inline; +label input[type='radio'] ~ i.fa.fa-square { + color: var(--main-color3); + display: inline; } -label input[type="radio"]~i.fa.fa-check-square { - display: none; +label input[type='radio'] ~ i.fa.fa-check-square { + display: none; } -label input[type="radio"]:checked~i.fa.fa-square { - display: none; +label input[type='radio']:checked ~ i.fa.fa-square { + display: none; } -label input[type="radio"]:checked~i.fa.fa-check-square { - display: inline; - color: var(--main-color2); +label input[type='radio']:checked ~ i.fa.fa-check-square { + display: inline; + color: var(--main-color2); } -label:hover input[type="radio"]~i.fa { - color: var(--main-color1); - /* filter: brightness(150%); */ +label:hover input[type='radio'] ~ i.fa { + color: var(--main-color1); + /* filter: brightness(150%); */ } -div[data-toggle="buttons"] label { - display: inline-block; - padding: 3px 12px; - margin-bottom: 0; - font-size: 20px; - font-weight: normal; - line-height: 2em; - text-align: left; - white-space: nowrap; - vertical-align: top; - cursor: pointer; - background-color: none; - border-radius: 3px; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - -o-user-select: none; - user-select: none; +div[data-toggle='buttons'] label { + display: inline-block; + padding: 3px 12px; + margin-bottom: 0; + font-size: 20px; + font-weight: normal; + line-height: 2em; + text-align: left; + white-space: nowrap; + vertical-align: top; + cursor: pointer; + background-color: none; + border-radius: 3px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; } -div[data-toggle="buttons"] label:active, -div[data-toggle="buttons"] label.active { - -webkit-box-shadow: none; - box-shadow: none; -} \ No newline at end of file +div[data-toggle='buttons'] label:active, +div[data-toggle='buttons'] label.active { + -webkit-box-shadow: none; + box-shadow: none; +} diff --git a/src/css/home.css b/src/css/home.css index b612164..07cf50a 100644 --- a/src/css/home.css +++ b/src/css/home.css @@ -1,253 +1,262 @@ /* Basic styling */ :root { - overflow: hidden; - --main-color1: #6e2c8c; - --main-color1-temp: #6e2c8c; - /*Left bar and top right bar*/ - --main-color2: white; - --main-color2-temp: white; - /*Icons and text*/ - --main-color3: #211e1e; - --main-color3-temp: #211e1e; - /*Buttons and input*/ - --main-color4: #2f2c34; - --main-color4-temp: #2f2c34; - --top-bar: #100b12; - --top-bar-temp: #100b12; - --mid-section: #352d3d; - --mid-section-temp: #352d3d; - --chat-bubble: #7a6d7f; - --chat-bubble-header: #141414; - --chat-bubble-username: white; - --chat-bubble-message: white; - --chat-bubble-temp: #7a6d7f; - --chat-bubble-header-temp: #141414; - --chat-bubble-message-temp: white; + overflow: hidden; + --main-color1: #6e2c8c; + --main-color1-temp: #6e2c8c; + /*Left bar and top right bar*/ + --main-color2: white; + --main-color2-temp: white; + /*Icons and text*/ + --main-color3: #211e1e; + --main-color3-temp: #211e1e; + /*Buttons and input*/ + --main-color4: #2f2c34; + --main-color4-temp: #2f2c34; + --top-bar: #100b12; + --top-bar-temp: #100b12; + --mid-section: #352d3d; + --mid-section-temp: #352d3d; + --chat-bubble: #7a6d7f; + --chat-bubble-header: #141414; + --chat-bubble-username: white; + --chat-bubble-message: white; + --chat-bubble-temp: #7a6d7f; + --chat-bubble-header-temp: #141414; + --chat-bubble-message-temp: white; } html { - box-sizing: border-box; + box-sizing: border-box; } *, *:before, *:after { - box-sizing: inherit; -} - -html, -body { - height: 100%; - margin: 0; - /* border-top-left-radius: 20px; */ - /* border-top-right-radius: 20px; */ - overflow-x: hidden; + box-sizing: inherit; } body { - font-family: 'Segoe UI', sans-serif; - background: transparent; -} - -/* Styling of window frame and titlebar */ - -body { - /* border: 1px solid #48545c; */ - overflow-y: hidden; + height: 100%; + margin: 0; + font-family: 'Segoe UI', sans-serif; + background: transparent; + position: relative; + /* overflow-y: hidden; + overflow-x: hidden; */ } #titlebar { - display: block; - /* position: fixed; */ - height: 32px; - width: calc(100%); - background-color: var(--top-bar); - /* border-top-left-radius: 20px; + display: block; + /* position: fixed; */ + height: 32px; + width: calc(100%); + background-color: var(--top-bar); + /* border-top-left-radius: 20px; border-top-right-radius: 20px; */ } .maximized #titlebar { - width: 100%; - padding: 0; + width: 100%; + padding: 0; } #main { - height: calc(100% - 32px); - margin-top: 32px; - padding: 20px; - overflow-y: auto; - display: flex; + height: calc(100% - 32px); + margin-top: 32px; + padding: 20px; + overflow-y: auto; + display: flex; } #titlebar { - padding: 4px; + padding: 4px; } #titlebar #drag-region { - width: 100%; - height: 100%; - -webkit-app-region: drag; - display: inline-flex; + width: 100%; + height: 100%; + -webkit-app-region: drag; + display: inline-flex; } #titlebar { - color: var(--main-color2); + color: var(--main-color2); } #window-title { - grid-column: 1; - display: flex; - align-items: center; - margin-left: 8px; - overflow: hidden; - font-family: 'Segoe UI', sans-serif; - font-size: 12px; + grid-column: 1; + display: flex; + align-items: center; + margin-left: 8px; + overflow: hidden; + font-family: 'Segoe UI', sans-serif; + font-size: 12px; } .maximized #window-title { - margin-left: 12px; + margin-left: 12px; } #window-title span { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - line-height: 1.5; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + line-height: 1.5; } #window-controls { - display: grid; - grid-template-columns: repeat(3, 46px); - position: absolute; - top: 0; - right: 0; - height: 32px; - -webkit-app-region: no-drag; + display: grid; + grid-template-columns: repeat(3, 46px); + position: absolute; + top: 0; + right: 0; + height: 32px; + -webkit-app-region: no-drag; } #window-controls .button { - grid-row: 1 / span 1; - display: flex; - justify-content: center; - align-items: center; - width: 100%; - height: 32px; + grid-row: 1 / span 1; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 32px; } @media (-webkit-device-pixel-ratio: 1.5), - (device-pixel-ratio: 1.5), - (-webkit-device-pixel-ratio: 2), - (device-pixel-ratio: 2), - (-webkit-device-pixel-ratio: 3), - (device-pixel-ratio: 3) { - #window-controls .icon { - width: 10px; - height: 10px; - } + (device-pixel-ratio: 1.5), + (-webkit-device-pixel-ratio: 2), + (device-pixel-ratio: 2), + (-webkit-device-pixel-ratio: 3), + (device-pixel-ratio: 3) { + #window-controls .icon { + width: 10px; + height: 10px; + } } #window-controls .button { - user-select: none; + user-select: none; } #window-controls .button:hover { - background: rgba(255, 255, 255, 0.1); - /* filter: brightness(150%); */ + background: rgba(255, 255, 255, 0.1); + /* filter: brightness(150%); */ } #window-controls .button:active { - background: rgba(255, 255, 255, 0.2); + background: rgba(255, 255, 255, 0.2); } #close-button:hover { - background: rgba(255, 255, 255, 0.1); - /* border-top-right-radius: 20px; */ - background: #f1707a !important; + background: rgba(255, 255, 255, 0.1); + /* border-top-right-radius: 20px; */ + background: #f1707a !important; } #close-button:active { - background: #f1707a !important; + background: #f1707a !important; } #close-button:active .icon { - filter: invert(1); - background: #f1707a !important; + filter: invert(1); + background: #f1707a !important; } #min-button { - grid-column: 1; + grid-column: 1; } #max-button, #restore-button { - grid-column: 2; + grid-column: 2; } #close-button { - grid-column: 3; + grid-column: 3; } #restore-button { - display: none !important; + display: none !important; } .maximized #restore-button { - display: flex !important; + display: flex !important; } .maximized #max-button { - display: none; + display: none; } .active-mic { - position: absolute; - bottom: 0; + position: absolute; + bottom: 0; } .about { - -webkit-app-region: no-drag; - position: absolute; - left: 0; - width: 32px; - text-align: -webkit-center; + -webkit-app-region: no-drag; + position: absolute; + left: 0; + width: 32px; + text-align: -webkit-center; } .language-selector { - position: absolute; - -webkit-app-region: no-drag; - display: inline-block; - background-color: transparent; - cursor: pointer; - font-family: 'NotoColorEmojiLimited', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, - 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; - left: 50%; - transform: translateX(-50%); + position: absolute; + -webkit-app-region: no-drag; + display: inline-block; + background-color: transparent; + cursor: pointer; + font-family: + 'NotoColorEmojiLimited', + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Helvetica, + Arial, + sans-serif, + 'Apple Color Emoji', + 'Segoe UI Emoji', + 'Segoe UI Symbol'; + left: 50%; + transform: translateX(-50%); } .language-dropdown { - display: none; - position: absolute; - background-color: #fff; - width: 55px; - box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); - z-index: 2; - font-family: 'NotoColorEmojiLimited', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, - 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; + display: none; + position: absolute; + background-color: #fff; + width: 55px; + box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); + z-index: 2; + font-family: + 'NotoColorEmojiLimited', + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Helvetica, + Arial, + sans-serif, + 'Apple Color Emoji', + 'Segoe UI Emoji', + 'Segoe UI Symbol'; } .language-item { - padding: 5px; - cursor: pointer; - background-color: var(--top-bar); + padding: 5px; + cursor: pointer; + background-color: var(--top-bar); } -.language-item:hover { - /* filter: brightness(150%); */ -} +/* .language-item:hover { + filter: brightness(150%); +} */ @font-face { - font-family: NotoColorEmojiLimited; - unicode-range: U+1F1E6-1F1FF; - src: url(https://raw.githack.com/googlefonts/noto-emoji/main/fonts/NotoColorEmoji.ttf); + font-family: NotoColorEmojiLimited; + unicode-range: U+1F1E6-1F1FF; + src: url(https://raw.githack.com/googlefonts/noto-emoji/main/fonts/NotoColorEmoji.ttf); } diff --git a/src/css/logger.css b/src/css/logger.css index 6b40515..4e2fae4 100644 --- a/src/css/logger.css +++ b/src/css/logger.css @@ -1,34 +1,34 @@ table { - margin-left: 10px; - background-color: white; - border-collapse: collapse; - width: 100%; - margin-top: 60px; + margin-left: 10px; + background-color: white; + border-collapse: collapse; + width: 100%; + margin-top: 60px; } th, td { - border: 1px solid black; - padding: 8px; - text-align: left; + border: 1px solid black; + padding: 8px; + text-align: left; } .info { - background-color: lightblue !important; + background-color: lightblue !important; } .warn { - background-color: #f39c12 !important; + background-color: #f39c12 !important; } .error { - background-color: #e74c3c !important; + background-color: #e74c3c !important; } #logTable { - width: 95%; + width: 95%; } #Logs { - overflow: auto; -} \ No newline at end of file + overflow: auto; +} diff --git a/src/css/menu.css b/src/css/menu.css index efdfed2..c874a9a 100644 --- a/src/css/menu.css +++ b/src/css/menu.css @@ -1,226 +1,273 @@ .container { - display: flex; - height: calc(100vh - 32px); - width: 100%; - display: -webkit-box; - display: -ms-flexbox; - display: flex; - /* will contain if #first is longer than #second */ + display: flex; + height: calc(100vh - 32px); + width: 100%; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + /* will contain if #first is longer than #second */ } .mid { - flex: 3; - display: block; - position: relative; + flex: 3; + display: block; + position: relative; } .OptionPanel { - /* visibility: hidden; */ - background-color: var(--mid-section); - flex: 3; - display: none; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; + /* visibility: hidden; */ + background-color: var(--mid-section); + flex: 3; + display: none; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; } .OptionPanel.show { - display: block; - overflow: auto; + display: block; + overflow: auto; } .menu { - height: 100%; - display: block; - width: 100%; - background: var(--main-color1); - z-index: 1; - position: relative; + height: 100%; + display: block; + width: 100%; + background: var(--main-color1); + z-index: 1; + position: relative; } .menu .items { - list-style: none; - margin: auto; - padding: 0; + list-style: none; + margin: auto; + padding: 0; } #rpe { - font-size: 8pt; - margin: 2px 0px 0px 0px; + font-size: 8pt; + margin: 2px 0px 0px 0px; } .menu .items .item { - width: 100%; - height: 50px; - display: flex; - justify-content: center; - align-items: center; - background: var(--main-color1); - color: var(--main-color2); -} - -.hdp:hover { - position: fixed; - /* filter: brightness(150%); */ + width: 100%; + height: 50px; + display: flex; + justify-content: center; + align-items: center; + background: var(--main-color1); + color: var(--main-color2); } .menu .items .item-active { - background: -webkit-linear-gradient(left, var(--main-color2) 10%, var(--main-color2), var(--main-color1) 10%, var(--main-color1) 10%); - color: var(--main-color2); - filter: brightness(90%); + background: -webkit-linear-gradient(left, var(--main-color2) 10%, var(--main-color2), var(--main-color1) 10%, var(--main-color1) 10%); + color: var(--main-color2); + filter: brightness(90%); } .menu .items .item:hover { - cursor: pointer; - color: var(--main-color2); - filter: brightness(120%); + cursor: pointer; + color: var(--main-color2); + filter: brightness(120%); } .sidepanel-left { - position: relative; - width: 50px; - font-size: 1.5em; - line-height: 1.5em; - font-family: Helvetica; - text-align: center; - box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px; - transition: 0.3s ease-in-out; + position: relative; + width: 50px; + font-size: 1.5em; + line-height: 1.5em; + font-family: Helvetica; + text-align: center; + box-shadow: + rgba(0, 0, 0, 0.16) 0px 3px 6px, + rgba(0, 0, 0, 0.23) 0px 3px 6px; + transition: 0.3s ease-in-out; } .sidepanel-right { - position: relative; - width: 200px; - font-size: 1.5em; - line-height: 1.5em; - font-family: Helvetica; - text-align: center; - box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px; - transition: 0.3s ease-in-out; + position: relative; + width: 200px; + font-size: 1.5em; + line-height: 1.5em; + font-family: Helvetica; + text-align: center; + box-shadow: + rgba(0, 0, 0, 0.16) 0px 3px 6px, + rgba(0, 0, 0, 0.23) 0px 3px 6px; + transition: 0.3s ease-in-out; } .collapse-menu-left { - margin-left: -50px; + margin-left: -50px; } .collapse-menu-right { - margin-right: -200px; + margin-right: -200px; } .sidepanel-left span { - position: relative; - display: block; + position: relative; + display: block; } .sidepanel-right span { - position: relative; - display: block; + position: relative; + display: block; } .circle-left { - position: absolute; - width: 50px; - height: 50px; - -webkit-clip-path: polygon(100% 0, 0 0, 0 100%); - clip-path: ellipse(68% 50% at 6% 50%); - font-size: 20px; - background-color: var(--main-color3); - color: var(--main-color2); - justify-content: left; - top: 32px; - left: 50px; - cursor: pointer; - display: flex; - z-index: 1; - transition: 0.3s ease-in-out; + position: absolute; + width: 50px; + height: 50px; + -webkit-clip-path: polygon(100% 0, 0 0, 0 100%); + clip-path: ellipse(68% 50% at 6% 50%); + font-size: 20px; + background-color: var(--main-color3); + color: var(--main-color2); + justify-content: left; + top: 32px; + left: 50px; + cursor: pointer; + display: flex; + z-index: 2; + transition: 0.3s ease-in-out; } .collapse-circle-left { - left: 0px; + left: 0px; } .circle-right { - position: absolute; - width: 50px; - height: 50px; - -webkit-clip-path: polygon(100% 100%, 0 0, 100% 0); - clip-path: ellipse(68% 50% at 94% 50%); - font-size: 20px; - background-color: var(--main-color3); - color: var(--main-color2); - justify-content: right; - top: 32px; - right: 199px; - cursor: pointer; - display: flex; - z-index: 1; - transition: 0.3s ease-in-out; + position: absolute; + width: 50px; + height: 50px; + -webkit-clip-path: polygon(100% 100%, 0 0, 100% 0); + clip-path: ellipse(68% 50% at 94% 50%); + font-size: 20px; + background-color: var(--main-color3); + color: var(--main-color2); + justify-content: right; + top: 32px; + right: 199px; + cursor: pointer; + display: flex; + z-index: 1; + transition: 0.3s ease-in-out; } .collapse-circle-right { - right: 0px; + right: 0px; } .fa-cog { - align-items: center; - align-self: center; - margin-left: 7px; + align-items: center; + align-self: center; + margin-left: 7px; } .fa-eye { - align-self: center; - margin-right: 7px; + align-self: center; + margin-right: 7px; } #min-button { - grid-column: 1; + grid-column: 1; } #max-button, #restore-button { - grid-column: 2; + grid-column: 2; } #close-button { - grid-column: 3; + grid-column: 3; } #mini-container { - width: 100%; - height: 300px; - display: flex; - flex-direction: column; - margin-bottom: 25px; + width: 100%; + height: 300px; + display: flex; + flex-direction: column; + margin-bottom: 25px; } #mini-main { - display: flex; - height: 100%; - flex-direction: row; + display: flex; + height: 100%; + flex-direction: row; } #mini-top-bar { - height: 30px; - background-color: var(--top-bar-temp); - /* border-top-left-radius: 20px; + height: 30px; + background-color: var(--top-bar-temp); + /* border-top-left-radius: 20px; border-top-right-radius: 20px; */ } #mini-left { - flex: 1; - background-color: var(--main-color1-temp); - height: 100%; + flex: 1; + background-color: var(--main-color1-temp); + height: 100%; } #mini-mid { - flex: 8; - background-color: var(--mid-section-temp); - height: 100%; + flex: 8; + background-color: var(--mid-section-temp); + height: 100%; } #mini-right { - flex: 2; - background-color: var(--main-color4-temp); - height: 100%; + flex: 2; + background-color: var(--main-color4-temp); + height: 100%; +} + +.pop { + position: relative; + cursor: pointer; +} + +.pop-selection { + width: 20px !important; + height: 20px !important; +} + +.miniText { + position: absolute; + font-size: 8pt; + color: white; + padding: 3px; +} + +.pop-content { + display: none; + position: absolute; + background-color: var(--main-color3); + z-index: 1; + height: 400px; + width: max-content; + overflow: auto; + grid-template-columns: repeat(3, 1fr); + top: -400px; + color: white; + border: 1px solid #444; +} +pop-content div { + color: white; + text-decoration: none; + display: block; +} +.pop-content div:hover { + backdrop-filter: invert(50%); +} +.pop:hover .pop-content { + display: grid; + grid-template-columns: repeat(3, 1fr); +} + +.language-select { + padding: 5px; + position: relative; } diff --git a/src/css/sliders.css b/src/css/sliders.css index ce80bf7..3742d78 100644 --- a/src/css/sliders.css +++ b/src/css/sliders.css @@ -1,179 +1,195 @@ /*generated with Input range slider CSS style generator (version 20211225) https://toughengineer.github.io/demo/slider-styler*/ input[type='range'].styled-slider { - /* height: 500px; */ - background: transparent; - -webkit-appearance: none; - width: 300px; + /* height: 500px; */ + background: transparent; + -webkit-appearance: none; + width: 300px; } /*progress support*/ input[type='range'].styled-slider.slider-progress1 { - --range: calc(var(--max) - var(--min)); - --ratio: calc((var(--tiempotemporal) - var(--min)) / var(--range)); - --sx: calc(0.5 * 2em + var(--ratio) * (100% - 2em)); + --range: calc(var(--max) - var(--min)); + --ratio: calc((var(--tiempotemporal) - var(--min)) / var(--range)); + --sx: calc(0.5 * 2em + var(--ratio) * (100% - 2em)); } input[type='range'].styled-slider.slider-progress2 { - --range: calc(var(--max) - var(--min)); - --ratio: calc((var(--tiempotemporal) - var(--min)) / var(--range)); - --sx: calc(0.5 * 2em + var(--ratio) * (100% - 2em)); + --range: calc(var(--max) - var(--min)); + --ratio: calc((var(--tiempotemporal) - var(--min)) / var(--range)); + --sx: calc(0.5 * 2em + var(--ratio) * (100% - 2em)); } input[type='range'].styled-slider.slider-progress3 { - --range: calc(var(--max) - var(--min)); - --ratio: calc((var(--tiempotemporal) - var(--min)) / var(--range)); - --sx: calc(0.5 * 2em + var(--ratio) * (100% - 2em)); + --range: calc(var(--max) - var(--min)); + --ratio: calc((var(--tiempotemporal) - var(--min)) / var(--range)); + --sx: calc(0.5 * 2em + var(--ratio) * (100% - 2em)); } input[type='range'].styled-slider.slider-progress4 { - --range: calc(var(--max) - var(--min)); - --ratio: calc((var(--tiempotemporal) - var(--min)) / var(--range)); - --sx: calc(0.5 * 2em + var(--ratio) * (100% - 2em)); + --range: calc(var(--max) - var(--min)); + --ratio: calc((var(--tiempotemporal) - var(--min)) / var(--range)); + --sx: calc(0.5 * 2em + var(--ratio) * (100% - 2em)); } /*webkit*/ input[type='range'].styled-slider::-webkit-slider-thumb { - -webkit-appearance: none; - width: 2em; - height: 40px; - border-radius: 20px; - background: #ffffff; - border: none; - box-shadow: 0 0 2px black; - margin-top: calc(2em * 0.5 - 2em * 0.5); + -webkit-appearance: none; + width: 2em; + height: 40px; + border-radius: 20px; + background: #ffffff; + border: none; + box-shadow: 0 0 2px black; + margin-top: calc(2em * 0.5 - 2em * 0.5); } input[type='range'].styled-slider::-webkit-slider-runnable-track { - height: 40px; - border: none; - border-radius: 20px; - background: #1a1a1a; - box-shadow: none; + height: 40px; + border: none; + border-radius: 20px; + background: #1a1a1a; + box-shadow: none; } input[type='range'].styled-slider.slider-progress1::-webkit-slider-runnable-track { - background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #1a1a1a; + background: + linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, + #1a1a1a; } input[type='range'].styled-slider.slider-progress2::-webkit-slider-runnable-track { - background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #1a1a1a; + background: + linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, + #1a1a1a; } input[type='range'].styled-slider.slider-progress3::-webkit-slider-runnable-track { - background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #1a1a1a; + background: + linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, + #1a1a1a; } input[type='range'].styled-slider.slider-progress4::-webkit-slider-runnable-track { - background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #1a1a1a; + background: + linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, + #1a1a1a; } /*mozilla*/ input[type='range'].styled-slider::-moz-range-thumb { - width: 2em; - height: 40px; - border-radius: 20px; - background: #ffffff; - border: none; - box-shadow: 0 0 2px black; + width: 2em; + height: 40px; + border-radius: 20px; + background: #ffffff; + border: none; + box-shadow: 0 0 2px black; } input[type='range'].styled-slider::-moz-range-track { - height: 40px; - border: none; - border-radius: 20px; - background: var(--main-color3); - box-shadow: none; + height: 40px; + border: none; + border-radius: 20px; + background: var(--main-color3); + box-shadow: none; } input[type='range'].styled-slider.slider-progress1::-moz-range-track { - background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #464646; + background: + linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, + #464646; } input[type='range'].styled-slider.slider-progress2::-moz-range-track { - background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #464646; + background: + linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, + #464646; } input[type='range'].styled-slider.slider-progress3::-moz-range-track { - background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #464646; + background: + linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, + #464646; } input[type='range'].styled-slider.slider-progress4::-moz-range-track { - background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #464646; + background: + linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, + #464646; } /*ms*/ input[type='range'].styled-slider::-ms-fill-upper { - background: transparent; - border-color: transparent; + background: transparent; + border-color: transparent; } input[type='range'].styled-slider::-ms-fill-lower { - background: transparent; - border-color: transparent; + background: transparent; + border-color: transparent; } input[type='range'].styled-slider::-ms-thumb { - width: 2em; - height: 40px; - border-radius: 20px; - background: #ffffff; - border: none; - box-shadow: 0 0 2px black; - margin-top: 0; - box-sizing: border-box; + width: 2em; + height: 40px; + border-radius: 20px; + background: #ffffff; + border: none; + box-shadow: 0 0 2px black; + margin-top: 0; + box-sizing: border-box; } input[type='range'].styled-slider::-ms-track { - height: 40px; - border-radius: 20px; - background: var(--main-color3); - border: none; - box-shadow: none; - box-sizing: border-box; + height: 40px; + border-radius: 20px; + background: var(--main-color3); + border: none; + box-shadow: none; + box-sizing: border-box; } input[type='range'].styled-slider.slider-progress1::-ms-fill-lower { - height: 40px; - border-radius: 1em 0 0 1em; - margin: -undefined 0 -undefined -undefined; - background: var(--main-color1); - border: none; - border-right-width: 0; + height: 40px; + border-radius: 1em 0 0 1em; + margin: -undefined 0 -undefined -undefined; + background: var(--main-color1); + border: none; + border-right-width: 0; } input[type='range'].styled-slider.slider-progress2::-ms-fill-lower { - height: 40px; - border-radius: 1em 0 0 1em; - margin: -undefined 0 -undefined -undefined; - background: var(--main-color1); - border: none; - border-right-width: 0; + height: 40px; + border-radius: 1em 0 0 1em; + margin: -undefined 0 -undefined -undefined; + background: var(--main-color1); + border: none; + border-right-width: 0; } input[type='range'].styled-slider.slider-progress3::-ms-fill-lower { - height: 40px; - border-radius: 1em 0 0 1em; - margin: -undefined 0 -undefined -undefined; - background: var(--main-color1); - border: none; - border-right-width: 0; + height: 40px; + border-radius: 1em 0 0 1em; + margin: -undefined 0 -undefined -undefined; + background: var(--main-color1); + border: none; + border-right-width: 0; } input[type='range'].styled-slider.slider-progress4::-ms-fill-lower { - height: 40px; - border-radius: 1em 0 0 1em; - margin: -undefined 0 -undefined -undefined; - background: var(--main-color1); - border: none; - border-right-width: 0; + height: 40px; + border-radius: 1em 0 0 1em; + margin: -undefined 0 -undefined -undefined; + background: var(--main-color1); + border: none; + border-right-width: 0; } .inputBox { - border: none; - width: 38px; - border-radius: 10px; - text-align: center; - font-size: 14pt; - font-weight: bold; + border: none; + width: 38px; + border-radius: 10px; + text-align: center; + font-size: 14pt; + font-weight: bold; } diff --git a/src/css/tabs.css b/src/css/tabs.css index e0eaaff..32dda3d 100644 --- a/src/css/tabs.css +++ b/src/css/tabs.css @@ -1,441 +1,419 @@ .viewer-list { - background-color: var(--main-color4); - height: 100%; - flex: 1; - font-size: 14px; - height: 100%; - display: block; + background-color: var(--main-color4); + height: 100%; + flex: 1; + font-size: 14px; + height: 100%; + display: block; } ul.no-bullets { - list-style-type: none; - list-style-position: outside; - margin: 0%; - padding: 0%; - padding-left: 5px; - text-align: left; + list-style-type: none; + list-style-position: outside; + margin: 0%; + padding: 0%; + padding-left: 5px; + text-align: left; } li { - line-height: 1em; + line-height: 1em; } h1.titles { - margin: 0px; - padding: 0%; - text-align: left; - padding-left: 5px; + margin: 0px; + padding: 0%; + text-align: left; + padding-left: 5px; } main { - background: var(--main-color1); - box-shadow: 0 3px 5px rgba(0, 0, 0, 0.2); - display: flex; - justify-content: space-between; - height: 32px; + background: var(--main-color1); + box-shadow: 0 3px 5px rgba(0, 0, 0, 0.2); + display: flex; + justify-content: space-between; + height: 32px; } main label { - text-align: center; - display: flex; - justify-content: center; - align-items: center; + text-align: center; + display: flex; + justify-content: center; + align-items: center; } p { - font-size: 13px; + font-size: 13px; } main input { - display: none; + display: none; } section { - clear: both; - padding-top: 10px; - display: none; - height: calc(100% - 60px); + clear: both; + padding-top: 10px; + display: none; + height: calc(100% - 60px); } .Basiclabel { - font-weight: bold; - font-size: 14px; - display: block; - float: left; - flex: auto; - font-size: 20px; - padding: 0px 10px; - width: 50px; - align-self: baseline; - text-align: center; + font-weight: bold; + font-size: 14px; + display: block; + float: left; + flex: auto; + font-size: 20px; + padding: 0px 10px; + width: 50px; + align-self: baseline; + text-align: center; } .scale { - height: 2em; - width: 2em; - vertical-align: bottom; + height: 2em; + width: 2em; + vertical-align: bottom; } .tab { - /* filter: invert(100%) sepia(100%) saturate(0%) hue-rotate(350deg) brightness(104%) contrast(101%); */ - align-items: flex-start; - margin: auto; - cursor: pointer; + /* filter: invert(100%) sepia(100%) saturate(0%) hue-rotate(350deg) brightness(104%) contrast(101%); */ + align-items: flex-start; + margin: auto; + cursor: pointer; } input:checked + label { - border-top-color: #ffb03d; - border-right-color: #ddd; - border-left-color: #ddd; - border-bottom-color: transparent; - text-decoration: none; + border-top-color: #ffb03d; + border-right-color: #ddd; + border-left-color: #ddd; + border-bottom-color: transparent; + text-decoration: none; } /* --------------------------------- */ .radius { - border-radius: 5px; + border-radius: 5px; } .radius .tabx:active::before { - border-radius: 5px !important; + border-radius: 5px !important; } .tabx-bar { - background-color: var(--main-color1); - /* padding: 5px; */ - box-shadow: 1px 4px 20px rgba(0, 0, 0, 0.2); - display: flex; - /* margin: 10px; */ - color: black; - height: 50px; + background-color: var(--main-color1); + /* padding: 5px; */ + box-shadow: 1px 4px 20px rgba(0, 0, 0, 0.2); + display: flex; + /* margin: 10px; */ + color: black; + height: 50px; } .tabx-bar .tabx { - box-sizing: border-box; - text-decoration: none; - color: inherit; - width: 70px; - height: 50px; - background: inherit; - display: inherit; - flex-direction: column; - justify-content: center; - align-items: center; - position: relative; - transition: all 0.3s; + box-sizing: border-box; + text-decoration: none; + color: inherit; + width: 70px; + height: 50px; + background: inherit; + display: inherit; + flex-direction: column; + justify-content: center; + align-items: center; + position: relative; + transition: all 0.3s; } .tabx-bar .tabx::before { - position: absolute; - content: ''; - width: 26%; - height: 13%; - border-top-left-radius: 200px; - border-top-right-radius: 200px; - border-bottom: none; - background-color: #607d8b; - /* bottom: -8px; */ - opacity: 0; - transition: all 0.3s ease-in-out; + position: absolute; + content: ''; + width: 26%; + height: 13%; + border-top-left-radius: 200px; + border-top-right-radius: 200px; + border-bottom: none; + background-color: #607d8b; + /* bottom: -8px; */ + opacity: 0; + transition: all 0.3s ease-in-out; } .tabx-bar .tabx:active::before { - width: 100%; - height: 100%; - background-color: #5fef8d; - border-radius: 0; + width: 100%; + height: 100%; + background-color: #5fef8d; + border-radius: 0; } .tabx-bar .tabx:hover::before { - opacity: 1; - bottom: 0px; + opacity: 1; + bottom: 0px; } -.tabx-bar .tabx::after { -} +/* .tabx-bar .tabx::after { +} */ .tabx-bar .tabx:hover { - padding-bottom: 10px; + padding-bottom: 10px; } .tabx-bar .tabx:hover::after { - opacity: 1; - top: 6px; + opacity: 1; + top: 6px; } .tabx-bar .tabx.selected { - background-color: rgba(0, 0, 0, 0.1); - border-radius: inherit; - padding-top: 0px; + background-color: rgba(0, 0, 0, 0.1); + border-radius: inherit; + padding-top: 0px; } .tabx-bar .tabx.selected::after { - opacity: 1; - top: -10px; + opacity: 1; + top: -10px; } .tabx-bar .tabx.selected::before { - opacity: 1; - bottom: 0px; + opacity: 1; + bottom: 0px; } .tabx-bar .tabx .icon { - color: inherit; - font-size: 24px; - display: inherit; + color: inherit; + font-size: 24px; + display: inherit; } .tabx-bar .tabx .icon img { - margin: auto; - height: 32 px; + margin: auto; + height: 32 px; } .language { - width: 80px; - text-align: center; + width: 80px; + text-align: center; } #AdvancedMenu_mask { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - margin-top: 0px; - width: 100%; - height: 100%; - visibility: hidden; - overflow: auto; - overflow-x: hidden; - background-color: var(--mid-section); - font-family: 'xxii_avenmedium'; - padding-left: 50px; - padding-right: 50px; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + margin-top: 0px; + width: 100%; + height: 100%; + visibility: hidden; + overflow: auto; + overflow-x: hidden; + background-color: var(--mid-section); + font-family: 'xxii_avenmedium'; + padding-left: 50px; + padding-right: 50px; } #ThemeCreator_mask { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - margin-top: 0px; - width: 100%; - height: 100%; - visibility: hidden; - flex-direction: column; - justify-content: center; - overflow: auto; - overflow-x: hidden; - background-color: var(--mid-section); - font-family: 'xxii_avenmedium'; - padding-left: 50px; - padding-right: 50px; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + margin-top: 0px; + width: 100%; + height: 100%; + visibility: hidden; + flex-direction: column; + justify-content: center; + overflow: auto; + overflow-x: hidden; + background-color: var(--mid-section); + font-family: 'xxii_avenmedium'; + padding-left: 50px; + padding-right: 50px; } .fname { - background: var(--main-color3); - border: none; - height: 40px; - border-radius: 40px; - width: 300px; - outline: none; - color: var(--main-color2); - font-size: 10pt; - padding-left: 10px; + background: var(--main-color3); + border: none; + height: 40px; + border-radius: 40px; + width: 300px; + outline: none; + color: var(--main-color2); + font-size: 10pt; + padding-left: 10px; } input[type='password'] { - background: var(--main-color3); - border: none; - height: 40px; - border-radius: 40px; - width: 300px; - outline: none; - color: var(--main-color2); - font-size: 10pt; - padding-left: 10px; - padding-right: 40px; - /* To make space for the reveal button */ + background: var(--main-color3); + border: none; + height: 40px; + border-radius: 40px; + width: 300px; + outline: none; + color: var(--main-color2); + font-size: 10pt; + padding-left: 10px; + padding-right: 40px; + /* To make space for the reveal button */ } input[type='url'] { - background: var(--main-color3); - border: none; - height: 40px; - border-radius: 40px; - width: 260px; - outline: none; - color: var(--main-color2); - font-size: 10pt; - margin-left: 10px; + background: var(--main-color3); + border: none; + height: 40px; + border-radius: 40px; + width: 260px; + outline: none; + color: var(--main-color2); + font-size: 10pt; + margin-left: 10px; } input[type='lol'] { - background: var(--main-color3); - border: none; - height: 40px; - border-radius: 40px; - width: 300px; - outline: none; - color: var(--main-color2); - font-size: 10pt; - padding-left: 10px; - padding-right: 40px; - /* To make space for the reveal button */ + background: var(--main-color3); + border: none; + height: 40px; + border-radius: 40px; + width: 300px; + outline: none; + color: var(--main-color2); + font-size: 10pt; + padding-left: 10px; + padding-right: 40px; + /* To make space for the reveal button */ } /* Style for the reveal button */ .password-toggle-btn { - position: absolute; - background-color: transparent; - border: none; - cursor: pointer; - left: 450px; + position: absolute; + background-color: transparent; + border: none; + cursor: pointer; + left: 450px; } /* Hide the default appearance of the button */ .password-toggle-btn::-moz-focus-inner { - border: 0; + border: 0; } /* Style the reveal icon (you can use your preferred icon or font) */ .password-toggle-icon { - font-size: 16px; - color: var(--main-color2); + font-size: 16px; + color: var(--main-color2); } #toasts { - position: absolute; - bottom: 20px; - /* Adjust the distance from the bottom of the screen */ - right: 0%; - /* Center the toasts horizontally */ - display: flex; - flex-direction: column; - z-index: 999; + position: absolute; + bottom: 20px; + /* Adjust the distance from the bottom of the screen */ + right: 0%; + /* Center the toasts horizontally */ + display: flex; + flex-direction: column; + z-index: 999; } .toast { - background-color: #333; - color: white; - border-radius: 5px; - padding: 1rem 2rem; - margin: 0.5rem; - opacity: 0; - transform: translateY(100%); - animation: toastAnimation 0.5s ease-in-out forwards, toastDisappear 0.5s ease-in-out 9s forwards; + background-color: #333; + color: white; + border-radius: 5px; + padding: 1rem 2rem; + margin: 0.5rem; + opacity: 0; + transform: translateY(100%); + animation: + toastAnimation 0.5s ease-in-out forwards, + toastDisappear 0.5s ease-in-out 9s forwards; } /* Apply different colors based on the toast type */ .info { - background-color: lightblue !important; + background-color: lightblue !important; } .success { - background-color: #2ecc71 !important; + background-color: #2ecc71 !important; } .warning { - background-color: #f39c12 !important; + background-color: #f39c12 !important; } .error { - background-color: #e74c3c !important; + background-color: #e74c3c !important; } /* CSS animation for the toast appearance */ @keyframes toastAnimation { - from { - opacity: 0; - transform: translateY(100%); - } + from { + opacity: 0; + transform: translateY(100%); + } - to { - opacity: 1; - transform: translateY(0); - } + to { + opacity: 1; + transform: translateY(0); + } } /* CSS animation for the toast disappearance */ @keyframes toastDisappear { - from { - opacity: 1; - } + from { + opacity: 1; + } - to { - opacity: 0; - } + to { + opacity: 0; + } } .menu-icon { - font-size: 17pt; + font-size: 17pt; } .tooltip { - position: absolute; - display: inline-block; - visibility: hidden; - font-size: 12px; - line-height: 20px; - padding: 5px; - background: var(--main-color3); - border-radius: 5px; - visibility: hidden; - opacity: 1; - box-shadow: -2px 2px 5px rgba(0, 0, 0, 0.2); - transition: opacity 0.3s, visibility 0s; - color: var(--main-color2); - font-family: 'xxii_avenmedium'; - z-index: 999; + position: absolute; + font-size: 12pt; + padding: 5px; + background: var(--main-color3); + border-radius: 5px; + visibility: hidden; + box-shadow: -2px 2px 5px rgba(0, 0, 0, 0.2); + color: var(--main-color2); + font-family: 'xxii_avenmedium'; + z-index: 999; + max-width: 200px; + width: max-content; } -/* .tooltip .tooltiptext { - width: 120px; - background-color: black; - color: #fff; - text-align: center; - border-radius: 6px; - padding: 5px 0; -} - -.tooltip .tooltiptext::after { - content: ""; - margin-left: -5px; - border-width: 5px; - border-style: solid; - border-color: black transparent transparent transparent; -} - -.tooltip:hover .tooltiptext { - visibility: visible; -} */ - div[type='text']:disabled { - background: #4b4b4b; - display: none; + background: #4b4b4b; + display: none; } input[type='text']:disabled { - background: #4b4b4b; + background: #4b4b4b; } button[type='text']:disabled { - background: #4b4b4b; - display: none; + background: #4b4b4b; + display: none; } input[type2='text']:disabled { - background: #4b4b4b; + background: #4b4b4b; } div:disabled { - background: #4b4b4b; - filter: brightness(200%); + background: #4b4b4b; + filter: brightness(200%); } div:disabled { - background: #4b4b4b; - filter: brightness(200%); + background: #4b4b4b; + filter: brightness(200%); } diff --git a/src/css/tts-menu.css b/src/css/tts-menu.css index fd36954..60284d9 100644 --- a/src/css/tts-menu.css +++ b/src/css/tts-menu.css @@ -1,161 +1,163 @@ #tstx { - display: flex; - flex-direction: row; - margin-left: 40px; + display: flex; + flex-direction: row; + margin-left: 40px; } .optionrow { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - justify-content: left; - margin-bottom: 10px; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + justify-content: left; + margin-bottom: 10px; } .LabelText { - margin-bottom: 5px; - color: var(--main-color2); - margin: 0px 0px 5px 0px; + margin-bottom: 5px; + color: var(--main-color2); + margin: 0px 0px 5px 0px; } #volume-icon { - color: var(--main-color2); - scale: 0.75; - cursor: pointer; - text-align: center; - align-self: center; + color: var(--main-color2); + scale: 0.75; + cursor: pointer; + text-align: center; + align-self: center; } #image { - width: 100px; - height: 100px; - margin-bottom: 10px; + width: 100px; + height: 100px; + margin-bottom: 10px; } .TTSVolume { - color: var(--main-color2); - font-size: 12pt; - text-align: center; + color: var(--main-color2); + font-size: 12pt; + text-align: center; } #SoundVolume { - color: var(--main-color2); - font-size: 12pt; + color: var(--main-color2); + font-size: 12pt; } .testLabel { - color: var(--main-color2); - font-size: 12pt; + color: var(--main-color2); + font-size: 12pt; } textarea { - height: 60px; - padding: 5px; - width: 300px; - resize: none; - border-radius: 5px; - background: var(--main-color3); - color: var(--main-color2); - font-family: 'xxii_avenmedium'; - border: none; + height: 60px; + padding: 5px; + width: 300px; + resize: none; + border-radius: 5px; + background: var(--main-color3); + color: var(--main-color2); + font-family: 'xxii_avenmedium'; + border: none; } .SaveConfig { - align-content: center; - display: flex; - flex-direction: column; - justify-content: center; - color: var(--main-color2); - margin-bottom: 10px; + align-content: center; + display: flex; + flex-direction: column; + justify-content: center; + color: var(--main-color2); + margin-bottom: 10px; } .SmallButton { - color: var(--main-color2); - width: 50px; - cursor: pointer; - text-shadow: 0 0 5px #070607, 0 0 5px #070607, 0 0 5px #070607; - /* transition: all 0.15s ease-in-out; */ - text-align: center; + color: var(--main-color2); + width: 50px; + cursor: pointer; + text-shadow: + 0 0 5px #070607, + 0 0 5px #070607, + 0 0 5px #070607; + transition: all 0.15s ease-in-out; + text-align: center; } .SmallButton:hover { - /* color: var(--main-color1); */ - width: 50px; - cursor: pointer; - /* filter: brightness(150%); */ + color: var(--main-color1); + cursor: pointer; } .SmallButton:active { - color: var(--main-color1); - transform: translateY(4px); - text-shadow: 0 0 5px #000, 0 0 5px #000, 0 0 5px #000; + color: var(--main-color1); + transform: translateY(4px); } .AdvancedMenuButton { - width: 300px; - height: 40px; - border-radius: 20px; - background-color: var(--main-color3); - color: var(--main-color2); - padding: 0; - border: none; - font-family: 'xxii_avenmedium'; - font-size: 14pt; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); - transition: box-shadow 0.3s ease, background-color 0.3s ease; - /* Add a smooth transition for box-shadow and background-color */ + width: 300px; + height: 40px; + border-radius: 20px; + background-color: var(--main-color3); + color: var(--main-color2); + padding: 0; + border: none; + font-family: 'xxii_avenmedium'; + font-size: 14pt; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + transition: + box-shadow 0.3s ease, + background-color 0.3s ease; + /* Add a smooth transition for box-shadow and background-color */ } .AdvancedMenuButton:hover { - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); - background-color: var(--main-color3); - filter: brightness(150%); - /* Darken the background color on hover */ - cursor: pointer; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); + background-color: var(--main-color3); + filter: brightness(150%); + /* Darken the background color on hover */ + cursor: pointer; } .AdvancedMenuButton:active { - transform: translateY(2px); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); - background-color: var(--main-color3); - /* Reset the background color on click */ + transform: translateY(2px); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + background-color: var(--main-color3); + /* Reset the background color on click */ } .AdvancedMenuIcon { - filter: invert(100%) sepia(100%) saturate(0%) hue-rotate(350deg) brightness(104%) contrast(101%); - align-items: flex-start; - margin: auto; - height: 24px; - width: 24px; + filter: invert(100%) sepia(100%) saturate(0%) hue-rotate(350deg) brightness(104%) contrast(101%); + align-items: flex-start; + margin: auto; + height: 24px; + width: 24px; } .AdvancedMenuIcon2 { - align-items: flex-start; - margin: auto; - height: 24px; - width: 24px; + align-items: flex-start; + margin: auto; + height: 24px; + width: 24px; } input:hover { - filter: brightness(120%); + filter: brightness(120%); } select:hover { - filter: brightness(120%); + filter: brightness(120%); } textarea:hover { - filter: brightness(120%); + filter: brightness(120%); } label:hover { - filter: brightness(120%); + filter: brightness(120%); } .circle-right:hover { - filter: brightness(120%); + filter: brightness(120%); } .circle-left:hover { - filter: brightness(120%); + filter: brightness(120%); } diff --git a/src/css/volume-slider.css b/src/css/volume-slider.css index 15fc8d9..ff7242f 100644 --- a/src/css/volume-slider.css +++ b/src/css/volume-slider.css @@ -1,87 +1,87 @@ .middle { - width: 100%; - height: 50px; - border-radius: 10px; - display: flex; - align-items: center; + width: 100%; + height: 50px; + border-radius: 10px; + display: flex; + align-items: center; } .slider-container { - width: 300px; - height: 100%; - position: relative; + width: 300px; + height: 100%; + position: relative; } .slider-container .bar { - display: block; - width: 100%; - height: 100%; - border-radius: 10px; - background-color: var(--main-color3); + display: block; + width: 100%; + height: 100%; + border-radius: 10px; + background-color: var(--main-color3); } .slider-container .bar .fill { - display: block; - width: 50%; - height: 100%; - border-radius: inherit; - background-color: var(--main-color1); + display: block; + width: 50%; + height: 100%; + border-radius: inherit; + background-color: var(--main-color1); } .slider-container .slider { - position: absolute; - top: 50%; - -webkit-appearance: none; - margin: 0; - width: 100%; - height: 0; - border-radius: 5px; - outline: none; - background-color: transparent; + position: absolute; + top: 50%; + -webkit-appearance: none; + margin: 0; + width: 100%; + height: 0; + border-radius: 5px; + outline: none; + background-color: transparent; } .slider-container .slider::-webkit-slider-thumb { - -webkit-appearance: none; - width: 30px; - height: 30px; - background-color: var(--main-color2); - border-top-left-radius: 50%; - border-top-right-radius: 50%; - border-bottom-right-radius: 50%; - border-bottom-left-radius: 50%; - transform: rotate(-45deg) translate(0%, 0%); - cursor: pointer; - outline: none; - /* box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); */ - box-shadow: 0 0 3px rgb(0 0 0 / 10%); - /* transition: .3s ease-in-out; */ + -webkit-appearance: none; + width: 30px; + height: 30px; + background-color: var(--main-color2); + border-top-left-radius: 50%; + border-top-right-radius: 50%; + border-bottom-right-radius: 50%; + border-bottom-left-radius: 50%; + transform: rotate(-45deg) translate(0%, 0%); + cursor: pointer; + outline: none; + /* box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); */ + box-shadow: 0 0 3px rgb(0 0 0 / 10%); + /* transition: .3s ease-in-out; */ } .slider-container .slider:active::-webkit-slider-thumb, .slider-container .slider::-webkit-slider-thumb:hover { - border-bottom-left-radius: 0; - transform: rotate(-45deg) translate(50%, -50%); + border-bottom-left-radius: 0; + transform: rotate(-45deg) translate(50%, -50%); } .slider-container .slider:active::-webkit-slider-thumb { - box-shadow: 0 0 0 3px rgba(255, 255, 255, 1); + box-shadow: 0 0 0 3px rgba(255, 255, 255, 1); } .option-icon-container { - display: flex; - flex-direction: column; - /* justify-content: center; */ - align-items: center; - /* width: 78px; */ - /* float: right; */ - flex-grow: 1; + display: flex; + flex-direction: column; + /* justify-content: center; */ + align-items: center; + /* width: 78px; */ + /* float: right; */ + flex-grow: 1; } .brush-icon-container { - margin-left: -20px; - font-size: 20pt; + margin-left: -20px; + font-size: 20pt; } .icon { - fill: var(--main-color2); + fill: var(--main-color2); } diff --git a/src/images/icon.ico b/src/images/icon.ico index 84862b2..5c32439 100644 Binary files a/src/images/icon.ico and b/src/images/icon.ico differ diff --git a/src/index.html b/src/index.html index 4a0caef..c1aa50d 100644 --- a/src/index.html +++ b/src/index.html @@ -1,652 +1,740 @@ - + - - - - - LoquendoBot + + + + + LoquendoBot - - - - - - - - - - - - - - - - - -
- -
-
-
- -  -
-
- - -
-
-
- -
-
- -
-
- -
-
-
-
- -
- - - -
- -
-
-
- -
-
- - - -
- -
- - - - - - - - - - -
LevelMessageMetadataTimestamp
-
- - - -
- - - -
- - - -
-
-
-
-
- -  -
General settings
-
-
-
Port
- - -
-
-
Default TTS Service
- -
-
-
2nd TTS Service
- -
-
-
TTS Output Device
- - -
-
-
TTS Volume
-
- -
-
- - -
-
-
- -
- -  - - -
Enable STT
-
-
-
STT Input Device
- -
-
-
Voice Language
- -
-
- -
- - - - -
Enable Language detection
-
-
-
Language detection service
- -
-
-
Translate incoming chat messages to
- - - -
-
-
Default TTS language
- -
-
-
2nd TTS language
- -
-
- -
- -  - - -
Enable internal TTS
-
-
-
Default Internal Voice
- -
-
-
Test default Internal Voice
- -
- - -
-
-
-
2nd Internal Voice
- -
-
-
Test 2nd Internal Voice
- -
- - -
-
-
-
- -  - - -
Enable notification sounds
-
-
-
Notification Volume
-
- -
-
- - -
-
-
-
Notification Sound
- -
- - -
-
-
- -
- -  - - -
Enable Twitch
-
-
-
Channel Name
- - -
-
-
Oauth Token
- - - -
-
-
Test credentials
- - -
-
- -
- - - - -
Enable Modules
-
-
-
Use Vtuber
- - - - -
-
-
Use ChatBubble
- - - - -
-
- -
- - - - -
Enable Amazon services
-
-
-
Access Key
- - -
-
-
Access Secret
- - -
-
-
Test credentials
- - -
-
-
Characters used
- - -
-
-
Default Amazon Voice
- -
-
-
Test Default Amazon Voice
- -
- - -
-
-
-
2nd Amazon Voice
- -
-
-
Test 2nd Amazon Voice
- -
- - -
-
-
- -
- - - - -
Enable Google services
-
-
-
API Key
- - -
-
-
Test credentials
- - -
-
-
Characters used
- - -
-
-
Default Google Voice
- -
-
-
Test default Google Voice
- -
- - -
-
-
-
2nd Google Voice
- -
-
-
Test 2nd Google Voice
- -
- - -
-
-
-
-
-
-
- - - -
-
- -
- - - - - -
-
- - -
-
-
- - -
- -
-
-
-
-
-
-
-
- -
- - - - -
Enable Custom Theme
-
-
- -
Main Color 1
-
-
- -
Main Color 2
-
-
- -
Main Color 3
-
-
- -
Main Color 4
-
-
- -
Top Bar
-
-
- -
Mid Section
-
-
- -
Chat Bubble Background
-
-
- -
Chat Bubble Header
-
-
- -
Chat Bubble Message
-
-
-
-
- -
-
- - - -
-
-
- -  - -
-
-

Moderators

-
    -

    Viewers

    -
      -
      -
      -
        -
      • a
      • -
      • b
      • -
      • c
      • -
      -
      -
      -
        -
      • 1
      • -
      • 2
      • -
      • 3
      • -
      -
      -
      -
      -
      -
      - -
      -
      - -
      - - - + + + + + + + + + + + + + + + + + + +
      + +
      +
      +
      + +  +
      +
      + + +
      +
      +
      + +
      +
      + +
      +
      + +
      +
      +
      +
      + +
      + + + +
      + +
      +
      +
      + +
      +
      + + + +
      + +
      + + + + + + + + + + +
      LevelMessageMetadataTimestamp
      +
      + + + +
      + + + +
      + + + +
      +
      +
      +
      +
      + +  +
      General settings
      +
      +
      +
      Port
      + + +
      +
      +
      Zoom level %
      + + +
      +
      +
      Open Settings file
      + + +
      +
      + +
      + +  + + +
      Enable TTS
      +
      + + +
      +
      TTS Output Device
      + + +
      +
      +
      TTS Volume
      +
      + +
      +
      + + +
      +
      +
      +
      Default Internal Voice
      + +
      +
      +
      Test default Internal Voice
      + +
      + + +
      +
      +
      +
      2nd Internal Voice
      + + +
      +
      +
      Test 2nd Internal Voice
      + +
      + + +
      +
      +
      + +
      + +  + + +
      Enable STT
      +
      +
      +
      STT Input Device
      + +
      +
      +
      Voice Language model
      + + +
      +
      + +
      + + + + +
      Enable Language detection
      +
      +
      +
      Default TTS service language
      + + +
      +
      +
      2nd TTS service language
      + + +
      +
      +
      Translate chat messages to
      + +
      +
      +
      Broadcast translation to chat
      + + +
      +
      +
      Output translation to TTS
      + + + +
      +
      +
      Send translated messages
      + + + +
      +
      + +
      + +  + + +
      Enable notification sounds
      +
      +
      +
      Notification sounds Output Device
      + + +
      +
      +
      Notification Volume
      +
      + +
      +
      + + +
      +
      +
      +
      Notification Sound
      + +
      + + +
      +
      +
      + +
      + +  + + +
      Enable Twitch
      +
      +
      +
      Oauth Token
      + + + +
      +
      +
      Channel Name
      + + +
      +
      +
      Test credentials
      + + +
      +
      +
      Get BetterTTV emotes
      + + +
      +
      + +
      + + + + +
      Enable Modules
      +
      + +
      +
      Use Vtuber
      + + + + +
      +
      +
      Use ChatBubble
      + + + + +
      + +
      + + + + +
      +
      +
      +
      + + + +
      +
      + +
      +
      + + +
      +
      +
      In
      + +
      +
      +
      +
      Out
      + +
      +
      + + + + + +
      +
      + + +
      +
      +
      + + +
      + +
      +
      +
      +
      +
      +
      +
      +
      + +
      + + + + +
      Enable Custom Theme
      +
      +
      + +
      Main Color 1
      +
      +
      + +
      Main Color 2
      +
      +
      + +
      Main Color 3
      +
      +
      + +
      Main Color 4
      +
      +
      + +
      Top Bar
      +
      +
      + +
      Mid Section
      +
      +
      + +
      Chat Bubble Background
      +
      +
      + +
      Chat Bubble Header
      +
      +
      + +
      Chat Bubble Message
      +
      +
      +
      +
      + +
      +
      + + + +
      +
      +
      + +  + +
      +
      +

      Moderators

      +
        +

        Viewers

        +
          +
          +
          +
            +
          • a
          • +
          • b
          • +
          • c
          • +
          +
          +
          +
            +
          • 1
          • +
          • 2
          • +
          • 3
          • +
          +
          +
          +
          +
          +
          + +
          +
          + +
          + + + + diff --git a/src/js/amazon.js b/src/js/amazon.js index fd2de7a..0ec9b52 100644 --- a/src/js/amazon.js +++ b/src/js/amazon.js @@ -1,118 +1,118 @@ +/* global settings, callback, addVoiceService, amazonVoices, */ + const https = require('https'); const querystring = require('querystring'); const aws4 = require('aws4'); function getAmazonVoices() { - if (!settings.AMAZON.USE_AMAZON) { - callback(); - return; - } + if (!settings.AMAZON.USE_AMAZON) { + callback(); + return; + } - addVoiceService('Amazon'); + addVoiceService('Amazon'); - let primaryVoice = document.querySelector('#primaryAmazonVoice'); - let secondaryVoice = document.querySelector('#secondaryAmazonVoice'); + const primaryVoice = document.querySelector('#primaryAmazonVoice'); + const secondaryVoice = document.querySelector('#secondaryAmazonVoice'); - function setVoicesinSelect(voiceSelect) { - const voices = Object.values(amazonVoices); - voices.forEach((voice) => { - const option = document.createElement('option'); - option.classList.add('option'); + function setVoicesinSelect(voiceSelect) { + const voices = Object.values(amazonVoices); + voices.forEach(voice => { + const option = document.createElement('option'); + option.classList.add('option'); - option.value = voice; - option.innerHTML = voice; + option.value = voice; + option.innerHTML = voice; - voiceSelect.appendChild(option); - }); - } - setVoicesinSelect(primaryVoice); - primaryVoice.value = settings.AMAZON.PRIMARY_VOICE; - setVoicesinSelect(secondaryVoice); - secondaryVoice.value = settings.AMAZON.SECONDARY_VOICE; + voiceSelect.appendChild(option); + }); + } + setVoicesinSelect(primaryVoice); + primaryVoice.value = settings.AMAZON.PRIMARY_VOICE; + setVoicesinSelect(secondaryVoice); + secondaryVoice.value = settings.AMAZON.SECONDARY_VOICE; } if (settings.AMAZON.USE_AMAZON) { - getAmazonVoices(); + getAmazonVoices(); } class PollyTTS { - constructor() {} + textToSpeech(options, callback) { + if (!options) { + return callback(new Error('Options are missing')); + } - textToSpeech(options, callback) { - if (!options) { - return callback(new Error('Options are missing')); + const qs = { + Text: options.text, + TextType: options.textType || 'text', + VoiceId: options.voiceId || 'Mia', + SampleRate: options.sampleRate || 22050, + OutputFormat: options.outputFormat || 'mp3', + Engine: options.engine || 'neural' + }; + + const opts = { + service: 'polly', + region: options.region || 'us-east-1', + path: `/v1/speech?${querystring.stringify(qs)}`, + signQuery: true + }; + + // you can also pass AWS credentials in explicitly (otherwise taken from process.env) + aws4.sign(opts, this.credentials); + https + .get(opts, res => { + if (res.statusCode !== 200) { + return callback(new Error(`Request Failed. Status Code: ${res.statusCode}`)); + } + callback(null, res); + return true; + }) + .on('error', e => { + callback(e); + }); + + return null; + } + + describeVoices(callback, credentials) { + this.credentials = credentials; + const qs = { + Engine: 'neural' + }; + + const opts = { + service: 'polly', + region: 'us-east-1', + path: `/v1/voices?${querystring.stringify(qs)}`, + signQuery: true + }; + + // you can also pass AWS credentials in explicitly (otherwise taken from process.env) + aws4.sign(opts, this.credentials); + https + .get(opts, res => { + if (res.statusCode !== 200) { + return callback(new Error(`Request Failed. Status Code: ${res.statusCode}`)); } - const qs = { - Text: options.text, - TextType: options.textType || 'text', - VoiceId: options.voiceId || 'Mia', - SampleRate: options.sampleRate || 22050, - OutputFormat: options.outputFormat || 'mp3', - Engine: options.engine || 'neural', - }; + let body = ''; + res.on('readable', () => { + body += res.read(); + }); + res.on('end', () => { + callback(null, body); + }); - const opts = { - service: 'polly', - region: options.region || 'us-east-1', - path: `/v1/speech?${querystring.stringify(qs)}`, - signQuery: true, - }; + return undefined; + }) + .on('error', e => { + callback(e); + }); - // you can also pass AWS credentials in explicitly (otherwise taken from process.env) - aws4.sign(opts, this.credentials); - https - .get(opts, (res) => { - if (res.statusCode !== 200) { - return callback(new Error(`Request Failed. Status Code: ${res.statusCode}`)); - } - callback(null, res); - return true; - }) - .on('error', (e) => { - callback(e); - }); - - return null; - } - - describeVoices(callback, credentials) { - this.credentials = credentials; - const qs = { - Engine: 'neural', - }; - - const opts = { - service: 'polly', - region: 'us-east-1', - path: `/v1/voices?${querystring.stringify(qs)}`, - signQuery: true, - }; - - // you can also pass AWS credentials in explicitly (otherwise taken from process.env) - aws4.sign(opts, this.credentials); - https - .get(opts, (res) => { - if (res.statusCode !== 200) { - return callback(new Error(`Request Failed. Status Code: ${res.statusCode}`)); - } - - let body = ''; - res.on('readable', () => { - body += res.read(); - }); - res.on('end', () => { - callback(null, body); - }); - - return undefined; - }) - .on('error', (e) => { - callback(e); - }); - - return null; - } + return null; + } } const pollyTTS = new PollyTTS(); diff --git a/src/js/auth.js b/src/js/auth.js index ec2d6a8..7777be0 100644 --- a/src/js/auth.js +++ b/src/js/auth.js @@ -1,103 +1,81 @@ +/* global settings,twitch, fs, settingsPath, ini, shell, options, axios */ + const twitchAuthentication = () => - new Promise((resolve) => { - const http = require('http'); - const redirectUri = 'http://localhost:1989/auth'; - const scopes = ['chat:edit', 'chat:read']; + new Promise(resolve => { + const http = require('http'); + const redirectUri = 'http://localhost:1989/auth'; + const scopes = ['chat:edit', 'chat:read', 'user:read:follows', 'user:read:subscriptions']; - const express = require('express'); - let tempAuthServer = express(); - const port = 1989; + const express = require('express'); + const tempAuthServer = express(); + const port = 1989; - const { parse: parseQueryString } = require('querystring'); + const { parse: parseQueryString } = require('querystring'); - tempAuthServer.use(function (req, res, next) { - if (req.url !== '/auth') { - let token = parseQueryString(req.query.auth); - settings.TWITCH.OAUTH_TOKEN = token['#access_token']; - fs.writeFileSync(settingsPath, ini.stringify(settings)); + tempAuthServer.use(function (req, res, next) { + if (req.url !== '/auth') { + const token = parseQueryString(req.query.auth); + settings.TWITCH.OAUTH_TOKEN = token['#access_token']; + fs.writeFileSync(settingsPath, ini.stringify(settings)); - resolve(token['#access_token']); - stopServer(); - } - next(); - }); - - function stopServer() { - tempAuthServer.close(); - } - - const htmlString = ` - - - - Authentication - - -

          Authentication successful! You can close this window now.

          -
          - -
          - - - - `; - - tempAuthServer.get('/auth', (req, res) => { - res.send(htmlString); - }); - - tempAuthServer.post('/auth', (req, res) => { - res.render('authentication', { name: req.body.name }); - }); - - const server = http.createServer(tempAuthServer); - - server.listen(port, () => { - const authURL = `https://id.twitch.tv/oauth2/authorize?client_id=${settings.TWITCH.CLIENT_ID}&redirect_uri=${encodeURIComponent( - redirectUri, - )}&response_type=token&scope=${scopes.join(' ')}`; - shell.openExternal(authURL); - }); - - function stopServer() { - server.close(() => {}); - } + resolve(token['#access_token']); + stopServer(); + } + next(); }); -function getTwitchUserId() { - // Get user Logo with access token - options = { - method: 'GET', - url: `https://api.twitch.tv/helix/users`, - headers: { 'Client-ID': settings.TWITCH.CLIENT_ID, Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}` }, - }; + const htmlString = ` + + + + Authentication + + +

          Authentication successful! You can close this window now.

          +
          + +
          + + + + `; + + tempAuthServer.get('/auth', (req, res) => { + res.send(htmlString); + }); + + tempAuthServer.post('/auth', (req, res) => { + res.render('authentication', { name: req.body.name }); + }); + + const server = http.createServer(tempAuthServer); + + server.listen(port, () => { + const authURL = `https://id.twitch.tv/oauth2/authorize?client_id=${settings.TWITCH.CLIENT_ID}&redirect_uri=${encodeURIComponent( + redirectUri + )}&response_type=token&scope=${scopes.join(' ')}`; + shell.openExternal(authURL); + }); + + function stopServer() { + server.close(() => {}); + } + }); - axios - .request(options) - .then((responseLogoUrl) => { - console.log(responseLogoUrl.data.data[0]); - settings.TWITCH.USERNAME = responseLogoUrl.data.data[0].display_name; - settings.TWITCH.USER_LOGO_URL = responseLogoUrl.data.data[0].profile_image_url; - settings.TWITCH.USER_ID = responseLogoUrl.data.data[0].id; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - }) - .catch((error) => { - console.error(error); - }); -} function getTwitchOauthToken() { - return twitchAuthentication().then((res) => { - getTwitchUserId(); - return res; - }); + return twitchAuthentication().then(res => { + twitch.getTwitchUserId(); + // twitch.getTwitchChannelId(); + return res; + }); } module.exports = { getTwitchOauthToken }; diff --git a/src/js/backend.js b/src/js/backend.js index e576585..b6c2600 100644 --- a/src/js/backend.js +++ b/src/js/backend.js @@ -1,167 +1,420 @@ +/* global settings, resourcesPath, sound, twitch, getLanguageProperties, addSingleTooltip, showChatMessage, languageObject, addVoiceService, internalVoices, ttsRequestCount, main, path, pythonPath, settingsPath, ipcRenderer */ + const spawn = require('child_process').spawn; -var kill = require('kill-process-by-name'); +const kill = require('kill-process-by-name'); let python; async function getInstalledVoices() { - if (!settings.TTS.USE_TTS) { - return; - } - addVoiceService('Internal'); + if (!settings.TTS.USE_TTS) { + return; + } + addVoiceService('Internal'); - try { - const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/voices`, { method: 'GET' }); - if (response.ok) { - const responseData = await response.json(); - console.log('Response:', responseData); - internalVoices = responseData; + try { + const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/voices`, { method: 'GET' }); + if (response.ok) { + const responseData = await response.json(); + console.log('Voices:', responseData); + internalVoices = responseData; + } else { + console.error('Failed to send termination signal to Flask server.'); + } + } catch (error) { + console.error('Error sending termination signal:', error); + } + + const primaryVoice = document.querySelector('#primaryVoice'); + const secondaryVoice = document.querySelector('#secondaryVoice'); + + function setVoicesinSelect(voiceSelect) { + const voices = Object.values(internalVoices.voices); + voices.forEach(voice => { + const option = document.createElement('option'); + option.classList.add('option'); + + option.value = voice; + option.innerHTML = voice; + + voiceSelect.appendChild(option); + }); + } + setVoicesinSelect(primaryVoice); + primaryVoice.value = settings.TTS.PRIMARY_VOICE; + setVoicesinSelect(secondaryVoice); + secondaryVoice.value = settings.TTS.SECONDARY_VOICE; +} + +// TODO: refactor +function setTranslatedUserMessage(message) { + const userMessage = document.getElementById(message.messageId); + const messageBox = userMessage.getElementsByClassName('msg-box')[0]; + + const languageElement = document.createElement('span'); + languageElement.classList = `fi fi-${message.language.selectedLanguage.ISO3166} fis flag-icon user-flag`; + languageElement.setAttribute('tip', message.language.selectedLanguage.name); + userMessage.appendChild(languageElement); + addSingleTooltip(languageElement); + + const translationHeader = document.createElement('div'); + translationHeader.className = 'translation-header user'; + translationHeader.innerText = 'Translation'; + messageBox.appendChild(translationHeader); + + const languageElement2 = document.createElement('span'); + languageElement2.classList = `fi fi-${message.language.detectedLanguage.ISO3166} fis flag-icon user`; + languageElement2.setAttribute('tip', message.language.detectedLanguage.name); + addSingleTooltip(languageElement2); + messageBox.appendChild(languageElement2); + + const translationMessage = document.createElement('div'); + translationMessage.className = 'translation-message user'; + translationMessage.innerText = message.translation; + messageBox.appendChild(translationMessage); +} + +function setTranslatedMessage(message) { + // this determines if it is a message that is send by a user + const languageBox = document.getElementById(message.messageId).getElementsByClassName('language-icon flag-icon')[0]; + if (!languageBox) { + twitch.sendMessage( + `[${message.language.detectedLanguage.name} ${message.language.detectedLanguage.ISO639} > ${message.language.selectedLanguage.name} ${message.language.selectedLanguage.ISO639}] @${message.username}: ${message.translation}` + ); + return setTranslatedUserMessage(message); + } + + if (message.language.selectedLanguage.ISO639 !== message.language.detectedLanguage.ISO639) { + const messageBox = document.getElementById(message.messageId).getElementsByClassName('msg-box')[0]; + + languageBox.classList = `fi fi-${message.language.detectedLanguage.ISO3166} fis language-icon flag-icon`; + languageBox.setAttribute('tip', message.language.detectedLanguage.name); + + const translationHeader = document.createElement('div'); + translationHeader.className = 'translation-header'; + translationHeader.innerText = 'Translation'; + messageBox.appendChild(translationHeader); + + const translationIcon = document.createElement('div'); + translationIcon.className = 'translation-icon'; + const languageElement = document.createElement('span'); + languageElement.classList = `fi fi-${message.language.selectedLanguage.ISO3166} fis flag-icon`; + languageElement.setAttribute('tip', message.language.selectedLanguage.name); + addSingleTooltip(languageElement); + translationIcon.appendChild(languageElement); + messageBox.appendChild(translationIcon); + + const translationMessage = document.createElement('div'); + translationMessage.className = 'translation-message'; + translationMessage.innerText = message.translation; + messageBox.appendChild(translationMessage); + showChatMessage(); + } + + if (settings.LANGUAGE.OUTPUT_TO_TTS) { + sound.playVoice({ + originalMessage: message.originalMessage, + filteredMessage: message.translation, + logoUrl: message.logoUrl, + username: message.username, + formattedMessage: message.formattedMessage, + language: message.language + }); + } + return message.language.detectedLanguage; +} + +async function getTranslatedMessage(message) { + const requestOptions = { + method: 'POST', // HTTP method + headers: { + 'Content-Type': 'application/json; charset="utf-8"' // Specify the content type + }, + body: JSON.stringify({ + message: message.message, + language: message.language.detectedLanguage.IETF + }) // Convert the data to JSON and include it in the request body + }; + + try { + const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/translate`, requestOptions); + const responseData = await response.json(); + if (response.ok) { + console.log('Translated message:', responseData); + + if (settings.LANGUAGE.BROADCAST_TRANSLATION) { + twitch.sendMessage( + `[${message.language.detectedLanguage.name} ${message.language.detectedLanguage.ISO639} > ${message.language.selectedLanguage.name} ${message.language.selectedLanguage.ISO639}] @${message.username}: ${responseData.translation}` + ); + } + setTranslatedMessage({ + originalMessage: message.message, + translation: responseData.translation, + messageId: message.messageId, + language: message.language, + formattedMessage: message.formattedMessage, + username: message.username, + logoUrl: message.logoUrl + }); + return message.language.detectedLanguage; + } else { + console.error(responseData); + if (responseData.code === 500) { + if (message.remainingDetectedLanguages.length > 0) { + message.language.detectedLanguage = getLanguageProperties(message.remainingDetectedLanguages[0]); + message.remainingDetectedLanguages.shift(); + return getTranslatedMessage(message); } else { - console.error('Failed to send termination signal to Flask server.'); + message.message = 'Error, Could not translate message'; + message.language.detectedLanguage = getLanguageProperties('en-GB'); + return getTranslatedMessage(message); } - } catch (error) { - console.error('Error sending termination signal:', error); - } - - let primaryVoice = document.querySelector('#primaryVoice'); - let secondaryVoice = document.querySelector('#secondaryVoice'); - - function setVoicesinSelect(voiceSelect) { - const voices = Object.values(internalVoices.voices); - voices.forEach((voice) => { - const option = document.createElement('option'); - option.classList.add('option'); - - option.value = voice; - option.innerHTML = voice; - - voiceSelect.appendChild(option); + } + if (responseData.code === 429) { + message.language.detectedLanguage = getLanguageProperties('en-GB'); + setTranslatedMessage({ + originalMessage: message.message, + translation: 'Rate limit exceeded, please change translation service.', + messageId: message.messageId, + language: message.language, + formattedMessage: message.formattedMessage, + username: message.username, + logoUrl: message.logoUrl }); + } } - setVoicesinSelect(primaryVoice); - primaryVoice.value = settings.TTS.PRIMARY_VOICE; - setVoicesinSelect(secondaryVoice); - secondaryVoice.value = settings.TTS.SECONDARY_VOICE; + } catch (error) { + console.error('Error sending termination signal:', error); + message.language.detectedLanguage = getLanguageProperties('en-GB'); + setTranslatedMessage({ + originalMessage: message.message, + translation: 'Error, could not translate message.', + messageId: message.messageId, + language: message.language, + formattedMessage: message.formattedMessage, + username: message.username, + logoUrl: message.logoUrl + }); + } +} + +async function filterLanguage(message) { + const selectedPrimaryLanguage = getLanguageProperties(settings.LANGUAGE.TRANSLATE_TO); + const selectedPrimaryLanguageIndex = + message.languages.indexOf(selectedPrimaryLanguage.ISO639) === -1 ? 99 : message.languages.indexOf(selectedPrimaryLanguage.ISO639); + + const selectedSecondaryLanguage = getLanguageProperties(settings.TTS.SECONDARY_TTS_LANGUAGE); + const selectedSecondaryLanguageIndex = + message.languages.indexOf(selectedSecondaryLanguage.ISO639) === -1 ? 99 : message.languages.indexOf(selectedSecondaryLanguage.ISO639); + + let detectedLanguage = ''; + const remainingDetectedLanguages = []; + const detectedLanguages = message.languages.slice(); + + for (const [index, language] of detectedLanguages.entries()) { + detectedLanguage = getLanguageProperties(language); + + if (detectedLanguage !== 'error') { + detectedLanguages.splice(index, 1); + break; + } + } + + for (const [index, language] of detectedLanguages.entries()) { + const remainderLanguage = getLanguageProperties(language); + if (remainderLanguage !== 'error') { + remainingDetectedLanguages.push(remainderLanguage.IETF); + } + } + + const language = selectedPrimaryLanguageIndex < selectedSecondaryLanguageIndex ? selectedPrimaryLanguage : detectedLanguage; + if (settings.LANGUAGE.TRANSLATE_TO !== 'none' && selectedPrimaryLanguage.ISO639 !== detectedLanguage.ISO639) { + getTranslatedMessage({ + message: message.message, + messageId: message.messageId, + remainingDetectedLanguages, + language: { + selectedLanguage: selectedPrimaryLanguage, + detectedLanguage: detectedLanguage + }, + username: message.username, + formattedMessage: message.formattedMessage, + logoUrl: message.logoUrl + }); + } else { + setTranslatedMessage({ + originalMessage: message.message, + translation: message.message, + messageId: message.messageId, + language: { + selectedLanguage: selectedPrimaryLanguage, + detectedLanguage: selectedPrimaryLanguage + }, + formattedMessage: message.formattedMessage, + username: message.username, + logoUrl: message.logoUrl + }); + } + + return language; +} + +async function getDetectedLanguage(message) { + if (!settings.LANGUAGE.USE_DETECTION) { + return; + } + + const requestOptions = { + method: 'POST', // HTTP method + headers: { + 'Content-Type': 'application/json' // Specify the content type + }, + body: JSON.stringify({ message: message.message }) // Convert the data to JSON and include it in the request body + }; + + try { + const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/detect`, requestOptions); + if (response.ok) { + const responseData = await response.json(); + + console.log('Detected Languages:', responseData); + return await filterLanguage({ + languages: responseData.languages, + message: message.message, + messageId: message.messageId, + username: message.username, + formattedMessage: message.formattedMessage + }); + } else { + console.error('Failed to send termination signal to Flask server.'); + } + } catch (error) { + console.error('Error sending termination signal:', error); + } } async function getBackendServerStatus() { - try { - const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/status`, { method: 'GET' }); - if (response.ok) { - const responseData = await response.json(); - console.log('Response:', responseData); - } else { - console.error('Failed to send termination signal to Flask server.'); - } - } catch (error) { - console.error('Error sending termination signal:', error); + try { + const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/status`, { method: 'GET' }); + if (response.ok) { + const responseData = await response.json(); + console.log('Status:', responseData); + } else { + console.error('Failed to send termination signal to Flask server.'); } + } catch (error) { + console.error('Error sending termination signal:', error); + } } function startSTT() { - const eventSource = new EventSource('http://127.0.0.1:9000/stream'); + const eventSource = new EventSource('http://127.0.0.1:9000/stream'); - eventSource.addEventListener('message', (event) => { - const result = event.data; - console.log(result); // Log the received data - }); + eventSource.addEventListener('message', event => { + const result = event.data; + console.log(result); // Log the received data + }); - eventSource.addEventListener('error', (event) => { - console.error('EventSource failed:', event); + eventSource.addEventListener('error', event => { + console.error('EventSource failed:', event); - eventSource.close(); - }); + eventSource.close(); + }); - window.addEventListener('beforeunload', () => { - eventSource.close(); - }); + window.addEventListener('beforeunload', () => { + eventSource.close(); + }); } async function getInternalTTSAudio(requestData) { - ttsRequestCount++; - requestData.count = ttsRequestCount; - const requestOptions = { - method: 'POST', // HTTP method - headers: { - 'Content-Type': 'application/json', // Specify the content type - }, - body: JSON.stringify(requestData), // Convert the data to JSON and include it in the request body - }; + ttsRequestCount++; + requestData.count = ttsRequestCount; + const requestOptions = { + method: 'POST', // HTTP method + headers: { + 'Content-Type': 'application/json' // Specify the content type + }, + body: JSON.stringify(requestData) // Convert the data to JSON and include it in the request body + }; - try { - const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/audio`, requestOptions); - if (response.ok) { - const responseData = await response.json(); - console.log('Response:', responseData); - return ttsRequestCount; - } else { - console.error('Failed to send termination signal to Flask server.'); - } - } catch (error) { - console.error('Error sending termination signal:', error); + try { + const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/audio`, requestOptions); + if (response.ok) { + const responseData = await response.json(); + console.log('Audio:', responseData); + return ttsRequestCount; + } else { + console.error('Failed to send termination signal to Flask server.'); } + } catch (error) { + console.error('Error sending termination signal:', error); + } } const createBackendServer = () => - new Promise((resolve) => { - if (main.isPackaged) { - python = spawn(path.join(pythonPath, './loquendoBot_backend.exe'), [settingsPath, 'prod']); - } else { - python = spawn('python', ['-u', path.join(pythonPath, './loquendoBot_backend.py'), settingsPath, 'dev']); - } - // Capture the stdout of the Python process - python.stdout.on('data', (data) => { - console.info(`${data}`); - }); - - // Capture the stderr of the Python process - python.stderr.on('data', (data) => { - console.error(`${data}`); - resolve('finished'); // cannot get it to resolve with stdout - }); - - // Listen for the Python process to exit - python.on('close', (code) => { - console.log(`Python process exited with code ${code}`); - }); - - if (typeof python.pid !== 'number') { - console.log('failed'); - } else { - console.log(`Spawned subprocess correctly!, PID = ${python.pid}`); - } + new Promise(resolve => { + if (main.isPackaged) { + python = spawn(path.join(pythonPath, './loquendoBot_backend.exe'), [settingsPath, 'prod']); + } else { + python = spawn('python', ['-u', path.join(resourcesPath, '../backend/loquendoBot_backend.py'), settingsPath, 'dev']); + } + // Capture the stdout of the Python process + python.stdout.on('data', data => { + console.info(`${data}`); }); -async function initiateBackend() { - try { - createBackendServer().then(() => { - getBackendServerStatus(); - getInstalledVoices(); - if (settings.STT.USE_STT) { - startSTT(); - } - }); - } catch (error) { - console.error('Error during backend initialization:', error); + // Capture the stderr of the Python process + python.stderr.on('data', data => { + // console.error(`${data}`); + if (data.toString().startsWith('INFO:waitress:Serving on')) { + resolve('finished'); + } else { + console.error(`${data}`); + } + }); + + // Listen for the Python process to exit + python.on('close', code => { + console.log(`Python process exited with code ${code}`); + }); + + if (typeof python.pid !== 'number') { + console.log('failed'); + } else { + // console.log(`Spawned subprocess correctly!, PID = ${python.pid}`); } + }); + +async function initiateBackend() { + try { + createBackendServer().then(() => { + getBackendServerStatus(); + getInstalledVoices(); + if (settings.STT.USE_STT && !settings.STT.LANGUAGE === '') { + startSTT(); + } + }); + } catch (error) { + console.error('Error during backend initialization:', error); + } } initiateBackend(); -//TODO: convert to restartServer function +// TODO: convert to restartServer function ipcRenderer.on('quit-event', async () => { - try { - const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/terminate`, { method: 'GET' }); - if (response.ok) { - const responseData = await response.json(); - console.log('Response:', responseData); - kill('loquendoBot_backend'); - } else { - console.error('Failed to send termination signal to Flask server.'); - kill('loquendoBot_backend'); - } - } catch (error) { - console.error('Error sending termination signal:', error); - kill('loquendoBot_backend'); + try { + const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/terminate`, { method: 'GET' }); + if (response.ok) { + const responseData = await response.json(); + console.log('Response:', responseData); + kill('loquendoBot_backend'); + } else { + console.error('Failed to send termination signal to Flask server.'); + kill('loquendoBot_backend'); } + } catch (error) { + console.error('Error sending termination signal:', error); + kill('loquendoBot_backend'); + } }); -module.exports = { getInternalTTSAudio }; +module.exports = { getInternalTTSAudio, getDetectedLanguage, getTranslatedMessage }; diff --git a/src/js/chat.js b/src/js/chat.js index 6ce1af7..827a014 100644 --- a/src/js/chat.js +++ b/src/js/chat.js @@ -1,55 +1,94 @@ -function getResponse() { - const userText = document.querySelector('#textInput').value; +/* global messageTemplates,getLanguageProperties, backend, messageId emojiPicker, settings, getPostTime, showChatMessage, twitch */ - // If nothing is written don't do anything - if (userText === '') { - return; - } +async function getResponse() { + const userText = document.querySelector('#textInput').value; - // Create chat message from received data - const article = document.createElement('article'); - article.className = 'msg-container-user'; + // If nothing is written don't do anything + if (userText === '') { + return; + } - article.innerHTML = messageTemplates.userTemplate; + messageId++; - const userImg = article.querySelector('.icon-container-user > .user-img-user'); - if (userImg) { - userImg.src = settings.TWITCH.USER_LOGO_URL; - } + // Create chat message from received data + const article = document.createElement('article'); + article.setAttribute('id', messageId); + article.className = 'msg-container user'; - const postTime = article.querySelector('.post-time-user'); + article.innerHTML = messageTemplates.userTemplate; - const iconContainer = article.querySelector('.icon-container-user'); - iconContainer.appendChild(postTime); + const userImg = article.querySelector('.user-img'); + if (userImg) { + userImg.src = settings.TWITCH.USER_LOGO_URL; + } - if (postTime) { - postTime.innerText = getPostTime(); - } + const postTime = article.querySelector('.post-time'); - const msg = article.querySelector('.msg-box-user'); - if (msg) { - msg.innerText = userText; - } + if (postTime) { + postTime.innerText = getPostTime(); + } - // Appends the message to the main chat box (shows the message) - showChatMessage(article, true); + article.appendChild(postTime); - twitch.sendMessage(userText); + const msg = article.querySelector('.msg-box'); + if (msg) { + await replaceChatMessageWithCustomEmojis(userText).then(data => { + msg.innerHTML = data; - // Empty input box after sending message - document.body.querySelector('#textInput').value = ''; + // Appends the message to the main chat box (shows the message) + showChatMessage(article); + + twitch.sendMessage(userText); + + if (settings.LANGUAGE.SEND_TRANSLATION) { + const selectedLanguage = getLanguageProperties(settings.LANGUAGE.SEND_TRANSLATION_IN); + const detectedLanguage = getLanguageProperties(settings.LANGUAGE.SEND_TRANSLATION_OUT); + backend.getTranslatedMessage({ + message: data, + messageId: messageId, + remainingDetectedLanguages: [], + language: { + selectedLanguage, + detectedLanguage + }, + formattedMessage: data, + username: 'You', + logoUrl: settings.TWITCH.USER_LOGO_URL + }); + } + + // Empty input box after sending message + document.body.querySelector('#textInput').value = ''; + }); + } } +const replaceChatMessageWithCustomEmojis = message => + new Promise(resolve => { + const words = message.split(' '); + words.forEach(async word => { + if (word !== '') { + await emojiPicker.database.getEmojiByUnicodeOrName(word).then(data => { + if (data && data.name === word) { + const url = ``; + message = message.replace(word, url); + } + }); + resolve(message); + } + }); + }); + // Function that will execute when you press 'enter' in the message box -document.body.querySelector('#textInput').addEventListener('keydown', (e) => { - if (e.which === 13) { - getResponse(); - } +document.body.querySelector('#textInput').addEventListener('keydown', e => { + if (e.which === 13) { + getResponse(); + } }); // Function that will execute when you click the 'send' button document.body.querySelector('#SendButton').addEventListener('click', () => { - getResponse(); + getResponse(); }); // #endregion @@ -58,21 +97,21 @@ document.body.querySelector('#SendButton').addEventListener('click', () => { // Left panel document.body.querySelector('.circle-left').addEventListener('click', () => { - const menu = document.body.querySelector('.sidepanel-left'); + const menu = document.body.querySelector('.sidepanel-left'); - if (menu.classList.contains('collapse-menu-left')) { - menu.classList.remove('collapse-menu-left'); - } else { - menu.classList.add('collapse-menu-left'); - } + if (menu.classList.contains('collapse-menu-left')) { + menu.classList.remove('collapse-menu-left'); + } else { + menu.classList.add('collapse-menu-left'); + } - const leftCircle = document.body.querySelector('.circle-left'); + const leftCircle = document.body.querySelector('.circle-left'); - if (leftCircle.classList.contains('collapse-circle-left')) { - leftCircle.classList.remove('collapse-circle-left'); - } else { - leftCircle.classList.add('collapse-circle-left'); - } + if (leftCircle.classList.contains('collapse-circle-left')) { + leftCircle.classList.remove('collapse-circle-left'); + } else { + leftCircle.classList.add('collapse-circle-left'); + } }); // #region Show panels @@ -81,27 +120,26 @@ document.body.querySelector('.circle-left').addEventListener('click', () => { // TODO : optimize show panels // Function that shows and hides the option panels. (TTS, Configuration, Commands) const displayPanel = (panelSelectorClass, panelSelectorID, btnSelectorID) => { - const btn = document.querySelector(btnSelectorID); - const panel = document.querySelector(panelSelectorID); - const panels = document.querySelectorAll(panelSelectorClass); + const btn = document.querySelector(btnSelectorID); + const panel = document.querySelector(panelSelectorID); + const panels = document.querySelectorAll(panelSelectorClass); - btn.addEventListener( - 'click', - (event) => { - event.stopPropagation(); - panels.forEach((el) => { - if (el === panel) return; - el.classList.remove('show'); - }); - if (panel.classList.contains('show')) { - } else { - panel.classList.add('show'); - } - }, - { - capture: true, - }, - ); + btn.addEventListener( + 'click', + event => { + event.stopPropagation(); + panels.forEach(el => { + if (el === panel) return; + el.classList.remove('show'); + }); + if (!panel.classList.contains('show')) { + panel.classList.add('show'); + } + }, + { + capture: true + } + ); }; displayPanel('.OptionPanel', '#Configuration', '#btnConfiguration'); @@ -114,27 +152,26 @@ displayPanel('.OptionPanel', '#ChatCreator', '#btnChatCreator'); // #endregion const displayPanelX = (panelSelectorClass, panelSelectorID, btnSelectorID) => { - const btn = document.querySelector(btnSelectorID); - const panel = document.querySelector(panelSelectorID); - const panels = document.querySelectorAll(panelSelectorClass); + const btn = document.querySelector(btnSelectorID); + const panel = document.querySelector(panelSelectorID); + const panels = document.querySelectorAll(panelSelectorClass); - btn.addEventListener( - 'click', - (event) => { - event.stopPropagation(); - panels.forEach((el) => { - if (el === panel) return; - el.classList.remove('item-active'); - }); - if (panel.classList.contains('item-active')) { - } else { - panel.classList.add('item-active'); - } - }, - { - capture: true, - }, - ); + btn.addEventListener( + 'click', + event => { + event.stopPropagation(); + panels.forEach(el => { + if (el === panel) return; + el.classList.remove('item-active'); + }); + if (!panel.classList.contains('item-active')) { + panel.classList.add('item-active'); + } + }, + { + capture: true + } + ); }; displayPanelX('.item', '#btnChat', '#btnChat'); @@ -148,3 +185,7 @@ displayPanelX('.item', '#btnChatCreator', '#btnChatCreator'); // #region Show/Hide Theme Creator // #endregion + +module.exports = { + replaceChatMessageWithCustomEmojis +}; diff --git a/src/js/google.js b/src/js/google.js index 9edfa68..81774e6 100644 --- a/src/js/google.js +++ b/src/js/google.js @@ -1,31 +1,33 @@ +/* global settings, addVoiceService, googleVoices */ + function getGoogleVoices() { - if (!settings.GOOGLE.USE_GOOGLE) { - return; - } + if (!settings.GOOGLE.USE_GOOGLE) { + return; + } - addVoiceService('Google'); + addVoiceService('Google'); - let primaryVoice = document.querySelector('#primaryGoogleVoice'); - let secondaryVoice = document.querySelector('#secondaryGoogleVoice'); + const primaryVoice = document.querySelector('#primaryGoogleVoice'); + const secondaryVoice = document.querySelector('#secondaryGoogleVoice'); - function setVoicesinSelect(voiceSelect) { - const voices = Object.values(googleVoices); - voices.forEach((voice) => { - const option = document.createElement('option'); - option.classList.add('option'); + function setVoicesinSelect(voiceSelect) { + const voices = Object.values(googleVoices); + voices.forEach(voice => { + const option = document.createElement('option'); + option.classList.add('option'); - option.value = voice; - option.innerHTML = voice; + option.value = voice; + option.innerHTML = voice; - voiceSelect.appendChild(option); - }); - } - setVoicesinSelect(primaryVoice); - primaryVoice.value = settings.GOOGLE.PRIMARY_VOICE; - setVoicesinSelect(secondaryVoice); - secondaryVoice.value = settings.GOOGLE.SECONDARY_VOICE; + voiceSelect.appendChild(option); + }); + } + setVoicesinSelect(primaryVoice); + primaryVoice.value = settings.GOOGLE.PRIMARY_VOICE; + setVoicesinSelect(secondaryVoice); + secondaryVoice.value = settings.GOOGLE.SECONDARY_VOICE; } if (settings.GOOGLE.USE_GOOGLE) { - getGoogleVoices(); + getGoogleVoices(); } diff --git a/src/js/languages.js b/src/js/languages.js index 3f7e0fc..8aaed9f 100644 --- a/src/js/languages.js +++ b/src/js/languages.js @@ -1,331 +1,334 @@ // TODO: Enable STT: -// Output STT to TTS? *TTS service selection* (for now, later add the option to choose a specific voice with mega dropdowns) +// Output STT to TTS? *TTS service selection* (for now, later add the option to choose a specific voice with mega dropdowns) // *automatic translation: make an translation.js and add ALL the texts and have it translated if user chooses a language in top bar // *info page with credits, version and more info const languages = { - acehnese: { IETF: 'ace-ID', 'ISO-639': 'ace' }, - afrikaans: { IETF: 'af-ZA', 'ISO-639': 'af' }, - akan: { IETF: 'ak-GH', 'ISO-639': 'ak' }, - albanian: { IETF: 'sq-AL', 'ISO-639': 'sq' }, - amharic: { IETF: 'am-ET', 'ISO-639': 'am' }, - 'antigua and barbuda creole english': { IETF: 'aig-AG', 'ISO-639': 'aig' }, - arabic: { IETF: 'ar-SA', 'ISO-639': 'ar' }, - 'arabic egyptian': { IETF: 'ar-EG', 'ISO-639': 'ar' }, - aragonese: { IETF: 'an-ES', 'ISO-639': 'an' }, - armenian: { IETF: 'hy-AM', 'ISO-639': 'hy' }, - assamese: { IETF: 'as-IN', 'ISO-639': 'as' }, - asturian: { IETF: 'ast-ES', 'ISO-639': 'ast' }, - 'austrian german': { IETF: 'de-AT', 'ISO-639': 'de' }, - awadhi: { IETF: 'awa-IN', 'ISO-639': 'awa' }, - 'ayacucho quechua': { IETF: 'quy-PE', 'ISO-639': 'quy' }, - azerbaijani: { IETF: 'az-AZ', 'ISO-639': 'az' }, - 'bahamas creole english': { IETF: 'bah-BS', 'ISO-639': 'bah' }, - bajan: { IETF: 'bjs-BB', 'ISO-639': 'bjs' }, - balinese: { IETF: 'ban-ID', 'ISO-639': 'ban' }, - 'balkan gipsy': { IETF: 'rm-RO', 'ISO-639': 'rm' }, - bambara: { IETF: 'bm-ML', 'ISO-639': 'bm' }, - banjar: { IETF: 'bjn-ID', 'ISO-639': 'bjn' }, - bashkir: { IETF: 'ba-RU', 'ISO-639': 'ba' }, - basque: { IETF: 'eu-ES', 'ISO-639': 'eu' }, - belarusian: { IETF: 'be-BY', 'ISO-639': 'be' }, - 'belgian french': { IETF: 'fr-BE', 'ISO-639': 'fr' }, - bemba: { IETF: 'bem-ZM', 'ISO-639': 'bem' }, - bengali: { IETF: 'bn-IN', 'ISO-639': 'bn' }, - bhojpuri: { IETF: 'bho-IN', 'ISO-639': 'bho' }, - bihari: { IETF: 'bh-IN', 'ISO-639': 'bh' }, - bislama: { IETF: 'bi-VU', 'ISO-639': 'bi' }, - borana: { IETF: 'gax-KE', 'ISO-639': 'gax' }, - bosnian: { IETF: 'bs-BA', 'ISO-639': 'bs' }, - 'bosnian (cyrillic)': { IETF: 'bs-Cyrl-BA', 'ISO-639': 'bs' }, - breton: { IETF: 'br-FR', 'ISO-639': 'br' }, - buginese: { IETF: 'bug-ID', 'ISO-639': 'bug' }, - bulgarian: { IETF: 'bg-BG', 'ISO-639': 'bg' }, - burmese: { IETF: 'my-MM', 'ISO-639': 'my' }, - catalan: { IETF: 'ca-ES', 'ISO-639': 'ca' }, - 'catalan valencian': { IETF: 'cav-ES', 'ISO-639': 'cav' }, - cebuano: { IETF: 'ceb-PH', 'ISO-639': 'ceb' }, - 'central atlas tamazight': { IETF: 'tzm-MA', 'ISO-639': 'tzm' }, - 'central aymara': { IETF: 'ayr-BO', 'ISO-639': 'ayr' }, - 'central kanuri (latin script)': { IETF: 'knc-NG', 'ISO-639': 'knc' }, - 'chadian arabic': { IETF: 'shu-TD', 'ISO-639': 'shu' }, - chamorro: { IETF: 'ch-GU', 'ISO-639': 'ch' }, - cherokee: { IETF: 'chr-US', 'ISO-639': 'chr' }, - chhattisgarhi: { IETF: 'hne-IN', 'ISO-639': 'hne' }, - 'chinese simplified': { IETF: 'zh-CN', 'ISO-639': 'zh' }, - 'chinese trad. (hong kong)': { IETF: 'zh-HK', 'ISO-639': 'zh' }, - 'chinese traditional': { IETF: 'zh-TW', 'ISO-639': 'zh' }, - 'chinese traditional macau': { IETF: 'zh-MO', 'ISO-639': 'zh' }, - chittagonian: { IETF: 'ctg-BD', 'ISO-639': 'ctg' }, - chokwe: { IETF: 'cjk-AO', 'ISO-639': 'cjk' }, - 'classical greek': { IETF: 'grc-GR', 'ISO-639': 'grc' }, - 'comorian ngazidja': { IETF: 'zdj-KM', 'ISO-639': 'zdj' }, - coptic: { IETF: 'cop-EG', 'ISO-639': 'cop' }, - 'crimean tatar': { IETF: 'crh-RU', 'ISO-639': 'crh' }, - 'crioulo upper guinea': { IETF: 'pov-GW', 'ISO-639': 'pov' }, - croatian: { IETF: 'hr-HR', 'ISO-639': 'hr' }, - czech: { IETF: 'cs-CZ', 'ISO-639': 'cs' }, - danish: { IETF: 'da-DK', 'ISO-639': 'da' }, - dari: { IETF: 'prs-AF', 'ISO-639': 'prs' }, - dimli: { IETF: 'diq-TR', 'ISO-639': 'diq' }, - dutch: { IETF: 'nl-NL', 'ISO-639': 'nl' }, - dyula: { IETF: 'dyu-CI', 'ISO-639': 'dyu' }, - dzongkha: { IETF: 'dz-BT', 'ISO-639': 'dz' }, - 'eastern yiddish': { IETF: 'ydd-US', 'ISO-639': 'ydd' }, - emakhuwa: { IETF: 'vmw-MZ', 'ISO-639': 'vmw' }, - english: { IETF: 'en-GB', 'ISO-639': 'en' }, - 'english australia': { IETF: 'en-AU', 'ISO-639': 'en' }, - 'english canada': { IETF: 'en-CA', 'ISO-639': 'en' }, - 'english india': { IETF: 'en-IN', 'ISO-639': 'en' }, - 'english ireland': { IETF: 'en-IE', 'ISO-639': 'en' }, - 'english new zealand': { IETF: 'en-NZ', 'ISO-639': 'en' }, - 'english singapore': { IETF: 'en-SG', 'ISO-639': 'en' }, - 'english south africa': { IETF: 'en-ZA', 'ISO-639': 'en' }, - 'english us': { IETF: 'en-US', 'ISO-639': 'en' }, - esperanto: { IETF: 'eo-EU', 'ISO-639': 'eo' }, - estonian: { IETF: 'et-EE', 'ISO-639': 'et' }, - ewe: { IETF: 'ee-GH', 'ISO-639': 'ee' }, - fanagalo: { IETF: 'fn-FNG', 'ISO-639': 'fn' }, - faroese: { IETF: 'fo-FO', 'ISO-639': 'fo' }, - fijian: { IETF: 'fj-FJ', 'ISO-639': 'fj' }, - filipino: { IETF: 'fil-PH', 'ISO-639': 'fil' }, - finnish: { IETF: 'fi-FI', 'ISO-639': 'fi' }, - flemish: { IETF: 'nl-BE', 'ISO-639': 'nl' }, - fon: { IETF: 'fon-BJ', 'ISO-639': 'fon' }, - french: { IETF: 'fr-FR', 'ISO-639': 'fr' }, - 'french canada': { IETF: 'fr-CA', 'ISO-639': 'fr' }, - 'french swiss': { IETF: 'fr-CH', 'ISO-639': 'fr' }, - friulian: { IETF: 'fur-IT', 'ISO-639': 'fur' }, - fula: { IETF: 'ff-FUL', 'ISO-639': 'ff' }, - galician: { IETF: 'gl-ES', 'ISO-639': 'gl' }, - gamargu: { IETF: 'mfi-NG', 'ISO-639': 'mfi' }, - garo: { IETF: 'grt-IN', 'ISO-639': 'grt' }, - georgian: { IETF: 'ka-GE', 'ISO-639': 'ka' }, - german: { IETF: 'de-DE', 'ISO-639': 'de' }, - gilbertese: { IETF: 'gil-KI', 'ISO-639': 'gil' }, - glavda: { IETF: 'glw-NG', 'ISO-639': 'glw' }, - greek: { IETF: 'el-GR', 'ISO-639': 'el' }, - 'grenadian creole english': { IETF: 'gcl-GD', 'ISO-639': 'gcl' }, - guarani: { IETF: 'gn-PY', 'ISO-639': 'gn' }, - gujarati: { IETF: 'gu-IN', 'ISO-639': 'gu' }, - 'guyanese creole english': { IETF: 'gyn-GY', 'ISO-639': 'gyn' }, - 'haitian creole french': { IETF: 'ht-HT', 'ISO-639': 'ht' }, - 'halh mongolian': { IETF: 'khk-MN', 'ISO-639': 'khk' }, - hausa: { IETF: 'ha-NE', 'ISO-639': 'ha' }, - hawaiian: { IETF: 'haw-US', 'ISO-639': 'haw' }, - hebrew: { IETF: 'he-IL', 'ISO-639': 'he' }, - higi: { IETF: 'hig-NG', 'ISO-639': 'hig' }, - hiligaynon: { IETF: 'hil-PH', 'ISO-639': 'hil' }, - 'hill mari': { IETF: 'mrj-RU', 'ISO-639': 'mrj' }, - hindi: { IETF: 'hi-IN', 'ISO-639': 'hi' }, - hmong: { IETF: 'hmn-CN', 'ISO-639': 'hmn' }, - hungarian: { IETF: 'hu-HU', 'ISO-639': 'hu' }, - icelandic: { IETF: 'is-IS', 'ISO-639': 'is' }, - 'igbo ibo': { IETF: 'ibo-NG', 'ISO-639': 'ibo' }, - 'igbo ig': { IETF: 'ig-NG', 'ISO-639': 'ig' }, - ilocano: { IETF: 'ilo-PH', 'ISO-639': 'ilo' }, - indonesian: { IETF: 'id-ID', 'ISO-639': 'id' }, - 'inuktitut greenlandic': { IETF: 'kl-GL', 'ISO-639': 'kl' }, - 'irish gaelic': { IETF: 'ga-IE', 'ISO-639': 'ga' }, - italian: { IETF: 'it-IT', 'ISO-639': 'it' }, - 'italian swiss': { IETF: 'it-CH', 'ISO-639': 'it' }, - 'jamaican creole english': { IETF: 'jam-JM', 'ISO-639': 'jam' }, - japanese: { IETF: 'ja-JP', 'ISO-639': 'ja' }, - javanese: { IETF: 'jv-ID', 'ISO-639': 'jv' }, - jingpho: { IETF: 'kac-MM', 'ISO-639': 'kac' }, - "k'iche'": { IETF: 'quc-GT', 'ISO-639': 'quc' }, - 'kabiy�': { IETF: 'kbp-TG', 'ISO-639': 'kbp' }, - kabuverdianu: { IETF: 'kea-CV', 'ISO-639': 'kea' }, - kabylian: { IETF: 'kab-DZ', 'ISO-639': 'kab' }, - kalenjin: { IETF: 'kln-KE', 'ISO-639': 'kln' }, - kamba: { IETF: 'kam-KE', 'ISO-639': 'kam' }, - kannada: { IETF: 'kn-IN', 'ISO-639': 'kn' }, - kanuri: { IETF: 'kr-KAU', 'ISO-639': 'kr' }, - karen: { IETF: 'kar-MM', 'ISO-639': 'kar' }, - 'kashmiri (devanagari script)': { IETF: 'ks-IN', 'ISO-639': 'ks' }, - 'kashmiri (arabic script)': { IETF: 'kas-IN', 'ISO-639': 'kas' }, - kazakh: { IETF: 'kk-KZ', 'ISO-639': 'kk' }, - khasi: { IETF: 'kha-IN', 'ISO-639': 'kha' }, - khmer: { IETF: 'km-KH', 'ISO-639': 'km' }, - 'kikuyu kik': { IETF: 'kik-KE', 'ISO-639': 'kik' }, - 'kikuyu ki': { IETF: 'ki-KE', 'ISO-639': 'ki' }, - kimbundu: { IETF: 'kmb-AO', 'ISO-639': 'kmb' }, - kinyarwanda: { IETF: 'rw-RW', 'ISO-639': 'rw' }, - kirundi: { IETF: 'rn-BI', 'ISO-639': 'rn' }, - kisii: { IETF: 'guz-KE', 'ISO-639': 'guz' }, - kongo: { IETF: 'kg-CG', 'ISO-639': 'kg' }, - konkani: { IETF: 'kok-IN', 'ISO-639': 'kok' }, - korean: { IETF: 'ko-KR', 'ISO-639': 'ko' }, - 'northern kurdish': { IETF: 'kmr-TR', 'ISO-639': 'kmr' }, - 'kurdish sorani': { IETF: 'ckb-IQ', 'ISO-639': 'ckb' }, - kyrgyz: { IETF: 'ky-KG', 'ISO-639': 'ky' }, - lao: { IETF: 'lo-LA', 'ISO-639': 'lo' }, - latgalian: { IETF: 'ltg-LV', 'ISO-639': 'ltg' }, - latin: { IETF: 'la-XN', 'ISO-639': 'la' }, - latvian: { IETF: 'lv-LV', 'ISO-639': 'lv' }, - ligurian: { IETF: 'lij-IT', 'ISO-639': 'lij' }, - limburgish: { IETF: 'li-NL', 'ISO-639': 'li' }, - lingala: { IETF: 'ln-LIN', 'ISO-639': 'ln' }, - lithuanian: { IETF: 'lt-LT', 'ISO-639': 'lt' }, - lombard: { IETF: 'lmo-IT', 'ISO-639': 'lmo' }, - 'luba-kasai': { IETF: 'lua-CD', 'ISO-639': 'lua' }, - luganda: { IETF: 'lg-UG', 'ISO-639': 'lg' }, - luhya: { IETF: 'luy-KE', 'ISO-639': 'luy' }, - luo: { IETF: 'luo-KE', 'ISO-639': 'luo' }, - luxembourgish: { IETF: 'lb-LU', 'ISO-639': 'lb' }, - maa: { IETF: 'mas-KE', 'ISO-639': 'mas' }, - macedonian: { IETF: 'mk-MK', 'ISO-639': 'mk' }, - magahi: { IETF: 'mag-IN', 'ISO-639': 'mag' }, - maithili: { IETF: 'mai-IN', 'ISO-639': 'mai' }, - malagasy: { IETF: 'mg-MG', 'ISO-639': 'mg' }, - malay: { IETF: 'ms-MY', 'ISO-639': 'ms' }, - malayalam: { IETF: 'ml-IN', 'ISO-639': 'ml' }, - maldivian: { IETF: 'dv-MV', 'ISO-639': 'dv' }, - maltese: { IETF: 'mt-MT', 'ISO-639': 'mt' }, - mandara: { IETF: 'mfi-CM', 'ISO-639': 'mfi' }, - manipuri: { IETF: 'mni-IN', 'ISO-639': 'mni' }, - 'manx gaelic': { IETF: 'gv-IM', 'ISO-639': 'gv' }, - maori: { IETF: 'mi-NZ', 'ISO-639': 'mi' }, - marathi: { IETF: 'mr-IN', 'ISO-639': 'mr' }, - margi: { IETF: 'mrt-NG', 'ISO-639': 'mrt' }, - mari: { IETF: 'mhr-RU', 'ISO-639': 'mhr' }, - marshallese: { IETF: 'mh-MH', 'ISO-639': 'mh' }, - mende: { IETF: 'men-SL', 'ISO-639': 'men' }, - meru: { IETF: 'mer-KE', 'ISO-639': 'mer' }, - mijikenda: { IETF: 'nyf-KE', 'ISO-639': 'nyf' }, - minangkabau: { IETF: 'min-ID', 'ISO-639': 'min' }, - mizo: { IETF: 'lus-IN', 'ISO-639': 'lus' }, - mongolian: { IETF: 'mn-MN', 'ISO-639': 'mn' }, - montenegrin: { IETF: 'sr-ME', 'ISO-639': 'sr' }, - morisyen: { IETF: 'mfe-MU', 'ISO-639': 'mfe' }, - 'moroccan arabic': { IETF: 'ar-MA', 'ISO-639': 'ar' }, - mossi: { IETF: 'mos-BF', 'ISO-639': 'mos' }, - ndau: { IETF: 'ndc-MZ', 'ISO-639': 'ndc' }, - ndebele: { IETF: 'nr-ZA', 'ISO-639': 'nr' }, - nepali: { IETF: 'ne-NP', 'ISO-639': 'ne' }, - 'nigerian fulfulde': { IETF: 'fuv-NG', 'ISO-639': 'fuv' }, - niuean: { IETF: 'niu-NU', 'ISO-639': 'niu' }, - 'north azerbaijani': { IETF: 'azj-AZ', 'ISO-639': 'azj' }, - sesotho: { IETF: 'nso-ZA', 'ISO-639': 'nso' }, - 'northern uzbek': { IETF: 'uzn-UZ', 'ISO-639': 'uzn' }, - 'norwegian bokm�l': { IETF: 'nb-NO', 'ISO-639': 'nb' }, - 'norwegian nynorsk': { IETF: 'nn-NO', 'ISO-639': 'nn' }, - nuer: { IETF: 'nus-SS', 'ISO-639': 'nus' }, - nyanja: { IETF: 'ny-MW', 'ISO-639': 'ny' }, - occitan: { IETF: 'oc-FR', 'ISO-639': 'oc' }, - 'occitan aran': { IETF: 'oc-ES', 'ISO-639': 'oc' }, - odia: { IETF: 'or-IN', 'ISO-639': 'or' }, - oriya: { IETF: 'ory-IN', 'ISO-639': 'ory' }, - urdu: { IETF: 'ur-PK', 'ISO-639': 'ur' }, - palauan: { IETF: 'pau-PW', 'ISO-639': 'pau' }, - pali: { IETF: 'pi-IN', 'ISO-639': 'pi' }, - pangasinan: { IETF: 'pag-PH', 'ISO-639': 'pag' }, - papiamentu: { IETF: 'pap-CW', 'ISO-639': 'pap' }, - pashto: { IETF: 'ps-PK', 'ISO-639': 'ps' }, - persian: { IETF: 'fa-IR', 'ISO-639': 'fa' }, - pijin: { IETF: 'pis-SB', 'ISO-639': 'pis' }, - 'plateau malagasy': { IETF: 'plt-MG', 'ISO-639': 'plt' }, - polish: { IETF: 'pl-PL', 'ISO-639': 'pl' }, - portuguese: { IETF: 'pt-PT', 'ISO-639': 'pt' }, - 'portuguese brazil': { IETF: 'pt-BR', 'ISO-639': 'pt' }, - potawatomi: { IETF: 'pot-US', 'ISO-639': 'pot' }, - punjabi: { IETF: 'pa-IN', 'ISO-639': 'pa' }, - 'punjabi (pakistan)': { IETF: 'pnb-PK', 'ISO-639': 'pnb' }, - quechua: { IETF: 'qu-PE', 'ISO-639': 'qu' }, - rohingya: { IETF: 'rhg-MM', 'ISO-639': 'rhg' }, - rohingyalish: { IETF: 'rhl-MM', 'ISO-639': 'rhl' }, - romanian: { IETF: 'ro-RO', 'ISO-639': 'ro' }, - romansh: { IETF: 'roh-CH', 'ISO-639': 'roh' }, - rundi: { IETF: 'run-BI', 'ISO-639': 'run' }, - russian: { IETF: 'ru-RU', 'ISO-639': 'ru' }, - 'saint lucian creole french': { IETF: 'acf-LC', 'ISO-639': 'acf' }, - samoan: { IETF: 'sm-WS', 'ISO-639': 'sm' }, - sango: { IETF: 'sg-CF', 'ISO-639': 'sg' }, - sanskrit: { IETF: 'sa-IN', 'ISO-639': 'sa' }, - santali: { IETF: 'sat-IN', 'ISO-639': 'sat' }, - sardinian: { IETF: 'sc-IT', 'ISO-639': 'sc' }, - 'scots gaelic': { IETF: 'gd-GB', 'ISO-639': 'gd' }, - sena: { IETF: 'seh-ZW', 'ISO-639': 'seh' }, - 'serbian cyrillic': { IETF: 'sr-Cyrl-RS', 'ISO-639': 'sr' }, - 'serbian latin': { IETF: 'sr-Latn-RS', 'ISO-639': 'sr' }, - 'seselwa creole french': { IETF: 'crs-SC', 'ISO-639': 'crs' }, - 'setswana (south africa)': { IETF: 'tn-ZA', 'ISO-639': 'tn' }, - shan: { IETF: 'shn-MM', 'ISO-639': 'shn' }, - shona: { IETF: 'sn-ZW', 'ISO-639': 'sn' }, - sicilian: { IETF: 'scn-IT', 'ISO-639': 'scn' }, - silesian: { IETF: 'szl-PL', 'ISO-639': 'szl' }, - 'sindhi snd': { IETF: 'snd-PK', 'ISO-639': 'snd' }, - 'sindhi sd': { IETF: 'sd-PK', 'ISO-639': 'sd' }, - sinhala: { IETF: 'si-LK', 'ISO-639': 'si' }, - slovak: { IETF: 'sk-SK', 'ISO-639': 'sk' }, - slovenian: { IETF: 'sl-SI', 'ISO-639': 'sl' }, - somali: { IETF: 'so-SO', 'ISO-639': 'so' }, - 'sotho southern': { IETF: 'st-LS', 'ISO-639': 'st' }, - 'south azerbaijani': { IETF: 'azb-AZ', 'ISO-639': 'azb' }, - 'southern pashto': { IETF: 'pbt-PK', 'ISO-639': 'pbt' }, - 'southwestern dinka': { IETF: 'dik-SS', 'ISO-639': 'dik' }, - spanish: { IETF: 'es-ES', 'ISO-639': 'es' }, - 'spanish argentina': { IETF: 'es-AR', 'ISO-639': 'es' }, - 'spanish colombia': { IETF: 'es-CO', 'ISO-639': 'es' }, - 'spanish latin america': { IETF: 'es-419', 'ISO-639': 'es' }, - 'spanish mexico': { IETF: 'es-MX', 'ISO-639': 'es' }, - 'spanish united states': { IETF: 'es-US', 'ISO-639': 'es' }, - 'sranan tongo': { IETF: 'srn-SR', 'ISO-639': 'srn' }, - 'standard latvian': { IETF: 'lvs-LV', 'ISO-639': 'lvs' }, - 'standard malay': { IETF: 'zsm-MY', 'ISO-639': 'zsm' }, - sundanese: { IETF: 'su-ID', 'ISO-639': 'su' }, - swahili: { IETF: 'sw-KE', 'ISO-639': 'sw' }, - swati: { IETF: 'ss-SZ', 'ISO-639': 'ss' }, - swedish: { IETF: 'sv-SE', 'ISO-639': 'sv' }, - 'swiss german': { IETF: 'de-CH', 'ISO-639': 'de' }, - 'syriac (aramaic)': { IETF: 'syc-TR', 'ISO-639': 'syc' }, - tagalog: { IETF: 'tl-PH', 'ISO-639': 'tl' }, - tahitian: { IETF: 'ty-PF', 'ISO-639': 'ty' }, - tajik: { IETF: 'tg-TJ', 'ISO-639': 'tg' }, - 'tamashek (tuareg)': { IETF: 'tmh-DZ', 'ISO-639': 'tmh' }, - tamasheq: { IETF: 'taq-ML', 'ISO-639': 'taq' }, - 'tamil india': { IETF: 'ta-IN', 'ISO-639': 'ta' }, - 'tamil sri lanka': { IETF: 'ta-LK', 'ISO-639': 'ta' }, - taroko: { IETF: 'trv-TW', 'ISO-639': 'trv' }, - tatar: { IETF: 'tt-RU', 'ISO-639': 'tt' }, - telugu: { IETF: 'te-IN', 'ISO-639': 'te' }, - tetum: { IETF: 'tet-TL', 'ISO-639': 'tet' }, - thai: { IETF: 'th-TH', 'ISO-639': 'th' }, - tibetan: { IETF: 'bo-CN', 'ISO-639': 'bo' }, - tigrinya: { IETF: 'ti-ET', 'ISO-639': 'ti' }, - 'tok pisin': { IETF: 'tpi-PG', 'ISO-639': 'tpi' }, - tokelauan: { IETF: 'tkl-TK', 'ISO-639': 'tkl' }, - tongan: { IETF: 'to-TO', 'ISO-639': 'to' }, - 'tosk albanian': { IETF: 'als-AL', 'ISO-639': 'als' }, - tsonga: { IETF: 'ts-ZA', 'ISO-639': 'ts' }, - tswa: { IETF: 'tsc-MZ', 'ISO-639': 'tsc' }, - tswana: { IETF: 'tn-BW', 'ISO-639': 'tn' }, - tumbuka: { IETF: 'tum-MW', 'ISO-639': 'tum' }, - turkish: { IETF: 'tr-TR', 'ISO-639': 'tr' }, - turkmen: { IETF: 'tk-TM', 'ISO-639': 'tk' }, - tuvaluan: { IETF: 'tvl-TV', 'ISO-639': 'tvl' }, - twi: { IETF: 'tw-GH', 'ISO-639': 'tw' }, - udmurt: { IETF: 'udm-RU', 'ISO-639': 'udm' }, - ukrainian: { IETF: 'uk-UA', 'ISO-639': 'uk' }, - uma: { IETF: 'ppk-ID', 'ISO-639': 'ppk' }, - umbundu: { IETF: 'umb-AO', 'ISO-639': 'umb' }, - 'uyghur uig': { IETF: 'uig-CN', 'ISO-639': 'uig' }, - 'uyghur ug': { IETF: 'ug-CN', 'ISO-639': 'ug' }, - uzbek: { IETF: 'uz-UZ', 'ISO-639': 'uz' }, - venetian: { IETF: 'vec-IT', 'ISO-639': 'vec' }, - vietnamese: { IETF: 'vi-VN', 'ISO-639': 'vi' }, - 'vincentian creole english': { IETF: 'svc-VC', 'ISO-639': 'svc' }, - 'virgin islands creole english': { IETF: 'vic-US', 'ISO-639': 'vic' }, - wallisian: { IETF: 'wls-WF', 'ISO-639': 'wls' }, - 'waray (philippines)': { IETF: 'war-PH', 'ISO-639': 'war' }, - welsh: { IETF: 'cy-GB', 'ISO-639': 'cy' }, - 'west central oromo': { IETF: 'gaz-ET', 'ISO-639': 'gaz' }, - 'western persian': { IETF: 'pes-IR', 'ISO-639': 'pes' }, - wolof: { IETF: 'wo-SN', 'ISO-639': 'wo' }, - xhosa: { IETF: 'xh-ZA', 'ISO-639': 'xh' }, - yiddish: { IETF: 'yi-YD', 'ISO-639': 'yi' }, - yoruba: { IETF: 'yo-NG', 'ISO-639': 'yo' }, - zulu: { IETF: 'zu-ZA', 'ISO-639': 'zu' }, + none: { IETF: 'none', ISO639: 'none', ISO3166: 'xx' }, + english: { IETF: 'en-GB', ISO639: 'en', ISO3166: 'gb' }, + spanish: { IETF: 'es-ES', ISO639: 'es', ISO3166: 'es' }, + dutch: { IETF: 'nl-NL', ISO639: 'nl', ISO3166: 'nl' }, + 'chinese simplified': { IETF: 'zh-CN', ISO639: 'zh', ISO3166: 'cn' }, + russian: { IETF: 'ru-RU', ISO639: 'ru', ISO3166: 'ru' }, + indonesian: { IETF: 'id-ID', ISO639: 'id', ISO3166: 'id' }, + hindi: { IETF: 'hi-IN', ISO639: 'hi', ISO3166: 'in' }, + filipino: { IETF: 'fil-PH', ISO639: 'fil', ISO3166: 'ph' }, + turkish: { IETF: 'tr-TR', ISO639: 'tr', ISO3166: 'tr' }, + acehnese: { IETF: 'ace-ID', ISO639: 'ace', ISO3166: 'id' }, + afrikaans: { IETF: 'af-ZA', ISO639: 'af', ISO3166: 'za' }, + akan: { IETF: 'ak-GH', ISO639: 'ak', ISO3166: 'gh' }, + albanian: { IETF: 'sq-AL', ISO639: 'sq', ISO3166: 'al' }, + amharic: { IETF: 'am-ET', ISO639: 'am', ISO3166: 'et' }, + 'antigua and barbuda creole english': { IETF: 'aig-AG', ISO639: 'aig', ISO3166: 'ag' }, + arabic: { IETF: 'ar-SA', ISO639: 'ar', ISO3166: 'sa' }, + 'arabic egyptian': { IETF: 'ar-EG', ISO639: 'arz', ISO3166: 'eg' }, + aragonese: { IETF: 'es-ES', ISO639: 'an', ISO3166: 'es' }, + armenian: { IETF: 'hy-AM', ISO639: 'hy', ISO3166: 'am' }, + assamese: { IETF: 'as-IN', ISO639: 'as', ISO3166: 'in' }, + asturian: { IETF: 'ast-ES', ISO639: 'ast', ISO3166: 'es' }, + 'austrian german': { IETF: 'de-AT', ISO639: 'de', ISO3166: 'at' }, + awadhi: { IETF: 'awa-IN', ISO639: 'awa', ISO3166: 'in' }, + 'ayacucho quechua': { IETF: 'quy-PE', ISO639: 'quy', ISO3166: 'pe' }, + azerbaijani: { IETF: 'az-AZ', ISO639: 'az', ISO3166: 'az' }, + 'bahamas creole english': { IETF: 'bah-BS', ISO639: 'bah', ISO3166: 'bs' }, + bajan: { IETF: 'bjs-BB', ISO639: 'bjs', ISO3166: 'bb' }, + balinese: { IETF: 'ban-ID', ISO639: 'ban', ISO3166: 'id' }, + 'balkan gipsy': { IETF: 'rm-RO', ISO639: 'rm', ISO3166: 'ro' }, + bambara: { IETF: 'bm-ML', ISO639: 'bm', ISO3166: 'ml' }, + banjar: { IETF: 'bjn-ID', ISO639: 'bjn', ISO3166: 'id' }, + bashkir: { IETF: 'ba-RU', ISO639: 'ba', ISO3166: 'ru' }, + basque: { IETF: 'eu-ES', ISO639: 'eu', ISO3166: 'es-pv' }, + belarusian: { IETF: 'be-BY', ISO639: 'be', ISO3166: 'by' }, + 'belgian french': { IETF: 'fr-BE', ISO639: 'fr', ISO3166: 'be' }, + bemba: { IETF: 'bem-ZM', ISO639: 'bem', ISO3166: 'zm' }, + bengali: { IETF: 'bn-IN', ISO639: 'bn', ISO3166: 'bd' }, + bhojpuri: { IETF: 'bho-IN', ISO639: 'bho', ISO3166: 'in' }, + bihari: { IETF: 'bh-IN', ISO639: 'bh', ISO3166: 'in' }, + bislama: { IETF: 'bi-VU', ISO639: 'bi', ISO3166: 'vu' }, + borana: { IETF: 'gax-KE', ISO639: 'gax', ISO3166: 'ke' }, + bosnian: { IETF: 'bs-BA', ISO639: 'bs', ISO3166: 'ba' }, + 'bosnian (cyrillic)': { IETF: 'bs-Cyrl-BA', ISO639: 'bs', ISO3166: 'ba' }, + breton: { IETF: 'br-FR', ISO639: 'br', ISO3166: 'fr' }, + buginese: { IETF: 'bug-ID', ISO639: 'bug', ISO3166: 'id' }, + bulgarian: { IETF: 'bg-BG', ISO639: 'bg', ISO3166: 'bg' }, + burmese: { IETF: 'my-MM', ISO639: 'my', ISO3166: 'mm' }, + catalan: { IETF: 'ca-ES', ISO639: 'ca', ISO3166: 'es' }, + 'catalan valencian': { IETF: 'cav-ES', ISO639: 'cav', ISO3166: 'es' }, + cebuano: { IETF: 'ceb-PH', ISO639: 'ceb', ISO3166: 'ph' }, + 'central atlas tamazight': { IETF: 'tzm-MA', ISO639: 'tzm', ISO3166: 'ma' }, + 'central aymara': { IETF: 'ayr-BO', ISO639: 'ayr', ISO3166: 'bo' }, + 'central kanuri (latin script)': { IETF: 'knc-NG', ISO639: 'knc', ISO3166: 'ng' }, + 'chadian arabic': { IETF: 'shu-TD', ISO639: 'shu', ISO3166: 'td' }, + chamorro: { IETF: 'ch-GU', ISO639: 'ch', ISO3166: 'gu' }, + cherokee: { IETF: 'chr-US', ISO639: 'chr', ISO3166: 'us' }, + chhattisgarhi: { IETF: 'hne-IN', ISO639: 'hne', ISO3166: 'in' }, + 'chinese trad. (hong kong)': { IETF: 'zh-HK', ISO639: 'zh', ISO3166: 'hk' }, + 'chinese traditional': { IETF: 'zh-TW', ISO639: 'zh', ISO3166: 'tw' }, + 'chinese traditional macau': { IETF: 'zh-MO', ISO639: 'zh', ISO3166: 'mo' }, + chittagonian: { IETF: 'ctg-BD', ISO639: 'ctg', ISO3166: 'bd' }, + chokwe: { IETF: 'cjk-AO', ISO639: 'cjk', ISO3166: 'ao' }, + 'classical greek': { IETF: 'grc-GR', ISO639: 'grc', ISO3166: 'gr' }, + 'comorian ngazidja': { IETF: 'zdj-KM', ISO639: 'zdj', ISO3166: 'km' }, + coptic: { IETF: 'cop-EG', ISO639: 'cop', ISO3166: 'eg' }, + 'crimean tatar': { IETF: 'crh-RU', ISO639: 'crh', ISO3166: 'tr' }, + 'crioulo upper guinea': { IETF: 'pov-GW', ISO639: 'pov', ISO3166: 'gw' }, + croatian: { IETF: 'hr-HR', ISO639: 'hr', ISO3166: 'hr' }, + 'serbo-croatian': { IETF: 'sr-Cyrl-RS', ISO639: 'sh', ISO3166: 'sr' }, + czech: { IETF: 'cs-CZ', ISO639: 'cs', ISO3166: 'cz' }, + danish: { IETF: 'da-DK', ISO639: 'da', ISO3166: 'dk' }, + dari: { IETF: 'prs-AF', ISO639: 'prs', ISO3166: 'af' }, + dimli: { IETF: 'diq-TR', ISO639: 'diq', ISO3166: 'tr' }, + dyula: { IETF: 'dyu-CI', ISO639: 'dyu', ISO3166: 'ci' }, + dzongkha: { IETF: 'dz-BT', ISO639: 'dz', ISO3166: 'bt' }, + 'eastern yiddish': { IETF: 'ydd-US', ISO639: 'ydd', ISO3166: 'il' }, + emakhuwa: { IETF: 'vmw-MZ', ISO639: 'vmw', ISO3166: 'mz' }, + 'english australia': { IETF: 'en-AU', ISO639: 'en', ISO3166: 'au' }, + 'english canada': { IETF: 'en-CA', ISO639: 'en', ISO3166: 'ca' }, + 'english india': { IETF: 'en-IN', ISO639: 'en', ISO3166: 'in' }, + 'english ireland': { IETF: 'en-IE', ISO639: 'en', ISO3166: 'ie' }, + 'english new zealand': { IETF: 'en-NZ', ISO639: 'en', ISO3166: 'nz' }, + 'english singapore': { IETF: 'en-SG', ISO639: 'en', ISO3166: 'sg' }, + 'english south africa': { IETF: 'en-ZA', ISO639: 'en', ISO3166: 'za' }, + 'english us': { IETF: 'en-US', ISO639: 'en', ISO3166: 'us' }, + esperanto: { IETF: 'eo-EU', ISO639: 'eo', ISO3166: 'eu' }, + estonian: { IETF: 'et-EE', ISO639: 'et', ISO3166: 'ee' }, + ewe: { IETF: 'ee-GH', ISO639: 'ee', ISO3166: 'gh' }, + fanagalo: { IETF: 'fn-FNG', ISO639: 'fn', ISO3166: 'za' }, + faroese: { IETF: 'fo-FO', ISO639: 'fo', ISO3166: 'fo' }, + fijian: { IETF: 'fj-FJ', ISO639: 'fj', ISO3166: 'fj' }, + finnish: { IETF: 'fi-FI', ISO639: 'fi', ISO3166: 'fi' }, + flemish: { IETF: 'nl-BE', ISO639: 'nl', ISO3166: 'be' }, + fon: { IETF: 'fon-BJ', ISO639: 'fon', ISO3166: 'bj' }, + french: { IETF: 'fr-FR', ISO639: 'fr', ISO3166: 'fr' }, + 'french canada': { IETF: 'fr-CA', ISO639: 'fr', ISO3166: 'ca' }, + 'french swiss': { IETF: 'fr-CH', ISO639: 'fr', ISO3166: 'ch' }, + friulian: { IETF: 'fur-IT', ISO639: 'fur', ISO3166: 'it' }, + fula: { IETF: 'ff-FUL', ISO639: 'ff', ISO3166: 'cm' }, + galician: { IETF: 'gl-ES', ISO639: 'gl', ISO3166: 'es-ga' }, + gamargu: { IETF: 'mfi-NG', ISO639: 'mfi', ISO3166: 'ng' }, + garo: { IETF: 'grt-IN', ISO639: 'grt', ISO3166: 'in' }, + georgian: { IETF: 'ka-GE', ISO639: 'ka', ISO3166: 'ge' }, + german: { IETF: 'de-DE', ISO639: 'de', ISO3166: 'de' }, + 'Low German': { IETF: 'nl-NL', ISO639: 'nds', ISO3166: 'nl' }, + gilbertese: { IETF: 'gil-KI', ISO639: 'gil', ISO3166: 'ki' }, + glavda: { IETF: 'glw-NG', ISO639: 'glw', ISO3166: 'ng' }, + greek: { IETF: 'el-GR', ISO639: 'el', ISO3166: 'gr' }, + 'grenadian creole english': { IETF: 'gcl-GD', ISO639: 'gcl', ISO3166: 'gd' }, + guarani: { IETF: 'gn-PY', ISO639: 'gn', ISO3166: 'py' }, + gujarati: { IETF: 'gu-IN', ISO639: 'gu', ISO3166: 'in' }, + 'guyanese creole english': { IETF: 'gyn-GY', ISO639: 'gyn', ISO3166: 'gy' }, + 'haitian creole french': { IETF: 'ht-HT', ISO639: 'ht', ISO3166: 'ht' }, + 'halh mongolian': { IETF: 'khk-MN', ISO639: 'khk', ISO3166: 'mn' }, + hausa: { IETF: 'ha-NE', ISO639: 'ha', ISO3166: 'ne' }, + hawaiian: { IETF: 'haw-US', ISO639: 'haw', ISO3166: 'xx' }, + hebrew: { IETF: 'he-IL', ISO639: 'he', ISO3166: 'il' }, + higi: { IETF: 'hig-NG', ISO639: 'hig', ISO3166: 'ng' }, + hiligaynon: { IETF: 'hil-PH', ISO639: 'hil', ISO3166: 'ph' }, + 'hill mari': { IETF: 'mrj-RU', ISO639: 'mrj', ISO3166: 'xx' }, + hmong: { IETF: 'hmn-CN', ISO639: 'hmn', ISO3166: 'cn' }, + hungarian: { IETF: 'hu-HU', ISO639: 'hu', ISO3166: 'hu' }, + icelandic: { IETF: 'is-IS', ISO639: 'is', ISO3166: 'is' }, + 'igbo ibo': { IETF: 'ibo-NG', ISO639: 'ibo', ISO3166: 'ng' }, + 'igbo ig': { IETF: 'ig-NG', ISO639: 'ig', ISO3166: 'ng' }, + ilocano: { IETF: 'ilo-PH', ISO639: 'ilo', ISO3166: 'ph' }, + 'inuktitut greenlandic': { IETF: 'kl-GL', ISO639: 'kl', ISO3166: 'gl' }, + 'irish gaelic': { IETF: 'ga-IE', ISO639: 'ga', ISO3166: 'ie' }, + italian: { IETF: 'it-IT', ISO639: 'it', ISO3166: 'it' }, + 'italian swiss': { IETF: 'it-CH', ISO639: 'it', ISO3166: 'ch' }, + 'jamaican creole english': { IETF: 'jam-JM', ISO639: 'jam', ISO3166: 'jm' }, + japanese: { IETF: 'ja-JP', ISO639: 'ja', ISO3166: 'jp' }, + javanese: { IETF: 'jv-ID', ISO639: 'jv', ISO3166: 'id' }, + jingpho: { IETF: 'kac-MM', ISO639: 'kac', ISO3166: 'mm' }, + "k'iche'": { IETF: 'quc-GT', ISO639: 'quc', ISO3166: 'gt' }, + kabiye: { IETF: 'kbp-TG', ISO639: 'kbp', ISO3166: 'tg' }, + kabuverdianu: { IETF: 'kea-CV', ISO639: 'kea', ISO3166: 'cv' }, + kabylian: { IETF: 'kab-DZ', ISO639: 'kab', ISO3166: 'dz' }, + kalenjin: { IETF: 'kln-KE', ISO639: 'kln', ISO3166: 'ke' }, + kamba: { IETF: 'kam-KE', ISO639: 'kam', ISO3166: 'ke' }, + kannada: { IETF: 'kn-IN', ISO639: 'kn', ISO3166: 'in' }, + kanuri: { IETF: 'kr-KAU', ISO639: 'kr', ISO3166: 'xx' }, + karen: { IETF: 'kar-MM', ISO639: 'kar', ISO3166: 'mm' }, + 'kashmiri (devanagari script)': { IETF: 'ks-IN', ISO639: 'ks', ISO3166: 'in' }, + 'kashmiri (arabic script)': { IETF: 'kas-IN', ISO639: 'kas', ISO3166: 'in' }, + kazakh: { IETF: 'kk-KZ', ISO639: 'kk', ISO3166: 'kz' }, + khasi: { IETF: 'kha-IN', ISO639: 'kha', ISO3166: 'in' }, + khmer: { IETF: 'km-KH', ISO639: 'km', ISO3166: 'kh' }, + 'kikuyu kik': { IETF: 'kik-KE', ISO639: 'kik', ISO3166: 'ke' }, + 'kikuyu ki': { IETF: 'ki-KE', ISO639: 'ki', ISO3166: 'ke' }, + kimbundu: { IETF: 'kmb-AO', ISO639: 'kmb', ISO3166: 'ao' }, + kinyarwanda: { IETF: 'rw-RW', ISO639: 'rw', ISO3166: 'rw' }, + kirundi: { IETF: 'rn-BI', ISO639: 'rn', ISO3166: 'bi' }, + kisii: { IETF: 'guz-KE', ISO639: 'guz', ISO3166: 'ke' }, + kongo: { IETF: 'kg-CG', ISO639: 'kg', ISO3166: 'cg' }, + konkani: { IETF: 'kok-IN', ISO639: 'kok', ISO3166: 'in' }, + korean: { IETF: 'ko-KR', ISO639: 'ko', ISO3166: 'kr' }, + 'northern kurdish': { IETF: 'kmr-TR', ISO639: 'kmr', ISO3166: 'tr' }, + 'kurdish sorani': { IETF: 'ckb-IQ', ISO639: 'ckb', ISO3166: 'iq' }, + kyrgyz: { IETF: 'ky-KG', ISO639: 'ky', ISO3166: 'kg' }, + lao: { IETF: 'lo-LA', ISO639: 'lo', ISO3166: 'la' }, + latgalian: { IETF: 'ltg-LV', ISO639: 'ltg', ISO3166: 'lv' }, + latin: { IETF: 'la-XN', ISO639: 'la', ISO3166: 'xx' }, + latvian: { IETF: 'lv-LV', ISO639: 'lv', ISO3166: 'lg' }, + ligurian: { IETF: 'lij-IT', ISO639: 'lij', ISO3166: 'it' }, + limburgish: { IETF: 'li-NL', ISO639: 'li', ISO3166: 'nl' }, + lingala: { IETF: 'ln-LIN', ISO639: 'ln', ISO3166: 'cd' }, + lithuanian: { IETF: 'lt-LT', ISO639: 'lt', ISO3166: 'lt' }, + lombard: { IETF: 'lmo-IT', ISO639: 'lmo', ISO3166: 'it' }, + 'luba-kasai': { IETF: 'lua-CD', ISO639: 'lua', ISO3166: 'cd' }, + luganda: { IETF: 'lg-UG', ISO639: 'lg', ISO3166: 'ug' }, + luhya: { IETF: 'luy-KE', ISO639: 'luy', ISO3166: 'ke' }, + luo: { IETF: 'luo-KE', ISO639: 'luo', ISO3166: 'ke' }, + luxembourgish: { IETF: 'lb-LU', ISO639: 'lb', ISO3166: 'lu' }, + maa: { IETF: 'mas-KE', ISO639: 'mas', ISO3166: 'ke' }, + macedonian: { IETF: 'mk-MK', ISO639: 'mk', ISO3166: 'mk' }, + magahi: { IETF: 'mag-IN', ISO639: 'mag', ISO3166: 'in' }, + maithili: { IETF: 'mai-IN', ISO639: 'mai', ISO3166: 'in' }, + malagasy: { IETF: 'mg-MG', ISO639: 'mg', ISO3166: 'mg' }, + malay: { IETF: 'ms-MY', ISO639: 'ms', ISO3166: 'my' }, + malayalam: { IETF: 'ml-IN', ISO639: 'ml', ISO3166: 'in' }, + maldivian: { IETF: 'dv-MV', ISO639: 'dv', ISO3166: 'mv' }, + maltese: { IETF: 'mt-MT', ISO639: 'mt', ISO3166: 'mt' }, + mandara: { IETF: 'mfi-CM', ISO639: 'mfi', ISO3166: 'cm' }, + manipuri: { IETF: 'mni-IN', ISO639: 'mni', ISO3166: 'in' }, + 'manx gaelic': { IETF: 'gv-IM', ISO639: 'gv', ISO3166: 'im' }, + maori: { IETF: 'mi-NZ', ISO639: 'mi', ISO3166: 'nz' }, + marathi: { IETF: 'mr-IN', ISO639: 'mr', ISO3166: 'in' }, + margi: { IETF: 'mrt-NG', ISO639: 'mrt', ISO3166: 'ng' }, + mari: { IETF: 'mhr-RU', ISO639: 'mhr', ISO3166: 'xx' }, + marshallese: { IETF: 'mh-MH', ISO639: 'mh', ISO3166: 'mh' }, + mende: { IETF: 'men-SL', ISO639: 'men', ISO3166: 'sl' }, + meru: { IETF: 'mer-KE', ISO639: 'mer', ISO3166: 'ke' }, + mijikenda: { IETF: 'nyf-KE', ISO639: 'nyf', ISO3166: 'ke' }, + minangkabau: { IETF: 'min-ID', ISO639: 'min', ISO3166: 'id' }, + mizo: { IETF: 'lus-IN', ISO639: 'lus', ISO3166: 'in' }, + mongolian: { IETF: 'mn-MN', ISO639: 'mn', ISO3166: 'mn' }, + montenegrin: { IETF: 'sr-ME', ISO639: 'sr', ISO3166: 'me' }, + morisyen: { IETF: 'mfe-MU', ISO639: 'mfe', ISO3166: 'mu' }, + 'moroccan arabic': { IETF: 'ar-MA', ISO639: 'ar', ISO3166: 'ma' }, + mossi: { IETF: 'mos-BF', ISO639: 'mos', ISO3166: 'bf' }, + ndau: { IETF: 'ndc-MZ', ISO639: 'ndc', ISO3166: 'mz' }, + ndebele: { IETF: 'nr-ZA', ISO639: 'nr', ISO3166: 'za' }, + nepali: { IETF: 'ne-NP', ISO639: 'ne', ISO3166: 'np' }, + 'nigerian fulfulde': { IETF: 'fuv-NG', ISO639: 'fuv', ISO3166: 'ng' }, + niuean: { IETF: 'niu-NU', ISO639: 'niu', ISO3166: 'nu' }, + 'north azerbaijani': { IETF: 'azj-AZ', ISO639: 'azj', ISO3166: 'az' }, + sesotho: { IETF: 'nso-ZA', ISO639: 'nso', ISO3166: 'za' }, + 'northern uzbek': { IETF: 'uzn-UZ', ISO639: 'uzn', ISO3166: 'uz' }, + 'norwegian bokm�l': { IETF: 'nb-NO', ISO639: 'nb', ISO3166: 'no' }, + 'norwegian nynorsk': { IETF: 'nn-NO', ISO639: 'nn', ISO3166: 'no' }, + nuer: { IETF: 'nus-SS', ISO639: 'nus', ISO3166: 'ss' }, + nyanja: { IETF: 'ny-MW', ISO639: 'ny', ISO3166: 'mw' }, + occitan: { IETF: 'oc-FR', ISO639: 'oc', ISO3166: 'fr' }, + 'occitan aran': { IETF: 'oc-ES', ISO639: 'oc', ISO3166: 'es-ct' }, + odia: { IETF: 'or-IN', ISO639: 'or', ISO3166: 'in' }, + oriya: { IETF: 'ory-IN', ISO639: 'ory', ISO3166: 'in' }, + urdu: { IETF: 'ur-PK', ISO639: 'ur', ISO3166: 'pk' }, + palauan: { IETF: 'pau-PW', ISO639: 'pau', ISO3166: 'pw' }, + pali: { IETF: 'pi-IN', ISO639: 'pi', ISO3166: 'in' }, + pangasinan: { IETF: 'pag-PH', ISO639: 'pag', ISO3166: 'ph' }, + papiamentu: { IETF: 'pap-CW', ISO639: 'pap', ISO3166: 'cw' }, + pashto: { IETF: 'ps-PK', ISO639: 'ps', ISO3166: 'pk' }, + persian: { IETF: 'fa-IR', ISO639: 'fa', ISO3166: 'ir' }, + pijin: { IETF: 'pis-SB', ISO639: 'pis', ISO3166: 'sb' }, + 'plateau malagasy': { IETF: 'plt-MG', ISO639: 'plt', ISO3166: 'mg' }, + polish: { IETF: 'pl-PL', ISO639: 'pl', ISO3166: 'pl' }, + portuguese: { IETF: 'pt-PT', ISO639: 'pt', ISO3166: 'pt' }, + 'portuguese brazil': { IETF: 'pt-BR', ISO639: 'pt', ISO3166: 'br' }, + potawatomi: { IETF: 'pot-US', ISO639: 'pot', ISO3166: 'us' }, + punjabi: { IETF: 'pa-IN', ISO639: 'pa', ISO3166: 'in' }, + 'punjabi (pakistan)': { IETF: 'pnb-PK', ISO639: 'pnb', ISO3166: 'pk' }, + quechua: { IETF: 'qu-PE', ISO639: 'qu', ISO3166: 'pe' }, + rohingya: { IETF: 'rhg-MM', ISO639: 'rhg', ISO3166: 'mm' }, + rohingyalish: { IETF: 'rhl-MM', ISO639: 'rhl', ISO3166: 'mm' }, + romanian: { IETF: 'ro-RO', ISO639: 'ro', ISO3166: 'ro' }, + romansh: { IETF: 'roh-CH', ISO639: 'roh', ISO3166: 'ch' }, + rundi: { IETF: 'run-BI', ISO639: 'run', ISO3166: 'bi' }, + 'saint lucian creole french': { IETF: 'acf-LC', ISO639: 'acf', ISO3166: 'lc' }, + samoan: { IETF: 'sm-WS', ISO639: 'sm', ISO3166: 'ws' }, + sango: { IETF: 'sg-CF', ISO639: 'sg', ISO3166: 'cf' }, + sanskrit: { IETF: 'sa-IN', ISO639: 'sa', ISO3166: 'in' }, + santali: { IETF: 'sat-IN', ISO639: 'sat', ISO3166: 'in' }, + sardinian: { IETF: 'sc-IT', ISO639: 'sc', ISO3166: 'it' }, + 'scots gaelic': { IETF: 'gd-GB', ISO639: 'gd', ISO3166: 'gb-sct' }, + sena: { IETF: 'seh-ZW', ISO639: 'seh', ISO3166: 'zw' }, + 'serbian cyrillic': { IETF: 'sr-Cyrl-RS', ISO639: 'sr', ISO3166: 'rs' }, + 'serbian latin': { IETF: 'sr-Latn-RS', ISO639: 'sr', ISO3166: 'rs' }, + 'seselwa creole french': { IETF: 'crs-SC', ISO639: 'crs', ISO3166: 'sc' }, + 'setswana (south africa)': { IETF: 'tn-ZA', ISO639: 'tn', ISO3166: 'za' }, + shan: { IETF: 'shn-MM', ISO639: 'shn', ISO3166: 'mm' }, + shona: { IETF: 'sn-ZW', ISO639: 'sn', ISO3166: 'zw' }, + sicilian: { IETF: 'scn-IT', ISO639: 'scn', ISO3166: 'it' }, + silesian: { IETF: 'szl-PL', ISO639: 'szl', ISO3166: 'pl' }, + 'sindhi snd': { IETF: 'snd-PK', ISO639: 'snd', ISO3166: 'pk' }, + 'sindhi sd': { IETF: 'sd-PK', ISO639: 'sd', ISO3166: 'pk' }, + sinhala: { IETF: 'si-LK', ISO639: 'si', ISO3166: 'lk' }, + slovak: { IETF: 'sk-SK', ISO639: 'sk', ISO3166: 'sk' }, + slovenian: { IETF: 'sl-SI', ISO639: 'sl', ISO3166: 'si' }, + somali: { IETF: 'so-SO', ISO639: 'so', ISO3166: 'so' }, + 'sotho southern': { IETF: 'st-LS', ISO639: 'st', ISO3166: 'ls' }, + 'south azerbaijani': { IETF: 'azb-AZ', ISO639: 'azb', ISO3166: 'az' }, + 'southern pashto': { IETF: 'pbt-PK', ISO639: 'pbt', ISO3166: 'pk' }, + 'southwestern dinka': { IETF: 'dik-SS', ISO639: 'dik', ISO3166: 'ss' }, + 'spanish argentina': { IETF: 'es-AR', ISO639: 'es', ISO3166: 'ar' }, + 'spanish colombia': { IETF: 'es-CO', ISO639: 'es', ISO3166: 'co' }, + 'spanish latin america': { IETF: 'es-419', ISO639: 'es', ISO3166: 'do' }, + 'spanish mexico': { IETF: 'es-MX', ISO639: 'es', ISO3166: 'mx' }, + 'spanish united states': { IETF: 'es-US', ISO639: 'es', ISO3166: 'es' }, + 'sranan tongo': { IETF: 'srn-SR', ISO639: 'srn', ISO3166: 'sr' }, + 'standard latvian': { IETF: 'lvs-LV', ISO639: 'lvs', ISO3166: 'lv' }, + 'standard malay': { IETF: 'zsm-MY', ISO639: 'zsm', ISO3166: 'my' }, + sundanese: { IETF: 'su-ID', ISO639: 'su', ISO3166: 'id' }, + swahili: { IETF: 'sw-KE', ISO639: 'sw', ISO3166: 'ke' }, + swati: { IETF: 'ss-SZ', ISO639: 'ss', ISO3166: 'sz' }, + swedish: { IETF: 'sv-SE', ISO639: 'sv', ISO3166: 'se' }, + 'swiss german': { IETF: 'de-CH', ISO639: 'de', ISO3166: 'ch' }, + 'syriac (aramaic)': { IETF: 'syc-TR', ISO639: 'syc', ISO3166: 'tr' }, + tagalog: { IETF: 'tl-PH', ISO639: 'tl', ISO3166: 'ph' }, + tahitian: { IETF: 'ty-PF', ISO639: 'ty', ISO3166: 'pf' }, + tajik: { IETF: 'tg-TJ', ISO639: 'tg', ISO3166: 'tj' }, + 'tamashek (tuareg)': { IETF: 'tmh-DZ', ISO639: 'tmh', ISO3166: 'dz' }, + tamasheq: { IETF: 'taq-ML', ISO639: 'taq', ISO3166: 'ml' }, + 'tamil india': { IETF: 'ta-IN', ISO639: 'ta', ISO3166: 'in' }, + 'tamil sri lanka': { IETF: 'ta-LK', ISO639: 'ta', ISO3166: 'lk' }, + taroko: { IETF: 'trv-TW', ISO639: 'trv', ISO3166: 'tw' }, + tatar: { IETF: 'tt-RU', ISO639: 'tt', ISO3166: 'ru' }, + telugu: { IETF: 'te-IN', ISO639: 'te', ISO3166: 'in' }, + tetum: { IETF: 'tet-TL', ISO639: 'tet', ISO3166: 'tl' }, + thai: { IETF: 'th-TH', ISO639: 'th', ISO3166: 'th' }, + tibetan: { IETF: 'bo-CN', ISO639: 'bo', ISO3166: 'cn' }, + tigrinya: { IETF: 'ti-ET', ISO639: 'ti', ISO3166: 'et' }, + 'tok pisin': { IETF: 'tpi-PG', ISO639: 'tpi', ISO3166: 'pg' }, + tokelauan: { IETF: 'tkl-TK', ISO639: 'tkl', ISO3166: 'tk' }, + tongan: { IETF: 'to-TO', ISO639: 'to', ISO3166: 'to' }, + 'tosk albanian': { IETF: 'als-AL', ISO639: 'als', ISO3166: 'al' }, + tsonga: { IETF: 'ts-ZA', ISO639: 'ts', ISO3166: 'za' }, + tswa: { IETF: 'tsc-MZ', ISO639: 'tsc', ISO3166: 'mz' }, + tswana: { IETF: 'tn-BW', ISO639: 'tn', ISO3166: 'bw' }, + tumbuka: { IETF: 'tum-MW', ISO639: 'tum', ISO3166: 'mw' }, + turkmen: { IETF: 'tk-TM', ISO639: 'tk', ISO3166: 'tm' }, + tuvaluan: { IETF: 'tvl-TV', ISO639: 'tvl', ISO3166: 'tv' }, + twi: { IETF: 'tw-GH', ISO639: 'tw', ISO3166: 'gh' }, + udmurt: { IETF: 'udm-RU', ISO639: 'udm', ISO3166: 'xx' }, + ukrainian: { IETF: 'uk-UA', ISO639: 'uk', ISO3166: 'ua' }, + uma: { IETF: 'ppk-ID', ISO639: 'ppk', ISO3166: 'id' }, + umbundu: { IETF: 'umb-AO', ISO639: 'umb', ISO3166: 'ao' }, + 'uyghur uig': { IETF: 'uig-CN', ISO639: 'uig', ISO3166: 'cn' }, + 'uyghur ug': { IETF: 'ug-CN', ISO639: 'ug', ISO3166: 'cn' }, + uzbek: { IETF: 'uz-UZ', ISO639: 'uz', ISO3166: 'uz' }, + venetian: { IETF: 'vec-IT', ISO639: 'vec', ISO3166: 'it' }, + vietnamese: { IETF: 'vi-VN', ISO639: 'vi', ISO3166: 'vn' }, + 'vincentian creole english': { IETF: 'svc-VC', ISO639: 'svc', ISO3166: 'vc' }, + 'virgin islands creole english': { IETF: 'vic-US', ISO639: 'vic', ISO3166: 'vi' }, + wallisian: { IETF: 'wls-WF', ISO639: 'wls', ISO3166: 'wf' }, + 'waray (philippines)': { IETF: 'war-PH', ISO639: 'war', ISO3166: 'ph' }, + welsh: { IETF: 'cy-GB', ISO639: 'cy', ISO3166: 'gb-wls' }, + 'west central oromo': { IETF: 'gaz-ET', ISO639: 'gaz', ISO3166: 'et' }, + 'western persian': { IETF: 'pes-IR', ISO639: 'pes', ISO3166: 'ir' }, + wolof: { IETF: 'wo-SN', ISO639: 'wo', ISO3166: 'sn' }, + xhosa: { IETF: 'xh-ZA', ISO639: 'xh', ISO3166: 'za' }, + yiddish: { IETF: 'yi-YD', ISO639: 'yi', ISO3166: 'il' }, + yoruba: { IETF: 'yo-NG', ISO639: 'yo', ISO3166: 'ng' }, + zulu: { IETF: 'zu-ZA', ISO639: 'zu', ISO3166: 'za' } }; module.exports = { languages }; diff --git a/src/js/logger.js b/src/js/logger.js index d7ea220..60bc0de 100644 --- a/src/js/logger.js +++ b/src/js/logger.js @@ -6,73 +6,75 @@ const path = require('path'); const consoleloggerLevel = process.env.WINSTON_LOGGER_LEVEL || 'info'; const consoleFormat = format.combine( - format.colorize(), - format.timestamp(), - format.align(), - format.printf((info) => `${info.timestamp} - ${info.level}: ${info.message} ${JSON.stringify(info.metadata)}`), + format.colorize(), + format.timestamp(), + format.align(), + format.printf(info => `${info.timestamp} - ${info.level}: ${info.message} ${JSON.stringify(info.metadata)}`) ); const fileFormat = format.combine( - format.timestamp(), - format.metadata({ fillExcept: ['message', 'level', 'timestamp', 'label'] }), - format.json(), + format.timestamp(), + format.metadata({ fillExcept: ['message', 'level', 'timestamp', 'label'] }), + format.json() ); const logger = createLogger({ - level: 'info', - format: fileFormat, - transports: [ - new transports.File({ - filename: path.join(__dirname, '../logs/error.log'), - level: 'error', - }), - new transports.File({ - filename: path.join(__dirname, '../logs/activity.log'), - maxsize: 5242880, - maxFiles: 5, - }), - ], + level: 'info', + format: fileFormat, + transports: [ + new transports.File({ + filename: path.join(__dirname, '../logs/error.log'), + level: 'error' + }), + new transports.File({ + filename: path.join(__dirname, '../logs/activity.log'), + maxsize: 5242880, + maxFiles: 5 + }) + ] }); if (process.env.NODE_ENV !== 'production') { - logger.add( - new transports.Console({ - level: consoleloggerLevel, - format: consoleFormat, - }), - ); + logger.add( + new transports.Console({ + level: consoleloggerLevel, + format: consoleFormat + }) + ); } fetch(path.join(__dirname, '../logs/activity.log')) - .then((response) => response.text()) - .then((logData) => { - const logLines = logData.trim().split('\n'); - const tableBody = document.getElementById('logContent'); + .then(response => response.text()) + .then(logData => { + const logLines = logData.trim().split('\n'); + const tableBody = document.getElementById('logContent'); - logLines.forEach((logLine) => { - const logObject = JSON.parse(logLine); - const row = document.createElement('tr'); + logLines.forEach(logLine => { + const logObject = JSON.parse(logLine); + const row = document.createElement('tr'); - const levelCell = document.createElement('td'); - levelCell.textContent = logObject.level; - levelCell.classList.add(logObject.level); // Add class for styling - row.appendChild(levelCell); + const levelCell = document.createElement('td'); + levelCell.textContent = logObject.level; + levelCell.classList.add(logObject.level); // Add class for styling + row.appendChild(levelCell); - const messageCell = document.createElement('td'); - messageCell.textContent = logObject.message; - row.appendChild(messageCell); + const messageCell = document.createElement('td'); + messageCell.textContent = logObject.message; + row.appendChild(messageCell); - const metadataCell = document.createElement('td'); - metadataCell.textContent = JSON.stringify(logObject.metadata, null, 2); - row.appendChild(metadataCell); + const metadataCell = document.createElement('td'); + metadataCell.textContent = JSON.stringify(logObject.metadata, null, 2); + row.appendChild(metadataCell); - const timestampCell = document.createElement('td'); - timestampCell.textContent = logObject.timestamp; - row.appendChild(timestampCell); + const timestampCell = document.createElement('td'); + timestampCell.textContent = logObject.timestamp; + row.appendChild(timestampCell); - tableBody.appendChild(row); - }); - }) - .catch((error) => {}); + tableBody.appendChild(row); + }); + }) + .catch(error => { + console.error(error); + }); module.exports = logger; diff --git a/src/js/mediaDevices.js b/src/js/mediaDevices.js index ee21c35..d2a1ef8 100644 --- a/src/js/mediaDevices.js +++ b/src/js/mediaDevices.js @@ -1,49 +1,51 @@ -let micSelect = document.querySelector('#microphone'); +/* global settings, */ + +const micSelect = document.querySelector('#microphone'); let selectedMic; function getAvailableMediaDevices(type) { - return new Promise((resolve, reject) => { - navigator.mediaDevices - .enumerateDevices() - .then((devices) => { - const microphones = devices.filter((device) => device.kind === type); - resolve(microphones); - }) - .catch((error) => { - reject(error); - }); - }); + return new Promise((resolve, reject) => { + navigator.mediaDevices + .enumerateDevices() + .then(devices => { + const microphones = devices.filter(device => device.kind === type); + resolve(microphones); + }) + .catch(error => { + reject(error); + }); + }); } // Microphones getAvailableMediaDevices('audioinput') - .then((microphones) => { - let i = 0; - let tempname = ''; - for (let mic of microphones) { - if (mic.deviceId === 'default') { - tempname = mic.label.slice(10); // remove "default -" from the label to get the default device name. - } + .then(microphones => { + let i = 0; + let tempname = ''; + for (const mic of microphones) { + if (mic.deviceId === 'default') { + tempname = mic.label.slice(10); // remove "default -" from the label to get the default device name. + } - if (mic.deviceId === 'communications' || mic.label === tempname) { - continue; - } + if (mic.deviceId === 'communications' || mic.label === tempname) { + continue; + } - const option = document.createElement('option'); + const option = document.createElement('option'); - // Set the options value and text. - option.value = i; - option.innerHTML = `${mic.label}`; + // Set the options value and text. + option.value = i; + option.innerHTML = `${mic.label}`; - // Add the option to the voice selector. - micSelect.appendChild(option); + // Add the option to the voice selector. + micSelect.appendChild(option); - if (i === microphones.length - 1) { - document.getElementById('microphone').value = settings.STT.SELECTED_MICROPHONE; - } - i++; - } - }) - .catch((error) => { - console.error('Error retrieving microphones:', error); - }); + if (i === microphones.length - 1) { + document.getElementById('microphone').value = settings.STT.SELECTED_MICROPHONE; + } + i++; + } + }) + .catch(error => { + console.error('Error retrieving microphones:', error); + }); diff --git a/src/js/messageTemplates.js b/src/js/messageTemplates.js index 10e4eb0..58bf5ec 100644 --- a/src/js/messageTemplates.js +++ b/src/js/messageTemplates.js @@ -1,41 +1,26 @@ const twitchTemplate = ` -
          - - -
          - -
          -
          + + + + +
          `.trim(); const userTemplate = ` -
          - You - - -
          - You -
          -
          + + + + You +
          `.trim(); const messageTemplate = ` -
          -
          - - -
          -
          -
          -
          - You${getPostTime()} -
          -

          - hello there -

          -
          -
          -
          +
          + + + 12:00 PM + You +
          Hello there
          `.trim(); diff --git a/src/js/renderer.js b/src/js/renderer.js index 45f7da7..6ccf41a 100644 --- a/src/js/renderer.js +++ b/src/js/renderer.js @@ -1,14 +1,12 @@ +/* eslint-disable no-unused-vars */ const fs = require('fs'); const ini = require('ini'); const path = require('path'); // get directory path const axios = require('axios'); -const { ipcRenderer, shell } = require('electron'); // necessary electron libraries to send data to the app +const { webFrame, ipcRenderer, shell } = require('electron'); // necessary electron libraries to send data to the app const io = require('socket.io-client'); -const util = require('util'); -const exec = util.promisify(require('child_process').exec); - const GoogleTTS = require('node-google-tts-api'); const tts = new GoogleTTS(); @@ -17,14 +15,20 @@ const { Socket } = require('socket.io-client'); const main = ipcRenderer.sendSync('environment'); const resourcesPath = main.resourcesPath; -let settingsPath = main.settingsPath.toString(); -let pythonPath = main.pythonPath.toString(); +const settingsPath = main.settingsPath.toString(); +const pythonPath = main.pythonPath.toString(); const settings = main.settings; // TODO: remove gooogle voices txt and use api instead const googleVoices = fs.readFileSync(path.join(__dirname, './config/googleVoices.txt')).toString().split('\r\n'); // TODO: remove amazon voices txt and use api instead (sakura project has it) const amazonVoices = fs.readFileSync(path.join(__dirname, './config/amazonVoices.txt')).toString().split('\r\n'); +const twitchEmoteListSavePath = + main.isPackaged === true ? path.join(resourcesPath, './twitch-emotes.json') : path.join(resourcesPath, './config/twitch-emotes.json'); +const betterTtvEmoteListSavePath = + main.isPackaged === true + ? path.join(resourcesPath, './betterttv-emotes.json') + : path.join(resourcesPath, './config/betterttv-emotes.json'); // html elements const root = document.documentElement; @@ -35,6 +39,9 @@ const devicesDropdown = document.querySelector('#devicesDropdown'); const notificationSound = document.querySelector('#notification'); // obtain the html reference of the sound comboBox const sttModel = document.querySelector('#sttModel'); // obtain the html reference of the sound comboBox const ttsAudioDevices = document.querySelector('#ttsAudioDevice'); // obtain the html reference of the installedTTS comboBox +const notificationSoundAudioDevices = document.querySelector('#notificationSoundAudioDevice'); // obtain the html reference of the installedTTS comboBox +const emojiPicker = document.body.querySelector('emoji-picker'); +const lol = document.body.querySelector('country-flag-emoji-polyfill'); // laod local javascript files const chat = require(path.join(__dirname, './js/chat')); @@ -47,18 +54,19 @@ const config = require(path.join(__dirname, './js/settings')); const mediaDevices = require(path.join(__dirname, './js/mediaDevices')); -let notificationSounds = path.join(__dirname, './sounds/notifications'); -let sttModels = path.join(__dirname, '../speech_to_text_models'); +const notificationSounds = path.join(resourcesPath, main.isPackaged ? './sounds/notifications' : '../sounds/notifications'); +const sttModels = path.join(resourcesPath, main.isPackaged ? './speech_to_text_models' : '../speech_to_text_models'); function reset() { - ipcRenderer.send('restart'); + ipcRenderer.send('restart'); } -let server = require(path.join(__dirname, './js/server')); +const server = require(path.join(__dirname, './js/server')); const backend = require(path.join(__dirname, './js/backend')); -let socket = io(`http://localhost:${settings.GENERAL.PORT}`); // Connect to your Socket.IO server +const socket = io(`http://localhost:${settings.GENERAL.PORT}`); // Connect to your Socket.IO server -let twitch = settings.TWITCH.USE_TWITCH ? require(path.join(__dirname, './js/twitch')) : ''; +let twitch = null; +twitch = settings.TWITCH.USE_TWITCH ? require(path.join(__dirname, './js/twitch')) : ''; const Polly = settings.AMAZON.USE_AMAZON ? require(path.join(__dirname, './js/amazon')) : ''; const google = settings.GOOGLE.USE_GOOGLE ? require(path.join(__dirname, './js/google')) : ''; @@ -66,6 +74,11 @@ const theme = require(path.join(__dirname, './js/theme')); const auth = require(path.join(__dirname, './js/auth')); let ttsRequestCount = 0; +ttsRequestCount = 0; +let customEmojis = []; +customEmojis = []; +let messageId = 0; +messageId = 0; // initialize values config.getGeneralSettings(); @@ -77,179 +90,347 @@ const StartDateAndTime = Date.now(); const speakButton = document.querySelector('#speakBtn'); const amazonCredentials = { - accessKeyId: settings.AMAZON.ACCESS_KEY, - secretAccessKey: settings.AMAZON.ACCESS_SECRET, + accessKeyId: settings.AMAZON.ACCESS_KEY, + secretAccessKey: settings.AMAZON.ACCESS_SECRET }; // Check for installed sounds fs.readdir(notificationSounds, (err, files) => { - files.forEach((file, i) => { - // Create a new option element. - const option = document.createElement('option'); + if (err) { + console.error(err); + } - // Set the options value and text. - option.value = i; - option.innerHTML = file; + files.forEach((file, i) => { + // Create a new option element. + const option = document.createElement('option'); - // Add the option to the sound selector. - notificationSound.appendChild(option); - }); + // Set the options value and text. + option.value = i; + option.innerHTML = file; - // set the saved notification sound - notificationSound.selectedIndex = settings.AUDIO.NOTIFICATION_SOUND; + // Add the option to the sound selector. + notificationSound.appendChild(option); + }); + + // set the saved notification sound + notificationSound.selectedIndex = settings.AUDIO.NOTIFICATION_SOUND; }); // Check for installed stt models fs.readdir(sttModels, (err, files) => { - for (let file of files) { - if (file.includes('.txt')) { - continue; - } - // Create a new option element. - const option = document.createElement('option'); + if (err) { + console.error(err); + } - // Set the options value and text. - option.value = file; - option.innerHTML = file; - - // Add the option to the sound selector. - sttModel.appendChild(option); + for (const file of files) { + if (file.includes('.txt')) { + continue; } + // Create a new option element. + const option = document.createElement('option'); - // set the saved notification sound - sttModel.value = settings.STT.LANGUAGE; + // Set the options value and text. + option.value = file; + option.innerHTML = file; + + // Add the option to the sound selector. + sttModel.appendChild(option); + } + + // set the saved notification sound + sttModel.value = settings.STT.LANGUAGE; }); +// TODO: refactor obtaining audio devices. async function getAudioDevices() { - if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) { - return; - } + if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) { + return; + } - const devices = await navigator.mediaDevices.enumerateDevices(); - const audioOutputDevices = devices.filter((device) => device.kind === 'audiooutput'); + const devices = await navigator.mediaDevices.enumerateDevices(); + const audioOutputDevices = devices.filter(device => device.kind === 'audiooutput'); - audioOutputDevices.forEach((device) => { - const option = document.createElement('option'); - option.text = device.label || `Output ${device.deviceId}`; - option.value = device.deviceId; - ttsAudioDevices.appendChild(option); - }); + audioOutputDevices.forEach(device => { + const option1 = document.createElement('option'); + const option2 = document.createElement('option'); + option1.text = device.label || `Output ${device.deviceId}`; + option2.text = device.label || `Output ${device.deviceId}`; + option1.value = device.deviceId; + option2.value = device.deviceId; + ttsAudioDevices.appendChild(option1); + notificationSoundAudioDevices.appendChild(option2); + }); - ttsAudioDevices.selectedIndex = settings.AUDIO.SELECTED_TTS_AUDIO_DEVICE; + ttsAudioDevices.selectedIndex = settings.AUDIO.SELECTED_TTS_AUDIO_DEVICE; + notificationSoundAudioDevices.selectedIndex = settings.AUDIO.SELECTED_NOTIFICATION_AUDIO_DEVICE; } getAudioDevices(); -function setLanguagesinSelect(languageSelector, setting) { - let languageSelect = document.querySelector(languageSelector); // obtain the html reference of the google voices comboBox - - for (const language in languageObject.languages) { - if (languageObject.languages.hasOwnProperty(language)) { - const iso639 = languageObject.languages[language]['ISO-639']; - const option = document.createElement('option'); - option.value = iso639; - option.innerHTML = `${iso639} - ${language}`; - languageSelect.appendChild(option); - } - } - - languageSelect.selectedIndex = setting; +function setSelectedLanguageinSelect(languageSelect, language) { + const button = languageSelect.querySelector('.SmallButton'); + const languageElement = document.createElement('span'); + languageElement.classList = `fi fi-${language.ISO3166} fis pop-selection`; + languageElement.setAttribute('tip', language.name); + button.innerHTML = ''; + button.appendChild(languageElement); + addSingleTooltip(languageElement); } -setLanguagesinSelect('#language', settings.GENERAL.LANGUAGE); +function setLanguagesinSelectx(languageSelector, language) { + const languageSelect = document.querySelector(languageSelector); // obtain the html reference of the google voices comboBox + const languageSelectContent = languageSelect.querySelector('.pop-content'); + + languageSelectContent.addEventListener('click', e => { + const parent = e.target.parentElement.id; + language = getLanguageProperties(e.target.getAttribute('value')); + + if (parent === 'SEND_TRANSLATION_IN') { + settings.LANGUAGE.SEND_TRANSLATION_IN = language.IETF; + } else { + settings.LANGUAGE.SEND_TRANSLATION_OUT = language.IETF; + } + + fs.writeFileSync(settingsPath, ini.stringify(settings)); + setSelectedLanguageinSelect(languageSelect, language); + }); + + for (const language in languageObject.languages) { + if (Object.prototype.hasOwnProperty.call(languageObject.languages, language)) { + const IETF = languageObject.languages[language].IETF; + const ISO639 = languageObject.languages[language].ISO639; + const ISO3166 = languageObject.languages[language].ISO3166; + + const option = document.createElement('div'); + option.classList = 'language-select'; + + const languageElement = document.createElement('span'); + languageElement.classList = `fi fi-${ISO3166} fis`; + languageElement.style.pointerEvents = 'none'; + option.setAttribute('tip', language); + + const text = document.createElement('span'); + text.style.pointerEvents = 'none'; + text.innerHTML = ` - ${ISO639}`; + + option.setAttribute('value', IETF); + + languageSelectContent.appendChild(option); + option.appendChild(languageElement); + option.appendChild(text); + addSingleTooltip(option); + } + } + + setSelectedLanguageinSelect(languageSelect, language); +} + +setLanguagesinSelectx('.pop.in', getLanguageProperties(settings.LANGUAGE.SEND_TRANSLATION_IN)); +setLanguagesinSelectx('.pop.out', getLanguageProperties(settings.LANGUAGE.SEND_TRANSLATION_OUT)); + +function setLanguagesinSelect(languageSelector, setting) { + const languageSelect = document.querySelector(languageSelector); // obtain the html reference of the google voices comboBox + + for (const language in languageObject.languages) { + if (Object.prototype.hasOwnProperty.call(languageObject.languages, language)) { + const IETF = languageObject.languages[language].IETF; + const ISO639 = languageObject.languages[language].ISO639; + const option = document.createElement('option'); + + option.value = IETF; + option.innerHTML = `${ISO639} : ${language}`; + languageSelect.appendChild(option); + } + } + + languageSelect.selectedIndex = setting; +} + +setLanguagesinSelect('#language', settings.GENERAL.LANGUAGE_INDEX); setLanguagesinSelect('#defaultLanguage', settings.TTS.PRIMARY_TTS_LANGUAGE_INDEX); setLanguagesinSelect('#secondaryLanguage', settings.TTS.SECONDARY_TTS_LANGUAGE_INDEX); +setLanguagesinSelect('#TRANSLATE_TO', settings.LANGUAGE.TRANSLATE_TO_INDEX); function addVoiceService(name) { - function addToselect(select) { - let ttsService = document.querySelector(select); - const option = document.createElement('option'); - ttsService.appendChild(option); + function addToselect(select) { + const ttsService = document.querySelector(select); + const option = document.createElement('option'); + ttsService.appendChild(option); - option.value = name; - option.innerHTML = name; - } - addToselect('#primaryTTSService'); - addToselect('#secondaryTTSService'); + option.value = name; + option.innerHTML = name; + } + addToselect('#primaryTTSService'); + addToselect('#secondaryTTSService'); +} + +function determineTootlTipPosition(element) { + const horizontal = document.body.clientWidth / 2; + const vertical = document.body.clientHeight / 2; + + element.tip.style.left = `${element.mouse.x}px`; + element.tip.style.top = `${element.mouse.y}px`; + + const tipPosition = element.tip.getBoundingClientRect(); + + if (element.position.x < horizontal && element.position.y < vertical) { + element.tip.style.top = `${parseInt(element.tip.style.top) + 25}px`; + element.tip.style.left = `${parseInt(element.tip.style.left) + 10}px`; + } + + if (element.position.x < horizontal && element.position.y > vertical) { + element.tip.style.top = `${parseInt(element.tip.style.top) - tipPosition.height}px`; + element.tip.style.left = `${parseInt(element.tip.style.left) + 10}px`; + } + + if (element.position.x > horizontal && element.position.y < vertical) { + element.tip.style.top = `${parseInt(element.tip.style.top) + 25}px`; + element.tip.style.left = `${parseInt(element.tip.style.left) - tipPosition.width}px`; + } + + if (element.position.x > horizontal && element.position.y > vertical) { + element.tip.style.top = `${parseInt(element.tip.style.top) - tipPosition.height}px`; + element.tip.style.left = `${parseInt(element.tip.style.left) - tipPosition.width}px`; + } + + element.tip.style.visibility = 'visible'; } // Small tooltip -Array.from(document.body.querySelectorAll('[tip]')).forEach((el) => { - const tip = document.createElement('div'); - const body = document.querySelector('.container'); - const element = el; - tip.classList.add('tooltip'); - tip.classList.add('tooltiptext'); - tip.innerText = el.getAttribute('tip'); - tip.style.transform = `translate(${el.hasAttribute('tip-left') ? 'calc(-100% - 5px)' : '15px'}, ${ - el.hasAttribute('tip-top') ? '-100%' : '15px' - })`; - body.appendChild(tip); - element.onmousemove = (e) => { - tip.style.left = `${e.x}px`; - tip.style.top = `${e.y}px`; - tip.style.zIndex = 1; - tip.style.visibility = 'visible'; - }; - element.onmouseleave = (e) => { - tip.style.visibility = 'hidden'; - }; +function addSingleTooltip(el) { + const tip = document.createElement('div'); + const body = document.querySelector('.container'); + const element = el; + tip.classList.add('tooltip'); + tip.innerText = el.getAttribute('tip'); + if (el.src) { + const image = document.createElement('img'); + image.src = el.src; + tip.appendChild(image); + } + body.appendChild(tip); + tip.pointerEvents = 'none'; + element.onmousemove = e => { + determineTootlTipPosition({ + position: element.getBoundingClientRect(), + mouse: { x: e.x, y: e.y }, + tip + }); + }; + element.onmouseleave = e => { + tip.style.visibility = 'hidden'; + }; +} + +Array.from(document.body.querySelectorAll('[tip]')).forEach(el => { + addSingleTooltip(el); }); -function showChatMessage(article, isUser) { +function showChatMessage(article) { + if (article !== undefined) { document.querySelector('#chatBox').appendChild(article); - let usernameHtml; - let msg; - let messages = Array.from(document.body.querySelectorAll('.msg-container')); + } - if (isUser) { - usernameHtml = article.querySelector('.username-user'); - msg = article.querySelector('.msg-box-user'); - } else { - usernameHtml = article.querySelector('.username'); - msg = article.querySelector('.msg-box'); - } + const messages = document.body.querySelectorAll('.msg-container'); - var style = getComputedStyle(usernameHtml); - var style2 = getComputedStyle(usernameHtml); - - const lastMessage = messages[messages.length - 1]; - lastMessage.scrollIntoView({ behavior: 'smooth' }); + const lastMessage = messages[messages.length - 1]; + lastMessage.scrollIntoView({ block: 'end', behavior: 'smooth' }); } function getPostTime() { - const date = new Date(); - document.body.querySelectorAll('.container').innerHTML = date.getHours(); - const hours = date.getHours(); - var ampm = hours >= 12 ? 'PM' : 'AM'; - const minutes = (date.getMinutes() < 10 ? '0' : '') + date.getMinutes(); - const time = `${hours}:${minutes} ${ampm}`; + const date = new Date(); + document.body.querySelectorAll('.container').innerHTML = date.getHours(); + const hours = date.getHours(); + const ampm = hours >= 12 ? 'PM' : 'AM'; + const minutes = (date.getMinutes() < 10 ? '0' : '') + date.getMinutes(); + const time = `${hours}:${minutes} ${ampm}`; - return time; + return time; } function showPreviewChatMessage() { - const message = messageTemplates.messageTemplate; - document.querySelector('#mini-mid').innerHTML += message; - const messages = Array.from(document.body.querySelectorAll('#mini-mid')); - const lastMessage = messages[messages.length - 1]; - lastMessage.scrollIntoView({ behavior: 'smooth' }); + const message = messageTemplates.messageTemplate; + document.querySelector('#mini-mid').innerHTML += message; + const messages = Array.from(document.body.querySelectorAll('#mini-mid')); + const lastMessage = messages[messages.length - 1]; + lastMessage.scrollIntoView({ behavior: 'smooth' }); } showPreviewChatMessage(); function hideText(button, field) { - document.body.querySelector(button).addEventListener('click', () => { - const passwordInput = document.querySelector(field); - if (passwordInput.type === 'password') { - passwordInput.type = 'lol'; - } else { - passwordInput.type = 'password'; - } - }); + document.body.querySelector(button).addEventListener('click', () => { + const passwordInput = document.querySelector(field); + if (passwordInput.type === 'password') { + passwordInput.type = 'lol'; + } else { + passwordInput.type = 'password'; + } + }); } hideText('.password-toggle-btn1', '#TWITCH_OAUTH_TOKEN'); hideText('.password-toggle-btn4', '#AMAZON_ACCESS_KEY'); hideText('.password-toggle-btn5', '#AMAZON_ACCESS_SECRET'); hideText('.password-toggle-btn6', '#GOOGLE_API_KEY'); + +function setZoomLevel(currentZoom, zoomIn) { + let newZoom = currentZoom.toFixed(2); + + if (zoomIn === true && currentZoom < 4.95) { + newZoom = (currentZoom + 0.05).toFixed(2); + } + if (zoomIn === false && currentZoom > 0.25) { + newZoom = (currentZoom - 0.05).toFixed(2); + } + + webFrame.setZoomFactor(parseFloat(newZoom)); + settings.GENERAL.ZOOMLEVEL = newZoom; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + document.body.querySelector('#ZOOMLEVEL').value = (settings.GENERAL.ZOOMLEVEL * 100).toFixed(0); +} + +// TODO: refactor +let twitchEmotes = null; +if (fs.existsSync(twitchEmoteListSavePath)) { + const xxx = fs.readFileSync(twitchEmoteListSavePath); + twitchEmotes = JSON.parse(xxx); + emojiPicker.customEmoji = [...twitchEmotes]; +} +let betterTtvEmotes = null; +if (fs.existsSync(betterTtvEmoteListSavePath)) { + const xxx = fs.readFileSync(betterTtvEmoteListSavePath); + betterTtvEmotes = JSON.parse(xxx); + emojiPicker.customEmoji = [...betterTtvEmotes]; +} + +if (twitchEmotes && betterTtvEmotes) { + emojiPicker.customEmoji = [...twitchEmotes, ...betterTtvEmotes]; +} + +function getLanguageProperties(languageToDetect) { + try { + const filteredLanguage = Object.keys(languageObject.languages).reduce(function (accumulator, currentValue) { + if ( + languageObject.languages[currentValue].IETF === languageToDetect || + languageObject.languages[currentValue].ISO639 === languageToDetect || + languageObject.languages[currentValue].ISO3166 === languageToDetect + ) { + accumulator[currentValue] = languageObject.languages[currentValue]; + } + return accumulator; + }, {}); + + const language = { + name: Object.getOwnPropertyNames(filteredLanguage)[0], + ISO3166: filteredLanguage[Object.keys(filteredLanguage)[0]].ISO3166, + ISO639: filteredLanguage[Object.keys(filteredLanguage)[0]].ISO639, + IETF: filteredLanguage[Object.keys(filteredLanguage)[0]].IETF + }; + + return language; + } catch (e) { + // console.error(error); + return 'error'; + } +} diff --git a/src/js/responses.js b/src/js/responses.js index 4c7c082..49bd1a2 100644 --- a/src/js/responses.js +++ b/src/js/responses.js @@ -1,18 +1,21 @@ function getBotResponse(input) { - // rock paper scissors - if (input === 'rock') { - return 'paper'; - } if (input === 'paper') { - return 'scissors'; - } if (input === 'scissors') { - return 'rock'; - } + // rock paper scissors + if (input === 'rock') { + return 'paper'; + } + if (input === 'paper') { + return 'scissors'; + } + if (input === 'scissors') { + return 'rock'; + } - // Simple responses - if (input === 'hello') { - return 'Hello there!'; - } if (input === 'goodbye') { - return 'Talk to you later!'; - } - return 'Try asking something else!'; + // Simple responses + if (input === 'hello') { + return 'Hello there!'; + } + if (input === 'goodbye') { + return 'Talk to you later!'; + } + return 'Try asking something else!'; } diff --git a/src/js/server.js b/src/js/server.js index 97d9ff8..772a946 100644 --- a/src/js/server.js +++ b/src/js/server.js @@ -1,3 +1,5 @@ +/* global settings */ + const express = require('express'); const app = express(); const path = require('path'); @@ -5,42 +7,42 @@ const http = require('http'); const localServer = http.createServer(app); const io = require('socket.io')(localServer); -let requestCount = 0; +const requestCount = 0; function startVtuberModule() { - if (!settings.MODULES.USE_VTUBER) { - return; - } + if (!settings.MODULES.USE_VTUBER) { + return; + } - app.use('/vtuber', express.static(path.join(__dirname, '../modules/vtuber/'))); + app.use('/vtuber', express.static(path.join(__dirname, '../modules/vtuber/'))); - let vtuber = document.body.querySelector('#BrowsersourceVtuber'); - let vtuberframe = document.createElement('iframe'); - vtuberframe.class = 'frame'; - vtuberframe.src = `http://localhost:${settings.GENERAL.PORT}/vtuber`; - vtuberframe.style.width = '100%'; - vtuberframe.style.height = '100%'; - vtuberframe.frameBorder = 0; - vtuber.appendChild(vtuberframe); + const vtuber = document.body.querySelector('#BrowsersourceVtuber'); + const vtuberframe = document.createElement('iframe'); + vtuberframe.class = 'frame'; + vtuberframe.src = `http://localhost:${settings.GENERAL.PORT}/vtuber`; + vtuberframe.style.width = '100%'; + vtuberframe.style.height = '100%'; + vtuberframe.frameBorder = 0; + vtuber.appendChild(vtuberframe); } startVtuberModule(); function startChatBubbleModule() { - if (!settings.MODULES.USE_CHATBUBBLE) { - return; - } + if (!settings.MODULES.USE_CHATBUBBLE) { + return; + } - app.use('/chat', express.static(path.join(__dirname, '../modules/chat'))); + app.use('/chat', express.static(path.join(__dirname, '../modules/chat'))); - let chat = document.body.querySelector('#BrowsersourceChat'); - let chatframe = document.createElement('iframe'); - chatframe.class = 'frame'; - chatframe.src = `http://localhost:${settings.GENERAL.PORT}/chat`; - chatframe.style.width = '100%'; - chatframe.style.height = '100%'; - chatframe.frameBorder = 0; - chat.appendChild(chatframe); + const chat = document.body.querySelector('#BrowsersourceChat'); + const chatframe = document.createElement('iframe'); + chatframe.class = 'frame'; + chatframe.src = `http://localhost:${settings.GENERAL.PORT}/chat`; + chatframe.style.width = '100%'; + chatframe.style.height = '100%'; + chatframe.frameBorder = 0; + chat.appendChild(chatframe); } startChatBubbleModule(); @@ -49,34 +51,31 @@ function startSTT() {} // Middleware to conditionally serve routes app.use((req, res, next) => { - if (!settings.MODULES.USE_VTUBER && req.path === '/vtuber') { - res.sendStatus(404); // Return a 404 status for /vtuber when it's disabled - } else if (!settings.MODULES.USE_CHATBUBBLE && req.path === '/chat') { - res.sendStatus(404); // Return a 404 status for /chat when it's disabled - } else { - next(); // Proceed to the next middleware or route handler - } + if (!settings.MODULES.USE_VTUBER && req.path === '/vtuber') { + res.sendStatus(404); // Return a 404 status for /vtuber when it's disabled + } else if (!settings.MODULES.USE_CHATBUBBLE && req.path === '/chat') { + res.sendStatus(404); // Return a 404 status for /chat when it's disabled + } else { + next(); // Proceed to the next middleware or route handler + } }); localServer.listen(settings.GENERAL.PORT, () => { - startVtuberModule(); - startChatBubbleModule(); - - if (settings.TTS.USE_TTS) { - } + startVtuberModule(); + startChatBubbleModule(); }); // Handle socket connections -io.on('connection', (socket) => { - // Receive data from the client - socket.on('message', (data) => {}); +io.on('connection', socket => { + // Receive data from the client + socket.on('message', data => {}); - // Receive data from the client - socket.on('xxx', (logoUrl, username, message) => { - socket.broadcast.emit('message', logoUrl, username, message); - }); + // Receive data from the client + socket.on('xxx', (logoUrl, username, message) => { + socket.broadcast.emit('message', logoUrl, username, message); + }); - socket.on('disconnect', () => {}); + socket.on('disconnect', () => {}); }); module.exports = { startVtuberModule, startChatBubbleModule }; diff --git a/src/js/settings.js b/src/js/settings.js index 3e57ed7..ea58ce5 100644 --- a/src/js/settings.js +++ b/src/js/settings.js @@ -1,600 +1,792 @@ +/* global settings,main sttModels, setZoomLevel, webFrame, theme, fs, settingsPath, ini, startVoiceRecognition,notificationSoundAudioDevices, ttsAudioDevices, notificationSound, path, resourcesPath, ipcRenderer, auth, shell, sound, twitch, server, backend */ + function getGeneralSettings() { - // General - document.body.querySelector('#PORT').value = settings.GENERAL.PORT; + // General + document.body.querySelector('#PORT').value = settings.GENERAL.PORT; + document.body.querySelector('#ZOOMLEVEL').value = settings.GENERAL.ZOOMLEVEL * 100; + webFrame.setZoomFactor(parseFloat(settings.GENERAL.ZOOMLEVEL)); + // Theme + document.querySelector('#USE_CUSTOM_THEME').value = settings.THEME.USE_CUSTOM_THEME; + document.body.querySelector('#USE_CUSTOM_THEME').checked = settings.THEME.USE_CUSTOM_THEME === true ? 1 : 0; + theme.setTheme(); - // Theme - document.querySelector('#USE_CUSTOM_THEME').value = settings.THEME.USE_CUSTOM_THEME; - document.body.querySelector('#USE_CUSTOM_THEME').checked = settings.THEME.USE_CUSTOM_THEME === true ? 1 : 0; - theme.setTheme(); + // STT + document.body.querySelector('#USE_STT').checked = settings.STT.USE_STT; - // STT - document.body.querySelector('#USE_STT').checked = settings.STT.USE_STT; + // Language detection + document.body.querySelector('#USE_DETECTION').checked = settings.LANGUAGE.USE_DETECTION; + document.body.querySelector('#OUTPUT_TO_TTS').checked = settings.LANGUAGE.OUTPUT_TO_TTS; + document.body.querySelector('#SEND_TRANSLATION').checked = settings.LANGUAGE.SEND_TRANSLATION; + document.body.querySelector('#BROADCAST_TRANSLATION').checked = settings.LANGUAGE.BROADCAST_TRANSLATION; - // Language detection - document.body.querySelector('#USE_DETECTION').checked = settings.LANGUAGE.USE_DETECTION; + // TTS + document.body.querySelector('#USE_TTS').checked = settings.TTS.USE_TTS; - // TTS - document.body.querySelector('#USE_TTS').checked = settings.TTS.USE_TTS; + // Notification sounds + document.body.querySelector('#USE_NOTIFICATION_SOUNDS').checked = settings.AUDIO.USE_NOTIFICATION_SOUNDS; - // Notification sounds - document.body.querySelector('#USE_NOTIFICATION_SOUNDS').checked = settings.AUDIO.USE_NOTIFICATION_SOUNDS; + // Twitch + document.body.querySelector('#USE_TWITCH').checked = settings.TWITCH.USE_TWITCH; + document.body.querySelector('#TWITCH_CHANNEL_NAME').value = settings.TWITCH.CHANNEL_NAME; + document.body.querySelector('#TWITCH_OAUTH_TOKEN').value = settings.TWITCH.OAUTH_TOKEN; - // Twitch - document.body.querySelector('#USE_TWITCH').checked = settings.TWITCH.USE_TWITCH; - document.body.querySelector('#TWITCH_CHANNEL_NAME').value = settings.TWITCH.CHANNEL_NAME; - document.body.querySelector('#TWITCH_OAUTH_TOKEN').value = settings.TWITCH.OAUTH_TOKEN; + // Modules + document.body.querySelector('#USE_MODULES').checked = settings.MODULES.USE_MODULES; + document.body.querySelector('#USE_VTUBER').checked = settings.MODULES.USE_VTUBER; + document.body.querySelector('#VTUBER_URL').value = `http://localhost:${settings.GENERAL.PORT}/vtuber/`; + showMenuButton('#btnBrowsersourceVtuber', settings.MODULES.USE_VTUBER); + document.body.querySelector('#USE_CHATBUBBLE').checked = settings.MODULES.USE_CHATBUBBLE; + document.body.querySelector('#CHATBUBBLE_URL').value = `http://localhost:${settings.GENERAL.PORT}/chat/`; + showMenuButton('#btnBrowsersourceChat', settings.GENERAL.USE_CHATBUBBLE); - // Modules - document.body.querySelector('#USE_MODULES').checked = settings.MODULES.USE_MODULES; - document.body.querySelector('#USE_VTUBER').checked = settings.MODULES.USE_VTUBER; - document.body.querySelector('#VTUBER_URL').value = `http://localhost:${settings.GENERAL.PORT}/vtuber/`; - showMenuButton('#btnBrowsersourceVtuber', settings.MODULES.USE_VTUBER); - document.body.querySelector('#USE_CHATBUBBLE').checked = settings.MODULES.USE_CHATBUBBLE; - document.body.querySelector('#CHATBUBBLE_URL').value = `http://localhost:${settings.GENERAL.PORT}/chat/`; - showMenuButton('#btnBrowsersourceChat', settings.GENERAL.USE_CHATBUBBLE); + // Amazon + document.body.querySelector('#USE_AMAZON').checked = settings.AMAZON.USE_AMAZON; + document.body.querySelector('#AMAZON_ACCESS_KEY').value = settings.AMAZON.ACCESS_KEY; + document.body.querySelector('#AMAZON_ACCESS_SECRET').value = settings.AMAZON.ACCESS_SECRET; - // Amazon - document.body.querySelector('#USE_AMAZON').checked = settings.AMAZON.USE_AMAZON; - document.body.querySelector('#AMAZON_ACCESS_KEY').value = settings.AMAZON.ACCESS_KEY; - document.body.querySelector('#AMAZON_ACCESS_SECRET').value = settings.AMAZON.ACCESS_SECRET; - - // Google - document.body.querySelector('#USE_GOOGLE').checked = settings.GOOGLE.USE_GOOGLE; - document.body.querySelector('#GOOGLE_API_KEY').value = settings.GOOGLE.API_KEY; + // Google + document.body.querySelector('#USE_GOOGLE').checked = settings.GOOGLE.USE_GOOGLE; + document.body.querySelector('#GOOGLE_API_KEY').value = settings.GOOGLE.API_KEY; } document.body.querySelector('#primaryAmazonVoice').addEventListener('change', () => { - var select = document.querySelector('#primaryAmazonVoice'); - settings.AMAZON.PRIMARY_VOICE = select.value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - createNotification('Saved Amazon primary voice!', 'success'); + const select = document.querySelector('#primaryAmazonVoice'); + settings.AMAZON.PRIMARY_VOICE = select.value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + createNotification('Saved Amazon primary voice!', 'success'); }); document.body.querySelector('#secondaryAmazonVoice').addEventListener('change', () => { - var select = document.querySelector('#secondaryAmazonVoice'); - settings.AMAZON.SECONDARY_VOICE = select.value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - createNotification('Saved Amazon secondary voice!', 'success'); + const select = document.querySelector('#secondaryAmazonVoice'); + settings.AMAZON.SECONDARY_VOICE = select.value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + createNotification('Saved Amazon secondary voice!', 'success'); }); document.body.querySelector('#primaryGoogleVoice').addEventListener('change', () => { - var select = document.querySelector('#primaryGoogleVoice'); - settings.GOOGLE.PRIMARY_VOICE = select.value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - createNotification('Saved Google primary voice!', 'success'); + const select = document.querySelector('#primaryGoogleVoice'); + settings.GOOGLE.PRIMARY_VOICE = select.value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + createNotification('Saved Google primary voice!', 'success'); }); document.body.querySelector('#secondaryGoogleVoice').addEventListener('change', () => { - var select = document.querySelector('#secondaryGoogleVoice'); - settings.GOOGLE.SECONDARY_VOICE = select.value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - createNotification('Saved Google secondary voice!', 'success'); + const select = document.querySelector('#secondaryGoogleVoice'); + settings.GOOGLE.SECONDARY_VOICE = select.value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + createNotification('Saved Google secondary voice!', 'success'); }); document.body.querySelector('#primaryVoice').addEventListener('change', () => { - var select = document.querySelector('#primaryVoice'); - settings.TTS.PRIMARY_VOICE = select.value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - createNotification('Saved primary voice!', 'success'); + const select = document.querySelector('#primaryVoice'); + settings.TTS.PRIMARY_VOICE = select.value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + createNotification('Saved primary voice!', 'success'); }); document.body.querySelector('#microphone').addEventListener('change', () => { - var select = document.querySelector('#microphone'); - settings.STT.MICROPHONE = select.value; - settings.STT.MICROPHONE_ID = select.options[select.selectedIndex].text; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - createNotification('Saved microphone!', 'success'); - startVoiceRecognition(); + const select = document.querySelector('#microphone'); + settings.STT.MICROPHONE = select.value; + settings.STT.MICROPHONE_ID = select.options[select.selectedIndex].text; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + createNotification('Saved microphone!', 'success'); + startVoiceRecognition(); }); document.body.querySelector('#sttModel').addEventListener('change', () => { - var select = document.querySelector('#sttModel'); - settings.STT.LANGUAGE = select.value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - createNotification('Saved voice detection language!', 'success'); + const select = document.querySelector('#sttModel'); + settings.STT.LANGUAGE = select.value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + createNotification('Saved voice detection language!', 'success'); }); document.body.querySelector('#defaultLanguage').addEventListener('change', () => { - var select = document.querySelector('#defaultLanguage'); - settings.TTS.PRIMARY_TTS_LANGUAGE_INDEX = select.selectedIndex; - settings.TTS.PRIMARY_TTS_LANGUAGE = select.options[select.selectedIndex].text; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - createNotification('Saved default language!', 'success'); + const select = document.querySelector('#defaultLanguage'); + settings.TTS.PRIMARY_TTS_LANGUAGE_INDEX = select.selectedIndex; + settings.TTS.PRIMARY_TTS_LANGUAGE = select.options[select.selectedIndex].value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + createNotification('Saved default language!', 'success'); }); document.body.querySelector('#secondaryVoice').addEventListener('change', () => { - var select = document.querySelector('#secondaryVoice'); - settings.TTS.SECONDARY_VOICE = select.value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - createNotification('Saved secondary voice!', 'success'); + const select = document.querySelector('#secondaryVoice'); + settings.TTS.SECONDARY_VOICE = select.value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + createNotification('Saved secondary voice!', 'success'); }); document.body.querySelector('#secondaryLanguage').addEventListener('change', () => { - var select = document.querySelector('#secondaryLanguage'); - settings.TTS.SECONDARY_TTS_LANGUAGE_INDEX = select.selectedIndex; - settings.TTS.SECONDARY_TTS_LANGUAGE = select.options[select.selectedIndex].text; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - createNotification('Saved secondary language!', 'success'); + const select = document.querySelector('#secondaryLanguage'); + settings.TTS.SECONDARY_TTS_LANGUAGE_INDEX = select.selectedIndex; + settings.TTS.SECONDARY_TTS_LANGUAGE = select.options[select.selectedIndex].value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + createNotification('Saved secondary language!', 'success'); +}); + +document.body.querySelector('#language').addEventListener('change', () => { + const select = document.querySelector('#language'); + settings.GENERAL.LANGUAGE_INDEX = select.selectedIndex; + settings.GENERAL.LANGUAGE = select.options[select.selectedIndex].value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + createNotification('Saved language!', 'success'); +}); + +function setTranslateToOptions() { + const options = document.querySelectorAll('.TRANSLATE_TO'); + const index = parseInt(settings.LANGUAGE.TRANSLATE_TO_INDEX); + + if (index === 0) { + settings.LANGUAGE.BROADCAST_TRANSLATION = false; + settings.LANGUAGE.OUTPUT_TO_TTS = false; + options.forEach(item => { + item.style.visibility = 'hidden'; + item.style.height = '0px'; + item.checked = false; + }); + } else { + options.forEach(item => { + item.style.visibility = ''; + item.style.height = ''; + }); + } +} + +setTranslateToOptions(); + +document.body.querySelector('#TRANSLATE_TO').addEventListener('change', () => { + const select = document.querySelector('#TRANSLATE_TO'); + settings.LANGUAGE.TRANSLATE_TO_INDEX = select.selectedIndex; + settings.LANGUAGE.TRANSLATE_TO = select.options[select.selectedIndex].value; + setTranslateToOptions(); + + fs.writeFileSync(settingsPath, ini.stringify(settings)); + createNotification('Saved primary voice!', 'success'); }); document.body.querySelector('#ttsAudioDevice').addEventListener('change', () => { - settings.AUDIO.TTS_AUDIO_DEVICE = ttsAudioDevices.value; - settings.AUDIO.SELECTED_TTS_AUDIO_DEVICE = ttsAudioDevices.selectedIndex; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - createNotification('Saved audio device!', 'success'); + settings.AUDIO.TTS_AUDIO_DEVICE = ttsAudioDevices.value; + settings.AUDIO.SELECTED_TTS_AUDIO_DEVICE = ttsAudioDevices.selectedIndex; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + createNotification('Saved audio device!', 'success'); +}); + +document.body.querySelector('#notificationSoundAudioDevice').addEventListener('change', () => { + settings.AUDIO.SELECTED_NOTIFICATION_AUDIO_DEVICE = notificationSoundAudioDevices.value; + settings.AUDIO.NOTIFICATION_AUDIO_DEVICE = notificationSoundAudioDevices.selectedIndex; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + createNotification('Saved audio device!', 'success'); }); document.body.querySelector('#TWITCH_CHANNEL_NAME').addEventListener('change', () => { - settings.TWITCH.CHANNEL_NAME = document.body.querySelector('#TWITCH_CHANNEL_NAME').value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - - let button = document.body.querySelector('#TestTwitchCredentials'); - button.className = 'AdvancedMenuButton'; - createNotification('Saved Channel name, please restart the application to reset twitch service', 'warning'); + settings.TWITCH.CHANNEL_NAME = document.body.querySelector('#TWITCH_CHANNEL_NAME').value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + createNotification('Saved Channel name, please restart the application to reset twitch service', 'warning'); + twitch.getTwitchChannelId(); }); document.body.querySelector('#TWITCH_OAUTH_TOKEN').addEventListener('change', () => { - settings.TWITCH.OAUTH_TOKEN = document.body.querySelector('#TWITCH_OAUTH_TOKEN').value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - createNotification('Saved OAuth token!', 'success'); - - let button = document.body.querySelector('#TestTwitchCredentials'); - button.className = 'AdvancedMenuButton'; - createNotification('Saved OAuth token, please restart the application to reset twitch service', 'warning'); + settings.TWITCH.OAUTH_TOKEN = document.body.querySelector('#TWITCH_OAUTH_TOKEN').value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + createNotification('Saved OAuth token, please restart the application to reset twitch service', 'warning'); }); +setInputFilter( + document.body.querySelector('#PORT'), + function (value) { + return /^\d*\.?\d*$/.test(value); // Allow digits and '.' only, using a RegExp. + }, + "Only digits and '.' are allowed" +); + document.body.querySelector('#PORT').addEventListener('change', () => { - settings.GENERAL.PORT = document.body.querySelector('#PORT').value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - createNotification('Saved port, please restart the application to reset the port', 'warning'); + settings.GENERAL.PORT = document.body.querySelector('#PORT').value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + createNotification('Saved port, please restart the application to reset the port', 'warning'); }); document.body.querySelector('#AMAZON_ACCESS_KEY').addEventListener('change', () => { - settings.AMAZON.ACCESS_KEY = document.body.querySelector('#AMAZON_ACCESS_KEY').value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - createNotification('Saved Amazon access key!', 'success'); + settings.AMAZON.ACCESS_KEY = document.body.querySelector('#AMAZON_ACCESS_KEY').value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + createNotification('Saved Amazon access key!', 'success'); }); document.body.querySelector('#AMAZON_ACCESS_SECRET').addEventListener('change', () => { - settings.AMAZON.ACCESS_SECRET = document.body.querySelector('#AMAZON_ACCESS_SECRET').value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - createNotification('Saved Amazon access secret!', 'success'); + settings.AMAZON.ACCESS_SECRET = document.body.querySelector('#AMAZON_ACCESS_SECRET').value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + createNotification('Saved Amazon access secret!', 'success'); }); document.body.querySelector('#GOOGLE_API_KEY').addEventListener('change', () => { - settings.GOOGLE.API_KEY = document.body.querySelector('#GOOGLE_API_KEY').value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - createNotification('Saved Google api key!', 'success'); + settings.GOOGLE.API_KEY = document.body.querySelector('#GOOGLE_API_KEY').value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + createNotification('Saved Google api key!', 'success'); }); document.body.querySelector('#notification').addEventListener('change', () => { - settings.AUDIO.NOTIFICATION_SOUND = notificationSound.selectedIndex; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - createNotification('Saved notification sound!', 'success'); + settings.AUDIO.NOTIFICATION_SOUND = notificationSound.selectedIndex; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + createNotification('Saved notification sound!', 'success'); }); function showMenuButton(menuButton, toggle) { - let option = document.body.querySelector(menuButton); - if (!toggle) { - option.style.display = 'none'; - } else { - option.style.display = ''; - } + const option = document.body.querySelector(menuButton); + if (!toggle) { + option.style.display = 'none'; + } else { + option.style.display = ''; + } } const notificationToasts = document.querySelector('#toasts'); // toast messages function createNotification(message = null, type = null) { - const notification = document.createElement('div'); - notification.classList.add('toast'); - notification.classList.add(type); - notification.innerText = message; - notificationToasts.appendChild(notification); + const notification = document.createElement('div'); + notification.classList.add('toast'); + notification.classList.add(type); + notification.innerText = message; + notificationToasts.appendChild(notification); - let alertSound = 'info.mp3'; - if (type === 'error') { - alertSound = 'error.mp3'; - } + let alertSound = 'info.mp3'; + if (type === 'error') { + alertSound = 'error.mp3'; + } - let notfication = new Audio(path.join(resourcesPath, `./sounds/notifications/${alertSound}`)); + if (settings.AUDIO.USE_NOTIFICATION_SOUNDS) { + const notfication = new Audio( + path.join(resourcesPath, main.isPackaged ? `./sounds/notifications/${alertSound}` : `../sounds/notifications/${alertSound}`) + ); notfication.volume = settings.AUDIO.NOTIFICATION_VOLUME / 100; notfication.play(); - setTimeout(() => notification.remove(), 10000); + } + + setTimeout(() => notification.remove(), 3000); } // Check for configs if (!settings.TWITCH.USE_TWITCH) { - const text = 'Please setup a service to connect to in Configuration > Show Advanced'; - createNotification(text, 'warning'); + const text = 'Please setup a service to connect to in Configuration > Show Advanced'; + createNotification(text, 'warning'); } if (settings.TWITCH.USE_TWITCH && !settings.TWITCH.CHANNEL_NAME) { - const text = 'No channel name inserted in the Twitch service'; - createNotification(text, 'warning'); + const text = 'No channel name inserted in the Twitch service'; + createNotification(text, 'warning'); } if (settings.TWITCH.USE_TWITCH && !settings.TWITCH.USERNAME) { - const text = 'No username inserted in the Twitch service'; - createNotification(text, 'warning'); + const text = 'No username inserted in the Twitch service'; + createNotification(text, 'warning'); } function toggleRadio(toggle, inputs) { - const element = inputs; - if (toggle === true) { - for (let i = 0; i < inputs.length; i += 1) { - element[i].style.display = ''; - } - } else { - for (let i = 0; i < inputs.length; i += 1) { - element[i].style.display = 'none'; - } + const element = inputs; + if (toggle === true) { + for (let i = 0; i < inputs.length; i += 1) { + element[i].style.display = ''; } + } else { + for (let i = 0; i < inputs.length; i += 1) { + element[i].style.display = 'none'; + } + } } +document.body.querySelector('#OPEN_SETTINGS_FILE').addEventListener('click', () => { + shell.openExternal(settingsPath); +}); + +document.body.querySelector('#Info_VOICE_MODELS_FOLDER').addEventListener('click', () => { + shell.openExternal(sttModels); +}); + // #region Use Custom theme toggle logic document.body.querySelector('#USE_CUSTOM_THEME').addEventListener('click', () => { - const toggle = document.getElementById('USE_CUSTOM_THEME').checked; - const inputs = document.getElementsByClassName('inputTheme'); - toggleRadio(toggle, inputs); + const toggle = document.getElementById('USE_CUSTOM_THEME').checked; + const inputs = document.getElementsByClassName('inputTheme'); + toggleRadio(toggle, inputs); - settings.THEME.USE_CUSTOM_THEME = toggle; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - theme.setTheme(); - createNotification(`${toggle ? 'Enabled' : 'Disabled'} custom theme!`, 'success'); + settings.THEME.USE_CUSTOM_THEME = toggle; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + theme.setTheme(); + createNotification(`${toggle ? 'Enabled' : 'Disabled'} custom theme!`, 'success'); }); // #region Top bar buttons document.body.querySelector('#min-button').addEventListener('click', () => { - ipcRenderer.send('minimize-window'); + ipcRenderer.send('minimize-window'); }); // #region Top bar buttons document.body.querySelector('#Info_USERNAME').addEventListener('click', async () => { - let element = document.body.querySelector('#TWITCH_OAUTH_TOKEN'); - element.value = await auth.getTwitchOauthToken(); - - createNotification('Saved OAuth token!', 'success'); + const element = document.body.querySelector('#TWITCH_OAUTH_TOKEN'); + element.value = await auth.getTwitchOauthToken(); + twitch.checkIfTokenIsValid(); + createNotification('Saved OAuth token!', 'success'); }); -let hideInputToggleButton = document.body.querySelectorAll('.password-toggle-btn .password-toggle-icon .fa-eye-slash'); -hideInputToggleButton.forEach((item) => { - item.addEventListener('click', () => { - if (item.classList.contains('fa-eye')) { - item.classList.remove('fa-eye'); - item.classList.add('fa-eye-slash'); - } else { - item.classList.remove('fa-eye-slash'); - item.classList.add('fa-eye'); - } - }); +document.body.querySelector('#GetBetterTtvEmotes').addEventListener('click', async () => { + twitch.getBetterTtvGLobalEmotes(); + createNotification('Saved BetterTTV emotes!', 'success'); +}); + +const hideInputToggleButton = document.body.querySelectorAll('.password-toggle-btn .password-toggle-icon .fa-eye-slash'); +hideInputToggleButton.forEach(item => { + item.addEventListener('click', () => { + if (item.classList.contains('fa-eye')) { + item.classList.remove('fa-eye'); + item.classList.add('fa-eye-slash'); + } else { + item.classList.remove('fa-eye-slash'); + item.classList.add('fa-eye'); + } + }); }); function hideOrShowViewerPanel() { - const menu = document.body.querySelector('.sidepanel-right'); - const leftCircle = document.body.querySelector('.circle-right'); + const menu = document.body.querySelector('.sidepanel-right'); + const leftCircle = document.body.querySelector('.circle-right'); - if (!settings.GENERAL.VIEWERS_PANEL) { - menu.classList.add('collapse-menu-right'); - leftCircle.classList.add('collapse-circle-right'); - } else { - menu.classList.remove('collapse-menu-right'); - leftCircle.classList.remove('collapse-circle-right'); - } - fs.writeFileSync(settingsPath, ini.stringify(settings)); + if (!settings.GENERAL.VIEWERS_PANEL) { + menu.classList.add('collapse-menu-right'); + leftCircle.classList.add('collapse-circle-right'); + } else { + menu.classList.remove('collapse-menu-right'); + leftCircle.classList.remove('collapse-circle-right'); + } + fs.writeFileSync(settingsPath, ini.stringify(settings)); } hideOrShowViewerPanel(); document.body.querySelector('#VIEWERS_PANEL').addEventListener('click', () => { - if (settings.GENERAL.VIEWERS_PANEL) { - settings.GENERAL.VIEWERS_PANEL = false; - } else { - settings.GENERAL.VIEWERS_PANEL = true; - } - hideOrShowViewerPanel(); + if (settings.GENERAL.VIEWERS_PANEL) { + settings.GENERAL.VIEWERS_PANEL = false; + } else { + settings.GENERAL.VIEWERS_PANEL = true; + } + hideOrShowViewerPanel(); }); document.body.querySelector('#Info_VTUBER').addEventListener('click', () => { - shell.openExternal(`http://localhost:${settings.GENERAL.PORT}/vtuber/`); + shell.openExternal(`http://localhost:${settings.GENERAL.PORT}/vtuber/`); }); document.body.querySelector('#Info_CHATBUBBLE').addEventListener('click', () => { - shell.openExternal(`http://localhost:${settings.GENERAL.PORT}/chat/`); + shell.openExternal(`http://localhost:${settings.GENERAL.PORT}/chat/`); }); document.body.querySelector('#max-button').addEventListener('click', () => { - ipcRenderer.send('maximize-window'); + ipcRenderer.send('maximize-window'); }); document.body.querySelector('#close-button').addEventListener('click', () => { - ipcRenderer.send('close-window'); + ipcRenderer.send('close-window'); }); // #endregion // #region Notification sound test document.body.querySelector('#SoundTestButton').addEventListener('click', () => { - sound.playNotificationSound(); + sound.playNotificationSound(); }); document.body.querySelector('#TestTwitchCredentials').addEventListener('click', () => { - twitch.ping('#TestTwitchCredentials'); - // resetTwitch(; + twitch.ping('#TestTwitchCredentials'); + // resetTwitch(; }); function toggleTwitch() { - const toggle = settings.TWITCH.USE_TWITCH; - const inputs = document.getElementsByClassName('inputTwitch'); - toggleRadio(toggle, inputs); + const toggle = settings.TWITCH.USE_TWITCH; + const inputs = document.getElementsByClassName('inputTwitch'); + toggleRadio(toggle, inputs); } toggleTwitch(); document.body.querySelector('#USE_TWITCH').addEventListener('click', () => { - const toggle = document.getElementById('USE_TWITCH').checked; - settings.TWITCH.USE_TWITCH = toggle; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - const inputs = document.getElementsByClassName('inputTwitch'); - toggleRadio(toggle, inputs); - twitch = settings.TWITCH.USE_TWITCH ? require(path.join(__dirname, './twitch')) : null; - createNotification(`${toggle ? 'Enabled' : 'Disabled'} Twitch settings!`, 'success'); + const toggle = document.getElementById('USE_TWITCH').checked; + settings.TWITCH.USE_TWITCH = toggle; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + const inputs = document.getElementsByClassName('inputTwitch'); + toggleRadio(toggle, inputs); + twitch = settings.TWITCH.USE_TWITCH ? require(path.join(__dirname, './twitch')) : null; + createNotification(`${toggle ? 'Enabled' : 'Disabled'} Twitch settings!`, 'success'); }); function toggleGoogle() { - const toggle = settings.GOOGLE.USE_GOOGLE; - const inputs = document.getElementsByClassName('inputGoogle'); - toggleRadio(toggle, inputs); + const toggle = settings.GOOGLE.USE_GOOGLE; + const inputs = document.getElementsByClassName('inputGoogle'); + toggleRadio(toggle, inputs); } toggleGoogle(); document.body.querySelector('#USE_GOOGLE').addEventListener('click', () => { - const toggle = document.getElementById('USE_GOOGLE').checked; - settings.GOOGLE.USE_GOOGLE = toggle; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - const inputs = document.getElementsByClassName('inputGoogle'); - toggleRadio(toggle, inputs); - createNotification(`${toggle ? 'Enabled' : 'Disabled'} Google settings!`, 'success'); + const toggle = document.getElementById('USE_GOOGLE').checked; + settings.GOOGLE.USE_GOOGLE = toggle; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + const inputs = document.getElementsByClassName('inputGoogle'); + toggleRadio(toggle, inputs); + createNotification(`${toggle ? 'Enabled' : 'Disabled'} Google settings!`, 'success'); }); function toggleAmazon() { - const toggle = settings.AMAZON.USE_AMAZON; - const inputs = document.getElementsByClassName('inputAmazon'); - toggleRadio(toggle, inputs); + const toggle = settings.AMAZON.USE_AMAZON; + const inputs = document.getElementsByClassName('inputAmazon'); + toggleRadio(toggle, inputs); } toggleAmazon(); document.body.querySelector('#USE_AMAZON').addEventListener('click', () => { - const toggle = document.getElementById('USE_AMAZON').checked; - settings.AMAZON.USE_AMAZON = toggle; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - const inputs = document.getElementsByClassName('inputAmazon'); - toggleRadio(toggle, inputs); - createNotification(`${toggle ? 'Enabled' : 'Disabled'} Amazon settings!`, 'success'); + const toggle = document.getElementById('USE_AMAZON').checked; + settings.AMAZON.USE_AMAZON = toggle; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + const inputs = document.getElementsByClassName('inputAmazon'); + toggleRadio(toggle, inputs); + createNotification(`${toggle ? 'Enabled' : 'Disabled'} Amazon settings!`, 'success'); }); function toggleServer() { - const toggle = settings.MODULES.USE_MODULES; - const inputs = document.getElementsByClassName('inputServer'); - toggleRadio(toggle, inputs); + const toggle = settings.MODULES.USE_MODULES; + const inputs = document.getElementsByClassName('inputServer'); + toggleRadio(toggle, inputs); } toggleServer(); document.body.querySelector('#USE_MODULES').addEventListener('click', () => { - const toggle = document.getElementById('USE_MODULES').checked; - settings.MODULES.USE_MODULES = toggle; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - const inputs = document.getElementsByClassName('inputServer'); - toggleRadio(toggle, inputs); - createNotification( - `${toggle ? 'Enabled' : 'Disabled'} server settings!, the service will stop working after restarting the application + const toggle = document.getElementById('USE_MODULES').checked; + settings.MODULES.USE_MODULES = toggle; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + const inputs = document.getElementsByClassName('inputServer'); + toggleRadio(toggle, inputs); + createNotification( + `${toggle ? 'Enabled' : 'Disabled'} server settings!, the service will stop working after restarting the application ${toggle ? '' : ', the service will stop working after restarting the application'}`, - 'success', - ); + 'success' + ); }); document.body.querySelector('#USE_VTUBER').addEventListener('change', () => { - const toggle = document.getElementById('USE_VTUBER').checked; - settings.MODULES.USE_VTUBER = toggle; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - showMenuButton('#btnBrowsersourceVtuber', toggle); - createNotification( - `${toggle ? 'Enabled' : 'Disabled'} Vtuber setting! + const toggle = document.getElementById('USE_VTUBER').checked; + settings.MODULES.USE_VTUBER = toggle; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + showMenuButton('#btnBrowsersourceVtuber', toggle); + createNotification( + `${toggle ? 'Enabled' : 'Disabled'} Vtuber setting! ${toggle ? '' : ', the service will stop working after restarting the application'}`, - 'success', - ); - server.startVtuberModule(); + 'success' + ); + server.startVtuberModule(); }); document.body.querySelector('#USE_CHATBUBBLE').addEventListener('change', () => { - const toggle = document.getElementById('USE_CHATBUBBLE').checked; - settings.MODULES.USE_CHATBUBBLE = toggle; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - showMenuButton('#btnBrowsersourceChat', toggle); - createNotification(`${toggle ? 'Enabled' : 'Disabled'} chatbubble setting!`, 'success'); - server.startChatBubbleModule(); + const toggle = document.getElementById('USE_CHATBUBBLE').checked; + settings.MODULES.USE_CHATBUBBLE = toggle; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + showMenuButton('#btnBrowsersourceChat', toggle); + createNotification(`${toggle ? 'Enabled' : 'Disabled'} chatbubble setting!`, 'success'); + server.startChatBubbleModule(); }); function toggleTTS() { - const toggle = settings.TTS.USE_TTS; - const inputs = document.getElementsByClassName('inputTTS'); - toggleRadio(toggle, inputs); + const toggle = settings.TTS.USE_TTS; + const inputs = document.getElementsByClassName('inputTTS'); + toggleRadio(toggle, inputs); } toggleTTS(); document.body.querySelector('#USE_TTS').addEventListener('change', () => { - const toggle = document.getElementById('USE_TTS').checked; - settings.TTS.USE_TTS = toggle; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - const inputs = document.getElementsByClassName('inputTTS'); - toggleRadio(toggle, inputs); - createNotification(`${toggle ? 'Enabled' : 'Disabled'} text to speech!`, 'success'); + const toggle = document.getElementById('USE_TTS').checked; + settings.TTS.USE_TTS = toggle; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + const inputs = document.getElementsByClassName('inputTTS'); + toggleRadio(toggle, inputs); + createNotification(`${toggle ? 'Enabled' : 'Disabled'} text to speech!`, 'success'); }); function toggleSTT() { - const toggle = settings.STT.USE_STT; - const inputs = document.getElementsByClassName('inputSTT'); - toggleRadio(toggle, inputs); + const toggle = settings.STT.USE_STT; + const inputs = document.getElementsByClassName('inputSTT'); + toggleRadio(toggle, inputs); } toggleSTT(); document.body.querySelector('#USE_STT').addEventListener('change', () => { - const toggle = document.getElementById('USE_STT').checked; - settings.STT.USE_STT = toggle; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - const inputs = document.getElementsByClassName('inputSTT'); - toggleRadio(toggle, inputs); - createNotification(`${toggle ? 'Enabled' : 'Disabled'} speech to text!`, 'success'); + const toggle = document.getElementById('USE_STT').checked; + settings.STT.USE_STT = toggle; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + const inputs = document.getElementsByClassName('inputSTT'); + toggleRadio(toggle, inputs); + createNotification(`${toggle ? 'Enabled' : 'Disabled'} speech to text!`, 'success'); +}); + +function toggleSendTranslation() { + const toggle = settings.LANGUAGE.SEND_TRANSLATION; + const inputs = document.getElementsByClassName('send-translation'); + toggleRadio(toggle, inputs); +} + +toggleSendTranslation(); + +document.body.querySelector('#SEND_TRANSLATION').addEventListener('change', () => { + const toggle = document.getElementById('SEND_TRANSLATION').checked; + settings.LANGUAGE.SEND_TRANSLATION = toggle; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + const inputs = document.getElementsByClassName('send-translation'); + toggleRadio(toggle, inputs); + createNotification(`${toggle ? 'Enabled' : 'Disabled'} Sending translations!`, 'success'); +}); + +document.body.querySelector('#OUTPUT_TO_TTS').addEventListener('change', () => { + let toggle = document.getElementById('OUTPUT_TO_TTS').checked; + if (!settings.TTS.USE_TTS) { + toggle = false; + createNotification('Enable TTS first', 'error'); + return; + } + + settings.LANGUAGE.OUTPUT_TO_TTS = toggle; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + createNotification(`${toggle ? 'Enabled' : 'Disabled'} Outputting translations to TTS!`, 'success'); +}); + +document.body.querySelector('#BROADCAST_TRANSLATION').addEventListener('change', () => { + const toggle = document.getElementById('BROADCAST_TRANSLATION').checked; + settings.LANGUAGE.BROADCAST_TRANSLATION = toggle; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + createNotification(`${toggle ? 'Enabled' : 'Disabled'} Language detection!`, 'success'); }); function toggleLanguageDetection() { - const toggle = settings.LANGUAGE.USE_DETECTION; - const inputs = document.getElementsByClassName('languageDetectionInput'); - toggleRadio(toggle, inputs); + const toggle = settings.LANGUAGE.USE_DETECTION; + const inputs = document.getElementsByClassName('languageDetectionInput'); + toggleRadio(toggle, inputs); } toggleLanguageDetection(); document.body.querySelector('#USE_DETECTION').addEventListener('change', () => { - const toggle = document.getElementById('USE_DETECTION').checked; - settings.LANGUAGE.USE_DETECTION = toggle; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - const inputs = document.getElementsByClassName('languageDetectionInput'); - toggleRadio(toggle, inputs); - createNotification(`${toggle ? 'Enabled' : 'Disabled'} Language detection!`, 'success'); + const toggle = document.getElementById('USE_DETECTION').checked; + settings.LANGUAGE.USE_DETECTION = toggle; + + if (!toggle) { + settings.LANGUAGE.BROADCAST_TRANSLATION = false; + document.body.querySelector('#BROADCAST_TRANSLATION').checked = false; + settings.LANGUAGE.OUTPUT_TO_TTS = false; + document.body.querySelector('#OUTPUT_TO_TTS').checked = false; + } + + fs.writeFileSync(settingsPath, ini.stringify(settings)); + const inputs = document.getElementsByClassName('languageDetectionInput'); + toggleRadio(toggle, inputs); + createNotification(`${toggle ? 'Enabled' : 'Disabled'} Language detection!`, 'success'); }); function toggleNotificationSounds() { - const toggle = settings.AUDIO.USE_NOTIFICATION_SOUNDS; - const inputs = document.getElementsByClassName('inputNotificationSound'); - toggleRadio(toggle, inputs); + const toggle = settings.AUDIO.USE_NOTIFICATION_SOUNDS; + const inputs = document.getElementsByClassName('inputNotificationSound'); + toggleRadio(toggle, inputs); } toggleNotificationSounds(); document.body.querySelector('#USE_NOTIFICATION_SOUNDS').addEventListener('change', () => { - const toggle = document.getElementById('USE_NOTIFICATION_SOUNDS').checked; - settings.AUDIO.USE_NOTIFICATION_SOUNDS = toggle; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - const inputs = document.getElementsByClassName('inputNotificationSound'); - toggleRadio(toggle, inputs); - createNotification(`${toggle ? 'Enabled' : 'Disabled'} notification sounds!`, 'success'); + const toggle = document.getElementById('USE_NOTIFICATION_SOUNDS').checked; + settings.AUDIO.USE_NOTIFICATION_SOUNDS = toggle; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + const inputs = document.getElementsByClassName('inputNotificationSound'); + toggleRadio(toggle, inputs); + createNotification(`${toggle ? 'Enabled' : 'Disabled'} notification sounds!`, 'success'); }); document.body.querySelector('#notificationVolume').addEventListener('change', () => { - let element = document.body.querySelector('#notificationVolume'); - settings.AUDIO.NOTIFICATION_VOLUME = element.value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); + const element = document.body.querySelector('#notificationVolume'); + settings.AUDIO.NOTIFICATION_VOLUME = element.value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); - const slider = document.querySelector('#notificationVolumeSlider'); - slider.value = settings.AUDIO.NOTIFICATION_VOLUME; - slider.style.setProperty('--tiempotemporal', settings.AUDIO.NOTIFICATION_VOLUME); + const slider = document.querySelector('#notificationVolumeSlider'); + slider.value = settings.AUDIO.NOTIFICATION_VOLUME; + slider.style.setProperty('--tiempotemporal', settings.AUDIO.NOTIFICATION_VOLUME); - createNotification('Saved notification volume!', 'success'); + createNotification('Saved notification volume!', 'success'); }); document.body.querySelector('#notificationVolumeSlider').addEventListener('change', () => { - const e = document.querySelector('#notificationVolumeSlider'); - e.style.setProperty('--tiempotemporal', e.value); - e.style.setProperty('--min', e.min === '' ? '0' : e.min); - e.style.setProperty('--max', e.max === '' ? '100' : e.max); - document.querySelector('#notificationVolume').value = e.value; + const e = document.querySelector('#notificationVolumeSlider'); + e.style.setProperty('--tiempotemporal', e.value); + e.style.setProperty('--min', e.min === '' ? '0' : e.min); + e.style.setProperty('--max', e.max === '' ? '100' : e.max); + document.querySelector('#notificationVolume').value = e.value; - e.addEventListener('input', () => { - e.style.setProperty('--tiempotemporal', e.value); - document.querySelector('#notificationVolume').value = e.value; - settings.AUDIO.NOTIFICATION_VOLUME = e.value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - }); + e.addEventListener('input', () => { + e.style.setProperty('--tiempotemporal', e.value); + document.querySelector('#notificationVolume').value = e.value; + settings.AUDIO.NOTIFICATION_VOLUME = e.value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + }); }); document.body.querySelector('#notificationVolumeSlider').addEventListener('mouseup', () => { - createNotification('Saved notification volume!', 'success'); + createNotification('Saved notification volume!', 'success'); }); if (settings.AUDIO.NOTIFICATION_VOLUME) { - document.querySelector('#notificationVolumeSlider').value = settings.AUDIO.NOTIFICATION_VOLUME; - document.querySelector('#notificationVolumeSlider').dispatchEvent(new Event('change')); + document.querySelector('#notificationVolumeSlider').value = settings.AUDIO.NOTIFICATION_VOLUME; + document.querySelector('#notificationVolumeSlider').dispatchEvent(new Event('change')); } else { - document.querySelector('#notificationVolumeSlider').dispatchEvent(new Event('change', { value: 50 })); + document.querySelector('#notificationVolumeSlider').dispatchEvent(new Event('change', { value: 50 })); } document.body.querySelector('#ttsVolume').addEventListener('change', () => { - let element = document.body.querySelector('#ttsVolume'); - settings.AUDIO.TTS_VOLUME = element.value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); + const element = document.body.querySelector('#ttsVolume'); + settings.AUDIO.TTS_VOLUME = element.value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); - const slider = document.querySelector('#ttsVolumeSlider'); - slider.value = settings.AUDIO.TTS_VOLUME; - slider.style.setProperty('--tiempotemporal', settings.AUDIO.TTS_VOLUME); + const slider = document.querySelector('#ttsVolumeSlider'); + slider.value = settings.AUDIO.TTS_VOLUME; + slider.style.setProperty('--tiempotemporal', settings.AUDIO.TTS_VOLUME); - createNotification('Saved TTS volume!', 'success'); + createNotification('Saved TTS volume!', 'success'); }); document.body.querySelector('#ttsVolumeSlider').addEventListener('change', () => { - const e = document.querySelector('#ttsVolumeSlider'); - e.style.setProperty('--tiempotemporal', e.value); - e.style.setProperty('--min', e.min === '' ? '0' : e.min); - e.style.setProperty('--max', e.max === '' ? '100' : e.max); - document.querySelector('#ttsVolume').value = e.value; + const e = document.querySelector('#ttsVolumeSlider'); + e.style.setProperty('--tiempotemporal', e.value); + e.style.setProperty('--min', e.min === '' ? '0' : e.min); + e.style.setProperty('--max', e.max === '' ? '100' : e.max); + document.querySelector('#ttsVolume').value = e.value; - e.addEventListener('input', () => { - e.style.setProperty('--tiempotemporal', e.value); - document.querySelector('#ttsVolume').value = e.value; - settings.AUDIO.TTS_VOLUME = e.value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - }); + e.addEventListener('input', () => { + e.style.setProperty('--tiempotemporal', e.value); + document.querySelector('#ttsVolume').value = e.value; + settings.AUDIO.TTS_VOLUME = e.value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + }); }); document.body.querySelector('#ttsVolumeSlider').addEventListener('mouseup', () => { - createNotification('Saved TTS volume!', 'success'); + createNotification('Saved TTS volume!', 'success'); }); if (settings.AUDIO.TTS_VOLUME) { - document.querySelector('#ttsVolumeSlider').value = settings.AUDIO.TTS_VOLUME; - document.querySelector('#ttsVolumeSlider').dispatchEvent(new Event('change')); + document.querySelector('#ttsVolumeSlider').value = settings.AUDIO.TTS_VOLUME; + document.querySelector('#ttsVolumeSlider').dispatchEvent(new Event('change')); } else { - document.querySelector('#ttsVolumeSlider').dispatchEvent(new Event('change', { value: 50 })); + document.querySelector('#ttsVolumeSlider').dispatchEvent(new Event('change', { value: 50 })); } document.body.querySelector('#ttsVolume').addEventListener('change', () => { - let element = document.body.querySelector('#ttsVolume'); - settings.AUDIO.TTS_VOLUME = element.value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); + const element = document.body.querySelector('#ttsVolume'); + settings.AUDIO.TTS_VOLUME = element.value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); - const slider = document.querySelector('#ttsVolumeSlider'); - slider.value = settings.AUDIO.TTS_VOLUME; - slider.style.setProperty('--tiempotemporal', settings.AUDIO.TTS_VOLUME); + const slider = document.querySelector('#ttsVolumeSlider'); + slider.value = settings.AUDIO.TTS_VOLUME; + slider.style.setProperty('--tiempotemporal', settings.AUDIO.TTS_VOLUME); }); document.body.querySelector('#TestDefaultTTSButton').addEventListener('click', async () => { - const text = document.getElementById('testPrimaryTTS').value; - const requestData = { - message: `user: ${text}`, - voice: settings.TTS.PRIMARY_VOICE, - }; - let count = await backend.getInternalTTSAudio(requestData); - let textObject = { filtered: text, formatted: text }; - sound.playAudio({ service: 'Internal', message: textObject, count }); + if (!settings.TTS.PRIMARY_VOICE) { + return; + } + const text = document.getElementById('testPrimaryTTS').value; + const requestData = { + message: `user: ${text}`, + voice: settings.TTS.PRIMARY_VOICE + }; + const count = await backend.getInternalTTSAudio(requestData); + const textObject = { filtered: text, formatted: text }; + sound.playAudio({ service: 'Internal', message: textObject, count }); }); document.body.querySelector('#TestSecondaryTTSButton').addEventListener('click', async () => { - const text = document.getElementById('testSecondaryTTS').value; - const requestData = { - message: `user: ${text}`, - voice: settings.TTS.SECONDARY_VOICE, - }; + if (!settings.TTS.SECONDARY_VOICE) { + return; + } + const text = document.getElementById('testSecondaryTTS').value; + const requestData = { + message: `user: ${text}`, + voice: settings.TTS.SECONDARY_VOICE + }; - let count = await backend.getInternalTTSAudio(requestData); - let textObject = { filtered: text, formatted: text }; + const count = await backend.getInternalTTSAudio(requestData); + const textObject = { filtered: text, formatted: text }; - sound.playAudio({ service: 'Internal', message: textObject, count }); + sound.playAudio({ service: 'Internal', message: textObject, count }); +}); + +// Restricts input for the given textbox to the given inputFilter function. +function setInputFilter(textbox, inputFilter, errMsg) { + ['input', 'keydown', 'keyup', 'mousedown', 'mouseup', 'select', 'contextmenu', 'drop', 'focusout'].forEach(function (event) { + textbox.addEventListener(event, function (e) { + if (inputFilter(this.value)) { + // Accepted value. + if (['keydown', 'mousedown', 'focusout'].indexOf(e.type) >= 0) { + this.classList.remove('input-error'); + this.setCustomValidity(''); + } + + this.oldValue = this.value; + this.oldSelectionStart = this.selectionStart; + this.oldSelectionEnd = this.selectionEnd; + } else if (Object.prototype.hasOwnProperty.call(this, 'oldValue')) { + // Rejected value: restore the previous one. + this.classList.add('input-error'); + this.setCustomValidity(errMsg); + this.reportValidity(); + this.value = this.oldValue; + this.setSelectionRange(this.oldSelectionStart, this.oldSelectionEnd); + } else { + // Rejected value: nothing to restore. + this.value = ''; + } + }); + }); +} + +webFrame.setVisualZoomLevelLimits(1, 5); + +document.body.addEventListener('wheel', e => { + if (e.ctrlKey) { + const currentZoom = webFrame.getZoomFactor(); + const zoomIn = Boolean(e.deltaY < 0); + setZoomLevel(currentZoom, zoomIn); + } +}); + +setInputFilter( + document.body.querySelector('#ZOOMLEVEL'), + function (value) { + return /^\d*\.?\d*$/.test(value); // Allow digits and '.' only, using a RegExp. + }, + "Only digits and '.' are allowed" +); + +document.body.querySelector('#ZOOMLEVEL').addEventListener('change', () => { + const newZoom = parseInt(document.body.querySelector('#ZOOMLEVEL').value) / 100; + settings.GENERAL.ZOOMLEVEL = newZoom; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + setZoomLevel(newZoom, null); + createNotification('Saved zoom new level', 'warning'); +}); + +document.body.querySelector('emoji-picker').addEventListener('emoji-click', e => { + // console.log(e.detail); + const div = document.getElementById('textInput'); + if (e.detail.unicode === undefined) { + div.value += e.detail.name + ' '; + } else { + div.value += e.detail.unicode + ' '; + } + + div.focus(); }); module.exports = { - getGeneralSettings, + getGeneralSettings, + createNotification }; diff --git a/src/js/sound.js b/src/js/sound.js index 679b2a3..6a570b7 100644 --- a/src/js/sound.js +++ b/src/js/sound.js @@ -1,120 +1,138 @@ -let trueMessage = ''; -let currentLogoUrl = ''; -let currentUsername = ''; -let voiceSoundArray = []; +/* global ttsAudioFile, main, path, getLanguageProperties, resourcesPath, settings, fs, notificationSound, backend, socket, requestData */ + +const voiceSoundArray = []; let status = 0; -let counter = 0; +const counter = 0; -const playTTS = (data) => - new Promise((resolve) => { - ttsAudioFile = path.join(resourcesPath, `./sounds/tts/${data.service}_${data.count}.mp3`); - const tts = new Audio(ttsAudioFile); - console.log(settings.AUDIO.TTS_AUDIO_DEVICE); - tts.setSinkId(settings.AUDIO.TTS_AUDIO_DEVICE); +const playTTS = data => + new Promise(resolve => { + ttsAudioFile = path.join( + resourcesPath, + main.isPackaged ? `./sounds/${data.service}_${data.count}.mp3` : `../sounds/${data.service}_${data.count}.mp3` + ); + const tts = new Audio(ttsAudioFile); + tts.setSinkId(settings.AUDIO.TTS_AUDIO_DEVICE); - tts.addEventListener('ended', () => { - console.log('ended'); - fs.unlink(ttsAudioFile, (err) => { - if (err) { - console.error('TEST'); - - resolve('finished'); - return; - } - resolve('finished'); - }); - }); - - tts.setSinkId(settings.AUDIO.TTS_AUDIO_DEVICE) - .then(() => { - console.log('playing'); - tts.volume = settings.AUDIO.TTS_VOLUME / 100; - tts.play().catch((error) => { - resolve('finished'); - }); - }) - .catch((error) => { - console.error('Failed to set audio output device:', error); - resolve('finished'); - }); + tts.addEventListener('ended', () => { + // console.log('ended'); + fs.unlink(ttsAudioFile, err => { + if (err) { + console.error(err); + resolve('finished'); + return; + } + resolve('finished'); + }); }); + tts + .setSinkId(settings.AUDIO.TTS_AUDIO_DEVICE) + .then(() => { + // console.log('playing'); + tts.volume = settings.AUDIO.TTS_VOLUME / 100; + tts.play().catch(error => { + if (error) { + console.error(error); + } + resolve('finished'); + }); + }) + .catch(error => { + console.error('Failed to set audio output device:', error); + resolve('finished'); + }); + }); + async function shiftVoice() { - status = 1; - while (voiceSoundArray.length > 0) { - await playTTS(voiceSoundArray.shift()); - } - status = 0; + status = 1; + while (voiceSoundArray.length > 0) { + await playTTS(voiceSoundArray.shift()); + } + status = 0; } function add(data) { - voiceSoundArray.push(data); - if (status === 0) { - shiftVoice(); - } + voiceSoundArray.push(data); + if (status === 0) { + shiftVoice(); + } } function playNotificationSound() { - if (settings.AUDIO.USE_NOTIFICATION_SOUNDS) { - let notfication = new Audio( - path.join(resourcesPath, `./sounds/notifications/${notificationSound.options[settings.AUDIO.NOTIFICATION_SOUND].text}`), - ); + if (settings.AUDIO.USE_NOTIFICATION_SOUNDS) { + const notfication = new Audio( + path.join( + resourcesPath, + main.isPackaged + ? `./sounds/notifications/${notificationSound.options[settings.AUDIO.NOTIFICATION_SOUND].text}` + : `../sounds/notifications/${notificationSound.options[settings.AUDIO.NOTIFICATION_SOUND].text}` + ) + ); + + notfication + .setSinkId(settings.AUDIO.SELECTED_NOTIFICATION_AUDIO_DEVICE) + .then(() => { + // console.log('playing'); notfication.volume = settings.AUDIO.NOTIFICATION_VOLUME / 100; - notfication.play(); - } + notfication.play().catch(error => { + if (error) { + console.error(error); + } + }); + }) + .catch(error => { + console.error('Failed to set audio output device:', error); + }); + } } // Play sound function function playAudio(data) { - if (data.service !== '') { - add(data); - } + if (data.service !== '') { + add(data); + } } -async function playVoice(filteredMessage, logoUrl, username, message) { - trueMessage = filteredMessage; - currentLogoUrl = logoUrl; - currentUsername = username; - let textObject = { filtered: filteredMessage, formatted: message }; - let voice; - textObject.filtered = `${username}: ${filteredMessage}`; +async function playVoice(message) { + if (!settings.TTS.PRIMARY_VOICE) { + return; + } + const textObject = { filtered: message.filteredMessage, formatted: message.formattedMessage }; + let voice = settings.TTS.PRIMARY_VOICE; + textObject.filtered = `${message.username}: ${message.filteredMessage}`; - // if ( - // settings.TTS.PRIMARY_TTS_LANGUAGE.toLowerCase() !== settings.TTS.SECONDARY_TTS_LANGUAGE.toLowerCase() && - // language[0].lang === settings.TTS.SECONDARY_TTS_LANGUAGE.toLowerCase() - // ) { - // voice = settings.TTS.SECONDARY_TTS_NAME; - // textObject.filtered = `${username}: ${filteredMessage}`; - // } else { - // voice = settings.TTS.PRIMARY_TTS_NAME; - // textObject.filtered = `${username}: ${filteredMessage}`; - // } - - const service = document.getElementById('primaryTTSService').value; - - switch (service) { - case 'Internal': - const requestData = { - message: textObject.filtered, - voice: settings.TTS.PRIMARY_VOICE, - }; - - let count = await backend.getInternalTTSAudio(requestData); - playAudio({ service, message: textObject, count }); - break; - case 'Amazon': - // playAudio({ service: 'Amazon', message: textObject, count }); - break; - case 'Google': - // playAudio({ service: 'Google', message: textObject, count }); - break; + if (settings.LANGUAGE.USE_DETECTION && settings.TTS.SECONDARY_VOICE) { + const secondaryTTSLanguage = getLanguageProperties(settings.TTS.SECONDARY_TTS_LANGUAGE); + if (message.language.detectedLanguage === null || message.language.detectedLanguage.ISO639 === secondaryTTSLanguage.ISO639) { + voice = settings.TTS.SECONDARY_VOICE; + textObject.filtered = message.originalMessage ? message.originalMessage : message.filteredMessage; } + } - if (settings.MODULES.USE_CHATBUBBLE) { - socket.emit('xxx', currentLogoUrl, currentUsername, textObject); + const service = document.getElementById('primaryTTSService').value; + + switch (service) { + case 'Internal': { + const requestData = { + message: textObject.filtered, + voice: voice + }; + + const count = await backend.getInternalTTSAudio(requestData); + playAudio({ service, message: textObject, count }); + break; } + case 'Amazon': + // playAudio({ service: 'Amazon', message: textObject, count }); + break; + case 'Google': + // playAudio({ service: 'Google', message: textObject, count }); + break; + } - playNotificationSound(); + if (settings.MODULES.USE_CHATBUBBLE) { + socket.emit('xxx', message); + } } module.exports = { playAudio, playVoice, playNotificationSound }; diff --git a/src/js/theme.js b/src/js/theme.js index d324b87..8fb2653 100644 --- a/src/js/theme.js +++ b/src/js/theme.js @@ -1,145 +1,143 @@ +/* global settings, root, fs, settingsPath, ini */ + function changeColor(section, setting, tempSection) { - document.querySelector(section).value = setting; - const value = document.querySelector(section).value; - root.style.setProperty(tempSection, value); + document.querySelector(section).value = setting; + const value = document.querySelector(section).value; + root.style.setProperty(tempSection, value); } function setCurrentTheme(adjustTemp = false) { - changeColor('#MAIN_COLOR_1', settings.THEME.MAIN_COLOR_1, adjustTemp ? '--main-color1-temp' : '--main-color1'); - changeColor('#MAIN_COLOR_2', settings.THEME.MAIN_COLOR_2, adjustTemp ? '--main-color2-temp' : '--main-color2'); - changeColor('#MAIN_COLOR_3', settings.THEME.MAIN_COLOR_3, adjustTemp ? '--main-color3-temp' : '--main-color3'); - changeColor('#MAIN_COLOR_4', settings.THEME.MAIN_COLOR_4, adjustTemp ? '--main-color4-temp' : '--main-color4'); - changeColor('#TOP_BAR', settings.THEME.TOP_BAR, adjustTemp ? '--top-bar-temp' : '--top-bar'); - changeColor('#MID_SECTION', settings.THEME.MID_SECTION, adjustTemp ? '--mid-section-temp' : '--mid-section'); - changeColor('#CHAT_BUBBLE_BG', settings.THEME.CHAT_BUBBLE_BG, adjustTemp ? '--chat-bubble-temp' : '--chat-bubble'); - changeColor( - '#CHAT_BUBBLE_HEADER', - settings.THEME.CHAT_BUBBLE_HEADER, - adjustTemp ? '--chat-bubble-header-temp' : '--chat-bubble-header', - ); - changeColor( - '#CHAT_BUBBLE_MESSAGE', - settings.THEME.CHAT_BUBBLE_MESSAGE, - adjustTemp ? '--chat-bubble-message-temp' : '--chat-bubble-message', - ); + changeColor('#MAIN_COLOR_1', settings.THEME.MAIN_COLOR_1, adjustTemp ? '--main-color1-temp' : '--main-color1'); + changeColor('#MAIN_COLOR_2', settings.THEME.MAIN_COLOR_2, adjustTemp ? '--main-color2-temp' : '--main-color2'); + changeColor('#MAIN_COLOR_3', settings.THEME.MAIN_COLOR_3, adjustTemp ? '--main-color3-temp' : '--main-color3'); + changeColor('#MAIN_COLOR_4', settings.THEME.MAIN_COLOR_4, adjustTemp ? '--main-color4-temp' : '--main-color4'); + changeColor('#TOP_BAR', settings.THEME.TOP_BAR, adjustTemp ? '--top-bar-temp' : '--top-bar'); + changeColor('#MID_SECTION', settings.THEME.MID_SECTION, adjustTemp ? '--mid-section-temp' : '--mid-section'); + changeColor('#CHAT_BUBBLE_BG', settings.THEME.CHAT_BUBBLE_BG, adjustTemp ? '--chat-bubble-temp' : '--chat-bubble'); + changeColor('#CHAT_BUBBLE_HEADER', settings.THEME.CHAT_BUBBLE_HEADER, adjustTemp ? '--chat-bubble-header-temp' : '--chat-bubble-header'); + changeColor( + '#CHAT_BUBBLE_MESSAGE', + settings.THEME.CHAT_BUBBLE_MESSAGE, + adjustTemp ? '--chat-bubble-message-temp' : '--chat-bubble-message' + ); } setCurrentTheme(true); function setTheme() { - if (settings.THEME.USE_CUSTOM_THEME) { - setCurrentTheme(); - } else { - root.style.setProperty('--main-color1', '#6e2c8c'); - root.style.setProperty('--main-color2', 'white'); - root.style.setProperty('--main-color3', '#211E1E'); - root.style.setProperty('--main-color4', '#2f2c34'); - root.style.setProperty('--top-bar', '#100B12'); - root.style.setProperty('--mid-section', '#352d3d'); - root.style.setProperty('--chat-bubble', ' #7A6D7F'); - root.style.setProperty('--chat-bubble-header', '#141414'); - root.style.setProperty('--chat-bubble-message', 'white'); - } + if (settings.THEME.USE_CUSTOM_THEME) { + setCurrentTheme(); + } else { + root.style.setProperty('--main-color1', '#6e2c8c'); + root.style.setProperty('--main-color2', 'white'); + root.style.setProperty('--main-color3', '#211E1E'); + root.style.setProperty('--main-color4', '#2f2c34'); + root.style.setProperty('--top-bar', '#100B12'); + root.style.setProperty('--mid-section', '#352d3d'); + root.style.setProperty('--chat-bubble', ' #7A6D7F'); + root.style.setProperty('--chat-bubble-header', '#141414'); + root.style.setProperty('--chat-bubble-message', 'white'); + } } document.body.querySelector('#MAIN_COLOR_1').addEventListener('input', () => { - const x = document.getElementById('MAIN_COLOR_1').value; - root.style.setProperty('--main-color1-temp', x); - console.log(x); + const x = document.getElementById('MAIN_COLOR_1').value; + root.style.setProperty('--main-color1-temp', x); + console.log(x); }); document.body.querySelector('#MAIN_COLOR_1').addEventListener('change', () => { - settings.THEME.MAIN_COLOR_1 = document.getElementById('MAIN_COLOR_1').value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - changeColor('#MAIN_COLOR_1', settings.THEME.MAIN_COLOR_1, '--main-color1'); + settings.THEME.MAIN_COLOR_1 = document.getElementById('MAIN_COLOR_1').value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + changeColor('#MAIN_COLOR_1', settings.THEME.MAIN_COLOR_1, '--main-color1'); }); document.body.querySelector('#MAIN_COLOR_2').addEventListener('input', () => { - const x = document.getElementById('MAIN_COLOR_2').value; - root.style.setProperty('--main-color2-temp', x); + const x = document.getElementById('MAIN_COLOR_2').value; + root.style.setProperty('--main-color2-temp', x); }); document.body.querySelector('#MAIN_COLOR_2').addEventListener('change', () => { - settings.THEME.MAIN_COLOR_2 = document.getElementById('MAIN_COLOR_2').value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - changeColor('#MAIN_COLOR_2', settings.THEME.MAIN_COLOR_2, '--main-color2'); + settings.THEME.MAIN_COLOR_2 = document.getElementById('MAIN_COLOR_2').value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + changeColor('#MAIN_COLOR_2', settings.THEME.MAIN_COLOR_2, '--main-color2'); }); document.body.querySelector('#MAIN_COLOR_3').addEventListener('input', () => { - const x = document.getElementById('MAIN_COLOR_3').value; - root.style.setProperty('--main-color3-temp', x); + const x = document.getElementById('MAIN_COLOR_3').value; + root.style.setProperty('--main-color3-temp', x); }); document.body.querySelector('#MAIN_COLOR_3').addEventListener('change', () => { - settings.THEME.MAIN_COLOR_3 = document.getElementById('MAIN_COLOR_3').value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - changeColor('#MAIN_COLOR_3', settings.THEME.MAIN_COLOR_3, '--main-color3'); + settings.THEME.MAIN_COLOR_3 = document.getElementById('MAIN_COLOR_3').value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + changeColor('#MAIN_COLOR_3', settings.THEME.MAIN_COLOR_3, '--main-color3'); }); document.body.querySelector('#MAIN_COLOR_4').addEventListener('input', () => { - const x = document.getElementById('MAIN_COLOR_4').value; - root.style.setProperty('--main-color4-temp', x); + const x = document.getElementById('MAIN_COLOR_4').value; + root.style.setProperty('--main-color4-temp', x); }); document.body.querySelector('#MAIN_COLOR_4').addEventListener('change', () => { - settings.THEME.MAIN_COLOR_4 = document.getElementById('MAIN_COLOR_4').value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - changeColor('#MAIN_COLOR_4', settings.THEME.MAIN_COLOR_4, '--main-color4'); + settings.THEME.MAIN_COLOR_4 = document.getElementById('MAIN_COLOR_4').value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + changeColor('#MAIN_COLOR_4', settings.THEME.MAIN_COLOR_4, '--main-color4'); }); document.body.querySelector('#TOP_BAR').addEventListener('input', () => { - const x = document.getElementById('TOP_BAR').value; - root.style.setProperty('--top-bar-temp', x); + const x = document.getElementById('TOP_BAR').value; + root.style.setProperty('--top-bar-temp', x); }); document.body.querySelector('#TOP_BAR').addEventListener('change', () => { - settings.THEME.TOP_BAR = document.getElementById('TOP_BAR').value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - changeColor('#TOP_BAR', settings.THEME.TOP_BAR, '--top-bar'); + settings.THEME.TOP_BAR = document.getElementById('TOP_BAR').value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + changeColor('#TOP_BAR', settings.THEME.TOP_BAR, '--top-bar'); }); document.body.querySelector('#MID_SECTION').addEventListener('input', () => { - const x = document.getElementById('MID_SECTION').value; - root.style.setProperty('--mid-section-temp', x); + const x = document.getElementById('MID_SECTION').value; + root.style.setProperty('--mid-section-temp', x); }); document.body.querySelector('#MID_SECTION').addEventListener('change', () => { - settings.THEME.MID_SECTION = document.getElementById('MID_SECTION').value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - changeColor('#MID_SECTION', settings.THEME.MID_SECTION, '--mid-section'); + settings.THEME.MID_SECTION = document.getElementById('MID_SECTION').value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + changeColor('#MID_SECTION', settings.THEME.MID_SECTION, '--mid-section'); }); document.body.querySelector('#CHAT_BUBBLE_BG').addEventListener('input', () => { - const x = document.getElementById('CHAT_BUBBLE_BG').value; - root.style.setProperty('--chat-bubble-temp', x); + const x = document.getElementById('CHAT_BUBBLE_BG').value; + root.style.setProperty('--chat-bubble-temp', x); }); document.body.querySelector('#CHAT_BUBBLE_BG').addEventListener('change', () => { - settings.THEME.CHAT_BUBBLE_BG = document.getElementById('CHAT_BUBBLE_BG').value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - changeColor('#CHAT_BUBBLE_BG', settings.THEME.CHAT_BUBBLE_BG, '--chat-bubble'); + settings.THEME.CHAT_BUBBLE_BG = document.getElementById('CHAT_BUBBLE_BG').value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + changeColor('#CHAT_BUBBLE_BG', settings.THEME.CHAT_BUBBLE_BG, '--chat-bubble'); }); document.body.querySelector('#CHAT_BUBBLE_HEADER').addEventListener('input', () => { - const x = document.getElementById('CHAT_BUBBLE_HEADER').value; - root.style.setProperty('--chat-bubble-header-temp', x); + const x = document.getElementById('CHAT_BUBBLE_HEADER').value; + root.style.setProperty('--chat-bubble-header-temp', x); }); document.body.querySelector('#CHAT_BUBBLE_HEADER').addEventListener('change', () => { - settings.THEME.CHAT_BUBBLE_HEADER = document.getElementById('CHAT_BUBBLE_HEADER').value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - changeColor('#CHAT_BUBBLE_HEADER', settings.THEME.CHAT_BUBBLE_HEADER, '--chat-bubble-header'); + settings.THEME.CHAT_BUBBLE_HEADER = document.getElementById('CHAT_BUBBLE_HEADER').value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + changeColor('#CHAT_BUBBLE_HEADER', settings.THEME.CHAT_BUBBLE_HEADER, '--chat-bubble-header'); }); document.body.querySelector('#CHAT_BUBBLE_MESSAGE').addEventListener('input', () => { - const x = document.getElementById('CHAT_BUBBLE_MESSAGE').value; - root.style.setProperty('--chat-bubble-message-temp', x); + const x = document.getElementById('CHAT_BUBBLE_MESSAGE').value; + root.style.setProperty('--chat-bubble-message-temp', x); }); document.body.querySelector('#CHAT_BUBBLE_MESSAGE').addEventListener('change', () => { - settings.THEME.CHAT_BUBBLE_MESSAGE = document.getElementById('CHAT_BUBBLE_MESSAGE').value; - fs.writeFileSync(settingsPath, ini.stringify(settings)); - changeColor('#CHAT_BUBBLE_MESSAGE', settings.THEME.CHAT_BUBBLE_MESSAGE, '--chat-bubble-message'); + settings.THEME.CHAT_BUBBLE_MESSAGE = document.getElementById('CHAT_BUBBLE_MESSAGE').value; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + changeColor('#CHAT_BUBBLE_MESSAGE', settings.THEME.CHAT_BUBBLE_MESSAGE, '--chat-bubble-message'); }); module.exports = { setTheme }; diff --git a/src/js/twitch.js b/src/js/twitch.js index ef4f299..dcf8e95 100644 --- a/src/js/twitch.js +++ b/src/js/twitch.js @@ -1,147 +1,514 @@ +/* global client, playNotificationSound, chat, replaceChatMessageWithCustomEmojis, messageId, addSingleTooltip, settingsPath, fs, ini, backend, main, path, resourcesPath, customEmojis, emojiPicker,config, settings, options, sound, showChatMessage, messageTemplates, getPostTime */ + const tmi = require('tmi.js'); const axios = require('axios'); -let client; +let client = null; +let logoUrl = null; +const twitchChannels = []; function sendMessage(message) { - client.say(settings.TWITCH.CHANNEL_NAME, message).catch(console.error); + client.say(settings.TWITCH.CHANNEL_NAME, message).catch(console.error); } -client = new tmi.Client({ +if (settings.TWITCH.USERNAME && settings.TWITCH.OAUTH_TOKEN) { + client = new tmi.Client({ options: { - skipUpdatingEmotesets: true, + skipUpdatingEmotesets: true }, identity: { - username: settings.TWITCH.USERNAME, - password: settings.TWITCH.OAUTH_TOKEN, + username: settings.TWITCH.USERNAME, + password: settings.TWITCH.OAUTH_TOKEN }, - channels: [settings.TWITCH.CHANNEL_NAME], -}); + channels: [settings.TWITCH.CHANNEL_NAME] + }); -client + client .connect() - .then((data) => {}) + .then(data => {}) .catch(console.error); -function ping(element) { - let value = document.body.querySelector(element); - - client - .ping() - .then((data) => { - value.classList.add('success'); - value.innerText = 'Success!'; - }) - .catch((e) => { - value.classList.add('error'); - value.innerText = 'Failed!'; - }); -} - -function displayTwitchMessage(logoUrl, username, messageObject, fileteredMessage) { - const article = document.createElement('article'); - article.className = 'msg-container msg-remote'; - - article.innerHTML = messageTemplates.twitchTemplate; - - const userImg = article.querySelector('.icon-container > .user-img'); - if (userImg) { - userImg.src = logoUrl; - } - - const usernameHtml = article.querySelector('.username'); - if (usernameHtml) { - usernameHtml.innerText = username; - } - - const postTime = document.createElement('span'); - postTime.classList.add('post-time'); - - if (postTime) { - postTime.innerText = getPostTime(); - } - - const iconContainer = article.querySelector('.icon-container'); - iconContainer.appendChild(postTime); - - const msg = article.querySelector('.msg-box'); - if (msg) { - messageObject.forEach((entry) => { - if (entry.text) { - msg.innerHTML += entry.text; - } else { - msg.innerHTML += entry.html; - } - }); - // msg.appendChild(postTime); - } - - // Appends the message to the main chat box (shows the message) - showChatMessage(article, false); - - if (fileteredMessage) { - sound.playVoice(fileteredMessage, logoUrl, username, msg); - } - - window.article = article; -} - -function getProfileImage(userid, username, message, fileteredMessage) { - // Get user Logo with access token - options = { - method: 'GET', - url: `https://api.twitch.tv/helix/users?id=${userid}`, - headers: { 'Client-ID': settings.TWITCH.CLIENT_ID, Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}` }, - }; - - axios - .request(options) - .then((responseLogoUrl) => { - const logoUrl = responseLogoUrl.data.data[0].profile_image_url; - displayTwitchMessage(logoUrl, username, message, fileteredMessage); - }) - .catch((error) => { - console.error(error); - }); -} - -function parseString(inputString) { - const regex = /()|([^<]+)/g; - const matches = inputString.match(regex) || []; - const result = []; - - for (let i = 0; i < matches.length; i++) { - const match = matches[i].trim(); - if (match.startsWith(' { + client.on('message', (channel, tags, message, self) => { if (self) { - return; + return; } const emotes = tags.emotes || {}; const emoteValues = Object.entries(emotes); - let fileteredMessage = message; + let filteredMessage = message; let emoteMessage = message; - emoteValues.forEach((entry) => { - entry[1].forEach((lol) => { - const [start, end] = lol.split('-'); - let emote = ``; - emoteMessage = emoteMessage.replaceAll(message.slice(parseInt(start), parseInt(end) + 1), emote); - fileteredMessage = fileteredMessage.replaceAll(message.slice(parseInt(start), parseInt(end) + 1), ''); - }); + emoteValues.forEach(entry => { + entry[1].forEach(lol => { + const [start, end] = lol.split('-'); + const emote = ``; + emoteMessage = emoteMessage.replaceAll(message.slice(parseInt(start), parseInt(end) + 1), emote); + filteredMessage = filteredMessage.replaceAll(message.slice(parseInt(start), parseInt(end) + 1), ''); + }); }); - let messageObject = parseString(emoteMessage); - getProfileImage(tags['user-id'], tags['display-name'], messageObject, fileteredMessage); -}); + const messageObject = parseString(emoteMessage); -module.exports = { sendMessage, ping, client }; + getProfileImage(tags['user-id'], tags['display-name'], messageObject, filteredMessage); + }); +} + +function checkIfTokenIsValid() { + options = { + method: 'GET', + url: 'https://id.twitch.tv/oauth2/validate', + headers: { + Authorization: `OAuth ${settings.TWITCH.OAUTH_TOKEN}` + } + }; + + axios + .request(options) + .then(response => { + document.getElementById('TWITCH_CHANNEL_NAME').disabled = false; + }) + .catch(error => { + console.error(error); + document.getElementById('TWITCH_CHANNEL_NAME').disabled = true; + config.createNotification('Oauth Token is invalid, please obtain a new one', 'error'); + }); +} + +setInterval(checkIfTokenIsValid, 600000); + +if (settings.TWITCH.OAUTH_TOKEN) { + checkIfTokenIsValid(); +} else { + document.getElementById('TWITCH_CHANNEL_NAME').disabled = true; +} + +function ping(element) { + const value = document.body.querySelector(element); + + client + .ping() + .then(data => { + value.classList.add('success'); + value.innerText = 'Success!'; + }) + .catch(e => { + value.classList.add('error'); + value.innerText = 'Failed!'; + }); +} + +async function displayTwitchMessage(logoUrl, username, messageObject, filteredMessage) { + messageId++; + const article = document.createElement('article'); + article.className = 'msg-container sender'; + article.setAttribute('id', messageId); + + article.innerHTML = messageTemplates.twitchTemplate; + const userImg = article.querySelector('.user-img'); + if (userImg) { + userImg.src = logoUrl; + userImg.setAttribute('tip', ''); + } + addSingleTooltip(userImg); + + const usernameHtml = article.querySelector('.username'); + if (usernameHtml) { + usernameHtml.innerText = username; + } + + const postTime = article.querySelector('.post-time'); + + if (postTime) { + postTime.innerText = getPostTime(); + } + + article.appendChild(postTime); + + const formattedMessage = article.querySelector('.msg-box'); + if (formattedMessage) { + messageObject.forEach(entry => { + if (entry.text) { + formattedMessage.innerHTML += entry.text; + } else { + formattedMessage.innerHTML += entry.html; + } + }); + } + + await chat.replaceChatMessageWithCustomEmojis(formattedMessage.innerHTML).then(data => { + formattedMessage.innerHTML = data; + showChatMessage(article); + }); + + if (settings.LANGUAGE.USE_DETECTION) { + await backend.getDetectedLanguage({ message: filteredMessage, messageId, username, logoUrl, formattedMessage }).then(language => { + const languageElement = document.createElement('span'); + languageElement.classList = `fi fi-${language.ISO3166} fis language-icon flag-icon`; + languageElement.setAttribute('tip', language.name); + article.appendChild(languageElement); + addSingleTooltip(languageElement); + + if (filteredMessage && !settings.LANGUAGE.OUTPUT_TO_TTS) { + sound.playVoice({ + filteredMessage, + logoUrl, + username, + formattedMessage, + language: { selectedLanguage: null, detectedLanguage: language } + }); + } + + // window.article = article; + }); + } else { + if (filteredMessage) { + sound.playVoice({ filteredMessage, logoUrl, username, formattedMessage }); + } + + // window.article = article; + } + + sound.playNotificationSound(); +} + +function getProfileImage(userid, username, message, filteredMessage) { + // Get user Logo with access token + options = { + method: 'GET', + url: `https://api.twitch.tv/helix/users?id=${userid}`, + headers: { 'Client-ID': settings.TWITCH.CLIENT_ID, Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}` } + }; + + axios + .request(options) + .then(responseLogoUrl => { + logoUrl = responseLogoUrl.data.data[0].profile_image_url; + displayTwitchMessage(logoUrl, username, message, filteredMessage); + }) + .catch(error => { + console.error(error); + }); +} + +function parseString(inputString) { + const regex = /()|([^<]+)/g; + const matches = inputString.match(regex) || []; + const result = []; + + for (let i = 0; i < matches.length; i++) { + const match = matches[i].trim(); + if (match.startsWith(' { + // throwing the error + // in case of a writing problem + if (error) { + // logging the error + console.error(error); + + throw error; + } + + // console.log('twitch-emotes.json written correctly'); + }); +} + +function formatTwitchEmotes(channel) { + if (channel.emotes.length === 0) { + return; + } + + channel.emotes.forEach(emote => { + if (channel.name !== 'Twitch Global' && emote.emote_type === 'bitstier') { + return; + } + if (channel.name !== 'Twitch Global' && emote.emote_type === 'subscriptions' && parseInt(channel.tier) < parseInt(emote.tier)) { + return; + } + if (channel.name !== 'Twitch Global' && emote.emote_type === 'follower ' && parseInt(channel.tier) === 0) { + return; + } + const emojiToBeAdded = { + name: emote.name, + shortcodes: [emote.name], + url: emote.images.url_1x, + category: channel.broadcaster_name + }; + customEmojis.push(emojiToBeAdded); + }); + emojiPicker.customEmoji = customEmojis; + saveTwitchEmotesToFile(customEmojis); +} + +function saveBetterTtvEmotesToFile(BetterTtvEmotes) { + const data = JSON.stringify(BetterTtvEmotes); + const savePath = + main.isPackaged === true + ? path.join(resourcesPath, './betterttv-emotes.json') + : path.join(resourcesPath, './config/betterttv-emotes.json'); + fs.writeFile(savePath, data, error => { + if (error) { + console.error(error); + + throw error; + } + }); +} + +function formatBetterTtvEmotes(data) { + if (data.emotes.length === 0) { + return; + } + + data.emotes.forEach(emote => { + const emojiToBeAdded = { + name: emote.code, + shortcodes: [emote.code], + url: `https://cdn.betterttv.net/emote/${emote.id}/1x.webp`, + category: data.name + }; + customEmojis.push(emojiToBeAdded); + }); + emojiPicker.customEmoji = customEmojis; + saveBetterTtvEmotesToFile(customEmojis); +} + +function getBetterTtvGLobalEmotes() { + // Get user Logo with access token + options = { + method: 'GET', + url: 'https://api.betterttv.net/3/cached/emotes/global', + headers: {} + }; + + axios + .request(options) + .then(response => { + formatBetterTtvEmotes({ name: 'BetterTTV Global', emotes: response.data }); + }) + .catch(error => { + console.error(error); + }); +} + +function getTwitchUserFollows(paginationToken) { + let url = ''; + if (!paginationToken) { + url = `https://api.twitch.tv/helix/channels/followed?user_id=${settings.TWITCH.USER_ID}&first=100`; + } else { + url = `https://api.twitch.tv/helix/channels/followed?user_id=${settings.TWITCH.USER_ID}&after=${paginationToken}`; + } + options = { + method: 'GET', + url: url, + headers: { + 'Client-ID': settings.TWITCH.CLIENT_ID, + Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}` + } + }; + + axios + .request(options) + .then(responseLogoUrl => { + // console.log(responseLogoUrl); + + responseLogoUrl.data.data.forEach(channel => { + twitchChannels.push({ + broadcaster_id: channel.broadcaster_id, + broadcaster_name: channel.broadcaster_name, + tier: '0' + }); + }); + + if (Object.keys(responseLogoUrl.data.pagination).length !== 0) { + getTwitchUserFollows(responseLogoUrl.data.pagination.cursor); + } else { + getTwitchChannelSubscriptions(twitchChannels); + } + }) + .catch(error => { + console.error(error); + }); +} + +function getTwitchChannelSubscriptions(channels) { + channels.forEach(channel => { + options = { + method: 'GET', + url: `https://api.twitch.tv/helix/subscriptions/user?broadcaster_id=${channel.broadcaster_id}&user_id=${settings.TWITCH.USER_ID}`, + headers: { + 'Client-ID': settings.TWITCH.CLIENT_ID, + Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}` + } + }; + + axios + .request(options) + .then(responseLogoUrl => { + const objIndex = twitchChannels.findIndex(obj => obj.broadcaster_id === channel.broadcaster_id); + twitchChannels[objIndex].tier = responseLogoUrl.data.data[0].tier; + getTwitchChannelEmotes(channel); + }) + .catch(error => { + if (error.response.status !== 404) { + console.error(error); + } + }); + }); +} + +function getTwitchChannelEmotes(channel) { + // Get user Logo with access token + options = { + method: 'GET', + url: `https://api.twitch.tv/helix/chat/emotes?broadcaster_id=${channel.broadcaster_id}`, + headers: { + 'Client-ID': settings.TWITCH.CLIENT_ID, + Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}` + } + }; + + axios + .request(options) + .then(responseLogoUrl => { + if (responseLogoUrl.data.data.length !== 0) { + channel.emotes = responseLogoUrl.data.data; + formatTwitchEmotes(channel); + } + }) + .catch(error => { + console.error(error); + }); +} + +function getTwitchGlobalEmotes() { + // Get user Logo with access token + options = { + method: 'GET', + url: 'https://api.twitch.tv/helix/chat/emotes/global', + headers: { + 'Client-ID': settings.TWITCH.CLIENT_ID, + Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}` + } + }; + + axios + .request(options) + .then(responseLogoUrl => { + formatTwitchEmotes({ broadcaster_name: 'Twitch Global', emotes: responseLogoUrl.data.data }); + }) + .catch(error => { + console.error(error); + }); +} + +async function getUserAvailableTwitchEmotes() { + if (settings.TWITCH.OAUTH_TOKEN) { + await getTwitchGlobalEmotes(); + await getTwitchUserFollows(); + await getTwitchChannelEmotes({ + broadcaster_id: settings.TWITCH.USER_ID, + broadcaster_name: settings.TWITCH.USERNAME, + tier: '3000' + }); + await getTwitchChannelEmotes({ + broadcaster_id: settings.TWITCH.CHANNEL_USER_ID, + broadcaster_name: settings.TWITCH.CHANNEL_NAME, + tier: '1' + }); + } +} + +function getTwitchChannelId() { + options = { + method: 'GET', + url: `https://api.twitch.tv/helix/users?login=${settings.TWITCH.CHANNEL_NAME}`, + headers: { + 'Client-ID': settings.TWITCH.CLIENT_ID, + Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}` + } + }; + + axios + .request(options) + .then(responseLogoUrl => { + settings.TWITCH.CHANNEL_USER_ID = responseLogoUrl.data.data[0].id; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + config.createNotification('Obtained channel info succesfully', 'success'); + getUserAvailableTwitchEmotes(); + }) + .catch(error => { + console.error(error); + config.createNotification('could not obtain channel info, please try again', 'error'); + }); +} + +function getTwitchUserId() { + // Get user Logo with access token + options = { + method: 'GET', + url: 'https://api.twitch.tv/helix/users', + headers: { + 'Client-ID': settings.TWITCH.CLIENT_ID, + Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}` + } + }; + + axios + .request(options) + .then(responseLogoUrl => { + // console.log(responseLogoUrl.data.data[0]); + settings.TWITCH.USERNAME = responseLogoUrl.data.data[0].display_name; + settings.TWITCH.USER_LOGO_URL = responseLogoUrl.data.data[0].profile_image_url; + settings.TWITCH.USER_ID = responseLogoUrl.data.data[0].id; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + config.createNotification('Obtained user info succesfully', 'success'); + }) + .catch(error => { + console.error(error); + config.createNotification('could not obtain user info, please try again', 'error'); + }); +} + +// const Sockette = require('sockette'); + +// const ws = new Sockette('wss://eventsub.wss.twitch.tv/ws', { +// timeout: 5e3, +// maxAttempts: 10, +// onopen: e => console.log('Connected!', e), +// onmessage: e => console.log('Received:', e), +// onreconnect: e => console.log('Reconnecting...', e), +// onmaximum: e => console.log('Stop Attempting!', e), +// onclose: e => console.log('Closed!', e), +// onerror: e => console.log('Error:', e) +// }); + +// ws.send('Hello, world!'); +// ws.json({ type: 'ping' }); +// ws.close(); // graceful shutdown + +// Reconnect 10s later +// setTimeout(ws.reconnect, 10e3); + +module.exports = { + sendMessage, + ping, + client, + getBetterTtvGLobalEmotes, + getUserAvailableTwitchEmotes, + getTwitchChannelId, + getTwitchUserId, + checkIfTokenIsValid +}; diff --git a/src/main.js b/src/main.js index 0bd5fab..2fcf9b0 100644 --- a/src/main.js +++ b/src/main.js @@ -1,3 +1,5 @@ +/* global pythonPath, a */ + const { app, BrowserWindow, ipcMain } = require('electron'); const { writeIniFile } = require('write-ini-file'); const path = require('path'); @@ -14,197 +16,208 @@ let settings; let window; if (app.isPackaged) { - settingsPath = path.join(process.resourcesPath, './settings.ini'); - pythonPath = path.join(process.resourcesPath, './backend'); - resourcesPath = process.resourcesPath; + settingsPath = path.join(process.resourcesPath, './settings.ini'); + pythonPath = path.join(process.resourcesPath, './backend'); + resourcesPath = process.resourcesPath; } else { - settingsPath = path.join(resourcesPath, './config/settings.ini'); - pythonPath = path.join(resourcesPath, './backend'); -} - -// Handle creating/removing shortcuts on Windows when installing/uninstalling. -if (require('electron-squirrel-startup')) { - app.quit(); + settingsPath = path.join(resourcesPath, './config/settings.ini'); + pythonPath = path.join(resourcesPath, './backend'); } async function createWindow() { - if (!fs.existsSync(settingsPath)) { - console.log(resourcesPath); - await createIniFile(); - } else { - settings = ini.parse(fs.readFileSync(settingsPath, 'utf-8')); + if (!fs.existsSync(settingsPath)) { + console.log(resourcesPath); + await createIniFile(); + } else { + settings = ini.parse(fs.readFileSync(settingsPath, 'utf-8')); + } + + window = new BrowserWindow({ + icon: path.join(__dirname, '/images/icon-512.png'), + width: parseInt(settings.GENERAL.WIDTH), + height: parseInt(settings.GENERAL.HEIGHT), + x: parseInt(settings.GENERAL.POSITION_X), + y: parseInt(settings.GENERAL.POSITION_Y), + frame: false, + webPreferences: { + nodeIntegration: true, + contextIsolation: false, + enableRemoteModule: true } + }); - window = new BrowserWindow({ - icon: path.join(__dirname, '/images/icon-512.png'), - width: parseInt(settings.GENERAL.WIDTH), - height: parseInt(settings.GENERAL.HEIGHT), - x: parseInt(settings.GENERAL.POSITION_X), - y: parseInt(settings.GENERAL.POSITION_Y), - frame: false, - webPreferences: { - nodeIntegration: true, - contextIsolation: false, - enableRemoteModule: true, - }, - }); + window.loadFile(path.join(__dirname, 'index.html')); - window.loadFile(path.join(__dirname, 'index.html')); + if (!app.isPackaged) { + window.webContents.openDevTools(); + } - if (!app.isPackaged) { - window.webContents.openDevTools(); - } + window.on('close', e => { + settings = ini.parse(fs.readFileSync(settingsPath, 'utf-8')); // load newest settings in case anything changed after starting the program + const bounds = window.getBounds(); - window.on('close', (e) => { - settings = ini.parse(fs.readFileSync(settingsPath, 'utf-8')); // load newest settings in case anything changed after starting the program - const bounds = window.getBounds(); + settings.GENERAL.WIDTH = bounds.width; + settings.GENERAL.HEIGHT = bounds.height; + settings.GENERAL.POSITION_X = bounds.x; + settings.GENERAL.POSITION_Y = bounds.y; - settings.GENERAL.WIDTH = bounds.width; - settings.GENERAL.HEIGHT = bounds.height; - settings.GENERAL.POSITION_X = bounds.x; - settings.GENERAL.POSITION_Y = bounds.y; - - fs.writeFileSync(settingsPath, ini.stringify(settings)); - }); + fs.writeFileSync(settingsPath, ini.stringify(settings)); + }); } app.whenReady().then(() => { - createWindow(); + createWindow(); }); -app.on('window-all-closed', (event) => { - if (process.platform !== 'darwin') { - app.quit(); - } +app.on('window-all-closed', event => { + if (process.platform !== 'darwin') { + kill('loquendoBot_backend'); + app.quit(); + } }); app.on('activate', () => { - if (BrowserWindow.getAllWindows().length === 0) { - createWindow(); - } + if (BrowserWindow.getAllWindows().length === 0) { + createWindow(); + } }); app.on('before-quit', () => { - window.webContents.send('quit-event'); + window.webContents.send('quit-event'); + kill('loquendoBot_backend'); }); ipcMain.on('resize-window', (event, width, height) => { - const browserWindow = BrowserWindow.fromWebContents(event.sender); - browserWindow.setSize(width, height); + const browserWindow = BrowserWindow.fromWebContents(event.sender); + browserWindow.setSize(width, height); }); -ipcMain.on('minimize-window', (event) => { - const browserWindow = BrowserWindow.fromWebContents(event.sender); - browserWindow.minimize(); +ipcMain.on('minimize-window', event => { + const browserWindow = BrowserWindow.fromWebContents(event.sender); + browserWindow.minimize(); }); -ipcMain.on('maximize-window', (event) => { - const browserWindow = BrowserWindow.fromWebContents(event.sender); +ipcMain.on('maximize-window', event => { + const browserWindow = BrowserWindow.fromWebContents(event.sender); - if (!browserWindow.isMaximized()) { - browserWindow.maximize(); - } else { - browserWindow.unmaximize(); - } + if (!browserWindow.isMaximized()) { + browserWindow.maximize(); + } else { + browserWindow.unmaximize(); + } }); -ipcMain.on('close-window', (event) => { - const browserWindow = BrowserWindow.fromWebContents(event.sender); - kill('loquendoBot_backend'); - browserWindow.close(); - app.quit(); +ipcMain.on('close-window', event => { + const browserWindow = BrowserWindow.fromWebContents(event.sender); + kill('loquendoBot_backend'); + browserWindow.close(); + app.quit(); }); -ipcMain.on('restart', (event) => { - app.relaunch(); +ipcMain.on('restart', event => { + app.relaunch(); }); -ipcMain.on('environment', (event) => { - event.returnValue = { resourcesPath, pythonPath, settingsPath, settings, isPackaged: app.isPackaged }; +ipcMain.on('environment', event => { + event.returnValue = { resourcesPath, pythonPath, settingsPath, settings, isPackaged: app.isPackaged }; }); async function createIniFile() { - await writeIniFile(settingsPath, { - GENERAL: { - VOICE_ENABLED: true, - NOTIFICATION_ENABLED: true, - POSITION_X: 0, - POSITION_Y: 0, - WIDTH: 1024, - HEIGHT: 768, - LANGUAGE: 'EN', - PORT: 9000, - VIEWERS_PANEL: false, - LOCATION: pythonPath, - }, - LANGUAGE: { - USE_DETECTION: false, - }, - TTS: { - USE_TTS: true, - PRIMARY_VOICE: '', - PRIMARY_TTS_LANGUAGE: 'EN', - SECONDARY_VOICE: '', - SECONDARY_TTS_LANGUAGE: 'EN', - }, - STT: { - USE_STT: false, - MICROPHONE_ID: 'default', - SELECTED_MICROPHONE: 'default', - MICROPHONE: 0, - LANGUAGE: 'vosk-model-small-es-0.42', - }, - AUDIO: { - USE_NOTIFICATION_SOUNDS: true, - NOTIFICATION_AUDIO_DEVICE: 0, - NOTIFICATION_SOUND: 0, - NOTIFICATION_VOLUME: 50, - SELECTED_TTS_AUDIO_DEVICE: 0, - TTS_AUDIO_DEVICE: 'default', - TTS_VOLUME: 50, - }, - THEME: { - USE_CUSTOM_THEME: false, - MAIN_COLOR_1: '#cdc1c1', - MAIN_COLOR_2: '#b12020', - MAIN_COLOR_3: '#6c4104', - MAIN_COLOR_4: '#532d2d', - TOP_BAR: '#c8ff00', - MID_SECTION: '#6b8578', - CHAT_BUBBLE_BG: '#447466', - CHAT_BUBBLE_HEADER: '#ffffff', - CHAT_BUBBLE_MESSAGE: '#b5b5b5', - }, - TWITCH: { - USE_TWITCH: false, - CHANNEL_NAME: '', - USERNAME: '', - USER_ID: '', - USER_LOGO_URL: '', - OAUTH_TOKEN: '', - CLIENT_ID: '2t206sj7rvtr1rutob3p627d13jch9', - }, - MODULES: { - USE_MODULES: false, - USE_VTUBER: false, - USE_CHATBUBBLE: false, - }, - AMAZON: { - USE_AMAZON: false, - ACCESS_KEY: '', - ACCESS_SECRET: '', - PRIMARY_VOICE: '', - SECONDARY_VOICE: '', - CHARACTERS_USED: 0, - }, - GOOGLE: { - USE_GOOGLE: false, - API_KEY: '', - PRIMARY_VOICE: '', - SECONDARY_VOICE: '', - CHARACTERS_USED: 0, - }, - }).then(() => { - settings = ini.parse(fs.readFileSync(settingsPath, 'utf-8')); - }); + await writeIniFile(settingsPath, { + GENERAL: { + VOICE_ENABLED: true, + NOTIFICATION_ENABLED: true, + POSITION_X: 0, + POSITION_Y: 0, + WIDTH: 1024, + HEIGHT: 768, + LANGUAGE: 'none', + LANGUAGE_INDEX: '0', + PORT: 9000, + VIEWERS_PANEL: false, + LOCATION: pythonPath, + ZOOMLEVEL: 1 + }, + LANGUAGE: { + USE_DETECTION: false, + TRANSLATE_TO: 'none', + LANGUAGE_INDEX: '0', + BROADCAST_TRANSLATION: false, + OUTPUT_TO_TTS: false, + TRANSLATE_TO_INDEX: 0, + SEND_TRANSLATION: false, + SEND_TRANSLATION_IN: 'none', + SEND_TRANSLATION_OUT: 'none' + }, + TTS: { + USE_TTS: false, + PRIMARY_VOICE: '', + PRIMARY_TTS_LANGUAGE: 'none', + SECONDARY_VOICE: '', + SECONDARY_TTS_LANGUAGE: 'none', + PRIMARY_TTS_LANGUAGE_INDEX: 0, + SECONDARY_TTS_LANGUAGE_INDEX: 0 + }, + STT: { + USE_STT: false, + MICROPHONE_ID: 'default', + SELECTED_MICROPHONE: 'default', + MICROPHONE: 0, + LANGUAGE: '' + }, + AUDIO: { + USE_NOTIFICATION_SOUNDS: false, + SELECTED_NOTIFICATION_AUDIO_DEVICE: 'default', + NOTIFICATION_AUDIO_DEVICE: 0, + NOTIFICATION_SOUND: 0, + NOTIFICATION_VOLUME: 50, + SELECTED_TTS_AUDIO_DEVICE: 0, + TTS_AUDIO_DEVICE: 'default', + TTS_VOLUME: 50 + }, + THEME: { + USE_CUSTOM_THEME: false, + MAIN_COLOR_1: '#cdc1c1', + MAIN_COLOR_2: '#b12020', + MAIN_COLOR_3: '#6c4104', + MAIN_COLOR_4: '#532d2d', + TOP_BAR: '#c8ff00', + MID_SECTION: '#6b8578', + CHAT_BUBBLE_BG: '#447466', + CHAT_BUBBLE_HEADER: '#ffffff', + CHAT_BUBBLE_MESSAGE: '#b5b5b5' + }, + TWITCH: { + USE_TWITCH: false, + CHANNEL_NAME: '', + CHANNEL_USER_ID: '', + USERNAME: '', + USER_ID: '', + USER_LOGO_URL: '', + OAUTH_TOKEN: '', + CLIENT_ID: '2t206sj7rvtr1rutob3p627d13jch9' + }, + MODULES: { + USE_MODULES: false, + USE_VTUBER: false, + USE_CHATBUBBLE: false + }, + AMAZON: { + USE_AMAZON: false, + ACCESS_KEY: '', + ACCESS_SECRET: '', + PRIMARY_VOICE: '', + SECONDARY_VOICE: '', + CHARACTERS_USED: 0 + }, + GOOGLE: { + USE_GOOGLE: false, + API_KEY: '', + PRIMARY_VOICE: '', + SECONDARY_VOICE: '', + CHARACTERS_USED: 0 + } + }).then(() => { + settings = ini.parse(fs.readFileSync(settingsPath, 'utf-8')); + }); } diff --git a/src/modules/chat/index.html b/src/modules/chat/index.html index c961cd4..81150e7 100644 --- a/src/modules/chat/index.html +++ b/src/modules/chat/index.html @@ -1,23 +1,23 @@ - + - - Chat - - - - - - -
          -
          -
          -
          -
          - - - + + Chat + + + + + + +
          +
          +
          +
          +
          + + + diff --git a/src/modules/chat/main.css b/src/modules/chat/main.css index bea4e5b..e5dde10 100644 --- a/src/modules/chat/main.css +++ b/src/modules/chat/main.css @@ -1,171 +1,174 @@ body { - background-color: transparent; - font-family: 'FRAMDCN'; + background-color: transparent; + font-family: 'FRAMDCN'; } :root { - --variable: 2s; - --buttonBackground: #bf2c2c; + --variable: 2s; + --buttonBackground: #bf2c2c; } .thomas { - position: relative; - float: center; - display: inline-block; + position: relative; + float: center; + /* display: inline-block; */ } .speechbubble { - display: block; - bottom: 0; - position: absolute; - z-index: -1; + display: block; + bottom: 0; + position: absolute; + z-index: -1; } .fade-outx { - animation: fade-outx var(--variable) linear; + animation: fade-outx var(--variable) linear; } @keyframes fade-outx { - from { - opacity: 1; - } + from { + opacity: 1; + } - to { - opacity: 0; - } + to { + opacity: 0; + } } .fade-outxx { - animation: fade-outxx var(--variable) linear; + animation: fade-outxx var(--variable) linear; } @keyframes fade-outxx { - from { - opacity: 1; - } + from { + opacity: 1; + } - to { - opacity: 0; - } + to { + opacity: 0; + } } .bounce-in { - animation: bounce-in 1s ease; + animation: bounce-in 1s ease; } @keyframes bounce-in { - 0% { - opacity: 0; - transform: scale(0.3); - } + 0% { + opacity: 0; + transform: scale(0.3); + } - 50% { - opacity: 1; - transform: scale(1.05); - } + 50% { + opacity: 1; + transform: scale(1.05); + } - 70% { - transform: scale(0.9); - } + 70% { + transform: scale(0.9); + } - 100% { - transform: scale(1); - } + 100% { + transform: scale(1); + } } .bounce-inx { - animation: bounce-inx 1s ease; + animation: bounce-inx 1s ease; } @keyframes bounce-inx { - 0% { - opacity: 0; - } + 0% { + opacity: 0; + } - 50% { - opacity: 1; - } + 50% { + opacity: 1; + } } .msg-container { - position: static; - display: inline-block; - width: 100%; - padding-top: 10px; + direction: ltr; + position: static; + width: 100%; + padding: 10px 0px 0px 0px; + display: grid; + grid-template: 1fr / 1fr; + align-self: center; + width: fit-content; +} + +.msg-container > * { + grid-column: 1 / 1; + grid-row: 1 / 1; } .message-window { - height: calc(100% - 50px); - overflow: hidden; - overflow-y: hidden; - display: flex; - flex-direction: column; - width: 80%; - margin: auto; - background: transparent; + height: calc(100% - 50px); + overflow: hidden; + overflow-y: hidden; + display: flex; + flex-direction: column; + width: 80%; + margin: auto; + background: transparent; } .message-window::before { - content: ''; - flex: 1 0 0px; + content: ''; + flex: 1 0 0px; } .OptionPanel { - flex: 3; - display: none; - position: absolute; - top: 10px; - left: 0; - width: 100%; - height: calc(100% - 25px); - background: transparent; + flex: 3; + display: none; + position: absolute; + top: 10px; + left: 0; + width: 100%; + height: calc(100% - 25px); + background: transparent; } .OptionPanel.show { - display: block; + display: block; } .message { - text-align: left; - max-width: 100%; - height: auto; - min-width: 125px; - hyphens: auto; - bottom: 0; - right: 0; - float: right; - overflow-wrap: break-word; -} - -.message { - position: relative; - border: 2px solid #ff80e1; - box-shadow: 0 2px 10px rgba(255, 128, 225, 0.5); - background: linear-gradient(45deg, rgb(15, 12, 41, 0.7), rgb(48, 43, 99, 0.7)); - /* background: linear-gradient(45deg, rgba(72, 0, 154, 0.7), rgba(138, 43, 226, 0.7)); */ - - color: white; - padding: 15px; - border-radius: 20px; -} - -.message::after { + text-align: left; + max-width: 100%; + height: auto; + min-width: fit-content; + hyphens: auto; + /* bottom: 0; */ + /* right: 0; */ + /* float: right; */ + overflow-wrap: break-word; + position: relative; + border: 2px solid #ff80e1; + /* box-shadow: 0 2px 10px rgba(255, 128, 225, 0.5); */ + background: linear-gradient(45deg, rgb(15, 12, 41, 0.7), rgb(48, 43, 99, 0.7)); + /* background: linear-gradient(45deg, rgba(72, 0, 154, 0.7), rgba(138, 43, 226, 0.7)); */ + color: white; + padding: 15px; + border-radius: 20px; + margin-bottom: 10px; } .arrow { - content: ''; - border: 2px solid #ff80e1; - position: absolute; - left: 50%; - top: 100%; - transform: translateX(-50%) rotate(180deg); - border-width: 10px; - border-style: solid; - border-color: transparent transparent rgb(255, 128, 225, 0.7) transparent; - color: #ff80e1; + content: ''; + border: 2px solid #ff80e1; + position: absolute; + left: 50%; + transform: translateX(-50%) rotate(180deg); + border-width: 10px; + border-style: solid; + border-color: transparent transparent rgb(255, 128, 225, 0.7) transparent; + color: #ff80e1; + bottom: -10px; } .sender { - color: #ff80e1; - font-size: 14pt; + color: #ff80e1; + font-size: 14pt; } diff --git a/src/modules/chat/main.js b/src/modules/chat/main.js index d4e92b9..de5c8db 100644 --- a/src/modules/chat/main.js +++ b/src/modules/chat/main.js @@ -1,3 +1,5 @@ +/* global io */ + // Connect to the Socket.IO server const socket = io(); @@ -5,18 +7,18 @@ const socket = io(); socket.emit('message', 'Hello, Server!'); function getPostTime() { - const d = new Date(); - document.body.querySelectorAll('.container').innerHTML = d.getHours(); - const hours = d.getHours(); - const minutes = (d.getMinutes() < 10 ? '0' : '') + d.getMinutes(); - const time = `${hours}:${minutes}`; - return time; + const d = new Date(); + document.body.querySelectorAll('.container').innerHTML = d.getHours(); + const hours = d.getHours(); + const minutes = (d.getMinutes() < 10 ? '0' : '') + d.getMinutes(); + const time = `${hours}:${minutes}`; + return time; } function showChatMessage(article) { - const main = document.querySelector('#chatBox'); - main.appendChild(article); - main.scrollTop = main.scrollHeight; + const main = document.querySelector('#chatBox'); + main.appendChild(article); + main.scrollTop = main.scrollHeight; } let textStreamContainer; @@ -28,96 +30,96 @@ let x; let currentIndex = 0; let messageStream = ''; -let tempMessageObject = ''; -let fullMessageLength = 0; +const tempMessageObject = ''; +const fullMessageLength = 0; function getFullMessageLength(text) { - let fullMessageLength = 0; - text.forEach((element) => { - if (element.text) { - fullMessageLength += element.text.length; - } - element.html; - fullMessageLength += 1; - }); + let fullMessageLength = 0; + text.forEach(element => { + if (element.text) { + fullMessageLength += element.text.length; + } + // element.html; + fullMessageLength += 1; + }); - return fullMessageLength; + return fullMessageLength; } function streamText() { - // if (currentIndex < fullMessageLength) { + // if (currentIndex < fullMessageLength) { - // textStreamContainer.innerHTML += messageStream.filtered.charAt(currentIndex); - // currentIndex++; - // setTimeout(streamText, 50); - // } - if (currentIndex < messageStream.length) { - textStreamContainer.innerHTML += messageStream.charAt(currentIndex); - currentIndex++; - setTimeout(streamText, 50); - } else { - currentIndex = 0; - x.classList.add('fade-outx'); - } + // textStreamContainer.innerHTML += messageStream.filtered.charAt(currentIndex); + // currentIndex++; + // setTimeout(streamText, 50); + // } + if (currentIndex < messageStream.length) { + textStreamContainer.innerHTML += messageStream.charAt(currentIndex); + currentIndex++; + setTimeout(streamText, 50); + } else { + currentIndex = 0; + x.classList.add('fade-outx'); + } } -function displayTwitchMessage(logoUrl, username, messageObject) { - if (!messageObject) { - return; +function displayTwitchMessage(message) { + if (!message.filteredMessage) { + return; + } + + const root = document.querySelector(':root'); + root.style.setProperty('--variable', '5s'); + + const article = document.createElement('article'); + x = article; + + article.className = 'msg-container'; + + const placeMessage = ` +
          +
          +
          +
          +
          +
          + `.trim(); + + article.innerHTML = placeMessage; + const msg = article.querySelector('.message'); + + msg.innerHTML = `
          ${message.username}
          `; // \n${message}`; + + msg.style.fontSize = '12pt'; + + showChatMessage(article); + + const elements = document.getElementsByClassName('msg-container'); + if (elements.length > 1) { + elements[0].remove(); + } + + article.addEventListener('animationend', e => { + if (e.animationName === 'fade-outx') { + article.remove(); } + }); - const root = document.querySelector(':root'); - root.style.setProperty('--variable', '5s'); - - const article = document.createElement('article'); - x = article; - - article.className = 'msg-container'; - - const placeMessage = ` -
          -
          -
          -
          -
          -
          - `.trim(); - - article.innerHTML = placeMessage; - const msg = article.querySelector('.message'); - - msg.innerHTML = `
          ${username}
          `; //\n${message}`; - - msg.style.fontSize = '12pt'; - - showChatMessage(article); - - const elements = document.getElementsByClassName('msg-container'); - if (elements.length > 1) { + if (elements.length > 1) { + elements[0].classList.add('fade-outxx'); + elements[0].addEventListener('animationend', e => { + if (e.animationName === 'fade-outxx') { elements[0].remove(); - } - - article.addEventListener('animationend', (e) => { - if (e.animationName == 'fade-outx') { - article.remove(); - } + } }); - - if (elements.length > 1) { - elements[0].classList.add('fade-outxx'); - elements[0].addEventListener('animationend', (e) => { - if (e.animationName == 'fade-outxx') { - elements[0].remove(); - } - }); - } - // fullMessageLength = getFullMessageLength(messageObject); - messageStream = messageObject.filtered; - textStreamContainer = document.querySelector('.message'); - streamText(); + } + // fullMessageLength = getFullMessageLength(messageObject); + messageStream = message.filteredMessage; + textStreamContainer = document.querySelector('.message'); + streamText(); } // // Receive a message from the server -socket.on('message', (logoUrl, username, message, messageDuration) => { - displayTwitchMessage(logoUrl, username, message); +socket.on('message', message => { + displayTwitchMessage(message); }); diff --git a/src/modules/vtuber/css/about.css b/src/modules/vtuber/css/about.css index 768ed15..8db9f7a 100644 --- a/src/modules/vtuber/css/about.css +++ b/src/modules/vtuber/css/about.css @@ -20,8 +20,8 @@ body { padding-left: 40dip; padding-right: 10dip; vertical-align: top; - foreground-repeat: no-repeat; - foreground-position: 16dip 50%; + /* foreground-repeat: no-repeat; + foreground-position: 16dip 50%; */ background-repeat: no-repeat; background-position: 10dip 50%; background-size: 64dip 64dip; @@ -37,7 +37,7 @@ body { .title { font-weight: bold !important; - flow: column !important; + /* flow: column !important; */ text-align: left !important; margin: auto !important; width: min-content !important; @@ -86,12 +86,12 @@ img { } #button-bar { - flow: horizontal; + /* flow: horizontal; */ padding: 10dip; border-spacing: 10dip; margin: 0; - flow: horizontal; - horizontal-align: right; + /* flow: horizontal; */ + /* horizontal-align: right; */ } label { @@ -103,7 +103,7 @@ label { text-shadow: #fff 0px 1px; min-width: 4em; line-height: 2em; - vertical-align: middle; + /* vertical-align: middle; */ width: min-intrinsic; text-align: center; -} \ No newline at end of file +} diff --git a/src/modules/vtuber/css/main.css b/src/modules/vtuber/css/main.css index daf7def..59c2c06 100644 --- a/src/modules/vtuber/css/main.css +++ b/src/modules/vtuber/css/main.css @@ -1,386 +1,380 @@ * { - box-sizing: border-box; - image-rendering: optimize-quality; + box-sizing: border-box; + image-rendering: optimize-quality; } :disabled { - display: none; + display: none; } html { - overflow: hidden; + overflow: hidden; } body { - margin: 0; - padding: 16dip; - position: relative; - background-image: url('../png/grid.png'); - background-size: 70px; - background-repeat: repeat; + margin: 0; + padding: 16dip; + position: relative; + background-image: url('../png/grid.png'); + background-size: 70px; + background-repeat: repeat; } #controls { - height: 100%; - width: 100%; - /* flow: horizontal; */ - overflow: hidden; - display: flex; + height: 100%; + width: 100%; + /* flow: horizontal; */ + overflow: hidden; + display: flex; } #controls-left { - order: 2px solid cyan; - width: 7%; - height: 10%; - /* flow: vertical; */ - border-spacing: 1%; + order: 2px solid cyan; + width: 7%; + height: 10%; + /* flow: vertical; */ + border-spacing: 1%; } #controls-top { - height: 10%; - width: 100%; - /* flow: horizontal; */ - border-spacing: 1%; - margin-left: 10px; + height: 10%; + width: 100%; + /* flow: horizontal; */ + border-spacing: 1%; + margin-left: 10px; } #meters { - display: flex; - height: 50vh; - /* flow: horizontal; */ - /* border-spacing: *; */ - position: relative; + display: flex; + height: 50vh; + /* flow: horizontal; */ + /* border-spacing: *; */ + position: relative; } #meter-microphone { - margin-left: 10%; - margin-right: 10%; + margin-left: 10%; + margin-right: 10%; } #meter-delay { - margin-right: 20%; + margin-right: 20%; } #meter-microphone, #meter-delay { - height: 45vh; - width: 25%; - background-repeat: no-repeat; + height: 45vh; + width: 25%; + background-repeat: no-repeat; } -input[type='vslider']>button.slider { - background-size: 100% auto; - border-radius: 0; - width: 275%; - height: 5%; - background-position: 0 50%; - border-width: 0; +input[type='vslider'] > button.slider { + background-size: 100% auto; + border-radius: 0; + width: 275%; + height: 5%; + background-position: 0 50%; + border-width: 0; } button { - height: 50px; - width: 50px; - background-size: cover; - overflow: hidden; - border-style: none; - background-color: transparent; + height: 50px; + width: 50px; + background-size: cover; + overflow: hidden; + border-style: none; + background-color: transparent; } -#meter-microphone>button.slider { - background-image: url('../png/controls/meters/left.png'); +#meter-microphone > button.slider { + background-image: url('../png/controls/meters/left.png'); } -#meter-delay>button.slider { - background-image: url('../png/controls/meters/right.png'); +#meter-delay > button.slider { + background-image: url('../png/controls/meters/right.png'); } #meter-microphone { - color: #2ebe38; + color: #2ebe38; } #meter-delay { - color: #453be2; + color: #453be2; } #slider-left { - left: -25%; + left: -25%; } #slider-right { - right: -25%; + right: -25%; } #buttons-left { - width: 100%; - height: 100%; - box-shadow: none; + width: 100%; + height: 100%; + box-shadow: none; } #buttons-top { - height: 50px; - width: 100%; - display: flex; + height: 50px; + width: 100%; + display: flex; } #buttons-left button { - width: 50; - height: 50px; - display: flex; - background-size: cover; - overflow: hidden; + width: 50; + height: 50px; + display: flex; + background-size: cover; + overflow: hidden; } #buttons-top button { - background-size: auto 100%; - background-position: 50% 50%; - border-width: 0; - border-radius: 0; + background-size: auto 100%; + background-position: 50% 50%; + border-width: 0; + border-radius: 0; } #buttons-left button:active, #about:active, #close:active { - filter: brightness(-10%); + filter: brightness(-10%); } popup[role='tooltip'] { - color: white; - background: rgba(0, 0, 0, 0.666); - border-width: 0; - padding: 0.333em; - font-size: 1.25em; - font-family: Calibri; + color: white; + background: rgba(0, 0, 0, 0.666); + border-width: 0; + padding: 0.333em; + font-size: 1.25em; + font-family: Calibri; } #microphone-device { - background-image: url('../png/controls/buttons/left/mic.png'); + background-image: url('../png/controls/buttons/left/mic.png'); } #background-color { - background-image: url('../png/controls/buttons/left/bg.png'); + background-image: url('../png/controls/buttons/left/bg.png'); } #open-file { - background-image: url('../png/controls/buttons/left/open.png'); + background-image: url('../png/controls/buttons/left/open.png'); } #save-file { - background-image: url('../png/controls/buttons/left/save.png'); + background-image: url('../png/controls/buttons/left/save.png'); } /* TOP ROW BUTTONS */ button.mouth-image.border-default { - background-image: url('../png/controls/buttons/top/avatar-change/border/default.png'); - position: absolute; - width: 50px; - height: 50px; - top: 0; - bottom: 0; + background-image: url('../png/controls/buttons/top/avatar-change/border/default.png'); + position: absolute; + width: 50px; + height: 50px; + top: 0; + bottom: 0; } button.mouth-image.border-add { - background-image: url('../png/controls/buttons/top/avatar-change/border/add.png'); + background-image: url('../png/controls/buttons/top/avatar-change/border/add.png'); } button.mouth-image:active { - filter: brightness(-10%); + filter: brightness(-10%); } button.mouth-image::before { - position: absolute; - top: 5%; - right: 10%; - width: 50px; - height: 66%; - background-size: 100% 100%; - z-index: -1; + position: absolute; + top: 5%; + right: 10%; + width: 50px; + height: 66%; + background-size: 100% 100%; + z-index: -1; } button.mouth-image::after { - position: absolute; - top: 69%; - left: 37%; - width: 50px; - height: 35%; - transform: translate(-50%, -50%); - background-size: 100% 100%; + position: absolute; + top: 69%; + left: 37%; + width: 50px; + height: 35%; + transform: translate(-50%, -50%); + background-size: 100% 100%; } #closed-mouth-image.border-default::before { - background-image: url('../png/avatars/crab/closed.png'); + background-image: url('../png/avatars/crab/closed.png'); } #closed-mouth-image::after { - background-image: url('../png/controls/buttons/top/avatar-change/closed.png'); + background-image: url('../png/controls/buttons/top/avatar-change/closed.png'); } #open-mouth-image.border-default::before { - background-image: url('../png/avatars/crab/open.png'); + background-image: url('../png/avatars/crab/open.png'); } #open-mouth-image::after { - background-image: url('../png/controls/buttons/top/avatar-change/open.png'); + background-image: url('../png/controls/buttons/top/avatar-change/open.png'); } #closed-mouth-blinking-image.border-default::before { - background-image: url('../png/avatars/crab/closed-blink.png'); + background-image: url('../png/avatars/crab/closed-blink.png'); } #closed-mouth-blinking-image::after { - background-image: url('../png/controls/buttons/top/avatar-change/closed-blink.png'); + background-image: url('../png/controls/buttons/top/avatar-change/closed-blink.png'); } #open-mouth-blinking-image.border-default::before { - background-image: url('../png/avatars/crab/open-blink.png'); + background-image: url('../png/avatars/crab/open-blink.png'); } #open-mouth-blinking-image::after { - background-image: url('../png/controls/buttons/top/avatar-change/open-blink.png'); + background-image: url('../png/controls/buttons/top/avatar-change/open-blink.png'); } button.motion { - /* background-image: url('../png/controls/buttons/top/motion/template.png'); */ - position: relative; - /* var(x): 0dip; + /* background-image: url('../png/controls/buttons/top/motion/template.png'); */ + position: relative; + /* var(x): 0dip; var(y): height(33%); */ - height: 50px; - width: 50px; + height: 50px; + width: 50px; } - .closed-mouth-motion { - background-image: url('../png/controls/buttons/top/motion/closed.png'); - box-sizing: border-box; - image-rendering: optimize-quality; - height: 50px; - width: 50px; - background-color: #9bccd4; + background-image: url('../png/controls/buttons/top/motion/closed.png'); + box-sizing: border-box; + image-rendering: optimize-quality; + height: 50px; + width: 50px; + background-color: #9bccd4; } .test { - width: 50px; - height: 50px; - background-size: cover; - overflow: hidden; + width: 50px; + height: 50px; + background-size: cover; + overflow: hidden; } .avatar-change { - background-image: url('../png/controls/buttons/top/avatar-change/border/default.png'); - background-size: cover; - width: 50px; - height: 50px; - position: relative; - left: -5px; + background-image: url('../png/controls/buttons/top/avatar-change/border/default.png'); + background-size: cover; + width: 50px; + height: 50px; + position: relative; + left: -5px; } .border { - background-image: url('../png/controls/buttons/top/motion/border.png'); - background-size: cover; - width: 50px; - height: 50px; - position: relative; - top: -50px; + background-image: url('../png/controls/buttons/top/motion/border.png'); + background-size: cover; + width: 50px; + height: 50px; + position: relative; + top: -50px; } .open-mouth-motion { - background-image: url('../png/controls/buttons/top/motion/open.png'); - box-sizing: border-box; - image-rendering: optimize-quality; - height: 50px; - width: 50px; + background-image: url('../png/controls/buttons/top/motion/open.png'); + box-sizing: border-box; + image-rendering: optimize-quality; + height: 50px; + width: 50px; } .mouth-transition { - background-image: url('../png/controls/buttons/top/avatar-change/border/default.png'); - box-sizing: border-box; - image-rendering: optimize-quality; - height: 50px; - width: 50px; - background-position: 50% 50%; - background-color: yellow; + background-image: url('../png/controls/buttons/top/avatar-change/border/default.png'); + box-sizing: border-box; + image-rendering: optimize-quality; + height: 50px; + width: 50px; + background-position: 50% 50%; + background-color: yellow; } .mouth-transitionx { - background-image: url('../png/controls/buttons/top/avatar-change/border/default.png'); - box-sizing: border-box; - image-rendering: optimize-quality; - height: 50px; - width: 50px; - background-position: 50% 50%; - background-color: red; + background-image: url('../png/controls/buttons/top/avatar-change/border/default.png'); + box-sizing: border-box; + image-rendering: optimize-quality; + height: 50px; + width: 50px; + background-position: 50% 50%; + background-color: red; } - #mouth-transition::before { - background-color: red; - background-image: url('../png/controls/buttons/top/avatar-change/border/default.png'); + background-color: red; + background-image: url('../png/controls/buttons/top/avatar-change/border/default.png'); } button.motion::before { - position: absolute; - width: 50px; - height: 90%; - background-size: 105% 105%; - transform: translate(-11%, -11%); - /* background-position: -50% -50%; */ - overflow: hidden; - z-index: -1; - /* hit-margin: -999px; */ + position: absolute; + width: 50px; + height: 90%; + background-size: 105% 105%; + transform: translate(-11%, -11%); + /* background-position: -50% -50%; */ + overflow: hidden; + z-index: -1; + /* hit-margin: -999px; */ } #closed-mouth-motion::before { - background-color: #2ebe38; + background-color: #2ebe38; } #open-mouth-motion::before { - background-image: url('../png/controls/buttons/top/motion/open.png'); - height: 50px; - width: 50px; - pointer-events: none; - background-color: yellow; + background-image: url('../png/controls/buttons/top/motion/open.png'); + height: 50px; + width: 50px; + pointer-events: none; + background-color: yellow; } button.motion::after { - position: absolute; - width: 80%; - height: 80%; - background-color: red; - overflow: hidden; - z-index: -1; - transform: translate(11%, 11%); - /* hit-margin: -999px; */ + position: absolute; + width: 80%; + height: 80%; + background-color: red; + overflow: hidden; + z-index: -1; + transform: translate(11%, 11%); + /* hit-margin: -999px; */ } #set-hotkey { - background-image: url('../png/controls/buttons/top/hotkey/set/default.png'); + background-image: url('../png/controls/buttons/top/hotkey/set/default.png'); } #hotkey-mode { - background-image: url('../png/controls/buttons/top/hotkey/mode/0.png'); + background-image: url('../png/controls/buttons/top/hotkey/mode/0.png'); } #delete-state { - background-image: url('../png/controls/buttons/right/delete.png'); + background-image: url('../png/controls/buttons/right/delete.png'); } #about { - background-image: url('../png/controls/buttons/top/info.png'); + background-image: url('../png/controls/buttons/top/info.png'); } #close { - background-image: url('../png/controls/buttons/top/close.png'); + background-image: url('../png/controls/buttons/top/close.png'); } #avatar { - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - width: 40%; - height: width(100%); + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + width: 40%; + height: width(100%); } - -menu.popup { - /* box-shadow: 3px 3px 1px rgba(0, 0, 0, 0.692); */ -} \ No newline at end of file diff --git a/src/modules/vtuber/html/about.html b/src/modules/vtuber/html/about.html index 5a1d1a6..36cf9cb 100644 --- a/src/modules/vtuber/html/about.html +++ b/src/modules/vtuber/html/about.html @@ -38,4 +38,4 @@ - \ No newline at end of file + diff --git a/src/modules/vtuber/index.html b/src/modules/vtuber/index.html index 084bdbf..2568880 100644 --- a/src/modules/vtuber/index.html +++ b/src/modules/vtuber/index.html @@ -1,76 +1,76 @@ - + - - TransTube - - - - + + TransTube + + + + - - -
          -
          -
          - - -
          -
          - - - - -
          -
          -
          -
          -
          - -
          -
          - -
          -
          - -
          -
          - -
          -
          - -
          -
          -
          - -
          -
          -
          - -
          -
          - - - - -
          -
          + + +
          +
          +
          + +
          +
          + + + + +
          +
          +
          +
          +
          + +
          +
          + +
          +
          + +
          +
          + +
          +
          + +
          +
          +
          + +
          +
          +
          + +
          +
          - -
        • change image
        • -
        • remove image
        • -
          - + + + +
          +
          +
          + + +
        • change image
        • +
        • remove image
        • +
          + diff --git a/src/modules/vtuber/js/main.js b/src/modules/vtuber/js/main.js index e5626a1..78117fb 100644 --- a/src/modules/vtuber/js/main.js +++ b/src/modules/vtuber/js/main.js @@ -10,311 +10,270 @@ globalThis.MOUTH_IS_OPEN = false; globalThis.BLINKING = false; function avatarBlink() { - const state = globalThis.BLINKING ? './png/avatars/crab/closed-blink.png' : './png/avatars/crab/closed.png'; - return state; + const state = globalThis.BLINKING ? './png/avatars/crab/closed-blink.png' : './png/avatars/crab/closed.png'; + return state; } function avatarTalk() { - let state = globalThis.MOUTH_IS_OPEN ? './png/avatars/crab/open.png' : './png/avatars/crab/closed.png'; - if (globalThis.BLINK) { - state = './png/avatars/crab/open-blink.png'; - } + let state = globalThis.MOUTH_IS_OPEN ? './png/avatars/crab/open.png' : './png/avatars/crab/closed.png'; + if (globalThis.BLINK) { + state = './png/avatars/crab/open-blink.png'; + } - return state; + return state; } function adjustWindow() { - // const [width, height] = Window.this.screenBox('frame', 'dimension'); - // const w = width * 0.666; - // const h = height * 0.666; - // Window.this.move(width / 2 - w / 2, height / 2 - h / 2, w, h, true); + // const [width, height] = Window.this.screenBox('frame', 'dimension'); + // const w = width * 0.666; + // const h = height * 0.666; + // Window.this.move(width / 2 - w / 2, height / 2 - h / 2, w, h, true); } adjustWindow(); async function monitorDelay() { - setInterval(() => { }); + setInterval(() => {}); } monitorDelay(); function animate() { - let avatarCurr = $('#avatar').src; + let avatarCurr = $('#avatar').src; - const animation = globalThis.MOUTH_IS_OPEN - ? globalThis.CHOSEN_OPEN_MOUTH_ANIMATION - : globalThis.CHOSEN_CLOSED_MOUTH_ANIMATION; + const animation = globalThis.MOUTH_IS_OPEN ? globalThis.CHOSEN_OPEN_MOUTH_ANIMATION : globalThis.CHOSEN_CLOSED_MOUTH_ANIMATION; - globalThis.ANIMATION = animation; + globalThis.ANIMATION = animation; - clearInterval(globalThis.ANIMATION_INTERVAL); + clearInterval(globalThis.ANIMATION_INTERVAL); - globalThis.ANIMATION_INTERVAL = setInterval(() => { - // console.log(globalThis.ANIMATION_INTERVAL); - const t = Date.now() / 1000; - const { x, y } = MOTION[animation](t); - const left = -50 - x * 5; - const top = -50 - y * 5; - document.body.querySelector('#avatar').style.transform = `translate(${left}%, ${top}%)`; + globalThis.ANIMATION_INTERVAL = setInterval(() => { + // console.log(globalThis.ANIMATION_INTERVAL); + const t = Date.now() / 1000; + const { x, y } = MOTION[animation](t); + const left = -50 - x * 5; + const top = -50 - y * 5; + document.body.querySelector('#avatar').style.transform = `translate(${left}%, ${top}%)`; - avatarCurr = $('#avatar').src; + avatarCurr = $('#avatar').src; - if (globalThis.MOUTH_IS_OPEN === false) { - // const avatarNew = avatarCurr.replace('open', 'closed'); + if (globalThis.MOUTH_IS_OPEN === false) { + // const avatarNew = avatarCurr.replace('open', 'closed'); - const avatarNew = avatarTalk(); + const avatarNew = avatarTalk(); - if (avatarNew !== avatarCurr) { - $('#avatar').src = avatarNew; - avatarCurr = avatarNew; - } - } else { - // const avatarNew = avatarCurr.replace('closed', 'open'); + if (avatarNew !== avatarCurr) { + $('#avatar').src = avatarNew; + avatarCurr = avatarNew; + } + } else { + // const avatarNew = avatarCurr.replace('closed', 'open'); - const avatarNew = avatarBlink(); + const avatarNew = avatarBlink(); - if (avatarNew !== avatarCurr) { - $('#avatar').src = avatarNew; - avatarCurr = avatarNew; - } - } - }); + if (avatarNew !== avatarCurr) { + $('#avatar').src = avatarNew; + avatarCurr = avatarNew; + } + } + }); } animate(); function updateMeter() { - setInterval(() => { - // let volume = 50//Window.this.xcall('get_volume'); - const volume = 100; - document.body.querySelector('#meter-microphone').value = volume; + setInterval(() => { + // let volume = 50//Window.this.xcall('get_volume'); + const volume = 100; + document.body.querySelector('#meter-microphone').value = volume; - const delayVolume = document.body.querySelector('#meter-delay').value; - const delaySlider = document.body.querySelector('#meter-delay').value; + const delayVolume = document.body.querySelector('#meter-delay').value; + const delaySlider = document.body.querySelector('#meter-delay').value; - const microphoneVolume = document.body.querySelector('#meter-microphone').value; - const microphoneSlider = document.body.querySelector('#meter-microphone').value; + const microphoneVolume = document.body.querySelector('#meter-microphone').value; + const microphoneSlider = document.body.querySelector('#meter-microphone').value; - if (microphoneVolume > microphoneSlider) { - document.body.querySelector('#meter-delay').value = 100; - } else { - document.body.querySelector('#meter-delay').value = Math.max(0, delayVolume - 1); - } + if (microphoneVolume > microphoneSlider) { + document.body.querySelector('#meter-delay').value = 100; + } else { + document.body.querySelector('#meter-delay').value = Math.max(0, delayVolume - 1); + } - if (delayVolume > delaySlider) { - if (globalThis.MOUTH_IS_OPEN === false) { - globalThis.MOUTH_IS_OPEN = true; - // console.log('monitor delay => delayVolume > delaySlider'); - $('#avatar').src = avatarTalk(); + if (delayVolume > delaySlider) { + if (globalThis.MOUTH_IS_OPEN === false) { + globalThis.MOUTH_IS_OPEN = true; + // console.log('monitor delay => delayVolume > delaySlider'); + $('#avatar').src = avatarTalk(); - animate(); - } - } else if (globalThis.MOUTH_IS_OPEN === true) { - globalThis.MOUTH_IS_OPEN = false; - // console.log('monitor delay => else'); - $('#avatar').src = avatarTalk(); - animate(); - } - }); + animate(); + } + } else if (globalThis.MOUTH_IS_OPEN === true) { + globalThis.MOUTH_IS_OPEN = false; + // console.log('monitor delay => else'); + $('#avatar').src = avatarTalk(); + animate(); + } + }); } updateMeter(); function animateButton(button, animation = 'motionless') { - clearInterval(button.BUTTON_INTERVAL); - const animatedButton = document.body.querySelector(button); - animatedButton.BUTTON_INTERVAL = setInterval(() => { - const t = Date.now() / 1000; - const { x, y } = MOTION[animation](t); - const left = 50 + x * 50; - const top = 50 - y * 50; + clearInterval(button.BUTTON_INTERVAL); + const animatedButton = document.body.querySelector(button); + animatedButton.BUTTON_INTERVAL = setInterval(() => { + const t = Date.now() / 1000; + const { x, y } = MOTION[animation](t); + const left = 50 + x * 50; + const top = 50 - y * 50; - // animatedButton + // animatedButton - // animatedButton.style.x = left; - // animatedButton.style.y = (top + 500) * -1; - // animatedButton.style.transform = `translate(${left}%, -${top + 500}%)`; - }); + // animatedButton.style.x = left; + // animatedButton.style.y = (top + 500) * -1; + // animatedButton.style.transform = `translate(${left}%, -${top + 500}%)`; + }); } -animateButton( - '#closed-mouth-motion', - globalThis.CHOSEN_CLOSED_MOUTH_ANIMATION, -); +animateButton('#closed-mouth-motion', globalThis.CHOSEN_CLOSED_MOUTH_ANIMATION); animateButton('#open-mouth-motion', globalThis.CHOSEN_OPEN_MOUTH_ANIMATION); function blink() { - let avatarCurr = $('#avatar').src; - setInterval(() => { - avatarCurr = $('#avatar').src; - const n = Date.now() % 3200; - if (n > 3000) { - globalThis.BLINKING = true; - const avatarNew = avatarBlink(); - if (avatarCurr !== avatarNew) { - document.body.querySelector('#avatar').src = avatarNew; - avatarCurr = avatarNew; - } - } else { - globalThis.BLINKING = false; - // const avatarNew = avatarCurr.replace('-blink', ''); - const avatarNew = avatarBlink(); - if (avatarCurr !== avatarNew) { - document.body.querySelector('#avatar').src = avatarNew; - avatarCurr = avatarNew; - } - } - }); + let avatarCurr = $('#avatar').src; + setInterval(() => { + avatarCurr = $('#avatar').src; + const n = Date.now() % 3200; + if (n > 3000) { + globalThis.BLINKING = true; + const avatarNew = avatarBlink(); + if (avatarCurr !== avatarNew) { + document.body.querySelector('#avatar').src = avatarNew; + avatarCurr = avatarNew; + } + } else { + globalThis.BLINKING = false; + // const avatarNew = avatarCurr.replace('-blink', ''); + const avatarNew = avatarBlink(); + if (avatarCurr !== avatarNew) { + document.body.querySelector('#avatar').src = avatarNew; + avatarCurr = avatarNew; + } + } + }); } blink(); async function _cycleAnimations() { - const animations = Object.keys(MOTION); - let i = 0; - while (true) { - const key = animations[i % animations.length]; - animate(key); - Window.this.caption = key; - i++; - await new Promise((r) => setTimeout(r, 2000)); - } + const animations = Object.keys(MOTION); + let i = 0; + while (true) { + const key = animations[i % animations.length]; + animate(key); + Window.this.caption = key; + i++; + await new Promise(resolve => setTimeout(resolve, 2000)); + } } -$(document).on( - '~mousedown', - '#closed-mouth-motion, #open-mouth-motion', - motionButtonEvent, -); +$(document).on('~mousedown', '#closed-mouth-motion, #open-mouth-motion', motionButtonEvent); -$(document).on( - '~doubleclick', - '#closed-mouth-motion, #open-mouth-motion', - motionButtonEvent, -); +$(document).on('~doubleclick', '#closed-mouth-motion, #open-mouth-motion', motionButtonEvent); function motionButtonEvent(evt) { - const { target: button } = evt; - button.attributes.counter = button.attributes.counter || 0; + const { target: button } = evt; + button.attributes.counter = button.attributes.counter || 0; - if (evt.button === 1) { - button.attributes.counter++; - } else if (evt.button === 2) { - button.attributes.counter--; - } + if (evt.button === 1) { + button.attributes.counter++; + } else if (evt.button === 2) { + button.attributes.counter--; + } - const color = [ - 'white', - '#9BCCD4', - '#8087D6', - '#AB65CF', - '#E7FD5B', - '#EC9F45', - '#E24555', - ][mod(button.attributes.counter, 7)]; - button.style.variable('color', color); + const color = ['white', '#9BCCD4', '#8087D6', '#AB65CF', '#E7FD5B', '#EC9F45', '#E24555'][mod(button.attributes.counter, 7)]; + button.style.variable('color', color); - const animation = [ - 'motionless', - 'vibing', - 'shaking', - 'shakingMore', - 'bouncy', - 'excited', - 'nervous', - ][mod(button.attributes.counter, 7)]; + const animation = ['motionless', 'vibing', 'shaking', 'shakingMore', 'bouncy', 'excited', 'nervous'][mod(button.attributes.counter, 7)]; - animateButton(button, animation); + animateButton(button, animation); - if (button.id === 'closed-mouth-motion') { - globalThis.CHOSEN_CLOSED_MOUTH_ANIMATION = animation; - animate(); - } else if (button.id === 'open-mouth-motion') { - globalThis.CHOSEN_OPEN_MOUTH_ANIMATION = animation; - animate(); - } + if (button.id === 'closed-mouth-motion') { + globalThis.CHOSEN_CLOSED_MOUTH_ANIMATION = animation; + animate(); + } else if (button.id === 'open-mouth-motion') { + globalThis.CHOSEN_OPEN_MOUTH_ANIMATION = animation; + animate(); + } } function animateMouthButton() { - setInterval(() => { - const n = Date.now() % 1200; - document.body.querySelector('.mouth-transition').style.backgroundImage = `url('../vtuber/png/controls/buttons/top/motion/${n > 600 ? 'open' : 'closed' - }.png')`; - }); + setInterval(() => { + const n = Date.now() % 1200; + document.body.querySelector('.mouth-transition').style.backgroundImage = `url('../vtuber/png/controls/buttons/top/motion/${ + n > 600 ? 'open' : 'closed' + }.png')`; + }); } animateMouthButton(); function mod(n, m) { - return ((n % m) + m) % m; + return ((n % m) + m) % m; } globalThis.CURRENT_BUTTON = null; -$(document).on( - 'click', - '.mouth-image.border-default:not(:first-of-type)', - (evt, el) => { - globalThis.CURRENT_BUTTON = el; - el.popup($('menu')); - }, -); +$(document).on('click', '.mouth-image.border-default:not(:first-of-type)', (evt, el) => { + globalThis.CURRENT_BUTTON = el; + el.popup($('menu')); +}); function loadImage() { - const filename = Window.this.selectFile({ - mode: 'open', - filter: - 'image files (*.bmp,*.dib,*.gif,*.png,*.apng,*.jpg,*.jpeg,*.jiff)|*.bmp;*.dib;*.gif;*.png;*.apng;*.jpg;*.jpeg;*.jiff', - caption: 'select image for closed mouth...', - }); - return filename; + const filename = Window.this.selectFile({ + mode: 'open', + filter: 'image files (*.bmp,*.dib,*.gif,*.png,*.apng,*.jpg,*.jpeg,*.jiff)|*.bmp;*.dib;*.gif;*.png;*.apng;*.jpg;*.jpeg;*.jiff', + caption: 'select image for closed mouth...' + }); + return filename; } function changeImage(evt, el) { - // setTimeout to avoid context menu popping up - setTimeout(() => { - if (el.matches('.mouth-image:first-of-type, .mouth-image.border-add')) { - globalThis.CURRENT_BUTTON = el; - } + // setTimeout to avoid context menu popping up + setTimeout(() => { + if (el.matches('.mouth-image:first-of-type, .mouth-image.border-add')) { + globalThis.CURRENT_BUTTON = el; + } - const filename = loadImage(); - if (!filename) return; - const which = globalThis.CURRENT_BUTTON.id - .match(/closed|open|blink/g) - .join('-'); - document.style.variable(which, `url('${filename}')`); + const filename = loadImage(); + if (!filename) return; + const which = globalThis.CURRENT_BUTTON.id.match(/closed|open|blink/g).join('-'); + document.style.variable(which, `url('${filename}')`); - globalThis.CURRENT_BUTTON.classList.add('border-default'); - globalThis.CURRENT_BUTTON.classList.remove('border-add'); - }); + globalThis.CURRENT_BUTTON.classList.add('border-default'); + globalThis.CURRENT_BUTTON.classList.remove('border-add'); + }); } -$(document).on( - 'click', - '.mouth-image:first-of-type, .mouth-image.border-add', - changeImage, -); +$(document).on('click', '.mouth-image:first-of-type, .mouth-image.border-add', changeImage); $(document).on('click', '#change-image', changeImage); $(document).on('click', '#remove-image', (evt, el) => { - globalThis.CURRENT_BUTTON.classList.remove('border-default'); - globalThis.CURRENT_BUTTON.classList.add('border-add'); + globalThis.CURRENT_BUTTON.classList.remove('border-default'); + globalThis.CURRENT_BUTTON.classList.add('border-add'); - const which = globalThis.CURRENT_BUTTON.id - .match(/closed|open|blink/g) - .join('-'); - document.style.variable(which, null); + const which = globalThis.CURRENT_BUTTON.id.match(/closed|open|blink/g).join('-'); + document.style.variable(which, null); }); movableView('body'); // Window.this.on('activate', (evt) => { -// if (evt.reason === 0) { -// document.classList.add('transparent'); -// } else { -// document.classList.remove('transparent'); -// } +// if (evt.reason === 0) { +// document.classList.add('transparent'); +// } else { +// document.classList.remove('transparent'); +// } // }); // setInterval(() => (Window.this.isTopmost = true)); diff --git a/src/modules/vtuber/js/motion.js b/src/modules/vtuber/js/motion.js index 35f9893..feb770a 100644 --- a/src/modules/vtuber/js/motion.js +++ b/src/modules/vtuber/js/motion.js @@ -1,65 +1,65 @@ import { pnoise1 } from './perlin_noise.js'; export default { - motionless, - vibing, - shaking, - shakingMore, - bouncy, - excited, - nervous, + motionless, + vibing, + shaking, + shakingMore, + bouncy, + excited, + nervous }; function motionless(t) { - return { x: 0, y: 0 }; + return { x: 0, y: 0 }; } function clamp01(number) { - return Math.max(0, Math.min(number, 1)); + return Math.max(0, Math.min(number, 1)); } function _shake(t, amount, velocity) { - let num = clamp01(pnoise1(t * velocity, 0)); - let num2 = clamp01(pnoise1(t * velocity, 50)); - num = num * 2 - 1; - num2 = num2 * 2 - 1; - return { - x: amount * (num * Math.sqrt(1 - (num2 * num2) / 2)), - y: amount * (num2 * Math.sqrt(1 - (num * num) / 2)), - }; + let num = clamp01(pnoise1(t * velocity, 0)); + let num2 = clamp01(pnoise1(t * velocity, 50)); + num = num * 2 - 1; + num2 = num2 * 2 - 1; + return { + x: amount * (num * Math.sqrt(1 - (num2 * num2) / 2)), + y: amount * (num2 * Math.sqrt(1 - (num * num) / 2)) + }; } function _jumpy(t, amountX, amountY, velocity) { - t *= velocity; - const num = t % 1; - const num2 = -8 * num * (num - 1) - 1; - let num3 = t % 2; - if (num3 > 1) { - num3 = 2 - num3; - } - return { x: (num3 * 2 - 1) * amountX, y: num2 * amountY }; + t *= velocity; + const num = t % 1; + const num2 = -8 * num * (num - 1) - 1; + let num3 = t % 2; + if (num3 > 1) { + num3 = 2 - num3; + } + return { x: (num3 * 2 - 1) * amountX, y: num2 * amountY }; } function vibing(t) { - return _shake(t, 0.7, 0.5); + return _shake(t, 0.7, 0.5); } function shaking(t) { - return _shake(t, 0.3, 10); + return _shake(t, 0.3, 10); } function shakingMore(t) { - return _shake(t, 1, 8); + return _shake(t, 1, 8); } function bouncy(t) { - return _jumpy(t, 0, 0.5, 1); + return _jumpy(t, 0, 0.5, 1); } function excited(t) { - return _jumpy(t, 0.5, 0.5, 2); + return _jumpy(t, 0.5, 0.5, 2); } function nervous(t) { - return _jumpy(t, 1, 1, 4); -} \ No newline at end of file + return _jumpy(t, 1, 1, 4); +} diff --git a/src/modules/vtuber/js/movable_view.js b/src/modules/vtuber/js/movable_view.js index 0b799ea..42968d4 100644 --- a/src/modules/vtuber/js/movable_view.js +++ b/src/modules/vtuber/js/movable_view.js @@ -1,64 +1,65 @@ if (!Number.prototype.limit) { - Number.prototype.limit = function (min, max) { - if (this < min) return min; - if (this > max) return max; - return this; - }; + // eslint-disable-next-line no-extend-native + Number.prototype.limit = function (min, max) { + if (this < min) return min; + if (this > max) return max; + return this; + }; } function movableView(s, screenBound = false) { - let xoff; let yoff; let minXY; let maxX; let - maxY; - let dragging = false; + let xoff; + let yoff; + let minXY; + let maxX; + let maxY; + let dragging = false; - function screenBounds() { - if (screenBound) { - [maxX, maxY] = Window.this.screenBox('workarea', 'dimension'); - const [w, h] = Window.this.box('dimension', 'border'); - maxX -= w; - maxY -= h; - minXY = 0; - } else { - maxX = Number.MAX_SAFE_INTEGER; - maxY = Number.MAX_SAFE_INTEGER; - minXY = Number.MIN_SAFE_INTEGER; - } - } + function screenBounds() { + if (screenBound) { + [maxX, maxY] = Window.this.screenBox('workarea', 'dimension'); + const [w, h] = Window.this.box('dimension', 'border'); + maxX -= w; + maxY -= h; + minXY = 0; + } else { + maxX = Number.MAX_SAFE_INTEGER; + maxY = Number.MAX_SAFE_INTEGER; + minXY = Number.MIN_SAFE_INTEGER; + } + } - function onMouseDown(e) { - screenBounds(); - e.target.state.capture(true); + function onMouseDown(e) { + screenBounds(); + e.target.state.capture(true); - const [x, y] = Window.this.box('position', 'border', 'screen'); - xoff = e.screenX - x; - yoff = e.screenY - y; + const [x, y] = Window.this.box('position', 'border', 'screen'); + xoff = e.screenX - x; + yoff = e.screenY - y; - dragging = true; - } + dragging = true; + } - function onMouseMove(e) { - if (dragging) { - Window.this.move( - (e.screenX - xoff).limit(minXY, maxX), - (e.screenY - yoff).limit(minXY, maxY), - ); - } - } + function onMouseMove(e) { + if (dragging) { + Window.this.move((e.screenX - xoff).limit(minXY, maxX), (e.screenY - yoff).limit(minXY, maxY)); + } + } - function onMouseUp(e) { - if (dragging) { - dragging = false; - e.target.state.capture(false); - } - } + function onMouseUp(e) { + if (dragging) { + dragging = false; + e.target.state.capture(false); + } + } - const elements = document.querySelectorAll(s); - for (let i = 0; i < elements.length; ++i) { - // elements[i].on('mousedown', onMouseDown); - // elements[i].on('mousemove', onMouseMove); - // elements[i].on('mouseup', onMouseUp); - } - return !!elements.length; + const elements = document.querySelectorAll(s); + for (let i = 0; i < elements.length; ++i) { + // elements[i].on('mousedown', onMouseDown); + // elements[i].on('mousemove', onMouseMove); + // elements[i].on('mouseup', onMouseUp); + } + return !!elements.length; } // | Module export (uncomment bellow) diff --git a/src/modules/vtuber/js/perlin_noise.js b/src/modules/vtuber/js/perlin_noise.js index 29c0119..1f308ac 100644 --- a/src/modules/vtuber/js/perlin_noise.js +++ b/src/modules/vtuber/js/perlin_noise.js @@ -11,14 +11,14 @@ // appreciate being told where this code finds any use, // but you may do as you like. -//Ported to JavaScript by Mike mikechambers -//http://www.mikechambers.com +// Ported to JavaScript by Mike mikechambers +// http://www.mikechambers.com // // Note, all return values are scaled to be between 0 and 1 // -//From original C at: -//https://github.com/stegu/perlin-noise -//https://github.com/stegu/perlin-noise/blob/master/src/noise1234.c +// From original C at: +// https://github.com/stegu/perlin-noise +// https://github.com/stegu/perlin-noise/blob/master/src/noise1234.c /* * This implementation is "Improved Noise" as presented by @@ -43,7 +43,7 @@ function lerp(t, a, b) { return a + t * (b - a); } -//--------------------------------------------------------------------- +// --------------------------------------------------------------------- // Static data /* @@ -64,521 +64,27 @@ function lerp(t, a, b) { * float-valued 4D noise 64 times. We want this to fit in the cache! */ const perm = [ - 151, - 160, - 137, - 91, - 90, - 15, - 131, - 13, - 201, - 95, - 96, - 53, - 194, - 233, - 7, - 225, - 140, - 36, - 103, - 30, - 69, - 142, - 8, - 99, - 37, - 240, - 21, - 10, - 23, - 190, - 6, - 148, - 247, - 120, - 234, - 75, - 0, - 26, - 197, - 62, - 94, - 252, - 219, - 203, - 117, - 35, - 11, - 32, - 57, - 177, - 33, - 88, - 237, - 149, - 56, - 87, - 174, - 20, - 125, - 136, - 171, - 168, - 68, - 175, - 74, - 165, - 71, - 134, - 139, - 48, - 27, - 166, - 77, - 146, - 158, - 231, - 83, - 111, - 229, - 122, - 60, - 211, - 133, - 230, - 220, - 105, - 92, - 41, - 55, - 46, - 245, - 40, - 244, - 102, - 143, - 54, - 65, - 25, - 63, - 161, - 1, - 216, - 80, - 73, - 209, - 76, - 132, - 187, - 208, - 89, - 18, - 169, - 200, - 196, - 135, - 130, - 116, - 188, - 159, - 86, - 164, - 100, - 109, - 198, - 173, - 186, - 3, - 64, - 52, - 217, - 226, - 250, - 124, - 123, - 5, - 202, - 38, - 147, - 118, - 126, - 255, - 82, - 85, - 212, - 207, - 206, - 59, - 227, - 47, - 16, - 58, - 17, - 182, - 189, - 28, - 42, - 223, - 183, - 170, - 213, - 119, - 248, - 152, - 2, - 44, - 154, - 163, - 70, - 221, - 153, - 101, - 155, - 167, - 43, - 172, - 9, - 129, - 22, - 39, - 253, - 19, - 98, - 108, - 110, - 79, - 113, - 224, - 232, - 178, - 185, - 112, - 104, - 218, - 246, - 97, - 228, - 251, - 34, - 242, - 193, - 238, - 210, - 144, - 12, - 191, - 179, - 162, - 241, - 81, - 51, - 145, - 235, - 249, - 14, - 239, - 107, - 49, - 192, - 214, - 31, - 181, - 199, - 106, - 157, - 184, - 84, - 204, - 176, - 115, - 121, - 50, - 45, - 127, - 4, - 150, - 254, - 138, - 236, - 205, - 93, - 222, - 114, - 67, - 29, - 24, - 72, - 243, - 141, - 128, - 195, - 78, - 66, - 215, - 61, - 156, - 180, - 151, - 160, - 137, - 91, - 90, - 15, - 131, - 13, - 201, - 95, - 96, - 53, - 194, - 233, - 7, - 225, - 140, - 36, - 103, - 30, - 69, - 142, - 8, - 99, - 37, - 240, - 21, - 10, - 23, - 190, - 6, - 148, - 247, - 120, - 234, - 75, - 0, - 26, - 197, - 62, - 94, - 252, - 219, - 203, - 117, - 35, - 11, - 32, - 57, - 177, - 33, - 88, - 237, - 149, - 56, - 87, - 174, - 20, - 125, - 136, - 171, - 168, - 68, - 175, - 74, - 165, - 71, - 134, - 139, - 48, - 27, - 166, - 77, - 146, - 158, - 231, - 83, - 111, - 229, - 122, - 60, - 211, - 133, - 230, - 220, - 105, - 92, - 41, - 55, - 46, - 245, - 40, - 244, - 102, - 143, - 54, - 65, - 25, - 63, - 161, - 1, - 216, - 80, - 73, - 209, - 76, - 132, - 187, - 208, - 89, - 18, - 169, - 200, - 196, - 135, - 130, - 116, - 188, - 159, - 86, - 164, - 100, - 109, - 198, - 173, - 186, - 3, - 64, - 52, - 217, - 226, - 250, - 124, - 123, - 5, - 202, - 38, - 147, - 118, - 126, - 255, - 82, - 85, - 212, - 207, - 206, - 59, - 227, - 47, - 16, - 58, - 17, - 182, - 189, - 28, - 42, - 223, - 183, - 170, - 213, - 119, - 248, - 152, - 2, - 44, - 154, - 163, - 70, - 221, - 153, - 101, - 155, - 167, - 43, - 172, - 9, - 129, - 22, - 39, - 253, - 19, - 98, - 108, - 110, - 79, - 113, - 224, - 232, - 178, - 185, - 112, - 104, - 218, - 246, - 97, - 228, - 251, - 34, - 242, - 193, - 238, - 210, - 144, - 12, - 191, - 179, - 162, - 241, - 81, - 51, - 145, - 235, - 249, - 14, - 239, - 107, - 49, - 192, - 214, - 31, - 181, - 199, - 106, - 157, - 184, - 84, - 204, - 176, - 115, - 121, - 50, - 45, - 127, - 4, - 150, - 254, - 138, - 236, - 205, - 93, - 222, - 114, - 67, - 29, - 24, - 72, - 243, - 141, - 128, - 195, - 78, - 66, - 215, - 61, - 156, - 180, + 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, + 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, + 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, + 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, + 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, + 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, + 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, + 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, + 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, + 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, + 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, + 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, + 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, + 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, + 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, + 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, + 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, + 180 ]; -//--------------------------------------------------------------------- +// --------------------------------------------------------------------- /* * Helper functions to compute gradients-dot-residualvectors (1D to 4D) @@ -594,41 +100,45 @@ const perm = [ */ function grad1(hash, x) { - let h = hash & 15; + const h = hash & 15; let grad = 1.0 + (h & 7); // Gradient value 1.0, 2.0, ..., 8.0 if (h & 8) grad = -grad; // and a random sign for the gradient return grad * x; // Multiply the gradient with the distance } function grad2(hash, x, y) { - let h = hash & 7; // Convert low 3 bits of hash code - let u = h < 4 ? x : y; // into 8 simple gradient directions, - let v = h < 4 ? y : x; // and compute the dot product with (x,y). + const h = hash & 7; // Convert low 3 bits of hash code + const u = h < 4 ? x : y; // into 8 simple gradient directions, + const v = h < 4 ? y : x; // and compute the dot product with (x,y). return (h & 1 ? -u : u) + (h & 2 ? -2.0 * v : 2.0 * v); } function grad3(hash, x, y, z) { - let h = hash & 15; // Convert low 4 bits of hash code into 12 simple - let u = h < 8 ? x : y; // gradient directions, and compute dot product. - let v = h < 4 ? y : h == 12 || h == 14 ? x : z; // Fix repeats at h = 12 to 15 + const h = hash & 15; // Convert low 4 bits of hash code into 12 simple + const u = h < 8 ? x : y; // gradient directions, and compute dot product. + const v = h < 4 ? y : h === 12 || h === 14 ? x : z; // Fix repeats at h = 12 to 15 return (h & 1 ? -u : u) + (h & 2 ? -v : v); } function grad4(hash, x, y, z, t) { - let h = hash & 31; // Convert low 5 bits of hash code into 32 simple - let u = h < 24 ? x : y; // gradient directions, and compute dot product. - let v = h < 16 ? y : z; - let w = h < 8 ? z : t; + const h = hash & 31; // Convert low 5 bits of hash code into 32 simple + const u = h < 24 ? x : y; // gradient directions, and compute dot product. + const v = h < 16 ? y : z; + const w = h < 8 ? z : t; return (h & 1 ? -u : u) + (h & 2 ? -v : v) + (h & 4 ? -w : w); } -//--------------------------------------------------------------------- +// --------------------------------------------------------------------- /** 1D float Perlin noise, SL "noise()" */ export function noise1(x) { - let ix0, ix1; - let fx0, fx1; - let s, n0, n1; + let ix0 = null; + let ix1 = null; + let fx0 = null; + let fx1 = null; + let s = null; + let n0 = null; + let n1 = null; ix0 = Math.floor(x); // Integer part of x fx0 = x - ix0; // Fractional part of x @@ -643,131 +153,121 @@ export function noise1(x) { return scale(0.188 * lerp(s, n0, n1)); } -//--------------------------------------------------------------------- +// --------------------------------------------------------------------- /** 1D float Perlin periodic noise, SL "pnoise()" */ export function pnoise1(x, px) { - let ix0, ix1; - let fx0, fx1; - let s, n0, n1; - - ix0 = Math.floor(x); // Integer part of x - fx0 = x - ix0; // Fractional part of x - fx1 = fx0 - 1.0; - ix1 = (ix0 + 1) % px & 0xff; // Wrap to 0..px-1 *and* wrap to 0..255 + let ix0 = Math.floor(x); // Integer part of x + const fx0 = x - ix0; // Fractional part of x + const fx1 = fx0 - 1.0; + const ix1 = (ix0 + 1) % px & 0xff; // Wrap to 0..px-1 *and* wrap to 0..255 ix0 = ix0 % px & 0xff; // (because px might be greater than 256) - s = fade(fx0); + const s = fade(fx0); - n0 = grad1(perm[ix0], fx0); - n1 = grad1(perm[ix1], fx1); + const n0 = grad1(perm[ix0], fx0); + const n1 = grad1(perm[ix1], fx1); return scale(0.188 * lerp(s, n0, n1)); } -//--------------------------------------------------------------------- +// --------------------------------------------------------------------- /** 2D float Perlin noise. */ export function noise2(x, y) { - let ix0, iy0, ix1, iy1; - let fx0, fy0, fx1, fy1; - let s, t, nx0, nx1, n0, n1; + // let ix0, iy0, ix1, iy1; + // let fx0, fy0, fx1, fy1; + // let s, t, nx0, nx1, n0, n1; + + let ix0 = Math.floor(x); // Integer part of x + let iy0 = Math.floor(y); // Integer part of y + const fx0 = x - ix0; // Fractional part of x + const fy0 = y - iy0; // Fractional part of y + const fx1 = fx0 - 1.0; + const fy1 = fy0 - 1.0; + const ix1 = (ix0 + 1) & 0xff; // Wrap to 0..255 + const iy1 = (iy0 + 1) & 0xff; - ix0 = Math.floor(x); // Integer part of x - iy0 = Math.floor(y); // Integer part of y - fx0 = x - ix0; // Fractional part of x - fy0 = y - iy0; // Fractional part of y - fx1 = fx0 - 1.0; - fy1 = fy0 - 1.0; - ix1 = (ix0 + 1) & 0xff; // Wrap to 0..255 - iy1 = (iy0 + 1) & 0xff; ix0 = ix0 & 0xff; iy0 = iy0 & 0xff; - t = fade(fy0); - s = fade(fx0); + const t = fade(fy0); + const s = fade(fx0); - nx0 = grad2(perm[ix0 + perm[iy0]], fx0, fy0); - nx1 = grad2(perm[ix0 + perm[iy1]], fx0, fy1); - n0 = lerp(t, nx0, nx1); + let nx0 = grad2(perm[ix0 + perm[iy0]], fx0, fy0); + let nx1 = grad2(perm[ix0 + perm[iy1]], fx0, fy1); + const n0 = lerp(t, nx0, nx1); nx0 = grad2(perm[ix1 + perm[iy0]], fx1, fy0); nx1 = grad2(perm[ix1 + perm[iy1]], fx1, fy1); - n1 = lerp(t, nx0, nx1); + const n1 = lerp(t, nx0, nx1); return scale(0.507 * lerp(s, n0, n1)); } -//--------------------------------------------------------------------- +// --------------------------------------------------------------------- /** 2D float Perlin periodic noise. */ export function pnoise2(x, y, px, py) { - let ix0, iy0, ix1, iy1; - let fx0, fy0, fx1, fy1; - let s, t, nx0, nx1, n0, n1; - - ix0 = Math.floor(x); // Integer part of x - iy0 = Math.floor(y); // Integer part of y - fx0 = x - ix0; // Fractional part of x - fy0 = y - iy0; // Fractional part of y - fx1 = fx0 - 1.0; - fy1 = fy0 - 1.0; - ix1 = (ix0 + 1) % px & 0xff; // Wrap to 0..px-1 and wrap to 0..255 - iy1 = (iy0 + 1) % py & 0xff; // Wrap to 0..py-1 and wrap to 0..255 + let ix0 = Math.floor(x); // Integer part of x + let iy0 = Math.floor(y); // Integer part of y + const fx0 = x - ix0; // Fractional part of x + const fy0 = y - iy0; // Fractional part of y + const fx1 = fx0 - 1.0; + const fy1 = fy0 - 1.0; + const ix1 = (ix0 + 1) % px & 0xff; // Wrap to 0..px-1 and wrap to 0..255 + const iy1 = (iy0 + 1) % py & 0xff; // Wrap to 0..py-1 and wrap to 0..255 ix0 = ix0 % px & 0xff; iy0 = iy0 % py & 0xff; - t = fade(fy0); - s = fade(fx0); + const t = fade(fy0); + const s = fade(fx0); - nx0 = grad2(perm[ix0 + perm[iy0]], fx0, fy0); - nx1 = grad2(perm[ix0 + perm[iy1]], fx0, fy1); - n0 = lerp(t, nx0, nx1); + let nx0 = grad2(perm[ix0 + perm[iy0]], fx0, fy0); + let nx1 = grad2(perm[ix0 + perm[iy1]], fx0, fy1); + const n0 = lerp(t, nx0, nx1); nx0 = grad2(perm[ix1 + perm[iy0]], fx1, fy0); nx1 = grad2(perm[ix1 + perm[iy1]], fx1, fy1); - n1 = lerp(t, nx0, nx1); + const n1 = lerp(t, nx0, nx1); return scale(0.507 * lerp(s, n0, n1)); } -//--------------------------------------------------------------------- +// --------------------------------------------------------------------- /** 3D float Perlin noise. */ export function noise3(x, y, z) { - let ix0, iy0, ix1, iy1, iz0, iz1; - let fx0, fy0, fz0, fx1, fy1, fz1; - let s, t, r; - let nxy0, nxy1, nx0, nx1, n0, n1; + let ix0 = Math.floor(x); // Integer part of x + let iy0 = Math.floor(y); // Integer part of y + let iz0 = Math.floor(z); // Integer part of z + + const fx0 = x - ix0; // Fractional part of x + const fy0 = y - iy0; // Fractional part of y + const fz0 = z - iz0; // Fractional part of z + const fx1 = fx0 - 1.0; + const fy1 = fy0 - 1.0; + const fz1 = fz0 - 1.0; + const ix1 = (ix0 + 1) & 0xff; // Wrap to 0..255 + const iy1 = (iy0 + 1) & 0xff; + const iz1 = (iz0 + 1) & 0xff; - ix0 = Math.floor(x); // Integer part of x - iy0 = Math.floor(y); // Integer part of y - iz0 = Math.floor(z); // Integer part of z - fx0 = x - ix0; // Fractional part of x - fy0 = y - iy0; // Fractional part of y - fz0 = z - iz0; // Fractional part of z - fx1 = fx0 - 1.0; - fy1 = fy0 - 1.0; - fz1 = fz0 - 1.0; - ix1 = (ix0 + 1) & 0xff; // Wrap to 0..255 - iy1 = (iy0 + 1) & 0xff; - iz1 = (iz0 + 1) & 0xff; ix0 = ix0 & 0xff; iy0 = iy0 & 0xff; iz0 = iz0 & 0xff; - r = fade(fz0); - t = fade(fy0); - s = fade(fx0); + const r = fade(fz0); + const t = fade(fy0); + const s = fade(fx0); - nxy0 = grad3(perm[ix0 + perm[iy0 + perm[iz0]]], fx0, fy0, fz0); - nxy1 = grad3(perm[ix0 + perm[iy0 + perm[iz1]]], fx0, fy0, fz1); - nx0 = lerp(r, nxy0, nxy1); + let nxy0 = grad3(perm[ix0 + perm[iy0 + perm[iz0]]], fx0, fy0, fz0); + let nxy1 = grad3(perm[ix0 + perm[iy0 + perm[iz1]]], fx0, fy0, fz1); + let nx0 = lerp(r, nxy0, nxy1); nxy0 = grad3(perm[ix0 + perm[iy1 + perm[iz0]]], fx0, fy1, fz0); nxy1 = grad3(perm[ix0 + perm[iy1 + perm[iz1]]], fx0, fy1, fz1); - nx1 = lerp(r, nxy0, nxy1); + let nx1 = lerp(r, nxy0, nxy1); - n0 = lerp(t, nx0, nx1); + const n0 = lerp(t, nx0, nx1); nxy0 = grad3(perm[ix1 + perm[iy0 + perm[iz0]]], fx1, fy0, fz0); nxy1 = grad3(perm[ix1 + perm[iy0 + perm[iz1]]], fx1, fy0, fz1); @@ -777,49 +277,44 @@ export function noise3(x, y, z) { nxy1 = grad3(perm[ix1 + perm[iy1 + perm[iz1]]], fx1, fy1, fz1); nx1 = lerp(r, nxy0, nxy1); - n1 = lerp(t, nx0, nx1); + const n1 = lerp(t, nx0, nx1); return scale(0.936 * lerp(s, n0, n1)); } -//--------------------------------------------------------------------- +// --------------------------------------------------------------------- /** 3D float Perlin periodic noise. */ export function pnoise3(x, y, z, px, py, pz) { - let ix0, iy0, ix1, iy1, iz0, iz1; - let fx0, fy0, fz0, fx1, fy1, fz1; - let s, t, r; - let nxy0, nxy1, nx0, nx1, n0, n1; - - ix0 = Math.floor(x); // Integer part of x - iy0 = Math.floor(y); // Integer part of y - iz0 = Math.floor(z); // Integer part of z - fx0 = x - ix0; // Fractional part of x - fy0 = y - iy0; // Fractional part of y - fz0 = z - iz0; // Fractional part of z - fx1 = fx0 - 1.0; - fy1 = fy0 - 1.0; - fz1 = fz0 - 1.0; - ix1 = (ix0 + 1) % px & 0xff; // Wrap to 0..px-1 and wrap to 0..255 - iy1 = (iy0 + 1) % py & 0xff; // Wrap to 0..py-1 and wrap to 0..255 - iz1 = (iz0 + 1) % pz & 0xff; // Wrap to 0..pz-1 and wrap to 0..255 + let ix0 = Math.floor(x); // Integer part of x + let iy0 = Math.floor(y); // Integer part of y + let iz0 = Math.floor(z); // Integer part of z + const fx0 = x - ix0; // Fractional part of x + const fy0 = y - iy0; // Fractional part of y + const fz0 = z - iz0; // Fractional part of z + const fx1 = fx0 - 1.0; + const fy1 = fy0 - 1.0; + const fz1 = fz0 - 1.0; + const ix1 = (ix0 + 1) % px & 0xff; // Wrap to 0..px-1 and wrap to 0..255 + const iy1 = (iy0 + 1) % py & 0xff; // Wrap to 0..py-1 and wrap to 0..255 + const iz1 = (iz0 + 1) % pz & 0xff; // Wrap to 0..pz-1 and wrap to 0..255 ix0 = ix0 % px & 0xff; iy0 = iy0 % py & 0xff; iz0 = iz0 % pz & 0xff; - r = fade(fz0); - t = fade(fy0); - s = fade(fx0); + const r = fade(fz0); + const t = fade(fy0); + const s = fade(fx0); - nxy0 = grad3(perm[ix0 + perm[iy0 + perm[iz0]]], fx0, fy0, fz0); - nxy1 = grad3(perm[ix0 + perm[iy0 + perm[iz1]]], fx0, fy0, fz1); - nx0 = lerp(r, nxy0, nxy1); + let nxy0 = grad3(perm[ix0 + perm[iy0 + perm[iz0]]], fx0, fy0, fz0); + let nxy1 = grad3(perm[ix0 + perm[iy0 + perm[iz1]]], fx0, fy0, fz1); + let nx0 = lerp(r, nxy0, nxy1); nxy0 = grad3(perm[ix0 + perm[iy1 + perm[iz0]]], fx0, fy1, fz0); nxy1 = grad3(perm[ix0 + perm[iy1 + perm[iz1]]], fx0, fy1, fz1); - nx1 = lerp(r, nxy0, nxy1); + let nx1 = lerp(r, nxy0, nxy1); - n0 = lerp(t, nx0, nx1); + const n0 = lerp(t, nx0, nx1); nxy0 = grad3(perm[ix1 + perm[iy0 + perm[iz0]]], fx1, fy0, fz0); nxy1 = grad3(perm[ix1 + perm[iy0 + perm[iz1]]], fx1, fy0, fz1); @@ -829,365 +324,163 @@ export function pnoise3(x, y, z, px, py, pz) { nxy1 = grad3(perm[ix1 + perm[iy1 + perm[iz1]]], fx1, fy1, fz1); nx1 = lerp(r, nxy0, nxy1); - n1 = lerp(t, nx0, nx1); + const n1 = lerp(t, nx0, nx1); return scale(0.936 * lerp(s, n0, n1)); } -//--------------------------------------------------------------------- +// --------------------------------------------------------------------- /** 4D float Perlin noise. */ export function noise4(x, y, z, w) { - let ix0, iy0, iz0, iw0, ix1, iy1, iz1, iw1; - let fx0, fy0, fz0, fw0, fx1, fy1, fz1, fw1; - let s, t, r, q; - let nxyz0, nxyz1, nxy0, nxy1, nx0, nx1, n0, n1; - - ix0 = Math.floor(x); // Integer part of x - iy0 = Math.floor(y); // Integer part of y - iz0 = Math.floor(z); // Integer part of y - iw0 = Math.floor(w); // Integer part of w - fx0 = x - ix0; // Fractional part of x - fy0 = y - iy0; // Fractional part of y - fz0 = z - iz0; // Fractional part of z - fw0 = w - iw0; // Fractional part of w - fx1 = fx0 - 1.0; - fy1 = fy0 - 1.0; - fz1 = fz0 - 1.0; - fw1 = fw0 - 1.0; - ix1 = (ix0 + 1) & 0xff; // Wrap to 0..255 - iy1 = (iy0 + 1) & 0xff; - iz1 = (iz0 + 1) & 0xff; - iw1 = (iw0 + 1) & 0xff; + let ix0 = Math.floor(x); // Integer part of x + let iy0 = Math.floor(y); // Integer part of y + let iz0 = Math.floor(z); // Integer part of y + let iw0 = Math.floor(w); // Integer part of w + const fx0 = x - ix0; // Fractional part of x + const fy0 = y - iy0; // Fractional part of y + const fz0 = z - iz0; // Fractional part of z + const fw0 = w - iw0; // Fractional part of w + const fx1 = fx0 - 1.0; + const fy1 = fy0 - 1.0; + const fz1 = fz0 - 1.0; + const fw1 = fw0 - 1.0; + const ix1 = (ix0 + 1) & 0xff; // Wrap to 0..255 + const iy1 = (iy0 + 1) & 0xff; + const iz1 = (iz0 + 1) & 0xff; + const iw1 = (iw0 + 1) & 0xff; ix0 = ix0 & 0xff; iy0 = iy0 & 0xff; iz0 = iz0 & 0xff; iw0 = iw0 & 0xff; - q = fade(fw0); - r = fade(fz0); - t = fade(fy0); - s = fade(fx0); + const q = fade(fw0); + const r = fade(fz0); + const t = fade(fy0); + const s = fade(fx0); - nxyz0 = grad4( - perm[ix0 + perm[iy0 + perm[iz0 + perm[iw0]]]], - fx0, - fy0, - fz0, - fw0 - ); - nxyz1 = grad4( - perm[ix0 + perm[iy0 + perm[iz0 + perm[iw1]]]], - fx0, - fy0, - fz0, - fw1 - ); + let nxyz0 = grad4(perm[ix0 + perm[iy0 + perm[iz0 + perm[iw0]]]], fx0, fy0, fz0, fw0); + let nxyz1 = grad4(perm[ix0 + perm[iy0 + perm[iz0 + perm[iw1]]]], fx0, fy0, fz0, fw1); + let nxy0 = lerp(q, nxyz0, nxyz1); + + nxyz0 = grad4(perm[ix0 + perm[iy0 + perm[iz1 + perm[iw0]]]], fx0, fy0, fz1, fw0); + nxyz1 = grad4(perm[ix0 + perm[iy0 + perm[iz1 + perm[iw1]]]], fx0, fy0, fz1, fw1); + let nxy1 = lerp(q, nxyz0, nxyz1); + + let nx0 = lerp(r, nxy0, nxy1); + + nxyz0 = grad4(perm[ix0 + perm[iy1 + perm[iz0 + perm[iw0]]]], fx0, fy1, fz0, fw0); + nxyz1 = grad4(perm[ix0 + perm[iy1 + perm[iz0 + perm[iw1]]]], fx0, fy1, fz0, fw1); nxy0 = lerp(q, nxyz0, nxyz1); - nxyz0 = grad4( - perm[ix0 + perm[iy0 + perm[iz1 + perm[iw0]]]], - fx0, - fy0, - fz1, - fw0 - ); - nxyz1 = grad4( - perm[ix0 + perm[iy0 + perm[iz1 + perm[iw1]]]], - fx0, - fy0, - fz1, - fw1 - ); + nxyz0 = grad4(perm[ix0 + perm[iy1 + perm[iz1 + perm[iw0]]]], fx0, fy1, fz1, fw0); + nxyz1 = grad4(perm[ix0 + perm[iy1 + perm[iz1 + perm[iw1]]]], fx0, fy1, fz1, fw1); + nxy1 = lerp(q, nxyz0, nxyz1); + + let nx1 = lerp(r, nxy0, nxy1); + + const n0 = lerp(t, nx0, nx1); + + nxyz0 = grad4(perm[ix1 + perm[iy0 + perm[iz0 + perm[iw0]]]], fx1, fy0, fz0, fw0); + nxyz1 = grad4(perm[ix1 + perm[iy0 + perm[iz0 + perm[iw1]]]], fx1, fy0, fz0, fw1); + nxy0 = lerp(q, nxyz0, nxyz1); + + nxyz0 = grad4(perm[ix1 + perm[iy0 + perm[iz1 + perm[iw0]]]], fx1, fy0, fz1, fw0); + nxyz1 = grad4(perm[ix1 + perm[iy0 + perm[iz1 + perm[iw1]]]], fx1, fy0, fz1, fw1); nxy1 = lerp(q, nxyz0, nxyz1); nx0 = lerp(r, nxy0, nxy1); - nxyz0 = grad4( - perm[ix0 + perm[iy1 + perm[iz0 + perm[iw0]]]], - fx0, - fy1, - fz0, - fw0 - ); - nxyz1 = grad4( - perm[ix0 + perm[iy1 + perm[iz0 + perm[iw1]]]], - fx0, - fy1, - fz0, - fw1 - ); + nxyz0 = grad4(perm[ix1 + perm[iy1 + perm[iz0 + perm[iw0]]]], fx1, fy1, fz0, fw0); + nxyz1 = grad4(perm[ix1 + perm[iy1 + perm[iz0 + perm[iw1]]]], fx1, fy1, fz0, fw1); nxy0 = lerp(q, nxyz0, nxyz1); - nxyz0 = grad4( - perm[ix0 + perm[iy1 + perm[iz1 + perm[iw0]]]], - fx0, - fy1, - fz1, - fw0 - ); - nxyz1 = grad4( - perm[ix0 + perm[iy1 + perm[iz1 + perm[iw1]]]], - fx0, - fy1, - fz1, - fw1 - ); + nxyz0 = grad4(perm[ix1 + perm[iy1 + perm[iz1 + perm[iw0]]]], fx1, fy1, fz1, fw0); + nxyz1 = grad4(perm[ix1 + perm[iy1 + perm[iz1 + perm[iw1]]]], fx1, fy1, fz1, fw1); nxy1 = lerp(q, nxyz0, nxyz1); nx1 = lerp(r, nxy0, nxy1); - n0 = lerp(t, nx0, nx1); - - nxyz0 = grad4( - perm[ix1 + perm[iy0 + perm[iz0 + perm[iw0]]]], - fx1, - fy0, - fz0, - fw0 - ); - nxyz1 = grad4( - perm[ix1 + perm[iy0 + perm[iz0 + perm[iw1]]]], - fx1, - fy0, - fz0, - fw1 - ); - nxy0 = lerp(q, nxyz0, nxyz1); - - nxyz0 = grad4( - perm[ix1 + perm[iy0 + perm[iz1 + perm[iw0]]]], - fx1, - fy0, - fz1, - fw0 - ); - nxyz1 = grad4( - perm[ix1 + perm[iy0 + perm[iz1 + perm[iw1]]]], - fx1, - fy0, - fz1, - fw1 - ); - nxy1 = lerp(q, nxyz0, nxyz1); - - nx0 = lerp(r, nxy0, nxy1); - - nxyz0 = grad4( - perm[ix1 + perm[iy1 + perm[iz0 + perm[iw0]]]], - fx1, - fy1, - fz0, - fw0 - ); - nxyz1 = grad4( - perm[ix1 + perm[iy1 + perm[iz0 + perm[iw1]]]], - fx1, - fy1, - fz0, - fw1 - ); - nxy0 = lerp(q, nxyz0, nxyz1); - - nxyz0 = grad4( - perm[ix1 + perm[iy1 + perm[iz1 + perm[iw0]]]], - fx1, - fy1, - fz1, - fw0 - ); - nxyz1 = grad4( - perm[ix1 + perm[iy1 + perm[iz1 + perm[iw1]]]], - fx1, - fy1, - fz1, - fw1 - ); - nxy1 = lerp(q, nxyz0, nxyz1); - - nx1 = lerp(r, nxy0, nxy1); - - n1 = lerp(t, nx0, nx1); + const n1 = lerp(t, nx0, nx1); return scale(0.87 * lerp(s, n0, n1)); } -//--------------------------------------------------------------------- +// --------------------------------------------------------------------- /** 4D float Perlin periodic noise. */ export function pnoise4(x, y, z, w, px, py, pz, pw) { - let ix0, iy0, iz0, iw0, ix1, iy1, iz1, iw1; - let fx0, fy0, fz0, fw0, fx1, fy1, fz1, fw1; - let s, t, r, q; - let nxyz0, nxyz1, nxy0, nxy1, nx0, nx1, n0, n1; - - ix0 = Math.floor(x); // Integer part of x - iy0 = Math.floor(y); // Integer part of y - iz0 = Math.floor(z); // Integer part of y - iw0 = Math.floor(w); // Integer part of w - fx0 = x - ix0; // Fractional part of x - fy0 = y - iy0; // Fractional part of y - fz0 = z - iz0; // Fractional part of z - fw0 = w - iw0; // Fractional part of w - fx1 = fx0 - 1.0; - fy1 = fy0 - 1.0; - fz1 = fz0 - 1.0; - fw1 = fw0 - 1.0; - ix1 = (ix0 + 1) % px & 0xff; // Wrap to 0..px-1 and wrap to 0..255 - iy1 = (iy0 + 1) % py & 0xff; // Wrap to 0..py-1 and wrap to 0..255 - iz1 = (iz0 + 1) % pz & 0xff; // Wrap to 0..pz-1 and wrap to 0..255 - iw1 = (iw0 + 1) % pw & 0xff; // Wrap to 0..pw-1 and wrap to 0..255 + let ix0 = Math.floor(x); // Integer part of x + let iy0 = Math.floor(y); // Integer part of y + let iz0 = Math.floor(z); // Integer part of y + let iw0 = Math.floor(w); // Integer part of w + const fx0 = x - ix0; // Fractional part of x + const fy0 = y - iy0; // Fractional part of y + const fz0 = z - iz0; // Fractional part of z + const fw0 = w - iw0; // Fractional part of w + const fx1 = fx0 - 1.0; + const fy1 = fy0 - 1.0; + const fz1 = fz0 - 1.0; + const fw1 = fw0 - 1.0; + const ix1 = (ix0 + 1) % px & 0xff; // Wrap to 0..px-1 and wrap to 0..255 + const iy1 = (iy0 + 1) % py & 0xff; // Wrap to 0..py-1 and wrap to 0..255 + const iz1 = (iz0 + 1) % pz & 0xff; // Wrap to 0..pz-1 and wrap to 0..255 + const iw1 = (iw0 + 1) % pw & 0xff; // Wrap to 0..pw-1 and wrap to 0..255 ix0 = ix0 % px & 0xff; iy0 = iy0 % py & 0xff; iz0 = iz0 % pz & 0xff; iw0 = iw0 % pw & 0xff; - q = fade(fw0); - r = fade(fz0); - t = fade(fy0); - s = fade(fx0); + const q = fade(fw0); + const r = fade(fz0); + const t = fade(fy0); + const s = fade(fx0); - nxyz0 = grad4( - perm[ix0 + perm[iy0 + perm[iz0 + perm[iw0]]]], - fx0, - fy0, - fz0, - fw0 - ); - nxyz1 = grad4( - perm[ix0 + perm[iy0 + perm[iz0 + perm[iw1]]]], - fx0, - fy0, - fz0, - fw1 - ); + let nxyz0 = grad4(perm[ix0 + perm[iy0 + perm[iz0 + perm[iw0]]]], fx0, fy0, fz0, fw0); + let nxyz1 = grad4(perm[ix0 + perm[iy0 + perm[iz0 + perm[iw1]]]], fx0, fy0, fz0, fw1); + let nxy0 = lerp(q, nxyz0, nxyz1); + + nxyz0 = grad4(perm[ix0 + perm[iy0 + perm[iz1 + perm[iw0]]]], fx0, fy0, fz1, fw0); + nxyz1 = grad4(perm[ix0 + perm[iy0 + perm[iz1 + perm[iw1]]]], fx0, fy0, fz1, fw1); + let nxy1 = lerp(q, nxyz0, nxyz1); + + let nx0 = lerp(r, nxy0, nxy1); + + nxyz0 = grad4(perm[ix0 + perm[iy1 + perm[iz0 + perm[iw0]]]], fx0, fy1, fz0, fw0); + nxyz1 = grad4(perm[ix0 + perm[iy1 + perm[iz0 + perm[iw1]]]], fx0, fy1, fz0, fw1); nxy0 = lerp(q, nxyz0, nxyz1); - nxyz0 = grad4( - perm[ix0 + perm[iy0 + perm[iz1 + perm[iw0]]]], - fx0, - fy0, - fz1, - fw0 - ); - nxyz1 = grad4( - perm[ix0 + perm[iy0 + perm[iz1 + perm[iw1]]]], - fx0, - fy0, - fz1, - fw1 - ); + nxyz0 = grad4(perm[ix0 + perm[iy1 + perm[iz1 + perm[iw0]]]], fx0, fy1, fz1, fw0); + nxyz1 = grad4(perm[ix0 + perm[iy1 + perm[iz1 + perm[iw1]]]], fx0, fy1, fz1, fw1); + nxy1 = lerp(q, nxyz0, nxyz1); + + let nx1 = lerp(r, nxy0, nxy1); + + const n0 = lerp(t, nx0, nx1); + + nxyz0 = grad4(perm[ix1 + perm[iy0 + perm[iz0 + perm[iw0]]]], fx1, fy0, fz0, fw0); + nxyz1 = grad4(perm[ix1 + perm[iy0 + perm[iz0 + perm[iw1]]]], fx1, fy0, fz0, fw1); + nxy0 = lerp(q, nxyz0, nxyz1); + + nxyz0 = grad4(perm[ix1 + perm[iy0 + perm[iz1 + perm[iw0]]]], fx1, fy0, fz1, fw0); + nxyz1 = grad4(perm[ix1 + perm[iy0 + perm[iz1 + perm[iw1]]]], fx1, fy0, fz1, fw1); nxy1 = lerp(q, nxyz0, nxyz1); nx0 = lerp(r, nxy0, nxy1); - nxyz0 = grad4( - perm[ix0 + perm[iy1 + perm[iz0 + perm[iw0]]]], - fx0, - fy1, - fz0, - fw0 - ); - nxyz1 = grad4( - perm[ix0 + perm[iy1 + perm[iz0 + perm[iw1]]]], - fx0, - fy1, - fz0, - fw1 - ); + nxyz0 = grad4(perm[ix1 + perm[iy1 + perm[iz0 + perm[iw0]]]], fx1, fy1, fz0, fw0); + nxyz1 = grad4(perm[ix1 + perm[iy1 + perm[iz0 + perm[iw1]]]], fx1, fy1, fz0, fw1); nxy0 = lerp(q, nxyz0, nxyz1); - nxyz0 = grad4( - perm[ix0 + perm[iy1 + perm[iz1 + perm[iw0]]]], - fx0, - fy1, - fz1, - fw0 - ); - nxyz1 = grad4( - perm[ix0 + perm[iy1 + perm[iz1 + perm[iw1]]]], - fx0, - fy1, - fz1, - fw1 - ); + nxyz0 = grad4(perm[ix1 + perm[iy1 + perm[iz1 + perm[iw0]]]], fx1, fy1, fz1, fw0); + nxyz1 = grad4(perm[ix1 + perm[iy1 + perm[iz1 + perm[iw1]]]], fx1, fy1, fz1, fw1); nxy1 = lerp(q, nxyz0, nxyz1); nx1 = lerp(r, nxy0, nxy1); - n0 = lerp(t, nx0, nx1); - - nxyz0 = grad4( - perm[ix1 + perm[iy0 + perm[iz0 + perm[iw0]]]], - fx1, - fy0, - fz0, - fw0 - ); - nxyz1 = grad4( - perm[ix1 + perm[iy0 + perm[iz0 + perm[iw1]]]], - fx1, - fy0, - fz0, - fw1 - ); - nxy0 = lerp(q, nxyz0, nxyz1); - - nxyz0 = grad4( - perm[ix1 + perm[iy0 + perm[iz1 + perm[iw0]]]], - fx1, - fy0, - fz1, - fw0 - ); - nxyz1 = grad4( - perm[ix1 + perm[iy0 + perm[iz1 + perm[iw1]]]], - fx1, - fy0, - fz1, - fw1 - ); - nxy1 = lerp(q, nxyz0, nxyz1); - - nx0 = lerp(r, nxy0, nxy1); - - nxyz0 = grad4( - perm[ix1 + perm[iy1 + perm[iz0 + perm[iw0]]]], - fx1, - fy1, - fz0, - fw0 - ); - nxyz1 = grad4( - perm[ix1 + perm[iy1 + perm[iz0 + perm[iw1]]]], - fx1, - fy1, - fz0, - fw1 - ); - nxy0 = lerp(q, nxyz0, nxyz1); - - nxyz0 = grad4( - perm[ix1 + perm[iy1 + perm[iz1 + perm[iw0]]]], - fx1, - fy1, - fz1, - fw0 - ); - nxyz1 = grad4( - perm[ix1 + perm[iy1 + perm[iz1 + perm[iw1]]]], - fx1, - fy1, - fz1, - fw1 - ); - nxy1 = lerp(q, nxyz0, nxyz1); - - nx1 = lerp(r, nxy0, nxy1); - - n1 = lerp(t, nx0, nx1); + const n1 = lerp(t, nx0, nx1); return scale(0.87 * lerp(s, n0, n1)); } @@ -1199,17 +492,14 @@ function scale(n) { export default function noise(x, y, z, w) { switch (arguments.length) { case 1: - return noise1(x); //todo: move these to perlin functions - break; + return noise1(x); // todo: move these to perlin functions case 2: - return noise2(x, y); //todo: move these to perlin functions - break; + return noise2(x, y); // todo: move these to perlin functions case 3: return noise3(x, y, z); - case 3: + case 4: return noise4(x, y, z, w); - break; } } -//--------------------------------------------------------------------- +// --------------------------------------------------------------------- diff --git a/src/sounds/tts/Amazon_audio.mp3 b/src/sounds/tts/Amazon_audio.mp3 deleted file mode 100644 index e69de29..0000000 diff --git a/src/sounds/tts/Google_audio.mp3 b/src/sounds/tts/Google_audio.mp3 deleted file mode 100644 index d7f31c8..0000000 Binary files a/src/sounds/tts/Google_audio.mp3 and /dev/null differ