├── .github ├── ISSUE_TEMPLATE │ ├── Bug_report.yaml │ ├── Feature_request.yaml │ └── config.yaml ├── PULL_REQUEST_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE │ ├── Bug_fixes.md │ └── Feature_addition.md └── workflows │ ├── automated-tests.yaml │ └── stale.yaml ├── .gitignore ├── .prettierignore ├── API ├── README.md └── api.js ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── MMM-Remote-Control.css ├── MMM-Remote-Control.js ├── README.md ├── cspell.config.json ├── custom_menu.example.json ├── docs ├── favicon.png ├── index.html └── swagger.json ├── eslint.config.mjs ├── img ├── apple-touch-icon.png ├── hide_show_module_screencast.gif ├── main_screenshot.png └── power_screenshot.png ├── installer.sh ├── modules.json.template ├── node_helper.js ├── package-lock.json ├── package.json ├── prettier.config.mjs ├── remote.css ├── remote.html ├── remote.js ├── scripts ├── download_modules.js └── download_modules_manually.mjs └── translations ├── ca.json ├── da.json ├── de.json ├── en.json ├── es.json ├── fr.json ├── id.json ├── it.json ├── nb.json ├── nl.json ├── pt.json ├── sv.json ├── tr.json └── zh-cn.json /.github/ISSUE_TEMPLATE/Bug_report.yaml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Think you found a bug? Let us know! 3 | labels: [bug, to check] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | If you publish this issue, you accept that you made your own tests, and this is 100% a bug. 9 | You should have the steps to being able to reproduce this bug, for us to check if it's truly a bug. 10 | 11 | Also, you accept that, if this issue it's invalid in any way, will be discarded without receiving any response about it. 12 | If you're not sure if it's a bug, please [ask here](https://forum.magicmirror.builders/category/29/troubleshooting). 13 | 14 | Thanks for taking the time to help Remote Control get better every day! 15 | - type: input 16 | attributes: 17 | label: OS 18 | description: | 19 | example: 20 | - OS: Ubuntu 20.04 21 | validations: 22 | required: true 23 | - type: input 24 | attributes: 25 | label: NodeJS Version 26 | description: | 27 | example: 28 | - NodeJS: 22.0.0 29 | placeholder: "22.x.x" 30 | validations: 31 | required: true 32 | - type: input 33 | attributes: 34 | label: MagicMirror² Version 35 | description: | 36 | example: 37 | - MM: 2.29.0 38 | placeholder: "2.x.x" 39 | validations: 40 | required: true 41 | - type: input 42 | attributes: 43 | label: Remote Control Version 44 | description: | 45 | example: 46 | - Remote Control: 3.0.0 47 | placeholder: "3.x.x" 48 | validations: 49 | required: true 50 | - type: checkboxes 51 | attributes: 52 | label: Did you try using just Remote Control alone with MM? 53 | options: 54 | - label: I have and the error still happening 55 | required: true 56 | - type: textarea 57 | attributes: 58 | label: Description 59 | description: Short explanation of what you were going to do, what did you want to accomplish? 60 | validations: 61 | required: true 62 | - type: textarea 63 | attributes: 64 | label: Expected behavior 65 | description: What should actually happen? 66 | validations: 67 | required: true 68 | - type: textarea 69 | attributes: 70 | label: Current behavior 71 | description: What happened instead? 72 | validations: 73 | required: true 74 | - type: textarea 75 | attributes: 76 | label: Possible solution 77 | description: You can suggest a reason for the bug, if know about it. 78 | validations: 79 | required: false 80 | - type: textarea 81 | attributes: 82 | label: Steps to reproduce 83 | description: Please give details about how do you reach that behavior 84 | placeholder: | 85 | 1. Start MM... 86 | 2. Wait until everything loads 87 | 3. Click button... 88 | 4. See error... 89 | validations: 90 | required: true 91 | - type: textarea 92 | attributes: 93 | label: Log 94 | description: | 95 | Paste the log you're getting, and possibly the error. 96 | **Please, make sure that none of your personal information, such as IP or passwords are exposed.** 97 | render: Shell 98 | validations: 99 | required: true 100 | - type: textarea 101 | attributes: 102 | label: config.js 103 | description: | 104 | Paste here your config.js file. 105 | 106 | Make sure that none of your personal data is present, specially check for: 107 | - IPs from MM instance, or the ones that are whitelisted. Please, replace them with *** so we're totally sure that you have set them. 108 | - API keys, Tokens from different modules, URL to private calendars. 109 | - Directions or Personal data. 110 | render: JavaScript 111 | validations: 112 | required: true 113 | - type: textarea 114 | attributes: 115 | label: Additional info 116 | description: Everything else that you think could be useful for us. ;D 117 | validations: 118 | required: false 119 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature_request.yaml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Could be a nice addition? Feel free to post! 3 | labels: [feature, to check] 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: Description 8 | description: Tell us more about your awesome feature. Why do you feel that this should be implemented? 9 | validations: 10 | required: true 11 | - type: textarea 12 | attributes: 13 | label: Changelog 14 | description: Do you, by any chance, know which files are needed to be changed in order to make it work? 15 | placeholder: | 16 | Probably `api.js` would need a change, because... 17 | validations: 18 | required: false 19 | - type: textarea 20 | attributes: 21 | label: Requirements 22 | description: You have a suggestion of how to implement it? Let's heard it! 23 | placeholder: | 24 | You can change Line 35 of this file and everything is gonna be just fine. 25 | validations: 26 | required: false 27 | - type: textarea 28 | attributes: 29 | label: Additional info 30 | description: Everything else that you think could be useful for us. ;D 31 | validations: 32 | required: false 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yaml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Discussions 4 | url: https://forum.magicmirror.builders/category/29/troubleshooting 5 | about: Don't know how to do it? Ask the community! 6 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | [Bug fixes](?template=Bug_fixes.md) 12 | 13 | [Feature](?template=Feature_addition.md) 14 | 15 | 20 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/Bug_fixes.md: -------------------------------------------------------------------------------- 1 | 11 | 12 | ## Bug Fix 13 | 14 | Fixes # 15 | Fixes # 16 | Fixes # 17 | 18 | 19 | 20 | ### Steps to reproduce 21 | 22 | 23 | 24 | ### Additional info 25 | 26 | 27 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/Feature_addition.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | ## Feature Addition 12 | 13 | ### Description 14 | 15 | 16 | 17 | ### Changelog 18 | 19 | 20 | 21 | ### Requirements 22 | 23 | 24 | 25 | ### Additional info 26 | 27 | 28 | -------------------------------------------------------------------------------- /.github/workflows/automated-tests.yaml: -------------------------------------------------------------------------------- 1 | name: Automated Tests 2 | on: 3 | push: 4 | branches: [master, develop] 5 | pull_request: 6 | branches: [master, develop] 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | code-quality: 13 | runs-on: ubuntu-latest 14 | timeout-minutes: 10 15 | steps: 16 | - run: "echo '🚀 Starting code quality checks for ${{ github.repository }} (ref: ${{ github.ref }})'" 17 | - name: Check out repository code 18 | uses: actions/checkout@v4 19 | - name: Use Node.js 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: lts/* 23 | cache: npm 24 | - name: Install dependencies 25 | run: npm ci 26 | - name: Check spelling 27 | run: node --run test:spelling 28 | - name: Check linting 29 | run: node --run lint 30 | - run: "echo '✨ Code quality checks completed with status: ${{ job.status }}.'" 31 | -------------------------------------------------------------------------------- /.github/workflows/stale.yaml: -------------------------------------------------------------------------------- 1 | name: "Close stale issues and PRs" 2 | on: 3 | schedule: 4 | - cron: "0 0 * * 2" # Every Tuesday at midnight 5 | - cron: "0 0 * * 6" # Every Saturday at midnight 6 | 7 | jobs: 8 | stale: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | issues: write 12 | pull-requests: write 13 | steps: 14 | - uses: actions/stale@v9 15 | with: 16 | stale-issue-message: | 17 | This issue has been automatically marked as stale because it has not had 18 | recent activity. It will be closed if no further activity occurs. Thank you 19 | for your contributions. 20 | stale-pr-message: | 21 | This pull request has been automatically marked as stale because it has not had 22 | recent activity. It will be closed if no further activity occurs. Thank you 23 | for your contributions. 24 | days-before-issue-stale: 20 25 | days-before-pr-stale: 30 26 | days-before-issue-close: 7 27 | days-before-pr-close: 7 28 | stale-issue-label: "stale" 29 | exempt-issue-labels: "working,help wanted,PR Welcome!" 30 | stale-pr-label: "stale" 31 | exempt-pr-labels: "working,help wanted" 32 | exempt-all-milestones: true 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | modules.json 3 | settings.json -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.js 2 | *.mjs -------------------------------------------------------------------------------- /API/README.md: -------------------------------------------------------------------------------- 1 | # MagicMirror² Remote Control API 2 | 3 | ## Introduction 4 | 5 | The MMM-Remote-Control Module for MagicMirror² implements a RESTful(-ish) API to control the MagicMirror² using the existing functionality built-in to MMM-Remote-Control, as well as the notifications commands built into most modules. In addition, the API creates a basic framework which allows for each module to expand or customize their own API by a simple notification. 6 | 7 | This expansion was developed by [shbatm](https://github.com/shbatm) using [juzim's MMM-Api](https://github.com/juzim/MMM-Api) and of-course, [jopyth's MMM-Remote-Control](https://github.com/jopyth/MMM-Remote-Control). 8 | 9 | Modified by [ezeholz](https://github.com/ezeholz) in the 2.2.0+, in the effort of making a more simplified version for everyone to use it. 10 | 11 | ## Overview 12 | 13 | This extension exposes the `/api` URL from your MagicMirror² installation. Just like using the regular MMM-Remote-Control module, make sure your configuration listens on more than just `localhost`. 14 | 15 | All URLs will be of the form: `http://magicmirrorip:8080/api/{your command}` and depending on the command, either `GET` or `POST` methods are accepted. 16 | 17 | ### Basic examples for showing an Alert on the screen 18 | 19 | ```bash 20 | curl -X GET http://magicmirrorip:8080/api/module/alert/showalert?message=Hello&timer=2000 21 | ``` 22 | 23 | ```bash 24 | $ curl -X POST http://magicmirrorip:8080/api/module/alert/showalert \ 25 | -H 'content-type: application/json' \ 26 | -d '{ 27 | "title": "Hello World!", 28 | "message": "Alert Successfully Shown!", 29 | "timer": 2000 30 | }' 31 | ``` 32 | 33 | ### Basic examples of sending a Module Notification 34 | 35 | ```bash 36 | curl -X GET http://magicmirrorip:8080/api/notification/HELLO_WORLD 37 | ``` 38 | 39 | ```bash 40 | $ curl -X POST http://magicmirrorip:8080/api/notification/HELLO_WORLD \ 41 | -H 'content-type: application/json' \ 42 | -d '{ 43 | "mypayload": "Hello World!", 44 | "somthingelse": "Wooo!" 45 | }' 46 | ``` 47 | 48 | ## Authentication 49 | 50 | Providing an API key is recommended; however, remains optional. If you wish to use an API key to authenticate, add an `apiKey:` option to the config section for this module. 51 | 52 | If you ran the `installer.sh` script when you installed the module, a non-canonical UUID is generated for you to use; you can use this unique code, or use any string you wish. 53 | 54 | ### Example Config Section 55 | 56 | ```js 57 | { 58 | module: 'MMM-Remote-Control' 59 | config: { 60 | apiKey: 'bc2e979db92f4741afad01d5d18eb8e2' 61 | } 62 | }, 63 | ``` 64 | 65 | ### Passing your API key 66 | 67 | The API Key can be passed in one of two ways, either as part of the query string at the end of the URL: 68 | 69 | ```bash 70 | curl -X GET http://magicmirrorip:8080/api/module/alert/showalert?message=Hello&timer=2000&apiKey=bc2e979db92f4741afad01d5d18eb8e2 71 | ``` 72 | 73 | It can also be passed as an Authorization Header: 74 | 75 | ```bash 76 | $ curl -X POST http://magicmirrorip:8080/api/module/alert/showalert \ 77 | -H 'content-type: application/json' \ 78 | -H 'Authorization: apiKey bc2e979db92f4741afad01d5d18eb8e2' \ 79 | -d '{ 80 | "title": "Hello World!", 81 | "message": "Alert Successfully Shown!", 82 | "timer": 2000 83 | }' 84 | ``` 85 | 86 | **_For convenience, the remainder of the examples omit the API Key_** 87 | 88 | ## Secure Endpoints 89 | 90 | Since 2.2.0, and in a way to prevent malicious actions on your mirror, a new config was added. This config allow you to, in case you don't use an apikey or never use the API at all, prevent some endpoints to work without an apikey. 91 | As usual, this option can be disabled, but this will expose your Mirror to potentials hackers, so it's up to you to turn it off. 92 | 93 | ```js 94 | { 95 | module: 'MMM-Remote-Control' 96 | config: { 97 | secureEndpoints: true 98 | } 99 | }, 100 | ``` 101 | 102 | By default, secureEndpoints it's true, defending commands like shutdown or install modules when no apikey it's present. 103 | Setting secureEndpoints to false allow every endpoint to be reachable externally, even without an apikey. (Just like the old times) 104 | 105 | ## Methods 106 | 107 | There are three general categories of API commands: 108 | 109 | **1. MMM-Remote-Control Internal Commands** -- these are used to call the existing commands that MMM-Remote-Control already exposes. For example, to turn off the monitor ("MONITOROFF"): 110 | 111 | ```bash 112 | curl -X GET http://magicmirrorip:8080/api/monitor/off 113 | ``` 114 | 115 | **2. External APIs (Guessed)** -- when this module first loads, it parses all of the installed modules' source code and checks for any custom notifications that are used. From this basic search, it tries to "guess" notification actions that may be valid, without them being explicitly defined anywhere else. For example, the "alert" command examples above are not defined within this module, the 'alert' module just looks for a notification, "SHOW_ALERT"--this is exposed as a `/module/alert/showalert` action in the External API processor. Full credit to this idea goes to `juzim` from the MMM-Api module. 116 | 117 | **3. External APIs (Explicit)** -- these commands are developed when a module loads and sends a "REGISTER_API" notification with command details to this module. These commands will overwrite any "guessed" commands and can provide a way for a module to define its own API, but still use the same routes already in place. 118 | 119 | ### 1. MMM-Remote-Control Internal Commands 120 | 121 | The majority of MMM-Remote-Control's abilities are extended to the API (this is a fundamental reason for "extending" this module instead of creating a new one for the API). 122 | 123 | Review the API documentation online [here](https://ezeholz.github.io/MMM-Remote-Control/) 124 | 125 | Or check it in your own installation using 126 | 127 | ### 2. External APIs (Guessed) 128 | 129 | As discussed above, these methods are guessed based on your currently installed modules. To see what actions are available on your particular installation: 130 | 131 | | Method | URL | Description | 132 | | ------ | ----------------------- | --------------------------------------------------------------------------------------------------------------------------- | 133 | | GET | /api/module | Return a list of all external API actions registered. | 134 | | GET | /api/module/:moduleName | Returns registered API actions for a given module
`:moduleName`: Name or Identifier for an installed & activated module. | 135 | 136 | - _NOTE:_ Just because an action appears in this list, does not necessarily mean it is valid and the related module will do what you want. Consult each modules' README for details on what notifications can be used and how. 137 | 138 | #### Example 139 | 140 | ```bash 141 | curl -X GET http://magicmirrorip:8080/api/module/newsfeed 142 | ``` 143 | 144 | #### Returns 145 | 146 | ```json 147 | { 148 | "success": true, 149 | "module": "newsfeed", 150 | "path": "newsfeed", 151 | "actions": { 152 | "newsitems": { 153 | "notification": "NEWS_ITEMS" 154 | }, 155 | "articlenext": { 156 | "notification": "ARTICLE_NEXT" 157 | }, 158 | "articleprevious": { 159 | "notification": "ARTICLE_PREVIOUS" 160 | }, 161 | "articlemoredetails": { 162 | "notification": "ARTICLE_MORE_DETAILS" 163 | }, 164 | "articlescrollup": { 165 | "notification": "ARTICLE_SCROLL_UP" 166 | }, 167 | "articlelessdetails": { 168 | "notification": "ARTICLE_LESS_DETAILS" 169 | }, 170 | "articletogglefull": { 171 | "notification": "ARTICLE_TOGGLE_FULL" 172 | } 173 | }, 174 | "guessed": true 175 | } 176 | ``` 177 | 178 | | Parameter | Description | 179 | | :---------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 180 | | `"success"` | Result of the GET call | 181 | | `"module"` | Module name | 182 | | `"path"` | API path to use. All lower case, with "MMM-" and "-"s removed (e.g. MMM-Remote-Control's path if it had one would be `/api/module/remotecontrol/`). Can be customized for explicit External APIs. | 183 | | `"actions"` | The list of actions registered, along with the respective notifications that they will call.
For example, `GET /api/module/newsfeed/articlenext` will send a `"ARTICLE_NEXT"` notification to the `newsfeed` module. | 184 | | `"guessed"` | Whether or not the API actions were guessed (not all are reliable) or if they were explicitly provided by the module. | 185 | 186 | ### 3. External APIs (Explicit) - Extending Another Module with this API 187 | 188 | For module developers, you can extend the API to accommodate your needs by sending a "REGISTER_API" module notification. Below is an example and details. 189 | 190 | If correctly formatted, any details sent here will override the "guessed" action by #2 above. 191 | 192 | ```js 193 | let payload = { 194 | module: this.name, 195 | path: "myModuleName", 196 | actions: { 197 | actionName: { 198 | method: "GET", 199 | notification: "NOTIFICATION_TO_SEND", 200 | payload: ObjectToSend, 201 | prettyName: "Action Name" 202 | }, 203 | anotherActionName: { 204 | method: "POST", 205 | notification: "NOTIFICATION_TO_SEND" 206 | } 207 | } 208 | }; 209 | this.sendNotification("REGISTER_API", payload); 210 | ``` 211 | 212 | | Parameter | Description | 213 | | :------------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 214 | | `module` | Actual Name of your Module (e.g. "MMM-Your-Module-Name, or just use `this.name`). | 215 | | `path` | Path to use in the API (e.g. `path: "myModuleName"`) translates to `/api/module/myModuleName`. | 216 | | `actions` | An `Object` defining the actions you want to expose. See below for details. | 217 | | `actionName` | The name for your action (e.g. called from `/api/module/myModuleName/actionName`). | 218 | | `method` | _Optional:_ The HTTP Method to use.
Valid options are: `"GET"` or `"POST"`. If `method` is not provided in an action, then both `"GET"` or `"POST"` methods will be treated as valid. | 219 | | `notification` | The notification to send to your module. When the API receives a valid action, it passes a Module Notification to your module. It is your responsibility to do something with that notification to make the action work. | 220 | | `prettyName` | _Optional:_ You can specify a Formatted Name to use in dynamic menus, like the MMM-Remote-Control Module Control menu, otherwise one will be guessed based on the Notification text. | 221 | | `payload` | _Optional:_ If you always want the module to send the same `payload`, you can provide an `Object` here. It will be merged into the `payload` sent with the notification, which will also include:
1. URL Parameter, if used. See notes on `payload` Object below.
2. Query String, if used. API key will be removed.
3. Request body, if `POST` method is used and a body sent.
4. Finally, this parameter. | 222 | 223 | #### About the `payload` object 224 | 225 | Your module will be sent a `payload` with the notification, depending on the request details, and if you provided a `payload` Object to send. It is a merged object, containing one or more of the following inputs. 226 | 227 | 1. URL parameter. (e.g. `/api/module/myModuleName/action/:p`, where `:p` is the parameter). If nothing else below is passed or provided, this will be returned as a string. If anything else below is sent, this will be provided at `payload.param` in the notification's `payload` Object. 228 | 2. Query String. Anything passed to the query string, except the API key (if used) will be passed through `payload`. For example, `/api/module/myModuleName/action?param1=Something¶m2=Else` will be passed in `payload` as `{ param1: "Something", param2: "Else" }` 229 | 3. `POST` Body. Same as query string above. 230 | 4. Custom Payload. Any `Object` provided with the `payload:` key when you send the initial "REGISTER_API" notification. 231 | 232 | The response sent by the API will include the `payload` Object it sent to your module in the response body for debugging purposes. 233 | -------------------------------------------------------------------------------- /API/api.js: -------------------------------------------------------------------------------- 1 | /* global Module */ 2 | 3 | /* 4 | * MagicMirror² 5 | * Module Extension: Remote Control API 6 | * 7 | * By shbatm 8 | * MIT Licensed. 9 | */ 10 | 11 | const path = require("path"); 12 | const url = require("url"); 13 | const {v4: uuid} = require("uuid"); 14 | const express = require("express"); 15 | 16 | module.exports = { 17 | 18 | /* 19 | * getApiKey 20 | * Middleware method for ExpressJS to check if an API key is provided. 21 | * Only checks for an API key if one is defined in the module's config section. 22 | */ 23 | getApiKey () { 24 | const thisConfig = this.configOnHd.modules.find((x) => x.module === "MMM-Remote-Control"); 25 | if (typeof thisConfig !== "undefined" && 26 | "config" in thisConfig) { 27 | if ("apiKey" in thisConfig.config && 28 | thisConfig.config.apiKey !== "") { 29 | this.apiKey = thisConfig.config.apiKey; 30 | } else { 31 | this.apiKey = undefined; 32 | } 33 | if ("secureEndpoints" in thisConfig.config && 34 | !thisConfig.config.secureEndpoints) { 35 | this.secureEndpoints = false; 36 | } else { 37 | this.secureEndpoints = true; 38 | } 39 | } 40 | }, 41 | 42 | /* 43 | * getExternalApiByGuessing() 44 | * This method is called when an API call is made to /module or /modules 45 | * It checks if a string is a Module Name or an Instance Name and returns 46 | * the actual Module Name 47 | * 48 | * @updates this.externalApiRoutes 49 | */ 50 | getExternalApiByGuessing () { 51 | if (!this.configOnHd) { return undefined; } 52 | 53 | const getActions = function (content) { 54 | const re = /notification ===? (?:"|')([A-Z_-]+?)(?:"|')|case (?:"|')([A-Z_-]+)(?:"|')/g; 55 | const availableActions = []; 56 | if (re.test(content)) { 57 | content.match(re).forEach((match) => { 58 | const n = match.replace(re, "$1"); 59 | if ([ 60 | "ALL_MODULES_STARTED", 61 | "DOM_OBJECTS_CREATED", 62 | "KEYPRESS", 63 | "MODULE_DOM_CREATED", 64 | "KEYPRESS_MODE_CHANGED", 65 | "USER_PRESENCE" 66 | ].indexOf(n) === -1) { 67 | availableActions.push(n); 68 | } 69 | }); 70 | } 71 | return availableActions; 72 | }; 73 | 74 | const skippedModules = ["clock", "compliments", "MMM-Remote-Control"]; 75 | 76 | this.configOnHd.modules.filter((mod) => skippedModules.indexOf(mod.module) === -1).forEach((mod) => { 77 | try { 78 | const modActions = getActions(Module.notificationHandler[mod.module]); 79 | 80 | if (modActions.length > 0) { 81 | // Generate formatted actions object 82 | const actionsGuess = {}; 83 | 84 | modActions.forEach((a) => { 85 | actionsGuess[a.replace(/[-_]/g, "").toLowerCase()] = {notification: a, guessed: true}; 86 | }); 87 | 88 | if (mod.module in this.externalApiRoutes) { 89 | this.externalApiRoutes[mod.module].actions = {...actionsGuess, ...this.externalApiRoutes[mod.module].actions}; 90 | } else { 91 | this.externalApiRoutes[mod.module] = { 92 | module: mod.module, 93 | path: mod.module.replace(/MMM-/g, "").replace(/-/g, "").toLowerCase(), 94 | actions: actionsGuess 95 | }; 96 | } 97 | } 98 | } catch (err) { 99 | console.warn(`getExternalApiByGuessing failed for ${mod.module}: ${err.message}`); 100 | } 101 | }); 102 | 103 | this.updateModuleApiMenu(); 104 | }, 105 | 106 | createApiRoutes () { 107 | const self = this; 108 | 109 | this.getApiKey(); 110 | 111 | this.expressApp.use(express.urlencoded({extended: true})); 112 | this.expressApp.use(express.json()); 113 | 114 | this.expressApp.use("/api/docs", express.static(path.join(__dirname, "../docs"))); // Docs without apikey 115 | 116 | this.expressRouter = express.Router(); 117 | 118 | /* 119 | * MagicMirror² switches to Express 5 with v2.32.0 - to keep compatibility with older versions we need 120 | * to check for the Express version. Since Express 5 dropt the .del method, we can use that to check. 121 | * If the method is not available, we are using Express 4.x and need to use the old syntax. 122 | * This is a temporary solution and will be removed in the future. 123 | */ 124 | const expressVersionLessThan5 = express.application.del ? true : false; 125 | 126 | // Route for testing the api at http://mirror:8080/api/test 127 | this.expressRouter.route(["/test", "/"]). // Test without apiKey 128 | get((req, res) => { 129 | if (!this.checkInitialized(res)) { return; } 130 | res.json({success: true}); 131 | }); 132 | 133 | /* 134 | * Check for authorization if apiKey is defined in the config. 135 | * Can be passed as a header "Authorization: apiKey YOURAPIKEY" or "Authorization: Bearer YOURAPIKEY" 136 | * or can be passed in the url ?apiKey=YOURAPIKEY 137 | */ 138 | this.expressRouter.use((req, res, next) => { 139 | if (typeof this.apiKey !== "undefined") { 140 | if (!("authorization" in req.headers) || req.headers.authorization.search(/(apikey|bearer)/gi) === -1) { 141 | // API Key was not provided as a header. Check the URL. 142 | const {query} = url.parse(req.url, true); 143 | if ("apiKey" in query) { 144 | if (query.apiKey !== this.apiKey) { 145 | return res.status(401).json({success: false, message: "Unauthorized: Wrong API Key Provided!"}); 146 | } 147 | } else { 148 | return res.status(403).json({success: false, message: "Forbidden: API Key Not Provided!"}); 149 | } 150 | } else if (req.headers.authorization.split(" ")[1] !== this.apiKey) { 151 | return res.status(401).json({success: false, message: "Unauthorized: Wrong API Key Provided!"}); 152 | } 153 | } 154 | 155 | // Check for correct Content-Type header: 156 | if (req.method === "POST" && !req.is("application/json")) { 157 | res.status(400).json({success: false, message: "Incorrect content-type, must be 'application/json'"}); 158 | return; 159 | } 160 | 161 | next(); // make sure we go to the next routes and don't stop here 162 | }); 163 | 164 | this.expressRouter.route([ 165 | "/saves", 166 | "/classes", 167 | "/module/installed", 168 | "/module/available", 169 | "/brightness", 170 | "/translations", 171 | "/mmUpdateAvailable", 172 | "/config" 173 | ]).get((req, res) => { 174 | let r = req.path.substring(1); 175 | r = r.replace(/\/([a-z])/i, (v) => v.substring(1).toUpperCase()).replace("/", ""); 176 | self.answerGet({data: r}, res); 177 | }); 178 | 179 | let route = expressVersionLessThan5 180 | ? [ 181 | "/refresh/:delayed?", 182 | "/shutdown/:delayed?", 183 | "/reboot/:delayed?", 184 | "/restart/:delayed?", 185 | "/save", 186 | "/minimize", 187 | "/togglefullscreen", 188 | "/devtools" 189 | ] 190 | : [ 191 | "/refresh{/:delayed}", 192 | "/shutdown{/:delayed}", 193 | "/reboot{/:delayed}", 194 | "/restart{/:delayed}", 195 | "/save", 196 | "/minimize", 197 | "/togglefullscreen", 198 | "/devtools" 199 | ]; 200 | 201 | this.expressRouter.route(route). 202 | get((req, res) => { 203 | if (!this.apiKey && this.secureEndpoints) { return res.status(403).json({success: false, message: "Forbidden: API Key Not Provided in Config! Use secureEndpoints to bypass this message"}); } 204 | const r = req.path.split("/")[1].toUpperCase(); 205 | console.log(req.path); 206 | self.executeQuery(this.checkDelay({action: r}, req), res); 207 | }); 208 | 209 | this.expressRouter.route("/classes/:value"). 210 | get((req, res) => { 211 | const classes = self.getConfig().modules.find((m) => m.module === "MMM-Remote-Control").config || {}; 212 | const val = decodeURIComponent(req.params.value); 213 | if (classes.classes && classes.classes[val]) { 214 | self.executeQuery({action: "MANAGE_CLASSES", payload: {classes: req.params.value}}, res); 215 | } else { 216 | res.status(400).json({success: false, message: `Invalid value ${val} provided in request. Use /api/classes to see actual values`}); 217 | } 218 | }); 219 | 220 | this.expressRouter.route("/command/:value"). 221 | get((req, res) => { 222 | if (!this.apiKey && this.secureEndpoints) { return res.status(403).json({success: false, message: "Forbidden: API Key Not Provided in Config! Use secureEndpoints to bypass this message"}); } 223 | self.executeQuery({action: "COMMAND", command: req.params.value}, res); 224 | }); 225 | 226 | route = expressVersionLessThan5 227 | ? "/userpresence/:value?" 228 | : "/userpresence{/:value}"; 229 | this.expressRouter.route(route). 230 | get((req, res) => { 231 | if (req.params.value) { 232 | if (req.params.value === "true" || req.params.value === "false") { 233 | self.executeQuery({action: "USER_PRESENCE", value: req.params.value === "true"}, res); 234 | } else { 235 | res.status(400).json({success: false, message: `Invalid value ${req.params.value} provided in request. Must be true or false.`}); 236 | } 237 | } else { 238 | self.answerGet({data: "userPresence"}, res); 239 | } 240 | }); 241 | 242 | route = expressVersionLessThan5 243 | ? "/update/:moduleName?" 244 | : "/update{/:moduleName}"; 245 | this.expressRouter.route(route). 246 | get((req, res) => { 247 | if (!this.apiKey && this.secureEndpoints) { return res.status(403).json({success: false, message: "Forbidden: API Key Not Provided in Config! Use secureEndpoints to bypass this message"}); } 248 | if (!req.params.moduleName) { return self.answerGet({data: "mmUpdateAvailable"}, res); } 249 | switch (req.params.moduleName) { 250 | case "mm": case "MM": self.answerGet({data: "mmUpdateAvailable"}, res); break; 251 | case "rc": case "RC": this.updateModule("MMM-Remote-Control", res); break; 252 | default: this.updateModule(req.params.moduleName, res); break; 253 | } 254 | }); 255 | 256 | this.expressRouter.route("/install"). 257 | get((req, res) => { 258 | res.status(400).json({success: false, message: "Invalid method, use POST"}); 259 | }). 260 | post((req, res) => { 261 | if (!this.apiKey && this.secureEndpoints) { return res.status(403).json({success: false, message: "Forbidden: API Key Not Provided in Config! Use secureEndpoints to bypass this message"}); } 262 | if (typeof req.body !== "undefined" && "url" in req.body) { 263 | this.installModule(req.body.url, res); 264 | } else { 265 | res.status(400).json({success: false, message: "Invalid URL provided in request body"}); 266 | } 267 | }); 268 | 269 | // edit config, payload is completely new config object with your changes(edits). 270 | this.expressRouter.route("/config/edit"). 271 | get((req, res) => { 272 | res.status(400).json({success: false, message: "Invalid method, use POST"}); 273 | }). 274 | post((req, res) => { 275 | if (!this.apiKey && this.secureEndpoints) { return res.status(403).json({success: false, message: "Forbidden: API Key Not Provided in Config! Use secureEndpoints to bypass this message"}); } 276 | if (typeof req.body !== "undefined" && "payload" in req.body) { 277 | this.answerPost({data: "config"}, {body: req.body.payload}, res); 278 | } else { 279 | res.status(400).json({success: false, message: "Invalid URL provided in request body"}); 280 | } 281 | }); 282 | // edit config 283 | 284 | route = expressVersionLessThan5 285 | ? "/notification/:notification/:p?/:delayed?" 286 | : "/notification/:notification{/:p}{/:delayed}"; 287 | this.expressRouter.route(route). 288 | get((req, res) => { 289 | if (!this.apiKey && this.secureEndpoints) { return res.status(403).json({success: false, message: "Forbidden: API Key Not Provided in Config! Use secureEndpoints to bypass this message"}); } 290 | this.answerNotifyApi(req, res); 291 | }). 292 | post((req, res) => { 293 | if (!this.apiKey && this.secureEndpoints) { return res.status(403).json({success: false, message: "Forbidden: API Key Not Provided in Config! Use secureEndpoints to bypass this message"}); } 294 | req.params = { 295 | ...req.params, 296 | ...req.body 297 | }; 298 | this.answerNotifyApi(req, res); 299 | }); 300 | 301 | route = expressVersionLessThan5 302 | ? "/module/:moduleName?/:action?/:delayed?" 303 | : "/module{/:moduleName}{/:action}{/:delayed}"; 304 | this.expressRouter.route(route). 305 | get((req, res) => { 306 | this.answerModuleApi(req, res); 307 | }). 308 | post((req, res) => { 309 | req.params = { 310 | ...req.params, 311 | ...req.body 312 | }; 313 | this.answerModuleApi(req, res); 314 | }); 315 | 316 | route = expressVersionLessThan5 317 | ? "/monitor/:action?/:delayed?" 318 | : "/monitor{/:action}{/:delayed}"; 319 | this.expressRouter.route(route). 320 | get((req, res) => { 321 | if (!req.params.action) { req.params.action = "STATUS"; } 322 | const actionName = req.params.action.toUpperCase(); 323 | this.executeQuery(this.checkDelay({action: `MONITOR${actionName}`}, req), res); 324 | }). 325 | post((req, res) => { 326 | let actionName = "STATUS"; 327 | if (typeof req.body !== "undefined" && "monitor" in req.body) { 328 | if (["OFF", "ON", "TOGGLE"].includes(req.body.monitor.toUpperCase())) { 329 | actionName = req.body.monitor.toUpperCase(); 330 | } 331 | } else { 332 | actionName = req.params.action ? req.params.action.toUpperCase() : "STATUS"; 333 | } 334 | this.executeQuery(this.checkDelay({action: `MONITOR${actionName}`}, req), res); 335 | }); 336 | 337 | this.expressRouter.route(["/brightness/:setting"]). 338 | get((req, res) => { 339 | // Only allow numeric settings, otherwise return 400 340 | if (!(/^\d+$/).test(req.params.setting)) { 341 | return res.status(400).json({success: false, message: "Invalid brightness setting"}); 342 | } 343 | this.executeQuery({action: "BRIGHTNESS", value: req.params.setting}, res); 344 | }); 345 | 346 | this.expressRouter.route("/timers").get((req, res) => { this.sendResponse(res, undefined, this.delayedQueryTimers); }); 347 | 348 | this.expressApp.use("/api", this.expressRouter); 349 | 350 | this.getExternalApiByGuessing(); 351 | }, 352 | 353 | checkDelay: (query, req) => { 354 | 355 | /* 356 | * expects .../delay?did=SOME_UNIQUE_ID&timeout=10&abort=false 357 | * accepts .../delay 358 | * defaults to a 10s delay with a random UUID as ID. 359 | */ 360 | if (req.params && req.params.delayed && req.params.delayed === "delay") { 361 | const dQuery = { 362 | action: "DELAYED", 363 | did: req.query.did ? req.query.did : req.body.did ? req.body.did : uuid().replace(/-/g, ""), 364 | timeout: req.query.timeout ? req.query.timeout : req.body.timeout ? req.body.timeout : 10, 365 | abort: req.query.abort && req.query.abort === "true" ? true : Boolean(req.query.abort && req.query.abort === "true"), 366 | query 367 | }; 368 | return dQuery; 369 | } 370 | return query; 371 | }, 372 | 373 | mergeData () { 374 | const extApiRoutes = this.externalApiRoutes; 375 | const modules = this.configData.moduleData; 376 | const query = {success: true, data: []}; 377 | 378 | modules.forEach((mod) => { 379 | if (extApiRoutes[mod.name] === undefined) { 380 | query.data.push(mod); 381 | } else { 382 | query.data.push({...mod, actions: extApiRoutes[mod.name].actions, urlPath: extApiRoutes[mod.name].path}); 383 | } 384 | }); 385 | 386 | return query; 387 | }, 388 | 389 | answerModuleApi (req, res) { 390 | if (!this.checkInitialized(res)) { return; } 391 | const dataMerged = this.mergeData().data; 392 | 393 | if (!req.params.moduleName) { 394 | res.json({success: true, data: dataMerged}); 395 | return; 396 | } 397 | 398 | let modData = []; 399 | if (req.params.moduleName !== "all") { 400 | modData = dataMerged.filter((m) => { 401 | const name = req.params.moduleName; 402 | return name.includes(m.identifier) || name.includes(m.name) || name.includes(m.urlPath); 403 | }); 404 | if (!modData.length) { 405 | modData = dataMerged.filter((m) => req.params.moduleName.includes(m.name)); 406 | } 407 | } else { 408 | modData = dataMerged; 409 | } 410 | 411 | if (!modData.length) { 412 | res.status(400).json({success: false, message: "Module Name or Identifier Not Found!"}); 413 | return; 414 | } 415 | 416 | if (!req.params.action) { 417 | res.json({success: true, data: modData}); 418 | return; 419 | } 420 | 421 | let action = req.params.action.toUpperCase(); 422 | 423 | if (["SHOW", "HIDE", "FORCE", "TOGGLE", "DEFAULTS"].indexOf(action) !== -1) { // /api/modules part of the code 424 | if (action === "DEFAULTS") { 425 | this.answerGet({data: "defaultConfig", module: req.params.moduleName}, res); 426 | return; 427 | } 428 | 429 | if (req.params.moduleName === "all") { 430 | const query = {module: "all"}; 431 | if (action === "FORCE") { 432 | query.action = "SHOW"; 433 | query.force = true; 434 | } else { 435 | query.action = action; 436 | } 437 | this.executeQuery(this.checkDelay(query, req), res); 438 | return; 439 | } 440 | 441 | modData.forEach((mod) => { 442 | const query = {module: mod.identifier}; 443 | if (action === "FORCE") { 444 | query.action = "SHOW"; 445 | query.force = true; 446 | } else { 447 | query.action = action; 448 | } 449 | this.executeQuery(this.checkDelay(query, req), res); 450 | }); 451 | this.sendSocketNotification("UPDATE"); 452 | return; 453 | } 454 | 455 | action = modData[0].actions[req.params.action]; 456 | 457 | if (action) { 458 | if ("method" in action && action.method !== req.method) { 459 | res.status(400).json({success: false, info: `Method ${req.method} is not allowed for ${req.params.moduleName}/${req.params.action}.`}); 460 | return; 461 | } 462 | this.answerNotifyApi(req, res, action); 463 | } 464 | }, 465 | 466 | answerNotifyApi (req, res, action) { 467 | // Build the payload to send with our notification. 468 | let n = ""; 469 | if (action) { n = action.notification; } else if ("notification" in req.params) { 470 | n = decodeURI(req.params.notification); 471 | } 472 | 473 | /* 474 | * If only a URL Parameter is passed, it will be sent as a string 475 | * If we have either a query string or a payload already provided w the action, 476 | * then the parameter will be inside the payload.param property. 477 | */ 478 | delete req.query.apiKey; 479 | let query = {notification: n}; 480 | if (req.params.p && req.params.p === "delay") { 481 | req.params.delayed = req.params.p; 482 | delete req.params.p; 483 | } 484 | if (req.params.delayed && req.params.delayed === "delay") { 485 | query = this.checkDelay(query, req); 486 | if (req.query) { 487 | delete req.query.did; 488 | delete req.query.abort; 489 | delete req.query.timeout; 490 | } 491 | } 492 | 493 | let payload = {}; 494 | if (Object.keys(req.query).length === 0 && typeof req.params.p !== "undefined") { 495 | payload = req.params.p; 496 | } else if (Object.keys(req.query).length !== 0 && typeof req.params.p !== "undefined") { 497 | payload = {param: req.params.p, ...req.query}; 498 | } else { 499 | payload = req.query; 500 | } 501 | if (req.method === "POST" && typeof req.body !== "undefined") { 502 | if (typeof payload === "object") { 503 | payload = {...payload, ...req.body}; 504 | } else { 505 | payload = {param: payload, ...req.body}; 506 | } 507 | } 508 | if (action && action.payload) { 509 | if (typeof payload === "object") { 510 | payload = {...payload, ...action.payload}; 511 | } else { 512 | payload = {param: payload, ...action.payload}; 513 | } 514 | } 515 | 516 | if ("action" in query && query.action == "DELAYED") { 517 | query.query.payload = payload; 518 | query.query.action = "NOTIFICATION"; 519 | this.delayedQuery(query, res); 520 | } else { 521 | query.payload = payload; 522 | this.sendSocketNotification("NOTIFICATION", query); 523 | res.json({success: true, ...query}); 524 | } 525 | 526 | }, 527 | 528 | checkInitialized (res) { 529 | if (!this.initialized) { 530 | this.sendResponse(res, "Not initialized, have you opened or refreshed your browser since the last time you started MagicMirror²?"); 531 | return false; 532 | } 533 | return true; 534 | }, 535 | 536 | updateModuleApiMenu () { 537 | if (!this.thisConfig.showModuleApiMenu) { return; } 538 | 539 | this.moduleApiMenu = { 540 | id: "module-control", 541 | type: "menu", 542 | text: this.translate("%%TRANSLATE:MODULE_CONTROLS%%"), 543 | icon: "window-restore", 544 | items: [] 545 | }; 546 | Object.values(this.externalApiRoutes).forEach((r) => { 547 | const sub = { 548 | id: `mc-${r.path}`, 549 | type: "menu", 550 | icon: "bars", 551 | text: this.formatName(r.module), 552 | items: [] 553 | }; 554 | Object.keys(r.actions).forEach((a) => { 555 | const item = { 556 | id: `mc-${r.path}-${a}`, 557 | menu: "item", 558 | icon: "dot-circle-o", 559 | action: "NOTIFICATION", 560 | content: r.actions[a] 561 | }; 562 | if ("prettyName" in r.actions[a]) { 563 | item.text = this.translate(r.actions[a].prettyName); 564 | } else { 565 | item.text = this.translate(r.actions[a].notification).toLowerCase().replace(/(^|_)(\w)/g, ($0, $1, $2) => ($1 && " ") + $2.toUpperCase()); 566 | } 567 | sub.items.push(item); 568 | }); 569 | this.moduleApiMenu.items.push(sub); 570 | }); 571 | 572 | // console.log(JSON.stringify(this.moduleApiMenu, undefined, 3)); 573 | this.sendSocketNotification("REMOTE_CLIENT_MODULEAPI_MENU", this.moduleApiMenu); 574 | } 575 | }; 576 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # MMM-Remote-Control Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](https://semver.org/). 7 | 8 | ## [3.1.13](https://github.com/jopyth/MMM-Remote-Control/compare/v3.1.12...v3.1.13) - 2025-05-24 9 | 10 | ## Changed 11 | 12 | - chore: switch stale workflow schedule to twice a week 13 | - chore: update dependencies 14 | - refactor: use `fs.constants.F_OK` for file access checks 15 | 16 | ## [3.1.12](https://github.com/jopyth/MMM-Remote-Control/compare/v3.1.11...v3.1.12) - 2025-05-20 17 | 18 | ### Fixed 19 | 20 | - docs: correct config file path 21 | - fix: ignore `settings.json` to prevent merge conflicts. This was accidentally removed in commit 6d7c85b12c8dce2ec772f9a9b1892b098576872f. 22 | - fix: set `brightness` and `temp` for saving into `settings.json` and offering in API 23 | 24 | ### Changed 25 | 26 | - refactor: centralize config path retrieval in `combineConfig` and `answerPost` 27 | 28 | ## [3.1.11](https://github.com/jopyth/MMM-Remote-Control/compare/v3.1.10...v3.1.11) - 2025-05-18 29 | 30 | ### Fixed 31 | 32 | - fix: support Express v4 and v5 - to fix issue #340 33 | - The issue was introduced in 3.1.9, when the module was updated to be compatible with Express v5. 34 | - Since the new route definitions are not compatible with Express v4, the module will now check the version of Express and use the appropriate route definitions. 35 | 36 | ## [3.1.10](https://github.com/jopyth/MMM-Remote-Control/compare/v3.1.9...v3.1.10) - 2025-05-18 37 | 38 | ### Changed 39 | 40 | - chore: re-add Prettier config (was removed in 3.1.9, but caused issues in GitHub actions) 41 | - chore: update devDependencies 42 | - docs: move images to own directory 43 | - docs: update screenshots and add screenshot heading to README 44 | 45 | ## [3.1.9](https://github.com/jopyth/MMM-Remote-Control/compare/v3.1.8...v3.1.9) - 2025-05-17 46 | 47 | ### Changed 48 | 49 | - chore: review linter setup 50 | - chore: update devDependencies 51 | - chore: update log messages for module default retrieval outputs 52 | - docs: update URL to own wiki 53 | - refactor: replace `body-parser` with express's built-in body parsing 54 | - refactor: update route definitions and improve response handling to be compatible with express v5 (without this MMM-Remote-Control would not work with the next release of MagicMirror²) 55 | - refactor: update scripts to use `node --run` 56 | 57 | ### Fixed 58 | 59 | - fix: replace not working discussions URL with forum URL 60 | 61 | ## [3.1.8](https://github.com/jopyth/MMM-Remote-Control/compare/v3.1.7...v3.1.8) - 2025-04-27 62 | 63 | ### Changed 64 | 65 | - refactor: choose installation command depending on the existence of `package-lock.json` and `package.json` This also fixes a problem that occurred with modules with `package.json` but without `package-lock.json` during installation with `npm ci`. 66 | - refactor: don't save `header` to config file if not set 67 | - refactor: get module defaults also from browser to handle bundled modules better (this will fix [#331](https://github.com/Jopyth/MMM-Remote-Control/issues/331)) 68 | - chore: update devDependencies 69 | 70 | ### Fixed 71 | 72 | - fix: get default config while adding a module 73 | - fix: don't save module position if not set. Since MM meanwhile checks the position values, an error message appears without this fix. 74 | 75 | ## [3.1.7](https://github.com/jopyth/MMM-Remote-Control/compare/v3.1.6...v3.1.7) - 2025-04-27 76 | 77 | ### Changed 78 | 79 | - refactor: replace jQuery with vanilla JavaScript 80 | - refactor: reorder `#alert` styles to remove rule from stylelint config 81 | - chore: replace `npm install` with `npm ci --omit=dev` for less update issues and improved performance 82 | - chore: update devDependencies 83 | 84 | ## [3.1.6](https://github.com/jopyth/MMM-Remote-Control/compare/v3.1.5...v3.1.6) - 2025-04-13 85 | 86 | ### Changed 87 | 88 | - refactor: replace XMLHttpRequest by fetch 89 | - chore: update English and German translations 90 | - chore: more detailed logging 91 | - chore: update dependencies 92 | - chore: update ESLint configuration to use new import plugin structure 93 | - chore: adjust ESLint rules 94 | - docs: rephrase introduction in README 95 | 96 | ## [3.1.5](https://github.com/jopyth/MMM-Remote-Control/compare/v3.1.4...v3.1.5) - 2025-03-26 97 | 98 | ### Fixed 99 | 100 | - fix: Refactor `setBrightness` and `setTemp`. To fix #322. 101 | 102 | ### Changed 103 | 104 | - refactor: var -> let 105 | - chore: Update devDependencies 106 | - chore: Update modules.json.template 107 | 108 | ## [3.1.4](https://github.com/jopyth/MMM-Remote-Control/compare/v3.1.3...v3.1.4) - 2025-03-22 109 | 110 | ### Fixed 111 | 112 | - fix: Prevent merge conflicts with `modules.json` while updating MMM-Remote-Control via `git pull` (#323) 113 | 114 | ### Changed 115 | 116 | - chore: Update devDependencies 117 | - chore: Refactor error logging in node_helper.js to include module context 118 | - chore: Update bug report template to reflect new version placeholders 119 | 120 | ## [3.1.3](https://github.com/jopyth/MMM-Remote-Control/compare/v3.1.2...v3.1.3) - 2025-03-20 121 | 122 | ### Fixed 123 | 124 | - fix: Only remove `classesButton` when it's there. There was a console error when returning to the main menu from a sub page. 125 | 126 | ### Changed 127 | 128 | - chore: Remove unused `background-color` from `MMM-Remote-Control.css` 129 | - chore: Use vw and vh instead of 100% in `MMM-Remote-Control.css` 130 | 131 | ## [3.1.2](https://github.com/jopyth/MMM-Remote-Control/compare/v3.1.1...v3.1.2) - 2025-03-11 132 | 133 | ### Fixed 134 | 135 | - Fix temperature overlay to fit fullscreen 136 | 137 | ## Changed 138 | 139 | - Update dependencies 140 | - Remove old remote-control-overlay definition 141 | - Simplify stylelint-prettier configuration 142 | 143 | ## [3.1.1](https://github.com/jopyth/MMM-Remote-Control/compare/v3.1.0...v3.1.1) - 2025-03-09 144 | 145 | ### Changed 146 | 147 | - Handle cspell issues 148 | - Refactor setBrightness Use filter instead an overlay 149 | - Optimize slider - Allow 0 for brightness - Set default to center 150 | - Handle linter warnings 151 | - Refactor 152 | 153 | ## [3.1.0](https://github.com/jopyth/MMM-Remote-Control/compare/v3.0.1...v3.1.0) - 2025-03-09 154 | 155 | ### Added 156 | 157 | - Added Color Temperature changing feature (#296) by @Andoramb 158 | 159 | ## [3.0.1](https://github.com/jopyth/MMM-Remote-Control/compare/v3.0.0...v3.0.1) - 2025-03-09 160 | 161 | ### Fixed 162 | 163 | - Solve issue when not using standard config file (#320) by @dangherve 164 | 165 | ### Changed 166 | 167 | - chore: Add @stylistic/eslint-plugin and handle linter issues 168 | - chore: Replace eslint-plugin-import with eslint-plugin-import-x 169 | - chore: Update devDependencies 170 | 171 | ## [3.0.0](https://github.com/jopyth/MMM-Remote-Control/compare/v2.5.4...v3.0.0) - 2025-03-04 172 | 173 | There shouldn't be any breaking changes in this release. But since there are a some changes, which could lead to unexpected behavior, we decided to bump the major version. 174 | 175 | ### Fixed 176 | 177 | - Fix action endpoint for modules #292 178 | 179 | ### Changed 180 | 181 | - Use npm package for jQuery, showdown and swagger-ui (and switch to current versions) 182 | - Update dependencies 183 | - Handle some linter issues 184 | - Drop Google fonts for API page 185 | 186 | ### Added 187 | 188 | - Add compare links to CHANGELOG 189 | - Add Code of Conduct 190 | 191 | ## [2.5.4](https://github.com/jopyth/MMM-Remote-Control/compare/v2.5.3...v2.5.4) - 2025-03-03 192 | 193 | ### Added 194 | 195 | - Add prettier, markdownlint and stylelint to CI 196 | 197 | ### Changed 198 | 199 | - Update dependencies 200 | 201 | ### Fixed 202 | 203 | - Fix linting and formatter issues 204 | 205 | ## [2.5.3](https://github.com/jopyth/MMM-Remote-Control/compare/v2.5.2...v2.5.3) - 2025-01-20 206 | 207 | ### Fixed 208 | 209 | - Fix `download_modules.js` script to automatically download the modules list from the MagicMirror² wiki. This will fix #301. 210 | 211 | ### Changed 212 | 213 | - Replace `node-fetch` by internal fetch API 214 | - Replace old python 2 script `download_modules.py` by JavaScript script, you can run it with `npm run download_modules` to download the modules list from the MagicMirror² wiki. 215 | - Update `uuid`. This will fix #318. 216 | 217 | ## [2.5.2](https://github.com/jopyth/MMM-Remote-Control/compare/v2.5.1...v2.5.2) - 2025-01-18 218 | 219 | ### Fixed 220 | 221 | - Fixed an issue with bundled modules (reported in #302) - the defaults of some bundled modules could not be read. 222 | - Fixed/Updated some URLs. 223 | 224 | ### Changed 225 | 226 | - Replaced `lodash` with built-in JavaScript functions. 227 | - Format and handle some linting issues in `node_helper.js`. 228 | - Switch LICENSE file to markdown for better readability. 229 | - Update `devDependencies`. 230 | 231 | ## [2.5.1](https://github.com/jopyth/MMM-Remote-Control/compare/v2.5.0...v2.5.1) - 2024-12-17 232 | 233 | ### Fixed 234 | 235 | - An error in the installation script. (Since MagicMirror² v2.27.0, the string used as TEST_STRING in `installer.sh` has changed and the installer script was not able to detect the MagicMirror² directory.) 236 | 237 | ## [2.5.0](https://github.com/jopyth/MMM-Remote-Control/compare/v2.4.0...v2.5.0) - 2024-11-20 238 | 239 | ### Added 240 | 241 | - Added a spell checker and fixed problems that were found (#308). 242 | - Added JavaScript linting (for the start with soft rules) (#310). 243 | - Added GitHub workflow for linting and spell checking on every push and pull request (#310). 244 | - Added Turkish language (#305) 245 | 246 | ## [2.4.0](https://github.com/jopyth/MMM-Remote-Control/compare/v2.3.8...v2.4.0) - 2024-10-08 247 | 248 | ### Fixed 249 | 250 | - Module fixing. Thanks @khassel (#307) 251 | 252 | ## [2.3.8](https://github.com/jopyth/MMM-Remote-Control/compare/v2.3.7...v2.3.8) - 2023-10-03 253 | 254 | ### Added 255 | 256 | - `node-fetch` now added to package.json (#293) 257 | 258 | ### Fixed 259 | 260 | - Module name lookup now working as expected (#289) 261 | - QOL Code Cleaning (#287) 262 | 263 | ## [2.3.7](https://github.com/jopyth/MMM-Remote-Control/compare/v2.3.6...v2.3.7) - 2022-10-13 264 | 265 | ### Added 266 | 267 | - Header name to identify instances of modules (#283) 268 | 269 | ### Fixed 270 | 271 | - API now grabs a single module, instead of every instance of the module (#282) 272 | 273 | ## [2.3.6](https://github.com/jopyth/MMM-Remote-Control/compare/v2.3.5...v2.3.6) - 2021-08-01 274 | 275 | ### Fixed 276 | 277 | - API now updates the modules list 278 | 279 | ## [2.3.5](https://github.com/jopyth/MMM-Remote-Control/compare/v2.3.4...v2.3.5) - 2021-07-08 280 | 281 | ### Added 282 | 283 | - Simplified Chinese translation 284 | - 'PM2 not installed' warning 285 | 286 | ## [2.3.4](https://github.com/jopyth/MMM-Remote-Control/compare/v2.3.3...v2.3.4) - 2021-04-21 287 | 288 | ### Added 289 | 290 | - Now you can use MANAGE_CLASSES to use them between modules, instead of just the API 291 | 292 | ### Fixed 293 | 294 | - Classes now detects when you're using identifiers and names in the same action (#259) 295 | 296 | ## [2.3.3](https://github.com/jopyth/MMM-Remote-Control/compare/v2.3.2...v2.3.3) - 2021-04-05 297 | 298 | ### Changed 299 | 300 | - `request` is deprecated inside MM package. Now using `node-fetch` (#257) 301 | 302 | ## [2.3.2](https://github.com/jopyth/MMM-Remote-Control/compare/v2.3.1...v2.3.2) - 2021-02-19 303 | 304 | ### Fixed 305 | 306 | - `value` now travels along with the payload when slide or input it's used on Custom Menus. (#251) 307 | 308 | ### Changed 309 | 310 | - If you use slide or input, and you add a string payload, it'll now be available in the `string` object of the payload. 311 | 312 | ## [2.3.1](https://github.com/jopyth/MMM-Remote-Control/compare/v2.3.0...v2.3.1) - 2020-12-29 313 | 314 | ### Fixed 315 | 316 | - `Find` it's not defined inside some Electron instances (#242 and #235) 317 | - `undefined` modules generated by the `disabled` tag are now handled. (MagicMirrorOrg/MagicMirror#2382) 318 | 319 | ## [2.3.0](https://github.com/jopyth/MMM-Remote-Control/compare/v2.2.2...v2.3.0) - 2020-12-23 320 | 321 | ### Added 322 | 323 | - Custom Shell Commands for everyone! (#159) 324 | - Custom Menus: User Input Field (#181) and Sliders 325 | 326 | ### Fixed 327 | 328 | - "TV is off" now detected (#234) 329 | - Toggle and Status Monitor working as expected (#234) 330 | 331 | ### Changed 332 | 333 | - Now the system used for turn on and off the screen will be `vcgencmd` (#227 and more) 334 | 335 | ## [2.2.2](https://github.com/jopyth/MMM-Remote-Control/compare/v2.2.1...v2.2.2) - 2020-11-24 336 | 337 | ### Fixed 338 | 339 | - Module Installation now working 340 | - iframe now working (#161) 341 | 342 | ## [2.2.1](https://github.com/jopyth/MMM-Remote-Control/compare/v2.2.0...v2.2.1) - 2020-11-18 343 | 344 | ### Fixed 345 | 346 | - Module Identifier now working as expected (#229) 347 | - Update Installation seems to work 348 | 349 | ## [2.2.0](https://github.com/jopyth/MMM-Remote-Control/compare/v2.1.0...v2.2.0) - 2020-11-16 350 | 351 | ### Fixed 352 | 353 | - Default values now removed from backup (#12) 354 | - Custom Menus now works as expected 355 | - API working, not well implemented in the past 356 | - API userPresence now working as expected 357 | 358 | ### Added 359 | 360 | - Updates now show if there's an update available every day (#52) 361 | - Templates for issues and PRs, and also stale for auto management of issues. 362 | - Close Dev Tools (#119) 363 | - Undo Config Implementation (Beta) 364 | - Classes to show, hide or toggle multiple modules at once (#34) 365 | - Classes and saves API 366 | - Changelog of every module updated 367 | - [Showdown](https://github.com/showdownjs/showdown) implemented in order to show changelog markdown. 368 | - secureEndpoint config to bypass the non-apikey limitation. This could be dangerous, use it with caution. 369 | - Added POST support for Monitor API (#200) 370 | - Added endpoint to edit config file (#206) 371 | - Endpoint /api/docs now shows you the documentation available for the API. You can test your mirror right there! 372 | 373 | ### Changed 374 | 375 | - **[lodash](https://lodash.com/) required**. Do `npm install` on the Remote Control module. 376 | - Alert button don't show up when Alert module isn't active 377 | - The way monitor turn on and off (#225) 378 | - Now hide, show or toggle modules also accept arrays 379 | - /api/test can be reach without any apiKey 380 | - /api/modules/installed and /available are now /api/module/installed and /available 381 | - ApiKey required in order to change substantial things on the Mirror 382 | - Some Endpoints are gonna be deprecated in the future. You can see those inside /api/docs, in the Legacy menu. 383 | 384 | ### Removed 385 | 386 | - /api/modules it's no longer available, you can use /api/module instead. 387 | - Postman collection deprecated ~ (Sorry n.n) 388 | 389 | ## [2.1.0](https://github.com/jopyth/MMM-Remote-Control/compare/v2.0.1...v2.1.0) - 2020-11-01 390 | 391 | Hello! Ezequiel here. Just wanted to say thanks for trust in me, in the past days I made a lot of changes into the code, adding some functions that'll surely be in a future release, and also putting everything together in my fork. I answered almost every issue raised, and tried to help every person that use this module. Today, I'm glad to be able to share everything I learned to all of you. I apologize for some fast and uncommented commits, I just thought that some things needed to be fixed ASAP. 392 | See you in future commits, issues and PRs :D 393 | 394 | ### Fixed 395 | 396 | - A typo in `es` translation 397 | - A few typos in README.md (#134 and #149) and API/README.md (#179) 398 | - Delayed commands should now work (#190) 399 | - Typo on remote_action (#184) 400 | - IP now showing (#194) 401 | - MM restart button don't just stop anymore (#126) 402 | - Saving config should work as expected now (#153) 403 | - installer.sh now detects where's the node installation (#222) 404 | 405 | ### Added 406 | 407 | - Danish translation (#157) 408 | - Italian translation (#162) 409 | - Port now showing according to config.js (#98) 410 | - Custom commands for shutdown and reboot 411 | 412 | ### Changed 413 | 414 | - Overwrite of local changes when updating from a repository 415 | - Now requires MagicMirror² version 2.12 416 | 417 | ## [2.0.1](https://github.com/jopyth/MMM-Remote-Control/compare/v2.0.0...v2.0.1) - 2020-10-28 418 | 419 | **Huge thanks to [@ezeholz](https://github.com/ezeholz)** who has offered to maintain the module from now on! 420 | Credit for this (and future) versions and releases goes to @ezeholz (unless noted otherwise). 421 | 422 | Now requires MagicMirror² version 2.7. 423 | 424 | ### Fixed 425 | 426 | - Path to font awesome icons 427 | - A few typos in `ca` and `es` translations 428 | - Updates to `remote.html` to support new `basePath` feature in MM `config.js`, [follow up to this MM issue](https://github.com/MagicMirrorOrg/MagicMirror/issues/1973), related to #185 429 | 430 | ## [2.0.0](https://github.com/jopyth/MMM-Remote-Control/compare/v1.1.5...v2.0.0) - 2019-02-21 431 | 432 | Huge shout out to [shbatm](https://github.com/shbatm) for his work on this new major version, which brings a new API, custom menus and commands and lots of other stuff: 433 | 434 | ### Added 435 | 436 | - REST API interface for controlling all aspects of the MagicMirror² from HTTP RESTful-style GET and POST calls, based on principles from [MMM-Api](https://github.com/juzim/MMM-Api) 437 | - Full API Documentation at [API/README.md](API/README.md) 438 | - Live daily updates of `modules.json` from the MagicMirror² wiki, based on principles from [MMM-Remote-Control-Repository](https://github.com/eouia/MMM-Remote-Control-Repository). 439 | - Incorporated some features found in [MMM-OnScreenMenu](https://github.com/shbatm/MMM-OnScreenMenu) that were not originally in this module. 440 | - Monitor (Connected Screen) On-Off Status and Toggle 441 | - Delayed calls ("DELAYED" Query option and `.../delay` API paths) 442 | - If using Electron: Open Dev Tools, Minimize, Toggle Fullscreen 443 | - Configuration Option to send custom shell commands to use. Currently, only custom monitor on/off/status commands are supported. 444 | - Module Control menu - Automatically generated from the API to control the different modules you have installed, based on their `notificationReceived` function. 445 | - Custom menu items. See [Custom Menu Items in README](README.md#custom-menu-items) 446 | - Norsk bokmål translation 447 | 448 | ### Changed 449 | 450 | - Updates to `remote.html` and the `node_helper.js` to use direct SocketIO communication back and forth instead of separate HTTP calls. 451 | - Future framework for following PM2 logs and more live update options. 452 | - General clean-up and standardization of status reporting for GET and POST calls, to original URLs and to new API URLs. 453 | - Updated to ES2015 (ES Version 6) function calls in most locations. 454 | - Added USER_PRESENCE controls from [AgP42](https://github.com/AgP42) 455 | - Added/updated french translations from [BKeyport](https://github.com/Bkeyport) and [Mysh3ll](https://github.com/Mysh3ll) 456 | - Added SHOW/HIDE/TOGGLE ALL modules option per request from [Rene1709](https://github.com/Rene1709) 457 | 458 | ### Upcoming Changes 459 | 460 | - Add additional MMM-OnScreenMenu features: 461 | - Moving modules' positions 462 | - PM2 Log Follower / Terminal Window 463 | - Added Notification Echo option to config to echo all Module Notifications to the remote's DevTools console for debugging. 464 | - Allow for text input in the Module Controls menu to be able to provide a notification payload. 465 | 466 | ## [1.1.5](https://github.com/jopyth/MMM-Remote-Control/compare/v1.1.4...v1.1.5) - 2018-05-14 467 | 468 | ### Added 469 | 470 | - French translation 471 | 472 | ### Fixed 473 | 474 | - Updated documentation to new MagicMirror² version 475 | - Fix error on updating MagicMirror² 476 | 477 | ## [1.1.4](https://github.com/jopyth/MMM-Remote-Control/compare/v1.1.3...v1.1.4) - 2017-09-17 478 | 479 | ### Added 480 | 481 | - Dutch translation 482 | - Updating a module tries to install dependencies with `npm install` 483 | - Module identifier is shown if a module is locked with lock strings 484 | - Confirmation dialog before restart and shutdown 485 | 486 | ### Fixed 487 | 488 | - Internal save file format and mistakenly hiding modules which were hidden by other modules 489 | - Restart should work for new installations 490 | 491 | ### Changed 492 | 493 | - German translation for power menu changed from "Ausschalten" to "Energieoptionen" 494 | 495 | ## [1.1.3](https://github.com/jopyth/MMM-Remote-Control/compare/v1.1.2...v1.1.3) - 2017-04-23 496 | 497 | ### Added 498 | 499 | - Portuguese translation 500 | - Indonesian translation 501 | - Support for iOS Icon and Webapp 502 | 503 | ### Changed 504 | 505 | - Installation no longer needs a temporary file 506 | 507 | ### Fixed 508 | 509 | - Icon paths adapted to changes in [MagicMirror²](https://github.com/MagicMirrorOrg/MagicMirror) 2.1.0 510 | 511 | ## [1.1.2](https://github.com/jopyth/MMM-Remote-Control/compare/v1.1.1...v1.1.2) - 2017-02-01 512 | 513 | **Note:** Since version 1.1.0 this module uses (new) dependencies, check the [Updating section in the README.md](README.md#update). 514 | 515 | ### Added 516 | 517 | - Swedish translation 518 | 519 | ### Changed 520 | 521 | - Installation process updated in [README.md](README.md#Installation) 522 | - Automatic installer/updater includes hint to restart [MagicMirror²](https://github.com/MagicMirrorOrg/MagicMirror) 523 | 524 | ### Fixed 525 | 526 | - Issues with not applying shown and hidden status correctly to modules 527 | - Issues where lockstrings were missing 528 | - Modules sometimes did not show correctly in the UI as hidden or shown: 529 | - This is due to a bug in [MagicMirror²](https://github.com/MagicMirrorOrg/MagicMirror) 530 | - PR [#659](https://github.com/MagicMirrorOrg/MagicMirror/pull/659) to fix this was made in the project, will be released in the next version 531 | 532 | ## [1.1.1](https://github.com/jopyth/MMM-Remote-Control/compare/v1.1.0...v1.1.1) - 2017-01-26 533 | 534 | ### Changed 535 | 536 | - Updated internal list of available modules 537 | 538 | ## [1.1.0](https://github.com/jopyth/MMM-Remote-Control/compare/v1.0.0...v1.1.0) - 2017-01-26 539 | 540 | ### Added 541 | 542 | - First version of installer script 543 | - Menu to send Alerts and/or Notifications to your mirror 544 | - Menu to update your MagicMirror² installation and your modules (through `git pull`) 545 | - Menu to change the `config.js` 546 | - Modules can be installed, added, removed, configured 547 | - There will be backups of the five last versions of the `config.js` in the `config` folder 548 | - Some of these parts are hidden behind an "experimental" warning, do **not** ignore that warning 549 | - NOTIFICATION action, see [README.md](README.md#notification-request) for details 550 | 551 | ### Changed 552 | 553 | - Menu structure 554 | - Old "Edit" and "Settings" are now under "Edit view" 555 | - Smaller font sizes in lists 556 | 557 | ### Fixed 558 | 559 | - Issues coming from disabled modules since MM version 2.1.0 560 | 561 | ## [1.0.0](https://github.com/jopyth/MMM-Remote-Control/compare/v0.1.0...v1.0.0) - 2016-10-24 - First Official Release 562 | 563 | ### Added 564 | 565 | - Changelog 566 | - New buttons in user interface 567 | - Hide/show all modules 568 | - Link to MagicMirror² homepage 569 | - Option to adapt brightness (making the mirror brighter than 100% can be limited to certain modules) 570 | - Contributing hints 571 | - Internal versioning of saved config (current version: 1) 572 | - Added action `MODULE_DATA` to return module data in JSON format 573 | 574 | ### Changed 575 | 576 | - Internal timeout for commands increased from 5 to 8 seconds 577 | - Symbols for display on and off 578 | - Internal changes in preparation for MagicMirror² version `2.1.0` 579 | 580 | ## [0.1.0](https://github.com/Jopyth/MMM-Remote-Control/releases/tag/v0.1.0) - 2016-09-30 581 | 582 | Initial release of the Remote Control module. 583 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official email address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series of 86 | actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or permanent 93 | ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within the 113 | community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.1, available at 119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 126 | [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 130 | [Mozilla CoC]: https://github.com/mozilla/diversity 131 | [FAQ]: https://www.contributor-covenant.org/faq 132 | [translations]: https://www.contributor-covenant.org/translations 133 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for contributing! 4 | 5 | We're trying to keep the module up to date and we're adding new features every time. 6 | Your contribution help us so much in a lot of ways. 7 | 8 | We ask you to keep contributing, and feel free to open as many issues and PR as you need. 9 | 10 | ## Code of Conduct 11 | 12 | Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. 13 | 14 | ## Developer commands 15 | 16 | - `npm install` - Install all dependencies. 17 | - `node --run download_modules` - Download module list manually. 18 | - `node --run lint` - Run linting and formatter checks. 19 | - `node --run lint:fix` - Fix linting and formatter issues. 20 | - `node --run test` - Run linting and formatter checks + Run spelling check. 21 | - `node --run test:spelling` - Run spelling check. 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright © 2016 Joseph Bethge 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MMM-Remote-Control.css: -------------------------------------------------------------------------------- 1 | #remote-control-overlay-temp { 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | z-index: 2; 6 | width: 100vw; 7 | height: 100vh; 8 | } 9 | -------------------------------------------------------------------------------- /MMM-Remote-Control.js: -------------------------------------------------------------------------------- 1 | /* global Module, Log, MM */ 2 | 3 | /* 4 | * MagicMirror² 5 | * Module: Remote Control 6 | * 7 | * By Joseph Bethge 8 | * MIT Licensed. 9 | */ 10 | 11 | Module.register("MMM-Remote-Control", { 12 | 13 | requiresVersion: "2.12.0", 14 | 15 | // Default module config. 16 | defaults: { 17 | customCommand: {} 18 | }, 19 | 20 | // Define start sequence. 21 | start () { 22 | Log.info(`Starting module: ${this.name}`); 23 | 24 | this.settingsVersion = 2; 25 | 26 | this.addresses = []; 27 | this.port = ""; 28 | 29 | this.brightness = 100; 30 | this.temp = 327; 31 | }, 32 | 33 | getStyles () { 34 | return ["MMM-Remote-Control.css"]; 35 | }, 36 | 37 | notificationReceived (notification, payload, sender) { 38 | Log.debug(`${this.name} received a module notification: ${notification} from sender: ${sender}`); 39 | if (notification === "DOM_OBJECTS_CREATED") { 40 | this.sendSocketNotification("REQUEST_DEFAULT_SETTINGS"); 41 | this.sendCurrentData(); 42 | } 43 | if (notification === "REMOTE_ACTION") { 44 | this.sendSocketNotification(notification, payload); 45 | } 46 | if (notification === "REGISTER_API") { 47 | this.sendSocketNotification(notification, payload); 48 | } 49 | if (notification === "USER_PRESENCE") { 50 | this.sendSocketNotification(notification, payload); 51 | } 52 | }, 53 | 54 | // Override socket notification handler. 55 | socketNotificationReceived (notification, payload) { 56 | if (notification === "UPDATE") { 57 | this.sendCurrentData(); 58 | } 59 | if (notification === "IP_ADDRESSES") { 60 | this.addresses = payload; 61 | if (this.data.position) { 62 | this.updateDom(); 63 | } 64 | } 65 | if (notification === "LOAD_PORT") { 66 | this.port = payload; 67 | if (this.data.position) { 68 | this.updateDom(); 69 | } 70 | } 71 | 72 | if (notification === "USER_PRESENCE") { 73 | this.sendNotification(notification, payload); 74 | } 75 | if (notification === "DEFAULT_SETTINGS") { 76 | let {settingsVersion} = payload; 77 | 78 | if (settingsVersion === undefined) { 79 | settingsVersion = 0; 80 | } 81 | if (settingsVersion < this.settingsVersion) { 82 | if (settingsVersion === 0) { 83 | // move old data into moduleData 84 | payload = {moduleData: payload, brightness: 100}; 85 | payload = {moduleData: payload, temp: 327}; 86 | } 87 | } 88 | 89 | const {moduleData} = payload; 90 | const hideModules = {}; 91 | for (let i = 0; i < moduleData.length; i++) { 92 | for (let k = 0; k < moduleData[i].lockStrings.length; k++) { 93 | if (moduleData[i].lockStrings[k].indexOf("MMM-Remote-Control") >= 0) { 94 | hideModules[moduleData[i].identifier] = true; 95 | break; 96 | } 97 | } 98 | } 99 | 100 | const modules = MM.getModules(); 101 | 102 | const options = {lockString: this.identifier}; 103 | 104 | modules.enumerate((module) => { 105 | if (Object.hasOwn(hideModules, module.identifier)) { 106 | module.hide(0, options); 107 | } 108 | }); 109 | 110 | this.setBrightness(payload.brightness); 111 | this.setTemp(payload.temp); 112 | } 113 | if (notification === "BRIGHTNESS") { 114 | this.setBrightness(parseInt(payload)); 115 | } 116 | if (notification === "TEMP") { 117 | this.setTemp(parseInt(payload)); 118 | } 119 | if (notification === "REFRESH") { 120 | document.location.reload(); 121 | } 122 | if (notification === "RESTART") { 123 | setTimeout(() => { 124 | document.location.reload(); 125 | Log.log("Delayed REFRESH"); 126 | }, 60000); 127 | } 128 | if (notification === "SHOW_ALERT") { 129 | this.sendNotification(notification, payload); 130 | } 131 | if (notification === "HIDE_ALERT") { 132 | this.sendNotification(notification); 133 | } 134 | if (notification === "HIDE" || notification === "SHOW" || notification === "TOGGLE") { 135 | const options = {lockString: this.identifier}; 136 | if (payload.force) { options.force = true; } 137 | let modules = []; 138 | if (payload.module !== "all") { 139 | let x = payload.module; 140 | modules = modules.concat(MM.getModules().filter((m) => { 141 | if (m && x.includes(m.identifier)) { 142 | if (typeof x === "object") { x = x.filter((t) => t != m.identifier); } else { x = ""; } 143 | return true; 144 | } 145 | }), MM.getModules().filter((m) => { 146 | if (m) { 147 | return x.includes(m.name); 148 | } 149 | })); 150 | } else { 151 | modules = MM.getModules(); 152 | } 153 | if (!modules.length) { return; } 154 | modules.forEach((mod) => { 155 | if (notification === "HIDE" || 156 | notification === "TOGGLE" && !mod.hidden) { 157 | mod.hide(1000, options); 158 | } else if (notification === "SHOW" || 159 | notification === "TOGGLE" && mod.hidden) { 160 | mod.show(1000, options); 161 | } 162 | }); 163 | } 164 | if (notification === "NOTIFICATION") { 165 | this.sendNotification(payload.notification, payload.payload); 166 | } 167 | }, 168 | 169 | setBrightness (newBrightnessValue) { 170 | if (newBrightnessValue < 10) { 171 | newBrightnessValue = 0; // Setting Brightness to 0 turns off some displays backlight, it's neat for power saving 172 | } else if (newBrightnessValue > 200) { 173 | newBrightnessValue = 200; 174 | } 175 | const filterValue = `brightness(${newBrightnessValue}%)`; 176 | Log.debug("BRIGHTNESS", newBrightnessValue); 177 | this.brightness = newBrightnessValue; 178 | const childNodesList = document.body.childNodes; 179 | for (let i = 0; i < childNodesList.length; i++) { 180 | if (childNodesList[i].nodeName !== "SCRIPT" && childNodesList[i].nodeName !== "#text") { 181 | childNodesList[i].style.filter = filterValue; 182 | } 183 | } 184 | }, 185 | 186 | setTemp (temp) { 187 | let overlay = document.getElementById("remote-control-overlay-temp"); 188 | if (!overlay) { 189 | // if not existing, create overlay 190 | overlay = document.createElement("div"); 191 | overlay.id = "remote-control-overlay-temp"; 192 | const parent = document.body; 193 | parent.insertBefore(overlay, parent.firstChild); 194 | } 195 | 196 | if (temp > 327) { 197 | overlay.style.backgroundColor = `rgba(255,215,0,${(temp - 325) / 865})`; 198 | } else { 199 | if (temp < 154) { 200 | temp = 154; 201 | } 202 | overlay.style.backgroundColor = `rgba(0, 150, 255,${(325 - temp) / 865})`; 203 | } 204 | Log.debug("TEMP", temp); 205 | this.temp = temp; 206 | }, 207 | 208 | getDom () { 209 | const wrapper = document.createElement("div"); 210 | let portToShow = ""; 211 | if (this.addresses.length === 0) { 212 | this.addresses = ["ip-of-your-mirror"]; 213 | } 214 | switch (this.port) { 215 | case "": case "8080": portToShow = ":8080"; break; 216 | case "80": portToShow = ""; break; 217 | default: portToShow = `:${this.port}`; break; 218 | } 219 | wrapper.innerHTML = `http://${this.addresses[0]}${portToShow}/remote.html`; 220 | wrapper.className = "normal xsmall"; 221 | return wrapper; 222 | }, 223 | 224 | sendCurrentData () { 225 | const modules = MM.getModules(); 226 | const currentModuleData = []; 227 | modules.enumerate((module) => { 228 | const modData = {...module.data}; 229 | modData.hidden = module.hidden; 230 | modData.lockStrings = module.lockStrings; 231 | modData.urlPath = module.name.replace(/MMM-/g, "").replace(/-/g, "").toLowerCase(); 232 | modData.config = module.config; 233 | const modPrototype = Object.getPrototypeOf(module); 234 | modData.defaults = modPrototype.defaults; 235 | currentModuleData.push(modData); 236 | }); 237 | const configData = { 238 | moduleData: currentModuleData, 239 | brightness: this.brightness, 240 | temp: this.temp, 241 | settingsVersion: this.settingsVersion, 242 | remoteConfig: this.config 243 | }; 244 | this.sendSocketNotification("CURRENT_STATUS", configData); 245 | } 246 | }); 247 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MMM-Remote-Control 2 | 3 | **MMM-Remote-Control** is a module for [MagicMirror²](https://github.com/MagicMirrorOrg/MagicMirror) that allows you to use a browser, to quickly shut down your mirror, hide and show modules on your mirror and do other cool stuff. 4 | 5 | The website should work fine on any device (desktop, smartphone, tablet, ...). 6 | 7 | Since we all want our [SD cards to live a long and prosper life](http://raspberrypi.stackexchange.com/a/383) we shut down properly every time before unplugging, right? 8 | 9 | The module also includes a **RESTful API** for controlling all aspects of your mirror from other network-enabled devices and controllers--anything that can open a URL. See the [API README](API/README.md) for more info! 10 | 11 | ## Screenshots 12 | 13 | ### Main menu 14 | 15 | ![main menu](img/main_screenshot.png) 16 | 17 | ### Power menu 18 | 19 | ![power menu](img/power_screenshot.png) 20 | 21 | ### Screencast "Hide and show a module" 22 | 23 | ![Hide and show a module](img/hide_show_module_screencast.gif) 24 | 25 | ## Installation 26 | 27 | ### Quick install 28 | 29 | If you followed the default installation instructions for the [MagicMirror²](https://github.com/MagicMirrorOrg/MagicMirror) project, you should be able to use the automatic installer. 30 | 31 | The following command will download the installer and execute it: 32 | 33 | ```bash 34 | bash -c "$(curl -s https://raw.githubusercontent.com/Jopyth/MMM-Remote-Control/master/installer.sh)" 35 | ``` 36 | 37 | ### Manual install 38 | 39 | - (1) Clone this repository in your `modules` folder, and install dependencies: 40 | 41 | ```bash 42 | cd ~/MagicMirror/modules 43 | git clone https://github.com/Jopyth/MMM-Remote-Control 44 | cd MMM-Remote-Control 45 | npm ci --omit=dev 46 | ``` 47 | 48 | - (2) Add the module to your `config.js` file, if you add a `position`, it will display the URL to the remote on the mirror. 49 | 50 | ```js 51 | { 52 | module: 'MMM-Remote-Control', 53 | // uncomment the following line to show the URL of the remote control on the mirror 54 | // position: 'bottom_left', 55 | // you can hide this module afterwards from the remote control itself 56 | config: { 57 | customCommand: {}, // Optional, See "Using Custom Commands" below 58 | showModuleApiMenu: true, // Optional, Enable the Module Controls menu 59 | secureEndpoints: true, // Optional, See API/README.md 60 | // uncomment any of the lines below if you're gonna use it 61 | // customMenu: "custom_menu.json", // Optional, See "Custom Menu Items" below 62 | // apiKey: "", // Optional, See API/README.md for details 63 | // classes: {} // Optional, See "Custom Classes" below 64 | } 65 | }, 66 | ``` 67 | 68 | - (3) For security reasons, the MagicMirror² (and therefore the Remote Control) is _not_ reachable externally. 69 | To change this, configure `address`, and `ipWhitelist` in your `config.js` (see [these lines in the sample config](https://github.com/MagicMirrorOrg/MagicMirror/blob/master/config/config.js.sample#L12-L22)). 70 | For example change `address` to `0.0.0.0` and add two allowed devices with IP addresses `192.168.0.42` and `192.168.0.50`: 71 | 72 | ```js 73 | address : '0.0.0.0', 74 | port: 8080, 75 | ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:192.168.0.42", "::ffff:192.168.0.50"]," 76 | ``` 77 | 78 | You can also add multiple devices in an IP range (e.g. all devices with `192.168.0.X`): 79 | 80 | ```js 81 | ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:192.168.0.1/120", "192.168.0.1/24"], 82 | ``` 83 | 84 | - (4) Restart your MagicMirror² (i.e. `pm2 restart MagicMirror`). 85 | 86 | - (5) Access the remote interface on [http://192.168.xxx.xxx:8080/remote.html](http://192.168.xxx.xxx:8080/remote.html) (replace with IP address of your RaspberryPi). 87 | 88 | Note: If your user does not have `sudo` rights, the shutdown does not work (it _should_ work for everyone who did not change anything on this matter). 89 | 90 | ### Update 91 | 92 | Update this module by navigating into its folder on the command line and using `git pull`: 93 | 94 | ```bash 95 | cd ~/MagicMirror/modules/MMM-Remote-Control 96 | git pull 97 | npm ci --omit=dev 98 | ``` 99 | 100 | Alternatively you can run the `installer.sh` script again: 101 | 102 | ```bash 103 | ~/MagicMirror/modules/MMM-Remote-Control/installer.sh 104 | ``` 105 | 106 | ### Select version manually 107 | 108 | You can check out specific versions in the following way. 109 | First look at which versions are available: 110 | 111 | ```bash 112 | cd MagicMirror/modules/MMM-Remote-Control # or wherever you installed the Mirror and the module 113 | git fetch # fetch all tags 114 | git tag # display them 115 | ``` 116 | 117 | The output should look similar to this: 118 | 119 | ```bash 120 | v1.0.0 121 | v1.1.0 122 | v1.1.1 123 | v1.1.2 124 | ``` 125 | 126 | Then you can checkout that version with, for example `git checkout v1.0.0`, or use `git checkout master` to checkout the most recent version. 127 | 128 | ## Known limitations 129 | 130 | Whenever you change the order of modules in `config.js` or add/remove modules, the indices of the modules change. 131 | Therefore the hidden/shown status of modules might not be correctly applied. 132 | If this happens, simply reconfigure and save it again. 133 | 134 | ## Call methods from other modules 135 | 136 | You can call any of the methods provided in the UI directly through a GET request, or a module notification. 137 | For example you can use [MMM-ModuleScheduler](https://forum.magicmirror.builders/topic/691/mmm-modulescheduler) to automatically shutdown your RaspberryPi at a certain time, or integrate it with home automation systems. Or use [MMM-Navigate](https://github.com/Ax-LED/MMM-Navigate) to allow direct actions from your Mirror by using a rotating button. 138 | 139 | ### Examples 140 | 141 | - Example for a REST API GET request to trigger a RaspberryPi restart: 142 | 143 | `http://192.168.xxx.xxx:8080/api/restart` 144 | 145 | - Example to trigger a RaspberryPi restart in your module: 146 | 147 | ```js 148 | this.sendNotification("REMOTE_ACTION", { action: "RESTART" }); 149 | ``` 150 | 151 | See some specific examples for controlling your mirror from other modules and add your own examples [in the Wiki page here](https://github.com/Jopyth/MMM-Remote-Control/wiki/Examples-for-Controlling-from-Another-Module) 152 | 153 | ### List of actions 154 | 155 | #### System Control 156 | 157 | | Action | Description | 158 | | :-----------: | ----------------------------------------------------------------------------- | 159 | | SHUTDOWN | Shutdown your RaspberryPi | 160 | | REBOOT | Restart your RaspberryPi | 161 | | MONITORON | Switch your display on. Also sends a `"USER_PRESENCE": true` notification. | 162 | | MONITOROFF | Switch your display off. Also sends a `"USER_PRESENCE": false` notification. | 163 | | MONITORTOGGLE | Toggle the display on or off (with respective `"USER_PRESENCE"` notification. | 164 | | MONITORSTATUS | Report back the monitor status (on or off) | 165 | 166 | #### MagicMirror² Control 167 | 168 | | Action | Description | 169 | | :--------: | -------------------------------------------------------------------------------------------------------------------------------------- | 170 | | RESTART | Restart your MagicMirror² | 171 | | REFRESH | Refresh mirror page | 172 | | UPDATE | Update MagicMirror² and any of it's modules | 173 | | SAVE | Save the current configuration (show and hide status of modules, and brightness), will be applied after the mirror starts | 174 | | BRIGHTNESS | Change mirror brightness, with the new value specified by `value`. `100` equals the default, possible range is between `10` and `200`. | 175 | 176 | #### MagicMirror² Electron Browser Window Control 177 | 178 | | Action | Description | 179 | | :--------------: | ---------------------------------- | 180 | | MINIMIZE | Minimize the browser window. | 181 | | TOGGLEFULLSCREEN | Toggle fullscreen mode on and off. | 182 | | DEVTOOLS | Open the DevTools console window. | 183 | 184 | #### Module Control 185 | 186 | | Action | Description | 187 | | :---------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 188 | | HIDE | Hide a module, with the name (or identifier--see `MODULE_DATA` action) specified by `module` in the payload. You can also send `module: "all"` to hide all modules. | 189 | | SHOW | Show a module (see above for how to specify which one). | 190 | | TOGGLE | Toggle a module's visibility (see above for how to specify which one). | 191 | | FORCE | Force a module to show (see above for how to specify which one). | 192 | | MODULE_DATA | Returns a JSON format of the data displayed in the UI, including all valid identifiers for the `HIDE` and `SHOW` action. | 193 | 194 | #### Alerts and Notifications 195 | 196 | | Action | Description | 197 | | :-----------: | ----------------------------------------------------------------------------------------------------------------------- | 198 | | SHOW_ALERT | Show Default Alert/Notification | 199 | | HIDE_ALERT | Hide Default Alert/Notification | 200 | | USER_PRESENCE | Will send a notification "USER_PRESENCE" = true or false (according to "value" to all other modules. See examples above | 201 | | NOTIFICATION | To send a notification to all modules, see the example in the [API README](API/README.md) | 202 | | DELAYED | Send any of the above nested inside a "DELAYED" call to delay the action. Default is 10s. See Delayed Actions below. | 203 | 204 | ### Format of module data response 205 | 206 | The response will be in the JSON format, here is an example: 207 | 208 | ```json 209 | { 210 | "moduleData": [ 211 | { "hidden": false, "name": "alert", "identifier": "module_0_alert" }, 212 | { 213 | "hidden": true, 214 | "name": "clock", 215 | "identifier": "module_1_clock", 216 | "position": "bottom_right" 217 | }, 218 | { 219 | "hidden": false, 220 | "name": "weather", 221 | "identifier": "module_2_weather", 222 | "position": "top_right" 223 | } 224 | ], 225 | "brightness": 40, 226 | "settingsVersion": 1 227 | } 228 | ``` 229 | 230 | ### Delayed Actions 231 | 232 | You can delay an action by sending the notification nested inside of a `"DELAYED"` notification. Below is an example of turning off the monitor in 60s. 233 | 234 | ```js 235 | this.sendSocketNotification("REMOTE_ACTION", { 236 | action: "DELAYED", 237 | did: "SOME_UNIQUE_ID", // Optional; Some string, in case you want to cancel later. 238 | timeout: 60, // Optional; Default 10s 239 | abort: false, // Optional; send true to cancel an existing timer 240 | query: { 241 | action: "MONITOROFF" 242 | } 243 | }); 244 | ``` 245 | 246 | Can also be used with the [API](https://documenter.getpostman.com/view/6167403/Rzfni66c) by adding `/delay?timeout=10s&did=something` to some routes. 247 | 248 | ### Using Custom Commands 249 | 250 | Depending on your installation, some `shell` commands used by this module are not appropriate and can be overwritten by something that will work for you. To overwrite the commands, add a `customCommand` object to your config section. The following commands are supported: 251 | 252 | ```js 253 | customCommand: { 254 | shutdownCommand: 'shell command to shutdown your pc', 255 | rebootCommand: 'shell command to reboot your pc', 256 | monitorOnCommand: 'shell command to turn on your monitor', 257 | monitorOffCommand: 'shell command to turn off your monitor', 258 | monitorStatusCommand: 'shell command to return status of monitor, must return either "HDMI" or "true" if screen is on; or "TV is Off" or "false" if it is off to be recognized' 259 | } 260 | ``` 261 | 262 | ### Custom Classes 263 | 264 | You probably wanna hide or show some modules at the same time, right? It's everything that we want this module for, of course. 265 | Well, now you can add as many classes as you like, and define whether they show themselves, hide or toggle between the two stages! 266 | 267 | ```js 268 | classes: { 269 | "Any Name You Want": { 270 | hide: ["calendar"], 271 | show: ["newsfeed"], 272 | toggle: ["clock"], 273 | }, 274 | "Another Name You Want": { 275 | hide: ["newsfeed"], 276 | show: ["calendar"], 277 | }, 278 | } 279 | ``` 280 | 281 | ### Custom Menu Items 282 | 283 | You can create your own customized menu items by providing creating a JSON file for the menu and providing a `customMenu: "custom_menu.json"` directive in your config. The file may be called whatever you want, but the name must be provided in the `config` section, and it must be stored in the Mirror's `config/` directory (same place as your `config.js` file). 284 | 285 | An example menu is provided in this module's folder, titled `custom_menu.example.json`. You can copy this to the `/config` folder and modify as you need. 286 | 287 | #### Key Components 288 | 289 | | Name | Description | 290 | | :------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 291 | | `id` | The HTML id prefix to use for the menu item. | 292 | | `type` | The item type, either `'menu'` or `'item'`. `'menu'` is used to indicate the item is a sub-menu and has an `items` array. `'item'` is used for single menu items and will send a socket "REMOTE_ACTION" notification back to the server. This requires `action:` and `content:` parameters before it can do anything. | 293 | | `text` | The text to display. You can use the translate string `'%%TRANSLATE:YOUR_KEY_HERE%%'`, but remember to also update the appropriate file in `/translations`. | 294 | | `icon` | The [FontAwesome](https://fontawesome.com/v4.7.0/icons/) icon to use (without the leading `-fa`). | 295 | | `items` | An array of sub-menu items to use with `"type":"menu"`. Should be the same format as the top level menu (i.e. the menu structure is recursive). | 296 | | `action` | The `REMOTE_ACTION` notification action name, usually `NOTIFICATION`. Required for `"type":"item"` items to be able to do anything. | 297 | 298 | ## Contributing 299 | 300 | For contributing to this repository, please see the [CONTRIBUTING.md](CONTRIBUTING.md) file. 301 | 302 | ## License 303 | 304 | This project is licensed under the MIT License - see the [LICENSE](LICENSE.md) file for details. 305 | 306 | ## Changelog 307 | 308 | All notable changes to this project will be documented in the [CHANGELOG.md](CHANGELOG.md) file. 309 | -------------------------------------------------------------------------------- /cspell.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2", 3 | "language": "en", 4 | "words": [ 5 | "Andoramb", 6 | "apikey", 7 | "articlelessdetails", 8 | "articlemoredetails", 9 | "articlenext", 10 | "articleprevious", 11 | "articlescrollup", 12 | "articletogglefull", 13 | "Ausschalten", 14 | "Bethge", 15 | "bokmål", 16 | "cambio", 17 | "chlog", 18 | "dangherve", 19 | "defaultmodules", 20 | "Energieoptionen", 21 | "extdocs", 22 | "ezeholz", 23 | "Ezequiel", 24 | "Flickr", 25 | "FULLSCREEN", 26 | "HIDEALERT", 27 | "HIDEALL", 28 | "jopyth", 29 | "juzim", 30 | "kapsolas", 31 | "Keyport", 32 | "khassel", 33 | "Kristjan", 34 | "kvpairs", 35 | "listname", 36 | "LOCKSTRING", 37 | "lockstrings", 38 | "longname", 39 | "MODULEAPI", 40 | "MONITOROFF", 41 | "MONITORON", 42 | "MONITORSTATUS", 43 | "MONITORTIMED", 44 | "MONITORTOGGLE", 45 | "mypayload", 46 | "Mysh", 47 | "navicon", 48 | "newsfeed", 49 | "newsitems", 50 | "Norsk", 51 | "plusplus", 52 | "REFRESHMM", 53 | "remotecontrol", 54 | "RESTARTMM", 55 | "revparse", 56 | "SENDALERT", 57 | "shbatm", 58 | "showalert", 59 | "SHOWALL", 60 | "somthingelse", 61 | "stylelint", 62 | "TOGGLEFULLSCREEN", 63 | "UPDATEMM", 64 | "userpresence", 65 | "vcgencmd", 66 | "Wooo", 67 | "xsmall", 68 | "YOURAPIKEY" 69 | ], 70 | "ignorePaths": [ 71 | "modules.json", 72 | "modules.json.template", 73 | "node_modules/**", 74 | "settings.json", 75 | "translations/**", 76 | "*.min.js" 77 | ], 78 | "dictionaries": ["node"] 79 | } 80 | -------------------------------------------------------------------------------- /custom_menu.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "custom", 3 | "type": "menu", 4 | "icon": "id-card-o", 5 | "text": "%%TRANSLATE:CUSTOM_MENU%%", 6 | "items": [ 7 | { 8 | "id": "custom-item-1", 9 | "type": "item", 10 | "icon": "dot-circle-o", 11 | "text": "Menu Item 1", 12 | "action": "NOTIFICATION", 13 | "content": { 14 | "notification": "NOTIFICATION_TEXT_1", 15 | "payload": "This notification requires a string payload" 16 | } 17 | }, 18 | { 19 | "id": "custom-item-2", 20 | "type": "item", 21 | "icon": "dot-circle-o", 22 | "text": "Menu Item 2", 23 | "action": "NOTIFICATION", 24 | "content": { 25 | "notification": "NOTIFICATION_TEXT_2", 26 | "payload": { 27 | "title": "Payload Object", 28 | "message": "This notification requires a payload object" 29 | } 30 | } 31 | }, 32 | { 33 | "id": "custom-item-3", 34 | "type": "item", 35 | "icon": "dot-circle-o", 36 | "text": "Menu Item 3", 37 | "action": "NOTIFICATION", 38 | "content": { 39 | "notification": "NOTIFICATION_TEXT_3", 40 | "payload": null 41 | } 42 | }, 43 | { 44 | "id": "level2", 45 | "type": "menu", 46 | "menu": "custom", 47 | "icon": "bars", 48 | "text": "Sub-Menu", 49 | "items": [ 50 | { 51 | "id": "custom-item-4", 52 | "type": "item", 53 | "icon": "dot-circle-o", 54 | "text": "Sub-Menu Item 1", 55 | "action": "NOTIFICATION", 56 | "content": { 57 | "notification": "NOTIFICATION_TEXT_4", 58 | "payload": "This notification requires a string payload" 59 | } 60 | }, 61 | { 62 | "id": "custom-item-2", 63 | "type": "item", 64 | "icon": "dot-circle-o", 65 | "text": "Sub-Menu Item 2", 66 | "action": "NOTIFICATION", 67 | "content": { 68 | "notification": "NOTIFICATION_TEXT_5", 69 | "payload": { 70 | "title": "Payload Object", 71 | "message": "This notification requires a payload object" 72 | } 73 | } 74 | } 75 | ] 76 | } 77 | ] 78 | } 79 | -------------------------------------------------------------------------------- /docs/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jopyth/MMM-Remote-Control/20dd2619ac167fe77be8c14f6ebde8afe85b2cbc/docs/favicon.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Remote Control API 6 | 11 | 12 | 33 | 34 | 35 |
36 | 37 | 38 | 39 | 40 | 127 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import {defineConfig, globalIgnores} from "eslint/config"; 2 | import css from "@eslint/css"; 3 | import globals from "globals"; 4 | import {flatConfigs as importX} from "eslint-plugin-import-x"; 5 | import js from "@eslint/js"; 6 | import json from "@eslint/json"; 7 | import markdown from "@eslint/markdown"; 8 | import stylistic from "@stylistic/eslint-plugin"; 9 | 10 | export default defineConfig([ 11 | globalIgnores(["**/*.min.js"]), 12 | {"files": ["**/*.css"], "languageOptions": {"tolerant": true}, "plugins": {css}, "language": "css/css", "extends": ["css/recommended"], "rules": {"css/use-baseline": ["error", {"available": "newly"}]}}, 13 | { 14 | "files": ["**/*.js"], 15 | "languageOptions": { 16 | "ecmaVersion": "latest", 17 | "globals": { 18 | ...globals.browser, 19 | ...globals.node, 20 | "$item": "writable", 21 | "$node": "writable" 22 | } 23 | }, 24 | "plugins": {js, stylistic}, 25 | "extends": [importX.recommended, "js/recommended", "stylistic/all"], 26 | "rules": { 27 | "@stylistic/array-element-newline": ["error", "consistent"], 28 | "@stylistic/brace-style": ["error", "1tbs", {"allowSingleLine": true}], 29 | "@stylistic/function-call-argument-newline": "off", 30 | "@stylistic/indent": ["error", 2], 31 | "@stylistic/multiline-ternary": "off", 32 | "@stylistic/newline-per-chained-call": "off", 33 | "@stylistic/object-property-newline": "off", 34 | "@stylistic/padded-blocks": "off", 35 | "@stylistic/quote-props": ["error", "consistent"], 36 | "capitalized-comments": "off", 37 | "consistent-this": "off", 38 | "line-comment-position": "off", 39 | "max-lines-per-function": ["warn", 250], 40 | "max-statements": ["warn", 105], 41 | "multiline-comment-style": "off", 42 | "no-await-in-loop": "off", 43 | "no-inline-comments": "off", 44 | "no-magic-numbers": "off", 45 | "no-var": "error", 46 | "one-var": "off", 47 | "prefer-const": "error", 48 | "prefer-template": "error", 49 | "sort-keys": "off", 50 | "strict": "off" 51 | } 52 | }, 53 | { 54 | "files": ["**/*.mjs"], 55 | "languageOptions": { 56 | "ecmaVersion": "latest", 57 | "globals": { 58 | ...globals.node 59 | }, 60 | "sourceType": "module" 61 | }, 62 | "plugins": {js, stylistic}, 63 | "extends": [importX.recommended, "js/all", "stylistic/all"], 64 | "rules": { 65 | "@stylistic/array-element-newline": ["error", "consistent"], 66 | "@stylistic/indent": ["error", 2], 67 | "@stylistic/object-property-newline": ["error", {"allowAllPropertiesOnSameLine": true}], 68 | "import-x/no-unresolved": ["error", {"ignore": ["eslint/config"]}], 69 | "no-magic-numbers": "off", 70 | "sort-keys": "off" 71 | } 72 | }, 73 | {"files": ["**/*.json"], "ignores": ["package-lock.json"], "plugins": {json}, "extends": ["json/recommended"], "language": "json/json"}, 74 | {"files": ["**/*.md"], "plugins": {markdown}, "extends": ["markdown/recommended"], "language": "markdown/gfm"} 75 | ]); 76 | -------------------------------------------------------------------------------- /img/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jopyth/MMM-Remote-Control/20dd2619ac167fe77be8c14f6ebde8afe85b2cbc/img/apple-touch-icon.png -------------------------------------------------------------------------------- /img/hide_show_module_screencast.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jopyth/MMM-Remote-Control/20dd2619ac167fe77be8c14f6ebde8afe85b2cbc/img/hide_show_module_screencast.gif -------------------------------------------------------------------------------- /img/main_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jopyth/MMM-Remote-Control/20dd2619ac167fe77be8c14f6ebde8afe85b2cbc/img/main_screenshot.png -------------------------------------------------------------------------------- /img/power_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jopyth/MMM-Remote-Control/20dd2619ac167fe77be8c14f6ebde8afe85b2cbc/img/power_screenshot.png -------------------------------------------------------------------------------- /installer.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This is an experimental installer script for MagicMirror² Remote-Control 4 | 5 | echo "" 6 | echo "Installation for the MagicMirror² Remote-Control module started!" 7 | echo "" 8 | echo "Notice: This script and the installed software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, 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." 9 | echo "" 10 | 11 | check_yes() { 12 | read -p ">>> $1 [y/N]? " -n 1 REPLY 13 | echo "" 14 | if [[ ! "$REPLY" =~ ^[Yy]$ ]]; then 15 | return 1 16 | fi 17 | return 0 18 | } 19 | 20 | check_no() { 21 | read -p ">>> $1 [Y/n]? " -n 1 REPLY 22 | echo "" 23 | if [[ $REPLY =~ ^[Nn]$ ]]; then 24 | return 1 25 | fi 26 | return 0 27 | } 28 | 29 | if check_yes "Continue?"; then 30 | echo "" 31 | else 32 | exit 0 33 | fi 34 | 35 | # assume default install location 36 | MM_HOME=$HOME/MagicMirror 37 | MODULE_NAME=MMM-Remote-Control 38 | FORK=Jopyth 39 | 40 | # check if we are correct by searching for https://github.com/MagicMirrorOrg/MagicMirror in package.json 41 | TEST_STRING="\"url\": \"https://github.com/MagicMirrorOrg/MagicMirror\"" 42 | if grep -sq "$TEST_STRING" "$MM_HOME/package.json"; then 43 | # we found it 44 | echo -n "" 45 | else 46 | # assume we are in the correct directory 47 | MM_HOME=`pwd` 48 | if grep -sq "$TEST_STRING" "$MM_HOME/package.json"; then 49 | # found it again 50 | echo -n "" 51 | else 52 | echo "Could not find MagicMirror² installation directory." 53 | echo "Please start this script again from the MagicMirror directory." 54 | exit 1 55 | fi 56 | fi 57 | 58 | if [ -d "$MM_HOME/modules/$MODULE_NAME" ] ; then 59 | # already installed 60 | echo "Directory $MM_HOME/modules/$MODULE_NAME already exists." 61 | echo "" 62 | 63 | cd "$MM_HOME/modules/$MODULE_NAME" 64 | 65 | BRANCH="$(git symbolic-ref HEAD 2>/dev/null)" || BRANCH="(unnamed branch)" # detached HEAD 66 | 67 | BRANCH=${BRANCH##refs/heads/} 68 | 69 | echo "You are currently on the $BRANCH branch." 70 | echo "" 71 | if [ "$BRANCH" == "master" ]; then 72 | if check_yes "Do you want to switch to the develop branch?"; then 73 | git checkout develop 74 | fi 75 | else 76 | if check_yes "Do you want to switch to the master branch?"; then 77 | git checkout master 78 | fi 79 | fi 80 | echo "" 81 | if check_no "Do you want to update your branch?"; then 82 | echo "" 83 | echo "Pulling changes..." 84 | git pull 85 | if [ $? -ne 0 ]; then 86 | echo "Could not pull successfully." 87 | exit 1; 88 | fi 89 | echo "" 90 | echo "Checking for new dependencies to install..." 91 | echo "" 92 | npm ci --omit=dev 93 | if [ $? -ne 0 ]; then 94 | echo "Failed to install new dependencies." 95 | exit 1; 96 | fi 97 | echo "Done." 98 | echo "" 99 | echo "Update finished!" 100 | else 101 | echo "Already installed, not upgrading." 102 | fi 103 | else 104 | echo "MagicMirror² detected in: $MM_HOME" 105 | echo "" 106 | if check_yes "Is this correct and do you want to start installation?"; then 107 | echo "" 108 | echo "You can use either the master or the develop branch." 109 | echo "The develop branch contains more features, but is also more likely to cause errors or crashes." 110 | echo "This can be changed later by executing this script again, or using the git branch command." 111 | echo "" 112 | echo "By default the master branch will be installed." 113 | echo "" 114 | if check_yes "Do you want to install the develop branch instead?"; then 115 | BRANCH=develop 116 | else 117 | BRANCH=master 118 | fi 119 | 120 | cd "$MM_HOME/modules" 121 | echo "" 122 | echo "Cloning the repository on $BRANCH branch..." 123 | echo "" 124 | git clone https://github.com/$FORK/$MODULE_NAME.git -b $BRANCH 125 | if [ $? -ne 0 ]; then 126 | echo "Failed. Do you have an internet connection?" 127 | exit 1; 128 | fi 129 | cd $MODULE_NAME 130 | 131 | echo "" 132 | echo "Installing dependencies..." 133 | echo "" 134 | npm ci --omit=dev 135 | if [ $? -ne 0 ]; then 136 | echo "Failed to install dependencies." 137 | exit 1; 138 | fi 139 | echo "Done." 140 | echo "" 141 | echo "Installation finished." 142 | else 143 | echo "Installation skipped." 144 | fi 145 | fi 146 | 147 | # Get an UUID to use as an API key 148 | NODE_BIN=$(which node) 149 | APIKEY=$($NODE_BIN -e 'console.log(require("uuid").v4().replace(/-/g, ""));'); 150 | 151 | echo "" 152 | if check_no "Do you want to view instructions on how to configure the module?"; then 153 | echo "(1) Please add the following snippet into your modules array in your config.js:" 154 | echo -e "\033[33m -------------- copy below this line --------------" 155 | echo -e " {" 156 | echo -e " module: '$MODULE_NAME'", 157 | echo -e " // uncomment the following line to show the URL of the remote control on the mirror" 158 | echo -e " // position: 'bottom_left'", 159 | echo -e " // you can hide this module afterwards from the remote control itself" 160 | echo -e " config: {" 161 | echo -e "\033[31m apiKey: '$APIKEY'\033[33m" 162 | echo -e " }" 163 | echo -e " }," 164 | echo -e " -------------- copy above this line --------------\033[0m" 165 | echo "" 166 | echo "(2) Also you will need to change the address at which the server listens:" 167 | echo " Search for the following line in your config.js:" 168 | echo " address: \"localhost\"," 169 | echo " and change it to allow any device to contact your Mirror:" 170 | echo " address: \"0.0.0.0\"," 171 | echo "" 172 | echo "(3) Make sure to add the IPs of your devices from which you want to access the Remote Control to the ipWhitelist array." 173 | echo " If for example have a phone with the IP address 192.168.0.50 search for the following line:" 174 | echo " ipWhitelist: [\"127.0.0.1\", \"::ffff:127.0.0.1\", \"::1\"]," 175 | echo " and add your IP to the list:" 176 | echo " ipWhitelist: [\"127.0.0.1\", \"::ffff:127.0.0.1\", \"::1\", \"::ffff:192.168.0.50\"]," 177 | echo " Alternatively, you can also add multiple devices with IPs such as 192.168.0.XXX:" 178 | echo " ipWhitelist: [\"127.0.0.1\", \"::ffff:127.0.0.1\", \"::1\", \"::ffff:192.168.0.1/120\", \"192.168.0.1/24\"]," 179 | echo "" 180 | 181 | if check_no "Got it?"; then 182 | echo "" 183 | else 184 | echo "" 185 | echo "Please have a look at the links below for help." 186 | echo "" 187 | fi 188 | else 189 | echo "" 190 | echo -e "\033[31mYou should also set an API key in your config section!\033[0m" 191 | echo " It's dangerous to go alone! Take this. " 192 | echo -e "\033[31m apiKey: '$APIKEY'\033[0m" 193 | echo " I made it just for you." 194 | echo "" 195 | fi 196 | echo "Have fun with the module, if you have any problems, please search for help on github or in the forum:" 197 | echo "" 198 | echo " Github : https://github.com/$FORK/$MODULE_NAME" 199 | echo " Forum : https://forum.magicmirror.builders" 200 | echo "" 201 | echo "Do not forget to restart your MagicMirror² to activate the module! Installation finished." 202 | echo "" 203 | exit 0 204 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mmm-remote-control", 3 | "version": "3.1.13", 4 | "description": "This module for the MagicMirror² allows you to shutdown and configure your mirror through a web browser.", 5 | "keywords": [ 6 | "magic mirror", 7 | "smart mirror", 8 | "module", 9 | "remote control", 10 | "control" 11 | ], 12 | "homepage": "https://github.com/jopyth/MMM-Remote-Control#readme", 13 | "bugs": { 14 | "url": "git+https://github.com/jopyth/MMM-Remote-Control/issues" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/jopyth/MMM-Remote-Control" 19 | }, 20 | "license": "MIT", 21 | "author": "Joseph Bethge", 22 | "contributors": [ 23 | "https://github.com/jopyth/MMM-Remote-Control/graphs/contributors" 24 | ], 25 | "scripts": { 26 | "download_modules": "node ./scripts/download_modules_manually.mjs", 27 | "lint": "eslint && prettier . --check", 28 | "lint:fix": "eslint --fix && prettier . --write", 29 | "postinstall": "cp modules.json.template modules.json", 30 | "test": "node --run lint && node --run test:spelling", 31 | "test:spelling": "cspell ." 32 | }, 33 | "dependencies": { 34 | "showdown": "^2.1.0", 35 | "simple-git": "^3.27.0", 36 | "swagger-ui": "^5.22.0", 37 | "uuid": "^11.1.0" 38 | }, 39 | "devDependencies": { 40 | "@eslint/css": "^0.8.1", 41 | "@eslint/js": "^9.27.0", 42 | "@eslint/json": "^0.12.0", 43 | "@eslint/markdown": "^6.4.0", 44 | "@stylistic/eslint-plugin": "^4.4.0", 45 | "cspell": "^9.0.2", 46 | "eslint": "^9.27.0", 47 | "eslint-plugin-import-x": "^4.12.2", 48 | "globals": "^16.1.0", 49 | "prettier": "^3.5.3" 50 | }, 51 | "engines": { 52 | "node": ">=18" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /prettier.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | "trailingComma": "none" 3 | }; 4 | 5 | export default config; 6 | -------------------------------------------------------------------------------- /remote.css: -------------------------------------------------------------------------------- 1 | html { 2 | cursor: auto; 3 | } 4 | 5 | body { 6 | margin: 10px; 7 | height: calc(100% - 20px); 8 | width: calc(100% - 20px); 9 | } 10 | 11 | a { 12 | color: white; 13 | } 14 | 15 | .menu-element { 16 | flex: 0 0 auto; 17 | } 18 | 19 | .symbol-text-padding { 20 | padding-left: 0.2em; 21 | padding-right: 0.5em; 22 | } 23 | 24 | .fill { 25 | height: 100%; 26 | display: flex; 27 | flex-direction: column; 28 | } 29 | 30 | .above-fold { 31 | flex: 0 0 auto; 32 | } 33 | 34 | .below-fold { 35 | border-top: 1px solid #666; 36 | margin-top: 7px; 37 | padding-top: 7px; 38 | flex: 1 1 auto; 39 | overflow-y: auto; 40 | display: flex; 41 | flex-direction: column; 42 | } 43 | 44 | .hide-border { 45 | border-top: 0; 46 | margin-top: 0; 47 | padding-top: 0; 48 | } 49 | 50 | .menu-container { 51 | display: flex; 52 | flex-direction: column; 53 | min-height: min-content; 54 | } 55 | 56 | .menu { 57 | display: flex; 58 | flex-flow: row wrap; 59 | place-content: flex-start space-between; 60 | align-items: stretch; 61 | overflow-y: hidden; 62 | } 63 | 64 | .sub-menu { 65 | display: flex; 66 | flex-flow: row wrap; 67 | align-items: center; 68 | justify-content: space-between; 69 | } 70 | 71 | .result-list { 72 | width: 100%; 73 | } 74 | 75 | .results { 76 | display: flex; 77 | flex-direction: column; 78 | } 79 | 80 | .results:last-child { 81 | margin-bottom: 2em; 82 | } 83 | 84 | .menu-element-container { 85 | display: flex; 86 | flex-direction: column; 87 | flex: 1 0 auto; 88 | } 89 | 90 | .one-line { 91 | flex: 1 0 auto; 92 | display: flex; 93 | flex-flow: row nowrap; 94 | align-items: center; 95 | } 96 | 97 | .flex-like-button { 98 | flex: 0 1 auto; 99 | display: flex; 100 | flex-direction: row; 101 | align-items: center; 102 | justify-content: flex-start; 103 | } 104 | 105 | .button { 106 | cursor: pointer; 107 | border: 1px solid #666; 108 | border-radius: 0.2em; 109 | padding: 0.1em; 110 | margin: 0.1em; 111 | flex: 0 1 auto; 112 | display: flex; 113 | flex-direction: row; 114 | align-items: center; 115 | justify-content: flex-start; 116 | } 117 | 118 | .button.bright { 119 | border: 1px solid #999; 120 | } 121 | 122 | .button > * { 123 | flex: 0 0 auto; 124 | } 125 | 126 | .text { 127 | flex: 1 1 auto; 128 | margin-left: 0.2em; 129 | white-space: nowrap; 130 | overflow: hidden; 131 | text-overflow: ellipsis; 132 | min-width: 0; 133 | } 134 | 135 | .fa-angle-right { 136 | margin-right: 0.5em; 137 | text-align: right; 138 | } 139 | 140 | .status-indicator { 141 | display: none; 142 | margin: 0.1em; 143 | padding: 0.1em; 144 | } 145 | 146 | .overlay { 147 | position: absolute; 148 | width: 100%; 149 | height: 100%; 150 | background: black; 151 | opacity: 0.5; 152 | } 153 | 154 | .result { 155 | position: absolute; 156 | width: 70%; 157 | height: 50%; 158 | left: 15%; 159 | top: 25%; 160 | background: black; 161 | border: 2px solid #666; 162 | display: flex; 163 | border-radius: 0.2em; 164 | flex-direction: column; 165 | justify-content: center; 166 | font-size: 25px; 167 | line-height: 1.5em; 168 | } 169 | 170 | .module-line { 171 | margin-bottom: 5px; 172 | } 173 | 174 | #config-modules-results .module-line { 175 | display: flex; 176 | justify-content: space-between; 177 | flex-wrap: wrap; 178 | align-items: center; 179 | } 180 | 181 | #config-modules-results .module-line > span { 182 | overflow-x: auto; 183 | white-space: nowrap; 184 | } 185 | 186 | #update-module-results .module-line { 187 | display: flex; 188 | justify-content: space-between; 189 | flex-wrap: wrap; 190 | align-items: center; 191 | } 192 | 193 | #update-module-results .module-line > span { 194 | overflow-x: auto; 195 | white-space: nowrap; 196 | } 197 | 198 | .results > div:nth-child(even) { 199 | background-color: #222; 200 | } 201 | 202 | #add-module-results > div { 203 | background-color: #000; 204 | } 205 | 206 | #visible-modules-results > div { 207 | background-color: #000; 208 | } 209 | 210 | input, 211 | select, 212 | textarea { 213 | flex: 3 0 auto; 214 | background: #000; 215 | color: #999; 216 | font-size: inherit; 217 | border: 2px solid #999; 218 | border-radius: 0.2em; 219 | padding: 0.2em; 220 | } 221 | 222 | select { 223 | flex: 1 0 auto; 224 | } 225 | 226 | input:focus, 227 | select:focus, 228 | textarea:focus { 229 | color: #fff; 230 | border: 2px solid #fff; 231 | background-color: #444; 232 | } 233 | 234 | input:focus.input-error { 235 | border: 2px solid #f44; 236 | } 237 | 238 | #alert input { 239 | box-sizing: border-box; 240 | width: 100%; 241 | } 242 | 243 | #alert textarea { 244 | box-sizing: border-box; 245 | width: 100%; 246 | } 247 | 248 | .config-label { 249 | display: flex; 250 | flex-wrap: wrap; 251 | place-content: space-around space-between; 252 | align-items: center; 253 | margin-bottom: 0.8em; 254 | } 255 | 256 | .label-name { 257 | flex: 1 1 auto; 258 | white-space: nowrap; 259 | overflow-x: auto; 260 | } 261 | 262 | .type-edit { 263 | flex: 0 0 auto; 264 | white-space: nowrap; 265 | color: #666; 266 | } 267 | 268 | .highlight { 269 | color: #fff; 270 | background-color: #444; 271 | } 272 | 273 | .highlight .type-edit { 274 | color: #999; 275 | } 276 | 277 | .highlight .button { 278 | border: 1px solid #999; 279 | } 280 | 281 | .bottom-spacing { 282 | margin-bottom: 0.8em; 283 | } 284 | 285 | .search-container { 286 | position: relative; 287 | } 288 | 289 | .delete-button { 290 | cursor: pointer; 291 | position: absolute; 292 | right: 0.5em; 293 | top: 0.5em; 294 | } 295 | 296 | .popup .delete-button { 297 | right: 0.25em; 298 | top: 0.25em; 299 | } 300 | 301 | .result .delete-button { 302 | right: 0.25em; 303 | top: 0.25em; 304 | } 305 | 306 | /* general toggle rules */ 307 | .toggled-off > * > .fa-toggle-on { 308 | display: none; 309 | } 310 | 311 | .toggled-off > .fa-toggle-on { 312 | display: none; 313 | } 314 | 315 | .toggled-on > * > .fa-toggle-off { 316 | display: none; 317 | } 318 | 319 | .toggled-on > .fa-toggle-off { 320 | display: none; 321 | } 322 | 323 | /* edit button specific stuff */ 324 | .module-line > * > .fa-lock { 325 | display: none; 326 | } 327 | 328 | .external-locked > * > .fa-lock.inner-small-label { 329 | display: inline-block; 330 | } 331 | 332 | .slider-container { 333 | vertical-align: middle; 334 | width: calc(100% - 4em); 335 | } 336 | 337 | .stack { 338 | position: relative; 339 | display: inline-block; 340 | height: 1em; 341 | line-height: 1em; 342 | font-size: 1em; 343 | vertical-align: middle; 344 | } 345 | 346 | .inner-label { 347 | font-size: 0.9em; 348 | } 349 | 350 | .inner-monitor-label { 351 | font-size: 0.5em; 352 | top: -0.15em; 353 | left: -0.05em; 354 | } 355 | 356 | .inner-small-label { 357 | font-size: 0.4em; 358 | left: -0.5em; 359 | } 360 | 361 | .outer-label { 362 | font-size: 1em; 363 | } 364 | 365 | .slider { 366 | border: 0; 367 | } 368 | 369 | .popup { 370 | width: calc(100% - 4px); 371 | height: calc(100% - 4px); 372 | background-color: black; 373 | position: absolute; 374 | border: 2px solid #999; 375 | top: 0; 376 | border-radius: 5px; 377 | display: none; 378 | overflow-y: auto; 379 | } 380 | 381 | .success-popup { 382 | width: 30%; 383 | min-width: 100px; 384 | background-color: black; 385 | position: absolute; 386 | bottom: 0; 387 | display: none; 388 | padding: 6px; 389 | border: 2px solid #666; 390 | border-radius: 0.2em; 391 | font-size: 20px; 392 | line-height: 1.5em; 393 | } 394 | 395 | .popup-contents { 396 | padding: 5px 10px; 397 | display: flex; 398 | flex-direction: column; 399 | height: calc(100% - 10px); 400 | width: calc(100% - 20px); 401 | } 402 | 403 | .title { 404 | flex: 0 0 auto; 405 | margin: 0.2em 1.5em 0.2em 0.5em; 406 | text-align: center; 407 | } 408 | 409 | .subtitle { 410 | flex: 0 1 auto; 411 | padding-right: 5px; 412 | text-align: right; 413 | font-style: italic; 414 | } 415 | 416 | .indent { 417 | border: 1px dashed #666; 418 | margin: 0 0 0.8em; 419 | padding: 5px 8px; 420 | } 421 | 422 | .disabled { 423 | border: 2px solid #222; 424 | } 425 | 426 | .align-right { 427 | text-align: right; 428 | } 429 | 430 | .flex-fill { 431 | flex: 1 1 auto; 432 | overflow: auto; 433 | } 434 | 435 | .fixed-size { 436 | flex: 0 0 auto; 437 | } 438 | 439 | .add-input-wrapper { 440 | display: flex; 441 | align-items: stretch; 442 | align-content: stretch; 443 | } 444 | 445 | .add-input-wrapper .fa-plus { 446 | justify-content: space-around; 447 | } 448 | 449 | #load-error { 450 | font-size: medium; 451 | line-height: normal; 452 | } 453 | 454 | .inline-menu-element { 455 | display: inline-block; 456 | } 457 | 458 | .hidden { 459 | display: none; 460 | } 461 | 462 | /* checkbox shenanigans */ 463 | .visual-checkbox { 464 | text-align: center; 465 | } 466 | 467 | input[type="checkbox"] { 468 | opacity: 0; 469 | } 470 | 471 | input[type="checkbox"] ~ .fa-square-o { 472 | display: block; 473 | } 474 | 475 | input[type="checkbox"] ~ .fa-check-square-o { 476 | display: none; 477 | } 478 | 479 | input[type="checkbox"]:checked ~ .fa-square-o { 480 | display: none; 481 | } 482 | 483 | input[type="checkbox"]:checked ~ .fa-check-square-o { 484 | display: block; 485 | } 486 | 487 | /* slider appearance created with https://danielstern.ca/range.css/?ref=css-tricks#/ */ 488 | input[type="range"] { 489 | appearance: none; 490 | background: #000; 491 | width: calc(100% - 0.5em); 492 | } 493 | 494 | input[type="range"]:focus { 495 | outline: none; 496 | } 497 | 498 | input[type="range"]::-webkit-slider-runnable-track { 499 | width: 100%; 500 | height: 10px; 501 | cursor: pointer; 502 | box-shadow: 503 | 0 0 0 rgb(0 0 0 / 0%), 504 | 0 0 0 rgb(13 13 13 / 0%); 505 | background: #999; 506 | border-radius: 10px; 507 | border: 2px solid rgb(0 0 0 / 0%); 508 | } 509 | 510 | input[type="range"]::-webkit-slider-thumb { 511 | box-shadow: 512 | 0 0 0 rgb(0 0 0 / 0%), 513 | 0 0 0 rgb(13 13 13 / 0%); 514 | border: 2px solid rgb(0 0 0 / 0%); 515 | height: 30px; 516 | width: 30px; 517 | border-radius: 30px; 518 | background: #fff; 519 | cursor: pointer; 520 | appearance: none; 521 | margin-top: -12px; 522 | } 523 | 524 | input[type="range"]:focus::-webkit-slider-runnable-track { 525 | background: #9c9c9c; 526 | } 527 | 528 | input[type="range"]::-moz-range-track { 529 | width: 100%; 530 | height: 10px; 531 | cursor: pointer; 532 | box-shadow: 533 | 0 0 0 rgb(0 0 0 / 0%), 534 | 0 0 0 rgb(13 13 13 / 0%); 535 | background: #999; 536 | border-radius: 10px; 537 | border: 2px solid rgb(0 0 0 / 0%); 538 | } 539 | 540 | input[type="range"]::-moz-range-thumb { 541 | box-shadow: 542 | 0 0 0 rgb(0 0 0 / 0%), 543 | 0 0 0 rgb(13 13 13 / 0%); 544 | border: 2px solid rgb(0 0 0 / 0%); 545 | height: 30px; 546 | width: 30px; 547 | border-radius: 30px; 548 | background: #fff; 549 | cursor: pointer; 550 | } 551 | 552 | input[type="range"]::-ms-track { 553 | width: 100%; 554 | height: 10px; 555 | cursor: pointer; 556 | background: transparent; 557 | border-color: transparent; 558 | color: transparent; 559 | } 560 | 561 | input[type="range"]::-ms-fill-lower { 562 | background: #969696; 563 | border: 2px solid rgb(0 0 0 / 0%); 564 | border-radius: 20px; 565 | box-shadow: 566 | 0 0 0 rgb(0 0 0 / 0%), 567 | 0 0 0 rgb(13 13 13 / 0%); 568 | } 569 | 570 | input[type="range"]::-ms-fill-upper { 571 | background: #999; 572 | border: 2px solid rgb(0 0 0 / 0%); 573 | border-radius: 20px; 574 | box-shadow: 575 | 0 0 0 rgb(0 0 0 / 0%), 576 | 0 0 0 rgb(13 13 13 / 0%); 577 | } 578 | 579 | input[type="range"]::-ms-thumb { 580 | box-shadow: 581 | 0 0 0 rgb(0 0 0 / 0%), 582 | 0 0 0 rgb(13 13 13 / 0%); 583 | border: 2px solid rgb(0 0 0 / 0%); 584 | width: 30px; 585 | border-radius: 30px; 586 | background: #fff; 587 | cursor: pointer; 588 | height: 10px; 589 | } 590 | 591 | input[type="range"]:focus::-ms-fill-lower { 592 | background: #999; 593 | } 594 | 595 | input[type="range"]:focus::-ms-fill-upper { 596 | background: #9c9c9c; 597 | } 598 | 599 | code, 600 | samp, 601 | kbd { 602 | font-family: "Courier New", Courier, monospace, sans-serif; 603 | text-align: left; 604 | } 605 | 606 | pre code { 607 | line-height: 0.6em; 608 | font-size: 0.5rem; 609 | } 610 | 611 | pre { 612 | padding: 0.3em 0.5em 0.2em 0.7em; 613 | border-left: 2px solid #ccc; 614 | margin: 1em 0.3em 0.2em 0.6em; 615 | overflow: auto; 616 | width: 90%; 617 | max-height: 35vh; 618 | } 619 | 620 | #changelog { 621 | max-height: 30vh; 622 | font-size: small; 623 | overflow: scroll; 624 | } 625 | 626 | #temp-slider, 627 | #temp-reset { 628 | display: block; 629 | } 630 | -------------------------------------------------------------------------------- /remote.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %%TRANSLATE:TITLE%% 5 | 6 | 7 | 8 | 9 | 10 | 11 | 16 | 20 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 |
32 |
33 |
%%TRANSLATE:TITLE%%
34 |
35 | 197 | 475 |
476 | 480 | %%TRANSLATE:LOAD_ERROR%% 481 | %%TRANSLATE:ISSUE_LINK%% 484 |
485 |
486 | 487 | 495 | 503 |
504 | 505 | %%TRANSLATE:DONE%% 506 |
507 |
508 |
509 | 510 | 511 | 512 | 516 | 520 | 521 | 522 | -------------------------------------------------------------------------------- /scripts/download_modules.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * MagicMirror² Package List Downloader 4 | * Module: Remote Control 5 | * 6 | * Usage: 7 | * const downloadModules = require('./download_modules'); 8 | * downloadModules({ callback: function(result) { console.log(result); }}); 9 | * downloadModules accepts a CONFIG object which will overwrite the values 10 | * in the defaults section below. 11 | * 12 | * By shbatm 13 | * MIT Licensed. 14 | */ 15 | 16 | const path = require("node:path"); 17 | const fs = require("node:fs"); 18 | 19 | const downloadModules = { 20 | defaults: { 21 | modulesFile: path.resolve(__dirname, "../modules.json"), // Path to modules file 22 | sourceUrl: "https://raw.githubusercontent.com/wiki/MagicMirrorOrg/MagicMirror/3rd-Party-Modules.md", // Source url 23 | refreshRate: 24 * 3600, // Max Refresh of One Day 24 | force: false, // Force the update 25 | callback (result) { console.log(result); } // Callback to run on success or failure 26 | }, 27 | 28 | init (config) { 29 | if (!config) { config = {}; } 30 | this.config = {...this.defaults, ...config}; 31 | 32 | return this; 33 | }, 34 | 35 | parseList (content) { 36 | const re = /\|\s?\[(.*?)\]\((.*?)\)\s?\|(.*?)\|(.*)\|?/gu; 37 | const modules = []; 38 | 39 | content.match(re).forEach((line) => { 40 | line.replace(re, (match, name, url, author, desc) => { 41 | const modDetail = { 42 | longname: name.trim(), 43 | id: url.replace(".git", "").replace(/.*\/(.*?\/.*?)$/u, "$1").trim(), 44 | url: url.replace(".git", "").trim(), 45 | author: author.replace(/\[(.*)\]\(.*\)/u, "$1").trim(), 46 | desc: desc.replace(/\|/u, "").trim() 47 | }; 48 | modules.push(modDetail); 49 | }); 50 | }); 51 | 52 | return modules; 53 | }, 54 | 55 | async getPackages () { 56 | try { 57 | const response = await fetch(this.config.sourceUrl); 58 | if (response.status === 200) { 59 | const body = await response.text(); 60 | const modules = this.parseList(body); 61 | const json = `${JSON.stringify(modules, null, 2)}\n`; 62 | const jsonPath = this.config.modulesFile; 63 | fs.writeFile(jsonPath, json, "utf8", (err) => { 64 | if (err) { 65 | console.error(`MODULE LIST ERROR: modules.json updating fail:${err.message}`); 66 | this.config.callback("ERROR_UPDATING"); 67 | } else { 68 | this.config.callback("UPDATED"); 69 | } 70 | }); 71 | } else if (response.status === 401) { 72 | console.error("MODULE LIST ERROR: Could not load module data from wiki. 401 Error"); 73 | this.config.callback("ERROR_401"); 74 | } else { 75 | console.error("MODULE LIST ERROR: Could not load data.", response.statusText); 76 | this.config.callback("ERROR_LOADING_DATA"); 77 | } 78 | } catch (error) { 79 | console.error("MODULE LIST ERROR: Could not load data.", error); 80 | this.config.callback("ERROR_LOADING_DATA"); 81 | } 82 | }, 83 | 84 | async checkLastModified () { 85 | try { 86 | const stats = await fs.promises.stat(this.config.modulesFile); 87 | const mtime = Math.round(stats.mtime.getTime() / 1000); 88 | const updatedAfter = Date.now() - this.config.refreshRate * 1000; 89 | const needsUpdate = mtime <= updatedAfter; 90 | if (needsUpdate || this.config.force) { 91 | this.getPackages(); 92 | } else { 93 | this.config.callback("NO_UPDATE_REQUIRED"); 94 | } 95 | } catch (err) { 96 | console.error("MODULE LIST ERROR: Could not check last modified time.", err); 97 | this.config.callback("ERROR_CHECKING_LAST_MODIFIED"); 98 | } 99 | 100 | } 101 | }; 102 | 103 | if (typeof module !== "undefined") { 104 | module.exports = function (config) { 105 | downloadModules.init(config); 106 | downloadModules.checkLastModified(); 107 | 108 | }; 109 | } 110 | -------------------------------------------------------------------------------- /scripts/download_modules_manually.mjs: -------------------------------------------------------------------------------- 1 | /* This script is used to manually test the download_modules.js script. */ 2 | 3 | import downloadModules from "./download_modules.js"; 4 | 5 | downloadModules(); 6 | -------------------------------------------------------------------------------- /translations/ca.json: -------------------------------------------------------------------------------- 1 | { 2 | "TITLE": "Menú MagicMirror²", 3 | 4 | "EDIT_MENU_NAME": "Editar Vista", 5 | "SHUTDOWN_MENU_NAME": "Encendre/Apagar", 6 | "CONFIGURE_MENU_NAME": "Editar config.js", 7 | "VIEW_MIRROR": "MagicMirror²", 8 | 9 | "BACK": "Endarrera", 10 | 11 | "BRIGHTNESS": "Brillantor", 12 | 13 | "REFRESHMM": "Recarregar Navegador", 14 | "MONITOROFF": "Apagar monitor", 15 | "MONITORON": "Encendre monitor", 16 | "MONITORTIMED": "Encendre monitor breument", 17 | "RESTART": "Reiniciar RPi", 18 | "RESTARTMM": "Recarregar MagicMirror²", 19 | "SHUTDOWN": "Apagar RPi", 20 | "FULLSCREEN": "Pantalla sencera", 21 | "MINIMIZE": "Minimitzar Navegador", 22 | "DEVTOOLS": "Obrir Eines per desenvolupadors", 23 | 24 | "SHOWALL": "Mostrar Tots", 25 | "HIDEALL": "Amagar Tots", 26 | 27 | "ADD_MODULE": "Afegir mòdul", 28 | "SEARCH": "Buscar …", 29 | "INSTALLED": "Instal.lat", 30 | "DOWNLOAD": "Descarrega", 31 | "DOWNLOADING": "Descarregant …", 32 | "CODE_LINK": "Veure còdig", 33 | "BY": "per", 34 | "ADD_THIS": "Afegir aquest mòdul", 35 | "HELP": "Veure Readme", 36 | "MENU": "Menú", 37 | "RESET": "Reset", 38 | "NO_HEADER": "(sense capcelera)", 39 | "NO_POSITION": "(invisible)", 40 | "ADD_ENTRY": "Afegir entrada", 41 | "NEW_ENTRY_NAME": "(nom de la nova entrada)", 42 | "DELETE_ENTRY": "Esborrar", 43 | "UNSAVED_CHANGES": "Canvis sense guardar", 44 | "OK": "Ok", 45 | "DISCARD": "Descartar canvis", 46 | "CANCEL": "Cancel.lar", 47 | 48 | "NO_MODULES_LOADED": "No hi han móduls carregats.", 49 | "SAVE": "Guardar", 50 | 51 | "EXPERIMENTAL": "Aquesta es una funciò experimental, pot corrompre el teu arxiu config. Fes còpia de seguretat primer, per si de cas!
Vols continuar?", 52 | "PANIC": "No m'importa.", 53 | "NO_RISK_NO_FUN": "Qui no arrisca no pisca!", 54 | 55 | "CONFIRM_SHUTDOWN": "El sistema s'apagarà.", 56 | "CONFIRM_RESTART": "El sistema es recarregarà.", 57 | 58 | "LOAD_ERROR": "Si ves este mensaje, un error ha ocurrido cargando el archivo javascript. Si us plau, revisa aquest enllaç per veure si es un problema amb el teu navegador:", 59 | "ISSUE_LINK": "Pàgina de problemes de Github", 60 | 61 | "DONE": "Fet.", 62 | "ERROR": "Error!", 63 | "LOADING": "Carregant …", 64 | 65 | "LOCKSTRING_WARNING": "Aquest mòdul ha estat ocultat per LIST_OF_MODULES, no pot ser mostrat.", 66 | "FORCE_SHOW": "Fes-ho igualment.", 67 | 68 | "UPDATE_MENU_NAME": "Actualitzacions", 69 | "UPDATEMM": "Actualitza MagicMirror²", 70 | "UPDATE_AVAILABLE": "Actualitzaciò disponible", 71 | 72 | "ALERT_MENU_NAME": "Alertes / Notificacions", 73 | "SENDALERT": "Envia alerta", 74 | "HIDEALERT": "Oculta alerta", 75 | "FORM_TYPE": "Tipus:", 76 | "FORM_ALERT": "Alerta", 77 | "FORM_NOTIFICATION": "Notificaciò", 78 | "FORM_TITLE": "Títol:", 79 | "FORM_TITLE_PLACEHOLDER": "Introdueix títol...", 80 | "FORM_MESSAGE": "Missatge:", 81 | "FORM_MESSAGE_PLACEHOLDER": "Introdueix missatge...", 82 | "FORM_SECONDS": "Segons:", 83 | 84 | "RESPONSE_ERROR": "No ha funcionat. Comprova els MM logs per més detalls", 85 | "MODULE_CONTROLS": "Controls de mòduls", 86 | "CUSTOM_MENU": "El meu Menú Personalitzat" 87 | } 88 | -------------------------------------------------------------------------------- /translations/da.json: -------------------------------------------------------------------------------- 1 | { 2 | "TITLE": "MagicMirror² Menu", 3 | 4 | "EDIT_MENU_NAME": "Udseende", 5 | "SHUTDOWN_MENU_NAME": "Strøm", 6 | "CONFIGURE_MENU_NAME": "Rediger config.js", 7 | "VIEW_MIRROR": "MagicMirror²", 8 | 9 | "BACK": "Tilbage", 10 | 11 | "BRIGHTNESS": "Lysstyrke", 12 | 13 | "REFRESHMM": "Genindlæs Browser", 14 | "MONITOROFF": "Sluk skærm", 15 | "MONITORON": "Tænd skærm", 16 | "MONITORTIMED": "Tænd skærm kortvarig", 17 | "RESTART": "Genstart", 18 | "RESTARTMM": "Genstart MagicMirror²", 19 | "SHUTDOWN": "Luk ned", 20 | "FULLSCREEN": "Toggle fuldskærm", 21 | "MINIMIZE": "Minimer Browser", 22 | "DEVTOOLS": "Åben DevTools", 23 | 24 | "SHOWALL": "Vis alle", 25 | "HIDEALL": "Skjul alle", 26 | 27 | "ADD_MODULE": "Tilføj modul", 28 | "SEARCH": "Søg …", 29 | "INSTALLED": "Installeret", 30 | "DOWNLOAD": "Download", 31 | "DOWNLOADING": "Downloading …", 32 | "CODE_LINK": "Vis kode", 33 | "BY": "af", 34 | "ADD_THIS": "Tilføj dette modul", 35 | "HELP": "Vis readme", 36 | "MENU": "Menu", 37 | "RESET": "Nulstil", 38 | "NO_HEADER": "(ingen overskrift)", 39 | "NO_POSITION": "(usynlig)", 40 | "ADD_ENTRY": "Tilføj post", 41 | "NEW_ENTRY_NAME": "(skriv navn på ny post)", 42 | "DELETE_ENTRY": "Slet", 43 | "UNSAVED_CHANGES": "Ikke-gemte ændringer", 44 | "OK": "Ok", 45 | "DISCARD": "Forkast ændringer", 46 | "CANCEL": "Annullere", 47 | 48 | "NO_MODULES_LOADED": "Ingen moduler indlæst.", 49 | "SAVE": "Gem", 50 | 51 | "EXPERIMENTAL": "Dette er en eksperimentel funktion, det kan ødelægge din konfigurationsfil. Sikkerhedskopier først din konfiguration, hvis det er tilfældet!
Vil du fortsætte?", 52 | "PANIC": "Glem det.", 53 | "NO_RISK_NO_FUN": "Selvfølgelig!", 54 | 55 | "CONFIRM_SHUTDOWN": "Systemet lukker ned.", 56 | "CONFIRM_RESTART": "Systemet genstarter", 57 | 58 | "LOAD_ERROR": "Hvis du ser denne meddelelse, opstod der en fejl under indlæsning af javascript-filen. Gå til følgende link og se, om dette er et kendt problem med din browser:", 59 | "ISSUE_LINK": "Github fejlrapportering", 60 | 61 | "DONE": "Færdig.", 62 | "ERROR": "Fejl!", 63 | "LOADING": "Indlæser …", 64 | 65 | "LOCKSTRING_WARNING": "Dette modul blev skjult af LIST_OF_MODULES, det kan ikke vises.", 66 | "FORCE_SHOW": "Gør det alligevel.", 67 | 68 | "UPDATE_MENU_NAME": "Opdateringer", 69 | "UPDATEMM": "Opdater MagicMirror²", 70 | "UPDATE_AVAILABLE": "Opdatering tilgængelig", 71 | 72 | "ALERT_MENU_NAME": "Alarm", 73 | "SENDALERT": "Send", 74 | "HIDEALERT": "Skjul", 75 | "FORM_TYPE": "Type:", 76 | "FORM_ALERT": "Alarm", 77 | "FORM_NOTIFICATION": "Notifikation", 78 | "FORM_TITLE": "Titel:", 79 | "FORM_TITLE_PLACEHOLDER": "Skriv titel...", 80 | "FORM_MESSAGE": "Besked:", 81 | "FORM_MESSAGE_PLACEHOLDER": "Skriv besked...", 82 | "FORM_SECONDS": "Sekunder:", 83 | 84 | "RESPONSE_ERROR": "Nå, det virkede ikke. Kontroller MM-logfilerne for at få flere oplysninger", 85 | "MODULE_CONTROLS": "Modul kontrol", 86 | "CUSTOM_MENU": "Min tilpassede menu" 87 | } 88 | -------------------------------------------------------------------------------- /translations/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "TITLE": "MagicMirror² Menü", 3 | 4 | "EDIT_MENU_NAME": "Ansicht editieren", 5 | "SHUTDOWN_MENU_NAME": "Energieoptionen", 6 | "CONFIGURE_MENU_NAME": "Einstellungen", 7 | "VIEW_MIRROR": "MagicMirror²", 8 | 9 | "BACK": "Zurück", 10 | 11 | "BRIGHTNESS": "Helligkeit", 12 | 13 | "REFRESHMM": "Browser neu laden", 14 | "MONITOROFF": "Bildschirm ausschalten", 15 | "MONITORON": "Bildschirm einschalten", 16 | "MONITORTIMED": "Bildschirm kurz einschalten", 17 | "RESTART": "Neu starten", 18 | "RESTARTMM": "Starte MagicMirror² neu", 19 | "SHUTDOWN": "Ausschalten", 20 | "FULLSCREEN": "Vollbildmodus", 21 | "MINIMIZE": "Browser minimieren", 22 | "DEVTOOLS": "Entwicklerwerkzeuge", 23 | 24 | "SHOWALL": "Alle", 25 | "HIDEALL": "Alle", 26 | 27 | "ADD_MODULE": "Neues Modul", 28 | "SEARCH": "Suchen …", 29 | "INSTALLED": "Installiert", 30 | "DOWNLOAD": "Download", 31 | "DOWNLOADING": "Downloading …", 32 | "CODE_LINK": "Code ansehen", 33 | "BY": "von", 34 | "ADD_THIS": "Hinzufügen", 35 | "HELP": "Hilfe", 36 | "MENU": "Menü", 37 | "RESET": "Reset", 38 | "NO_HEADER": "(kein Header)", 39 | "NO_POSITION": "(unsichtbar)", 40 | "ADD_ENTRY": "Neuer Eintrag", 41 | "NEW_ENTRY_NAME": "(Name eintragen)", 42 | "DELETE_ENTRY": "Löschen", 43 | "UNSAVED_CHANGES": "Nicht gespeicherte Änderungen", 44 | "OK": "Ok", 45 | "DISCARD": "Verwerfen", 46 | "CANCEL": "Abbrechen", 47 | 48 | "NO_MODULES_LOADED": "Keine Module geladen.", 49 | "SAVE": "Speichern", 50 | 51 | "EXPERIMENTAL": "Dies ist ein experimentelles Feature, es könnte Probleme verursachen. Mache ein Backup von deiner config!
Weiter machen?", 52 | "PANIC": "Lieber nicht.", 53 | "NO_RISK_NO_FUN": "Ich liebe das Risiko!", 54 | 55 | "CONFIRM_SHUTDOWN": "Das System wird ausgeschaltet.", 56 | "CONFIRM_RESTART": "Das System wird neu starten.", 57 | 58 | "LOAD_ERROR": "Wenn du diese Nachricht lesen kannst, konnte die javascript Datei nicht korrekt geladen werden. Bitte folge dem Link, und sehe nach, ob dies ein bekanntes Problem mit deinem Browser ist:", 59 | "ISSUE_LINK": "Github-Issue-Seite", 60 | 61 | "DONE": "Fertig.", 62 | "ERROR": "Fehler!", 63 | "LOADING": "Lade …", 64 | 65 | "LOCKSTRING_WARNING": "Dieses Modul wurde durch LIST_OF_MODULES versteckt, es kann nicht angezeigt werden.", 66 | "FORCE_SHOW": "Mach es trootzdem!", 67 | 68 | "UPDATE_MENU_NAME": "Aktualisierungen", 69 | "UPDATEMM": "Aktualisiere MagicMirror²", 70 | "UPDATE_AVAILABLE": "Aktualisierungen verfügbar", 71 | 72 | "ALERT_MENU_NAME": "Alert", 73 | "SENDALERT": "Senden", 74 | "HIDEALERT": "Verstecken", 75 | "FORM_TYPE": "Typ:", 76 | "FORM_ALERT": "Groß", 77 | "FORM_NOTIFICATION": "Klein", 78 | "FORM_TITLE": "Titel:", 79 | "FORM_TITLE_PLACEHOLDER": "Title eingeben...", 80 | "FORM_MESSAGE": "Nachricht:", 81 | "FORM_MESSAGE_PLACEHOLDER": "Nachricht eingeben...", 82 | "FORM_SECONDS": "Sekunden:", 83 | 84 | "RESPONSE_ERROR": "Dies hat nicht funktioniert. Schau dir die MM-Logs für weitere Details an.", 85 | "MODULE_CONTROLS": "Modul-Steuerung", 86 | "CUSTOM_MENU": "Mein eigenes Menü" 87 | } 88 | -------------------------------------------------------------------------------- /translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "TITLE": "MagicMirror² Menu", 3 | 4 | "EDIT_MENU_NAME": "Edit view", 5 | "SHUTDOWN_MENU_NAME": "Power", 6 | "CONFIGURE_MENU_NAME": "Edit config.js", 7 | "VIEW_MIRROR": "MagicMirror²", 8 | 9 | "BACK": "Back", 10 | 11 | "BRIGHTNESS": "Brightness", 12 | 13 | "REFRESHMM": "Refresh Browser", 14 | "MONITOROFF": "Turn monitor OFF", 15 | "MONITORON": "Turn monitor ON", 16 | "MONITORTIMED": "Turn monitor ON briefly", 17 | "RESTART": "Restart", 18 | "RESTARTMM": "Restart MagicMirror²", 19 | "SHUTDOWN": "Shutdown", 20 | "FULLSCREEN": "Toggle Fullscreen", 21 | "MINIMIZE": "Minimize Browser", 22 | "DEVTOOLS": "Toggle DevTools", 23 | 24 | "SHOWALL": "All", 25 | "HIDEALL": "All", 26 | 27 | "ADD_MODULE": "Add module", 28 | "SEARCH": "Search …", 29 | "INSTALLED": "Installed", 30 | "DOWNLOAD": "Download", 31 | "DOWNLOADING": "Downloading …", 32 | "CODE_LINK": "View code", 33 | "BY": "by", 34 | "ADD_THIS": "Add this module", 35 | "HELP": "Show readme", 36 | "MENU": "Menu", 37 | "RESET": "Reset", 38 | "NO_HEADER": "(no header)", 39 | "NO_POSITION": "(invisible)", 40 | "ADD_ENTRY": "Add entry", 41 | "NEW_ENTRY_NAME": "(enter name of new entry)", 42 | "DELETE_ENTRY": "Delete", 43 | "UNSAVED_CHANGES": "Unsaved changes", 44 | "OK": "Ok", 45 | "DISCARD": "Discard changes", 46 | "CANCEL": "Cancel", 47 | 48 | "NO_MODULES_LOADED": "No module loaded.", 49 | "SAVE": "Save", 50 | 51 | "EXPERIMENTAL": "This is an experimental feature, it could corrupt your config file. Backup your config first, just in case!
Do you want to continue?", 52 | "PANIC": "Never mind.", 53 | "NO_RISK_NO_FUN": "No risk no fun!", 54 | 55 | "CONFIRM_SHUTDOWN": "The system will shut down.", 56 | "CONFIRM_RESTART": "The system will restart.", 57 | 58 | "LOAD_ERROR": "If you see this message, an errror occured when loading the javascript file. Please go to the following link and see if this a known problem with your browser:", 59 | "ISSUE_LINK": "Github issue page", 60 | 61 | "DONE": "Done.", 62 | "ERROR": "Error!", 63 | "LOADING": "Loading …", 64 | 65 | "LOCKSTRING_WARNING": "This module was hidden by LIST_OF_MODULES, it can not be shown.", 66 | "FORCE_SHOW": "Do it anyway.", 67 | 68 | "UPDATE_MENU_NAME": "Updates", 69 | "UPDATEMM": "Update MagicMirror²", 70 | "UPDATE_AVAILABLE": "Update available", 71 | 72 | "ALERT_MENU_NAME": "Alert", 73 | "SENDALERT": "Send", 74 | "HIDEALERT": "Hide", 75 | "FORM_TYPE": "Type:", 76 | "FORM_ALERT": "Alert", 77 | "FORM_NOTIFICATION": "Notification", 78 | "FORM_TITLE": "Title:", 79 | "FORM_TITLE_PLACEHOLDER": "Enter title...", 80 | "FORM_MESSAGE": "Message:", 81 | "FORM_MESSAGE_PLACEHOLDER": "Enter message...", 82 | "FORM_SECONDS": "Seconds:", 83 | 84 | "RESPONSE_ERROR": "Well that didn't work. Check the MM logs for more details.", 85 | "MODULE_CONTROLS": "Module Controls", 86 | "CUSTOM_MENU": "My Custom Menu" 87 | } 88 | -------------------------------------------------------------------------------- /translations/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "TITLE": "Menú MagicMirror²", 3 | 4 | "EDIT_MENU_NAME": "Editar Vista", 5 | "SHUTDOWN_MENU_NAME": "Encendido", 6 | "CONFIGURE_MENU_NAME": "Editar config.js", 7 | "VIEW_MIRROR": "MagicMirror²", 8 | 9 | "BACK": "Atrás", 10 | 11 | "BRIGHTNESS": "Brillo", 12 | 13 | "REFRESHMM": "Recargar Navegador", 14 | "MONITOROFF": "Apagar monitor", 15 | "MONITORON": "Encender monitor", 16 | "MONITORTIMED": "Encender monitor brevemente", 17 | "RESTART": "Recargar", 18 | "RESTARTMM": "Recargar MagicMirror²", 19 | "SHUTDOWN": "Apagar RPi", 20 | "FULLSCREEN": "Pantalla completa", 21 | "MINIMIZE": "Minimizar Navegador", 22 | "DEVTOOLS": "Abrir herram. para desarrolladores", 23 | 24 | "SHOWALL": "Mostrar Todos", 25 | "HIDEALL": "Ocultar Todos", 26 | 27 | "ADD_MODULE": "Añadir módulo", 28 | "SEARCH": "Buscar …", 29 | "INSTALLED": "Instalado", 30 | "DOWNLOAD": "Descarga", 31 | "DOWNLOADING": "Descargando …", 32 | "CODE_LINK": "Ver código", 33 | "BY": "por", 34 | "ADD_THIS": "Añadir este módulo", 35 | "HELP": "Ver Readme", 36 | "MENU": "Menú", 37 | "RESET": "Reset", 38 | "NO_HEADER": "(sin cabecera)", 39 | "NO_POSITION": "(invisible)", 40 | "ADD_ENTRY": "Añadir entrada", 41 | "NEW_ENTRY_NAME": "(nombre de la nueva entrada)", 42 | "DELETE_ENTRY": "Borrar", 43 | "UNSAVED_CHANGES": "Cambios sin guardar", 44 | "OK": "Ok", 45 | "DISCARD": "Descartar cambios", 46 | "CANCEL": "Cancelar", 47 | 48 | "NO_MODULES_LOADED": "No hay modulos cargados.", 49 | "SAVE": "Guardar", 50 | 51 | "EXPERIMENTAL": "Esta es una función experimental, puede corromper tu archivo config. Haz copia de tu configuración primero, por si acaso!
Quieres continuar?", 52 | "PANIC": "No me importa.", 53 | "NO_RISK_NO_FUN": "Sin riesgo no hay recompensa!", 54 | 55 | "CONFIRM_SHUTDOWN": "El sistema se apagará.", 56 | "CONFIRM_RESTART": "El sistema se recargará.", 57 | 58 | "LOAD_ERROR": "Si ves este mensaje, un error ha ocurrido cargando el archivo javascript. Por favor, revisa este enlace para ver si es un problema con tu navegador:", 59 | "ISSUE_LINK": "Página de problemas de Github", 60 | 61 | "DONE": "Hecho.", 62 | "ERROR": "Error!", 63 | "LOADING": "Cargando …", 64 | 65 | "LOCKSTRING_WARNING": "Este módulo fué ocultado por LIST_OF_MODULES, no puede ser mostrado.", 66 | "FORCE_SHOW": "Hazlo igualmente.", 67 | 68 | "UPDATE_MENU_NAME": "Actualizaciones", 69 | "UPDATEMM": "Actualiza MagicMirror²", 70 | "UPDATE_AVAILABLE": "Actualización disponible", 71 | 72 | "ALERT_MENU_NAME": "Alertas / Notificaciones", 73 | "SENDALERT": "Envía alerta", 74 | "HIDEALERT": "Oculta alerta", 75 | "FORM_TYPE": "Tipo:", 76 | "FORM_ALERT": "Alerta", 77 | "FORM_NOTIFICATION": "Notificación", 78 | "FORM_TITLE": "Título:", 79 | "FORM_TITLE_PLACEHOLDER": "Introduce título...", 80 | "FORM_MESSAGE": "Mensaje:", 81 | "FORM_MESSAGE_PLACEHOLDER": "Introduce mensaje...", 82 | "FORM_SECONDS": "Segundos:", 83 | 84 | "RESPONSE_ERROR": "No ha funcionado. Comprueba los MM logs para más detalles", 85 | "MODULE_CONTROLS": "Controles de módulos", 86 | "CUSTOM_MENU": "Mi Menú Personalizado" 87 | } 88 | -------------------------------------------------------------------------------- /translations/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "TITLE": "MagicMirror² Menu", 3 | 4 | "EDIT_MENU_NAME": "Afficher ou cacher les modules", 5 | "SHUTDOWN_MENU_NAME": "Alimentation", 6 | "CONFIGURE_MENU_NAME": "Éditer le fichier config.js", 7 | "VIEW_MIRROR": "MagicMirror²", 8 | 9 | "BACK": "Retour", 10 | 11 | "BRIGHTNESS": "Luminosité", 12 | 13 | "REFRESHMM": "Recharger l'affichage", 14 | "MONITOROFF": "Éteindre le miroir", 15 | "MONITORON": "Allumer le miroir", 16 | "MONITORTIMED": "Allumer brièvement le miroir", 17 | "RESTART": "Redémarrer RPI", 18 | "RESTARTMM": "Redémarrer MagicMirror²", 19 | "SHUTDOWN": "Éteindre RPI", 20 | 21 | "SHOWALL": "Tout voir", 22 | "HIDEALL": "Tout cacher", 23 | 24 | "ADD_MODULE": "Ajouter un module", 25 | "SEARCH": "Rechercher …", 26 | "INSTALLED": "Installer", 27 | "DOWNLOAD": "Télécharger", 28 | "DOWNLOADING": "Téléchargements …", 29 | "CODE_LINK": "Voir le code", 30 | "BY": "par", 31 | "ADD_THIS": "Ajouter ce module", 32 | "HELP": "Voir le readme", 33 | "MENU": "Menu", 34 | "RESET": "Réinitialiser", 35 | "NO_HEADER": "(no header)", 36 | "NO_POSITION": "(invisible)", 37 | "ADD_ENTRY": "Ajouter une entrée", 38 | "NEW_ENTRY_NAME": "Entrer le nom", 39 | "DELETE_ENTRY": "Supprimer", 40 | "UNSAVED_CHANGES": "Changements non sauvegardés", 41 | "OK": "Ok", 42 | "DISCARD": "Ignorer les changements", 43 | "CANCEL": "Annuler", 44 | 45 | "NO_MODULES_LOADED": "Aucun module chargé.", 46 | "SAVE": "Enregistrer", 47 | 48 | "EXPERIMENTAL": "Ceci est une fonctionnalité expérimentale, elle peut casser votre configuration. Enregistrez votre configuration au cas où !
Voulez vous continuer ?", 49 | "PANIC": "Non, tant pis !", 50 | "NO_RISK_NO_FUN": "On a qu'une vie ! Go !", 51 | 52 | "CONFIRM_SHUTDOWN": "Le système va s'éteindre.", 53 | "CONFIRM_RESTART": "Le système va redémarrer.", 54 | 55 | "LOAD_ERROR": "Si vous voyez ce message, une erreur s'est produite lors du chargement du fichier javascript. S'il vous plaît allez sur le lien suivant et voir si cela est un problème connu avec votre navigateur:", 56 | "ISSUE_LINK": "Page d'aide sur Github", 57 | 58 | "DONE": "Fait.", 59 | "ERROR": "Erreur!", 60 | "LOADING": "Chargement …", 61 | 62 | "LOCKSTRING_WARNING": "Ce module a été masqué par LIST_OF_MODULES, il ne peut pas être affiché.", 63 | "FORCE_SHOW": "Fais-le quand même.", 64 | 65 | "UPDATE_MENU_NAME": "Mises à jour", 66 | "UPDATEMM": "Update MagicMirror²", 67 | "UPDATE_AVAILABLE": "Mise à jour disponible", 68 | 69 | "ALERT_MENU_NAME": "Alerte", 70 | "SENDALERT": "Envoyer", 71 | "HIDEALERT": "Cacher", 72 | "FORM_TYPE": "Type:", 73 | "FORM_ALERT": "Alerte", 74 | "FORM_NOTIFICATION": "Notification", 75 | "FORM_TITLE": "Titre:", 76 | "FORM_TITLE_PLACEHOLDER": "Entrez le titre...", 77 | "FORM_MESSAGE": "Message:", 78 | "FORM_MESSAGE_PLACEHOLDER": "Entrez le message...", 79 | "FORM_SECONDS": "Secondes:" 80 | } 81 | -------------------------------------------------------------------------------- /translations/id.json: -------------------------------------------------------------------------------- 1 | { 2 | "TITLE": "Menu MagicMirror²", 3 | 4 | "EDIT_MENU_NAME": "Sunting tampilan", 5 | "SHUTDOWN_MENU_NAME": "Power", 6 | "CONFIGURE_MENU_NAME": "Sunting config.js", 7 | "VIEW_MIRROR": "MagicMirror²", 8 | 9 | "BACK": "Kembali", 10 | 11 | "BRIGHTNESS": "Kecerahan", 12 | 13 | "REFRESHMM": "Refresh Browser", 14 | "MONITOROFF": "Matikan monitor", 15 | "MONITORON": "Nyalakan monitor", 16 | "MONITORTIMED": "Nyalakan monitor sebentar", 17 | "RESTART": "Restart", 18 | "RESTARTMM": "Restart MagicMirror²", 19 | "SHUTDOWN": "Matikan", 20 | 21 | "SHOWALL": "Semua", 22 | "HIDEALL": "Semua", 23 | 24 | "ADD_MODULE": "Tambah modul", 25 | "SEARCH": "Cari …", 26 | "INSTALLED": "Terinstal", 27 | "DOWNLOAD": "Unduh", 28 | "DOWNLOADING": "Sedang mengunduh …", 29 | "CODE_LINK": "Lihat kode", 30 | "BY": "oleh", 31 | "ADD_THIS": "Tambahkan modul ini", 32 | "HELP": "Lihat readme", 33 | "MENU": "Menu", 34 | "RESET": "Reset", 35 | "NO_HEADER": "(tidak ada judul)", 36 | "NO_POSITION": "(tersembunyi)", 37 | "ADD_ENTRY": "Tambah entri", 38 | "NEW_ENTRY_NAME": "(masukkan nama entri baru)", 39 | "DELETE_ENTRY": "Hapus", 40 | "UNSAVED_CHANGES": "Perubahan tidak tersimpan", 41 | "OK": "Ok", 42 | "DISCARD": "Abaikan perubahan", 43 | "CANCEL": "Membatalkan", 44 | 45 | "NO_MODULES_LOADED": "Tidak ada modul yang dimuat.", 46 | "SAVE": "Simpan", 47 | 48 | "EXPERIMENTAL": "Ini adalah fitur percobaan, dapat merusak file config Anda. Backup config Anda terlebih dahulu, untuk berjaga-jaga!
Yakin untuk lanjut?", 49 | "PANIC": "Gak usah dipikirin.", 50 | "NO_RISK_NO_FUN": "Tanpa resiko tidak ada kesenangan!", 51 | 52 | "CONFIRM_SHUTDOWN": "Sistem akan matikan.", 53 | "CONFIRM_RESTART": "Sistem akan restart.", 54 | 55 | "LOAD_ERROR": "Jika Anda melihat pesan ini, telah terjadi kesalahan ketika memuat file javascript. Silakan kunjungi link berikut dan cek masalah ini di browser Anda:", 56 | "ISSUE_LINK": "Halaman isu Github", 57 | 58 | "DONE": "Selesai.", 59 | "ERROR": "Error!", 60 | "LOADING": "Memuat …", 61 | 62 | "UPDATE_MENU_NAME": "Pembaharuan", 63 | "UPDATEMM": "Perbarui MagicMirror²", 64 | "UPDATE_AVAILABLE": "Pembaharuan tersedia", 65 | 66 | "ALERT_MENU_NAME": "Peringatan", 67 | "SENDALERT": "Kirim", 68 | "HIDEALERT": "Sembunyikan", 69 | "FORM_TYPE": "Tipe:", 70 | "FORM_ALERT": "Peringatan", 71 | "FORM_NOTIFICATION": "Pemberitahuan", 72 | "FORM_TITLE": "Judul:", 73 | "FORM_TITLE_PLACEHOLDER": "Tuliskan judul...", 74 | "FORM_MESSAGE": "Pesan:", 75 | "FORM_MESSAGE_PLACEHOLDER": "Tuliskan pesan...", 76 | "FORM_SECONDS": "Detik:" 77 | } 78 | -------------------------------------------------------------------------------- /translations/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "TITLE": "Menù MagicMirror²", 3 | 4 | "EDIT_MENU_NAME": "Modifica vista", 5 | "SHUTDOWN_MENU_NAME": "Alimentazione", 6 | "CONFIGURE_MENU_NAME": "Modifica config.js", 7 | "VIEW_MIRROR": "MagicMirror²", 8 | 9 | "BACK": "Indietro", 10 | 11 | "BRIGHTNESS": "Luminosità", 12 | 13 | "REFRESHMM": "Aggiorna browser", 14 | "MONITOROFF": "Spegni il monitor", 15 | "MONITORON": "Accendi il monitor", 16 | "MONITORTIMED": "Accendi brevemente il monitor", 17 | "RESTART": "Riavvia", 18 | "RESTARTMM": "Riavvia MagicMirror²", 19 | "SHUTDOWN": "Spegni", 20 | 21 | "SHOWALL": "Tutto", 22 | "HIDEALL": "Tutto", 23 | 24 | "ADD_MODULE": "Aggiungi modulo", 25 | "SEARCH": "Cerca …", 26 | "INSTALLED": "Installati", 27 | "DOWNLOAD": "Scarica", 28 | "DOWNLOADING": "Sto scaricando …", 29 | "CODE_LINK": "Vedi sorgente", 30 | "BY": "di", 31 | "ADD_THIS": "Aggiungi questo modulo", 32 | "HELP": "Visualizza il file readme", 33 | "MENU": "Menù", 34 | "RESET": "Ripristina", 35 | "NO_HEADER": "(nessuna intestazione)", 36 | "NO_POSITION": "(invisibile)", 37 | "ADD_ENTRY": "Aggiungi voce", 38 | "NEW_ENTRY_NAME": "(inserisci il nome della nuova voce)", 39 | "DELETE_ENTRY": "Elimina", 40 | "UNSAVED_CHANGES": "Modifiche non salvate", 41 | "OK": "Ok", 42 | "DISCARD": "Annulla modifiche", 43 | "CANCEL": "Annulla", 44 | 45 | "NO_MODULES_LOADED": "Nessun modulo caricato.", 46 | "SAVE": "Salva", 47 | 48 | "EXPERIMENTAL": "Questa è una funzione sperimentale e potrebbe rendere illeggibile il file config. Effettua prima il backup del tuo config per sicurezza!
Vuoi continuare?", 49 | "PANIC": "Nessun problema.", 50 | "NO_RISK_NO_FUN": "Chi non risica non rosica!", 51 | 52 | "CONFIRM_SHUTDOWN": "Il sistema verrà arrestato.", 53 | "CONFIRM_RESTART": "Il sistema verrà riavviato.", 54 | 55 | "LOAD_ERROR": "Se leggi questo messaggo allora c'è stato un errore nel caricare il file javascript. Per favore vai al link seguente e verifica se sia un problema conosciuto del tuo browser:", 56 | "ISSUE_LINK": "Pagina Github del problema", 57 | 58 | "DONE": "Fatto.", 59 | "ERROR": "Errore!", 60 | "LOADING": "Sto caricando …", 61 | 62 | "LOCKSTRING_WARNING": "Questo modulo è stato nascosto da LIST_OF_MODULES e non può essere mostrato.", 63 | "FORCE_SHOW": "Procedi comunque.", 64 | 65 | "UPDATE_MENU_NAME": "Aggiornamenti", 66 | "UPDATEMM": "Aggiorna MagicMirror²", 67 | "UPDATE_AVAILABLE": "Aggiornamento disponibile", 68 | 69 | "ALERT_MENU_NAME": "Avviso", 70 | "SENDALERT": "Invia", 71 | "HIDEALERT": "Nascondi", 72 | "FORM_TYPE": "Tipo:", 73 | "FORM_ALERT": "Avviso", 74 | "FORM_NOTIFICATION": "Notifica", 75 | "FORM_TITLE": "Titolo:", 76 | "FORM_TITLE_PLACEHOLDER": "Inserisci il titolo...", 77 | "FORM_MESSAGE": "Messaggio:", 78 | "FORM_MESSAGE_PLACEHOLDER": "Inserisci il messaggio...", 79 | "FORM_SECONDS": "Secondi:" 80 | } 81 | -------------------------------------------------------------------------------- /translations/nb.json: -------------------------------------------------------------------------------- 1 | { 2 | "TITLE": "MagicMirror² Meny", 3 | 4 | "EDIT_MENU_NAME": "Rediger visning", 5 | "SHUTDOWN_MENU_NAME": "Strøm", 6 | "CONFIGURE_MENU_NAME": "Rediger config.js", 7 | "VIEW_MIRROR": "MagicMirror²", 8 | 9 | "BACK": "Tilbake", 10 | 11 | "BRIGHTNESS": "Lysstyrke", 12 | 13 | "REFRESHMM": "Oppdater nettleseren", 14 | "MONITOROFF": "Slå av skjermen", 15 | "MONITORON": "Slå på skjermen", 16 | "MONITORTIMED": "Slå skjermen på kort", 17 | "RESTART": "Omstart", 18 | "RESTARTMM": "Restart MagicMirror²", 19 | "SHUTDOWN": "Slå av", 20 | 21 | "SHOWALL": "Alle", 22 | "HIDEALL": "Alle", 23 | 24 | "ADD_MODULE": "Legg til modul", 25 | "SEARCH": "Søk …", 26 | "INSTALLED": "Installert", 27 | "DOWNLOAD": "Last ned", 28 | "DOWNLOADING": "Laster ned …", 29 | "CODE_LINK": "Se kildekode", 30 | "BY": "av", 31 | "ADD_THIS": "Legg til denne modulen", 32 | "HELP": "Vis readme", 33 | "MENU": "Meny", 34 | "RESET": "Reset", 35 | "NO_HEADER": "(ingen topptekst)", 36 | "NO_POSITION": "(usynlig)", 37 | "ADD_ENTRY": "Legg til oppføring", 38 | "NEW_ENTRY_NAME": "(skriv inn navn på ny oppføring)", 39 | "DELETE_ENTRY": "Slett", 40 | "UNSAVED_CHANGES": "Ulagrede endringer", 41 | "OK": "Ok", 42 | "DISCARD": "Forkaste endringer", 43 | "CANCEL": "Avbryt", 44 | 45 | "NO_MODULES_LOADED": "Ingen modul lastet.", 46 | "SAVE": "Lagre", 47 | 48 | "EXPERIMENTAL": "Dette er en eksperimentell funksjon, det kan ødelegge konfigurasjonsfilen din. Sikkerhetskopier din konfigurasjon først, bare i tilfelle!
Vil du fortsette?", 49 | "PANIC": "Glem det.", 50 | "NO_RISK_NO_FUN": "Ingen risiko, ingen morro!", 51 | 52 | "CONFIRM_SHUTDOWN": "Systemet slås av.", 53 | "CONFIRM_RESTART": "Systemet vil starte på nytt.", 54 | 55 | "LOAD_ERROR": "Hvis du ser denne meldingen, oppstod det en feil ved lasting av javascriptfilen. Vennligst gå til den følgende lenken og se om dette er et kjent problem med nettleseren din:", 56 | "ISSUE_LINK": "Github problem side", 57 | 58 | "DONE": "Ferdig.", 59 | "ERROR": "Feil!", 60 | "LOADING": "Laster …", 61 | 62 | "LOCKSTRING_WARNING": "Denne modulen ble skjult av LIST_OF_MODULES, det kan ikke vises.", 63 | "FORCE_SHOW": "Gjør det likevel.", 64 | 65 | "UPDATE_MENU_NAME": "Oppdateringer", 66 | "UPDATEMM": "Oppdater MagicMirror²", 67 | "UPDATE_AVAILABLE": "Oppdatering tilgjengelig", 68 | 69 | "ALERT_MENU_NAME": "Varsling", 70 | "SENDALERT": "Send", 71 | "HIDEALERT": "Skjul", 72 | "FORM_TYPE": "Type:", 73 | "FORM_ALERT": "Varsling", 74 | "FORM_NOTIFICATION": "Melding", 75 | "FORM_TITLE": "Tittel:", 76 | "FORM_TITLE_PLACEHOLDER": "Skriv inn tittel...", 77 | "FORM_MESSAGE": "Beskjed:", 78 | "FORM_MESSAGE_PLACEHOLDER": "Skriv inn melding...", 79 | "FORM_SECONDS": "Sekunder:" 80 | } 81 | -------------------------------------------------------------------------------- /translations/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "TITLE": "MagicMirror² Menu", 3 | 4 | "EDIT_MENU_NAME": "Aanzicht aanpassen", 5 | "SHUTDOWN_MENU_NAME": "Stroom", 6 | "CONFIGURE_MENU_NAME": "config.js aanpassen", 7 | "VIEW_MIRROR": "MagicMirror²", 8 | 9 | "BACK": "Terug", 10 | 11 | "BRIGHTNESS": "Helderheid", 12 | 13 | "REFRESHMM": "Browser verversen", 14 | "MONITOROFF": "Schakel monitor UIT", 15 | "MONITORON": "Schakel monitor AAN", 16 | "MONITORTIMED": "Schakel monitor korstondig AAN", 17 | "RESTART": "Herstarten", 18 | "RESTARTMM": "MagicMirror² herstarten", 19 | "SHUTDOWN": "Uitschakelen", 20 | 21 | "SHOWALL": "Alles", 22 | "HIDEALL": "Alles", 23 | 24 | "ADD_MODULE": "Module toevoegen", 25 | "SEARCH": "Zoeken …", 26 | "INSTALLED": "Gëinstalleerd", 27 | "DOWNLOAD": "Download", 28 | "DOWNLOADING": "Downloaden …", 29 | "CODE_LINK": "View code", 30 | "BY": "door", 31 | "ADD_THIS": "Voeg deze module toe", 32 | "HELP": "Toon 'readme'", 33 | "MENU": "Menu", 34 | "RESET": "Reset", 35 | "NO_HEADER": "(geen header)", 36 | "NO_POSITION": "(onzichtbaar)", 37 | "ADD_ENTRY": "Nieuwe toevoeging", 38 | "NEW_ENTRY_NAME": "(voor naam in van nieuwe toevoeging)", 39 | "DELETE_ENTRY": "Verwijder", 40 | "UNSAVED_CHANGES": "Onopgeslagen wijzigingen", 41 | "OK": "Ok", 42 | "DISCARD": "Wijzigingen ongedaan maken", 43 | "CANCEL": "Annuleren", 44 | 45 | "NO_MODULES_LOADED": "Geen module geladen.", 46 | "SAVE": "Opslaan", 47 | 48 | "EXPERIMENTAL": "Dit is een experimentele feature, het kan je configuratie bestand onleesbaar maken. Sla voor de zekerheid eerst je configuratie op!
Weet je zeker dat je wilt doorgaan?", 49 | "PANIC": "Laat maar.", 50 | "NO_RISK_NO_FUN": "No guts, no glory!", 51 | 52 | "CONFIRM_SHUTDOWN": "Het systeem wordt uitgeschakeld.", 53 | "CONFIRM_RESTART": "Het systeem wordt opnieuw gestart.", 54 | 55 | "LOAD_ERROR": "Wanneer je dit bericht ziet, dan is er een fout opgetreden bij het laden van het javascript bestand. Ga naar de volgende link en controlleer of dit een bekend probleem is het de browser die je gebruikt:", 56 | "ISSUE_LINK": "Github issue pagina", 57 | 58 | "DONE": "Klaar.", 59 | "ERROR": "Fout!", 60 | "LOADING": "Laden …", 61 | 62 | "UPDATE_MENU_NAME": "Updates", 63 | "UPDATEMM": "MagicMirror² updaten", 64 | "UPDATE_AVAILABLE": "Update beschikbaar", 65 | 66 | "ALERT_MENU_NAME": "Waarschuwing", 67 | "SENDALERT": "Verstuur", 68 | "HIDEALERT": "Verberg", 69 | "FORM_TYPE": "Type:", 70 | "FORM_ALERT": "Waarschuwing", 71 | "FORM_NOTIFICATION": "Notificatie", 72 | "FORM_TITLE": "Titel:", 73 | "FORM_TITLE_PLACEHOLDER": "Voor titel in...", 74 | "FORM_MESSAGE": "Bericht:", 75 | "FORM_MESSAGE_PLACEHOLDER": "Voer bericht in...", 76 | "FORM_SECONDS": "Seconden:" 77 | } 78 | -------------------------------------------------------------------------------- /translations/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "TITLE": "Menu do MagicMirror²", 3 | 4 | "EDIT_MENU_NAME": "Editar a aparência", 5 | "SHUTDOWN_MENU_NAME": "Ligar/Desligar", 6 | "CONFIGURE_MENU_NAME": "Editar o config.js", 7 | "VIEW_MIRROR": "MagicMirror²", 8 | 9 | "BACK": "Voltar", 10 | 11 | "BRIGHTNESS": "Luminosidade", 12 | 13 | "REFRESHMM": "Atualizar página", 14 | "MONITOROFF": "Desligar o monitor", 15 | "MONITORON": "Ligar o monitor", 16 | "MONITORTIMED": "Ligar o monitor temporariamente", 17 | "RESTART": "Reiniciar", 18 | "RESTARTMM": "Reiniciar o MagicMirror²", 19 | "SHUTDOWN": "Desligar", 20 | 21 | "SHOWALL": "Todos", 22 | "HIDEALL": "Todos", 23 | 24 | "ADD_MODULE": "Adicionar módulo", 25 | "SEARCH": "Procurar …", 26 | "INSTALLED": "Instalado", 27 | "DOWNLOAD": "Descarregar", 28 | "DOWNLOADING": "A descarregar …", 29 | "CODE_LINK": "Ver o código", 30 | "BY": "por", 31 | "ADD_THIS": "Adicionar este módulo", 32 | "HELP": "Instruções", 33 | "MENU": "Menu", 34 | "RESET": "Reset", 35 | "NO_HEADER": "(sem cabeçalho)", 36 | "NO_POSITION": "(invisível)", 37 | "ADD_ENTRY": "Adicionar entrada", 38 | "NEW_ENTRY_NAME": "(inserir nome da nova entrada)", 39 | "DELETE_ENTRY": "Eliminar", 40 | "UNSAVED_CHANGES": "Alterações não guardadas", 41 | "OK": "Ok", 42 | "DISCARD": "Cancelar alterações", 43 | "CANCEL": "Cancelar", 44 | 45 | "NO_MODULES_LOADED": "Nenhum módulo carregado.", 46 | "SAVE": "Guardar", 47 | 48 | "EXPERIMENTAL": "Esta é uma função experimental e pode corromper o seu ficheiro de configuração. Faça backup da sua configuração por precaução!
Deseja continuar?", 49 | "PANIC": "Deixa lá.", 50 | "NO_RISK_NO_FUN": "Quem não arrisca, não petisca!", 51 | 52 | "CONFIRM_SHUTDOWN": "O sistema irá desligar.", 53 | "CONFIRM_RESTART": "O sistema irá reiniciar.", 54 | 55 | "LOAD_ERROR": "Ocorreu um erro ao ler o seu ficheiro. Por favor, visite este website e veja se o caso aplica-se a si.", 56 | "ISSUE_LINK": "Página do problema no Github", 57 | 58 | "DONE": "Concluído.", 59 | "ERROR": "Erro!", 60 | "LOADING": "A carregar …", 61 | 62 | "UPDATE_MENU_NAME": "Atualizações", 63 | "UPDATEMM": "Atualizar o MagicMirror²", 64 | "UPDATE_AVAILABLE": "Atualização disponível", 65 | 66 | "ALERT_MENU_NAME": "Alerta", 67 | "SENDALERT": "Enviar", 68 | "HIDEALERT": "Esconder", 69 | "FORM_TYPE": "Tipo:", 70 | "FORM_ALERT": "Alerta", 71 | "FORM_NOTIFICATION": "Notificação", 72 | "FORM_TITLE": "Título:", 73 | "FORM_TITLE_PLACEHOLDER": "Digite o título...", 74 | "FORM_MESSAGE": "Mensagem:", 75 | "FORM_MESSAGE_PLACEHOLDER": "Digite a mensagem...", 76 | "FORM_SECONDS": "Segundos:" 77 | } 78 | -------------------------------------------------------------------------------- /translations/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "TITLE": "MagicMirror² Meny", 3 | 4 | "EDIT_MENU_NAME": "Editera synlighet", 5 | "SHUTDOWN_MENU_NAME": "Av och På", 6 | "CONFIGURE_MENU_NAME": "Editera config.js", 7 | "VIEW_MIRROR": "MagicMirror²", 8 | 9 | "BACK": "Tillbaka", 10 | 11 | "BRIGHTNESS": "Ljusstyrka", 12 | 13 | "REFRESHMM": "Uppdatea Webläsaren", 14 | "MONITOROFF": "Sätt skärmen i vila", 15 | "MONITORON": "Väck skärmen", 16 | "MONITORTIMED": "Väck skärmen en kort stund", 17 | "RESTART": "Starta om", 18 | "RESTARTMM": "Starta om MagicMirror²", 19 | "SHUTDOWN": "Stäng av", 20 | 21 | "SHOWALL": "Alla", 22 | "HIDEALL": "Alla", 23 | 24 | "ADD_MODULE": "Lägg till modul", 25 | "SEARCH": "Sök …", 26 | "INSTALLED": "Installerad", 27 | "DOWNLOAD": "Ladda ned", 28 | "DOWNLOADING": "Laddar ner …", 29 | "CODE_LINK": "Titta på källkod", 30 | "BY": "av", 31 | "ADD_THIS": "Lägg till modul", 32 | "HELP": "Visa läsmig", 33 | "MENU": "Meny", 34 | "RESET": "Återgå", 35 | "NO_HEADER": "(ingen överskrift)", 36 | "NO_POSITION": "(osynlig)", 37 | "ADD_ENTRY": "Lägg till värde", 38 | "NEW_ENTRY_NAME": "(Skriv ett namn på värdet)", 39 | "DELETE_ENTRY": "Tabort", 40 | "UNSAVED_CHANGES": "Osparade ändringar", 41 | "OK": "Ok", 42 | "DISCARD": "Spara inte ändringar", 43 | "CANCEL": "Avbryt", 44 | 45 | "NO_MODULES_LOADED": "Ingen modul laddad.", 46 | "SAVE": "Spara", 47 | 48 | "EXPERIMENTAL": "Detta är en experimentell funktion, den skulle kunna förstöra din konfigfil. Se till du har en kopia på din konfigfil innan du går vidare, för att vara på säkra sidan!
Vill du fortsätta?", 49 | "PANIC": "Nej, strunta i det.", 50 | "NO_RISK_NO_FUN": "Ja, kör på! Ingen risk, inget kul!", 51 | 52 | "CONFIRM_SHUTDOWN": "Systemet stängs av.", 53 | "CONFIRM_RESTART": "Systemet startas om.", 54 | 55 | "LOAD_ERROR": "Om du ser detta meddelande så har något gott fel när javascriptfilen laddades. Vänligen gå till följande länk och se om det är ett känt problem med din Webbläsare:", 56 | "ISSUE_LINK": "Githubs ärendesida", 57 | 58 | "DONE": "Klart.", 59 | "ERROR": "Fel!", 60 | "LOADING": "Laddar …", 61 | 62 | "UPDATE_MENU_NAME": "Uppdateringar", 63 | "UPDATEMM": "Uppdatera MagicMirror²", 64 | "UPDATE_AVAILABLE": "Uppdatering tillgänglig", 65 | 66 | "ALERT_MENU_NAME": "Larm", 67 | "SENDALERT": "Skicka", 68 | "HIDEALERT": "Göm", 69 | "FORM_TYPE": "Sort:", 70 | "FORM_ALERT": "Larm", 71 | "FORM_NOTIFICATION": "Notifikation", 72 | "FORM_TITLE": "Titel:", 73 | "FORM_TITLE_PLACEHOLDER": "Skriv titel...", 74 | "FORM_MESSAGE": "Meddelande:", 75 | "FORM_MESSAGE_PLACEHOLDER": "Skriv meddelande...", 76 | "FORM_SECONDS": "Sekunder:" 77 | } 78 | -------------------------------------------------------------------------------- /translations/tr.json: -------------------------------------------------------------------------------- 1 | { 2 | "TITLE": "MagicMirror² Menü", 3 | 4 | "EDIT_MENU_NAME": "Görünümü Düzenle", 5 | "SHUTDOWN_MENU_NAME": "Güç", 6 | "CONFIGURE_MENU_NAME": "config.js dosyasını düzenle", 7 | "VIEW_MIRROR": "MagicMirror²", 8 | 9 | "BACK": "Geri", 10 | 11 | "BRIGHTNESS": "Parlaklık", 12 | 13 | "REFRESHMM": "Tarayıcıyı Yenile", 14 | "MONITOROFF": "Monitörü KAPAT", 15 | "MONITORON": "Monitörü AÇ", 16 | "MONITORTIMED": "Turn monitor ON briefly", 17 | "RESTART": "Yeniden Başlat", 18 | "RESTARTMM": "MagicMirror²'u Yeniden Başlat", 19 | "SHUTDOWN": "Kapat", 20 | "FULLSCREEN": "Tam Ekran", 21 | "MINIMIZE": "Tarayıcıyı Küçült", 22 | "DEVTOOLS": "DevTools Aracını Çalıştır", 23 | 24 | "SHOWALL": "Hepsi", 25 | "HIDEALL": "Hepsi", 26 | 27 | "ADD_MODULE": "Modül Ekle", 28 | "SEARCH": "Ara …", 29 | "INSTALLED": "Kurulu", 30 | "DOWNLOAD": "İndir", 31 | "DOWNLOADING": "İndiriliyor …", 32 | "CODE_LINK": "Kodu Gör", 33 | "BY": "by", 34 | "ADD_THIS": "Bu modülü ekle", 35 | "HELP": "Beni Oku dosyasını göster", 36 | "MENU": "Menü", 37 | "RESET": "Sıfırla", 38 | "NO_HEADER": "(başlık yok)", 39 | "NO_POSITION": "(görünmez)", 40 | "ADD_ENTRY": "Giriş Ekle", 41 | "NEW_ENTRY_NAME": "(ismini yazın)", 42 | "DELETE_ENTRY": "Sil", 43 | "UNSAVED_CHANGES": "Kaydedilmemiş Değişiklikler", 44 | "OK": "Tama", 45 | "DISCARD": "Değişikliklerden Vazgeç", 46 | "CANCEL": "İptal", 47 | 48 | "NO_MODULES_LOADED": "Modül yüklenmedi.", 49 | "SAVE": "Kaydet", 50 | 51 | "EXPERIMENTAL": "Bu deneysel bir özellik dolayısıyla config.js dosyanız zarar görebilir. Önce config.js dosyanızı yedekleyin!
Devam etmek istiyor musunuz?", 52 | "PANIC": "Böyle zor bir kararı alamam. Ben vazgeçtim.", 53 | "NO_RISK_NO_FUN": "Risk yoksa eğlence de yoktur! Devam et...", 54 | 55 | "CONFIRM_SHUTDOWN": "Sistem kapatılacak.", 56 | "CONFIRM_RESTART": "Sistem yeniden başlatılacak.", 57 | 58 | "LOAD_ERROR": "Eğer bu mesajı görüyorsanız, javascript dosyası yüklenirken bir hata oluşmuş demektir. Lütfen aşağıdaki linke gidin ve tarayıcınızla yaşadığınız problemin daha önceden farkedilip edilmediğine bakın:", 59 | "ISSUE_LINK": "Github Sorun Sayfası", 60 | 61 | "DONE": "Tamamlandı.", 62 | "ERROR": "Hata!", 63 | "LOADING": "Yükleniyor …", 64 | 65 | "LOCKSTRING_WARNING": "Bu modül LIST_OF_MODULES kısmında gizlendiği için gösterilemez.", 66 | "FORCE_SHOW": "Yine de göster.", 67 | 68 | "UPDATE_MENU_NAME": "Güncellemeler", 69 | "UPDATEMM": "MagicMirror²'u Güncelle", 70 | "UPDATE_AVAILABLE": "Güncelleme Var", 71 | 72 | "ALERT_MENU_NAME": "Uyarı", 73 | "SENDALERT": "Gönder", 74 | "HIDEALERT": "Gizle", 75 | "FORM_TYPE": "Tür:", 76 | "FORM_ALERT": "Uyarı", 77 | "FORM_NOTIFICATION": "Bilgilendirme", 78 | "FORM_TITLE": "Başlık:", 79 | "FORM_TITLE_PLACEHOLDER": "Başlığı yazın...", 80 | "FORM_MESSAGE": "Mesaj:", 81 | "FORM_MESSAGE_PLACEHOLDER": "Mesajı yazın...", 82 | "FORM_SECONDS": "Saniye:", 83 | 84 | "RESPONSE_ERROR": "Bu şu anda çalışmıyor, daha fazla bilgi için MM log kayıtlarına bakın", 85 | "MODULE_CONTROLS": "Modül Kontrolleri", 86 | "CUSTOM_MENU": "Kişisel Menü" 87 | } 88 | -------------------------------------------------------------------------------- /translations/zh-cn.json: -------------------------------------------------------------------------------- 1 | { 2 | "TITLE": "MagicMirror² Menu 魔镜菜单", 3 | 4 | "EDIT_MENU_NAME": "编辑界面", 5 | "SHUTDOWN_MENU_NAME": "电源", 6 | "CONFIGURE_MENU_NAME": "编辑 config.js", 7 | "VIEW_MIRROR": "MagicMirror²", 8 | 9 | "BACK": "返回", 10 | 11 | "BRIGHTNESS": "亮度", 12 | 13 | "REFRESHMM": "刷新界面", 14 | "MONITOROFF": "关闭显示器", 15 | "MONITORON": "打开显示器", 16 | "MONITORTIMED": "短暂打开显示器", 17 | "RESTART": "重启", 18 | "RESTARTMM": "重启 MagicMirror²", 19 | "SHUTDOWN": "关闭", 20 | "FULLSCREEN": "切换全屏", 21 | "MINIMIZE": "最小化魔镜", 22 | "DEVTOOLS": "打开开发者工具", 23 | 24 | "SHOWALL": "打开全部", 25 | "HIDEALL": "关闭全部", 26 | 27 | "ADD_MODULE": "添加模块", 28 | "SEARCH": "搜索 …", 29 | "INSTALLED": "安装", 30 | "DOWNLOAD": "下载", 31 | "DOWNLOADING": "下载中 …", 32 | "CODE_LINK": "查看代码", 33 | "BY": "by", 34 | "ADD_THIS": "添加此模块", 35 | "HELP": "帮助", 36 | "MENU": "菜单", 37 | "RESET": "重置", 38 | "NO_HEADER": "(无标题)", 39 | "NO_POSITION": "(隐藏)", 40 | "ADD_ENTRY": "添加配置项", 41 | "NEW_ENTRY_NAME": "(添加配置项字段名)", 42 | "DELETE_ENTRY": "删除", 43 | "UNSAVED_CHANGES": "未保存更新", 44 | "OK": "Ok", 45 | "DISCARD": "放弃更改", 46 | "CANCEL": "取消", 47 | 48 | "NO_MODULES_LOADED": "无模块.", 49 | "SAVE": "保存", 50 | 51 | "EXPERIMENTAL": "这是一个实验特性, 它可能会损坏你的配置文件. 先备份你的配置文件,以防万一!
是否继续?", 52 | "PANIC": "不搞了.", 53 | "NO_RISK_NO_FUN": "就是折腾!", 54 | 55 | "CONFIRM_SHUTDOWN": "即将关机.", 56 | "CONFIRM_RESTART": "即将重启.", 57 | 58 | "LOAD_ERROR": "如果你看到这个消息, 说明加载javascript文件时出错。请转到以下链接,查看浏览器是否存在已知问题:", 59 | "ISSUE_LINK": "Issue链接", 60 | 61 | "DONE": "完成.", 62 | "ERROR": "错误!", 63 | "LOADING": "加载中 …", 64 | 65 | "LOCKSTRING_WARNING": "This module was hidden by LIST_OF_MODULES, it can not be shown.", 66 | "FORCE_SHOW": "继续.", 67 | 68 | "UPDATE_MENU_NAME": "更新", 69 | "UPDATEMM": "更新 MagicMirror²", 70 | "UPDATE_AVAILABLE": "更新可用", 71 | 72 | "ALERT_MENU_NAME": "通知", 73 | "SENDALERT": "发送", 74 | "HIDEALERT": "隐藏", 75 | "FORM_TYPE": "分类:", 76 | "FORM_ALERT": "警告", 77 | "FORM_NOTIFICATION": "通知", 78 | "FORM_TITLE": "标题:", 79 | "FORM_TITLE_PLACEHOLDER": "输入标题...", 80 | "FORM_MESSAGE": "消息:", 81 | "FORM_MESSAGE_PLACEHOLDER": "输入消息...", 82 | "FORM_SECONDS": "显示时长(秒):", 83 | 84 | "RESPONSE_ERROR": "操作失败. 查看 MagicMirror² 日志获取更多信息", 85 | "MODULE_CONTROLS": "模块控制", 86 | "CUSTOM_MENU": "自定义菜单" 87 | } 88 | --------------------------------------------------------------------------------