├── WebContent ├── WEB-INF │ ├── media │ │ └── foo.txt │ ├── config │ │ ├── cron.json │ │ ├── settings.json │ │ └── log4j2.xml │ ├── lib │ │ └── cron4j-2.2.5.jar │ ├── help.groovy │ ├── classes │ │ └── log4j2.xml │ ├── media.groovy │ ├── sender.groovy │ ├── web.xml │ ├── cron.groovy │ └── main.groovy ├── META-INF │ └── MANIFEST.MF ├── assets │ ├── css │ │ ├── styles.css │ │ └── toastr.min.css │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ ├── js │ │ ├── lang.js │ │ ├── sender.js │ │ ├── url.min.js │ │ ├── media.js │ │ ├── cron.js │ │ ├── spin.min.js │ │ ├── toastr.min.js │ │ ├── jquery.i18n.messagestore.js │ │ ├── jquery.i18n.fallbacks.js │ │ ├── jquery.i18n.emitter.js │ │ ├── jquery.i18n.parser.js │ │ ├── jquery.i18n.js │ │ ├── jquery.i18n.emitter.bidi.js │ │ ├── jquery.i18n.language.js │ │ ├── main.js │ │ └── jquery.history.js │ └── lang │ │ ├── en.json │ │ └── de.json └── index.groovy ├── _config.yml ├── .settings ├── org.eclipse.wst.jsdt.ui.superType.name ├── org.eclipse.wst.validation.prefs ├── org.eclipse.wst.jsdt.ui.superType.container ├── org.eclipse.m2e.core.prefs ├── org.eclipse.wst.ws.service.policy.prefs ├── org.eclipse.jdt.groovy.core.prefs ├── org.eclipse.ltk.core.refactoring.prefs ├── org.eclipse.core.resources.prefs ├── org.eclipse.wst.common.project.facet.core.prefs.xml ├── org.eclipse.wst.common.project.facet.core.xml ├── .jsdtscope ├── org.eclipse.wst.common.component └── org.eclipse.jdt.core.prefs ├── receiver ├── preloader.gif ├── loading.css ├── main.css ├── index.html └── main.js ├── img └── overview-screenshot.png ├── .gitignore ├── target └── m2e-wtp │ └── web-resources │ └── META-INF │ ├── MANIFEST.MF │ └── maven │ └── presenter │ └── presenter │ ├── pom.properties │ └── pom.xml ├── src ├── de │ └── michaelkuerbis │ │ └── presenter │ │ ├── test │ │ └── Test.java │ │ ├── utils │ │ ├── Settings.java │ │ ├── KioskStatusRequest.java │ │ ├── KioskStatusResponse.java │ │ ├── KioskUpdateRequest.java │ │ ├── CastConnection.java │ │ └── CronJob.java │ │ ├── rest │ │ ├── PresenterApplication.java │ │ ├── DiscoverREST.java │ │ ├── StatusREST.java │ │ ├── StartREST.java │ │ ├── MediaREST.java │ │ ├── SettingsREST.java │ │ └── CronREST.java │ │ └── servlets │ │ ├── ChromeCastDiscoverServlet.java │ │ ├── MediaServlet.java │ │ ├── SettingsServlet.java │ │ └── CronServlet.java └── main │ └── resources │ └── log4j.xml ├── .gitattributes ├── LICENSE ├── .project ├── .classpath ├── .github └── workflows │ └── codeql-analysis.yml ├── README.md └── pom.xml /WebContent/WEB-INF/media/foo.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.jsdt.ui.superType.name: -------------------------------------------------------------------------------- 1 | Window -------------------------------------------------------------------------------- /WebContent/WEB-INF/config/cron.json: -------------------------------------------------------------------------------- 1 | { 2 | "cronjobs":[] 3 | } -------------------------------------------------------------------------------- /WebContent/WEB-INF/config/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "castconnections":[] 3 | } -------------------------------------------------------------------------------- /WebContent/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Class-Path: 3 | 4 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.validation.prefs: -------------------------------------------------------------------------------- 1 | disabled=06target 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.jsdt.ui.superType.container: -------------------------------------------------------------------------------- 1 | org.eclipse.wst.jsdt.launching.baseBrowserLibrary -------------------------------------------------------------------------------- /WebContent/assets/css/styles.css: -------------------------------------------------------------------------------- 1 | 2 | li.active a { 3 | color: blue; 4 | border-bottom: 1px solid blue; 5 | } -------------------------------------------------------------------------------- /receiver/preloader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrothenbuecher/Chromecast-Kiosk/HEAD/receiver/preloader.gif -------------------------------------------------------------------------------- /img/overview-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrothenbuecher/Chromecast-Kiosk/HEAD/img/overview-screenshot.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | /build/ 3 | /target/ 4 | 5 | target/m2e-wtp/web-resources/META-INF/maven/presenter/presenter/pom.properties -------------------------------------------------------------------------------- /.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.ws.service.policy.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.wst.ws.service.policy.projectEnabled=false 3 | -------------------------------------------------------------------------------- /WebContent/WEB-INF/lib/cron4j-2.2.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrothenbuecher/Chromecast-Kiosk/HEAD/WebContent/WEB-INF/lib/cron4j-2.2.5.jar -------------------------------------------------------------------------------- /target/m2e-wtp/web-resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Build-Jdk-Spec: 11 3 | Created-By: Maven Integration for Eclipse 4 | 5 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.groovy.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | groovy.compiler.level=40 3 | groovy.script.filters=**/*.dsld,y,**/*.gradle,n 4 | -------------------------------------------------------------------------------- /.settings/org.eclipse.ltk.core.refactoring.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.ltk.core.refactoring.enable.project.refactoring.history=false 3 | -------------------------------------------------------------------------------- /src/de/michaelkuerbis/presenter/test/Test.java: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrothenbuecher/Chromecast-Kiosk/HEAD/src/de/michaelkuerbis/presenter/test/Test.java -------------------------------------------------------------------------------- /WebContent/assets/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrothenbuecher/Chromecast-Kiosk/HEAD/WebContent/assets/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /WebContent/assets/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrothenbuecher/Chromecast-Kiosk/HEAD/WebContent/assets/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /WebContent/assets/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrothenbuecher/Chromecast-Kiosk/HEAD/WebContent/assets/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /WebContent/assets/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrothenbuecher/Chromecast-Kiosk/HEAD/WebContent/assets/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /src/de/michaelkuerbis/presenter/utils/Settings.java: -------------------------------------------------------------------------------- 1 | package de.michaelkuerbis.presenter.utils; 2 | 3 | public class Settings { 4 | 5 | public final static String appId = "10B2AF08"; 6 | 7 | } 8 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//WebContent/assets/lang=UTF-8 3 | encoding//WebContent/assets/lang/de.json=UTF-8 4 | encoding//WebContent/assets/lang/en.json=UTF-8 5 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.common.project.facet.core.prefs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /WebContent/WEB-INF/help.groovy: -------------------------------------------------------------------------------- 1 | html.div { 2 | div( class:"container", id:"help", style:"display:none;"){ 3 | div(class:"col-xs-12"){ 4 | div( class:"page-header"){ h1('help') } 5 | } 6 | div(class:"col-xs-12"){ 7 | //TODO write help 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/de/michaelkuerbis/presenter/rest/PresenterApplication.java: -------------------------------------------------------------------------------- 1 | package de.michaelkuerbis.presenter.rest; 2 | 3 | import javax.ws.rs.ApplicationPath; 4 | import javax.ws.rs.core.Application; 5 | 6 | @ApplicationPath("/rest") 7 | public class PresenterApplication extends Application { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /receiver/loading.css: -------------------------------------------------------------------------------- 1 | 2 | #loading { 3 | width: 100%; 4 | height: 100%; 5 | z-index: 100; 6 | top: 0; 7 | left: 0; 8 | position: fixed; 9 | display: block; 10 | } 11 | 12 | #loading img { 13 | position:absolute; 14 | top:0; 15 | left:0; 16 | right:0; 17 | bottom:0; 18 | margin:auto; 19 | } -------------------------------------------------------------------------------- /target/m2e-wtp/web-resources/META-INF/maven/presenter/presenter/pom.properties: -------------------------------------------------------------------------------- 1 | #Generated by Maven Integration for Eclipse 2 | #Fri Apr 22 12:52:21 CEST 2022 3 | m2e.projectLocation=C\:\\Users\\Rothenb\u00FCcher\\Documents\\GitHub\\Chromecast-Kiosk 4 | m2e.projectName=presenter 5 | groupId=presenter 6 | artifactId=presenter 7 | version=0.0.1-SNAPSHOT 8 | -------------------------------------------------------------------------------- /receiver/main.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0px; 3 | padding: 0px; 4 | } 5 | 6 | body { 7 | background-color:white; 8 | } 9 | 10 | .container { 11 | height: 100%; 12 | width: 100%; 13 | margin: 0px; 14 | padding: 0px; 15 | overflow: hidden; 16 | background-color:white; 17 | } 18 | 19 | .container iframe { 20 | height: 100% !important; 21 | width: 100% !important; 22 | border: 0; 23 | z-index: 100; 24 | } 25 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.common.project.facet.core.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /WebContent/WEB-INF/classes/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /WebContent/WEB-INF/config/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.settings/.jsdtscope: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/de/michaelkuerbis/presenter/utils/KioskStatusRequest.java: -------------------------------------------------------------------------------- 1 | package de.michaelkuerbis.presenter.utils; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import su.litvak.chromecast.api.v2.Request; 6 | 7 | public class KioskStatusRequest implements Request { 8 | 9 | @JsonProperty 10 | boolean status; 11 | 12 | private Long requestId; 13 | 14 | public KioskStatusRequest(){ 15 | status = true; 16 | } 17 | 18 | @Override 19 | public Long getRequestId() { 20 | return requestId; 21 | } 22 | 23 | @Override 24 | public void setRequestId(Long requestId) { 25 | this.requestId = requestId; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/de/michaelkuerbis/presenter/utils/KioskStatusResponse.java: -------------------------------------------------------------------------------- 1 | package de.michaelkuerbis.presenter.utils; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import su.litvak.chromecast.api.v2.Response; 6 | 7 | public class KioskStatusResponse implements Response { 8 | 9 | //current url 10 | @JsonProperty("url") 11 | String url; 12 | //current refresh rate 13 | @JsonProperty("refresh") 14 | int refresh; 15 | 16 | @JsonProperty("requestId") 17 | Long requestId; 18 | 19 | public String getUrl(){ 20 | return this.url; 21 | } 22 | 23 | public int getRefresh(){ 24 | return this.refresh; 25 | } 26 | 27 | @Override 28 | public Long getRequestId() { 29 | return this.requestId; 30 | } 31 | 32 | @Override 33 | public void setRequestId(Long arg0) { 34 | this.requestId = arg0; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.common.component: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/de/michaelkuerbis/presenter/utils/KioskUpdateRequest.java: -------------------------------------------------------------------------------- 1 | package de.michaelkuerbis.presenter.utils; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import su.litvak.chromecast.api.v2.Request; 6 | 7 | public class KioskUpdateRequest implements Request { 8 | 9 | @JsonProperty 10 | final String url; 11 | @JsonProperty 12 | final String type; 13 | @JsonProperty("refresh") 14 | final long refresh; 15 | 16 | private Long requestId; 17 | 18 | public KioskUpdateRequest(String url, long refresh){ 19 | this.url = url; 20 | this.refresh = refresh; 21 | this.type = "load"; 22 | } 23 | 24 | @Override 25 | public Long getRequestId() { 26 | return requestId; 27 | } 28 | 29 | @Override 30 | public void setRequestId(Long requestId) { 31 | this.requestId = requestId; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/de/michaelkuerbis/presenter/rest/DiscoverREST.java: -------------------------------------------------------------------------------- 1 | package de.michaelkuerbis.presenter.rest; 2 | 3 | import java.util.List; 4 | 5 | import javax.ws.rs.GET; 6 | import javax.ws.rs.Path; 7 | import javax.ws.rs.core.Response; 8 | 9 | import org.json.simple.JSONArray; 10 | import org.json.simple.JSONObject; 11 | 12 | import su.litvak.chromecast.api.v2.ChromeCast; 13 | import su.litvak.chromecast.api.v2.ChromeCasts; 14 | 15 | @Path("/discovered") 16 | public class DiscoverREST { 17 | 18 | @GET 19 | @Path("/get") 20 | public Response start() throws InterruptedException { 21 | List casts = ChromeCasts.get(); 22 | JSONArray array = new JSONArray(); 23 | for (ChromeCast cast : casts) { 24 | JSONObject obj = new JSONObject(); 25 | obj.put("name", cast.getName()); 26 | obj.put("ip", cast.getAddress()); 27 | array.add(obj); 28 | } 29 | return Response.ok(array.toJSONString()).build(); 30 | 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/de/michaelkuerbis/presenter/utils/CastConnection.java: -------------------------------------------------------------------------------- 1 | package de.michaelkuerbis.presenter.utils; 2 | 3 | public class CastConnection { 4 | 5 | private String ip, name; 6 | private boolean isDefault = true; 7 | 8 | public CastConnection(String ip, String name){ 9 | this.ip = ip; 10 | this.name = name; 11 | } 12 | 13 | public CastConnection(String ip, String name, boolean isDefault){ 14 | this.ip = ip; 15 | this.name = name; 16 | this.isDefault = isDefault; 17 | } 18 | 19 | 20 | public String getIp() { 21 | return ip; 22 | } 23 | 24 | public String getName() { 25 | return name; 26 | } 27 | 28 | public void setIp(String ip) { 29 | this.ip = ip; 30 | } 31 | 32 | public void setName(String name) { 33 | this.name = name; 34 | } 35 | 36 | 37 | public boolean isDefault() { 38 | return isDefault; 39 | } 40 | 41 | 42 | public void setDefault(boolean isDefault) { 43 | this.isDefault = isDefault; 44 | } 45 | 46 | 47 | 48 | } 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Michael Rothenbücher 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 | -------------------------------------------------------------------------------- /WebContent/assets/js/lang.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | 4 | var set_locale_to = function(locale) { 5 | if (locale) 6 | $.i18n().locale = locale; 7 | }; 8 | 9 | $.i18n().load({ 10 | de: 'assets/lang/de.json', 11 | en: 'assets/lang/en.json' 12 | }).done(function() { 13 | set_locale_to(url('?locale')); 14 | History.Adapter.bind(window, 'statechange', function(){ 15 | set_locale_to(url('?locale')); 16 | }); 17 | console.log('lang download done!'); 18 | init('body'); 19 | }); 20 | 21 | function init(selector) { 22 | $(selector).i18n(); 23 | $(selector).find("[data-i18n-placeholder]").each(function(){ 24 | $(this).attr('placeholder', $.i18n($(this).data('i18n-placeholder'))); 25 | }); 26 | console.log('lang init done!'); 27 | } 28 | 29 | $(document).ready(function() { 30 | $('input[name="lang"]').on("change", function() { 31 | $this = $(this); 32 | console.log("change lang to: "+$this.val()); 33 | History.pushState(null, null, "?locale=" + $this.val()); 34 | init('body'); 35 | }); 36 | }); 37 | })(); 38 | -------------------------------------------------------------------------------- /receiver/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dashboard 4 | 5 | 6 | 7 | 8 |
9 | 10 |
11 |
12 | 13 | 14 | 15 | 16 | 17 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | presenter 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.wst.jsdt.core.javascriptValidator 10 | 11 | 12 | 13 | 14 | org.eclipse.jdt.core.javabuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.wst.common.project.facet.core.builder 20 | 21 | 22 | 23 | 24 | org.eclipse.wst.validation.validationbuilder 25 | 26 | 27 | 28 | 29 | org.eclipse.m2e.core.maven2Builder 30 | 31 | 32 | 33 | 34 | 35 | org.eclipse.jdt.groovy.core.groovyNature 36 | org.eclipse.m2e.core.maven2Nature 37 | org.eclipse.jem.workbench.JavaEMFNature 38 | org.eclipse.wst.common.modulecore.ModuleCoreNature 39 | org.eclipse.wst.common.project.facet.core.nature 40 | org.eclipse.jdt.core.javanature 41 | org.eclipse.wst.jsdt.core.jsNature 42 | 43 | 44 | -------------------------------------------------------------------------------- /WebContent/WEB-INF/media.groovy: -------------------------------------------------------------------------------- 1 | html.div { 2 | style(""" 3 | .btn-file { 4 | position: relative; 5 | overflow: hidden; 6 | } 7 | .btn-file input[type=file] { 8 | position: absolute; 9 | top: 0; 10 | right: 0; 11 | min-width: 100%; 12 | min-height: 100%; 13 | font-size: 100px; 14 | text-align: right; 15 | filter: alpha(opacity=0); 16 | opacity: 0; 17 | outline: none; 18 | background: white; 19 | cursor: inherit; 20 | display: block; 21 | } 22 | """) 23 | div( class:"container", id:"media", style:"display:none;"){ 24 | div(class:"col-xs-12"){ 25 | div( class:"page-header"){ h1('data-i18n':"media_title",'media') } 26 | } 27 | div(class:"col-xs-12"){ 28 | label( class:"btn btn-default btn-file"){ 29 | div("data-i18n":"media_browse","Browse") 30 | input(id:"media-file", type:"file", "hidden":"hidden") 31 | } 32 | button(class:"btn btn-info", id:"media-upload",'data-i18n':"media_upload","upload") 33 | } 34 | div(class:"col-xs-12"){ hr('') } 35 | div(class:"col-xs-12"){ 36 | label('data-i18n':"media_label","File:") 37 | select(class:"form-control media-list", id:"media-del-list"){ 38 | option("") 39 | } 40 | button(class:"btn btn-default",id:"media-refresh",'data-i18n':"media_refresh", "refresh") 41 | button(class:"btn btn-default",id:"media-delete",'data-i18n':"media_delete", "delete") 42 | } 43 | } 44 | script(src:"assets/js/media.js") 45 | } -------------------------------------------------------------------------------- /WebContent/assets/js/sender.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | function launch(ip, name) { 3 | $.post("rest/start/" + ip+"?"+$.param({ 4 | url: $("#sender").find('#url').val(), 5 | reload: $("#sender").find('#refresh').val(), 6 | })).done(function() { 7 | toastr['info']("Started on " + name + " successfully"); 8 | }).fail(function() { 9 | toastr['error']("Error on " + name); 10 | }); 11 | 12 | } 13 | 14 | $(document).ready(function() { 15 | $('#launch').click(function() { 16 | if (!$("#sender").find('#url').val()) { 17 | toastr['warning']("Please insert url"); 18 | } else { 19 | var all = $("#sender").find('#all').prop("checked"); 20 | if (all) { 21 | $("#sender").find('#receiver-ip').find('option').each(function() { 22 | $this = $(this); 23 | if ($this.data('default') == true) 24 | launch($this.val(), $this.text()); 25 | }); 26 | } else { 27 | var $option = $("#sender").find('#receiver-ip').find('option:selected'); 28 | if ($option) { 29 | launch($option.val(), $option.text()); 30 | } else { 31 | toastr['warning']("Please select chromecast"); 32 | } 33 | 34 | } 35 | } 36 | }); 37 | }); 38 | })(); 39 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/de/michaelkuerbis/presenter/servlets/ChromeCastDiscoverServlet.java: -------------------------------------------------------------------------------- 1 | package de.michaelkuerbis.presenter.servlets; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.ServletException; 6 | import javax.servlet.http.HttpServlet; 7 | 8 | import org.apache.logging.log4j.LogManager; 9 | import org.apache.logging.log4j.Logger; 10 | 11 | import de.michaelkuerbis.presenter.utils.CronJob; 12 | import su.litvak.chromecast.api.v2.ChromeCast; 13 | import su.litvak.chromecast.api.v2.ChromeCasts; 14 | import su.litvak.chromecast.api.v2.ChromeCastsListener; 15 | 16 | public class ChromeCastDiscoverServlet extends HttpServlet { 17 | 18 | /** 19 | * 20 | */ 21 | 22 | private final static Logger log = LogManager.getLogger(ChromeCastDiscoverServlet.class); 23 | 24 | private static final long serialVersionUID = -6986406595023612731L; 25 | 26 | @Override 27 | public void init() throws ServletException{ 28 | 29 | try { 30 | 31 | ChromeCasts.startDiscovery(); 32 | ChromeCasts.registerListener(new ChromeCastsListener(){ 33 | 34 | @Override 35 | public void chromeCastRemoved(ChromeCast arg0) { 36 | log.info("Cast removed: "+arg0.getName()+" "+arg0.getAddress()); 37 | 38 | } 39 | 40 | @Override 41 | public void newChromeCastDiscovered(ChromeCast arg0) { 42 | log.info("Cast found: "+arg0.getName()+" "+arg0.getAddress()); 43 | } 44 | 45 | }); 46 | 47 | log.info("Chromecast Discovery started"); 48 | 49 | } catch (IOException e) { 50 | // TODO Auto-generated catch block 51 | e.printStackTrace(); 52 | log.error(e); 53 | } 54 | 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/de/michaelkuerbis/presenter/servlets/MediaServlet.java: -------------------------------------------------------------------------------- 1 | package de.michaelkuerbis.presenter.servlets; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.nio.file.CopyOption; 7 | import java.nio.file.Files; 8 | import java.nio.file.Paths; 9 | import java.nio.file.StandardCopyOption; 10 | 11 | import javax.servlet.ServletException; 12 | import javax.servlet.annotation.MultipartConfig; 13 | import javax.servlet.http.HttpServlet; 14 | import javax.servlet.http.HttpServletRequest; 15 | import javax.servlet.http.HttpServletResponse; 16 | import javax.servlet.http.Part; 17 | 18 | import org.apache.logging.log4j.LogManager; 19 | import org.apache.logging.log4j.Logger; 20 | 21 | import de.michaelkuerbis.presenter.rest.SettingsREST; 22 | 23 | @MultipartConfig 24 | public class MediaServlet extends HttpServlet { 25 | 26 | private final static Logger log = LogManager.getLogger(MediaServlet.class); 27 | 28 | private static final long serialVersionUID = -8435883813255134209L; 29 | 30 | @Override 31 | protected void doPost(HttpServletRequest request, HttpServletResponse response) 32 | throws ServletException, IOException { 33 | Part filePart = request.getPart("file"); 34 | String fileName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString().toLowerCase().replace(' ', 35 | '_'); 36 | InputStream fileContent = filePart.getInputStream(); 37 | String path = request.getServletContext().getRealPath("/WEB-INF/media"); 38 | if (fileName.endsWith("aac") || fileName.endsWith("mp4") || fileName.endsWith("mp3") || fileName.endsWith("wav") 39 | || fileName.endsWith("webm")) { 40 | Files.copy(fileContent, Paths.get(path + File.separator + fileName), StandardCopyOption.REPLACE_EXISTING); 41 | } else { 42 | response.setStatus(400); 43 | } 44 | log.debug(path); 45 | fileContent.close(); 46 | // ... (do your job here) 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /WebContent/assets/js/url.min.js: -------------------------------------------------------------------------------- 1 | /*! js-url - v2.3.1 - 2016-10-18 */window.url=function(){function a(){}function b(a){return decodeURIComponent(a.replace(/\+/g," "))}function c(a,b){var c=a.charAt(0),d=b.split(c);return c===a?d:(a=parseInt(a.substring(1),10),d[0>a?d.length+a:a-1])}function d(a,c){for(var d=a.charAt(0),e=c.split("&"),f=[],g={},h=[],i=a.substring(1),j=0,k=e.length;k>j;j++)if(f=e[j].match(/(.*?)=(.*)/),f||(f=[e[j],e[j],""]),""!==f[1].replace(/\s/g,"")){if(f[2]=b(f[2]||""),i===f[1])return f[2];h=f[1].match(/(.*)\[([0-9]+)\]/),h?(g[h[1]]=g[h[1]]||[],g[h[1]][h[2]]=f[2]):g[f[1]]=f[2]}return d===a?g:g[i]}return function(b,e){var f,g={};if("tld?"===b)return a();if(e=e||window.location.toString(),!b)return e;if(b=b.toString(),f=e.match(/^mailto:([^\/].+)/))g.protocol="mailto",g.email=f[1];else{if((f=e.match(/(.*?)\/#\!(.*)/))&&(e=f[1]+f[2]),(f=e.match(/(.*?)#(.*)/))&&(g.hash=f[2],e=f[1]),g.hash&&b.match(/^#/))return d(b,g.hash);if((f=e.match(/(.*?)\?(.*)/))&&(g.query=f[2],e=f[1]),g.query&&b.match(/^\?/))return d(b,g.query);if((f=e.match(/(.*?)\:?\/\/(.*)/))&&(g.protocol=f[1].toLowerCase(),e=f[2]),(f=e.match(/(.*?)(\/.*)/))&&(g.path=f[2],e=f[1]),g.path=(g.path||"").replace(/^([^\/])/,"/$1").replace(/\/$/,""),b.match(/^[\-0-9]+$/)&&(b=b.replace(/^([^\/])/,"/$1")),b.match(/^\//))return c(b,g.path.substring(1));if(f=c("/-1",g.path.substring(1)),f&&(f=f.match(/(.*?)\.(.*)/))&&(g.file=f[0],g.filename=f[1],g.fileext=f[2]),(f=e.match(/(.*)\:([0-9]+)$/))&&(g.port=f[2],e=f[1]),(f=e.match(/(.*?)@(.*)/))&&(g.auth=f[1],e=f[2]),g.auth&&(f=g.auth.match(/(.*)\:(.*)/),g.user=f?f[1]:g.auth,g.pass=f?f[2]:void 0),g.hostname=e.toLowerCase(),"."===b.charAt(0))return c(b,g.hostname);a()&&(f=g.hostname.match(a()),f&&(g.tld=f[3],g.domain=f[2]?f[2]+"."+f[3]:void 0,g.sub=f[1]||void 0)),g.port=g.port||("https"===g.protocol?"443":"80"),g.protocol=g.protocol||("443"===g.port?"https":"http")}return b in g?g[b]:"{}"===b?g:void 0}}(),"undefined"!=typeof jQuery&&jQuery.extend({url:function(a,b){return window.url(a,b)}}); -------------------------------------------------------------------------------- /WebContent/WEB-INF/sender.groovy: -------------------------------------------------------------------------------- 1 | import de.michaelkuerbis.presenter.servlets.SettingsServlet; 2 | import de.michaelkuerbis.presenter.utils.CastConnection; 3 | 4 | html.div { 5 | div( class:"container", id:"sender", style:"display:none;"){ 6 | div(class:"col-xs-12"){ 7 | div( class:"page-header"){ h1('data-i18n':"sender_title",'start cast') } 8 | } 9 | div(class:"col-xs-12"){ 10 | div(class:"form-horizontal"){ 11 | 12 | div( class:"form-group"){ 13 | label(class:"col-sm-2 control-label",'data-i18n':"sender_receiver", "Receiver:") 14 | div( class:"col-sm-10"){ 15 | select(id:"receiver-ip", class:"form-control"){ 16 | for(CastConnection con: SettingsServlet.getConnections()){ 17 | option(value:con.getIp(),"data-default":con.isDefault,con.getName()) 18 | } 19 | } 20 | } 21 | label(class:"col-sm-2 control-label",'data-i18n':"sender_all", "All (cast with default property):") 22 | div( class:"col-sm-10"){ 23 | input( type:"checkbox",class:"form-control", id:"all") 24 | } 25 | } 26 | 27 | div( class:"form-group"){ 28 | label(class:"col-sm-2 control-label",'data-i18n':"sender_url", "Url:") 29 | div( class:"col-sm-10"){ 30 | input( type:"text",class:"form-control", id:"url",'data-i18n-placeholder':"sender_url_placeholder", placeholder:"http://www....") 31 | } 32 | } 33 | div( class:"form-group"){ 34 | div( class:"col-sm-offset-2 col-sm-10"){ 35 | button( id:"launch", class:"btn btn-default",'data-i18n':"sender_launch","launch") 36 | } 37 | } 38 | } 39 | div(class:"form-horizontal"){ 40 | div( class:"form-group"){ 41 | label(class:"col-sm-2 control-label",'data-i18n':"sender_reload", "Reload page after sec.") 42 | div( class:"col-sm-10"){ 43 | input( type:"number",class:"form-control", id:"refresh", min:"0", value:"0") 44 | } 45 | label(class:"col-sm-offset-2 col-sm-10 pull-left",'data-i18n':"sender_zero_reload", "0 means no reload at all") 46 | } 47 | } 48 | } 49 | } 50 | script(src:"assets/js/sender.js") 51 | } -------------------------------------------------------------------------------- /WebContent/assets/lang/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "mrothenbuecher" 5 | ], 6 | "last-updated": "2017-02-03", 7 | "locale": "en" 8 | }, 9 | "index_overview":"Overview", 10 | "index_start_cast":"start cast", 11 | "index_cronjobs":"cronjobs", 12 | "index_lang":"lang", 13 | "index_media":"media", 14 | "main_title":"Overview", 15 | "main_no_casts": "There are no chromecasts defined.", 16 | "main_no_casts_add_one": "Add one.", 17 | "main_panel_status":"Status:", 18 | "main_panel_app":"App:", 19 | "main_panel_remove":"Remove this chromecast?", 20 | "main_panel_remove_yes":"yes", 21 | "main_panel_remove_no":"no", 22 | "main_add_title":"Add Chromecast", 23 | "main_add_name":"Name", 24 | "main_add_name_placeholder":"Chromecast #1", 25 | "main_add_ip":"IP", 26 | "main_add_ip_placeholder":"127.0.0.1", 27 | "main_add_button":"add Chromecast", 28 | "main_discover_title":"discovered Chromecasts", 29 | "main_discover_add":"add", 30 | "sender_title":"start cast", 31 | "sender_receiver":"Receiver:", 32 | "sender_all":"All (cast with default property):", 33 | "sender_url":"URL:", 34 | "sender_url_placeholder":"http://www....", 35 | "sender_launch":"launch", 36 | "sender_reload":"Reload page after sec.", 37 | "sender_zero_reload":"0 means no reload at all", 38 | "cron_title":"add cronjobs", 39 | "cron_desc":"Description:", 40 | "cron_desc_placeholder":"description", 41 | "cron_receiver":"Receiver:", 42 | "cron_url":"URL:", 43 | "cron_url_placeholder":"http://www....", 44 | "cron_pattern":"Execution pattern:", 45 | "cron_pattern_placeholder":"* * * * *", 46 | "cron_add":"add", 47 | "cron_reload":"Reload page after sec.", 48 | "cron_zero_reload":"0 means no reload at all", 49 | "cron_current_jobs":"current cronjobs", 50 | "media_title":"media", 51 | "media_browse":"Browse File", 52 | "media_upload":"upload", 53 | "media_label":"Media", 54 | "media_refresh":"refresh list", 55 | "media_delete":"delete media", 56 | "media_upload_success":"upload successfull", 57 | "media_delete_success":"delete successfull" 58 | } 59 | -------------------------------------------------------------------------------- /WebContent/assets/js/media.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | $(document).ready(function() { 3 | $('#media-upload').click(function(e){ 4 | if($('#media-file')[0].files && $('#media-file')[0].files.length > 0){ 5 | var data; 6 | 7 | data = new FormData(); 8 | data.append( 'file', $('#media-file')[0].files[0] ); 9 | 10 | $.ajax({ 11 | url: 'media', 12 | data: data, 13 | cache: false, 14 | contentType: false, 15 | processData: false, 16 | type: 'POST', 17 | success: function(data){ 18 | toastr['info']($.i18n("media_upload_success")); 19 | } 20 | }); 21 | 22 | e.preventDefault(); 23 | } 24 | }); 25 | $('#media-refresh').click(function(e){ 26 | $('.media-list').html(""); 27 | $('.media-list').append(""); 28 | $.ajax({ 29 | type: "GET", 30 | url: "rest/media/list" 31 | }).done(function(data, textStatus, jqXHR) { 32 | var obj = JSON.parse(data); 33 | $.each(obj, function(i, elem) { 34 | $('.media-list').append(""); 35 | }); 36 | }).fail(function(jqXHR, textStatus, errorThrown) { 37 | // TODO error 38 | toastr['error']("error"); 39 | }).always(function() { 40 | 41 | }); 42 | }); 43 | $('#media-delete').click(function(e){ 44 | var option = $('#media-del-list option:selected'); 45 | if(option && option.text()){ 46 | $.ajax({ 47 | type: "DELETE", 48 | url: "rest/media/"+encodeURI(option.text()) 49 | }).done(function(data, textStatus, jqXHR) { 50 | //TODO success 51 | toastr['info']($.i18n("media_delete_success")); 52 | }).fail(function(jqXHR, textStatus, errorThrown) { 53 | // TODO error 54 | toastr['error']("error"); 55 | }).always(function() { 56 | $('#media-refresh').trigger("click"); 57 | }); 58 | } 59 | }); 60 | }); 61 | })(); 62 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [master] 9 | schedule: 10 | - cron: '0 8 * * 6' 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | # Override automatic language detection by changing the below list 21 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 22 | language: ['javascript', 'java'] 23 | # Learn more... 24 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 25 | 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v2 29 | with: 30 | # We must fetch at least the immediate parents so that if this is 31 | # a pull request then we can checkout the head. 32 | fetch-depth: 2 33 | 34 | # If this run was triggered by a pull request event, then checkout 35 | # the head of the pull request instead of the merge commit. 36 | - run: git checkout HEAD^2 37 | if: ${{ github.event_name == 'pull_request' }} 38 | 39 | # Initializes the CodeQL tools for scanning. 40 | - name: Initialize CodeQL 41 | uses: github/codeql-action/init@v1 42 | with: 43 | languages: ${{ matrix.language }} 44 | 45 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 46 | # If this step fails, then you should remove it and run the build manually (see below) 47 | - name: Autobuild 48 | uses: github/codeql-action/autobuild@v1 49 | 50 | # ℹ️ Command-line programs to run using the OS shell. 51 | # 📚 https://git.io/JvXDl 52 | 53 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 54 | # and modify them (or add more) to build your code if your project 55 | # uses a compiled language 56 | 57 | #- run: | 58 | # make bootstrap 59 | # make release 60 | 61 | - name: Perform CodeQL Analysis 62 | uses: github/codeql-action/analyze@v1 63 | -------------------------------------------------------------------------------- /WebContent/assets/lang/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "mrothenbuecher" 5 | ], 6 | "last-updated": "2017-02-03", 7 | "locale": "de" 8 | }, 9 | "index_overview":"Übersicht", 10 | "index_start_cast":"starte Casts", 11 | "index_cronjobs":"Cronjobs", 12 | "index_lang":"Sprache", 13 | "index_media":"Dateien", 14 | "main_title":"Übersicht", 15 | "main_no_casts": "Sie haben noch keine Casts definiert. ", 16 | "main_no_casts_add_one": "Einen hinzufügen.", 17 | "main_panel_status":"Status:", 18 | "main_panel_app":"App:", 19 | "main_panel_remove":"Wollen Sie diesen Chromecast wirklich entfernen?", 20 | "main_panel_remove_yes":"ja", 21 | "main_panel_remove_no":"nein", 22 | "main_add_title":"Chromecast hinzufügen", 23 | "main_add_name":"Name", 24 | "main_add_name_placeholder":"Chromecast #1", 25 | "main_add_ip":"IP", 26 | "main_add_ip_placeholder":"127.0.0.1", 27 | "main_add_button":"Chromecast hinzufügen", 28 | "main_discover_title":"entdeckte Chromecasts", 29 | "main_discover_add":"hinzufügen", 30 | "sender_title":"start Casts", 31 | "sender_receiver":"Chromecast:", 32 | "sender_all":"Alle (Casts mit default eigenschaft):", 33 | "sender_url":"URL:", 34 | "sender_url_placeholder":"http://www....", 35 | "sender_launch":"starten", 36 | "sender_reload":"Seite neuladen nach X sec.", 37 | "sender_zero_reload":"0 bedeutet kein neuladen", 38 | "cron_title":"Cronjob hinzufügen", 39 | "cron_desc":"Beschreibung:", 40 | "cron_desc_placeholder":"Beschreibung", 41 | "cron_receiver":"Chromecast:", 42 | "cron_url":"URL:", 43 | "cron_url_placeholder":"http://www....", 44 | "cron_pattern":"Job Pattern:", 45 | "cron_pattern_placeholder":"* * * * *", 46 | "cron_add":"hinzufügen", 47 | "cron_reload":"Seite neuladen nach X sec.", 48 | "cron_zero_reload":"0 bedeutet kein neuladen", 49 | "cron_current_jobs":"Aktuelle Cronjobs", 50 | "media_title":"Medien", 51 | "media_browse":"Datei auswählen", 52 | "media_upload":"hochladen", 53 | "media_label":"Datei", 54 | "media_refresh":"Liste aktualisieren", 55 | "media_delete":"Datei löschen", 56 | "media_upload_success":"erfolgreich hochgeladen", 57 | "media_delete_success":"erfolgreich gelöscht" 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chromecast-Kiosk 2 | A system to create a digital signage system with google chromecasts. 3 | 4 | ![screenshot](https://raw.githubusercontent.com/mrothenbuecher/Chromecast-Kiosk/master/img/overview-screenshot.png "screenshot") 5 | 6 | ### release state 7 | beta 8 | 9 | ## requirements 10 | * fixed ip address for each chromecast 11 | 12 | ## recommendations 13 | * Use ethernet instead of wlan, see [DIY Ethernet Adapter](https://productforums.google.com/forum/#!topic/chromecast/xo_NDh5CZA8) or the offical adapter. 14 | * Set a static ip address to the chromecast. 15 | * Use subneting to protect your casts from unwanted access. 16 | 17 | ## benefits 18 | * see what your casts are doing 19 | * discover the chromecast in your network 20 | * set the website which shall be displayed on your chromecast based on [chromecast-dashboard](https://github.com/boombatower/chromecast-dashboard) 21 | * you can manage multiple chromecast in one browser 22 | * cronjobs so you could plan what your casts should do 23 | * multi lang support 24 | 25 | ## installation 26 | All you need todo is deploy the *.war - File in the webapps directory of a Tomcat 8.0. 27 | 28 | ## upgrading 29 | 1. Copy the `*.json` files from WEB-INF/config as backup. 30 | 2. use the Tomcat manager to undeploy the current webapp and deploy the new one 31 | 3. copy the json files back to WEB-INF/config 32 | 33 | ## start casting 34 | 1. add a ChromeCast, by IP and Name 35 | 2. set target chromecast, the url and the refresh rate of the website you want to be displayed (for internal sides use the IP of the server not his name) 36 | 3. see if they are still alive an playing 37 | 38 | ## tips for using with google slides 39 | Example URL `https://docs.google.com/presentation/.../embed?start=true&loop=true&delayms=10000&rm=minimal` 40 | * start=true - start after loading 41 | * loop=true - loop the presentation 42 | * delayms=10000 - wait 10.000 ms between slides 43 | * rm=minimal - show minimal interface (don't show slidenumber etc.) 44 | 45 | 46 | ## Thanks to 47 | * [DIY Ethernet Adapter](https://productforums.google.com/forum/#!topic/chromecast/xo_NDh5CZA8) 48 | * ["C" is for Chromecast: hacking digital signage](http://labs.cooperhewitt.org/2013/c-is-for-chromecast-hacking-digital-signage/) 49 | * [ChromeCast Java API v2](https://github.com/vitalidze/chromecast-java-api-v2) 50 | * [chromecast-dashboard](https://github.com/boombatower/chromecast-dashboard) 51 | -------------------------------------------------------------------------------- /WebContent/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | kiosk 7 | 8 | index.groovy 9 | 10 | 11 | Groovy 12 | groovy.servlet.GroovyServlet 13 | 1 14 | 15 | 16 | Groovy 17 | *.groovy 18 | 19 | 20 | RESTServlet 21 | org.glassfish.jersey.servlet.ServletContainer 22 | 24 | 25 | javax.ws.rs.Application 26 | de.michaelkuerbis.presenter.rest.PresenterApplication 27 | 28 | 5 29 | 30 | 31 | RESTServlet 32 | /rest/* 33 | 34 | 35 | SettingsServlet 36 | de.michaelkuerbis.presenter.servlets.SettingsServlet 37 | 3 38 | 39 | 40 | CronServlet 41 | de.michaelkuerbis.presenter.servlets.CronServlet 42 | 3 43 | 44 | 45 | MediaServlet 46 | de.michaelkuerbis.presenter.servlets.MediaServlet 47 | 3 48 | 49 | 50 | MediaServlet 51 | /media 52 | 53 | 54 | DiscoveryServlet 55 | de.michaelkuerbis.presenter.servlets.ChromeCastDiscoverServlet 56 | 4 57 | 58 | -------------------------------------------------------------------------------- /receiver/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Main JavaScript for handling Chromecast interactions. 3 | */ 4 | 5 | window.onload = function() { 6 | cast.receiver.logger.setLevelValue(0); 7 | window.castReceiverManager = cast.receiver.CastReceiverManager.getInstance(); 8 | console.log('Starting Receiver Manager'); 9 | 10 | castReceiverManager.onReady = function(event) { 11 | console.log('Received Ready event: ' + JSON.stringify(event.data)); 12 | window.castReceiverManager.setApplicationState('chromecast-kiosk-web is ready...'); 13 | }; 14 | 15 | castReceiverManager.onSenderConnected = function(event) { 16 | console.log('Received Sender Connected event: ' + event.senderId); 17 | }; 18 | 19 | castReceiverManager.onSenderDisconnected = function(event) { 20 | console.log('Received Sender Disconnected event: ' + event.senderId); 21 | }; 22 | 23 | window.messageBus = 24 | window.castReceiverManager.getCastMessageBus( 25 | 'urn:x-cast:de.michaelkuerbis.kiosk', cast.receiver.CastMessageBus.MessageType.JSON); 26 | 27 | var current = {}; 28 | 29 | window.messageBus.onMessage = function(event) { 30 | if (event.data['type'] == 'load') { 31 | current.url = event.data['url']; 32 | current.refresh = event.data['refresh']; 33 | $('#dashboard').attr('src', event.data['url']); 34 | if (event.data['refresh'] > 0) { 35 | $('#dashboard').attr('data-refresh', event.data['refresh'] * 1000); 36 | setTimeout(reloadDashboard, $('#dashboard').attr('data-refresh')); 37 | } 38 | else { 39 | $('#dashboard').attr('data-refresh', 0); 40 | } 41 | } 42 | // return Information 43 | if(event.data.status){ 44 | current.requestId = event.data.requestId; 45 | current.url = current.url ? current.url:""; 46 | current.refresh = current.refresh ? current.refresh:0; 47 | window.messageBus.send(event.senderId, current); 48 | } 49 | } 50 | 51 | // Initialize the CastReceiverManager with an application status message. 52 | window.castReceiverManager.start({statusText: 'Application is starting'}); 53 | console.log('Receiver Manager started'); 54 | 55 | function reloadDashboard() { 56 | $('#dashboard').attr('src', $('#dashboard').attr('src')); 57 | if ($('#dashboard').attr('data-refresh')) { 58 | setTimeout(reloadDashboard, $('#dashboard').attr('data-refresh')); 59 | } 60 | } 61 | 62 | $('#dashboard').load(function() { 63 | $('#loading').hide(); 64 | console.log('Loading animation hidden.'); 65 | }); 66 | }; 67 | -------------------------------------------------------------------------------- /src/de/michaelkuerbis/presenter/rest/StatusREST.java: -------------------------------------------------------------------------------- 1 | package de.michaelkuerbis.presenter.rest; 2 | 3 | import java.io.IOException; 4 | import java.net.ConnectException; 5 | import java.security.GeneralSecurityException; 6 | 7 | import javax.ws.rs.GET; 8 | import javax.ws.rs.Path; 9 | import javax.ws.rs.PathParam; 10 | import javax.ws.rs.core.Response; 11 | 12 | import org.apache.logging.log4j.LogManager; 13 | import org.apache.logging.log4j.Logger; 14 | import org.json.simple.JSONObject; 15 | 16 | import de.michaelkuerbis.presenter.utils.KioskStatusRequest; 17 | import de.michaelkuerbis.presenter.utils.KioskStatusResponse; 18 | import su.litvak.chromecast.api.v2.Application; 19 | import su.litvak.chromecast.api.v2.ChromeCast; 20 | 21 | @Path("/status") 22 | public class StatusREST { 23 | 24 | private final static Logger log = LogManager.getLogger(StatusREST.class); 25 | 26 | @GET 27 | @Path("/get/{ip}") 28 | public Response getStatus(@PathParam("ip") String ip) { 29 | final JSONObject status = new JSONObject(); 30 | log.debug("request status for ip: "+ip); 31 | status.put("ip", ip); 32 | ChromeCast cast = new ChromeCast(ip); 33 | try { 34 | cast.connect(); 35 | status.put("status", "online"); 36 | status.put("application", cast.getRunningApp().name); 37 | log.debug("cast "+ip+" connected application: "+cast.getRunningApp().name); 38 | if(cast.getRunningApp().name.equals("chromecast-kiosk-web")){ 39 | Application cation = cast.getRunningApp(); 40 | KioskStatusResponse resp = cast.send("urn:x-cast:de.michaelkuerbis.kiosk",new KioskStatusRequest(), KioskStatusResponse.class); 41 | 42 | if(resp!=null){ 43 | status.put("url", resp.getUrl()); 44 | status.put("refresh", resp.getRefresh()); 45 | status.put("fetched", true); 46 | }else{ 47 | status.put("fetched", false); 48 | } 49 | 50 | 51 | } 52 | cast.disconnect(); 53 | } catch (IOException e) { 54 | status.put("status", "offline"); 55 | if (e instanceof ConnectException) { 56 | status.put("reason", "3"); 57 | log.error("cast "+ip+" not connected reason: "+e.getMessage()); 58 | } else{ 59 | status.put("reason", "1"); 60 | log.error("cast "+ip+" not connected reason: "+e.getMessage()); 61 | } 62 | e.printStackTrace(); 63 | } catch (GeneralSecurityException e) { 64 | status.put("status", "offline"); 65 | status.put("reason", "2"); 66 | log.error("cast "+ip+" not connected reason: "+e.getMessage()); 67 | e.printStackTrace(); 68 | } 69 | return Response.ok(status.toJSONString()).build(); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /WebContent/WEB-INF/cron.groovy: -------------------------------------------------------------------------------- 1 | import de.michaelkuerbis.presenter.servlets.SettingsServlet; 2 | import de.michaelkuerbis.presenter.utils.CastConnection; 3 | 4 | html.div { 5 | div( class:"container", id:"cron", style:"display:none;"){ 6 | div(class:"col-xs-12"){ 7 | div( class:"page-header"){ h1('data-i18n':"cron_title",'add cronjobs') } 8 | } 9 | div(class:"col-xs-12"){ 10 | div(class:"form-horizontal"){ 11 | 12 | div( class:"form-group"){ 13 | label(class:"col-sm-2 control-label",'data-i18n':"cron_desc", "Description:") 14 | div( class:"col-sm-10"){ 15 | input( type:"text",class:"form-control", id:"name",'data-i18n-placeholder':"cron_desc_placeholder", placeholder:"description") 16 | } 17 | } 18 | 19 | div( class:"form-group"){ 20 | label(class:"col-sm-2 control-label",'data-i18n':"cron_receiver", "Receiver:") 21 | div( class:"col-sm-10"){ 22 | select(id:"receiver-ip", class:"form-control"){ 23 | for(CastConnection con: SettingsServlet.getConnections()){ 24 | option(value:con.getIp(),"data-default":con.isDefault,con.getName()) 25 | } 26 | } 27 | } 28 | } 29 | 30 | div( class:"form-group"){ 31 | label(class:"col-sm-2 control-label",'data-i18n':"cron_pattern", "Execution pattern:") 32 | div( class:"col-sm-10"){ 33 | input( type:"text",class:"form-control", id:"pattern",'data-i18n-placeholder':"cron_pattern_placeholder", placeholder:"* * * * *") 34 | } 35 | } 36 | 37 | div( class:"form-group"){ 38 | label(class:"col-sm-2 control-label",'data-i18n':"cron_url", "Url:") 39 | div( class:"col-sm-10"){ 40 | input( type:"text",class:"form-control", id:"url",'data-i18n-placeholder':"cron_url_placeholder", placeholder:"http://www....") 41 | } 42 | } 43 | div( class:"form-group"){ 44 | div( class:"col-sm-offset-2 col-sm-10"){ 45 | button( id:"add", class:"btn btn-default",'data-i18n':"cron_add","add") 46 | } 47 | } 48 | } 49 | div(class:"form-horizontal"){ 50 | div( class:"form-group"){ 51 | label(class:"col-sm-2 control-label",'data-i18n':"cron_reload", "Reload page after sec.") 52 | div( class:"col-sm-10"){ 53 | input( type:"number",class:"form-control", id:"refresh", min:"0", value:"0") 54 | } 55 | label(class:"col-sm-offset-2 col-sm-10 pull-left",'data-i18n':"cron_zero_reload", "0 means no reload at all") 56 | } 57 | } 58 | } 59 | div(class:"col-xs-12"){ 60 | div( class:"page-header"){ 61 | h1(class:"pull-left",'data-i18n':"cron_current_jobs",'current cronjobs') 62 | div(style:"margin-left: 10px;padding-top: 20px;"){ 63 | button(class:"btn btn-default", id:"refreshjobs", style:"height: 34px;"){ 64 | span(class:"glyphicon glyphicon-refresh", " ") 65 | } 66 | } 67 | } 68 | } 69 | div(id:"currentjobs",class:"col-xs-12"){ 70 | } 71 | } 72 | script(src:"assets/js/cron.js") 73 | } -------------------------------------------------------------------------------- /src/de/michaelkuerbis/presenter/rest/StartREST.java: -------------------------------------------------------------------------------- 1 | package de.michaelkuerbis.presenter.rest; 2 | 3 | import java.io.IOException; 4 | import java.security.GeneralSecurityException; 5 | 6 | import javax.ws.rs.Consumes; 7 | import javax.ws.rs.FormParam; 8 | import javax.ws.rs.POST; 9 | import javax.ws.rs.Path; 10 | import javax.ws.rs.PathParam; 11 | import javax.ws.rs.QueryParam; 12 | import javax.ws.rs.core.MediaType; 13 | import javax.ws.rs.core.Response; 14 | 15 | import su.litvak.chromecast.api.v2.Application; 16 | import su.litvak.chromecast.api.v2.ChromeCast; 17 | import su.litvak.chromecast.api.v2.MediaStatus; 18 | import su.litvak.chromecast.api.v2.Status; 19 | import de.michaelkuerbis.presenter.utils.KioskUpdateRequest; 20 | import de.michaelkuerbis.presenter.utils.Settings; 21 | 22 | @Path("/start") 23 | public class StartREST { 24 | 25 | @POST 26 | //@Consumes(MediaType.APPLICATION_FORM_URLENCODED) 27 | @Path("/{ip}") 28 | public Response startCast(@PathParam("ip") String ip, 29 | @QueryParam("url") String url, @QueryParam("reload") int reload) { 30 | 31 | ChromeCast chromecast = new ChromeCast(ip); 32 | try { 33 | chromecast.connect(); 34 | if (chromecast.isConnected()) { 35 | Status status = chromecast.getStatus(); 36 | if (chromecast.isAppAvailable(Settings.appId)) { 37 | Application app = chromecast.launchApp(Settings.appId); 38 | chromecast.send("urn:x-cast:de.michaelkuerbis.kiosk", 39 | new KioskUpdateRequest(url, reload)); 40 | chromecast.disconnect(); 41 | return Response.ok().build(); 42 | } 43 | else { 44 | chromecast.disconnect(); 45 | return Response 46 | .status(Response.Status.INTERNAL_SERVER_ERROR) 47 | .entity("app is not available") 48 | .build(); 49 | } 50 | } else { 51 | return Response 52 | .status(Response.Status.INTERNAL_SERVER_ERROR) 53 | .entity("chromecast did not react / ip of chromecast may wrong") 54 | .build(); 55 | } 56 | } catch (IOException | GeneralSecurityException e) { 57 | // TODO Auto-generated catch block 58 | e.printStackTrace(); 59 | return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build(); 60 | } 61 | 62 | 63 | } 64 | 65 | @POST 66 | @Path("/stream/{ip}") 67 | public Response startStream(@PathParam("ip") String ip, @FormParam("file") String url) { 68 | 69 | ChromeCast chromecast = new ChromeCast(ip); 70 | try { 71 | chromecast.connect(); 72 | if (chromecast.isConnected()) { 73 | MediaStatus status = chromecast.load(url); 74 | return Response.ok().build(); 75 | } else { 76 | return Response 77 | .status(Response.Status.INTERNAL_SERVER_ERROR) 78 | .entity("chromecast did not react / ip of chromecast may wrong") 79 | .build(); 80 | } 81 | } catch (IOException | GeneralSecurityException e) { 82 | // TODO Auto-generated catch block 83 | e.printStackTrace(); 84 | return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build(); 85 | } 86 | 87 | 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/de/michaelkuerbis/presenter/servlets/SettingsServlet.java: -------------------------------------------------------------------------------- 1 | package de.michaelkuerbis.presenter.servlets; 2 | 3 | import java.io.FileWriter; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | import java.util.Vector; 7 | 8 | import javax.servlet.ServletContext; 9 | import javax.servlet.ServletException; 10 | import javax.servlet.http.HttpServlet; 11 | 12 | import org.apache.logging.log4j.LogManager; 13 | import org.apache.logging.log4j.Logger; 14 | import org.json.simple.JSONArray; 15 | import org.json.simple.JSONObject; 16 | import org.json.simple.parser.JSONParser; 17 | import org.json.simple.parser.ParseException; 18 | 19 | import de.michaelkuerbis.presenter.utils.CastConnection; 20 | 21 | public class SettingsServlet extends HttpServlet { 22 | 23 | private final static Logger log = LogManager.getLogger(SettingsServlet.class); 24 | 25 | /** 26 | * 27 | */ 28 | private static final long serialVersionUID = -9121110689011730052L; 29 | 30 | private static JSONObject settings; 31 | 32 | private static Vector connection; 33 | 34 | @Override 35 | public void init() throws ServletException { 36 | reloadSettings(this.getServletContext()); 37 | } 38 | 39 | public static void reloadSettings(ServletContext context) { 40 | connection = new Vector(); 41 | // TODO no config file ... 42 | JSONParser parser = new JSONParser(); 43 | 44 | try { 45 | settings = (JSONObject) parser 46 | .parse(new InputStreamReader(context.getResourceAsStream("/WEB-INF/config/settings.json"))); 47 | for (Object obj : ((JSONArray) settings.get("castconnections")).toArray()) { 48 | JSONObject con = (JSONObject) obj; 49 | if (con.get("isDefault") == null) { 50 | connection.add(new CastConnection((String) con.get("ip"), (String) con.get("name"))); 51 | } else { 52 | connection.add(new CastConnection((String) con.get("ip"), (String) con.get("name"), 53 | (boolean) con.get("isDefault"))); 54 | } 55 | } 56 | } catch (IOException e) { 57 | log.error(e); 58 | } catch (ParseException e) { 59 | log.error(e); 60 | } 61 | 62 | } 63 | 64 | public static boolean saveSettings(ServletContext context) { 65 | JSONObject obj = new JSONObject(); 66 | JSONArray castconnections = new JSONArray(); 67 | for (CastConnection con : connection) { 68 | JSONObject foo = new JSONObject(); 69 | foo.put("ip", con.getIp()); 70 | foo.put("name", con.getName()); 71 | foo.put("isDefault", con.isDefault()); 72 | castconnections.add(foo); 73 | } 74 | obj.put("castconnections", castconnections); 75 | try (FileWriter file = new FileWriter(context.getResource("/WEB-INF/config/settings.json").getFile())) { 76 | file.write(obj.toJSONString()); 77 | file.flush(); 78 | reloadSettings(context); 79 | return true; 80 | } catch (IOException e) { 81 | log.error(e); 82 | return false; 83 | } 84 | } 85 | 86 | public static Vector getConnections() { 87 | return connection; 88 | } 89 | 90 | public static void setConnections(Vector con) { 91 | connection = con; 92 | } 93 | 94 | public static JSONObject getConfig() { 95 | return settings; 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/de/michaelkuerbis/presenter/servlets/CronServlet.java: -------------------------------------------------------------------------------- 1 | package de.michaelkuerbis.presenter.servlets; 2 | 3 | import java.io.FileWriter; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | import java.util.Vector; 7 | 8 | import javax.servlet.ServletContext; 9 | import javax.servlet.ServletException; 10 | import javax.servlet.http.HttpServlet; 11 | 12 | import org.apache.logging.log4j.LogManager; 13 | import org.apache.logging.log4j.Logger; 14 | import org.json.simple.JSONArray; 15 | import org.json.simple.JSONObject; 16 | import org.json.simple.parser.JSONParser; 17 | import org.json.simple.parser.ParseException; 18 | 19 | import de.michaelkuerbis.presenter.utils.CronJob; 20 | 21 | public class CronServlet extends HttpServlet { 22 | 23 | /** 24 | * 25 | */ 26 | private static final long serialVersionUID = -9121110689011730052L; 27 | 28 | private final static Logger log = LogManager.getLogger(CronServlet.class); 29 | 30 | private static JSONObject settings; 31 | private static Vector jobs; 32 | 33 | @Override 34 | public void init() throws ServletException { 35 | reloadSettings(this.getServletContext()); 36 | for (CronJob job : jobs) { 37 | job.start(); 38 | } 39 | log.trace(jobs); 40 | } 41 | 42 | public static void reloadSettings(ServletContext context) { 43 | jobs = new Vector(); 44 | // TODO no config file ... 45 | JSONParser parser = new JSONParser(); 46 | 47 | // parser.par 48 | try { 49 | settings = (JSONObject) parser 50 | .parse(new InputStreamReader(context.getResourceAsStream("/WEB-INF/config/cron.json"))); 51 | for (Object obj : ((JSONArray) settings.get("cronjobs")).toArray()) { 52 | JSONObject con = (JSONObject) obj; 53 | CronJob job = new CronJob((String) con.get("target"), (String) con.get("url"), 54 | (String) con.get("pattern"), (String) con.get("name"), (Long) con.get("reload")); 55 | jobs.add(job); 56 | } 57 | } catch (IOException e) { 58 | log.error(e); 59 | } catch (ParseException e) { 60 | log.error(e); 61 | } 62 | 63 | } 64 | 65 | public static boolean saveSettings(ServletContext context) { 66 | JSONObject obj = new JSONObject(); 67 | JSONArray cronjobs = new JSONArray(); 68 | for (CronJob con : jobs) { 69 | JSONObject foo = new JSONObject(); 70 | foo.put("target", con.getTarget()); 71 | foo.put("name", con.getName()); 72 | foo.put("pattern", con.getPattern()); 73 | foo.put("reload", con.getReload()); 74 | foo.put("url", con.getUrl()); 75 | cronjobs.add(foo); 76 | } 77 | obj.put("cronjobs", cronjobs); 78 | 79 | // new FileWriter() 80 | try (FileWriter file = new FileWriter(context.getResource("/WEB-INF/config/cron.json").getFile())) { 81 | file.write(obj.toJSONString()); 82 | file.flush(); 83 | reloadSettings(context); 84 | return true; 85 | } catch (IOException e) { 86 | log.error(e); 87 | return false; 88 | } 89 | } 90 | 91 | public static Vector getCronJobs() { 92 | return jobs; 93 | } 94 | 95 | public static void setCronJobs(Vector job) { 96 | jobs = job; 97 | } 98 | 99 | public static JSONObject getConfig() { 100 | return settings; 101 | } 102 | 103 | } -------------------------------------------------------------------------------- /WebContent/index.groovy: -------------------------------------------------------------------------------- 1 | 2 | html.html('lang':"en"){ 3 | head { 4 | meta('http-equiv':"content-type", content:"text/html; charset=UTF-8") 5 | meta(charset:"UTF-8") 6 | title('Kiosk System') 7 | meta( name:"viewport", content:"width=device-width, initial-scale=1, maximum-scale=1") 8 | link( href:"assets/css/bootstrap.min.css", rel:"stylesheet") 9 | link( href:"assets/css/toastr.min.css", rel:"stylesheet") 10 | link( href:"assets/css/styles.css", rel:"stylesheet") 11 | script(src:"//ajax.googleapis.com/ajax/libs/jquery/2.0.2/jquery.min.js") 12 | } 13 | body{ 14 | div( class:"navbar navbar-default navbar-static-top"){ 15 | div( class:"container"){ 16 | div( class:"navbar-header"){ 17 | button( type:"button", class:"navbar-toggle", 'data-toggle':"collapse", 'data-target':".navbar-collapse"){ 18 | span( class:"icon-bar") 19 | span( class:"icon-bar") 20 | span( class:"icon-bar") 21 | } 22 | a (class:"navbar-brand", href:"#", 'Kiosk System') 23 | } 24 | div( class:"collapse navbar-collapse"){ 25 | ul( class:"nav navbar-nav"){ 26 | li(class:"active"){ 27 | a(href:"#overview",'data-i18n':"index_overview", 'Overview', id:"mainbutton") 28 | } 29 | li { 30 | a(href:"#start",'data-i18n':"index_start_cast", 'start cast', id:"senderbutton") 31 | } 32 | li { 33 | a(href:"#cron",'data-i18n':"index_cronjobs", 'cronjobs', id:"cronbutton") 34 | } 35 | li { 36 | a(href:"#media",'data-i18n':"index_media", 'media', id:"mediabutton") 37 | } 38 | } 39 | ul(class:"nav navbar-nav navbar-right"){ 40 | 41 | li( class:"dropdown"){ 42 | a( href:"#", class:"dropdown-toggle", 'data-toggle':"dropdown", role:"button", 'aria-haspopup':"true", 'aria-expanded':"false"){ 43 | div(class:"pull-left", 'data-i18n':"index_lang", 'lang') 44 | span( class:"caret") 45 | } 46 | File langs = new File(request.getServletContext().getRealPath("/assets/lang")); 47 | ul(class:"dropdown-menu"){ 48 | //TODO based on lang files 49 | for(File f: langs.listFiles()){ 50 | if(f.isFile() && f.getName().toLowerCase().matches(".*\\.json")){ 51 | String lang = f.getName().toLowerCase().replace(".json",""); 52 | li(class:"radio"){ 53 | label(lang) 54 | input(type:"radio", name:"lang",value:lang) 55 | } 56 | } 57 | } 58 | } 59 | } 60 | } 61 | } 62 | } 63 | } 64 | 65 | include('/WEB-INF/main.groovy') 66 | include('/WEB-INF/sender.groovy') 67 | include('/WEB-INF/cron.groovy') 68 | include('/WEB-INF/media.groovy') 69 | //include('/WEB-INF/help.groovy') 70 | 71 | script(src:"assets/js/bootstrap.min.js") 72 | script(src:"assets/js/spin.min.js") 73 | script(src:"assets/js/toastr.min.js") 74 | 75 | // history && url 76 | script(src:"assets/js/jquery.history.js") 77 | script(src:"assets/js/url.min.js") 78 | 79 | // i18n support 80 | script(src:"assets/js/jquery.i18n.js") 81 | script(src:"assets/js/jquery.i18n.messagestore.js") 82 | script(src:"assets/js/jquery.i18n.fallbacks.js") 83 | script(src:"assets/js/jquery.i18n.language.js") 84 | script(src:"assets/js/jquery.i18n.parser.js") 85 | script(src:"assets/js/jquery.i18n.emitter.js") 86 | script(src:"assets/js/jquery.i18n.emitter.bidi.js") 87 | 88 | // custom scripts 89 | script(src:"assets/js/main.js") 90 | script(src:"assets/js/lang.js") 91 | } 92 | } -------------------------------------------------------------------------------- /src/de/michaelkuerbis/presenter/utils/CronJob.java: -------------------------------------------------------------------------------- 1 | package de.michaelkuerbis.presenter.utils; 2 | 3 | import org.apache.logging.log4j.LogManager; 4 | import org.apache.logging.log4j.Logger; 5 | 6 | import it.sauronsoftware.cron4j.Scheduler; 7 | import it.sauronsoftware.cron4j.SchedulerListener; 8 | import it.sauronsoftware.cron4j.Task; 9 | import it.sauronsoftware.cron4j.TaskExecutionContext; 10 | import it.sauronsoftware.cron4j.TaskExecutor; 11 | import su.litvak.chromecast.api.v2.Application; 12 | import su.litvak.chromecast.api.v2.ChromeCast; 13 | import su.litvak.chromecast.api.v2.Status; 14 | 15 | public class CronJob extends Task { 16 | 17 | private final static Logger log = LogManager.getLogger(CronJob.class); 18 | 19 | private Scheduler scheduler = new Scheduler(); 20 | 21 | private String url, target; 22 | private String pattern, name; 23 | 24 | //private Scheduler scheduler; 25 | 26 | private long reload; 27 | 28 | public CronJob(String target, String url, String pattern, String nam, 29 | long reload) { 30 | this.url = url; 31 | this.target = target; 32 | this.pattern = pattern; 33 | this.name = nam; 34 | this.reload = reload; 35 | //this.scheduler = new Scheduler(); 36 | this.scheduler.addSchedulerListener(new SchedulerListener() { 37 | 38 | @Override 39 | public void taskFailed(TaskExecutor arg0, Throwable arg1) { 40 | log.warn("task " + name + " failed reason: " 41 | + arg1.getMessage()); 42 | } 43 | 44 | @Override 45 | public void taskLaunching(TaskExecutor arg0) { 46 | log.info("task " + name + " started"); 47 | } 48 | 49 | @Override 50 | public void taskSucceeded(TaskExecutor arg0) { 51 | log.info("task " + name + " succeeded"); 52 | } 53 | 54 | }); 55 | } 56 | 57 | public void start(){ 58 | scheduler.schedule(pattern, this); 59 | scheduler.start(); 60 | } 61 | 62 | public void stop() { 63 | scheduler.stop(); 64 | } 65 | 66 | public String getUrl() { 67 | return url; 68 | } 69 | 70 | public String getTarget() { 71 | return target; 72 | } 73 | 74 | public String getPattern() { 75 | return pattern; 76 | } 77 | 78 | public long getReload() { 79 | return reload; 80 | } 81 | 82 | public void setUrl(String url) { 83 | this.url = url; 84 | } 85 | 86 | public void setTarget(String target) { 87 | this.target = target; 88 | } 89 | 90 | public void setPattern(String pattern) { 91 | this.pattern = pattern; 92 | } 93 | 94 | public void setReload(int reload) { 95 | this.reload = reload; 96 | } 97 | 98 | public String getName() { 99 | return name; 100 | } 101 | 102 | public void setName(String name) { 103 | this.name = name; 104 | } 105 | 106 | public Scheduler getScheduler(){ 107 | return scheduler; 108 | } 109 | 110 | @Override 111 | public void execute(TaskExecutionContext arg0) throws RuntimeException { 112 | ChromeCast chromecast = new ChromeCast(target); 113 | try { 114 | chromecast.connect(); 115 | if (chromecast.isConnected()) { 116 | Status status = chromecast.getStatus(); 117 | if (chromecast.isAppAvailable(Settings.appId)) { 118 | Application app = chromecast.launchApp(Settings.appId); 119 | chromecast.send("urn:x-cast:de.michaelkuerbis.kiosk", 120 | new KioskUpdateRequest(url, reload)); 121 | return; 122 | } else 123 | throw new Exception("app is not available"); 124 | } else { 125 | throw new Exception( 126 | "chromecast did not react / ip of chromecast may wrong"); 127 | } 128 | } catch (Exception e) { 129 | log.error(e); 130 | } 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /WebContent/assets/js/cron.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | $(document).ready(function() { 3 | 4 | $('#add').click(function() { 5 | if (!$('#cron').find('#url').val()) { 6 | toastr['warning']("Please insert url"); 7 | } else if (!$('#cron').find('#pattern').val()) { 8 | toastr['warning']("Please insert pattern"); 9 | } else if (!$('#cron').find('#name').val()) { 10 | toastr['warning']("Please insert name"); 11 | } else { 12 | var $option = $('#cron').find('#receiver-ip').find('option:selected'); 13 | if ($option) { 14 | $.post("rest/cron/add/" + $option.val(), { 15 | url: $('#cron').find('#url').val(), 16 | reload: $('#cron').find('#refresh').val(), 17 | pattern: $('#cron').find('#pattern').val(), 18 | name: $('#cron').find('#name').val() 19 | }).done(function() { 20 | toastr['info']("added cronjob for " + $option.text() + " successfully"); 21 | }).fail(function(xhr, status, error) { 22 | toastr['error']("error on " + $option.text()); 23 | }); 24 | } else { 25 | toastr['warning']("Please select chromecast"); 26 | } 27 | 28 | } 29 | }); 30 | 31 | $('#refreshjobs').click(function(ev) { 32 | ev.preventDefault(); 33 | var $cjobs = $('#cron').find('#currentjobs'); 34 | $cjobs.html(""); 35 | $.ajax({ 36 | type: "GET", 37 | url: "rest/cron/get" 38 | }).done(function(data, textStatus, jqXHR) { 39 | var obj = JSON.parse(data); 40 | $.each(obj, function(i, elem) { 41 | var row = '
'; 42 | row += elem.name; 43 | row += "
"; 44 | row += '
'; 45 | row += '' + elem.url + ''; 46 | row += "
"; 47 | row += '
'; 48 | row += elem.desc + " (" + elem.pattern + ")"; 49 | row += "
"; 50 | row += '
'; 51 | row += ''; 52 | row+='

'; 53 | $cjobs.append(row); 54 | }); 55 | 56 | $('.del-cron').click(function(ev) { 57 | ev.preventDefault(); 58 | var name = $(this).data("name"); 59 | var target = $(this).data("target"); 60 | var $this = $(this); 61 | $.ajax({ 62 | type: "DELETE", 63 | url: "rest/cron/remove/" + target + "/" + name 64 | }).done(function(data, textStatus, jqXHR) { 65 | toastr['info']("deleted " + name); 66 | $this.parent().parent().hide(); 67 | }).fail(function(jqXHR, textStatus, errorThrown) { 68 | toastr['error']("error"); 69 | }); 70 | }); 71 | 72 | }).fail(function(jqXHR, textStatus, errorThrown) { 73 | // TODO error 74 | toastr['error']("error"); 75 | }).always(function() { 76 | 77 | }); 78 | }); 79 | }); 80 | })(); 81 | -------------------------------------------------------------------------------- /src/de/michaelkuerbis/presenter/rest/MediaREST.java: -------------------------------------------------------------------------------- 1 | package de.michaelkuerbis.presenter.rest; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import java.nio.file.Paths; 7 | 8 | import javax.servlet.http.HttpServletRequest; 9 | import javax.ws.rs.DELETE; 10 | import javax.ws.rs.GET; 11 | import javax.ws.rs.Path; 12 | import javax.ws.rs.PathParam; 13 | import javax.ws.rs.WebApplicationException; 14 | import javax.ws.rs.core.Context; 15 | import javax.ws.rs.core.MediaType; 16 | import javax.ws.rs.core.Response; 17 | import javax.ws.rs.core.StreamingOutput; 18 | 19 | import org.json.simple.JSONArray; 20 | 21 | @Path("/media") 22 | public class MediaREST { 23 | 24 | @Context 25 | private HttpServletRequest webRequest; 26 | 27 | @GET 28 | @Path("/get/{file}") 29 | public Response getMedia(@PathParam("file") String fname) { 30 | if(fname == null || fname.isEmpty()) { 31 | return Response.status(Response.Status.BAD_REQUEST).entity("file must be provided").build(); 32 | } 33 | final String file = fname.replaceAll("\\.", "").replaceAll("/", ""); 34 | String path = webRequest.getServletContext().getRealPath("/WEB-INF/media"); 35 | File f = new File(path+File.separator+file); 36 | if(f.exists()){ 37 | StreamingOutput fileStream = new StreamingOutput() 38 | { 39 | @Override 40 | public void write(java.io.OutputStream output) throws IOException, WebApplicationException 41 | { 42 | try 43 | { 44 | java.nio.file.Path path = Paths.get(webRequest.getServletContext().getRealPath("/WEB-INF/media")+File.separator+file); 45 | byte[] data = Files.readAllBytes(path); 46 | output.write(data); 47 | output.flush(); 48 | } 49 | catch (Exception e) 50 | { 51 | throw new WebApplicationException("File Not Found !!"); 52 | } 53 | } 54 | }; 55 | return Response 56 | .ok(fileStream, MediaType.APPLICATION_OCTET_STREAM) 57 | .header("content-disposition","attachment; filename = "+file) 58 | .build(); 59 | }else{ 60 | return Response.status(Response.Status.BAD_REQUEST).entity("file does not exist").build(); 61 | } 62 | } 63 | 64 | @DELETE 65 | @Path("/{file}") 66 | public Response delete(@PathParam("file") String fname) { 67 | if(fname == null || fname.isEmpty()) { 68 | return Response.status(Response.Status.BAD_REQUEST).entity("file must be provided").build(); 69 | } 70 | final String file = fname.replaceAll("\\.", "").replaceAll("/", ""); 71 | String path = webRequest.getServletContext().getRealPath("/WEB-INF/media"); 72 | File f = new File(path+File.separator+file); 73 | if(f.exists()){ 74 | if(f.delete()){ 75 | return Response.ok().build(); 76 | } 77 | else{ 78 | return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("could not delete file").build(); 79 | } 80 | } 81 | return Response.status(Response.Status.BAD_REQUEST).entity("file does not exist").build(); 82 | } 83 | 84 | @GET 85 | @Path("/list") 86 | public Response getList() { 87 | JSONArray list = new JSONArray(); 88 | String path = webRequest.getServletContext().getRealPath("/WEB-INF/media"); 89 | File mediaDir = new File(path); 90 | for (File f : mediaDir.listFiles()) { 91 | String fileName = f.getName().toLowerCase(); 92 | if (fileName.endsWith("aac") || fileName.endsWith("mp4") || fileName.endsWith("mp3") 93 | || fileName.endsWith("wav") || fileName.endsWith("webm")) { 94 | list.add(fileName); 95 | } 96 | } 97 | return Response.ok(list.toJSONString()).build(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /WebContent/assets/js/spin.min.js: -------------------------------------------------------------------------------- 1 | !function(a,b){"object"==typeof module&&module.exports?module.exports=b():"function"==typeof define&&define.amd?define(b):a.Spinner=b()}(this,function(){"use strict";function a(a,b){var c,d=document.createElement(a||"div");for(c in b)d[c]=b[c];return d}function b(a){for(var b=1,c=arguments.length;c>b;b++)a.appendChild(arguments[b]);return a}function c(a,b,c,d){var e=["opacity",b,~~(100*a),c,d].join("-"),f=.01+c/d*100,g=Math.max(1-(1-a)/b*(100-f),a),h=j.substring(0,j.indexOf("Animation")).toLowerCase(),i=h&&"-"+h+"-"||"";return m[e]||(k.insertRule("@"+i+"keyframes "+e+"{0%{opacity:"+g+"}"+f+"%{opacity:"+a+"}"+(f+.01)+"%{opacity:1}"+(f+b)%100+"%{opacity:"+a+"}100%{opacity:"+g+"}}",k.cssRules.length),m[e]=1),e}function d(a,b){var c,d,e=a.style;if(b=b.charAt(0).toUpperCase()+b.slice(1),void 0!==e[b])return b;for(d=0;d',c)}k.addRule(".spin-vml","behavior:url(#default#VML)"),h.prototype.lines=function(a,d){function f(){return e(c("group",{coordsize:k+" "+k,coordorigin:-j+" "+-j}),{width:k,height:k})}function h(a,h,i){b(m,b(e(f(),{rotation:360/d.lines*a+"deg",left:~~h}),b(e(c("roundrect",{arcsize:d.corners}),{width:j,height:d.scale*d.width,left:d.scale*d.radius,top:-d.scale*d.width>>1,filter:i}),c("fill",{color:g(d.color,a),opacity:d.opacity}),c("stroke",{opacity:0}))))}var i,j=d.scale*(d.length+d.width),k=2*d.scale*j,l=-(d.width+d.length)*d.scale*2+"px",m=e(f(),{position:"absolute",top:l,left:l});if(d.shadow)for(i=1;i<=d.lines;i++)h(i,-2,"progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)");for(i=1;i<=d.lines;i++)h(i);return b(a,m)},h.prototype.opacity=function(a,b,c,d){var e=a.firstChild;d=d.shadow&&d.lines||0,e&&b+d>1)+"px"})}for(var i,k=0,l=(f.lines-1)*(1-f.direction)/2;k vec = SettingsServlet.getConnections(); 47 | vec.add(con); 48 | SettingsServlet.setConnections(vec); 49 | if (SettingsServlet.saveSettings(this.webRequest 50 | .getServletContext())) { 51 | obj.put("ok", ""); 52 | } else { 53 | obj.put("error", "failed to save settings."); 54 | } 55 | } 56 | } else { 57 | obj.put("error", "ip and name must be defined."); 58 | } 59 | return Response.ok().entity(obj.toString()).build(); 60 | } 61 | 62 | @POST 63 | @Path("/update/{ip}/{default}") 64 | public Response updateChromecast(@PathParam("ip") String ip, 65 | @PathParam("default") String isDefault) { 66 | JSONObject obj = new JSONObject(); 67 | log.debug(isDefault +" val:"+(isDefault != null && !isDefault.isEmpty() 68 | && isDefault.toLowerCase().equals("true"))); 69 | if (ip != null && !ip.isEmpty()) { 70 | Vector vec = SettingsServlet.getConnections(); 71 | for (int i = 0; i < vec.size(); i++) { 72 | CastConnection con = vec.get(i); 73 | if (con.getIp().equals(ip)) { 74 | con.setDefault(isDefault != null && !isDefault.isEmpty() 75 | && isDefault.toLowerCase().equals("true")); 76 | vec.set(i, con); 77 | SettingsServlet.setConnections(vec); 78 | if (SettingsServlet.saveSettings(this.webRequest 79 | .getServletContext())) { 80 | obj.put("ok", ""); 81 | } else { 82 | obj.put("error", "failed to save settings."); 83 | } 84 | break; 85 | } 86 | } 87 | } else { 88 | obj.put("error", "ip and name must be defined."); 89 | } 90 | return Response.ok().entity(obj.toString()).build(); 91 | } 92 | 93 | @DELETE 94 | @Path("/remove/{ip}") 95 | public Response removeChromecast(@PathParam("ip") String ip) { 96 | JSONObject obj = new JSONObject(); 97 | if (ip != null && !ip.isEmpty()) { 98 | Vector vec = SettingsServlet.getConnections(); 99 | for (CastConnection con : vec) { 100 | if (con.getIp().equals(ip)) { 101 | vec.remove(con); 102 | SettingsServlet.setConnections(vec); 103 | if (SettingsServlet.saveSettings(this.webRequest 104 | .getServletContext())) { 105 | obj.put("ok", ""); 106 | } else { 107 | obj.put("error", "failed to save settings."); 108 | } 109 | break; 110 | } 111 | } 112 | } else { 113 | obj.put("error", "ip must be defined."); 114 | } 115 | return Response.ok().entity(obj.toString()).build(); 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /WebContent/assets/js/toastr.min.js: -------------------------------------------------------------------------------- 1 | !function(e){e(["jquery"],function(e){return function(){function t(e,t,n){return f({type:O.error,iconClass:g().iconClasses.error,message:e,optionsOverride:n,title:t})}function n(t,n){return t||(t=g()),v=e("#"+t.containerId),v.length?v:(n&&(v=c(t)),v)}function i(e,t,n){return f({type:O.info,iconClass:g().iconClasses.info,message:e,optionsOverride:n,title:t})}function o(e){w=e}function s(e,t,n){return f({type:O.success,iconClass:g().iconClasses.success,message:e,optionsOverride:n,title:t})}function a(e,t,n){return f({type:O.warning,iconClass:g().iconClasses.warning,message:e,optionsOverride:n,title:t})}function r(e){var t=g();v||n(t),l(e,t)||u(t)}function d(t){var i=g();return v||n(i),t&&0===e(":focus",t).length?void h(t):void(v.children().length&&v.remove())}function u(t){for(var n=v.children(),i=n.length-1;i>=0;i--)l(e(n[i]),t)}function l(t,n){return t&&0===e(":focus",t).length?(t[n.hideMethod]({duration:n.hideDuration,easing:n.hideEasing,complete:function(){h(t)}}),!0):!1}function c(t){return v=e("
").attr("id",t.containerId).addClass(t.positionClass).attr("aria-live","polite").attr("role","alert"),v.appendTo(e(t.target)),v}function p(){return{tapToDismiss:!0,toastClass:"toast",containerId:"toast-container",debug:!1,showMethod:"fadeIn",showDuration:300,showEasing:"swing",onShown:void 0,hideMethod:"fadeOut",hideDuration:1e3,hideEasing:"swing",onHidden:void 0,extendedTimeOut:1e3,iconClasses:{error:"toast-error",info:"toast-info",success:"toast-success",warning:"toast-warning"},iconClass:"toast-info",positionClass:"toast-top-right",timeOut:5e3,titleClass:"toast-title",messageClass:"toast-message",target:"body",closeHtml:'',newestOnTop:!0,preventDuplicates:!1,progressBar:!1}}function m(e){w&&w(e)}function f(t){function i(t){return!e(":focus",l).length||t?(clearTimeout(O.intervalId),l[r.hideMethod]({duration:r.hideDuration,easing:r.hideEasing,complete:function(){h(l),r.onHidden&&"hidden"!==b.state&&r.onHidden(),b.state="hidden",b.endTime=new Date,m(b)}})):void 0}function o(){(r.timeOut>0||r.extendedTimeOut>0)&&(u=setTimeout(i,r.extendedTimeOut),O.maxHideTime=parseFloat(r.extendedTimeOut),O.hideEta=(new Date).getTime()+O.maxHideTime)}function s(){clearTimeout(u),O.hideEta=0,l.stop(!0,!0)[r.showMethod]({duration:r.showDuration,easing:r.showEasing})}function a(){var e=(O.hideEta-(new Date).getTime())/O.maxHideTime*100;f.width(e+"%")}var r=g(),d=t.iconClass||r.iconClass;if("undefined"!=typeof t.optionsOverride&&(r=e.extend(r,t.optionsOverride),d=t.optionsOverride.iconClass||d),r.preventDuplicates){if(t.message===C)return;C=t.message}T++,v=n(r,!0);var u=null,l=e("
"),c=e("
"),p=e("
"),f=e("
"),w=e(r.closeHtml),O={intervalId:null,hideEta:null,maxHideTime:null},b={toastId:T,state:"visible",startTime:new Date,options:r,map:t};return t.iconClass&&l.addClass(r.toastClass).addClass(d),t.title&&(c.append(t.title).addClass(r.titleClass),l.append(c)),t.message&&(p.append(t.message).addClass(r.messageClass),l.append(p)),r.closeButton&&(w.addClass("toast-close-button").attr("role","button"),l.prepend(w)),r.progressBar&&(f.addClass("toast-progress"),l.prepend(f)),l.hide(),r.newestOnTop?v.prepend(l):v.append(l),l[r.showMethod]({duration:r.showDuration,easing:r.showEasing,complete:r.onShown}),r.timeOut>0&&(u=setTimeout(i,r.timeOut),O.maxHideTime=parseFloat(r.timeOut),O.hideEta=(new Date).getTime()+O.maxHideTime,r.progressBar&&(O.intervalId=setInterval(a,10))),l.hover(s,o),!r.onclick&&r.tapToDismiss&&l.click(i),r.closeButton&&w&&w.click(function(e){e.stopPropagation?e.stopPropagation():void 0!==e.cancelBubble&&e.cancelBubble!==!0&&(e.cancelBubble=!0),i(!0)}),r.onclick&&l.click(function(){r.onclick(),i()}),m(b),r.debug&&console&&console.log(b),l}function g(){return e.extend({},p(),b.options)}function h(e){v||(v=n()),e.is(":visible")||(e.remove(),e=null,0===v.children().length&&(v.remove(),C=void 0))}var v,w,C,T=0,O={error:"error",info:"info",success:"success",warning:"warning"},b={clear:r,remove:d,error:t,getContainer:n,info:i,options:{},subscribe:o,success:s,version:"2.1.0",warning:a};return b}()})}("function"==typeof define&&define.amd?define:function(e,t){"undefined"!=typeof module&&module.exports?module.exports=t(require("jquery")):window.toastr=t(window.jQuery)}); 2 | //# sourceMappingURL=toastr.js.map -------------------------------------------------------------------------------- /WebContent/assets/js/jquery.i18n.messagestore.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Internationalization library - Message Store 3 | * 4 | * Copyright (C) 2012 Santhosh Thottingal 5 | * 6 | * jquery.i18n is dual licensed GPLv2 or later and MIT. You don't have to do anything special to 7 | * choose one license or the other and you don't have to notify anyone which license you are using. 8 | * You are free to use UniversalLanguageSelector in commercial projects as long as the copyright 9 | * header is left intact. See files GPL-LICENSE and MIT-LICENSE for details. 10 | * 11 | * @licence GNU General Public Licence 2.0 or later 12 | * @licence MIT License 13 | */ 14 | 15 | ( function ( $, window, undefined ) { 16 | 'use strict'; 17 | 18 | var MessageStore = function () { 19 | this.messages = {}; 20 | this.sources = {}; 21 | }; 22 | 23 | /** 24 | * See https://github.com/wikimedia/jquery.i18n/wiki/Specification#wiki-Message_File_Loading 25 | */ 26 | MessageStore.prototype = { 27 | 28 | /** 29 | * General message loading API This can take a URL string for 30 | * the json formatted messages. 31 | * load('path/to/all_localizations.json'); 32 | * 33 | * This can also load a localization file for a locale 34 | * load( 'path/to/de-messages.json', 'de' ); 35 | * 36 | * A data object containing message key- message translation mappings 37 | * can also be passed Eg: 38 | * 39 | * load( { 'hello' : 'Hello' }, optionalLocale ); 40 | * If the data argument is 41 | * null/undefined/false, 42 | * all cached messages for the i18n instance will get reset. 43 | * 44 | * @param {string|Object} source 45 | * @param {string} locale Language tag 46 | * @return {jQuery.Promise} 47 | */ 48 | load: function ( source, locale ) { 49 | var key = null, 50 | deferred = null, 51 | deferreds = [], 52 | messageStore = this; 53 | 54 | if ( typeof source === 'string' ) { 55 | // This is a URL to the messages file. 56 | $.i18n.log( 'Loading messages from: ' + source ); 57 | deferred = jsonMessageLoader( source ) 58 | .done( function ( localization ) { 59 | messageStore.set( locale, localization ); 60 | } ); 61 | 62 | return deferred.promise(); 63 | } 64 | 65 | if ( locale ) { 66 | // source is an key-value pair of messages for given locale 67 | messageStore.set( locale, source ); 68 | 69 | return $.Deferred().resolve(); 70 | } else { 71 | // source is a key-value pair of locales and their source 72 | for ( key in source ) { 73 | if ( Object.prototype.hasOwnProperty.call( source, key ) ) { 74 | locale = key; 75 | // No {locale} given, assume data is a group of languages, 76 | // call this function again for each language. 77 | deferreds.push( messageStore.load( source[ key ], locale ) ); 78 | } 79 | } 80 | return $.when.apply( $, deferreds ); 81 | } 82 | 83 | }, 84 | 85 | /** 86 | * Set messages to the given locale. 87 | * If locale exists, add messages to the locale. 88 | * 89 | * @param {string} locale 90 | * @param {Object} messages 91 | */ 92 | set: function ( locale, messages ) { 93 | if ( !this.messages[ locale ] ) { 94 | this.messages[ locale ] = messages; 95 | } else { 96 | this.messages[ locale ] = $.extend( this.messages[ locale ], messages ); 97 | } 98 | }, 99 | 100 | /** 101 | * 102 | * @param {string} locale 103 | * @param {string} messageKey 104 | * @return {boolean} 105 | */ 106 | get: function ( locale, messageKey ) { 107 | return this.messages[ locale ] && this.messages[ locale ][ messageKey ]; 108 | } 109 | }; 110 | 111 | function jsonMessageLoader( url ) { 112 | var deferred = $.Deferred(); 113 | 114 | $.getJSON( url ) 115 | .done( deferred.resolve ) 116 | .fail( function ( jqxhr, settings, exception ) { 117 | $.i18n.log( 'Error in loading messages from ' + url + ' Exception: ' + exception ); 118 | // Ignore 404 exception, because we are handling fallabacks explicitly 119 | deferred.resolve(); 120 | } ); 121 | 122 | return deferred.promise(); 123 | } 124 | 125 | $.extend( $.i18n.messageStore, new MessageStore() ); 126 | }( jQuery, window ) ); 127 | -------------------------------------------------------------------------------- /src/de/michaelkuerbis/presenter/rest/CronREST.java: -------------------------------------------------------------------------------- 1 | package de.michaelkuerbis.presenter.rest; 2 | 3 | import java.text.ParseException; 4 | import java.util.Vector; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.ws.rs.Consumes; 8 | import javax.ws.rs.DELETE; 9 | import javax.ws.rs.FormParam; 10 | import javax.ws.rs.GET; 11 | import javax.ws.rs.POST; 12 | import javax.ws.rs.Path; 13 | import javax.ws.rs.PathParam; 14 | import javax.ws.rs.core.Context; 15 | import javax.ws.rs.core.MediaType; 16 | import javax.ws.rs.core.Response; 17 | 18 | import net.redhogs.cronparser.CronExpressionDescriptor; 19 | import net.redhogs.cronparser.Options; 20 | 21 | import org.json.simple.JSONArray; 22 | import org.json.simple.JSONObject; 23 | 24 | import de.michaelkuerbis.presenter.servlets.CronServlet; 25 | import de.michaelkuerbis.presenter.utils.CronJob; 26 | 27 | @Path("/cron") 28 | public class CronREST { 29 | 30 | @Context 31 | private HttpServletRequest webRequest; 32 | 33 | @POST 34 | @Consumes(MediaType.APPLICATION_FORM_URLENCODED) 35 | @Path("/add/{target}") 36 | public Response addChromecast(@PathParam("target") String target, 37 | @FormParam("name") String name, @FormParam("url") String url, 38 | @FormParam("pattern") String pattern, @FormParam("reload") int reload) { 39 | JSONObject obj = new JSONObject(); 40 | if (target != null && !target.isEmpty() && name != null 41 | && !name.isEmpty()) { 42 | boolean alreadyDefined = false; 43 | for (CronJob con : CronServlet.getCronJobs()) { 44 | if (con.getTarget().equals(target) && con.getName().equals(name)) { 45 | alreadyDefined = true; 46 | obj.put("error", "cronjob already defined with name: " 47 | + con.getName()); 48 | break; 49 | } 50 | } 51 | if (!alreadyDefined) { 52 | CronJob con = new CronJob(target, url, pattern, name, reload); 53 | Vector vec = CronServlet.getCronJobs(); 54 | vec.add(con); 55 | CronServlet.setCronJobs(vec); 56 | if (CronServlet.saveSettings(this.webRequest 57 | .getServletContext())) { 58 | con.start(); 59 | obj.put("ok", ""); 60 | } else { 61 | obj.put("error", "failed to save cronjob."); 62 | } 63 | } 64 | } else { 65 | obj.put("error", "ip and name must be defined."); 66 | } 67 | return Response.ok().entity(obj.toString()).build(); 68 | } 69 | 70 | @GET 71 | @Path("/get") 72 | public Response getCronJobs() { 73 | JSONArray status = new JSONArray(); 74 | for (CronJob job : CronServlet.getCronJobs()) { 75 | JSONObject foo = new JSONObject(); 76 | foo.put("target", job.getTarget()); 77 | foo.put("name", job.getName()); 78 | foo.put("pattern", job.getPattern()); 79 | foo.put("reload", job.getReload()); 80 | foo.put("url", job.getUrl()); 81 | foo.put("isrunning", job.getScheduler().isStarted()); 82 | try { 83 | //TODO Language 84 | foo.put("desc", CronExpressionDescriptor.getDescription(job.getPattern(), Options.twentyFourHour())); 85 | } catch (ParseException e) { 86 | // TODO Auto-generated catch block 87 | e.printStackTrace(); 88 | } 89 | status.add(foo); 90 | } 91 | return Response.ok(status.toJSONString()).build(); 92 | } 93 | 94 | @DELETE 95 | @Path("/remove/{target}/{name}") 96 | public Response removeChromecast(@PathParam("target") String target, @PathParam("name") String name) { 97 | JSONObject obj = new JSONObject(); 98 | if (target != null && !target.isEmpty() && name != null && !name.isEmpty()) { 99 | Vector vec = CronServlet.getCronJobs(); 100 | for (CronJob con : vec) { 101 | if (con.getTarget().equals(target) && con.getName().equals(name)) { 102 | con.stop(); 103 | vec.remove(con); 104 | CronServlet.setCronJobs(vec); 105 | if (CronServlet.saveSettings(this.webRequest 106 | .getServletContext())) { 107 | if(con.getScheduler().isStarted()) 108 | con.getScheduler().stop(); 109 | obj.put("ok", ""); 110 | } else { 111 | obj.put("error", "failed to save cronjobs."); 112 | } 113 | break; 114 | } 115 | } 116 | } else { 117 | obj.put("error", "ip and name must be defined."); 118 | } 119 | return Response.ok().entity(obj.toString()).build(); 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | presenter 4 | presenter 5 | 0.0.1-SNAPSHOT 6 | war 7 | 8 | [1.7.2,1.7.100] 9 | 2.17.2 10 | 2.33 11 | 12 | 13 | src 14 | 15 | 16 | maven-compiler-plugin 17 | 3.8.1 18 | 19 | groovy-eclipse-compiler 20 | 1.8 21 | 1.8 22 | 23 | 24 | 25 | org.codehaus.groovy 26 | groovy-eclipse-compiler 27 | 3.7.0 28 | 29 | 30 | org.codehaus.groovy 31 | groovy-eclipse-batch 32 | 3.0.7-03 33 | 34 | 35 | 36 | 37 | maven-war-plugin 38 | 3.3.1 39 | 40 | WebContent 41 | false 42 | 43 | 44 | 45 | 46 | 47 | 48 | javax.servlet 49 | javax.servlet-api 50 | [4.0.0,4.0.100] 51 | provided 52 | 53 | 54 | org.glassfish.jersey.containers 55 | jersey-container-servlet 56 | ${jersey.version} 57 | 58 | 59 | org.glassfish.jersey.inject 60 | jersey-hk2 61 | ${jersey.version} 62 | 63 | 64 | com.googlecode.json-simple 65 | json-simple 66 | 1.1.1 67 | 68 | 69 | javax.jmdns 70 | jmdns 71 | 3.4.1 72 | 73 | 74 | org.slf4j 75 | slf4j-api 76 | ${slf4j.version} 77 | 78 | 79 | org.slf4j 80 | slf4j-log4j12 81 | ${slf4j.version} 82 | 83 | 84 | org.apache.logging.log4j 85 | log4j-api 86 | ${log4j.version} 87 | 88 | 89 | org.apache.logging.log4j 90 | log4j-core 91 | ${log4j.version} 92 | 93 | 94 | su.litvak.chromecast 95 | api-v2 96 | 0.11.3 97 | 98 | 99 | org.slf4j 100 | slf4j-api 101 | 102 | 103 | 104 | 110 | 111 | net.redhogs.cronparser 112 | cron-parser-core 113 | 3.5 114 | 115 | 116 | org.slf4j 117 | slf4j-api 118 | 119 | 120 | 121 | 122 | org.codehaus.groovy 123 | groovy-all 124 | 3.0.7 125 | pom 126 | 127 | 128 | org.quartz-scheduler 129 | quartz 130 | 2.3.2 131 | 132 | 133 | -------------------------------------------------------------------------------- /target/m2e-wtp/web-resources/META-INF/maven/presenter/presenter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | presenter 4 | presenter 5 | 0.0.1-SNAPSHOT 6 | war 7 | 8 | [1.7.2,1.7.100] 9 | 2.17.2 10 | 2.33 11 | 12 | 13 | src 14 | 15 | 16 | maven-compiler-plugin 17 | 3.8.1 18 | 19 | groovy-eclipse-compiler 20 | 1.8 21 | 1.8 22 | 23 | 24 | 25 | org.codehaus.groovy 26 | groovy-eclipse-compiler 27 | 3.7.0 28 | 29 | 30 | org.codehaus.groovy 31 | groovy-eclipse-batch 32 | 3.0.7-03 33 | 34 | 35 | 36 | 37 | maven-war-plugin 38 | 3.3.1 39 | 40 | WebContent 41 | false 42 | 43 | 44 | 45 | 46 | 47 | 48 | javax.servlet 49 | javax.servlet-api 50 | [4.0.0,4.0.100] 51 | provided 52 | 53 | 54 | org.glassfish.jersey.containers 55 | jersey-container-servlet 56 | ${jersey.version} 57 | 58 | 59 | org.glassfish.jersey.inject 60 | jersey-hk2 61 | ${jersey.version} 62 | 63 | 64 | com.googlecode.json-simple 65 | json-simple 66 | 1.1.1 67 | 68 | 69 | javax.jmdns 70 | jmdns 71 | 3.4.1 72 | 73 | 74 | org.slf4j 75 | slf4j-api 76 | ${slf4j.version} 77 | 78 | 79 | org.slf4j 80 | slf4j-log4j12 81 | ${slf4j.version} 82 | 83 | 84 | org.apache.logging.log4j 85 | log4j-api 86 | ${log4j.version} 87 | 88 | 89 | org.apache.logging.log4j 90 | log4j-core 91 | ${log4j.version} 92 | 93 | 94 | su.litvak.chromecast 95 | api-v2 96 | 0.11.3 97 | 98 | 99 | org.slf4j 100 | slf4j-api 101 | 102 | 103 | 104 | 110 | 111 | net.redhogs.cronparser 112 | cron-parser-core 113 | 3.5 114 | 115 | 116 | org.slf4j 117 | slf4j-api 118 | 119 | 120 | 121 | 122 | org.codehaus.groovy 123 | groovy-all 124 | 3.0.7 125 | pom 126 | 127 | 128 | org.quartz-scheduler 129 | quartz 130 | 2.3.2 131 | 132 | 133 | -------------------------------------------------------------------------------- /WebContent/assets/js/jquery.i18n.fallbacks.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Internationalization library 3 | * 4 | * Copyright (C) 2012 Santhosh Thottingal 5 | * 6 | * jquery.i18n is dual licensed GPLv2 or later and MIT. You don't have to do anything special to 7 | * choose one license or the other and you don't have to notify anyone which license you are using. 8 | * You are free to use UniversalLanguageSelector in commercial projects as long as the copyright 9 | * header is left intact. See files GPL-LICENSE and MIT-LICENSE for details. 10 | * 11 | * @licence GNU General Public Licence 2.0 or later 12 | * @licence MIT License 13 | */ 14 | ( function ( $, undefined ) { 15 | 'use strict'; 16 | 17 | $.i18n = $.i18n || {}; 18 | $.extend( $.i18n.fallbacks, { 19 | ab: [ 'ru' ], 20 | ace: [ 'id' ], 21 | aln: [ 'sq' ], 22 | // Not so standard - als is supposed to be Tosk Albanian, 23 | // but in Wikipedia it's used for a Germanic language. 24 | als: [ 'gsw', 'de' ], 25 | an: [ 'es' ], 26 | anp: [ 'hi' ], 27 | arn: [ 'es' ], 28 | arz: [ 'ar' ], 29 | av: [ 'ru' ], 30 | ay: [ 'es' ], 31 | ba: [ 'ru' ], 32 | bar: [ 'de' ], 33 | 'bat-smg': [ 'sgs', 'lt' ], 34 | bcc: [ 'fa' ], 35 | 'be-x-old': [ 'be-tarask' ], 36 | bh: [ 'bho' ], 37 | bjn: [ 'id' ], 38 | bm: [ 'fr' ], 39 | bpy: [ 'bn' ], 40 | bqi: [ 'fa' ], 41 | bug: [ 'id' ], 42 | 'cbk-zam': [ 'es' ], 43 | ce: [ 'ru' ], 44 | crh: [ 'crh-latn' ], 45 | 'crh-cyrl': [ 'ru' ], 46 | csb: [ 'pl' ], 47 | cv: [ 'ru' ], 48 | 'de-at': [ 'de' ], 49 | 'de-ch': [ 'de' ], 50 | 'de-formal': [ 'de' ], 51 | dsb: [ 'de' ], 52 | dtp: [ 'ms' ], 53 | egl: [ 'it' ], 54 | eml: [ 'it' ], 55 | ff: [ 'fr' ], 56 | fit: [ 'fi' ], 57 | 'fiu-vro': [ 'vro', 'et' ], 58 | frc: [ 'fr' ], 59 | frp: [ 'fr' ], 60 | frr: [ 'de' ], 61 | fur: [ 'it' ], 62 | gag: [ 'tr' ], 63 | gan: [ 'gan-hant', 'zh-hant', 'zh-hans' ], 64 | 'gan-hans': [ 'zh-hans' ], 65 | 'gan-hant': [ 'zh-hant', 'zh-hans' ], 66 | gl: [ 'pt' ], 67 | glk: [ 'fa' ], 68 | gn: [ 'es' ], 69 | gsw: [ 'de' ], 70 | hif: [ 'hif-latn' ], 71 | hsb: [ 'de' ], 72 | ht: [ 'fr' ], 73 | ii: [ 'zh-cn', 'zh-hans' ], 74 | inh: [ 'ru' ], 75 | iu: [ 'ike-cans' ], 76 | jut: [ 'da' ], 77 | jv: [ 'id' ], 78 | kaa: [ 'kk-latn', 'kk-cyrl' ], 79 | kbd: [ 'kbd-cyrl' ], 80 | khw: [ 'ur' ], 81 | kiu: [ 'tr' ], 82 | kk: [ 'kk-cyrl' ], 83 | 'kk-arab': [ 'kk-cyrl' ], 84 | 'kk-latn': [ 'kk-cyrl' ], 85 | 'kk-cn': [ 'kk-arab', 'kk-cyrl' ], 86 | 'kk-kz': [ 'kk-cyrl' ], 87 | 'kk-tr': [ 'kk-latn', 'kk-cyrl' ], 88 | kl: [ 'da' ], 89 | 'ko-kp': [ 'ko' ], 90 | koi: [ 'ru' ], 91 | krc: [ 'ru' ], 92 | ks: [ 'ks-arab' ], 93 | ksh: [ 'de' ], 94 | ku: [ 'ku-latn' ], 95 | 'ku-arab': [ 'ckb' ], 96 | kv: [ 'ru' ], 97 | lad: [ 'es' ], 98 | lb: [ 'de' ], 99 | lbe: [ 'ru' ], 100 | lez: [ 'ru' ], 101 | li: [ 'nl' ], 102 | lij: [ 'it' ], 103 | liv: [ 'et' ], 104 | lmo: [ 'it' ], 105 | ln: [ 'fr' ], 106 | ltg: [ 'lv' ], 107 | lzz: [ 'tr' ], 108 | mai: [ 'hi' ], 109 | 'map-bms': [ 'jv', 'id' ], 110 | mg: [ 'fr' ], 111 | mhr: [ 'ru' ], 112 | min: [ 'id' ], 113 | mo: [ 'ro' ], 114 | mrj: [ 'ru' ], 115 | mwl: [ 'pt' ], 116 | myv: [ 'ru' ], 117 | mzn: [ 'fa' ], 118 | nah: [ 'es' ], 119 | nap: [ 'it' ], 120 | nds: [ 'de' ], 121 | 'nds-nl': [ 'nl' ], 122 | 'nl-informal': [ 'nl' ], 123 | no: [ 'nb' ], 124 | os: [ 'ru' ], 125 | pcd: [ 'fr' ], 126 | pdc: [ 'de' ], 127 | pdt: [ 'de' ], 128 | pfl: [ 'de' ], 129 | pms: [ 'it' ], 130 | pt: [ 'pt-br' ], 131 | 'pt-br': [ 'pt' ], 132 | qu: [ 'es' ], 133 | qug: [ 'qu', 'es' ], 134 | rgn: [ 'it' ], 135 | rmy: [ 'ro' ], 136 | 'roa-rup': [ 'rup' ], 137 | rue: [ 'uk', 'ru' ], 138 | ruq: [ 'ruq-latn', 'ro' ], 139 | 'ruq-cyrl': [ 'mk' ], 140 | 'ruq-latn': [ 'ro' ], 141 | sa: [ 'hi' ], 142 | sah: [ 'ru' ], 143 | scn: [ 'it' ], 144 | sg: [ 'fr' ], 145 | sgs: [ 'lt' ], 146 | sli: [ 'de' ], 147 | sr: [ 'sr-ec' ], 148 | srn: [ 'nl' ], 149 | stq: [ 'de' ], 150 | su: [ 'id' ], 151 | szl: [ 'pl' ], 152 | tcy: [ 'kn' ], 153 | tg: [ 'tg-cyrl' ], 154 | tt: [ 'tt-cyrl', 'ru' ], 155 | 'tt-cyrl': [ 'ru' ], 156 | ty: [ 'fr' ], 157 | udm: [ 'ru' ], 158 | ug: [ 'ug-arab' ], 159 | uk: [ 'ru' ], 160 | vec: [ 'it' ], 161 | vep: [ 'et' ], 162 | vls: [ 'nl' ], 163 | vmf: [ 'de' ], 164 | vot: [ 'fi' ], 165 | vro: [ 'et' ], 166 | wa: [ 'fr' ], 167 | wo: [ 'fr' ], 168 | wuu: [ 'zh-hans' ], 169 | xal: [ 'ru' ], 170 | xmf: [ 'ka' ], 171 | yi: [ 'he' ], 172 | za: [ 'zh-hans' ], 173 | zea: [ 'nl' ], 174 | zh: [ 'zh-hans' ], 175 | 'zh-classical': [ 'lzh' ], 176 | 'zh-cn': [ 'zh-hans' ], 177 | 'zh-hant': [ 'zh-hans' ], 178 | 'zh-hk': [ 'zh-hant', 'zh-hans' ], 179 | 'zh-min-nan': [ 'nan' ], 180 | 'zh-mo': [ 'zh-hk', 'zh-hant', 'zh-hans' ], 181 | 'zh-my': [ 'zh-sg', 'zh-hans' ], 182 | 'zh-sg': [ 'zh-hans' ], 183 | 'zh-tw': [ 'zh-hant', 'zh-hans' ], 184 | 'zh-yue': [ 'yue' ] 185 | } ); 186 | }( jQuery ) ); 187 | -------------------------------------------------------------------------------- /WebContent/assets/css/toastr.min.css: -------------------------------------------------------------------------------- 1 | .toast-title{font-weight:700}.toast-message{-ms-word-wrap:break-word;word-wrap:break-word}.toast-message a,.toast-message label{color:#fff}.toast-message a:hover{color:#ccc;text-decoration:none}.toast-close-button{position:relative;right:-.3em;top:-.3em;float:right;font-size:20px;font-weight:700;color:#fff;-webkit-text-shadow:0 1px 0 #fff;text-shadow:0 1px 0 #fff;opacity:.8;-ms-filter:alpha(Opacity=80);filter:alpha(opacity=80)}.toast-close-button:focus,.toast-close-button:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4;-ms-filter:alpha(Opacity=40);filter:alpha(opacity=40)}button.toast-close-button{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none}.toast-top-center{top:0;right:0;width:100%}.toast-bottom-center{bottom:0;right:0;width:100%}.toast-top-full-width{top:0;right:0;width:100%}.toast-bottom-full-width{bottom:0;right:0;width:100%}.toast-top-left{top:12px;left:12px}.toast-top-right{top:12px;right:12px}.toast-bottom-right{right:12px;bottom:12px}.toast-bottom-left{bottom:12px;left:12px}#toast-container{position:fixed;z-index:999999}#toast-container *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}#toast-container>div{position:relative;overflow:hidden;margin:0 0 6px;padding:15px 15px 15px 50px;width:300px;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;background-position:15px center;background-repeat:no-repeat;-moz-box-shadow:0 0 12px #999;-webkit-box-shadow:0 0 12px #999;box-shadow:0 0 12px #999;color:#fff;opacity:.8;-ms-filter:alpha(Opacity=80);filter:alpha(opacity=80)}#toast-container>:hover{-moz-box-shadow:0 0 12px #000;-webkit-box-shadow:0 0 12px #000;box-shadow:0 0 12px #000;opacity:1;-ms-filter:alpha(Opacity=100);filter:alpha(opacity=100);cursor:pointer}#toast-container>.toast-info{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=)!important}#toast-container>.toast-error{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=)!important}#toast-container>.toast-success{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==)!important}#toast-container>.toast-warning{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=)!important}#toast-container.toast-bottom-center>div,#toast-container.toast-top-center>div{width:300px;margin:auto}#toast-container.toast-bottom-full-width>div,#toast-container.toast-top-full-width>div{width:96%;margin:auto}.toast{background-color:#030303}.toast-success{background-color:#51a351}.toast-error{background-color:#bd362f}.toast-info{background-color:#2f96b4}.toast-warning{background-color:#f89406}.toast-progress{position:absolute;left:0;bottom:0;height:4px;background-color:#000;opacity:.4;-ms-filter:alpha(Opacity=40);filter:alpha(opacity=40)}@media all and (max-width:240px){#toast-container>div{padding:8px 8px 8px 50px;width:11em}#toast-container .toast-close-button{right:-.2em;top:-.2em}}@media all and (min-width:241px) and (max-width:480px){#toast-container>div{padding:8px 8px 8px 50px;width:18em}#toast-container .toast-close-button{right:-.2em;top:-.2em}}@media all and (min-width:481px) and (max-width:768px){#toast-container>div{padding:15px 15px 15px 50px;width:25em}} -------------------------------------------------------------------------------- /WebContent/WEB-INF/main.groovy: -------------------------------------------------------------------------------- 1 | import de.michaelkuerbis.presenter.servlets.SettingsServlet; 2 | import de.michaelkuerbis.presenter.utils.CastConnection; 3 | 4 | html.div { 5 | div( class:"container", id:"main"){ 6 | div(class:"col-xs-12"){ 7 | div( class:"page-header"){ 8 | h1(class:"pull-left",'data-i18n':"main_title",'Overview') 9 | //TODO Modal dialog 10 | div(style:"margin-left: 10px;padding-top: 20px;"){ 11 | button(class:"btn btn-default", 'data-toggle':"modal", 'data-target':"#addchromecast", "+") 12 | button(class:"btn btn-default", id:"refreshall", style:"height: 34px;"){ 13 | span(class:"glyphicon glyphicon-refresh", " ") 14 | } 15 | button(class:"btn btn-default",id:"searchcasts", 'data-toggle':"modal", 'data-target':"#discoverchromecast", style:"height: 34px;"){ 16 | span(class:"glyphicon glyphicon-search", " ") 17 | } 18 | } 19 | 20 | } 21 | } 22 | 23 | if(SettingsServlet.getConnections().size() == 0){ 24 | div(class:"col-xs-12 alert alert-warning"){ 25 | div(class:"pull-left",'data-i18n':"main_no_casts","There are no chromecasts defined.") 26 | a(href:"#",'data-toggle':"modal", 'data-target':"#addchromecast",'data-i18n':"main_no_casts_add_one", "add one.") 27 | } 28 | }else{ 29 | for(CastConnection con: SettingsServlet.getConnections()){ 30 | div(class:"col-xs-12 col-sm-6 col-sm-4 col-lg-3 chromecastpanel", 'data-ip': con.getIp()){ 31 | div(class:"panel panel-default"){ 32 | div(class:"panel-heading"){ 33 | h3(class:"panel-title", con.getName()){ 34 | button(class:"btn btn-default btn-xs panelrefresh", style:"min-height: 28px;"){ 35 | span(class:"glyphicon glyphicon-refresh", 'aria-hidden':"true") 36 | } 37 | 38 | button(class:"btn btn-default btn-xs paneltrash btn-warning pull-right", style:"min-height: 28px;"){ 39 | span(class:"glyphicon glyphicon-trash", 'aria-hidden':"true") 40 | } 41 | } 42 | } 43 | div(class:"panel-body"){ 44 | p("IP: "+con.getIp()) 45 | div{ 46 | p('data-i18n':"main_panel_status","Status: ") 47 | p(class:"status") 48 | p('data-i18n':"main_panel_app","App:") 49 | p(class:"application") 50 | div(class:"btn-group toogle-option", 'data-toggle':"buttons"){ 51 | a(href:"#",class:"btn btn-primary option-default "+(con.isDefault()?"active":""),'data-ip':con.getIp(),"default"){ 52 | if(con.isDefault()){ 53 | input( type:"radio", name:"options", autocomplete:"off", checked:"") 54 | }else{ 55 | input( type:"radio", name:"options", autocomplete:"off") 56 | } 57 | } 58 | a(href:"#",class:"btn btn-primary option-info "+(!con.isDefault()?"active":""),'data-ip':con.getIp(),"info"){ 59 | if(!con.isDefault()){ 60 | input( type:"radio", name:"options", autocomplete:"off", checked:"") 61 | }else{ 62 | input( type:"radio", name:"options", autocomplete:"off") 63 | } 64 | } 65 | } 66 | } 67 | div(class:"alert alert-warning trashmsg", style:"display:none;"){ 68 | p(class:"text-center",'data-i18n':"main_panel_remove", "Remove this chromecast?") 69 | p(class:"text-center"){ 70 | a(href:"#", class:"btn btn-danger yestrash",'data-i18n':"main_panel_remove_yes",'data-ip':con.getIp(), "yes") 71 | a(href:"#", class:"btn btn-default notrash",'data-i18n':"main_panel_remove_no", "no") 72 | } 73 | } 74 | } 75 | } 76 | } 77 | } 78 | } 79 | 80 | div( class:"modal fade", id:"addchromecast", tabindex:"-1", role:"dialog", 'aria-labelledby':"myModalLabel"){ 81 | div (class:"modal-dialog", role:"document"){ 82 | div (class:"modal-content"){ 83 | div (class:"modal-header"){ 84 | button( type:"button", class:"close", 'data-dismiss':"modal", 'aria-label':"Close"){ 85 | span( 'aria-hidden':"true", "x") 86 | } 87 | h4( class:"modal-title", id:"myModalLabel",'data-i18n':"main_add_title", "Add Chromecast") 88 | } 89 | div (class:"modal-body"){ 90 | div(class:"form-horizontal"){ 91 | div( class:"form-group"){ 92 | label(class:"col-sm-2 control-label",'data-i18n':"main_add_name", "Name:") 93 | div( class:"col-sm-10"){ 94 | input( type:"text",class:"form-control", id:"addname",'data-i18n-placeholder':"main_add_name_placeholder", placeholder:"Chromecast #1") 95 | } 96 | } 97 | div( class:"form-group"){ 98 | label(class:"col-sm-2 control-label",'data-i18n':"main_add_ip", "Ip:") 99 | div( class:"col-sm-10"){ 100 | input( type:"text",class:"form-control", id:"addip",'data-i18n-placeholder':"main_add_ip_placeholder", placeholder:"127.0.0.1") 101 | } 102 | } 103 | } 104 | } 105 | div (class:"modal-footer"){ 106 | button( type:"button", id:"addcastbutton", class:"btn btn-primary",'data-i18n':"main_add_button", "add Chromecast") 107 | } 108 | } 109 | } 110 | } 111 | div( class:"modal fade", id:"discoverchromecast", tabindex:"-1", role:"dialog", 'aria-labelledby':"myModalLabel"){ 112 | div (class:"modal-dialog", role:"document"){ 113 | div (class:"modal-content"){ 114 | div (class:"modal-header"){ 115 | button( type:"button", class:"close", 'data-dismiss':"modal", 'aria-label':"Close"){ 116 | span( 'aria-hidden':"true", "x") 117 | } 118 | h4( class:"modal-title", id:"myModalLabel",'data-i18n':"main_discover_title", "discovered Chromecasts") 119 | } 120 | div (class:"modal-body"){ h1("fooo") } 121 | } 122 | } 123 | } 124 | 125 | } 126 | } -------------------------------------------------------------------------------- /WebContent/assets/js/jquery.i18n.emitter.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Internationalization library 3 | * 4 | * Copyright (C) 2011-2013 Santhosh Thottingal, Neil Kandalgaonkar 5 | * 6 | * jquery.i18n is dual licensed GPLv2 or later and MIT. You don't have to do 7 | * anything special to choose one license or the other and you don't have to 8 | * notify anyone which license you are using. You are free to use 9 | * UniversalLanguageSelector in commercial projects as long as the copyright 10 | * header is left intact. See files GPL-LICENSE and MIT-LICENSE for details. 11 | * 12 | * @licence GNU General Public Licence 2.0 or later 13 | * @licence MIT License 14 | */ 15 | 16 | ( function ( $ ) { 17 | 'use strict'; 18 | 19 | var MessageParserEmitter = function () { 20 | this.language = $.i18n.languages[ String.locale ] || $.i18n.languages[ 'default' ]; 21 | }; 22 | 23 | MessageParserEmitter.prototype = { 24 | constructor: MessageParserEmitter, 25 | 26 | /** 27 | * (We put this method definition here, and not in prototype, to make 28 | * sure it's not overwritten by any magic.) Walk entire node structure, 29 | * applying replacements and template functions when appropriate 30 | * 31 | * @param {Mixed} node abstract syntax tree (top node or subnode) 32 | * @param {Array} replacements for $1, $2, ... $n 33 | * @return {Mixed} single-string node or array of nodes suitable for 34 | * jQuery appending. 35 | */ 36 | emit: function ( node, replacements ) { 37 | var ret, subnodes, operation, 38 | messageParserEmitter = this; 39 | 40 | switch ( typeof node ) { 41 | case 'string': 42 | case 'number': 43 | ret = node; 44 | break; 45 | case 'object': 46 | // node is an array of nodes 47 | subnodes = $.map( node.slice( 1 ), function ( n ) { 48 | return messageParserEmitter.emit( n, replacements ); 49 | } ); 50 | 51 | operation = node[ 0 ].toLowerCase(); 52 | 53 | if ( typeof messageParserEmitter[ operation ] === 'function' ) { 54 | ret = messageParserEmitter[ operation ]( subnodes, replacements ); 55 | } else { 56 | throw new Error( 'unknown operation "' + operation + '"' ); 57 | } 58 | 59 | break; 60 | case 'undefined': 61 | // Parsing the empty string (as an entire expression, or as a 62 | // paramExpression in a template) results in undefined 63 | // Perhaps a more clever parser can detect this, and return the 64 | // empty string? Or is that useful information? 65 | // The logical thing is probably to return the empty string here 66 | // when we encounter undefined. 67 | ret = ''; 68 | break; 69 | default: 70 | throw new Error( 'unexpected type in AST: ' + typeof node ); 71 | } 72 | 73 | return ret; 74 | }, 75 | 76 | /** 77 | * Parsing has been applied depth-first we can assume that all nodes 78 | * here are single nodes Must return a single node to parents -- a 79 | * jQuery with synthetic span However, unwrap any other synthetic spans 80 | * in our children and pass them upwards 81 | * 82 | * @param {Array} nodes Mixed, some single nodes, some arrays of nodes. 83 | * @return {string} 84 | */ 85 | concat: function ( nodes ) { 86 | var result = ''; 87 | 88 | $.each( nodes, function ( i, node ) { 89 | // strings, integers, anything else 90 | result += node; 91 | } ); 92 | 93 | return result; 94 | }, 95 | 96 | /** 97 | * Return escaped replacement of correct index, or string if 98 | * unavailable. Note that we expect the parsed parameter to be 99 | * zero-based. i.e. $1 should have become [ 0 ]. if the specified 100 | * parameter is not found return the same string (e.g. "$99" -> 101 | * parameter 98 -> not found -> return "$99" ) TODO throw error if 102 | * nodes.length > 1 ? 103 | * 104 | * @param {Array} nodes One element, integer, n >= 0 105 | * @param {Array} replacements for $1, $2, ... $n 106 | * @return {string} replacement 107 | */ 108 | replace: function ( nodes, replacements ) { 109 | var index = parseInt( nodes[ 0 ], 10 ); 110 | 111 | if ( index < replacements.length ) { 112 | // replacement is not a string, don't touch! 113 | return replacements[ index ]; 114 | } else { 115 | // index not found, fallback to displaying variable 116 | return '$' + ( index + 1 ); 117 | } 118 | }, 119 | 120 | /** 121 | * Transform parsed structure into pluralization n.b. The first node may 122 | * be a non-integer (for instance, a string representing an Arabic 123 | * number). So convert it back with the current language's 124 | * convertNumber. 125 | * 126 | * @param {Array} nodes List [ {String|Number}, {String}, {String} ... ] 127 | * @return {string} selected pluralized form according to current 128 | * language. 129 | */ 130 | plural: function ( nodes ) { 131 | var count = parseFloat( this.language.convertNumber( nodes[ 0 ], 10 ) ), 132 | forms = nodes.slice( 1 ); 133 | 134 | return forms.length ? this.language.convertPlural( count, forms ) : ''; 135 | }, 136 | 137 | /** 138 | * Transform parsed structure into gender Usage 139 | * {{gender:gender|masculine|feminine|neutral}}. 140 | * 141 | * @param {Array} nodes List [ {String}, {String}, {String} , {String} ] 142 | * @return {string} selected gender form according to current language 143 | */ 144 | gender: function ( nodes ) { 145 | var gender = nodes[ 0 ], 146 | forms = nodes.slice( 1 ); 147 | 148 | return this.language.gender( gender, forms ); 149 | }, 150 | 151 | /** 152 | * Transform parsed structure into grammar conversion. Invoked by 153 | * putting {{grammar:form|word}} in a message 154 | * 155 | * @param {Array} nodes List [{Grammar case eg: genitive}, {String word}] 156 | * @return {string} selected grammatical form according to current 157 | * language. 158 | */ 159 | grammar: function ( nodes ) { 160 | var form = nodes[ 0 ], 161 | word = nodes[ 1 ]; 162 | 163 | return word && form && this.language.convertGrammar( word, form ); 164 | } 165 | }; 166 | 167 | $.extend( $.i18n.parser.emitter, new MessageParserEmitter() ); 168 | }( jQuery ) ); 169 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled 3 | org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore 4 | org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull 5 | org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault 6 | org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable 7 | org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled 8 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 9 | org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate 10 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 11 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 12 | org.eclipse.jdt.core.compiler.compliance=1.7 13 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 14 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 15 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 16 | org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning 17 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 18 | org.eclipse.jdt.core.compiler.problem.autoboxing=ignore 19 | org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning 20 | org.eclipse.jdt.core.compiler.problem.deadCode=warning 21 | org.eclipse.jdt.core.compiler.problem.deprecation=warning 22 | org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled 23 | org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled 24 | org.eclipse.jdt.core.compiler.problem.discouragedReference=warning 25 | org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore 26 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 27 | org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore 28 | org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore 29 | org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled 30 | org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore 31 | org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning 32 | org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning 33 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 34 | org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning 35 | org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled 36 | org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning 37 | org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning 38 | org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore 39 | org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore 40 | org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning 41 | org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore 42 | org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore 43 | org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled 44 | org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore 45 | org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore 46 | org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled 47 | org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning 48 | org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore 49 | org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning 50 | org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning 51 | org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore 52 | org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning 53 | org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error 54 | org.eclipse.jdt.core.compiler.problem.nullReference=warning 55 | org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error 56 | org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning 57 | org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning 58 | org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore 59 | org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore 60 | org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore 61 | org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore 62 | org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning 63 | org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning 64 | org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore 65 | org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore 66 | org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore 67 | org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore 68 | org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore 69 | org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled 70 | org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning 71 | org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled 72 | org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled 73 | org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled 74 | org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore 75 | org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning 76 | org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled 77 | org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning 78 | org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning 79 | org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore 80 | org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning 81 | org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore 82 | org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore 83 | org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore 84 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore 85 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled 86 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled 87 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled 88 | org.eclipse.jdt.core.compiler.problem.unusedImport=warning 89 | org.eclipse.jdt.core.compiler.problem.unusedLabel=warning 90 | org.eclipse.jdt.core.compiler.problem.unusedLocal=warning 91 | org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore 92 | org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore 93 | org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled 94 | org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled 95 | org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled 96 | org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning 97 | org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore 98 | org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning 99 | org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning 100 | org.eclipse.jdt.core.compiler.source=1.7 101 | -------------------------------------------------------------------------------- /WebContent/assets/js/jquery.i18n.parser.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Internationalization library 3 | * 4 | * Copyright (C) 2011-2013 Santhosh Thottingal, Neil Kandalgaonkar 5 | * 6 | * jquery.i18n is dual licensed GPLv2 or later and MIT. You don't have to do 7 | * anything special to choose one license or the other and you don't have to 8 | * notify anyone which license you are using. You are free to use 9 | * UniversalLanguageSelector in commercial projects as long as the copyright 10 | * header is left intact. See files GPL-LICENSE and MIT-LICENSE for details. 11 | * 12 | * @licence GNU General Public Licence 2.0 or later 13 | * @licence MIT License 14 | */ 15 | 16 | ( function ( $ ) { 17 | 'use strict'; 18 | 19 | var MessageParser = function ( options ) { 20 | this.options = $.extend( {}, $.i18n.parser.defaults, options ); 21 | this.language = $.i18n.languages[ String.locale ] || $.i18n.languages[ 'default' ]; 22 | this.emitter = $.i18n.parser.emitter; 23 | }; 24 | 25 | MessageParser.prototype = { 26 | 27 | constructor: MessageParser, 28 | 29 | simpleParse: function ( message, parameters ) { 30 | return message.replace( /\$(\d+)/g, function ( str, match ) { 31 | var index = parseInt( match, 10 ) - 1; 32 | 33 | return parameters[ index ] !== undefined ? parameters[ index ] : '$' + match; 34 | } ); 35 | }, 36 | 37 | parse: function ( message, replacements ) { 38 | if ( message.indexOf( '{{' ) < 0 ) { 39 | return this.simpleParse( message, replacements ); 40 | } 41 | 42 | this.emitter.language = $.i18n.languages[ $.i18n().locale ] || 43 | $.i18n.languages[ 'default' ]; 44 | 45 | return this.emitter.emit( this.ast( message ), replacements ); 46 | }, 47 | 48 | ast: function ( message ) { 49 | var pipe, colon, backslash, anyCharacter, dollar, digits, regularLiteral, 50 | regularLiteralWithoutBar, regularLiteralWithoutSpace, escapedOrLiteralWithoutBar, 51 | escapedOrRegularLiteral, templateContents, templateName, openTemplate, 52 | closeTemplate, expression, paramExpression, result, 53 | pos = 0; 54 | 55 | // Try parsers until one works, if none work return null 56 | function choice( parserSyntax ) { 57 | return function () { 58 | var i, result; 59 | 60 | for ( i = 0; i < parserSyntax.length; i++ ) { 61 | result = parserSyntax[ i ](); 62 | 63 | if ( result !== null ) { 64 | return result; 65 | } 66 | } 67 | 68 | return null; 69 | }; 70 | } 71 | 72 | // Try several parserSyntax-es in a row. 73 | // All must succeed; otherwise, return null. 74 | // This is the only eager one. 75 | function sequence( parserSyntax ) { 76 | var i, res, 77 | originalPos = pos, 78 | result = []; 79 | 80 | for ( i = 0; i < parserSyntax.length; i++ ) { 81 | res = parserSyntax[ i ](); 82 | 83 | if ( res === null ) { 84 | pos = originalPos; 85 | 86 | return null; 87 | } 88 | 89 | result.push( res ); 90 | } 91 | 92 | return result; 93 | } 94 | 95 | // Run the same parser over and over until it fails. 96 | // Must succeed a minimum of n times; otherwise, return null. 97 | function nOrMore( n, p ) { 98 | return function () { 99 | var originalPos = pos, 100 | result = [], 101 | parsed = p(); 102 | 103 | while ( parsed !== null ) { 104 | result.push( parsed ); 105 | parsed = p(); 106 | } 107 | 108 | if ( result.length < n ) { 109 | pos = originalPos; 110 | 111 | return null; 112 | } 113 | 114 | return result; 115 | }; 116 | } 117 | 118 | // Helpers -- just make parserSyntax out of simpler JS builtin types 119 | 120 | function makeStringParser( s ) { 121 | var len = s.length; 122 | 123 | return function () { 124 | var result = null; 125 | 126 | if ( message.slice( pos, pos + len ) === s ) { 127 | result = s; 128 | pos += len; 129 | } 130 | 131 | return result; 132 | }; 133 | } 134 | 135 | function makeRegexParser( regex ) { 136 | return function () { 137 | var matches = message.slice( pos ).match( regex ); 138 | 139 | if ( matches === null ) { 140 | return null; 141 | } 142 | 143 | pos += matches[ 0 ].length; 144 | 145 | return matches[ 0 ]; 146 | }; 147 | } 148 | 149 | pipe = makeStringParser( '|' ); 150 | colon = makeStringParser( ':' ); 151 | backslash = makeStringParser( '\\' ); 152 | anyCharacter = makeRegexParser( /^./ ); 153 | dollar = makeStringParser( '$' ); 154 | digits = makeRegexParser( /^\d+/ ); 155 | regularLiteral = makeRegexParser( /^[^{}\[\]$\\]/ ); 156 | regularLiteralWithoutBar = makeRegexParser( /^[^{}\[\]$\\|]/ ); 157 | regularLiteralWithoutSpace = makeRegexParser( /^[^{}\[\]$\s]/ ); 158 | 159 | // There is a general pattern: 160 | // parse a thing; 161 | // if it worked, apply transform, 162 | // otherwise return null. 163 | // But using this as a combinator seems to cause problems 164 | // when combined with nOrMore(). 165 | // May be some scoping issue. 166 | function transform( p, fn ) { 167 | return function () { 168 | var result = p(); 169 | 170 | return result === null ? null : fn( result ); 171 | }; 172 | } 173 | 174 | // Used to define "literals" within template parameters. The pipe 175 | // character is the parameter delimeter, so by default 176 | // it is not a literal in the parameter 177 | function literalWithoutBar() { 178 | var result = nOrMore( 1, escapedOrLiteralWithoutBar )(); 179 | 180 | return result === null ? null : result.join( '' ); 181 | } 182 | 183 | function literal() { 184 | var result = nOrMore( 1, escapedOrRegularLiteral )(); 185 | 186 | return result === null ? null : result.join( '' ); 187 | } 188 | 189 | function escapedLiteral() { 190 | var result = sequence( [ backslash, anyCharacter ] ); 191 | 192 | return result === null ? null : result[ 1 ]; 193 | } 194 | 195 | choice( [ escapedLiteral, regularLiteralWithoutSpace ] ); 196 | escapedOrLiteralWithoutBar = choice( [ escapedLiteral, regularLiteralWithoutBar ] ); 197 | escapedOrRegularLiteral = choice( [ escapedLiteral, regularLiteral ] ); 198 | 199 | function replacement() { 200 | var result = sequence( [ dollar, digits ] ); 201 | 202 | if ( result === null ) { 203 | return null; 204 | } 205 | 206 | return [ 'REPLACE', parseInt( result[ 1 ], 10 ) - 1 ]; 207 | } 208 | 209 | templateName = transform( 210 | // see $wgLegalTitleChars 211 | // not allowing : due to the need to catch "PLURAL:$1" 212 | makeRegexParser( /^[ !"$&'()*,.\/0-9;=?@A-Z\^_`a-z~\x80-\xFF+\-]+/ ), 213 | 214 | function ( result ) { 215 | return result.toString(); 216 | } 217 | ); 218 | 219 | function templateParam() { 220 | var expr, 221 | result = sequence( [ pipe, nOrMore( 0, paramExpression ) ] ); 222 | 223 | if ( result === null ) { 224 | return null; 225 | } 226 | 227 | expr = result[ 1 ]; 228 | 229 | // use a "CONCAT" operator if there are multiple nodes, 230 | // otherwise return the first node, raw. 231 | return expr.length > 1 ? [ 'CONCAT' ].concat( expr ) : expr[ 0 ]; 232 | } 233 | 234 | function templateWithReplacement() { 235 | var result = sequence( [ templateName, colon, replacement ] ); 236 | 237 | return result === null ? null : [ result[ 0 ], result[ 2 ] ]; 238 | } 239 | 240 | function templateWithOutReplacement() { 241 | var result = sequence( [ templateName, colon, paramExpression ] ); 242 | 243 | return result === null ? null : [ result[ 0 ], result[ 2 ] ]; 244 | } 245 | 246 | templateContents = choice( [ 247 | function () { 248 | var res = sequence( [ 249 | // templates can have placeholders for dynamic 250 | // replacement eg: {{PLURAL:$1|one car|$1 cars}} 251 | // or no placeholders eg: 252 | // {{GRAMMAR:genitive|{{SITENAME}}} 253 | choice( [ templateWithReplacement, templateWithOutReplacement ] ), 254 | nOrMore( 0, templateParam ) 255 | ] ); 256 | 257 | return res === null ? null : res[ 0 ].concat( res[ 1 ] ); 258 | }, 259 | function () { 260 | var res = sequence( [ templateName, nOrMore( 0, templateParam ) ] ); 261 | 262 | if ( res === null ) { 263 | return null; 264 | } 265 | 266 | return [ res[ 0 ] ].concat( res[ 1 ] ); 267 | } 268 | ] ); 269 | 270 | openTemplate = makeStringParser( '{{' ); 271 | closeTemplate = makeStringParser( '}}' ); 272 | 273 | function template() { 274 | var result = sequence( [ openTemplate, templateContents, closeTemplate ] ); 275 | 276 | return result === null ? null : result[ 1 ]; 277 | } 278 | 279 | expression = choice( [ template, replacement, literal ] ); 280 | paramExpression = choice( [ template, replacement, literalWithoutBar ] ); 281 | 282 | function start() { 283 | var result = nOrMore( 0, expression )(); 284 | 285 | if ( result === null ) { 286 | return null; 287 | } 288 | 289 | return [ 'CONCAT' ].concat( result ); 290 | } 291 | 292 | result = start(); 293 | 294 | /* 295 | * For success, the pos must have gotten to the end of the input 296 | * and returned a non-null. 297 | * n.b. This is part of language infrastructure, so we do not throw an internationalizable message. 298 | */ 299 | if ( result === null || pos !== message.length ) { 300 | throw new Error( 'Parse error at position ' + pos.toString() + ' in input: ' + message ); 301 | } 302 | 303 | return result; 304 | } 305 | 306 | }; 307 | 308 | $.extend( $.i18n.parser, new MessageParser() ); 309 | }( jQuery ) ); 310 | -------------------------------------------------------------------------------- /WebContent/assets/js/jquery.i18n.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Internationalization library 3 | * 4 | * Copyright (C) 2012 Santhosh Thottingal 5 | * 6 | * jquery.i18n is dual licensed GPLv2 or later and MIT. You don't have to do 7 | * anything special to choose one license or the other and you don't have to 8 | * notify anyone which license you are using. You are free to use 9 | * UniversalLanguageSelector in commercial projects as long as the copyright 10 | * header is left intact. See files GPL-LICENSE and MIT-LICENSE for details. 11 | * 12 | * @licence GNU General Public Licence 2.0 or later 13 | * @licence MIT License 14 | */ 15 | 16 | ( function ( $ ) { 17 | 'use strict'; 18 | 19 | var nav, I18N, 20 | slice = Array.prototype.slice; 21 | /** 22 | * @constructor 23 | * @param {Object} options 24 | */ 25 | I18N = function ( options ) { 26 | // Load defaults 27 | this.options = $.extend( {}, I18N.defaults, options ); 28 | 29 | this.parser = this.options.parser; 30 | this.locale = this.options.locale; 31 | this.messageStore = this.options.messageStore; 32 | this.languages = {}; 33 | 34 | this.init(); 35 | }; 36 | 37 | I18N.prototype = { 38 | /** 39 | * Initialize by loading locales and setting up 40 | * String.prototype.toLocaleString and String.locale. 41 | */ 42 | init: function () { 43 | var i18n = this; 44 | 45 | // Set locale of String environment 46 | String.locale = i18n.locale; 47 | 48 | // Override String.localeString method 49 | String.prototype.toLocaleString = function () { 50 | var localeParts, localePartIndex, value, locale, fallbackIndex, 51 | tryingLocale, message; 52 | 53 | value = this.valueOf(); 54 | locale = i18n.locale; 55 | fallbackIndex = 0; 56 | 57 | while ( locale ) { 58 | // Iterate through locales starting at most-specific until 59 | // localization is found. As in fi-Latn-FI, fi-Latn and fi. 60 | localeParts = locale.split( '-' ); 61 | localePartIndex = localeParts.length; 62 | 63 | do { 64 | tryingLocale = localeParts.slice( 0, localePartIndex ).join( '-' ); 65 | message = i18n.messageStore.get( tryingLocale, value ); 66 | 67 | if ( message ) { 68 | return message; 69 | } 70 | 71 | localePartIndex--; 72 | } while ( localePartIndex ); 73 | 74 | if ( locale === 'en' ) { 75 | break; 76 | } 77 | 78 | locale = ( $.i18n.fallbacks[ i18n.locale ] && $.i18n.fallbacks[ i18n.locale ][ fallbackIndex ] ) || 79 | i18n.options.fallbackLocale; 80 | $.i18n.log( 'Trying fallback locale for ' + i18n.locale + ': ' + locale ); 81 | 82 | fallbackIndex++; 83 | } 84 | 85 | // key not found 86 | return ''; 87 | }; 88 | }, 89 | 90 | /* 91 | * Destroy the i18n instance. 92 | */ 93 | destroy: function () { 94 | $.removeData( document, 'i18n' ); 95 | }, 96 | 97 | /** 98 | * General message loading API This can take a URL string for 99 | * the json formatted messages. Example: 100 | * load('path/to/all_localizations.json'); 101 | * 102 | * To load a localization file for a locale: 103 | * 104 | * load('path/to/de-messages.json', 'de' ); 105 | * 106 | * 107 | * To load a localization file from a directory: 108 | * 109 | * load('path/to/i18n/directory', 'de' ); 110 | * 111 | * The above method has the advantage of fallback resolution. 112 | * ie, it will automatically load the fallback locales for de. 113 | * For most usecases, this is the recommended method. 114 | * It is optional to have trailing slash at end. 115 | * 116 | * A data object containing message key- message translation mappings 117 | * can also be passed. Example: 118 | * 119 | * load( { 'hello' : 'Hello' }, optionalLocale ); 120 | * 121 | * 122 | * A source map containing key-value pair of languagename and locations 123 | * can also be passed. Example: 124 | * 125 | * load( { 126 | * bn: 'i18n/bn.json', 127 | * he: 'i18n/he.json', 128 | * en: 'i18n/en.json' 129 | * } ) 130 | * 131 | * 132 | * If the data argument is null/undefined/false, 133 | * all cached messages for the i18n instance will get reset. 134 | * 135 | * @param {string|Object} source 136 | * @param {string} locale Language tag 137 | * @return {jQuery.Promise} 138 | */ 139 | load: function ( source, locale ) { 140 | var fallbackLocales, locIndex, fallbackLocale, sourceMap = {}; 141 | if ( !source && !locale ) { 142 | source = 'i18n/' + $.i18n().locale + '.json'; 143 | locale = $.i18n().locale; 144 | } 145 | if ( typeof source === 'string' && 146 | source.split( '.' ).pop() !== 'json' 147 | ) { 148 | // Load specified locale then check for fallbacks when directory is specified in load() 149 | sourceMap[ locale ] = source + '/' + locale + '.json'; 150 | fallbackLocales = ( $.i18n.fallbacks[ locale ] || [] ) 151 | .concat( this.options.fallbackLocale ); 152 | for ( locIndex in fallbackLocales ) { 153 | fallbackLocale = fallbackLocales[ locIndex ]; 154 | sourceMap[ fallbackLocale ] = source + '/' + fallbackLocale + '.json'; 155 | } 156 | return this.load( sourceMap ); 157 | } else { 158 | return this.messageStore.load( source, locale ); 159 | } 160 | 161 | }, 162 | 163 | /** 164 | * Does parameter and magic word substitution. 165 | * 166 | * @param {string} key Message key 167 | * @param {Array} parameters Message parameters 168 | * @return {string} 169 | */ 170 | parse: function ( key, parameters ) { 171 | var message = key.toLocaleString(); 172 | // FIXME: This changes the state of the I18N object, 173 | // should probably not change the 'this.parser' but just 174 | // pass it to the parser. 175 | this.parser.language = $.i18n.languages[ $.i18n().locale ] || $.i18n.languages[ 'default' ]; 176 | if ( message === '' ) { 177 | message = key; 178 | } 179 | return this.parser.parse( message, parameters ); 180 | } 181 | }; 182 | 183 | /** 184 | * Process a message from the $.I18N instance 185 | * for the current document, stored in jQuery.data(document). 186 | * 187 | * @param {string} key Key of the message. 188 | * @param {string} param1 [param...] Variadic list of parameters for {key}. 189 | * @return {string|$.I18N} Parsed message, or if no key was given 190 | * the instance of $.I18N is returned. 191 | */ 192 | $.i18n = function ( key, param1 ) { 193 | var parameters, 194 | i18n = $.data( document, 'i18n' ), 195 | options = typeof key === 'object' && key; 196 | 197 | // If the locale option for this call is different then the setup so far, 198 | // update it automatically. This doesn't just change the context for this 199 | // call but for all future call as well. 200 | // If there is no i18n setup yet, don't do this. It will be taken care of 201 | // by the `new I18N` construction below. 202 | // NOTE: It should only change language for this one call. 203 | // Then cache instances of I18N somewhere. 204 | if ( options && options.locale && i18n && i18n.locale !== options.locale ) { 205 | String.locale = i18n.locale = options.locale; 206 | } 207 | 208 | if ( !i18n ) { 209 | i18n = new I18N( options ); 210 | $.data( document, 'i18n', i18n ); 211 | } 212 | 213 | if ( typeof key === 'string' ) { 214 | if ( param1 !== undefined ) { 215 | parameters = slice.call( arguments, 1 ); 216 | } else { 217 | parameters = []; 218 | } 219 | 220 | return i18n.parse( key, parameters ); 221 | } else { 222 | // FIXME: remove this feature/bug. 223 | return i18n; 224 | } 225 | }; 226 | 227 | $.fn.i18n = function () { 228 | var i18n = $.data( document, 'i18n' ); 229 | 230 | if ( !i18n ) { 231 | i18n = new I18N(); 232 | $.data( document, 'i18n', i18n ); 233 | } 234 | String.locale = i18n.locale; 235 | return this.each( function () { 236 | var $this = $( this ), 237 | messageKey = $this.data( 'i18n' ), 238 | lBracket, rBracket, type, key; 239 | 240 | if ( messageKey ) { 241 | lBracket = messageKey.indexOf( '[' ); 242 | rBracket = messageKey.indexOf( ']' ); 243 | if ( lBracket !== -1 && rBracket !== -1 && lBracket < rBracket ) { 244 | type = messageKey.slice( lBracket + 1, rBracket ); 245 | key = messageKey.slice( rBracket + 1 ); 246 | if ( type === 'html' ) { 247 | $this.html( i18n.parse( key ) ); 248 | } else { 249 | $this.attr( type, i18n.parse( key ) ); 250 | } 251 | } else { 252 | $this.text( i18n.parse( messageKey ) ); 253 | } 254 | } else { 255 | $this.find( '[data-i18n]' ).i18n(); 256 | } 257 | } ); 258 | }; 259 | 260 | String.locale = String.locale || $( 'html' ).attr( 'lang' ); 261 | 262 | if ( !String.locale ) { 263 | if ( typeof window.navigator !== undefined ) { 264 | nav = window.navigator; 265 | String.locale = nav.language || nav.userLanguage || ''; 266 | } else { 267 | String.locale = ''; 268 | } 269 | } 270 | 271 | $.i18n.languages = {}; 272 | $.i18n.messageStore = $.i18n.messageStore || {}; 273 | $.i18n.parser = { 274 | // The default parser only handles variable substitution 275 | parse: function ( message, parameters ) { 276 | return message.replace( /\$(\d+)/g, function ( str, match ) { 277 | var index = parseInt( match, 10 ) - 1; 278 | return parameters[ index ] !== undefined ? parameters[ index ] : '$' + match; 279 | } ); 280 | }, 281 | emitter: {} 282 | }; 283 | $.i18n.fallbacks = {}; 284 | $.i18n.debug = false; 285 | $.i18n.log = function ( /* arguments */ ) { 286 | if ( window.console && $.i18n.debug ) { 287 | window.console.log.apply( window.console, arguments ); 288 | } 289 | }; 290 | /* Static members */ 291 | I18N.defaults = { 292 | locale: String.locale, 293 | fallbackLocale: 'en', 294 | parser: $.i18n.parser, 295 | messageStore: $.i18n.messageStore 296 | }; 297 | 298 | // Expose constructor 299 | $.i18n.constructor = I18N; 300 | }( jQuery ) ); 301 | -------------------------------------------------------------------------------- /WebContent/assets/js/jquery.i18n.emitter.bidi.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * BIDI embedding support for jQuery.i18n 3 | * 4 | * Copyright (C) 2015, David Chan 5 | * 6 | * This code is dual licensed GPLv2 or later and MIT. You don't have to do 7 | * anything special to choose one license or the other and you don't have to 8 | * notify anyone which license you are using. You are free to use this code 9 | * in commercial projects as long as the copyright header is left intact. 10 | * See files GPL-LICENSE and MIT-LICENSE for details. 11 | * 12 | * @licence GNU General Public Licence 2.0 or later 13 | * @licence MIT License 14 | */ 15 | 16 | ( function ( $ ) { 17 | 'use strict'; 18 | var strongDirRegExp; 19 | 20 | /** 21 | * Matches the first strong directionality codepoint: 22 | * - in group 1 if it is LTR 23 | * - in group 2 if it is RTL 24 | * Does not match if there is no strong directionality codepoint. 25 | * 26 | * Generated by UnicodeJS (see tools/strongDir) from the UCD; see 27 | * https://phabricator.wikimedia.org/diffusion/GUJS/ . 28 | */ 29 | strongDirRegExp = new RegExp( 30 | '(?:' + 31 | '(' + 32 | '[\u0041-\u005a\u0061-\u007a\u00aa\u00b5\u00ba\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02b8\u02bb-\u02c1\u02d0\u02d1\u02e0-\u02e4\u02ee\u0370-\u0373\u0376\u0377\u037a-\u037d\u037f\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0482\u048a-\u052f\u0531-\u0556\u0559-\u055f\u0561-\u0587\u0589\u0903-\u0939\u093b\u093d-\u0940\u0949-\u094c\u094e-\u0950\u0958-\u0961\u0964-\u0980\u0982\u0983\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd-\u09c0\u09c7\u09c8\u09cb\u09cc\u09ce\u09d7\u09dc\u09dd\u09df-\u09e1\u09e6-\u09f1\u09f4-\u09fa\u0a03\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a3e-\u0a40\u0a59-\u0a5c\u0a5e\u0a66-\u0a6f\u0a72-\u0a74\u0a83\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd-\u0ac0\u0ac9\u0acb\u0acc\u0ad0\u0ae0\u0ae1\u0ae6-\u0af0\u0af9\u0b02\u0b03\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b3e\u0b40\u0b47\u0b48\u0b4b\u0b4c\u0b57\u0b5c\u0b5d\u0b5f-\u0b61\u0b66-\u0b77\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bbe\u0bbf\u0bc1\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcc\u0bd0\u0bd7\u0be6-\u0bf2\u0c01-\u0c03\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c39\u0c3d\u0c41-\u0c44\u0c58-\u0c5a\u0c60\u0c61\u0c66-\u0c6f\u0c7f\u0c82\u0c83\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd-\u0cc4\u0cc6-\u0cc8\u0cca\u0ccb\u0cd5\u0cd6\u0cde\u0ce0\u0ce1\u0ce6-\u0cef\u0cf1\u0cf2\u0d02\u0d03\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d-\u0d40\u0d46-\u0d48\u0d4a-\u0d4c\u0d4e\u0d57\u0d5f-\u0d61\u0d66-\u0d75\u0d79-\u0d7f\u0d82\u0d83\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0dcf-\u0dd1\u0dd8-\u0ddf\u0de6-\u0def\u0df2-\u0df4\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e4f-\u0e5b\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0ed0-\u0ed9\u0edc-\u0edf\u0f00-\u0f17\u0f1a-\u0f34\u0f36\u0f38\u0f3e-\u0f47\u0f49-\u0f6c\u0f7f\u0f85\u0f88-\u0f8c\u0fbe-\u0fc5\u0fc7-\u0fcc\u0fce-\u0fda\u1000-\u102c\u1031\u1038\u103b\u103c\u103f-\u1057\u105a-\u105d\u1061-\u1070\u1075-\u1081\u1083\u1084\u1087-\u108c\u108e-\u109c\u109e-\u10c5\u10c7\u10cd\u10d0-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1360-\u137c\u1380-\u138f\u13a0-\u13f5\u13f8-\u13fd\u1401-\u167f\u1681-\u169a\u16a0-\u16f8\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1735\u1736\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17b6\u17be-\u17c5\u17c7\u17c8\u17d4-\u17da\u17dc\u17e0-\u17e9\u1810-\u1819\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191e\u1923-\u1926\u1929-\u192b\u1930\u1931\u1933-\u1938\u1946-\u196d\u1970-\u1974\u1980-\u19ab\u19b0-\u19c9\u19d0-\u19da\u1a00-\u1a16\u1a19\u1a1a\u1a1e-\u1a55\u1a57\u1a61\u1a63\u1a64\u1a6d-\u1a72\u1a80-\u1a89\u1a90-\u1a99\u1aa0-\u1aad\u1b04-\u1b33\u1b35\u1b3b\u1b3d-\u1b41\u1b43-\u1b4b\u1b50-\u1b6a\u1b74-\u1b7c\u1b82-\u1ba1\u1ba6\u1ba7\u1baa\u1bae-\u1be5\u1be7\u1bea-\u1bec\u1bee\u1bf2\u1bf3\u1bfc-\u1c2b\u1c34\u1c35\u1c3b-\u1c49\u1c4d-\u1c7f\u1cc0-\u1cc7\u1cd3\u1ce1\u1ce9-\u1cec\u1cee-\u1cf3\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u200e\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u214f\u2160-\u2188\u2336-\u237a\u2395\u249c-\u24e9\u26ac\u2800-\u28ff\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d70\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u3005-\u3007\u3021-\u3029\u302e\u302f\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u3190-\u31ba\u31f0-\u321c\u3220-\u324f\u3260-\u327b\u327f-\u32b0\u32c0-\u32cb\u32d0-\u32fe\u3300-\u3376\u337b-\u33dd\u33e0-\u33fe\u3400-\u4db5\u4e00-\u9fd5\ua000-\ua48c\ua4d0-\ua60c\ua610-\ua62b\ua640-\ua66e\ua680-\ua69d\ua6a0-\ua6ef\ua6f2-\ua6f7\ua722-\ua787\ua789-\ua7ad\ua7b0-\ua7b7\ua7f7-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua824\ua827\ua830-\ua837\ua840-\ua873\ua880-\ua8c3\ua8ce-\ua8d9\ua8f2-\ua8fd\ua900-\ua925\ua92e-\ua946\ua952\ua953\ua95f-\ua97c\ua983-\ua9b2\ua9b4\ua9b5\ua9ba\ua9bb\ua9bd-\ua9cd\ua9cf-\ua9d9\ua9de-\ua9e4\ua9e6-\ua9fe\uaa00-\uaa28\uaa2f\uaa30\uaa33\uaa34\uaa40-\uaa42\uaa44-\uaa4b\uaa4d\uaa50-\uaa59\uaa5c-\uaa7b\uaa7d-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaaeb\uaaee-\uaaf5\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uab30-\uab65\uab70-\uabe4\uabe6\uabe7\uabe9-\uabec\uabf0-\uabf9\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\ue000-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc]|\ud800[\udc00-\udc0b]|\ud800[\udc0d-\udc26]|\ud800[\udc28-\udc3a]|\ud800\udc3c|\ud800\udc3d|\ud800[\udc3f-\udc4d]|\ud800[\udc50-\udc5d]|\ud800[\udc80-\udcfa]|\ud800\udd00|\ud800\udd02|\ud800[\udd07-\udd33]|\ud800[\udd37-\udd3f]|\ud800[\uddd0-\uddfc]|\ud800[\ude80-\ude9c]|\ud800[\udea0-\uded0]|\ud800[\udf00-\udf23]|\ud800[\udf30-\udf4a]|\ud800[\udf50-\udf75]|\ud800[\udf80-\udf9d]|\ud800[\udf9f-\udfc3]|\ud800[\udfc8-\udfd5]|\ud801[\udc00-\udc9d]|\ud801[\udca0-\udca9]|\ud801[\udd00-\udd27]|\ud801[\udd30-\udd63]|\ud801\udd6f|\ud801[\ude00-\udf36]|\ud801[\udf40-\udf55]|\ud801[\udf60-\udf67]|\ud804\udc00|\ud804[\udc02-\udc37]|\ud804[\udc47-\udc4d]|\ud804[\udc66-\udc6f]|\ud804[\udc82-\udcb2]|\ud804\udcb7|\ud804\udcb8|\ud804[\udcbb-\udcc1]|\ud804[\udcd0-\udce8]|\ud804[\udcf0-\udcf9]|\ud804[\udd03-\udd26]|\ud804\udd2c|\ud804[\udd36-\udd43]|\ud804[\udd50-\udd72]|\ud804[\udd74-\udd76]|\ud804[\udd82-\uddb5]|\ud804[\uddbf-\uddc9]|\ud804\uddcd|\ud804[\uddd0-\udddf]|\ud804[\udde1-\uddf4]|\ud804[\ude00-\ude11]|\ud804[\ude13-\ude2e]|\ud804\ude32|\ud804\ude33|\ud804\ude35|\ud804[\ude38-\ude3d]|\ud804[\ude80-\ude86]|\ud804\ude88|\ud804[\ude8a-\ude8d]|\ud804[\ude8f-\ude9d]|\ud804[\ude9f-\udea9]|\ud804[\udeb0-\udede]|\ud804[\udee0-\udee2]|\ud804[\udef0-\udef9]|\ud804\udf02|\ud804\udf03|\ud804[\udf05-\udf0c]|\ud804\udf0f|\ud804\udf10|\ud804[\udf13-\udf28]|\ud804[\udf2a-\udf30]|\ud804\udf32|\ud804\udf33|\ud804[\udf35-\udf39]|\ud804[\udf3d-\udf3f]|\ud804[\udf41-\udf44]|\ud804\udf47|\ud804\udf48|\ud804[\udf4b-\udf4d]|\ud804\udf50|\ud804\udf57|\ud804[\udf5d-\udf63]|\ud805[\udc80-\udcb2]|\ud805\udcb9|\ud805[\udcbb-\udcbe]|\ud805\udcc1|\ud805[\udcc4-\udcc7]|\ud805[\udcd0-\udcd9]|\ud805[\udd80-\uddb1]|\ud805[\uddb8-\uddbb]|\ud805\uddbe|\ud805[\uddc1-\udddb]|\ud805[\ude00-\ude32]|\ud805\ude3b|\ud805\ude3c|\ud805\ude3e|\ud805[\ude41-\ude44]|\ud805[\ude50-\ude59]|\ud805[\ude80-\udeaa]|\ud805\udeac|\ud805\udeae|\ud805\udeaf|\ud805\udeb6|\ud805[\udec0-\udec9]|\ud805[\udf00-\udf19]|\ud805\udf20|\ud805\udf21|\ud805\udf26|\ud805[\udf30-\udf3f]|\ud806[\udca0-\udcf2]|\ud806\udcff|\ud806[\udec0-\udef8]|\ud808[\udc00-\udf99]|\ud809[\udc00-\udc6e]|\ud809[\udc70-\udc74]|\ud809[\udc80-\udd43]|\ud80c[\udc00-\udfff]|\ud80d[\udc00-\udc2e]|\ud811[\udc00-\ude46]|\ud81a[\udc00-\ude38]|\ud81a[\ude40-\ude5e]|\ud81a[\ude60-\ude69]|\ud81a\ude6e|\ud81a\ude6f|\ud81a[\uded0-\udeed]|\ud81a\udef5|\ud81a[\udf00-\udf2f]|\ud81a[\udf37-\udf45]|\ud81a[\udf50-\udf59]|\ud81a[\udf5b-\udf61]|\ud81a[\udf63-\udf77]|\ud81a[\udf7d-\udf8f]|\ud81b[\udf00-\udf44]|\ud81b[\udf50-\udf7e]|\ud81b[\udf93-\udf9f]|\ud82c\udc00|\ud82c\udc01|\ud82f[\udc00-\udc6a]|\ud82f[\udc70-\udc7c]|\ud82f[\udc80-\udc88]|\ud82f[\udc90-\udc99]|\ud82f\udc9c|\ud82f\udc9f|\ud834[\udc00-\udcf5]|\ud834[\udd00-\udd26]|\ud834[\udd29-\udd66]|\ud834[\udd6a-\udd72]|\ud834\udd83|\ud834\udd84|\ud834[\udd8c-\udda9]|\ud834[\uddae-\udde8]|\ud834[\udf60-\udf71]|\ud835[\udc00-\udc54]|\ud835[\udc56-\udc9c]|\ud835\udc9e|\ud835\udc9f|\ud835\udca2|\ud835\udca5|\ud835\udca6|\ud835[\udca9-\udcac]|\ud835[\udcae-\udcb9]|\ud835\udcbb|\ud835[\udcbd-\udcc3]|\ud835[\udcc5-\udd05]|\ud835[\udd07-\udd0a]|\ud835[\udd0d-\udd14]|\ud835[\udd16-\udd1c]|\ud835[\udd1e-\udd39]|\ud835[\udd3b-\udd3e]|\ud835[\udd40-\udd44]|\ud835\udd46|\ud835[\udd4a-\udd50]|\ud835[\udd52-\udea5]|\ud835[\udea8-\udeda]|\ud835[\udedc-\udf14]|\ud835[\udf16-\udf4e]|\ud835[\udf50-\udf88]|\ud835[\udf8a-\udfc2]|\ud835[\udfc4-\udfcb]|\ud836[\udc00-\uddff]|\ud836[\ude37-\ude3a]|\ud836[\ude6d-\ude74]|\ud836[\ude76-\ude83]|\ud836[\ude85-\ude8b]|\ud83c[\udd10-\udd2e]|\ud83c[\udd30-\udd69]|\ud83c[\udd70-\udd9a]|\ud83c[\udde6-\ude02]|\ud83c[\ude10-\ude3a]|\ud83c[\ude40-\ude48]|\ud83c\ude50|\ud83c\ude51|[\ud840-\ud868][\udc00-\udfff]|\ud869[\udc00-\uded6]|\ud869[\udf00-\udfff]|[\ud86a-\ud86c][\udc00-\udfff]|\ud86d[\udc00-\udf34]|\ud86d[\udf40-\udfff]|\ud86e[\udc00-\udc1d]|\ud86e[\udc20-\udfff]|[\ud86f-\ud872][\udc00-\udfff]|\ud873[\udc00-\udea1]|\ud87e[\udc00-\ude1d]|[\udb80-\udbbe][\udc00-\udfff]|\udbbf[\udc00-\udffd]|[\udbc0-\udbfe][\udc00-\udfff]|\udbff[\udc00-\udffd]' + 33 | ')|(' + 34 | '[\u0590\u05be\u05c0\u05c3\u05c6\u05c8-\u05ff\u07c0-\u07ea\u07f4\u07f5\u07fa-\u0815\u081a\u0824\u0828\u082e-\u0858\u085c-\u089f\u200f\ufb1d\ufb1f-\ufb28\ufb2a-\ufb4f\u0608\u060b\u060d\u061b-\u064a\u066d-\u066f\u0671-\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u0710\u0712-\u072f\u074b-\u07a5\u07b1-\u07bf\u08a0-\u08e2\ufb50-\ufd3d\ufd40-\ufdcf\ufdf0-\ufdfc\ufdfe\ufdff\ufe70-\ufefe]|\ud802[\udc00-\udd1e]|\ud802[\udd20-\ude00]|\ud802\ude04|\ud802[\ude07-\ude0b]|\ud802[\ude10-\ude37]|\ud802[\ude3b-\ude3e]|\ud802[\ude40-\udee4]|\ud802[\udee7-\udf38]|\ud802[\udf40-\udfff]|\ud803[\udc00-\ude5f]|\ud803[\ude7f-\udfff]|\ud83a[\udc00-\udccf]|\ud83a[\udcd7-\udfff]|\ud83b[\udc00-\uddff]|\ud83b[\udf00-\udfff]|\ud83b[\udf00-\udfff]|\ud83b[\udf00-\udfff]|\ud83b[\udf00-\udfff]|\ud83b[\udf00-\udfff]|\ud83b[\udf00-\udfff]|\ud83b[\udf00-\udfff]|\ud83b[\udf00-\udfff]|\ud83b[\udf00-\udfff]|\ud83b[\udf00-\udfff]|\ud83b[\udf00-\udfff]|\ud83b[\udf00-\udfff]|\ud83b[\udf00-\udfff]|\ud83b[\ude00-\udeef]|\ud83b[\udef2-\udeff]' + 35 | ')' + 36 | ')' 37 | ); 38 | 39 | /** 40 | * Gets directionality of the first strongly directional codepoint 41 | * 42 | * This is the rule the BIDI algorithm uses to determine the directionality of 43 | * paragraphs ( http://unicode.org/reports/tr9/#The_Paragraph_Level ) and 44 | * FSI isolates ( http://unicode.org/reports/tr9/#Explicit_Directional_Isolates ). 45 | * 46 | * TODO: Does not handle BIDI control characters inside the text. 47 | * TODO: Does not handle unallocated characters. 48 | */ 49 | function strongDirFromContent( text ) { 50 | var m = text.match( strongDirRegExp ); 51 | if ( !m ) { 52 | return null; 53 | } 54 | if ( m[ 2 ] === undefined ) { 55 | return 'ltr'; 56 | } 57 | return 'rtl'; 58 | } 59 | 60 | $.extend( $.i18n.parser.emitter, { 61 | /** 62 | * Wraps argument with unicode control characters for directionality safety 63 | * 64 | * This solves the problem where directionality-neutral characters at the edge of 65 | * the argument string get interpreted with the wrong directionality from the 66 | * enclosing context, giving renderings that look corrupted like "(Ben_(WMF". 67 | * 68 | * The wrapping is LRE...PDF or RLE...PDF, depending on the detected 69 | * directionality of the argument string, using the BIDI algorithm's own "First 70 | * strong directional codepoint" rule. Essentially, this works round the fact that 71 | * there is no embedding equivalent of U+2068 FSI (isolation with heuristic 72 | * direction inference). The latter is cleaner but still not widely supported. 73 | */ 74 | bidi: function ( nodes ) { 75 | var dir = strongDirFromContent( nodes[ 0 ] ); 76 | if ( dir === 'ltr' ) { 77 | // Wrap in LEFT-TO-RIGHT EMBEDDING ... POP DIRECTIONAL FORMATTING 78 | return '\u202A' + nodes[ 0 ] + '\u202C'; 79 | } 80 | if ( dir === 'rtl' ) { 81 | // Wrap in RIGHT-TO-LEFT EMBEDDING ... POP DIRECTIONAL FORMATTING 82 | return '\u202B' + nodes[ 0 ] + '\u202C'; 83 | } 84 | // No strong directionality: do not wrap 85 | return nodes[ 0 ]; 86 | } 87 | } ); 88 | }( jQuery ) ); 89 | -------------------------------------------------------------------------------- /WebContent/assets/js/jquery.i18n.language.js: -------------------------------------------------------------------------------- 1 | /*global pluralRuleParser */ 2 | ( function ( $ ) { 3 | 'use strict'; 4 | 5 | var language = { 6 | // CLDR plural rules generated using 7 | // libs/CLDRPluralRuleParser/tools/PluralXML2JSON.html 8 | pluralRules: { 9 | ak: { 10 | one: 'n = 0..1' 11 | }, 12 | am: { 13 | one: 'i = 0 or n = 1' 14 | }, 15 | ar: { 16 | zero: 'n = 0', 17 | one: 'n = 1', 18 | two: 'n = 2', 19 | few: 'n % 100 = 3..10', 20 | many: 'n % 100 = 11..99' 21 | }, 22 | be: { 23 | one: 'n % 10 = 1 and n % 100 != 11', 24 | few: 'n % 10 = 2..4 and n % 100 != 12..14', 25 | many: 'n % 10 = 0 or n % 10 = 5..9 or n % 100 = 11..14' 26 | }, 27 | bh: { 28 | one: 'n = 0..1' 29 | }, 30 | bn: { 31 | one: 'i = 0 or n = 1' 32 | }, 33 | br: { 34 | one: 'n % 10 = 1 and n % 100 != 11,71,91', 35 | two: 'n % 10 = 2 and n % 100 != 12,72,92', 36 | few: 'n % 10 = 3..4,9 and n % 100 != 10..19,70..79,90..99', 37 | many: 'n != 0 and n % 1000000 = 0' 38 | }, 39 | bs: { 40 | one: 'v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11', 41 | few: 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14' 42 | }, 43 | cs: { 44 | one: 'i = 1 and v = 0', 45 | few: 'i = 2..4 and v = 0', 46 | many: 'v != 0' 47 | }, 48 | cy: { 49 | zero: 'n = 0', 50 | one: 'n = 1', 51 | two: 'n = 2', 52 | few: 'n = 3', 53 | many: 'n = 6' 54 | }, 55 | da: { 56 | one: 'n = 1 or t != 0 and i = 0,1' 57 | }, 58 | fa: { 59 | one: 'i = 0 or n = 1' 60 | }, 61 | ff: { 62 | one: 'i = 0,1' 63 | }, 64 | fil: { 65 | one: 'i = 0..1 and v = 0' 66 | }, 67 | fr: { 68 | one: 'i = 0,1' 69 | }, 70 | ga: { 71 | one: 'n = 1', 72 | two: 'n = 2', 73 | few: 'n = 3..6', 74 | many: 'n = 7..10' 75 | }, 76 | gd: { 77 | one: 'n = 1,11', 78 | two: 'n = 2,12', 79 | few: 'n = 3..10,13..19' 80 | }, 81 | gu: { 82 | one: 'i = 0 or n = 1' 83 | }, 84 | guw: { 85 | one: 'n = 0..1' 86 | }, 87 | gv: { 88 | one: 'n % 10 = 1', 89 | two: 'n % 10 = 2', 90 | few: 'n % 100 = 0,20,40,60' 91 | }, 92 | he: { 93 | one: 'i = 1 and v = 0', 94 | two: 'i = 2 and v = 0', 95 | many: 'v = 0 and n != 0..10 and n % 10 = 0' 96 | }, 97 | hi: { 98 | one: 'i = 0 or n = 1' 99 | }, 100 | hr: { 101 | one: 'v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11', 102 | few: 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14' 103 | }, 104 | hy: { 105 | one: 'i = 0,1' 106 | }, 107 | is: { 108 | one: 't = 0 and i % 10 = 1 and i % 100 != 11 or t != 0' 109 | }, 110 | iu: { 111 | one: 'n = 1', 112 | two: 'n = 2' 113 | }, 114 | iw: { 115 | one: 'i = 1 and v = 0', 116 | two: 'i = 2 and v = 0', 117 | many: 'v = 0 and n != 0..10 and n % 10 = 0' 118 | }, 119 | kab: { 120 | one: 'i = 0,1' 121 | }, 122 | kn: { 123 | one: 'i = 0 or n = 1' 124 | }, 125 | kw: { 126 | one: 'n = 1', 127 | two: 'n = 2' 128 | }, 129 | lag: { 130 | zero: 'n = 0', 131 | one: 'i = 0,1 and n != 0' 132 | }, 133 | ln: { 134 | one: 'n = 0..1' 135 | }, 136 | lt: { 137 | one: 'n % 10 = 1 and n % 100 != 11..19', 138 | few: 'n % 10 = 2..9 and n % 100 != 11..19', 139 | many: 'f != 0' 140 | }, 141 | lv: { 142 | zero: 'n % 10 = 0 or n % 100 = 11..19 or v = 2 and f % 100 = 11..19', 143 | one: 'n % 10 = 1 and n % 100 != 11 or v = 2 and f % 10 = 1 and f % 100 != 11 or v != 2 and f % 10 = 1' 144 | }, 145 | mg: { 146 | one: 'n = 0..1' 147 | }, 148 | mk: { 149 | one: 'v = 0 and i % 10 = 1 or f % 10 = 1' 150 | }, 151 | mo: { 152 | one: 'i = 1 and v = 0', 153 | few: 'v != 0 or n = 0 or n != 1 and n % 100 = 1..19' 154 | }, 155 | mr: { 156 | one: 'i = 0 or n = 1' 157 | }, 158 | mt: { 159 | one: 'n = 1', 160 | few: 'n = 0 or n % 100 = 2..10', 161 | many: 'n % 100 = 11..19' 162 | }, 163 | naq: { 164 | one: 'n = 1', 165 | two: 'n = 2' 166 | }, 167 | nso: { 168 | one: 'n = 0..1' 169 | }, 170 | pa: { 171 | one: 'n = 0..1' 172 | }, 173 | pl: { 174 | one: 'i = 1 and v = 0', 175 | few: 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14', 176 | many: 'v = 0 and i != 1 and i % 10 = 0..1 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 12..14' 177 | }, 178 | pt: { 179 | one: 'i = 1 and v = 0 or i = 0 and t = 1' 180 | }, 181 | // jscs:disable requireCamelCaseOrUpperCaseIdentifiers 182 | pt_PT: { 183 | one: 'n = 1 and v = 0' 184 | }, 185 | // jscs:enable requireCamelCaseOrUpperCaseIdentifiers 186 | ro: { 187 | one: 'i = 1 and v = 0', 188 | few: 'v != 0 or n = 0 or n != 1 and n % 100 = 1..19' 189 | }, 190 | ru: { 191 | one: 'v = 0 and i % 10 = 1 and i % 100 != 11', 192 | many: 'v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 11..14' 193 | }, 194 | se: { 195 | one: 'n = 1', 196 | two: 'n = 2' 197 | }, 198 | sh: { 199 | one: 'v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11', 200 | few: 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14' 201 | }, 202 | shi: { 203 | one: 'i = 0 or n = 1', 204 | few: 'n = 2..10' 205 | }, 206 | si: { 207 | one: 'n = 0,1 or i = 0 and f = 1' 208 | }, 209 | sk: { 210 | one: 'i = 1 and v = 0', 211 | few: 'i = 2..4 and v = 0', 212 | many: 'v != 0' 213 | }, 214 | sl: { 215 | one: 'v = 0 and i % 100 = 1', 216 | two: 'v = 0 and i % 100 = 2', 217 | few: 'v = 0 and i % 100 = 3..4 or v != 0' 218 | }, 219 | sma: { 220 | one: 'n = 1', 221 | two: 'n = 2' 222 | }, 223 | smi: { 224 | one: 'n = 1', 225 | two: 'n = 2' 226 | }, 227 | smj: { 228 | one: 'n = 1', 229 | two: 'n = 2' 230 | }, 231 | smn: { 232 | one: 'n = 1', 233 | two: 'n = 2' 234 | }, 235 | sms: { 236 | one: 'n = 1', 237 | two: 'n = 2' 238 | }, 239 | sr: { 240 | one: 'v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11', 241 | few: 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14' 242 | }, 243 | ti: { 244 | one: 'n = 0..1' 245 | }, 246 | tl: { 247 | one: 'i = 0..1 and v = 0' 248 | }, 249 | tzm: { 250 | one: 'n = 0..1 or n = 11..99' 251 | }, 252 | uk: { 253 | one: 'v = 0 and i % 10 = 1 and i % 100 != 11', 254 | few: 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14', 255 | many: 'v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 11..14' 256 | }, 257 | wa: { 258 | one: 'n = 0..1' 259 | }, 260 | zu: { 261 | one: 'i = 0 or n = 1' 262 | } 263 | }, 264 | 265 | /** 266 | * Plural form transformations, needed for some languages. 267 | * 268 | * @param {integer} count 269 | * Non-localized quantifier 270 | * @param {Array} forms 271 | * List of plural forms 272 | * @return {string} Correct form for quantifier in this language 273 | */ 274 | convertPlural: function ( count, forms ) { 275 | var pluralRules, 276 | pluralFormIndex, 277 | index, 278 | explicitPluralPattern = new RegExp( '\\d+=', 'i' ), 279 | formCount, 280 | form; 281 | 282 | if ( !forms || forms.length === 0 ) { 283 | return ''; 284 | } 285 | 286 | // Handle for Explicit 0= & 1= values 287 | for ( index = 0; index < forms.length; index++ ) { 288 | form = forms[ index ]; 289 | if ( explicitPluralPattern.test( form ) ) { 290 | formCount = parseInt( form.slice( 0, form.indexOf( '=' ) ), 10 ); 291 | if ( formCount === count ) { 292 | return ( form.slice( form.indexOf( '=' ) + 1 ) ); 293 | } 294 | forms[ index ] = undefined; 295 | } 296 | } 297 | 298 | forms = $.map( forms, function ( form ) { 299 | if ( form !== undefined ) { 300 | return form; 301 | } 302 | } ); 303 | 304 | pluralRules = this.pluralRules[ $.i18n().locale ]; 305 | 306 | if ( !pluralRules ) { 307 | // default fallback. 308 | return ( count === 1 ) ? forms[ 0 ] : forms[ 1 ]; 309 | } 310 | 311 | pluralFormIndex = this.getPluralForm( count, pluralRules ); 312 | pluralFormIndex = Math.min( pluralFormIndex, forms.length - 1 ); 313 | 314 | return forms[ pluralFormIndex ]; 315 | }, 316 | 317 | /** 318 | * For the number, get the plural for index 319 | * 320 | * @param {integer} number 321 | * @param {Object} pluralRules 322 | * @return {integer} plural form index 323 | */ 324 | getPluralForm: function ( number, pluralRules ) { 325 | var i, 326 | pluralForms = [ 'zero', 'one', 'two', 'few', 'many', 'other' ], 327 | pluralFormIndex = 0; 328 | 329 | for ( i = 0; i < pluralForms.length; i++ ) { 330 | if ( pluralRules[ pluralForms[ i ] ] ) { 331 | if ( pluralRuleParser( pluralRules[ pluralForms[ i ] ], number ) ) { 332 | return pluralFormIndex; 333 | } 334 | 335 | pluralFormIndex++; 336 | } 337 | } 338 | 339 | return pluralFormIndex; 340 | }, 341 | 342 | /** 343 | * Converts a number using digitTransformTable. 344 | * 345 | * @param {number} num Value to be converted 346 | * @param {boolean} integer Convert the return value to an integer 347 | */ 348 | convertNumber: function ( num, integer ) { 349 | var tmp, item, i, 350 | transformTable, numberString, convertedNumber; 351 | 352 | // Set the target Transform table: 353 | transformTable = this.digitTransformTable( $.i18n().locale ); 354 | numberString = String( num ); 355 | convertedNumber = ''; 356 | 357 | if ( !transformTable ) { 358 | return num; 359 | } 360 | 361 | // Check if the restore to Latin number flag is set: 362 | if ( integer ) { 363 | if ( parseFloat( num, 10 ) === num ) { 364 | return num; 365 | } 366 | 367 | tmp = []; 368 | 369 | for ( item in transformTable ) { 370 | tmp[ transformTable[ item ] ] = item; 371 | } 372 | 373 | transformTable = tmp; 374 | } 375 | 376 | for ( i = 0; i < numberString.length; i++ ) { 377 | if ( transformTable[ numberString[ i ] ] ) { 378 | convertedNumber += transformTable[ numberString[ i ] ]; 379 | } else { 380 | convertedNumber += numberString[ i ]; 381 | } 382 | } 383 | 384 | return integer ? parseFloat( convertedNumber, 10 ) : convertedNumber; 385 | }, 386 | 387 | /** 388 | * Grammatical transformations, needed for inflected languages. 389 | * Invoked by putting {{grammar:form|word}} in a message. 390 | * Override this method for languages that need special grammar rules 391 | * applied dynamically. 392 | * 393 | * @param {string} word 394 | * @param {string} form 395 | * @return {string} 396 | */ 397 | convertGrammar: function ( word, form ) { /*jshint unused: false */ 398 | return word; 399 | }, 400 | 401 | /** 402 | * Provides an alternative text depending on specified gender. Usage 403 | * {{gender:[gender|user object]|masculine|feminine|neutral}}. If second 404 | * or third parameter are not specified, masculine is used. 405 | * 406 | * These details may be overriden per language. 407 | * 408 | * @param {string} gender 409 | * male, female, or anything else for neutral. 410 | * @param {Array} forms 411 | * List of gender forms 412 | * 413 | * @return {string} 414 | */ 415 | gender: function ( gender, forms ) { 416 | if ( !forms || forms.length === 0 ) { 417 | return ''; 418 | } 419 | 420 | while ( forms.length < 2 ) { 421 | forms.push( forms[ forms.length - 1 ] ); 422 | } 423 | 424 | if ( gender === 'male' ) { 425 | return forms[ 0 ]; 426 | } 427 | 428 | if ( gender === 'female' ) { 429 | return forms[ 1 ]; 430 | } 431 | 432 | return ( forms.length === 3 ) ? forms[ 2 ] : forms[ 0 ]; 433 | }, 434 | 435 | /** 436 | * Get the digit transform table for the given language 437 | * See http://cldr.unicode.org/translation/numbering-systems 438 | * 439 | * @param {string} language 440 | * @return {Array|boolean} List of digits in the passed language or false 441 | * representation, or boolean false if there is no information. 442 | */ 443 | digitTransformTable: function ( language ) { 444 | var tables = { 445 | ar: '٠١٢٣٤٥٦٧٨٩', 446 | fa: '۰۱۲۳۴۵۶۷۸۹', 447 | ml: '൦൧൨൩൪൫൬൭൮൯', 448 | kn: '೦೧೨೩೪೫೬೭೮೯', 449 | lo: '໐໑໒໓໔໕໖໗໘໙', 450 | or: '୦୧୨୩୪୫୬୭୮୯', 451 | kh: '០១២៣៤៥៦៧៨៩', 452 | pa: '੦੧੨੩੪੫੬੭੮੯', 453 | gu: '૦૧૨૩૪૫૬૭૮૯', 454 | hi: '०१२३४५६७८९', 455 | my: '၀၁၂၃၄၅၆၇၈၉', 456 | ta: '௦௧௨௩௪௫௬௭௮௯', 457 | te: '౦౧౨౩౪౫౬౭౮౯', 458 | th: '๐๑๒๓๔๕๖๗๘๙', // FIXME use iso 639 codes 459 | bo: '༠༡༢༣༤༥༦༧༨༩' // FIXME use iso 639 codes 460 | }; 461 | 462 | if ( !tables[ language ] ) { 463 | return false; 464 | } 465 | 466 | return tables[ language ].split( '' ); 467 | } 468 | }; 469 | 470 | $.extend( $.i18n.languages, { 471 | 'default': language 472 | } ); 473 | }( jQuery ) ); 474 | -------------------------------------------------------------------------------- /WebContent/assets/js/main.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var spinner_opts = { 3 | lines: 10 // The number of lines to draw 4 | , 5 | length: 0 // The length of each line 6 | , 7 | width: 6 // The line thickness 8 | , 9 | radius: 10 // The radius of the inner circle 10 | , 11 | scale: 1 // Scales overall size of the spinner 12 | , 13 | corners: 1 // Corner roundness (0..1) 14 | , 15 | color: '#fff' // #rgb or #rrggbb or array of colors 16 | , 17 | opacity: 0 // Opacity of the lines 18 | , 19 | rotate: 0 // The rotation offset 20 | , 21 | direction: 1 // 1: clockwise, -1: counterclockwise 22 | , 23 | speed: 2 // Rounds per second 24 | , 25 | trail: 100 // Afterglow percentage 26 | , 27 | fps: 20 // Frames per second when using setTimeout() as a fallback for 28 | // CSS 29 | , 30 | zIndex: 2e9 // The z-index (defaults to 2000000000) 31 | , 32 | className: 'spinner' // The CSS class to assign to the spinner 33 | , 34 | top: '50%' // Top position relative to parent 35 | , 36 | left: '50%' // Left position relative to parent 37 | , 38 | shadow: true // Whether to render a shadow 39 | , 40 | hwaccel: true // Whether to use hardware acceleration 41 | , 42 | position: 'absolute' // Element positioning 43 | } 44 | 45 | function requestStatus(container) { 46 | var spinner = new Spinner(spinner_opts).spin(); 47 | var status = container.find('p.status'); 48 | var application = container.find('p.application'); 49 | 50 | status.removeClass("alert alert-success"); 51 | status.removeClass("alert alert-danger"); 52 | 53 | application.removeClass("alert alert-success"); 54 | application.removeClass("alert alert-warning"); 55 | application.removeClass("alert alert-danger"); 56 | 57 | status.after(spinner.el); 58 | status.html(""); 59 | application.html(""); 60 | $.ajax({ 61 | type: "GET", 62 | url: "rest/status/get/" + container.data('ip'), 63 | beforeSend: function() { 64 | container.find(".panelrefresh").attr("disabled", "disabled"); 65 | } 66 | }).done(function(data, textStatus, jqXHR) { 67 | var obj = JSON.parse(data); 68 | status.html(obj['status']); 69 | if (obj['status'] === "online") { 70 | status.addClass("alert alert-success"); 71 | application.addClass("alert alert-success"); 72 | // the Backdrop shall not be default ... 73 | if (obj['application'] === "Backdrop") 74 | application.addClass("alert alert-warning"); 75 | application.html(obj['application']); 76 | if (obj['application'] == "chromecast-kiosk-web" && obj['application']) { 77 | application.html('' + obj['application'] + ''); 78 | } 79 | } else { 80 | status.addClass("alert alert-danger"); 81 | application.addClass("alert alert-danger"); 82 | application.html("-"); 83 | } 84 | }).fail(function(jqXHR, textStatus, errorThrown) { 85 | // TODO error 86 | console.log("error"); 87 | }).always(function() { 88 | spinner.stop(); 89 | container.find(".panelrefresh").removeAttr("disabled", "disabled"); 90 | }); 91 | } 92 | 93 | function requestEach() { 94 | $('.chromecastpanel').each(function() { 95 | var attr = $(this).find(".panelrefresh").attr('disabled'); 96 | if (typeof attr !== typeof undefined && attr !== false) { 97 | // if its disabled do nothing 98 | } else { 99 | requestStatus($(this)); 100 | } 101 | }); 102 | } 103 | 104 | function addCast(ip, name) { 105 | $.ajax({ 106 | type: "POST", 107 | url: "rest/settings/add/" + ip + "/" + name 108 | }).done(function(data, textStatus, jqXHR) { 109 | var obj = JSON.parse(data); 110 | if (obj['error']) { 111 | toastr['warning'](obj['error']); 112 | console.log("error:" + obj['error']); 113 | } else { 114 | location.reload(); 115 | } 116 | }).fail(function(jqXHR, textStatus, errorThrown) { 117 | console.log("error"); 118 | }).always(function() { 119 | 120 | }); 121 | } 122 | 123 | $(document).ready(function() { 124 | requestEach(); 125 | 126 | $('#addcastbutton').click(function(ev) { 127 | ev.preventDefault(); 128 | var ip = $('#addip').val(); 129 | var name = $('#addname').val(); 130 | if (ip && name) { 131 | $('#addip').val(""); 132 | $('#addname').val(""); 133 | addCast(ip, name); 134 | } 135 | }); 136 | 137 | $('.paneltrash').click( 138 | function(ev) { 139 | ev.preventDefault(); 140 | $(this).parent().parent().parent().find( 141 | '.trashmsg').slideToggle(); 142 | }); 143 | 144 | $('a.option-info').click(function(ev) { 145 | ev.preventDefault(); 146 | var ip = $(this).data("ip"); 147 | if (ip) { 148 | $.ajax({ 149 | type: "POST", 150 | url: "rest/settings/update/" + ip + "/false" 151 | }).done(function(data, textStatus, jqXHR) { 152 | var obj = JSON.parse(data); 153 | if (obj['error']) { 154 | toastr['warning'](obj['error']); 155 | } else { 156 | toastr['info']("change done"); 157 | } 158 | }).fail(function(jqXHR, textStatus, errorThrown) { 159 | console.log("error"); 160 | }).always(function() { 161 | 162 | }); 163 | } 164 | }); 165 | 166 | $('a.option-default').click(function(ev) { 167 | console.log("grrr"); 168 | ev.preventDefault(); 169 | var ip = $(this).data("ip"); 170 | if (ip) { 171 | $.ajax({ 172 | type: "POST", 173 | url: "rest/settings/update/" + ip + "/true" 174 | }).done(function(data, textStatus, jqXHR) { 175 | var obj = JSON.parse(data); 176 | if (obj['error']) { 177 | toastr['warning'](obj['error']); 178 | } else { 179 | //location.reload(); 180 | toastr['info']("change done"); 181 | } 182 | }).fail(function(jqXHR, textStatus, errorThrown) { 183 | console.log("error"); 184 | }).always(function() { 185 | 186 | }); 187 | } 188 | }); 189 | 190 | $('.notrash').click(function(ev) { 191 | ev.preventDefault(); 192 | $(this).parent().parent().slideToggle(); 193 | }); 194 | 195 | $('.yestrash').click(function(ev) { 196 | ev.preventDefault(); 197 | var ip = $(this).data("ip"); 198 | if (ip) { 199 | $.ajax({ 200 | type: "DELETE", 201 | url: "rest/settings/remove/" + ip 202 | }).done(function(data, textStatus, jqXHR) { 203 | var obj = JSON.parse(data); 204 | if (obj['error']) { 205 | toastr['warning'](obj['error']); 206 | } else { 207 | location.reload(); 208 | } 209 | }).fail(function(jqXHR, textStatus, errorThrown) { 210 | console.log("error"); 211 | }).always(function() { 212 | 213 | }); 214 | } 215 | }); 216 | 217 | $('.panelrefresh').click( 218 | function(ev) { 219 | ev.preventDefault(); 220 | requestStatus($(this).parent().parent() 221 | .parent().parent()); 222 | }); 223 | 224 | $('#refreshall').click(function(ev) { 225 | ev.preventDefault(); 226 | requestEach(); 227 | }); 228 | 229 | $('#searchcasts').click(function(ev) { 230 | // ev.preventDefault(); 231 | 232 | $.ajax({ 233 | type: "GET", 234 | url: "rest/discovered/get", 235 | beforeSend: function() { 236 | $('#discoverchromecast').find('.modal-body').html(""); 237 | } 238 | }).done(function(data, textStatus, jqXHR) { 239 | var obj = JSON.parse(data); 240 | $.each(obj, function(i, elem) { 241 | console.log(elem); 242 | var row = '
'; 243 | row += ''; 252 | row += "
" 253 | $('#discoverchromecast').find('.modal-body').append(row); 254 | }); 255 | $('.discover-add').click(function(ev) { 256 | ev.preventDefault(); 257 | addCast($(this).data("ip"), 258 | $(this).data("name")); 259 | }); 260 | }) 261 | .fail(function(jqXHR, textStatus, errorThrown) { 262 | // TODO error 263 | consolelog("error"); 264 | }); 265 | }); 266 | 267 | $('#mediabutton').click(function(ev) { 268 | ev.preventDefault(); 269 | $(this).parent().addClass("active"); 270 | $('#main').slideUp(); 271 | $('#help').slideUp(); 272 | $('#cron').slideUp(); 273 | $('#sender').slideUp(); 274 | $('#media').slideDown(); 275 | $('#senderbutton').parent().removeClass("active"); 276 | $('#mainbutton').parent().removeClass("active"); 277 | $('#cronbutton').parent().removeClass("active"); 278 | $('#helpbutton').parent().removeClass("active"); 279 | $('#media-refresh').trigger('click'); 280 | }); 281 | 282 | $('#senderbutton').click(function(ev) { 283 | ev.preventDefault(); 284 | $(this).parent().addClass("active"); 285 | $('#main').slideUp(); 286 | $('#help').slideUp(); 287 | $('#cron').slideUp(); 288 | $('#media').slideUp(); 289 | $('#sender').slideDown(); 290 | $('#mainbutton').parent().removeClass("active"); 291 | $('#cronbutton').parent().removeClass("active"); 292 | $('#helpbutton').parent().removeClass("active"); 293 | $('#mediabutton').parent().removeClass("active"); 294 | }); 295 | 296 | $('#mainbutton').click(function(ev) { 297 | ev.preventDefault(); 298 | $(this).parent().addClass("active"); 299 | $('#sender').slideUp(); 300 | $('#help').slideUp(); 301 | $('#cron').slideUp(); 302 | $('#media').slideUp(); 303 | $('#main').slideDown(); 304 | $('#senderbutton').parent().removeClass("active"); 305 | $('#cronbutton').parent().removeClass("active"); 306 | $('#helpbutton').parent().removeClass("active"); 307 | $('#mediabutton').parent().removeClass("active"); 308 | }); 309 | 310 | $('#helpbutton').click(function(ev) { 311 | ev.preventDefault(); 312 | $(this).parent().addClass("active"); 313 | $('#main').slideUp(); 314 | $('#sender').slideUp(); 315 | $('#cron').slideUp(); 316 | $('#media').slideUp(); 317 | $('#help').slideDown(); 318 | $('#senderbutton').parent().removeClass("active"); 319 | $('#cronbutton').parent().removeClass("active"); 320 | $('#mainbutton').parent().removeClass("active"); 321 | $('#mediabutton').parent().removeClass("active"); 322 | }); 323 | 324 | $('#cronbutton').click(function(ev) { 325 | ev.preventDefault(); 326 | $(this).parent().addClass("active"); 327 | $('#help').slideUp(); 328 | $('#sender').slideUp(); 329 | $('#main').slideUp(); 330 | $('#media').slideUp(); 331 | $('#cron').slideDown(); 332 | $('#senderbutton').parent().removeClass("active"); 333 | $('#helpbutton').parent().removeClass("active"); 334 | $('#mainbutton').parent().removeClass("active"); 335 | $('#mediabutton').parent().removeClass("active"); 336 | $('#refreshjobs').trigger('click'); 337 | }); 338 | 339 | }); 340 | })(); 341 | -------------------------------------------------------------------------------- /WebContent/assets/js/jquery.history.js: -------------------------------------------------------------------------------- 1 | typeof JSON!="object"&&(JSON={}),function(){"use strict";function f(e){return e<10?"0"+e:e}function quote(e){return escapable.lastIndex=0,escapable.test(e)?'"'+e.replace(escapable,function(e){var t=meta[e];return typeof t=="string"?t:"\\u"+("0000"+e.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+e+'"'}function str(e,t){var n,r,i,s,o=gap,u,a=t[e];a&&typeof a=="object"&&typeof a.toJSON=="function"&&(a=a.toJSON(e)),typeof rep=="function"&&(a=rep.call(t,e,a));switch(typeof a){case"string":return quote(a);case"number":return isFinite(a)?String(a):"null";case"boolean":case"null":return String(a);case"object":if(!a)return"null";gap+=indent,u=[];if(Object.prototype.toString.apply(a)==="[object Array]"){s=a.length;for(n=0;n")&&n[0]);return e>4?e:!1}();return e},h.isInternetExplorer=function(){var e=h.isInternetExplorer.cached=typeof h.isInternetExplorer.cached!="undefined"?h.isInternetExplorer.cached:Boolean(h.getInternetExplorerMajorVersion());return e},h.options.html4Mode?h.emulated={pushState:!0,hashChange:!0}:h.emulated={pushState:!Boolean(e.history&&e.history.pushState&&e.history.replaceState&&!/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i.test(i.userAgent)&&!/AppleWebKit\/5([0-2]|3[0-2])/i.test(i.userAgent)),hashChange:Boolean(!("onhashchange"in e||"onhashchange"in r)||h.isInternetExplorer()&&h.getInternetExplorerMajorVersion()<8)},h.enabled=!h.emulated.pushState,h.bugs={setHash:Boolean(!h.emulated.pushState&&i.vendor==="Apple Computer, Inc."&&/AppleWebKit\/5([0-2]|3[0-3])/.test(i.userAgent)),safariPoll:Boolean(!h.emulated.pushState&&i.vendor==="Apple Computer, Inc."&&/AppleWebKit\/5([0-2]|3[0-3])/.test(i.userAgent)),ieDoubleCheck:Boolean(h.isInternetExplorer()&&h.getInternetExplorerMajorVersion()<8),hashEscape:Boolean(h.isInternetExplorer()&&h.getInternetExplorerMajorVersion()<7)},h.isEmptyObject=function(e){for(var t in e)if(e.hasOwnProperty(t))return!1;return!0},h.cloneObject=function(e){var t,n;return e?(t=l.stringify(e),n=l.parse(t)):n={},n},h.getRootUrl=function(){var e=r.location.protocol+"//"+(r.location.hostname||r.location.host);if(r.location.port||!1)e+=":"+r.location.port;return e+="/",e},h.getBaseHref=function(){var e=r.getElementsByTagName("base"),t=null,n="";return e.length===1&&(t=e[0],n=t.href.replace(/[^\/]+$/,"")),n=n.replace(/\/+$/,""),n&&(n+="/"),n},h.getBaseUrl=function(){var e=h.getBaseHref()||h.getBasePageUrl()||h.getRootUrl();return e},h.getPageUrl=function(){var e=h.getState(!1,!1),t=(e||{}).url||h.getLocationHref(),n;return n=t.replace(/\/+$/,"").replace(/[^\/]+$/,function(e,t,n){return/\./.test(e)?e:e+"/"}),n},h.getBasePageUrl=function(){var e=h.getLocationHref().replace(/[#\?].*/,"").replace(/[^\/]+$/,function(e,t,n){return/[^\/]$/.test(e)?"":e}).replace(/\/+$/,"")+"/";return e},h.getFullUrl=function(e,t){var n=e,r=e.substring(0,1);return t=typeof t=="undefined"?!0:t,/[a-z]+\:\/\//.test(e)||(r==="/"?n=h.getRootUrl()+e.replace(/^\/+/,""):r==="#"?n=h.getPageUrl().replace(/#.*/,"")+e:r==="?"?n=h.getPageUrl().replace(/[\?#].*/,"")+e:t?n=h.getBaseUrl()+e.replace(/^(\.\/)+/,""):n=h.getBasePageUrl()+e.replace(/^(\.\/)+/,"")),n.replace(/\#$/,"")},h.getShortUrl=function(e){var t=e,n=h.getBaseUrl(),r=h.getRootUrl();return h.emulated.pushState&&(t=t.replace(n,"")),t=t.replace(r,"/"),h.isTraditionalAnchor(t)&&(t="./"+t),t=t.replace(/^(\.\/)+/g,"./").replace(/\#$/,""),t},h.getLocationHref=function(e){return e=e||r,e.URL===e.location.href?e.location.href:e.location.href===decodeURIComponent(e.URL)?e.URL:e.location.hash&&decodeURIComponent(e.location.href.replace(/^[^#]+/,""))===e.location.hash?e.location.href:e.URL.indexOf("#")==-1&&e.location.href.indexOf("#")!=-1?e.location.href:e.URL||e.location.href},h.store={},h.idToState=h.idToState||{},h.stateToId=h.stateToId||{},h.urlToId=h.urlToId||{},h.storedStates=h.storedStates||[],h.savedStates=h.savedStates||[],h.normalizeStore=function(){h.store.idToState=h.store.idToState||{},h.store.urlToId=h.store.urlToId||{},h.store.stateToId=h.store.stateToId||{}},h.getState=function(e,t){typeof e=="undefined"&&(e=!0),typeof t=="undefined"&&(t=!0);var n=h.getLastSavedState();return!n&&t&&(n=h.createStateObject()),e&&(n=h.cloneObject(n),n.url=n.cleanUrl||n.url),n},h.getIdByState=function(e){var t=h.extractId(e.url),n;if(!t){n=h.getStateString(e);if(typeof h.stateToId[n]!="undefined")t=h.stateToId[n];else if(typeof h.store.stateToId[n]!="undefined")t=h.store.stateToId[n];else{for(;;){t=(new Date).getTime()+String(Math.random()).replace(/\D/g,"");if(typeof h.idToState[t]=="undefined"&&typeof h.store.idToState[t]=="undefined")break}h.stateToId[n]=t,h.idToState[t]=e}}return t},h.normalizeState=function(e){var t,n;if(!e||typeof e!="object")e={};if(typeof e.normalized!="undefined")return e;if(!e.data||typeof e.data!="object")e.data={};return t={},t.normalized=!0,t.title=e.title||"",t.url=h.getFullUrl(e.url?e.url:h.getLocationHref()),t.hash=h.getShortUrl(t.url),t.data=h.cloneObject(e.data),t.id=h.getIdByState(t),t.cleanUrl=t.url.replace(/\??\&_suid.*/,""),t.url=t.cleanUrl,n=!h.isEmptyObject(t.data),(t.title||n)&&h.options.disableSuid!==!0&&(t.hash=h.getShortUrl(t.url).replace(/\??\&_suid.*/,""),/\?/.test(t.hash)||(t.hash+="?"),t.hash+="&_suid="+t.id),t.hashedUrl=h.getFullUrl(t.hash),(h.emulated.pushState||h.bugs.safariPoll)&&h.hasUrlDuplicate(t)&&(t.url=t.hashedUrl),t},h.createStateObject=function(e,t,n){var r={data:e,title:t,url:n};return r=h.normalizeState(r),r},h.getStateById=function(e){e=String(e);var n=h.idToState[e]||h.store.idToState[e]||t;return n},h.getStateString=function(e){var t,n,r;return t=h.normalizeState(e),n={data:t.data,title:e.title,url:e.url},r=l.stringify(n),r},h.getStateId=function(e){var t,n;return t=h.normalizeState(e),n=t.id,n},h.getHashByState=function(e){var t,n;return t=h.normalizeState(e),n=t.hash,n},h.extractId=function(e){var t,n,r,i;return e.indexOf("#")!=-1?i=e.split("#")[0]:i=e,n=/(.*)\&_suid=([0-9]+)$/.exec(i),r=n?n[1]||e:e,t=n?String(n[2]||""):"",t||!1},h.isTraditionalAnchor=function(e){var t=!/[\/\?\.]/.test(e);return t},h.extractState=function(e,t){var n=null,r,i;return t=t||!1,r=h.extractId(e),r&&(n=h.getStateById(r)),n||(i=h.getFullUrl(e),r=h.getIdByUrl(i)||!1,r&&(n=h.getStateById(r)),!n&&t&&!h.isTraditionalAnchor(e)&&(n=h.createStateObject(null,null,i))),n},h.getIdByUrl=function(e){var n=h.urlToId[e]||h.store.urlToId[e]||t;return n},h.getLastSavedState=function(){return h.savedStates[h.savedStates.length-1]||t},h.getLastStoredState=function(){return h.storedStates[h.storedStates.length-1]||t},h.hasUrlDuplicate=function(e){var t=!1,n;return n=h.extractState(e.url),t=n&&n.id!==e.id,t},h.storeState=function(e){return h.urlToId[e.url]=e.id,h.storedStates.push(h.cloneObject(e)),e},h.isLastSavedState=function(e){var t=!1,n,r,i;return h.savedStates.length&&(n=e.id,r=h.getLastSavedState(),i=r.id,t=n===i),t},h.saveState=function(e){return h.isLastSavedState(e)?!1:(h.savedStates.push(h.cloneObject(e)),!0)},h.getStateByIndex=function(e){var t=null;return typeof e=="undefined"?t=h.savedStates[h.savedStates.length-1]:e<0?t=h.savedStates[h.savedStates.length+e]:t=h.savedStates[e],t},h.getCurrentIndex=function(){var e=null;return h.savedStates.length<1?e=0:e=h.savedStates.length-1,e},h.getHash=function(e){var t=h.getLocationHref(e),n;return n=h.getHashByUrl(t),n},h.unescapeHash=function(e){var t=h.normalizeHash(e);return t=decodeURIComponent(t),t},h.normalizeHash=function(e){var t=e.replace(/[^#]*#/,"").replace(/#.*/,"");return t},h.setHash=function(e,t){var n,i;return t!==!1&&h.busy()?(h.pushQueue({scope:h,callback:h.setHash,args:arguments,queue:t}),!1):(h.busy(!0),n=h.extractState(e,!0),n&&!h.emulated.pushState?h.pushState(n.data,n.title,n.url,!1):h.getHash()!==e&&(h.bugs.setHash?(i=h.getPageUrl(),h.pushState(null,null,i+"#"+e,!1)):r.location.hash=e),h)},h.escapeHash=function(t){var n=h.normalizeHash(t);return n=e.encodeURIComponent(n),h.bugs.hashEscape||(n=n.replace(/\%21/g,"!").replace(/\%26/g,"&").replace(/\%3D/g,"=").replace(/\%3F/g,"?")),n},h.getHashByUrl=function(e){var t=String(e).replace(/([^#]*)#?([^#]*)#?(.*)/,"$2");return t=h.unescapeHash(t),t},h.setTitle=function(e){var t=e.title,n;t||(n=h.getStateByIndex(0),n&&n.url===e.url&&(t=n.title||h.options.initialTitle));try{r.getElementsByTagName("title")[0].innerHTML=t.replace("<","<").replace(">",">").replace(" & "," & ")}catch(i){}return r.title=t,h},h.queues=[],h.busy=function(e){typeof e!="undefined"?h.busy.flag=e:typeof h.busy.flag=="undefined"&&(h.busy.flag=!1);if(!h.busy.flag){u(h.busy.timeout);var t=function(){var e,n,r;if(h.busy.flag)return;for(e=h.queues.length-1;e>=0;--e){n=h.queues[e];if(n.length===0)continue;r=n.shift(),h.fireQueueItem(r),h.busy.timeout=o(t,h.options.busyDelay)}};h.busy.timeout=o(t,h.options.busyDelay)}return h.busy.flag},h.busy.flag=!1,h.fireQueueItem=function(e){return e.callback.apply(e.scope||h,e.args||[])},h.pushQueue=function(e){return h.queues[e.queue||0]=h.queues[e.queue||0]||[],h.queues[e.queue||0].push(e),h},h.queue=function(e,t){return typeof e=="function"&&(e={callback:e}),typeof t!="undefined"&&(e.queue=t),h.busy()?h.pushQueue(e):h.fireQueueItem(e),h},h.clearQueue=function(){return h.busy.flag=!1,h.queues=[],h},h.stateChanged=!1,h.doubleChecker=!1,h.doubleCheckComplete=function(){return h.stateChanged=!0,h.doubleCheckClear(),h},h.doubleCheckClear=function(){return h.doubleChecker&&(u(h.doubleChecker),h.doubleChecker=!1),h},h.doubleCheck=function(e){return h.stateChanged=!1,h.doubleCheckClear(),h.bugs.ieDoubleCheck&&(h.doubleChecker=o(function(){return h.doubleCheckClear(),h.stateChanged||e(),!0},h.options.doubleCheckInterval)),h},h.safariStatePoll=function(){var t=h.extractState(h.getLocationHref()),n;if(!h.isLastSavedState(t))return n=t,n||(n=h.createStateObject()),h.Adapter.trigger(e,"popstate"),h;return},h.back=function(e){return e!==!1&&h.busy()?(h.pushQueue({scope:h,callback:h.back,args:arguments,queue:e}),!1):(h.busy(!0),h.doubleCheck(function(){h.back(!1)}),p.go(-1),!0)},h.forward=function(e){return e!==!1&&h.busy()?(h.pushQueue({scope:h,callback:h.forward,args:arguments,queue:e}),!1):(h.busy(!0),h.doubleCheck(function(){h.forward(!1)}),p.go(1),!0)},h.go=function(e,t){var n;if(e>0)for(n=1;n<=e;++n)h.forward(t);else{if(!(e<0))throw new Error("History.go: History.go requires a positive or negative integer passed.");for(n=-1;n>=e;--n)h.back(t)}return h};if(h.emulated.pushState){var v=function(){};h.pushState=h.pushState||v,h.replaceState=h.replaceState||v}else h.onPopState=function(t,n){var r=!1,i=!1,s,o;return h.doubleCheckComplete(),s=h.getHash(),s?(o=h.extractState(s||h.getLocationHref(),!0),o?h.replaceState(o.data,o.title,o.url,!1):(h.Adapter.trigger(e,"anchorchange"),h.busy(!1)),h.expectedStateId=!1,!1):(r=h.Adapter.extractEventData("state",t,n)||!1,r?i=h.getStateById(r):h.expectedStateId?i=h.getStateById(h.expectedStateId):i=h.extractState(h.getLocationHref()),i||(i=h.createStateObject(null,null,h.getLocationHref())),h.expectedStateId=!1,h.isLastSavedState(i)?(h.busy(!1),!1):(h.storeState(i),h.saveState(i),h.setTitle(i),h.Adapter.trigger(e,"statechange"),h.busy(!1),!0))},h.Adapter.bind(e,"popstate",h.onPopState),h.pushState=function(t,n,r,i){if(h.getHashByUrl(r)&&h.emulated.pushState)throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(i!==!1&&h.busy())return h.pushQueue({scope:h,callback:h.pushState,args:arguments,queue:i}),!1;h.busy(!0);var s=h.createStateObject(t,n,r);return h.isLastSavedState(s)?h.busy(!1):(h.storeState(s),h.expectedStateId=s.id,p.pushState(s.id,s.title,s.url),h.Adapter.trigger(e,"popstate")),!0},h.replaceState=function(t,n,r,i){if(h.getHashByUrl(r)&&h.emulated.pushState)throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(i!==!1&&h.busy())return h.pushQueue({scope:h,callback:h.replaceState,args:arguments,queue:i}),!1;h.busy(!0);var s=h.createStateObject(t,n,r);return h.isLastSavedState(s)?h.busy(!1):(h.storeState(s),h.expectedStateId=s.id,p.replaceState(s.id,s.title,s.url),h.Adapter.trigger(e,"popstate")),!0};if(s){try{h.store=l.parse(s.getItem("History.store"))||{}}catch(m){h.store={}}h.normalizeStore()}else h.store={},h.normalizeStore();h.Adapter.bind(e,"unload",h.clearAllIntervals),h.saveState(h.storeState(h.extractState(h.getLocationHref(),!0))),s&&(h.onUnload=function(){var e,t,n;try{e=l.parse(s.getItem("History.store"))||{}}catch(r){e={}}e.idToState=e.idToState||{},e.urlToId=e.urlToId||{},e.stateToId=e.stateToId||{};for(t in h.idToState){if(!h.idToState.hasOwnProperty(t))continue;e.idToState[t]=h.idToState[t]}for(t in h.urlToId){if(!h.urlToId.hasOwnProperty(t))continue;e.urlToId[t]=h.urlToId[t]}for(t in h.stateToId){if(!h.stateToId.hasOwnProperty(t))continue;e.stateToId[t]=h.stateToId[t]}h.store=e,h.normalizeStore(),n=l.stringify(e);try{s.setItem("History.store",n)}catch(i){if(i.code!==DOMException.QUOTA_EXCEEDED_ERR)throw i;s.length&&(s.removeItem("History.store"),s.setItem("History.store",n))}},h.intervalList.push(a(h.onUnload,h.options.storeInterval)),h.Adapter.bind(e,"beforeunload",h.onUnload),h.Adapter.bind(e,"unload",h.onUnload));if(!h.emulated.pushState){h.bugs.safariPoll&&h.intervalList.push(a(h.safariStatePoll,h.options.safariPollInterval));if(i.vendor==="Apple Computer, Inc."||(i.appCodeName||"")==="Mozilla")h.Adapter.bind(e,"hashchange",function(){h.Adapter.trigger(e,"popstate")}),h.getHash()&&h.Adapter.onDomLoad(function(){h.Adapter.trigger(e,"hashchange")})}},(!h.options||!h.options.delayInit)&&h.init()}(window) --------------------------------------------------------------------------------