'initial' commit
No files matched your search
@@ -0,0 +1,5 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
npm-debug.log
|
||||
config.json
|
||||
public/js/gscreen-config.js
|
||||
@@ -0,0 +1,12 @@
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,236 @@
|
||||
# GScreen
|
||||
|
||||
A digital signage solution using the Web and Chromecast devices.
|
||||
|
||||
Built with Angular.js, Node.js, Express.js, and CouchDB.
|
||||
|
||||
## How to Use
|
||||
|
||||
### Home
|
||||
|
||||

|
||||
|
||||
On the homepage, you will find several options currently at your disposal, namely:
|
||||
|
||||
- Chromcasts
|
||||
- Channels
|
||||
- Alerts
|
||||
- Channel Takeover
|
||||
|
||||
### Channels
|
||||
|
||||

|
||||
|
||||
Chromecasts tune into 'Channels', which refer to certain URLs.
|
||||
|
||||
Create a channel by clicking the 'Add a Channel' button.
|
||||
|
||||

|
||||
|
||||
Here, you can specify the name of your channel as well as the URL it points to.
|
||||
You can also choose to serve multiple URLs, which rotate at a specified time interval.
|
||||
|
||||

|
||||
|
||||
To edit a channel, click the name of your channel and adjust accordingly.
|
||||
|
||||
### Chromecasts
|
||||
|
||||

|
||||
|
||||
Here, you can browse the available chromecasts. Click 'Add a chromecast' to connect a new Chromecast.
|
||||
|
||||
You can also preview what that Chromecast is tuning into, by clicking 'Preview.'
|
||||
|
||||

|
||||
|
||||
You will be prompted by the Google Cast extension to select a Chromecast. Select one of the available devices.
|
||||
This will automatically populate the 'Name' field with the name of the Chromecast.
|
||||
|
||||
Describe the location of the Chromcast in the 'Location' field.
|
||||
|
||||
From the dropdown, select the channel that you want that chromecast to view.
|
||||
|
||||

|
||||
|
||||
You can edit a Chromecast by clicking a Chromecast's name. From here, you can edit the location and the channel.
|
||||
|
||||
That Chromecast will be automatically updated with that location and channel.
|
||||
|
||||
In the case that a Chromecast becomes disconnected, you can also reconnect a Chromecast by clicking 'Reconnect.'
|
||||
Just like adding a Chromecast, you can select the appropriate Chromecast, and it will associate itself with the current
|
||||
record.
|
||||
|
||||
### Alerts
|
||||
|
||||

|
||||
|
||||
You can also push an alert to all Chromecasts using 'Push Alert'.
|
||||
|
||||
Here, you can specify the alert's content, its type, and its duration.
|
||||
|
||||
Clicking 'Push Alert' will publicize this alert on all pages. Any subsequent alerts will currently override the current alert.
|
||||
|
||||

|
||||
|
||||
### Channel Takeover
|
||||
|
||||

|
||||
|
||||
Here, you can do a 'Channel Takeover' to set all Chromecasts to a specific channel, and also choose to stop said takeover.
|
||||
|
||||
## Getting Started: Local
|
||||
|
||||
### Installing dependencies
|
||||
|
||||
To install dependencies:
|
||||
|
||||
```shell
|
||||
brew install couchdb
|
||||
npm install
|
||||
```
|
||||
|
||||
You must also have the Google Cast Chrome extension, as well as a Chromecast setup application.
|
||||
|
||||
You'll also need at least one Chromecast :)
|
||||
|
||||
### Registering your Application
|
||||
|
||||
In order for your Chromecast to properly receive our Custom Receiver, your must first
|
||||
[register your Cast Application](https://developers.google.com/cast/docs/registration#RegisterApp).
|
||||
|
||||
Following the instructions, you should sign into the [Google Cast SDK Developer Console](https://cast.google.com/publish),
|
||||
create a new application, and select **Custom Receiver**.
|
||||
|
||||
In the **URL** field, you will need to provide a URL to the application.
|
||||
|
||||
By default, the URL you want will be:
|
||||
|
||||
```
|
||||
http://<YOUR-LOCAL-IP>:4994/setup-chromecast.html
|
||||
```
|
||||
|
||||
For OS X, you can find your local IP in System Preferences > Network, and will generally begin with `192.`
|
||||
|
||||
### Registering your Chromecasts
|
||||
|
||||
Along with registering your Chromecast Application, you will need to register your Chromecasts. This is done through the
|
||||
[Google Cast SDK Developer Console](https://cast.google.com/publish).
|
||||
|
||||
Serial numbers can be found on the back of your Chromcast. Once registration is complete, make sure to reboot your devices, which
|
||||
can be done through the Chromecast Desktop Application.
|
||||
|
||||
### Setting up your Chromecasts
|
||||
|
||||
Set up your chromecasts using the Chromecast Setup Application, through mobile or desktop (Desktop preferred).
|
||||
|
||||
Make sure that they are on the same Wi-Fi network as your local machine.
|
||||
|
||||
**Make sure that each Chromecast is configured to send its ID number via the Chromecast App.** In the desktop application,
|
||||
this involves checking the box that says to 'Send this Chromecast's serial number to Google when checking for updates'.
|
||||
|
||||
Once that preference is saved, it may be useful to reboot your chromecast. Doesn't hurt!
|
||||
|
||||
### Application Config
|
||||
|
||||
You will need to create and edit some config files.
|
||||
|
||||
```
|
||||
npm run create-config
|
||||
```
|
||||
|
||||
In `public/js/gscreen-config.js`, edit the `chromecastApplicationId` field to your Chromecast Application ID.
|
||||
|
||||
You can configure the couchDB and sever details in config.json.
|
||||
|
||||
### Starting the Server
|
||||
|
||||
**Phew!** Now that all of that yucky registration is done, you're ready to start the Greenscreen server.
|
||||
|
||||
Start up couchdb with:
|
||||
|
||||
```shell
|
||||
couchdb
|
||||
```
|
||||
|
||||
Start the server with:
|
||||
|
||||
```
|
||||
npm start
|
||||
```
|
||||
|
||||
(Optional) You can seed the database with dummy chromecasts.
|
||||
|
||||
```shell
|
||||
npm run seed
|
||||
```
|
||||
|
||||
View the project locally at [http://localhost:4994](http://localhost:4994).
|
||||
|
||||
## Developing
|
||||
|
||||
The client-side code is bundled from the `src/client` directory. To build, run:
|
||||
|
||||
```shell
|
||||
npm run bundle
|
||||
```
|
||||
|
||||
This will watch all changes to client side code, and bundle it to public/js/gscreen.js.
|
||||
|
||||
`public/js/gscreen.js` **is automatically generated, so don't edit this file!**
|
||||
|
||||
While the server is running with `npm start`, changes in the `src/server` will cause the server to restart
|
||||
with the new changes.
|
||||
|
||||
## Tests
|
||||
|
||||
End-to-end tests are run using protractor. To start tests:
|
||||
|
||||
You will need protractor installed:
|
||||
|
||||
```sh
|
||||
npm install -g protractor
|
||||
```
|
||||
|
||||
Start up the application locally, and then:
|
||||
|
||||
Start a webdriver-manager:
|
||||
|
||||
```sh
|
||||
npm run webdriver
|
||||
```
|
||||
|
||||
Start the tests:
|
||||
```sh
|
||||
npm test
|
||||
```
|
||||
|
||||
|
||||
## Known Issues
|
||||
|
||||
> When adding a Chromecast, the Chrome extension may time out on requesting a Session, though the Chromecast may successfully
|
||||
> display the 'Setting up Chromecast' page. This timeout occurs because connecting to your local server tends to be pretty slow.
|
||||
|
||||
To remedy this, select the Chromecast in the Google Cast extension, and select 'Stop Casting.' You'll need to
|
||||
navigate back to the Chromecasts list, and click 'Add new Chromecast' to try again.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
> My Chromecasts aren't appearing in the Google Cast extension!
|
||||
|
||||
Make sure that you are able to see your Chromecast(s) in the Google Cast extension on other tabs/pages. If not, this is
|
||||
most likely an error with your extension, or your connection.
|
||||
|
||||
- Check that your Chromecast and your local development server are on the same Wi-Fi network
|
||||
- Try rebooting your Chromcasts through the desktop applications or manually, and reconnecting them.
|
||||
- Try restarting your Chrome browser (Completely quit the application and re-open)
|
||||
|
||||
**When all else fails, reboot your chromecasts, and restart Chrome.**
|
||||
|
||||
## Security
|
||||
|
||||
Obviously, you don't neccessarily want to have everyone able to manipulate every Chromecast through the web
|
||||
interface. Currently, the best way to secure this is by setting up a private Wi-Fi network for the Chromecasts
|
||||
to connect to.
|
||||
|
||||
#### Good luck, and happy casting! :)
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"couch": {
|
||||
"host": "http://localhost",
|
||||
"port": 5984,
|
||||
"db": "gscreen"
|
||||
},
|
||||
"server": {
|
||||
"port": 4994
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "gscreen",
|
||||
"version": "0.0.0",
|
||||
"description": "Digital signage using Google Chromecast",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "nodemon src/server/server.coffee",
|
||||
"test": "protractor tests/conf.js",
|
||||
"webdriver": "webdriver-manager start",
|
||||
"bundle": "node_modules/commonjs-everywhere/bin/cjsify -o public/js/gscreen.js src/client/gscreen.coffee -w",
|
||||
"seed": "coffee scripts/seed",
|
||||
"create-config": "cp config.template.json config.json && cp public/js/gscreen-config.template.js public/js/gscreen-config.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.groupondev.com:cpowers/gscreen.git"
|
||||
},
|
||||
"author": "Chris Powers <cpowers@groupon.com>",
|
||||
"contributors": [
|
||||
"Mike Aparicio <maparicio@groupon.com>",
|
||||
"Edwin Zhang <ehzhang@mit.edu>"
|
||||
],
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"body-parser": "1.4.3",
|
||||
"coffee-script": "^1.7.1",
|
||||
"commonjs-everywhere": "~0.9.7",
|
||||
"cradle": "~0.6.7",
|
||||
"express": "^4.5.1",
|
||||
"method-override": "^2.1.2",
|
||||
"morgan": "^1.2.2",
|
||||
"socket.io": "^1.0.6",
|
||||
"underscore": "^1.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"coffee-script": "^1.7.1",
|
||||
"nodemon": "^1.2.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html ng-app="GScreen" ng-controller="Screen">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Greenscreen - {{ channel.name }}</title>
|
||||
<link rel="stylesheet" href="/css/app.css">
|
||||
<link rel="stylesheet" href="/css/channel.css">
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script src="/js/vendor/jquery.js"></script>
|
||||
<script src="/js/vendor/underscore.js"></script>
|
||||
<script src="/js/vendor/angular.js"></script>
|
||||
<script src="/js/vendor/angular-route.js"></script>
|
||||
<script src="/js/vendor/angular-resource.js"></script>
|
||||
<script src="/js/gscreen.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="template-full-screen widescreen">
|
||||
<div class="main-window">
|
||||
<iframe src="{{ mainContentUrl }}"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,760 @@
|
||||
/*! normalize.css v3.0.0 | MIT License | git.io/normalize */
|
||||
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
main,
|
||||
nav,
|
||||
section,
|
||||
summary {
|
||||
display: block;
|
||||
}
|
||||
audio,
|
||||
canvas,
|
||||
progress,
|
||||
video {
|
||||
display: inline-block;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
[hidden],
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
a {
|
||||
background: transparent;
|
||||
}
|
||||
a:active,
|
||||
a:hover {
|
||||
outline: 0;
|
||||
}
|
||||
abbr[title] {
|
||||
border-bottom: 1px dotted;
|
||||
}
|
||||
b,
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
mark {
|
||||
background: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
figure {
|
||||
margin: 0 0 20px 0;
|
||||
}
|
||||
hr {
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
}
|
||||
pre {
|
||||
overflow: auto;
|
||||
}
|
||||
code,
|
||||
kbd,
|
||||
pre,
|
||||
samp {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
button {
|
||||
overflow: visible;
|
||||
}
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
button,
|
||||
html input[type="button"],
|
||||
input[type="reset"],
|
||||
input[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
cursor: pointer;
|
||||
}
|
||||
button[disabled],
|
||||
html input[disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
input {
|
||||
line-height: normal;
|
||||
}
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
input[type="search"] {
|
||||
-webkit-appearance: textfield;
|
||||
-moz-box-sizing: content-box;
|
||||
-webkit-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
fieldset {
|
||||
border: 1px solid #c0c0c0;
|
||||
margin: 0 2px;
|
||||
padding: 0.35em 0.625em 0.75em;
|
||||
}
|
||||
legend {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
optgroup {
|
||||
font-weight: bold;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
td,
|
||||
th {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Fonts */
|
||||
|
||||
@font-face{
|
||||
font-family: OpenSansRegular;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: url("fonts/OpenSansRegular-v1.eot");
|
||||
src: url("fonts/OpenSansRegular-v1.woff") format('woff'),
|
||||
url("fonts/OpenSansRegular-v1.ttf") format('truetype'),
|
||||
url("fonts/OpenSansRegular-v1.woff#OpenSansRegular") format('svg');
|
||||
}
|
||||
@font-face{
|
||||
font-family: OpenSansLight;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: url("fonts/OpenSansLight-v1.eot");
|
||||
src: url("fonts/OpenSansLight-v1.woff") format('woff'),
|
||||
url("fonts/OpenSansLight-v1.ttf") format('truetype'),
|
||||
url("fonts/OpenSansLight-v1.woff#OpenSansLight") format('svg');
|
||||
}
|
||||
@font-face{
|
||||
font-family: OpenSansSemiBold;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: url("fonts/OpenSansSemiBold-v1.eot");
|
||||
src: url("fonts/OpenSansSemiBold-v1.woff") format('woff'),
|
||||
url("fonts/OpenSansSemiBold-v1.ttf") format('truetype'),
|
||||
url("fonts/OpenSansSemiBold-v1.woff#OpenSansSemiBold") format('svg');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Gscreen';
|
||||
src:url('fonts/Gscreen.eot?-bfkvnn');
|
||||
src:url('fonts/Gscreen.eot?#iefix-bfkvnn') format('embedded-opentype'),
|
||||
url('fonts/Gscreen.woff?-bfkvnn') format('woff'),
|
||||
url('fonts/Gscreen.ttf?-bfkvnn') format('truetype'),
|
||||
url('fonts/Gscreen.svg?-bfkvnn#Gscreen') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
[class^="icon-"], [class*=" icon-"] {
|
||||
font-family: 'Gscreen';
|
||||
speak: none;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
/* Better Font Rendering =========== */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
.icon-plus:before {
|
||||
content: "\e600";
|
||||
}
|
||||
.icon-down:before {
|
||||
content: "\e601";
|
||||
}
|
||||
.icon-logo-g:before {
|
||||
content: "\e602";
|
||||
}
|
||||
.icon-times:before {
|
||||
content: "\e603";
|
||||
}
|
||||
.icon-cancel:before {
|
||||
content: "\e604";
|
||||
}
|
||||
.icon-eye:before {
|
||||
content: "\e605";
|
||||
}
|
||||
.icon-cog:before {
|
||||
content: "\e606";
|
||||
}
|
||||
|
||||
/* Grid */
|
||||
|
||||
.row {
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 .625em;
|
||||
}
|
||||
.column {
|
||||
-webkit-flex: 0 0 100%;
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
[class^="column"] {
|
||||
padding: 0 .625em;
|
||||
}
|
||||
.column--mobile-2of5 { -webkit-flex: 1 0 40%; flex: 1 0 40%; }
|
||||
.column--mobile-1of2 { -webkit-flex: 1 0 50%; flex: 1 0 50%; }
|
||||
.column--mobile-3of5 { -webkit-flex: 1 0 60%; flex: 1 0 60%; }
|
||||
.column--mobile-2of3 { -webkit-flex: 1 0 66.6666%; flex: 1 0 66.6666%; }
|
||||
.column--mobile-3of4 { -webkit-flex: 1 0 75%; flex: 1 0 75%; }
|
||||
.column--mobile-4of5 { -webkit-flex: 1 0 80%; flex: 1 0 80%; }
|
||||
@media (min-width: 500px) {
|
||||
.row {
|
||||
max-width: 960px;
|
||||
}
|
||||
.column {
|
||||
-webkit-flex: 1 1 auto;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.column--2of5 { -webkit-flex: 0 0 40%; flex: 0 0 40%; }
|
||||
.column--1of2 { -webkit-flex: 0 0 50%; flex: 0 0 50%; }
|
||||
.column--3of5 { -webkit-flex: 0 0 60%; flex: 0 0 60%; }
|
||||
.column--2of3 { -webkit-flex: 0 0 66.6666%; flex: 0 0 66.6666%; }
|
||||
.column--3of4 { -webkit-flex: 0 0 75%; flex: 0 0 75%; }
|
||||
.column--4of5 { -webkit-flex: 0 0 80%; flex: 0 0 80%; }
|
||||
}
|
||||
@media (min-width: 980px) {
|
||||
.row {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* Global Styles */
|
||||
|
||||
* {
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
body {
|
||||
font-family: 'OpenSansRegular', 'Helvetica Neue', Arial, sans-serif;
|
||||
color: #222;
|
||||
}
|
||||
h1, h2, th, strong, label {
|
||||
font-weight: normal;
|
||||
font-family: 'OpenSansSemiBold';
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.25em;
|
||||
line-height: 1.6em;
|
||||
margin: 0 0 1em 0;
|
||||
font-family: 'OpenSansRegular';
|
||||
font-weight: normal;
|
||||
}
|
||||
a {
|
||||
color: #0185c6;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
margin: 0 0 .5em 0;
|
||||
}
|
||||
th {
|
||||
text-align: left;
|
||||
border-bottom: 2px solid #ccc;
|
||||
}
|
||||
td {
|
||||
border-bottom: 1px solid #f2f2f2;
|
||||
}
|
||||
th, td {
|
||||
font-size: .8em;
|
||||
height: 3em;
|
||||
padding: 0 1.25em;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
font-size: .8em;
|
||||
margin: 0 0 .25em 0;
|
||||
}
|
||||
input, select {
|
||||
margin: 0 0 1em 0;
|
||||
width: 100%;
|
||||
}
|
||||
input[type="text"],
|
||||
input[type="number"],
|
||||
select,
|
||||
textarea {
|
||||
padding: .5em;
|
||||
border: 1px solid #ccc;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
textarea {
|
||||
display: block;
|
||||
width: 100%;
|
||||
resize: none;
|
||||
margin: 0 0 .25em 0;
|
||||
}
|
||||
select {
|
||||
-webkit-appearance: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
.input-group {
|
||||
position: relative;
|
||||
}
|
||||
.input-group .icon-down {
|
||||
color: #666;
|
||||
position: absolute;
|
||||
font-size: 1.25em;
|
||||
right: .5em;
|
||||
top: 1.55em;
|
||||
}
|
||||
.flex {
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
}
|
||||
/* App Styles */
|
||||
header {
|
||||
background-color: #82b548;
|
||||
color: #fff;
|
||||
}
|
||||
.logo {
|
||||
font-size: 1.5em;
|
||||
line-height: 2em;
|
||||
margin: 0;
|
||||
font-family: 'OpenSansLight';
|
||||
}
|
||||
.logo a {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
.logo .icon-logo-g:before {
|
||||
margin-right: 2px;
|
||||
}
|
||||
.user {
|
||||
font-size: .8em;
|
||||
line-height: 3.8em;
|
||||
display: block;
|
||||
text-align: right;
|
||||
font-family: 'OpenSansLight';
|
||||
}
|
||||
.flex h2 {
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
}
|
||||
section {
|
||||
padding: 1.25em;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
section + section {
|
||||
border-top: 1px solid #ccc;
|
||||
}
|
||||
.main-nav {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.main-nav li {
|
||||
margin-bottom: .625em;
|
||||
}
|
||||
.main-nav a {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
border-radius: 3px;
|
||||
line-height: 2.8em;
|
||||
background-color: #0185c6;
|
||||
color: #fff;
|
||||
font-family: 'OpenSansLight';
|
||||
}
|
||||
.btn, input[type="submit"] {
|
||||
background-color: #0185c6;
|
||||
color: #fff;
|
||||
border: 0;
|
||||
border-radius: 3px;
|
||||
font-size: .8em;
|
||||
line-height: 3.5em;
|
||||
height: 44px;
|
||||
padding: 0 .78125em;
|
||||
margin: 0 0 .78125em 0;
|
||||
font-family: 'OpenSansLight';
|
||||
}
|
||||
.btn-red, input[type="submit"].btn-red {
|
||||
background-color: #ba0c2f;
|
||||
}
|
||||
.btn-add, .btn-delete {
|
||||
line-height: 2.5em;
|
||||
height: 2.5em;
|
||||
-webkit-align-self: flex-start;
|
||||
align-self: flex-start;
|
||||
text-decoration: none;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.btn-add:before,
|
||||
.btn-delete:before {
|
||||
font-family: 'Gscreen';
|
||||
margin-right: 2px;
|
||||
}
|
||||
.btn-add:before {
|
||||
content: "\e600";
|
||||
}
|
||||
.btn-delete:before {
|
||||
content: "\e603";
|
||||
}
|
||||
|
||||
.edit-link {
|
||||
text-decoration: none;
|
||||
font-size: 2em;
|
||||
line-height: 1.5em;
|
||||
color: #666;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
.edit-link:hover {
|
||||
color: #333;
|
||||
}
|
||||
.non-editable {
|
||||
font-size: 2em;
|
||||
line-height: 1.5em;
|
||||
color: #ccc;
|
||||
}
|
||||
.channel-link {
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
line-height: 3em;
|
||||
}
|
||||
.channel-link:hover {
|
||||
color: #ba0c2f;
|
||||
}
|
||||
.channel-table th:first-child {
|
||||
width: 100%;
|
||||
}
|
||||
.chromecast-table td:first-child {
|
||||
width: 25%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.chromecast-table td:nth-child(2) {
|
||||
width: 50%;
|
||||
}
|
||||
.chromecast-table td:nth-child(2) span {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
|
||||
}
|
||||
.chromecast-table td:last-child {
|
||||
width: 1%;
|
||||
}
|
||||
.chromecast-location {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
|
||||
.layout-select {
|
||||
margin: 0 0 1em 0;
|
||||
}
|
||||
.layout-label {
|
||||
-webkit-flex: 0 0 138px;
|
||||
flex: 0 0 138px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.sample-fullscreen,
|
||||
.sample-with-sidebar {
|
||||
width: 100%;
|
||||
height: 72px;
|
||||
border: 1px solid #999;
|
||||
text-align: center;
|
||||
background-color: #f2f2f2;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.radio:checked + .sample-fullscreen,
|
||||
.radio:checked + .sample-with-sidebar {
|
||||
background-color: #c5dc6c;
|
||||
border-color: #000;
|
||||
}
|
||||
.radio:checked ~ .sample-label {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.sample-fullscreen .sample-main {
|
||||
width: 100%;
|
||||
line-height: 70px;
|
||||
display: block;
|
||||
}
|
||||
.sample-with-sidebar .sample-main {
|
||||
width: 75%;
|
||||
line-height: 70px;
|
||||
display: inline-block;
|
||||
}
|
||||
.sample-with-sidebar .sample-sidebar {
|
||||
width: 25%;
|
||||
line-height: 70px;
|
||||
border-left: 1px solid #999;
|
||||
}
|
||||
.sample-label {
|
||||
width: 100%;
|
||||
display: block;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-family: 'OpenSansRegular';
|
||||
font-size: 90%;
|
||||
line-height: 2;
|
||||
}
|
||||
.submit-buttons {
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
}
|
||||
.submit-buttons > * {
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
}
|
||||
.submit-buttons > *:first-child {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.btn-cancel {
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
background-color: #e2e2e2;
|
||||
color: #666;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.url-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0 0 1em 0;
|
||||
}
|
||||
.url-item {
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
margin: 0 0 1em 0;
|
||||
}
|
||||
.url-item label {
|
||||
-webkit-flex-shrink: 0;
|
||||
flex-shrink: 0;
|
||||
-webkit-flex-basis: 60px;
|
||||
flex-basis: 60px;
|
||||
white-space: nowrap;
|
||||
padding-right: 10px;
|
||||
line-height: 40px;
|
||||
color: #666;
|
||||
font-family: 'OpenSansLight';
|
||||
}
|
||||
.url-item input {
|
||||
margin: 0;
|
||||
}
|
||||
.remove-link {
|
||||
-webkit-flex-shrink: 0;
|
||||
flex-shrink: 0;
|
||||
width: 44px;
|
||||
line-height: 40px;
|
||||
font-size: 150%;
|
||||
color: #ba0c2f;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.radio {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.channel-rotation,
|
||||
.alert-duration {
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
}
|
||||
.channel-rotation > *,
|
||||
.alert-duration > * {
|
||||
-webkit-flex-basis: 88px;
|
||||
flex-basis: 88px;
|
||||
}
|
||||
.channel-rotation > * + *,
|
||||
.alert-duration > * + * {
|
||||
padding-left: 5px;
|
||||
}
|
||||
.channel-rotation span,
|
||||
.alert-duration span {
|
||||
text-align: center;
|
||||
background-color: #e2e2e2;
|
||||
line-height: 44px;
|
||||
border-radius: 3px;
|
||||
display: block;
|
||||
font-family: 'OpenSansRegular';
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
}
|
||||
.radio:checked + span {
|
||||
background-color: #c5dc6c;
|
||||
color: #000;
|
||||
}
|
||||
.new-url {
|
||||
font-size: 80%;
|
||||
display: block;
|
||||
margin: 0 0 1.25em 0;
|
||||
cursor: pointer;
|
||||
font-family: 'OpenSansLight';
|
||||
}
|
||||
.new-url:before {
|
||||
font-family: 'Gscreen';
|
||||
content: "\e600";
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.char-count {
|
||||
font-size: 80%;
|
||||
color: #999;
|
||||
display: block;
|
||||
text-align: right;
|
||||
margin: 0 0 1.25em 0;
|
||||
}
|
||||
.alert-styles {
|
||||
margin: 0 0 1em 0;
|
||||
}
|
||||
.alert-style-label {
|
||||
line-height: 44px;
|
||||
font-family: 'OpenSansRegular';
|
||||
margin: 0 0 .5em 0;
|
||||
}
|
||||
[class^="alert-sample"] {
|
||||
color: #fff;
|
||||
padding: 0 1em;
|
||||
cursor: pointer;
|
||||
border: 2px solid #fff;
|
||||
}
|
||||
.radio:checked + [class^="alert-sample"] {
|
||||
border: 2px solid #0185c6;
|
||||
position: relative;
|
||||
}
|
||||
.radio:checked + [class^="alert-sample"]:before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
border: 1px solid #fff;
|
||||
}
|
||||
.alert-sample-urgent {
|
||||
background-color: #ba0c2f;
|
||||
}
|
||||
.alert-sample-default {
|
||||
background-color: #333;
|
||||
}
|
||||
.alert-sample-green {
|
||||
background-color: #82b548;
|
||||
}
|
||||
.alert-duration {
|
||||
margin: 0 0 1em 0;
|
||||
}
|
||||
.flash-msg {
|
||||
background-color: #4bc1de;
|
||||
color: #fff;
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
font-size: 80%;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
-webkit-animation: flash 4s;
|
||||
-webkit-transform: translateY(48px);
|
||||
animation: flash 4s;
|
||||
transform: translateY(48px);
|
||||
}
|
||||
@-webkit-keyframes flash {
|
||||
0%, 100% { -webkit-transform: translateY(48px); }
|
||||
10%, 90% { -webkit-transform: translateY(0); }
|
||||
}
|
||||
@keyframes flash {
|
||||
0%, 100% { transform: translateY(48px); }
|
||||
10%, 90% { transform: translateY(0); }
|
||||
}
|
||||
.note {
|
||||
font-size: 80%;
|
||||
margin: 0 0 1.25em 0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
section {
|
||||
padding: 1.25em .625em;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
/* Templates */
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: #000;
|
||||
}
|
||||
body {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.template-full-screen,
|
||||
.template-with-sidebar {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
height: 0;
|
||||
}
|
||||
.widescreen {
|
||||
padding-bottom: 56.25%;
|
||||
}
|
||||
.macbook {
|
||||
padding-bottom: 62.5%;
|
||||
}
|
||||
.main-window {
|
||||
background-color: #ccc;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-align-items: stretch;
|
||||
align-items: stretch;
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
.main-window iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
}
|
||||
.template-full-screen .main-window {
|
||||
width: 100%;
|
||||
}
|
||||
.template-with-sidebar .main-window {
|
||||
width: 75%;
|
||||
}
|
||||
.template-with-sidebar .sidebar {
|
||||
background-color: #fff;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 25%;
|
||||
padding: 2vw;
|
||||
border-left: 1px solid #ccc;
|
||||
font-size: 2vw;
|
||||
}
|
||||
|
||||
.alert {
|
||||
color: #fff;
|
||||
font-size: 2vw;
|
||||
padding: 1vw 2vw;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
.alert.urgent {
|
||||
background-color: #ba0c2f;
|
||||
}
|
||||
.alert.default {
|
||||
background-color: #333;
|
||||
}
|
||||
.alert.green {
|
||||
background-color: #82b548;
|
||||
}
|
||||
.channel-overlay {
|
||||
position: absolute;
|
||||
bottom: 1vw;
|
||||
right: 2vw;
|
||||
color: #fff;
|
||||
text-shadow: 0 0 3px rgba(0,0,0,.6);
|
||||
font-size: 2vw;
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
.setup {
|
||||
position: relative;
|
||||
}
|
||||
.setup .main-window {
|
||||
background-color: #82b548;
|
||||
}
|
||||
.setup h1 {
|
||||
font-size: 4vw;
|
||||
line-height: 1;
|
||||
margin: -2vw 0 0 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
color: #333;
|
||||
z-index: 100;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>Generated by IcoMoon</metadata>
|
||||
<defs>
|
||||
<font id="Gscreen" horiz-adv-x="512">
|
||||
<font-face units-per-em="512" ascent="480" descent="-32" />
|
||||
<missing-glyph horiz-adv-x="512" />
|
||||
<glyph unicode=" " d="" horiz-adv-x="256" />
|
||||
<glyph unicode="" d="M384 256h-85.334v85.334c0 23.552-19.115 42.666-42.666 42.666s-42.666-19.114-42.666-42.666l1.514-85.334h-86.848c-23.552 0-42.666-19.114-42.666-42.667s19.115-42.666 42.666-42.666l86.848 1.514-1.514-86.848c0-23.552 19.114-42.666 42.666-42.666s42.666 19.115 42.666 42.666v86.848l85.334-1.514c23.552 0 42.666 19.115 42.666 42.666s-19.115 42.667-42.666 42.667z" />
|
||||
<glyph unicode="" d="M392.832 286.165c-16.661 16.661-43.669 16.661-60.331 0l-76.501-76.48-76.501 76.48c-16.661 16.661-43.669 16.661-60.331 0-8.341-8.32-12.501-19.243-12.501-30.166s4.16-21.846 12.501-30.166l136.832-136.854 136.832 136.854c16.661 16.64 16.661 43.69 0 60.331z" />
|
||||
<glyph unicode="" d="M508.544 222.272c0-55.424-13.856-102.528-42.24-144.096-47.136-68.608-126.144-110.176-209.952-110.176-72.736 0-134.4 27.008-182.912 78.272-46.432 49.184-69.984 110.144-69.984 182.912 0 67.904 26.336 130.272 74.816 178.048 48.512 47.84 109.504 72.768 179.456 72.768 105.312 0 197.472-64.448 236.96-169.728h-137.216c-30.464 31.872-62.304 47.776-101.12 47.776-72.032 0-128.864-58.88-128.864-129.536 0-78.304 60.96-138.592 128.864-138.592 45.728 0 86.592 23.552 110.88 66.496h-140.672v98.432h280.576c0.704-11.104 1.408-22.176 1.408-32.576z" />
|
||||
<glyph unicode="" d="M371.498 328.832c-16.64 16.661-43.69 16.661-60.33 0l-55.168-55.168-55.168 55.168c-16.64 16.661-43.69 16.661-60.331 0-16.662-16.661-16.662-43.67 0-60.33l55.147-55.168-55.147-55.168c-16.662-16.661-16.662-43.669 0-60.331 8.32-8.341 19.243-12.502 30.166-12.502s21.845 4.16 30.166 12.502l55.168 55.168 55.168-55.168c8.32-8.341 19.243-12.502 30.166-12.502s21.846 4.16 30.166 12.502c16.661 16.661 16.661 43.669 0 60.331l-55.147 55.168 55.147 55.168c16.661 16.661 16.661 43.669 0 60.33z" />
|
||||
<glyph unicode="" d="M256 384c-94.101 0-170.667-76.565-170.667-170.666s76.566-170.667 170.667-170.667 170.667 76.565 170.667 170.667-76.565 170.667-170.667 170.667zM149.333 213.333c0 17.75 4.779 34.219 12.458 48.96l143.168-143.168c-14.741-7.68-31.211-12.459-48.96-12.459-58.816 0-106.667 47.851-106.667 106.666zM350.208 164.373l-143.168 143.168c14.742 7.68 31.21 12.459 48.96 12.459 58.816 0 106.666-47.851 106.666-106.667 0-17.75-4.779-34.219-12.459-48.96z" />
|
||||
<glyph unicode="" d="M465.514 204.16c-1.771 2.538-43.99 62.805-102.25 104-30.208 21.397-68.31 33.173-107.264 33.173-38.934 0-77.034-11.776-107.306-33.173-58.261-41.195-100.438-101.462-102.208-104-5.078-7.317-5.078-17.024 0-24.341 1.77-2.538 43.947-62.805 102.208-104 30.272-21.376 68.374-33.152 107.306-33.152 38.954 0 77.056 11.776 107.264 33.173 58.262 41.195 100.48 101.462 102.25 104 5.099 7.296 5.099 17.024 0 24.32zM256 117.333c-41.258 0-74.666 33.493-74.666 74.666 0 41.259 33.408 74.667 74.666 74.667 41.173 0 74.666-33.408 74.666-74.667 0-41.173-33.493-74.666-74.666-74.666zM298.666 192c0-23.51-19.157-42.666-42.666-42.666-23.573 0-42.666 19.158-42.666 42.666 0 23.574 19.094 42.667 42.666 42.667 23.51 0 42.666-19.094 42.666-42.667z" />
|
||||
<glyph unicode="" d="M200.256 94.976l7.915-31.616c2.838-11.37 14.762-20.694 26.496-20.694h21.333c11.733 0 23.659 9.322 26.496 20.693l7.914 31.616c2.837 11.37 14.4 18.048 25.664 14.805l31.296-8.96c11.286-3.221 25.344 2.432 31.19 12.608l10.666 18.496c5.846 10.176 3.776 25.152-4.672 33.322l-23.424 22.635c-8.448 8.171-8.448 21.504 0.022 29.654l23.382 22.635c8.448 8.149 10.538 23.125 4.694 33.302l-10.688 18.496c-5.866 10.176-19.904 15.83-31.168 12.608l-31.296-8.96c-11.286-3.221-22.827 3.434-25.685 14.805l-7.894 31.574c-2.837 11.349-14.762 20.672-26.496 20.672h-21.333c-11.733 0-23.659-9.323-26.496-20.694l-7.893-31.573c-2.858-11.37-14.4-18.048-25.685-14.827l-31.296 8.96c-11.286 3.242-25.344-2.432-31.189-12.587l-10.666-18.496c-5.846-10.176-3.776-25.152 4.694-33.322l23.381-22.592c8.427-8.171 8.427-21.504 0-29.675l-23.424-22.635c-8.427-8.171-10.538-23.147-4.672-33.322l10.688-18.496c5.846-10.176 19.904-15.829 31.189-12.608l31.296 8.96c11.264 3.264 22.827-3.413 25.664-14.784zM245.333 245.333c23.552 0 42.667-19.094 42.667-42.667 0-23.552-19.115-42.666-42.666-42.666s-42.666 19.115-42.666 42.666c0 23.574 19.114 42.667 42.666 42.667z" />
|
||||
</font></defs></svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
@@ -0,0 +1,245 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata></metadata>
|
||||
<defs>
|
||||
<font id="open_sanslight" horiz-adv-x="1169" >
|
||||
<font-face units-per-em="2048" ascent="1638" descent="-410" />
|
||||
<missing-glyph horiz-adv-x="532" />
|
||||
<glyph unicode="fi" horiz-adv-x="1077" d="M29 1001v58l202 37v84q0 200 73.5 293.5t240.5 93.5q90 0 180 -27l-23 -86q-80 25 -159 25q-116 0 -164.5 -68.5t-48.5 -222.5v-101h256v-86h-256v-1001h-99v1001h-202zM782 1389q0 96 63 96q31 0 48.5 -25t17.5 -71q0 -45 -17.5 -71t-48.5 -26q-63 0 -63 97zM796 0v1087 h99v-1087h-99z" />
|
||||
<glyph unicode="fl" horiz-adv-x="1077" d="M29 1001v58l202 37v84q0 200 73.5 293.5t240.5 93.5q90 0 180 -27l-23 -86q-80 25 -159 25q-116 0 -164.5 -68.5t-48.5 -222.5v-101h256v-86h-256v-1001h-99v1001h-202zM796 0v1556h99v-1556h-99z" />
|
||||
<glyph unicode="ffi" horiz-adv-x="1692" d="M29 1001v58l202 37v84q0 200 73.5 293.5t240.5 93.5q90 0 180 -27l-23 -86q-80 25 -159 25q-116 0 -164.5 -68.5t-48.5 -222.5v-101h256v-86h-256v-1001h-99v1001h-202zM643 1001v58l202 37v84q0 200 73.5 293.5t240.5 93.5q90 0 180 -27l-23 -86q-80 25 -159 25 q-116 0 -164.5 -68.5t-48.5 -222.5v-101h256v-86h-256v-1001h-99v1001h-202zM1397 1389q0 96 63 96q31 0 48.5 -25t17.5 -71q0 -45 -17.5 -71t-48.5 -26q-63 0 -63 97zM1411 0v1087h99v-1087h-99z" />
|
||||
<glyph unicode="ffl" horiz-adv-x="1692" d="M29 1001v58l202 37v84q0 200 73.5 293.5t240.5 93.5q90 0 180 -27l-23 -86q-80 25 -159 25q-116 0 -164.5 -68.5t-48.5 -222.5v-101h256v-86h-256v-1001h-99v1001h-202zM643 1001v58l202 37v84q0 200 73.5 293.5t240.5 93.5q90 0 180 -27l-23 -86q-80 25 -159 25 q-116 0 -164.5 -68.5t-48.5 -222.5v-101h256v-86h-256v-1001h-99v1001h-202zM1411 0v1556h99v-1556h-99z" />
|
||||
<glyph horiz-adv-x="2048" />
|
||||
<glyph horiz-adv-x="2048" />
|
||||
<glyph unicode="
" horiz-adv-x="1044" />
|
||||
<glyph unicode=" " horiz-adv-x="532" />
|
||||
<glyph unicode="	" horiz-adv-x="532" />
|
||||
<glyph unicode=" " horiz-adv-x="532" />
|
||||
<glyph unicode="!" horiz-adv-x="492" d="M164 78q0 98 80 98q82 0 82 -98t-82 -98q-80 0 -80 98zM186 1462h119l-29 -1085h-61z" />
|
||||
<glyph unicode=""" horiz-adv-x="723" d="M133 1462h127l-33 -528h-61zM463 1462h127l-33 -528h-61z" />
|
||||
<glyph unicode="#" horiz-adv-x="1323" d="M55 451v79h299l76 398h-297v80h311l86 454h91l-89 -454h365l88 454h86l-88 -454h285v-80h-301l-76 -398h303v-79h-320l-86 -451h-90l88 451h-360l-86 -451h-88l86 451h-283zM440 530h363l78 398h-363z" />
|
||||
<glyph unicode="$" d="M164 186v103q75 -36 179.5 -61t193.5 -25v508q-145 44 -215 88t-102 104t-32 146q0 124 94.5 208.5t254.5 104.5v192h81v-190q197 -9 351 -72l-33 -90q-141 62 -318 72v-486q213 -66 293 -144t80 -204q0 -133 -99 -217t-274 -106v-236h-81v232q-92 2 -200.5 22.5 t-172.5 50.5zM297 1049q0 -86 57 -141t183 -93v453q-119 -16 -179.5 -76t-60.5 -143zM618 209q122 13 192.5 75t70.5 160q0 85 -63 140.5t-200 95.5v-471z" />
|
||||
<glyph unicode="%" horiz-adv-x="1653" d="M113 1026q0 223 72 340t212 117q139 0 215 -120.5t76 -336.5q0 -226 -75 -343.5t-216 -117.5q-133 0 -208.5 120.5t-75.5 340.5zM211 1026q0 -186 45 -279.5t141 -93.5q193 0 193 373q0 184 -49.5 276.5t-143.5 92.5q-96 0 -141 -92.5t-45 -276.5zM373 0l811 1462h96 l-811 -1462h-96zM965 438q0 225 73.5 341t212.5 116q137 0 213 -120t76 -337q0 -226 -74 -343.5t-215 -117.5q-136 0 -211 121.5t-75 339.5zM1063 438q0 -185 45 -277.5t141 -92.5q193 0 193 370q0 369 -193 369q-96 0 -141 -91.5t-45 -277.5z" />
|
||||
<glyph unicode="&" horiz-adv-x="1460" d="M123 371q0 138 73.5 235t274.5 205l-75 82q-66 71 -98 139t-32 142q0 143 95.5 227t256.5 84q155 0 245.5 -81t90.5 -224q0 -105 -70 -192.5t-253 -194.5l452 -457q61 72 104 157t75 201h96q-63 -246 -209 -426l266 -268h-135l-193 197q-92 -90 -164 -131.5t-157.5 -63.5 t-194.5 -22q-209 0 -328.5 103t-119.5 288zM227 375q0 -143 93 -224t258 -81q128 0 234.5 43.5t209.5 146.5l-483 485q-136 -72 -196.5 -122.5t-88 -109.5t-27.5 -138zM373 1176q0 -79 40 -146t152 -174q159 85 221 159t62 169q0 94 -62 152.5t-168 58.5q-114 0 -179.5 -58 t-65.5 -161z" />
|
||||
<glyph unicode="'" horiz-adv-x="393" d="M133 1462h127l-33 -528h-61z" />
|
||||
<glyph unicode="(" horiz-adv-x="557" d="M82 561q0 265 77.5 496t223.5 405h113q-148 -182 -227 -412.5t-79 -486.5q0 -483 304 -887h-111q-147 170 -224 397t-77 488z" />
|
||||
<glyph unicode=")" horiz-adv-x="557" d="M61 1462h113q147 -175 224 -406.5t77 -494.5t-77.5 -490t-223.5 -395h-111q304 404 304 887q0 257 -79 487.5t-227 411.5z" />
|
||||
<glyph unicode="*" horiz-adv-x="1128" d="M104 1124l19 131l401 -104l-39 405h146l-37 -405l405 104l21 -131l-395 -39l247 -340l-124 -71l-191 379l-180 -379l-125 71l242 340z" />
|
||||
<glyph unicode="+" d="M111 682v82h432v434h82v-434h434v-82h-434v-432h-82v432h-432z" />
|
||||
<glyph unicode="," horiz-adv-x="440" d="M68 -264q77 275 110 502h117l12 -21q-75 -265 -174 -481h-65z" />
|
||||
<glyph unicode="-" horiz-adv-x="659" d="M92 512v82h475v-82h-475z" />
|
||||
<glyph unicode="." horiz-adv-x="487" d="M162 78q0 98 80 98q82 0 82 -98t-82 -98q-80 0 -80 98z" />
|
||||
<glyph unicode="/" horiz-adv-x="698" d="M25 0l544 1462h105l-545 -1462h-104z" />
|
||||
<glyph unicode="0" d="M115 735q0 382 115.5 566t351.5 184q231 0 352 -190.5t121 -559.5q0 -385 -117.5 -570t-355.5 -185q-229 0 -348 190.5t-119 564.5zM223 735q0 -340 89 -502.5t270 -162.5q189 0 275.5 168t86.5 497q0 324 -86.5 492t-275.5 168t-274 -168t-85 -492z" />
|
||||
<glyph unicode="1" d="M199 1165l397 297h86v-1462h-98v1065q0 145 12 301q-15 -15 -31 -29t-309 -243z" />
|
||||
<glyph unicode="2" d="M113 0v88l389 406q164 170 230 260t97 172t31 172q0 131 -86 213t-223 82q-183 0 -350 -133l-54 69q183 154 406 154q191 0 300.5 -102t109.5 -281q0 -145 -73.5 -280.5t-268.5 -334.5l-375 -385v-4h782v-96h-915z" />
|
||||
<glyph unicode="3" d="M94 63v99q84 -44 188.5 -69t196.5 -25q221 0 332 89.5t111 252.5q0 145 -113.5 223t-333.5 78h-158v96h160q182 0 288.5 86.5t106.5 234.5q0 122 -86.5 195.5t-226.5 73.5q-109 0 -199 -30.5t-202 -104.5l-49 67q85 71 205 112.5t243 41.5q202 0 312 -95.5t110 -269.5 q0 -136 -85.5 -229t-229.5 -119v-6q176 -22 268 -112t92 -242q0 -205 -139.5 -317.5t-401.5 -112.5q-223 0 -389 83z" />
|
||||
<glyph unicode="4" d="M43 373v67l725 1030h121v-1011h252v-86h-252v-373h-94v373h-752zM162 459h633v418q0 302 14 507h-8q-20 -37 -123 -188z" />
|
||||
<glyph unicode="5" d="M143 63v103q108 -55 192 -76.5t179 -21.5q192 0 308 101.5t116 274.5q0 163 -113 256t-307 93q-130 0 -272 -39l-60 39l58 669h704v-96h-610l-45 -516q156 29 244 29q234 0 368.5 -113t134.5 -311q0 -225 -140 -350t-386 -125q-109 0 -207 21.5t-164 61.5z" />
|
||||
<glyph unicode="6" d="M131 623q0 285 77.5 479.5t220 288.5t343.5 94q94 0 172 -23v-88q-73 27 -176 27q-247 0 -384.5 -178t-154.5 -518h13q76 98 174 148t207 50q205 0 320.5 -117t115.5 -323q0 -224 -121.5 -353.5t-327.5 -129.5q-222 0 -350.5 169.5t-128.5 473.5zM240 504 q0 -111 49.5 -213.5t134 -162.5t186.5 -60q164 0 255 103t91 294q0 168 -90 262t-245 94q-102 0 -189.5 -45t-139.5 -119.5t-52 -152.5z" />
|
||||
<glyph unicode="7" d="M109 1366v96h946v-73l-604 -1389h-117l602 1366h-827z" />
|
||||
<glyph unicode="8" d="M121 375q0 131 83 230t257 169q-161 76 -227 160.5t-66 202.5q0 105 53 184.5t148.5 122.5t212.5 43q186 0 299.5 -95t113.5 -257q0 -112 -70.5 -198t-228.5 -159q192 -79 270 -173t78 -228q0 -181 -126.5 -289t-339.5 -108q-221 0 -339 101t-118 294zM223 360 q0 -138 93.5 -214t261.5 -76q164 0 264 80.5t100 218.5q0 124 -78.5 201.5t-302.5 162.5q-184 -71 -261 -157t-77 -216zM268 1137q0 -70 31.5 -123.5t91 -97t199.5 -101.5q163 63 234 139t71 183q0 120 -84.5 190t-230.5 70q-141 0 -226.5 -69.5t-85.5 -190.5z" />
|
||||
<glyph unicode="9" d="M111 993q0 220 124.5 356t323.5 136q144 0 252 -75.5t166.5 -221.5t58.5 -346q0 -288 -75.5 -482t-220 -287t-349.5 -93q-104 0 -192 26v86q43 -14 103.5 -21.5t92.5 -7.5q247 0 387 178.5t156 520.5h-12q-73 -96 -174 -147.5t-211 -51.5q-203 0 -316.5 112t-113.5 318z M213 999q0 -174 87 -264t249 -90q101 0 188.5 45t139 119.5t51.5 151.5q0 117 -46.5 219t-130 159.5t-192.5 57.5q-158 0 -252 -106.5t-94 -291.5z" />
|
||||
<glyph unicode=":" horiz-adv-x="487" d="M162 78q0 98 80 98q82 0 82 -98t-82 -98q-80 0 -80 98zM162 971q0 98 80 98q82 0 82 -98q0 -53 -23.5 -76t-58.5 -23q-34 0 -57 23t-23 76z" />
|
||||
<glyph unicode=";" horiz-adv-x="487" d="M76 -264q29 97 62 245.5t48 256.5h117l12 -21q-75 -265 -174 -481h-65zM162 971q0 98 80 98q82 0 82 -98q0 -53 -23.5 -76t-58.5 -23q-34 0 -57 23t-23 76z" />
|
||||
<glyph unicode="<" d="M111 682v61l948 474v-95l-823 -405l823 -355v-96z" />
|
||||
<glyph unicode="=" d="M111 477v82h948v-82h-948zM111 885v82h948v-82h-948z" />
|
||||
<glyph unicode=">" d="M111 266v96l823 355l-823 405v95l948 -474v-61z" />
|
||||
<glyph unicode="?" horiz-adv-x="862" d="M57 1403q110 48 184.5 64t153.5 16q183 0 288 -98.5t105 -270.5q0 -68 -18 -119t-50.5 -94.5t-78.5 -84t-102 -87.5q-64 -54 -98.5 -98.5t-50 -93.5t-15.5 -146v-14h-82v37q0 123 37.5 201t138.5 167l91 79q72 61 103 121t31 138q0 127 -83.5 202t-219.5 75 q-79 0 -148 -17.5t-149 -56.5zM260 78q0 98 80 98q82 0 82 -98t-82 -98q-80 0 -80 98z" />
|
||||
<glyph unicode="@" horiz-adv-x="1815" d="M113 561q0 256 108.5 460.5t307 317.5t448.5 113q215 0 380.5 -89t255 -254.5t89.5 -383.5q0 -228 -90.5 -366t-245.5 -138q-89 0 -144.5 54t-64.5 147h-4q-43 -100 -124 -150.5t-189 -50.5q-148 0 -229 96.5t-81 270.5q0 202 120.5 330.5t314.5 128.5q138 0 286 -41 l-22 -464v-30q0 -104 35 -156.5t116 -52.5q103 0 168.5 116.5t65.5 303.5q0 194 -79 340t-225.5 224.5t-334.5 78.5q-230 0 -405.5 -99.5t-270 -281.5t-94.5 -418q0 -322 167 -497.5t474 -175.5q93 0 188.5 18t231.5 70v-99q-203 -80 -414 -80q-349 0 -544 200.5t-195 557.5 zM633 590q0 -143 55 -215t174 -72q255 0 273 346l16 291q-79 27 -193 27q-149 0 -237 -102.5t-88 -274.5z" />
|
||||
<glyph unicode="A" horiz-adv-x="1229" d="M0 0l588 1468h65l576 -1468h-115l-203 516h-594l-204 -516h-113zM354 608h523l-199 527q-25 62 -60 172q-27 -96 -59 -174z" />
|
||||
<glyph unicode="B" horiz-adv-x="1284" d="M207 0v1462h401q271 0 398 -92t127 -278q0 -127 -77.5 -211.5t-226.5 -108.5v-6q175 -26 257.5 -110.5t82.5 -235.5q0 -202 -134 -311t-380 -109h-448zM309 90h344q406 0 406 330q0 301 -428 301h-322v-631zM309 811h322q206 0 299.5 68.5t93.5 214.5t-105.5 212 t-314.5 66h-295v-561z" />
|
||||
<glyph unicode="C" horiz-adv-x="1272" d="M129 735q0 223 84.5 393t243 262.5t368.5 92.5q214 0 383 -80l-41 -92q-160 80 -336 80q-275 0 -433 -176t-158 -482q0 -313 149 -486t426 -173q184 0 338 47v-90q-145 -51 -362 -51q-308 0 -485 199t-177 556z" />
|
||||
<glyph unicode="D" horiz-adv-x="1446" d="M207 0v1462h395q350 0 532.5 -183t182.5 -534q0 -368 -193 -556.5t-567 -188.5h-350zM309 90h242q655 0 655 651q0 314 -159.5 472.5t-468.5 158.5h-269v-1282z" />
|
||||
<glyph unicode="E" horiz-adv-x="1130" d="M207 0v1462h799v-94h-697v-553h658v-94h-658v-627h697v-94h-799z" />
|
||||
<glyph unicode="F" horiz-adv-x="1028" d="M207 0v1462h801v-94h-699v-620h660v-95h-660v-653h-102z" />
|
||||
<glyph unicode="G" horiz-adv-x="1481" d="M129 729q0 223 91.5 395.5t262 266.5t391.5 94q239 0 429 -88l-41 -92q-190 88 -394 88q-289 0 -458.5 -178.5t-169.5 -481.5q0 -330 161 -496.5t473 -166.5q202 0 343 57v514h-435v96h539v-667q-212 -90 -477 -90q-346 0 -530.5 195.5t-184.5 553.5z" />
|
||||
<glyph unicode="H" horiz-adv-x="1473" d="M207 0v1462h102v-649h854v649h103v-1462h-103v719h-854v-719h-102z" />
|
||||
<glyph unicode="I" horiz-adv-x="516" d="M207 0v1462h102v-1462h-102z" />
|
||||
<glyph unicode="J" horiz-adv-x="506" d="M-184 -254q78 -20 149 -20q242 0 242 264v1472h102v-1462q0 -369 -342 -369q-92 0 -151 27v88z" />
|
||||
<glyph unicode="K" horiz-adv-x="1190" d="M207 0v1462h102v-760l162 162l573 598h130l-599 -618l615 -844h-125l-561 772l-195 -172v-600h-102z" />
|
||||
<glyph unicode="L" horiz-adv-x="1051" d="M207 0v1462h102v-1366h697v-96h-799z" />
|
||||
<glyph unicode="M" horiz-adv-x="1767" d="M207 0v1462h158l518 -1286h6l518 1286h154v-1462h-103v1108q0 116 12 240h-8l-547 -1348h-65l-545 1350h-8q8 -124 8 -254v-1096h-98z" />
|
||||
<glyph unicode="N" horiz-adv-x="1477" d="M207 0v1462h102l865 -1296h6q-9 180 -9 342v954h99v-1462h-103l-866 1298h-8q12 -232 12 -350v-948h-98z" />
|
||||
<glyph unicode="O" horiz-adv-x="1565" d="M129 735q0 349 175.5 549.5t479.5 200.5q306 0 479 -201.5t173 -550.5q0 -348 -174 -550.5t-480 -202.5q-305 0 -479 202.5t-174 552.5zM240 733q0 -314 140 -485.5t402 -171.5q264 0 403.5 170t139.5 487q0 316 -139.5 484.5t-401.5 168.5q-261 0 -402.5 -170 t-141.5 -483z" />
|
||||
<glyph unicode="P" horiz-adv-x="1198" d="M207 0v1462h358q522 0 522 -420q0 -212 -144 -325t-408 -113h-226v-604h-102zM309 692h201q247 0 357 81.5t110 264.5q0 169 -104 250.5t-322 81.5h-242v-678z" />
|
||||
<glyph unicode="Q" horiz-adv-x="1565" d="M129 735q0 349 175.5 549.5t479.5 200.5q306 0 479 -201.5t173 -550.5q0 -294 -126 -486.5t-349 -246.5l333 -348h-166l-282 330l-33 -2h-31q-305 0 -479 202.5t-174 552.5zM240 733q0 -314 140 -485.5t402 -171.5q264 0 403.5 170t139.5 487q0 316 -139.5 484.5 t-401.5 168.5q-261 0 -402.5 -170t-141.5 -483z" />
|
||||
<glyph unicode="R" horiz-adv-x="1217" d="M207 0v1462h348q272 0 402 -100.5t130 -302.5q0 -147 -77.5 -248t-235.5 -145l397 -666h-122l-377 637h-363v-637h-102zM309 725h279q185 0 287 82.5t102 243.5q0 167 -100 243t-326 76h-242v-645z" />
|
||||
<glyph unicode="S" horiz-adv-x="1116" d="M111 39v102q158 -67 403 -67q180 0 285.5 82.5t105.5 216.5q0 83 -35 137.5t-114 99.5t-232 97q-224 77 -309.5 166.5t-85.5 238.5q0 164 128.5 267.5t330.5 103.5q206 0 387 -78l-37 -88q-182 76 -348 76q-162 0 -258 -75t-96 -204q0 -81 29.5 -133t96.5 -93.5 t230 -99.5q171 -59 257 -114.5t125.5 -126t39.5 -170.5q0 -183 -134.5 -290t-357.5 -107q-268 0 -411 59z" />
|
||||
<glyph unicode="T" horiz-adv-x="1073" d="M10 1366v96h1053v-96h-475v-1366h-103v1366h-475z" />
|
||||
<glyph unicode="U" horiz-adv-x="1473" d="M190 520v942h103v-946q0 -211 117 -328.5t331 -117.5q209 0 324 115.5t115 320.5v956h102v-946q0 -252 -146 -394t-407 -142q-254 0 -396.5 142.5t-142.5 397.5z" />
|
||||
<glyph unicode="V" horiz-adv-x="1182" d="M0 1462h109l368 -995q84 -225 113 -338q20 75 79 233l402 1100h111l-547 -1462h-90z" />
|
||||
<glyph unicode="W" horiz-adv-x="1827" d="M51 1462h107l256 -942q15 -57 28 -105.5t23.5 -91t19 -82t15.5 -79.5q24 136 102 413l250 887h113l293 -1018q51 -176 73 -284q13 72 33.5 153t308.5 1149h103l-404 -1462h-84l-321 1128q-40 139 -60 228q-16 -87 -45.5 -200t-322.5 -1156h-86z" />
|
||||
<glyph unicode="X" horiz-adv-x="1102" d="M0 0l492 762l-447 700h115l395 -626l401 626h109l-453 -698l490 -764h-117l-432 682l-440 -682h-113z" />
|
||||
<glyph unicode="Y" horiz-adv-x="1081" d="M0 1462h117l426 -800l428 800h110l-487 -897v-565h-105v557z" />
|
||||
<glyph unicode="Z" horiz-adv-x="1180" d="M82 0v76l856 1290h-817v96h954v-76l-858 -1290h881v-96h-1016z" />
|
||||
<glyph unicode="[" horiz-adv-x="653" d="M174 -324v1786h428v-94h-330v-1597h330v-95h-428z" />
|
||||
<glyph unicode="\" horiz-adv-x="698" d="M25 1462h102l547 -1462h-103z" />
|
||||
<glyph unicode="]" horiz-adv-x="653" d="M51 -229h330v1597h-330v94h428v-1786h-428v95z" />
|
||||
<glyph unicode="^" d="M88 561l465 912h68l460 -912h-100l-395 791l-398 -791h-100z" />
|
||||
<glyph unicode="_" horiz-adv-x="842" d="M-4 -184h850v-82h-850v82z" />
|
||||
<glyph unicode="`" horiz-adv-x="1182" d="M393 1552v17h142q26 -48 98.5 -142t142.5 -170v-16h-69q-96 79 -188.5 171.5t-125.5 139.5z" />
|
||||
<glyph unicode="a" horiz-adv-x="1085" d="M98 289q0 159 132.5 247t383.5 93l207 6v72q0 155 -63 234t-203 79q-151 0 -313 -84l-37 86q179 84 354 84q179 0 267.5 -93t88.5 -290v-723h-73l-25 172h-8q-82 -105 -168.5 -148.5t-204.5 -43.5q-160 0 -249 82t-89 227zM203 285q0 -102 62.5 -158.5t176.5 -56.5 q174 0 274.5 99.5t100.5 276.5v107l-190 -8q-229 -11 -326.5 -71.5t-97.5 -188.5z" />
|
||||
<glyph unicode="b" horiz-adv-x="1219" d="M182 0v1556h99v-391q0 -88 -4 -162l-3 -85h7q62 98 149.5 144t210.5 46q228 0 343.5 -143.5t115.5 -419.5q0 -271 -121.5 -418t-341.5 -147q-116 0 -209 48t-147 136h-9l-28 -164h-62zM281 528q0 -246 86.5 -353t269.5 -107q178 0 268 124.5t90 354.5q0 471 -356 471 q-192 0 -275 -110t-83 -363v-17z" />
|
||||
<glyph unicode="c" horiz-adv-x="973" d="M119 537q0 270 137 420.5t375 150.5q141 0 270 -49l-27 -88q-141 47 -245 47q-200 0 -303 -123.5t-103 -355.5q0 -220 103 -344.5t288 -124.5q148 0 275 53v-92q-104 -51 -273 -51q-233 0 -365 147t-132 410z" />
|
||||
<glyph unicode="d" horiz-adv-x="1219" d="M119 528q0 282 118 431t343 149q118 0 204 -43t154 -147h6q-6 126 -6 247v391h98v-1556h-65l-25 166h-8q-124 -186 -356 -186q-225 0 -344 140t-119 408zM223 530q0 -462 359 -462q184 0 270 107t86 353v17q0 252 -84.5 362.5t-273.5 110.5q-178 0 -267.5 -125 t-89.5 -363z" />
|
||||
<glyph unicode="e" horiz-adv-x="1124" d="M119 535q0 260 128 416.5t345 156.5q192 0 303 -134t111 -364v-80h-783q2 -224 104.5 -342t293.5 -118q93 0 163.5 13t178.5 56v-90q-92 -40 -170 -54.5t-172 -14.5q-237 0 -369.5 146t-132.5 409zM229 618h672q0 189 -82 295.5t-227 106.5q-157 0 -252 -103.5 t-111 -298.5z" />
|
||||
<glyph unicode="f" horiz-adv-x="614" d="M29 1001v58l202 37v84q0 200 73.5 293.5t240.5 93.5q90 0 180 -27l-23 -86q-80 25 -159 25q-116 0 -164.5 -68.5t-48.5 -222.5v-101h256v-86h-256v-1001h-99v1001h-202z" />
|
||||
<glyph unicode="g" horiz-adv-x="1071" d="M45 -193q0 112 69.5 186t188.5 101q-49 21 -78.5 59.5t-29.5 88.5q0 109 139 192q-95 39 -148 122.5t-53 191.5q0 163 103.5 261.5t279.5 98.5q107 0 166 -21h348v-69l-225 -14q90 -112 90 -246q0 -157 -104.5 -254.5t-280.5 -97.5q-74 0 -104 6q-59 -31 -90 -73t-31 -89 q0 -52 39.5 -76t132.5 -24h190q177 0 271 -71.5t94 -211.5q0 -172 -139.5 -265.5t-397.5 -93.5q-205 0 -317.5 79t-112.5 220zM150 -184q0 -224 333 -224q428 0 428 273q0 98 -67 142t-217 44h-178q-299 0 -299 -235zM233 748q0 -126 76.5 -195.5t204.5 -69.5 q136 0 208.5 69t72.5 200q0 139 -74.5 208.5t-208.5 69.5q-130 0 -204.5 -74.5t-74.5 -207.5z" />
|
||||
<glyph unicode="h" horiz-adv-x="1208" d="M182 0v1556h99v-495l-5 -139h7q61 98 154 142t231 44q370 0 370 -397v-711h-98v705q0 164 -69 238.5t-214 74.5q-195 0 -285.5 -98.5t-90.5 -319.5v-600h-99z" />
|
||||
<glyph unicode="i" horiz-adv-x="463" d="M168 1389q0 96 63 96q31 0 48.5 -25t17.5 -71q0 -45 -17.5 -71t-48.5 -26q-63 0 -63 97zM182 0v1087h99v-1087h-99z" />
|
||||
<glyph unicode="j" horiz-adv-x="463" d="M-98 -381q69 -20 129 -20q151 0 151 176v1312h99v-1298q0 -135 -63.5 -208t-180.5 -73q-80 0 -135 25v86zM168 1389q0 96 63 96q31 0 48.5 -25t17.5 -71q0 -45 -17.5 -71t-48.5 -26q-63 0 -63 97z" />
|
||||
<glyph unicode="k" horiz-adv-x="991" d="M182 0v1556h99v-780l-7 -299h5l555 610h120l-428 -464l465 -623h-119l-413 549l-178 -162v-387h-99z" />
|
||||
<glyph unicode="l" horiz-adv-x="463" d="M182 0v1556h99v-1556h-99z" />
|
||||
<glyph unicode="m" horiz-adv-x="1808" d="M182 0v1087h82l21 -149h6q45 81 128 125.5t183 44.5q257 0 330 -193h4q53 93 142.5 143t203.5 50q178 0 267 -95t89 -302v-711h-98v713q0 159 -62 232t-190 73q-167 0 -247 -92t-80 -289v-637h-101v743q0 275 -252 275q-171 0 -249 -99.5t-78 -318.5v-600h-99z" />
|
||||
<glyph unicode="n" horiz-adv-x="1208" d="M182 0v1087h84l19 -149h6q106 170 377 170q370 0 370 -397v-711h-98v705q0 164 -69 238.5t-214 74.5q-195 0 -285.5 -98.5t-90.5 -319.5v-600h-99z" />
|
||||
<glyph unicode="o" horiz-adv-x="1200" d="M119 545q0 266 129 414.5t354 148.5q224 0 351.5 -150.5t127.5 -412.5q0 -266 -129 -415.5t-356 -149.5q-143 0 -252 69t-167 198t-58 298zM223 545q0 -224 98.5 -349.5t278.5 -125.5t278.5 125.5t98.5 349.5q0 225 -99.5 349t-279.5 124t-277.5 -123.5t-97.5 -349.5z " />
|
||||
<glyph unicode="p" horiz-adv-x="1219" d="M182 -492v1579h84l19 -155h6q112 176 358 176q220 0 335.5 -144.5t115.5 -420.5q0 -268 -121.5 -415.5t-331.5 -147.5q-251 0 -366 188h-7l3 -84q4 -74 4 -162v-414h-99zM281 541q0 -255 85.5 -364t278.5 -109q167 0 258.5 124t91.5 347q0 479 -346 479 q-190 0 -279 -104.5t-89 -340.5v-32z" />
|
||||
<glyph unicode="q" horiz-adv-x="1219" d="M119 532q0 275 118 425.5t338 150.5q236 0 353 -174h6l18 153h84v-1579h-98v414q0 122 6 248h-6q-118 -190 -369 -190q-214 0 -332 142t-118 410zM223 530q0 -229 89.5 -345.5t258.5 -116.5q198 0 282.5 109t84.5 366v12q0 245 -85 354t-271 109q-176 0 -267.5 -124 t-91.5 -364z" />
|
||||
<glyph unicode="r" horiz-adv-x="797" d="M182 0v1087h84l10 -196h7q67 120 143 168.5t184 48.5q69 0 148 -14l-19 -95q-68 17 -141 17q-139 0 -228 -118t-89 -298v-600h-99z" />
|
||||
<glyph unicode="s" horiz-adv-x="954" d="M84 47v107q164 -82 346 -82q161 0 244.5 53.5t83.5 142.5q0 82 -66.5 138t-218.5 110q-163 59 -229 101.5t-99.5 96t-33.5 130.5q0 122 102.5 193t286.5 71q176 0 334 -66l-37 -90q-160 66 -297 66q-133 0 -211 -44t-78 -122q0 -85 60.5 -136t236.5 -114 q147 -53 214 -95.5t100.5 -96.5t33.5 -127q0 -146 -111 -224.5t-315 -78.5q-218 0 -346 67z" />
|
||||
<glyph unicode="t" horiz-adv-x="686" d="M25 1001v58l161 45l50 246h51v-263h319v-86h-319v-688q0 -125 44 -185t138 -60t164 16v-80q-72 -24 -166 -24q-144 0 -212.5 77t-68.5 242v702h-161z" />
|
||||
<glyph unicode="u" horiz-adv-x="1208" d="M170 377v710h98v-704q0 -164 69 -238.5t214 -74.5q194 0 285.5 98t91.5 319v600h98v-1087h-84l-18 150h-6q-106 -170 -377 -170q-371 0 -371 397z" />
|
||||
<glyph unicode="v" horiz-adv-x="940" d="M0 1087h102l281 -739q56 -142 84 -248h6q41 136 84 250l281 737h102l-420 -1087h-100z" />
|
||||
<glyph unicode="w" horiz-adv-x="1481" d="M31 1087h106l174 -630q61 -234 80 -344h6q59 234 86 311l224 663h90l213 -661q72 -235 88 -311h6q8 65 80 348l166 624h100l-295 -1087h-104l-238 727q-23 74 -59 217h-6l-21 -74l-45 -145l-242 -725h-98z" />
|
||||
<glyph unicode="x" horiz-adv-x="1020" d="M55 0l394 559l-379 528h114l324 -458l321 458h109l-373 -528l400 -559h-115l-342 485l-344 -485h-109z" />
|
||||
<glyph unicode="y" horiz-adv-x="940" d="M0 1087h102l230 -610q105 -281 133 -379h6q42 129 137 385l230 604h102l-487 -1263q-59 -154 -99 -208t-93.5 -81t-129.5 -27q-57 0 -127 21v86q58 -16 125 -16q51 0 90 24t70.5 74.5t73 160t53.5 142.5z" />
|
||||
<glyph unicode="z" horiz-adv-x="944" d="M82 0v63l645 936h-598v88h727v-63l-649 -936h651v-88h-776z" />
|
||||
<glyph unicode="{" horiz-adv-x="723" d="M61 528v80q122 2 176 51t54 148v350q0 299 360 305v-90q-138 -5 -200 -58t-62 -157v-305q0 -130 -44 -194t-142 -85v-8q97 -20 141.5 -83.5t44.5 -186.5v-322q0 -102 59.5 -152.5t202.5 -53.5v-91q-195 0 -277.5 75t-82.5 231v337q0 205 -230 209z" />
|
||||
<glyph unicode="|" horiz-adv-x="1108" d="M508 -506v2067h92v-2067h-92z" />
|
||||
<glyph unicode="}" horiz-adv-x="723" d="M72 -233q141 2 201.5 52.5t60.5 153.5v322q0 123 44.5 186.5t141.5 83.5v8q-97 20 -141.5 84t-44.5 195v305q0 103 -61.5 156.5t-200.5 58.5v90q174 0 267 -77.5t93 -227.5v-350q0 -100 54.5 -148.5t175.5 -50.5v-80q-230 -4 -230 -209v-337q0 -155 -82.5 -230.5 t-277.5 -75.5v91z" />
|
||||
<glyph unicode="~" d="M111 625v94q108 110 233 110q61 0 115 -13.5t155 -57.5q126 -58 220 -58q56 0 109.5 30.5t115.5 94.5v-96q-48 -49 -104.5 -81t-129.5 -32q-116 0 -270 72q-124 57 -221 57q-49 0 -108 -30.5t-115 -89.5z" />
|
||||
<glyph unicode="¡" horiz-adv-x="492" d="M166 1010q0 98 80 98q82 0 82 -98q0 -53 -23.5 -76t-58.5 -23q-34 0 -57 23t-23 76zM186 -375l29 1086h61l29 -1086h-119z" />
|
||||
<glyph unicode="¢" d="M211 745q0 232 102.5 381.5t288.5 182.5v174h82v-166h14q131 0 275 -55l-31 -84q-134 51 -237 51q-187 0 -288.5 -122.5t-101.5 -358.5q0 -225 100.5 -349.5t280.5 -124.5q131 0 267 58v-92q-110 -56 -267 -56h-12v-204h-82v210q-186 30 -288.5 175t-102.5 380z" />
|
||||
<glyph unicode="£" d="M78 0v84q110 21 171.5 110t61.5 224v258h-211v82h211v297q0 204 98 315t281 111q175 0 330 -68l-35 -86q-157 66 -295 66q-141 0 -209.5 -81t-68.5 -253v-301h411v-82h-411v-256q0 -116 -35 -196t-113 -128h809v-96h-995z" />
|
||||
<glyph unicode="¤" d="M127 326l139 141q-90 106 -90 256q0 147 90 258l-139 141l59 60l138 -142q103 93 260 93q155 0 260 -93l137 142l59 -60l-139 -141q90 -111 90 -258q0 -151 -90 -256l139 -141l-59 -60l-137 142q-110 -93 -260 -93q-153 0 -260 93l-138 -142zM260 723q0 -136 94.5 -232 t229.5 -96q134 0 228.5 95.5t94.5 232.5q0 136 -95 233t-228 97q-134 0 -229 -97t-95 -233z" />
|
||||
<glyph unicode="¥" d="M43 1462h117l426 -796l428 796h110l-432 -788h283v-82h-338v-205h338v-82h-338v-305h-105v305h-337v82h337v205h-337v82h278z" />
|
||||
<glyph unicode="¦" horiz-adv-x="1108" d="M508 258h92v-764h-92v764zM508 797v764h92v-764h-92z" />
|
||||
<glyph unicode="§" horiz-adv-x="1057" d="M129 63v95q182 -78 332 -78q162 0 247 49.5t85 140.5q0 55 -25 87.5t-88.5 65.5t-190.5 79q-200 73 -272 141.5t-72 169.5q0 83 50.5 152.5t138.5 107.5q-86 47 -125 102t-39 136q0 117 101.5 183.5t275.5 66.5q175 0 336 -64l-35 -80q-91 34 -158.5 47t-144.5 13 q-134 0 -205.5 -44.5t-71.5 -119.5q0 -54 25.5 -88.5t85.5 -65.5t188 -74q192 -64 264 -132.5t72 -170.5q0 -173 -186 -274q86 -42 129 -96t43 -136q0 -135 -113 -207.5t-311 -72.5q-92 0 -171 15t-165 52zM246 825q0 -65 31.5 -104t105.5 -75t250 -99q82 41 126 98t44 121 q0 62 -32 102t-108.5 77t-236.5 87q-81 -23 -130.5 -79t-49.5 -128z" />
|
||||
<glyph unicode="¨" horiz-adv-x="1182" d="M336 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86zM717 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86z" />
|
||||
<glyph unicode="©" horiz-adv-x="1704" d="M100 731q0 200 100 375t275 276t377 101q200 0 375 -100t276 -275t101 -377q0 -197 -97 -370t-272 -277t-383 -104q-207 0 -382 103.5t-272.5 276.5t-97.5 371zM193 731q0 -178 88.5 -329.5t240.5 -240.5t330 -89t329.5 88.5t240.5 240.5t89 330q0 174 -85.5 325 t-239 243t-334.5 92q-176 0 -328.5 -88.5t-241.5 -242.5t-89 -329zM489 725q0 208 111 332.5t297 124.5q119 0 227 -52l-37 -83q-98 45 -190 45q-142 0 -222.5 -94.5t-80.5 -264.5q0 -186 74.5 -275t220.5 -89q84 0 198 43v-88q-102 -45 -208 -45q-187 0 -288.5 115 t-101.5 331z" />
|
||||
<glyph unicode="ª" horiz-adv-x="686" d="M78 989q0 100 80 151.5t241 59.5l95 4v43q0 77 -38 114.5t-106 37.5q-87 0 -196 -49l-33 73q117 56 231 56q228 0 228 -215v-451h-68l-25 72q-84 -84 -202 -84q-95 0 -151 49t-56 139zM168 993q0 -54 35 -85t96 -31q90 0 142.5 50t52.5 142v64l-88 -5q-116 -6 -177 -36.5 t-61 -98.5z" />
|
||||
<glyph unicode="«" horiz-adv-x="885" d="M82 516v27l309 393l62 -43l-254 -363l254 -362l-62 -43zM442 516v27l310 393l61 -43l-254 -363l254 -362l-61 -43z" />
|
||||
<glyph unicode="¬" d="M111 682v82h927v-494h-82v412h-845z" />
|
||||
<glyph unicode="­" horiz-adv-x="659" d="M92 512v82h475v-82h-475z" />
|
||||
<glyph unicode="®" horiz-adv-x="1704" d="M100 731q0 200 100 375t275 276t377 101q200 0 375 -100t276 -275t101 -377q0 -197 -97 -370t-272 -277t-383 -104q-207 0 -382 103.5t-272.5 276.5t-97.5 371zM193 731q0 -178 88.5 -329.5t240.5 -240.5t330 -89t329.5 88.5t240.5 240.5t89 330q0 174 -85.5 325 t-239 243t-334.5 92q-176 0 -328.5 -88.5t-241.5 -242.5t-89 -329zM608 291v880h211q143 0 222 -62t79 -191q0 -79 -38.5 -139.5t-110.5 -94.5l237 -393h-121l-210 360h-168v-360h-101zM709 731h112q91 0 143 46.5t52 135.5q0 172 -197 172h-110v-354z" />
|
||||
<glyph unicode="¯" horiz-adv-x="1024" d="M-6 1556v82h1036v-82h-1036z" />
|
||||
<glyph unicode="°" horiz-adv-x="877" d="M139 1184q0 132 86.5 215.5t212.5 83.5t212.5 -83.5t86.5 -215.5t-86.5 -215.5t-212.5 -83.5q-130 0 -214.5 83t-84.5 216zM229 1184q0 -91 61 -154t148 -63q86 0 147.5 62t61.5 155q0 92 -60 154.5t-149 62.5q-90 0 -149.5 -64t-59.5 -153z" />
|
||||
<glyph unicode="±" d="M111 1v82h948v-82h-948zM111 682v82h432v434h82v-434h434v-82h-434v-432h-82v432h-432z" />
|
||||
<glyph unicode="²" horiz-adv-x="688" d="M53 586v78l242 237q125 121 172 193t47 149q0 71 -46.5 112.5t-123.5 41.5q-108 0 -217 -82l-49 65q119 103 270 103q124 0 194 -63.5t70 -174.5q0 -47 -13 -89t-40 -85.5t-68.5 -90t-308.5 -306.5h447v-88h-576z" />
|
||||
<glyph unicode="³" horiz-adv-x="688" d="M41 629v88q136 -62 266 -62q115 0 174.5 49t59.5 136q0 83 -59.5 122t-178.5 39h-131v84h135q105 0 158 43.5t53 120.5q0 67 -47 107.5t-127 40.5q-128 0 -246 -78l-47 70q130 94 293 94q127 0 199.5 -60t72.5 -163q0 -78 -44 -131.5t-117 -75.5q186 -45 186 -211 q0 -130 -88.5 -201.5t-247.5 -71.5q-144 0 -264 60z" />
|
||||
<glyph unicode="´" horiz-adv-x="1182" d="M393 1241v16q73 79 144.5 171.5t97.5 140.5h141v-17q-36 -52 -122.5 -138t-190.5 -173h-70z" />
|
||||
<glyph unicode="µ" horiz-adv-x="1221" d="M182 -492v1579h99v-704q0 -164 69 -238.5t213 -74.5q194 0 285.5 98t91.5 319v600h98v-1087h-84l-18 150h-6q-50 -77 -150 -123.5t-217 -46.5q-99 0 -167.5 27.5t-119.5 84.5q5 -92 5 -170v-414h-99z" />
|
||||
<glyph unicode="¶" horiz-adv-x="1341" d="M113 1042q0 260 109 387t341 127h543v-1816h-100v1722h-228v-1722h-100v819q-64 -18 -146 -18q-216 0 -317.5 125t-101.5 376z" />
|
||||
<glyph unicode="·" horiz-adv-x="487" d="M162 721q0 98 80 98q82 0 82 -98t-82 -98q-80 0 -80 98z" />
|
||||
<glyph unicode="¸" horiz-adv-x="420" d="M43 -393q30 -10 92 -10q78 0 119 28t41 80q0 94 -193 121l93 174h96l-66 -117q168 -37 168 -174q0 -100 -67.5 -150.5t-188.5 -50.5q-68 0 -94 11v88z" />
|
||||
<glyph unicode="¹" horiz-adv-x="688" d="M76 1298l274 164h92v-876h-98v547q0 99 12 233q-26 -23 -233 -145z" />
|
||||
<glyph unicode="º" horiz-adv-x="739" d="M70 1141q0 162 78 250t223 88q142 0 220.5 -87t78.5 -251q0 -161 -80 -250.5t-223 -89.5t-220 86t-77 254zM160 1141q0 -264 209 -264t209 264q0 131 -50 194.5t-159 63.5t-159 -63.5t-50 -194.5z" />
|
||||
<glyph unicode="»" horiz-adv-x="885" d="M72 168l254 362l-254 363l61 43l309 -391v-27l-309 -393zM432 168l254 362l-254 363l62 43l309 -391v-27l-309 -393z" />
|
||||
<glyph unicode="¼" horiz-adv-x="1516" d="M59 1298l274 164h92v-876h-98v547q0 99 12 233q-26 -23 -233 -145zM243 0l811 1462h94l-811 -1462h-94zM760 242v60l407 581h96v-563h129v-78h-129v-241h-90v241h-413zM864 320h309v221q0 132 8 232q-6 -12 -21.5 -35.5t-295.5 -417.5z" />
|
||||
<glyph unicode="½" horiz-adv-x="1516" d="M11 1298l274 164h92v-876h-98v547q0 99 12 233q-26 -23 -233 -145zM168 0l811 1462h94l-811 -1462h-94zM827 1v78l242 237q125 121 172 193t47 149q0 71 -46.5 112.5t-123.5 41.5q-108 0 -217 -82l-49 65q119 103 270 103q124 0 194 -63.5t70 -174.5q0 -47 -13 -89 t-40 -85.5t-68.5 -90t-308.5 -306.5h447v-88h-576z" />
|
||||
<glyph unicode="¾" horiz-adv-x="1516" d="M41 629v88q136 -62 266 -62q115 0 174.5 49t59.5 136q0 83 -59.5 122t-178.5 39h-131v84h135q105 0 158 43.5t53 120.5q0 67 -47 107.5t-127 40.5q-128 0 -246 -78l-47 70q130 94 293 94q127 0 199.5 -60t72.5 -163q0 -78 -44 -131.5t-117 -75.5q186 -45 186 -211 q0 -130 -88.5 -201.5t-247.5 -71.5q-144 0 -264 60zM395 0l811 1462h94l-811 -1462h-94zM863 242v60l407 581h96v-563h129v-78h-129v-241h-90v241h-413zM967 320h309v221q0 132 8 232q-6 -12 -21.5 -35.5t-295.5 -417.5z" />
|
||||
<glyph unicode="¿" horiz-adv-x="862" d="M74 -27q0 70 20 124t58.5 102t171.5 159q64 53 98.5 98.5t49.5 94t15 145.5v15h82v-37q0 -125 -39.5 -204.5t-136.5 -164.5l-90 -79q-73 -61 -104 -120.5t-31 -138.5q0 -124 82 -200t221 -76q125 0 233 46l64 27l37 -79q-111 -48 -185.5 -64t-152.5 -16q-184 0 -288.5 99 t-104.5 269zM440 1010q0 98 80 98q82 0 82 -98q0 -53 -23.5 -76t-58.5 -23q-34 0 -57 23t-23 76z" />
|
||||
<glyph unicode="À" horiz-adv-x="1229" d="M0 0l588 1468h65l576 -1468h-115l-203 516h-594l-204 -516h-113zM337 1890v17h142q26 -48 98.5 -142t142.5 -170v-16h-69q-96 79 -188.5 171.5t-125.5 139.5zM354 608h523l-199 527q-25 62 -60 172q-27 -96 -59 -174z" />
|
||||
<glyph unicode="Á" horiz-adv-x="1229" d="M0 0l588 1468h65l576 -1468h-115l-203 516h-594l-204 -516h-113zM354 608h523l-199 527q-25 62 -60 172q-27 -96 -59 -174zM504 1579v16q73 79 144.5 171.5t97.5 140.5h141v-17q-36 -52 -122.5 -138t-190.5 -173h-70z" />
|
||||
<glyph unicode="Â" horiz-adv-x="1229" d="M0 0l588 1468h65l576 -1468h-115l-203 516h-594l-204 -516h-113zM328 1579v16q62 67 131.5 156t110.5 156h98q68 -120 242 -312v-16h-70q-122 101 -221 207q-108 -114 -221 -207h-70zM354 608h523l-199 527q-25 62 -60 172q-27 -96 -59 -174z" />
|
||||
<glyph unicode="Ã" horiz-adv-x="1229" d="M0 0l588 1468h65l576 -1468h-115l-203 516h-594l-204 -516h-113zM287 1581q10 111 63 174.5t137 63.5q48 0 88 -25t82 -59q34 -28 66 -50t61 -22q46 0 77 36.5t48 119.5h76q-16 -116 -69 -177t-132 -61q-36 0 -75 18.5t-101 71.5q-32 26 -62.5 46t-62.5 20 q-45 0 -75 -34.5t-48 -121.5h-73zM354 608h523l-199 527q-25 62 -60 172q-27 -96 -59 -174z" />
|
||||
<glyph unicode="Ä" horiz-adv-x="1229" d="M0 0l588 1468h65l576 -1468h-115l-203 516h-594l-204 -516h-113zM354 608h523l-199 527q-25 62 -60 172q-27 -96 -59 -174zM367 1727q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86zM748 1727q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86z" />
|
||||
<glyph unicode="Å" horiz-adv-x="1229" d="M0 0l588 1468h65l576 -1468h-115l-203 516h-594l-204 -516h-113zM354 608h523l-199 527q-25 62 -60 172q-27 -96 -59 -174zM402 1610q0 94 60 152.5t157 58.5t157 -59t60 -152q0 -97 -60 -155t-157 -58t-157 58t-60 155zM482 1610q0 -66 37.5 -103.5t99.5 -37.5 t99.5 37.5t37.5 103.5q0 64 -39 101.5t-98 37.5q-62 0 -99.5 -38t-37.5 -101z" />
|
||||
<glyph unicode="Æ" horiz-adv-x="1653" d="M-2 0l653 1462h877v-94h-615v-553h576v-94h-576v-627h615v-94h-717v516h-475l-227 -516h-111zM377 608h434v760h-100z" />
|
||||
<glyph unicode="Ç" horiz-adv-x="1272" d="M129 735q0 223 84.5 393t243 262.5t368.5 92.5q214 0 383 -80l-41 -92q-160 80 -336 80q-275 0 -433 -176t-158 -482q0 -313 149 -486t426 -173q184 0 338 47v-90q-145 -51 -362 -51q-308 0 -485 199t-177 556zM561 -393q30 -10 92 -10q78 0 119 28t41 80q0 94 -193 121 l93 174h96l-66 -117q168 -37 168 -174q0 -100 -67.5 -150.5t-188.5 -50.5q-68 0 -94 11v88z" />
|
||||
<glyph unicode="È" horiz-adv-x="1130" d="M207 0v1462h799v-94h-697v-553h658v-94h-658v-627h697v-94h-799zM314 1890v17h142q26 -48 98.5 -142t142.5 -170v-16h-69q-96 79 -188.5 171.5t-125.5 139.5z" />
|
||||
<glyph unicode="É" horiz-adv-x="1130" d="M207 0v1462h799v-94h-697v-553h658v-94h-658v-627h697v-94h-799zM463 1579v16q73 79 144.5 171.5t97.5 140.5h141v-17q-36 -52 -122.5 -138t-190.5 -173h-70z" />
|
||||
<glyph unicode="Ê" horiz-adv-x="1130" d="M207 0v1462h799v-94h-697v-553h658v-94h-658v-627h697v-94h-799zM315 1579v16q62 67 131.5 156t110.5 156h98q68 -120 242 -312v-16h-70q-122 101 -221 207q-108 -114 -221 -207h-70z" />
|
||||
<glyph unicode="Ë" horiz-adv-x="1130" d="M207 0v1462h799v-94h-697v-553h658v-94h-658v-627h697v-94h-799zM354 1727q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86zM735 1727q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86z" />
|
||||
<glyph unicode="Ì" horiz-adv-x="516" d="M-63 1890v17h142q26 -48 98.5 -142t142.5 -170v-16h-69q-96 79 -188.5 171.5t-125.5 139.5zM207 0v1462h102v-1462h-102z" />
|
||||
<glyph unicode="Í" horiz-adv-x="516" d="M191 1579v16q73 79 144.5 171.5t97.5 140.5h141v-17q-36 -52 -122.5 -138t-190.5 -173h-70zM207 0v1462h102v-1462h-102z" />
|
||||
<glyph unicode="Î" horiz-adv-x="516" d="M-32 1579v16q62 67 131.5 156t110.5 156h98q68 -120 242 -312v-16h-70q-122 101 -221 207q-108 -114 -221 -207h-70zM207 0v1462h102v-1462h-102z" />
|
||||
<glyph unicode="Ï" horiz-adv-x="516" d="M5 1727q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86zM207 0v1462h102v-1462h-102zM386 1727q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86z" />
|
||||
<glyph unicode="Ð" horiz-adv-x="1466" d="M47 678v94h160v690h395q350 0 532.5 -183t182.5 -534q0 -368 -193 -556.5t-567 -188.5h-350v678h-160zM309 90h242q655 0 655 651q0 314 -159.5 472.5t-468.5 158.5h-269v-600h406v-94h-406v-588z" />
|
||||
<glyph unicode="Ñ" horiz-adv-x="1477" d="M207 0v1462h102l865 -1296h6q-9 180 -9 342v954h99v-1462h-103l-866 1298h-8q12 -232 12 -350v-948h-98zM400 1581q10 111 63 174.5t137 63.5q48 0 88 -25t82 -59q34 -28 66 -50t61 -22q46 0 77 36.5t48 119.5h76q-16 -116 -69 -177t-132 -61q-36 0 -75 18.5t-101 71.5 q-32 26 -62.5 46t-62.5 20q-45 0 -75 -34.5t-48 -121.5h-73z" />
|
||||
<glyph unicode="Ò" horiz-adv-x="1565" d="M129 735q0 349 175.5 549.5t479.5 200.5q306 0 479 -201.5t173 -550.5q0 -348 -174 -550.5t-480 -202.5q-305 0 -479 202.5t-174 552.5zM240 733q0 -314 140 -485.5t402 -171.5q264 0 403.5 170t139.5 487q0 316 -139.5 484.5t-401.5 168.5q-261 0 -402.5 -170 t-141.5 -483zM502 1890v17h142q26 -48 98.5 -142t142.5 -170v-16h-69q-96 79 -188.5 171.5t-125.5 139.5z" />
|
||||
<glyph unicode="Ó" horiz-adv-x="1565" d="M129 735q0 349 175.5 549.5t479.5 200.5q306 0 479 -201.5t173 -550.5q0 -348 -174 -550.5t-480 -202.5q-305 0 -479 202.5t-174 552.5zM240 733q0 -314 140 -485.5t402 -171.5q264 0 403.5 170t139.5 487q0 316 -139.5 484.5t-401.5 168.5q-261 0 -402.5 -170 t-141.5 -483zM686 1579v16q73 79 144.5 171.5t97.5 140.5h141v-17q-36 -52 -122.5 -138t-190.5 -173h-70z" />
|
||||
<glyph unicode="Ô" horiz-adv-x="1565" d="M129 735q0 349 175.5 549.5t479.5 200.5q306 0 479 -201.5t173 -550.5q0 -348 -174 -550.5t-480 -202.5q-305 0 -479 202.5t-174 552.5zM240 733q0 -314 140 -485.5t402 -171.5q264 0 403.5 170t139.5 487q0 316 -139.5 484.5t-401.5 168.5q-261 0 -402.5 -170 t-141.5 -483zM492 1579v16q62 67 131.5 156t110.5 156h98q68 -120 242 -312v-16h-70q-122 101 -221 207q-108 -114 -221 -207h-70z" />
|
||||
<glyph unicode="Õ" horiz-adv-x="1565" d="M129 735q0 349 175.5 549.5t479.5 200.5q306 0 479 -201.5t173 -550.5q0 -348 -174 -550.5t-480 -202.5q-305 0 -479 202.5t-174 552.5zM240 733q0 -314 140 -485.5t402 -171.5q264 0 403.5 170t139.5 487q0 316 -139.5 484.5t-401.5 168.5q-261 0 -402.5 -170 t-141.5 -483zM443 1581q10 111 63 174.5t137 63.5q48 0 88 -25t82 -59q34 -28 66 -50t61 -22q46 0 77 36.5t48 119.5h76q-16 -116 -69 -177t-132 -61q-36 0 -75 18.5t-101 71.5q-32 26 -62.5 46t-62.5 20q-45 0 -75 -34.5t-48 -121.5h-73z" />
|
||||
<glyph unicode="Ö" horiz-adv-x="1565" d="M129 735q0 349 175.5 549.5t479.5 200.5q306 0 479 -201.5t173 -550.5q0 -348 -174 -550.5t-480 -202.5q-305 0 -479 202.5t-174 552.5zM240 733q0 -314 140 -485.5t402 -171.5q264 0 403.5 170t139.5 487q0 316 -139.5 484.5t-401.5 168.5q-261 0 -402.5 -170 t-141.5 -483zM529 1727q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86zM910 1727q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86z" />
|
||||
<glyph unicode="×" d="M119 1130l57 58l408 -408l409 408l58 -58l-408 -407l406 -408l-58 -57l-407 408l-406 -408l-57 57l405 408z" />
|
||||
<glyph unicode="Ø" horiz-adv-x="1565" d="M129 735q0 349 175.5 549.5t479.5 200.5q232 0 392 -121l108 152l72 -60l-111 -153q191 -207 191 -570q0 -348 -174 -550.5t-480 -202.5q-236 0 -395 120l-86 -120l-74 59l90 127q-188 200 -188 569zM240 733q0 -312 139 -483l739 1034q-133 102 -334 102 q-261 0 -402.5 -170t-141.5 -483zM444 182q133 -106 338 -106q264 0 403.5 170t139.5 487q0 315 -139 486z" />
|
||||
<glyph unicode="Ù" horiz-adv-x="1473" d="M190 520v942h103v-946q0 -211 117 -328.5t331 -117.5q209 0 324 115.5t115 320.5v956h102v-946q0 -252 -146 -394t-407 -142q-254 0 -396.5 142.5t-142.5 397.5zM450 1890v17h142q26 -48 98.5 -142t142.5 -170v-16h-69q-96 79 -188.5 171.5t-125.5 139.5z" />
|
||||
<glyph unicode="Ú" horiz-adv-x="1473" d="M190 520v942h103v-946q0 -211 117 -328.5t331 -117.5q209 0 324 115.5t115 320.5v956h102v-946q0 -252 -146 -394t-407 -142q-254 0 -396.5 142.5t-142.5 397.5zM633 1579v16q73 79 144.5 171.5t97.5 140.5h141v-17q-36 -52 -122.5 -138t-190.5 -173h-70z" />
|
||||
<glyph unicode="Û" horiz-adv-x="1473" d="M190 520v942h103v-946q0 -211 117 -328.5t331 -117.5q209 0 324 115.5t115 320.5v956h102v-946q0 -252 -146 -394t-407 -142q-254 0 -396.5 142.5t-142.5 397.5zM444 1579v16q62 67 131.5 156t110.5 156h98q68 -120 242 -312v-16h-70q-122 101 -221 207 q-108 -114 -221 -207h-70z" />
|
||||
<glyph unicode="Ü" horiz-adv-x="1473" d="M190 520v942h103v-946q0 -211 117 -328.5t331 -117.5q209 0 324 115.5t115 320.5v956h102v-946q0 -252 -146 -394t-407 -142q-254 0 -396.5 142.5t-142.5 397.5zM481 1727q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86zM862 1727q0 46 15.5 66t47.5 20 q64 0 64 -86t-64 -86q-63 0 -63 86z" />
|
||||
<glyph unicode="Ý" horiz-adv-x="1081" d="M0 1462h117l426 -800l428 800h110l-487 -897v-565h-105v557zM434 1579v16q73 79 144.5 171.5t97.5 140.5h141v-17q-36 -52 -122.5 -138t-190.5 -173h-70z" />
|
||||
<glyph unicode="Þ" horiz-adv-x="1198" d="M207 0v1462h102v-264h256q522 0 522 -420q0 -212 -144 -325t-408 -113h-226v-340h-102zM309 428h201q247 0 357 81.5t110 264.5q0 169 -104 250.5t-322 81.5h-242v-678z" />
|
||||
<glyph unicode="ß" horiz-adv-x="1194" d="M182 0v1206q0 173 103.5 267t292.5 94q188 0 285.5 -72.5t97.5 -210.5q0 -139 -139 -250q-81 -64 -110.5 -100.5t-29.5 -75.5q0 -44 14.5 -68t51.5 -57t102 -78q106 -75 151.5 -124.5t68 -103t22.5 -120.5q0 -156 -88 -241.5t-246 -85.5q-95 0 -174.5 18.5t-126.5 48.5 v107q65 -38 148.5 -62t152.5 -24q114 0 174.5 54.5t60.5 160.5q0 83 -39 144t-149 136q-127 87 -175 147t-48 146q0 60 32.5 110t106.5 108q74 57 106.5 105.5t32.5 106.5q0 93 -70 143t-202 50q-145 0 -226 -69t-81 -196v-1214h-99z" />
|
||||
<glyph unicode="à" horiz-adv-x="1085" d="M98 289q0 159 132.5 247t383.5 93l207 6v72q0 155 -63 234t-203 79q-151 0 -313 -84l-37 86q179 84 354 84q179 0 267.5 -93t88.5 -290v-723h-73l-25 172h-8q-82 -105 -168.5 -148.5t-204.5 -43.5q-160 0 -249 82t-89 227zM203 285q0 -102 62.5 -158.5t176.5 -56.5 q174 0 274.5 99.5t100.5 276.5v107l-190 -8q-229 -11 -326.5 -71.5t-97.5 -188.5zM255 1552v17h142q26 -48 98.5 -142t142.5 -170v-16h-69q-96 79 -188.5 171.5t-125.5 139.5z" />
|
||||
<glyph unicode="á" horiz-adv-x="1085" d="M98 289q0 159 132.5 247t383.5 93l207 6v72q0 155 -63 234t-203 79q-151 0 -313 -84l-37 86q179 84 354 84q179 0 267.5 -93t88.5 -290v-723h-73l-25 172h-8q-82 -105 -168.5 -148.5t-204.5 -43.5q-160 0 -249 82t-89 227zM203 285q0 -102 62.5 -158.5t176.5 -56.5 q174 0 274.5 99.5t100.5 276.5v107l-190 -8q-229 -11 -326.5 -71.5t-97.5 -188.5zM422 1241v16q73 79 144.5 171.5t97.5 140.5h141v-17q-36 -52 -122.5 -138t-190.5 -173h-70z" />
|
||||
<glyph unicode="â" horiz-adv-x="1085" d="M98 289q0 159 132.5 247t383.5 93l207 6v72q0 155 -63 234t-203 79q-151 0 -313 -84l-37 86q179 84 354 84q179 0 267.5 -93t88.5 -290v-723h-73l-25 172h-8q-82 -105 -168.5 -148.5t-204.5 -43.5q-160 0 -249 82t-89 227zM203 285q0 -102 62.5 -158.5t176.5 -56.5 q174 0 274.5 99.5t100.5 276.5v107l-190 -8q-229 -11 -326.5 -71.5t-97.5 -188.5zM251 1241v16q62 67 131.5 156t110.5 156h98q68 -120 242 -312v-16h-70q-122 101 -221 207q-108 -114 -221 -207h-70z" />
|
||||
<glyph unicode="ã" horiz-adv-x="1085" d="M98 289q0 159 132.5 247t383.5 93l207 6v72q0 155 -63 234t-203 79q-151 0 -313 -84l-37 86q179 84 354 84q179 0 267.5 -93t88.5 -290v-723h-73l-25 172h-8q-82 -105 -168.5 -148.5t-204.5 -43.5q-160 0 -249 82t-89 227zM200 1243q10 111 63 174.5t137 63.5 q48 0 88 -25t82 -59q34 -28 66 -50t61 -22q46 0 77 36.5t48 119.5h76q-16 -116 -69 -177t-132 -61q-36 0 -75 18.5t-101 71.5q-32 26 -62.5 46t-62.5 20q-45 0 -75 -34.5t-48 -121.5h-73zM203 285q0 -102 62.5 -158.5t176.5 -56.5q174 0 274.5 99.5t100.5 276.5v107l-190 -8 q-229 -11 -326.5 -71.5t-97.5 -188.5z" />
|
||||
<glyph unicode="ä" horiz-adv-x="1085" d="M98 289q0 159 132.5 247t383.5 93l207 6v72q0 155 -63 234t-203 79q-151 0 -313 -84l-37 86q179 84 354 84q179 0 267.5 -93t88.5 -290v-723h-73l-25 172h-8q-82 -105 -168.5 -148.5t-204.5 -43.5q-160 0 -249 82t-89 227zM203 285q0 -102 62.5 -158.5t176.5 -56.5 q174 0 274.5 99.5t100.5 276.5v107l-190 -8q-229 -11 -326.5 -71.5t-97.5 -188.5zM282 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86zM663 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86z" />
|
||||
<glyph unicode="å" horiz-adv-x="1085" d="M98 289q0 159 132.5 247t383.5 93l207 6v72q0 155 -63 234t-203 79q-151 0 -313 -84l-37 86q179 84 354 84q179 0 267.5 -93t88.5 -290v-723h-73l-25 172h-8q-82 -105 -168.5 -148.5t-204.5 -43.5q-160 0 -249 82t-89 227zM203 285q0 -102 62.5 -158.5t176.5 -56.5 q174 0 274.5 99.5t100.5 276.5v107l-190 -8q-229 -11 -326.5 -71.5t-97.5 -188.5zM325 1456q0 94 60 152.5t157 58.5t157 -59t60 -152q0 -97 -60 -155t-157 -58t-157 58t-60 155zM405 1456q0 -66 37.5 -103.5t99.5 -37.5t99.5 37.5t37.5 103.5q0 64 -39 101.5t-98 37.5 q-62 0 -99.5 -38t-37.5 -101z" />
|
||||
<glyph unicode="æ" horiz-adv-x="1731" d="M98 289q0 154 125 243t377 97l201 6v72q0 155 -61.5 234t-198.5 79q-148 0 -305 -84l-37 86q173 84 346 84q261 0 325 -211q111 213 347 213q184 0 289.5 -134.5t105.5 -363.5v-80h-715q0 -460 348 -460q85 0 150 12t174 57v-90q-92 -41 -165 -55t-161 -14 q-295 0 -397 256q-68 -133 -168 -194.5t-252 -61.5q-156 0 -242 82.5t-86 226.5zM203 285q0 -102 61 -158.5t170 -56.5q169 0 266 99.5t97 276.5v107l-187 -8q-219 -11 -313 -71.5t-94 -188.5zM903 618h604q0 188 -77.5 295t-212.5 107q-284 0 -314 -402z" />
|
||||
<glyph unicode="ç" horiz-adv-x="973" d="M119 537q0 270 137 420.5t375 150.5q141 0 270 -49l-27 -88q-141 47 -245 47q-200 0 -303 -123.5t-103 -355.5q0 -220 103 -344.5t288 -124.5q148 0 275 53v-92q-104 -51 -273 -51q-233 0 -365 147t-132 410zM373 -393q30 -10 92 -10q78 0 119 28t41 80q0 94 -193 121 l93 174h96l-66 -117q168 -37 168 -174q0 -100 -67.5 -150.5t-188.5 -50.5q-68 0 -94 11v88z" />
|
||||
<glyph unicode="è" horiz-adv-x="1124" d="M119 535q0 260 128 416.5t345 156.5q192 0 303 -134t111 -364v-80h-783q2 -224 104.5 -342t293.5 -118q93 0 163.5 13t178.5 56v-90q-92 -40 -170 -54.5t-172 -14.5q-237 0 -369.5 146t-132.5 409zM229 618h672q0 189 -82 295.5t-227 106.5q-157 0 -252 -103.5 t-111 -298.5zM302 1552v17h142q26 -48 98.5 -142t142.5 -170v-16h-69q-96 79 -188.5 171.5t-125.5 139.5z" />
|
||||
<glyph unicode="é" horiz-adv-x="1124" d="M119 535q0 260 128 416.5t345 156.5q192 0 303 -134t111 -364v-80h-783q2 -224 104.5 -342t293.5 -118q93 0 163.5 13t178.5 56v-90q-92 -40 -170 -54.5t-172 -14.5q-237 0 -369.5 146t-132.5 409zM229 618h672q0 189 -82 295.5t-227 106.5q-157 0 -252 -103.5 t-111 -298.5zM452 1241v16q73 79 144.5 171.5t97.5 140.5h141v-17q-36 -52 -122.5 -138t-190.5 -173h-70z" />
|
||||
<glyph unicode="ê" horiz-adv-x="1124" d="M119 535q0 260 128 416.5t345 156.5q192 0 303 -134t111 -364v-80h-783q2 -224 104.5 -342t293.5 -118q93 0 163.5 13t178.5 56v-90q-92 -40 -170 -54.5t-172 -14.5q-237 0 -369.5 146t-132.5 409zM229 618h672q0 189 -82 295.5t-227 106.5q-157 0 -252 -103.5 t-111 -298.5zM290 1241v16q62 67 131.5 156t110.5 156h98q68 -120 242 -312v-16h-70q-122 101 -221 207q-108 -114 -221 -207h-70z" />
|
||||
<glyph unicode="ë" horiz-adv-x="1124" d="M119 535q0 260 128 416.5t345 156.5q192 0 303 -134t111 -364v-80h-783q2 -224 104.5 -342t293.5 -118q93 0 163.5 13t178.5 56v-90q-92 -40 -170 -54.5t-172 -14.5q-237 0 -369.5 146t-132.5 409zM229 618h672q0 189 -82 295.5t-227 106.5q-157 0 -252 -103.5 t-111 -298.5zM331 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86zM712 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86z" />
|
||||
<glyph unicode="ì" horiz-adv-x="463" d="M-34 1552v17h142q26 -48 98.5 -142t142.5 -170v-16h-69q-96 79 -188.5 171.5t-125.5 139.5zM182 0v1087h99v-1087h-99z" />
|
||||
<glyph unicode="í" horiz-adv-x="463" d="M107 1241v16q73 79 144.5 171.5t97.5 140.5h141v-17q-36 -52 -122.5 -138t-190.5 -173h-70zM182 0v1087h99v-1087h-99z" />
|
||||
<glyph unicode="î" horiz-adv-x="463" d="M-58 1241v16q62 67 131.5 156t110.5 156h98q68 -120 242 -312v-16h-70q-122 101 -221 207q-108 -114 -221 -207h-70zM182 0v1087h99v-1087h-99z" />
|
||||
<glyph unicode="ï" horiz-adv-x="463" d="M-21 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86zM182 0v1087h99v-1087h-99zM360 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86z" />
|
||||
<glyph unicode="ð" horiz-adv-x="1174" d="M117 471q0 228 126.5 357.5t342.5 129.5q108 0 187.5 -33t148.5 -96l4 2q-64 270 -269 459l-270 -157l-49 77l244 146q-86 62 -199 119l45 81q147 -69 248 -145l225 137l49 -84l-202 -121q154 -151 230.5 -353t76.5 -431q0 -276 -124 -427.5t-349 -151.5 q-214 0 -339.5 130t-125.5 361zM221 463q0 -186 94.5 -289.5t268.5 -103.5q179 0 272.5 123t93.5 364q0 146 -97 228.5t-267 82.5q-185 0 -275 -100.5t-90 -304.5z" />
|
||||
<glyph unicode="ñ" horiz-adv-x="1208" d="M182 0v1087h84l19 -149h6q106 170 377 170q370 0 370 -397v-711h-98v705q0 164 -69 238.5t-214 74.5q-195 0 -285.5 -98.5t-90.5 -319.5v-600h-99zM282 1243q10 111 63 174.5t137 63.5q48 0 88 -25t82 -59q34 -28 66 -50t61 -22q46 0 77 36.5t48 119.5h76 q-16 -116 -69 -177t-132 -61q-36 0 -75 18.5t-101 71.5q-32 26 -62.5 46t-62.5 20q-45 0 -75 -34.5t-48 -121.5h-73z" />
|
||||
<glyph unicode="ò" horiz-adv-x="1200" d="M119 545q0 266 129 414.5t354 148.5q224 0 351.5 -150.5t127.5 -412.5q0 -266 -129 -415.5t-356 -149.5q-143 0 -252 69t-167 198t-58 298zM223 545q0 -224 98.5 -349.5t278.5 -125.5t278.5 125.5t98.5 349.5q0 225 -99.5 349t-279.5 124t-277.5 -123.5t-97.5 -349.5z M335 1552v17h142q26 -48 98.5 -142t142.5 -170v-16h-69q-96 79 -188.5 171.5t-125.5 139.5z" />
|
||||
<glyph unicode="ó" horiz-adv-x="1200" d="M119 545q0 266 129 414.5t354 148.5q224 0 351.5 -150.5t127.5 -412.5q0 -266 -129 -415.5t-356 -149.5q-143 0 -252 69t-167 198t-58 298zM223 545q0 -224 98.5 -349.5t278.5 -125.5t278.5 125.5t98.5 349.5q0 225 -99.5 349t-279.5 124t-277.5 -123.5t-97.5 -349.5z M499 1241v16q73 79 144.5 171.5t97.5 140.5h141v-17q-36 -52 -122.5 -138t-190.5 -173h-70z" />
|
||||
<glyph unicode="ô" horiz-adv-x="1200" d="M119 545q0 266 129 414.5t354 148.5q224 0 351.5 -150.5t127.5 -412.5q0 -266 -129 -415.5t-356 -149.5q-143 0 -252 69t-167 198t-58 298zM223 545q0 -224 98.5 -349.5t278.5 -125.5t278.5 125.5t98.5 349.5q0 225 -99.5 349t-279.5 124t-277.5 -123.5t-97.5 -349.5z M309 1241v16q62 67 131.5 156t110.5 156h98q68 -120 242 -312v-16h-70q-122 101 -221 207q-108 -114 -221 -207h-70z" />
|
||||
<glyph unicode="õ" horiz-adv-x="1200" d="M119 545q0 266 129 414.5t354 148.5q224 0 351.5 -150.5t127.5 -412.5q0 -266 -129 -415.5t-356 -149.5q-143 0 -252 69t-167 198t-58 298zM223 545q0 -224 98.5 -349.5t278.5 -125.5t278.5 125.5t98.5 349.5q0 225 -99.5 349t-279.5 124t-277.5 -123.5t-97.5 -349.5z M264 1243q10 111 63 174.5t137 63.5q48 0 88 -25t82 -59q34 -28 66 -50t61 -22q46 0 77 36.5t48 119.5h76q-16 -116 -69 -177t-132 -61q-36 0 -75 18.5t-101 71.5q-32 26 -62.5 46t-62.5 20q-45 0 -75 -34.5t-48 -121.5h-73z" />
|
||||
<glyph unicode="ö" horiz-adv-x="1200" d="M119 545q0 266 129 414.5t354 148.5q224 0 351.5 -150.5t127.5 -412.5q0 -266 -129 -415.5t-356 -149.5q-143 0 -252 69t-167 198t-58 298zM223 545q0 -224 98.5 -349.5t278.5 -125.5t278.5 125.5t98.5 349.5q0 225 -99.5 349t-279.5 124t-277.5 -123.5t-97.5 -349.5z M346 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86zM727 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86z" />
|
||||
<glyph unicode="÷" d="M111 682v82h948v-82h-948zM504 371q0 98 80 98q82 0 82 -98q0 -53 -23.5 -76t-58.5 -23q-34 0 -57 23t-23 76zM504 1075q0 99 80 99q82 0 82 -99q0 -52 -23.5 -75t-58.5 -23q-34 0 -57 23t-23 75z" />
|
||||
<glyph unicode="ø" horiz-adv-x="1200" d="M119 545q0 266 129 414.5t354 148.5q179 0 301 -104l96 124l74 -55l-104 -137q112 -147 112 -391q0 -266 -129 -415.5t-356 -149.5q-173 0 -291 98l-86 -113l-72 58l93 120q-121 153 -121 402zM223 545q0 -200 78 -322l543 705q-98 90 -246 90q-180 0 -277.5 -123.5 t-97.5 -349.5zM362 152q94 -82 238 -82q180 0 278.5 125.5t98.5 349.5q0 190 -72 309z" />
|
||||
<glyph unicode="ù" horiz-adv-x="1208" d="M170 377v710h98v-704q0 -164 69 -238.5t214 -74.5q194 0 285.5 98t91.5 319v600h98v-1087h-84l-18 150h-6q-106 -170 -377 -170q-371 0 -371 397zM304 1552v17h142q26 -48 98.5 -142t142.5 -170v-16h-69q-96 79 -188.5 171.5t-125.5 139.5z" />
|
||||
<glyph unicode="ú" horiz-adv-x="1208" d="M170 377v710h98v-704q0 -164 69 -238.5t214 -74.5q194 0 285.5 98t91.5 319v600h98v-1087h-84l-18 150h-6q-106 -170 -377 -170q-371 0 -371 397zM495 1241v16q73 79 144.5 171.5t97.5 140.5h141v-17q-36 -52 -122.5 -138t-190.5 -173h-70z" />
|
||||
<glyph unicode="û" horiz-adv-x="1208" d="M170 377v710h98v-704q0 -164 69 -238.5t214 -74.5q194 0 285.5 98t91.5 319v600h98v-1087h-84l-18 150h-6q-106 -170 -377 -170q-371 0 -371 397zM313 1241v16q62 67 131.5 156t110.5 156h98q68 -120 242 -312v-16h-70q-122 101 -221 207q-108 -114 -221 -207h-70z" />
|
||||
<glyph unicode="ü" horiz-adv-x="1208" d="M170 377v710h98v-704q0 -164 69 -238.5t214 -74.5q194 0 285.5 98t91.5 319v600h98v-1087h-84l-18 150h-6q-106 -170 -377 -170q-371 0 -371 397zM350 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86zM731 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86 q-63 0 -63 86z" />
|
||||
<glyph unicode="ý" horiz-adv-x="940" d="M0 1087h102l230 -610q105 -281 133 -379h6q42 129 137 385l230 604h102l-487 -1263q-59 -154 -99 -208t-93.5 -81t-129.5 -27q-57 0 -127 21v86q58 -16 125 -16q51 0 90 24t70.5 74.5t73 160t53.5 142.5zM361 1241v16q73 79 144.5 171.5t97.5 140.5h141v-17 q-36 -52 -122.5 -138t-190.5 -173h-70z" />
|
||||
<glyph unicode="þ" horiz-adv-x="1219" d="M182 -492v2048h99v-391l-7 -247h7q114 190 368 190q220 0 335.5 -144.5t115.5 -420.5q0 -268 -121.5 -415.5t-331.5 -147.5q-251 0 -366 188h-7l3 -84q4 -74 4 -162v-414h-99zM281 541q0 -255 85.5 -364t278.5 -109q167 0 258.5 124t91.5 347q0 479 -348 479 q-193 0 -279.5 -105t-86.5 -354v-18z" />
|
||||
<glyph unicode="ÿ" horiz-adv-x="940" d="M0 1087h102l230 -610q105 -281 133 -379h6q42 129 137 385l230 604h102l-487 -1263q-59 -154 -99 -208t-93.5 -81t-129.5 -27q-57 0 -127 21v86q58 -16 125 -16q51 0 90 24t70.5 74.5t73 160t53.5 142.5zM214 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86 q-63 0 -63 86zM595 1389q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86z" />
|
||||
<glyph unicode="Œ" horiz-adv-x="1839" d="M129 735q0 347 174.5 545.5t480.5 198.5q78 0 183 -17h747v-94h-655v-553h616v-94h-616v-627h655v-94h-756q-76 -16 -176 -16q-305 0 -479 200t-174 551zM240 733q0 -315 140.5 -484t401.5 -169q109 0 174 18v1266q-62 16 -172 16q-262 0 -403 -167.5t-141 -479.5z" />
|
||||
<glyph unicode="œ" horiz-adv-x="1942" d="M119 545q0 266 129 414.5t354 148.5q151 0 251 -70t157 -209q110 279 399 279q192 0 303 -134t111 -364v-80h-762q2 -230 100.5 -345t276.5 -115q93 0 163.5 13t178.5 56v-90q-92 -40 -170 -54.5t-172 -14.5q-156 0 -266.5 67.5t-165.5 198.5q-59 -128 -158 -197 t-252 -69q-143 0 -252 69t-167 198t-58 298zM223 545q0 -224 98.5 -349.5t278.5 -125.5q174 0 265 122.5t91 352.5q0 224 -93 348.5t-265 124.5q-180 0 -277.5 -123.5t-97.5 -349.5zM1065 618h653q0 189 -82 295.5t-227 106.5q-155 0 -242 -104t-102 -298z" />
|
||||
<glyph unicode="Ÿ" horiz-adv-x="1081" d="M0 1462h117l426 -800l428 800h110l-487 -897v-565h-105v557zM288 1727q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86zM669 1727q0 46 15.5 66t47.5 20q64 0 64 -86t-64 -86q-63 0 -63 86z" />
|
||||
<glyph unicode="ˆ" horiz-adv-x="1182" d="M299 1241v16q62 67 131.5 156t110.5 156h98q68 -120 242 -312v-16h-70q-122 101 -221 207q-108 -114 -221 -207h-70z" />
|
||||
<glyph unicode="˜" horiz-adv-x="1182" d="M283 1243q10 111 63 174.5t137 63.5q48 0 88 -25t82 -59q34 -28 66 -50t61 -22q46 0 77 36.5t48 119.5h76q-16 -116 -69 -177t-132 -61q-36 0 -75 18.5t-101 71.5q-32 26 -62.5 46t-62.5 20q-45 0 -75 -34.5t-48 -121.5h-73z" />
|
||||
<glyph unicode=" " horiz-adv-x="953" />
|
||||
<glyph unicode=" " horiz-adv-x="1907" />
|
||||
<glyph unicode=" " horiz-adv-x="953" />
|
||||
<glyph unicode=" " horiz-adv-x="1907" />
|
||||
<glyph unicode=" " horiz-adv-x="635" />
|
||||
<glyph unicode=" " horiz-adv-x="476" />
|
||||
<glyph unicode=" " horiz-adv-x="317" />
|
||||
<glyph unicode=" " horiz-adv-x="317" />
|
||||
<glyph unicode=" " horiz-adv-x="238" />
|
||||
<glyph unicode=" " horiz-adv-x="381" />
|
||||
<glyph unicode=" " horiz-adv-x="105" />
|
||||
<glyph unicode="‐" horiz-adv-x="659" d="M92 512v82h475v-82h-475z" />
|
||||
<glyph unicode="‑" horiz-adv-x="659" d="M92 512v82h475v-82h-475z" />
|
||||
<glyph unicode="‒" horiz-adv-x="659" d="M92 512v82h475v-82h-475z" />
|
||||
<glyph unicode="–" horiz-adv-x="1024" d="M82 512v82h860v-82h-860z" />
|
||||
<glyph unicode="—" horiz-adv-x="2048" d="M82 512v82h1884v-82h-1884z" />
|
||||
<glyph unicode="‘" horiz-adv-x="297" d="M29 981q32 112 81.5 251t92.5 230h65q-30 -101 -64.5 -257t-45.5 -244h-117z" />
|
||||
<glyph unicode="’" horiz-adv-x="297" d="M29 961q29 96 61 241.5t49 259.5h117l12 -20q-75 -265 -174 -481h-65z" />
|
||||
<glyph unicode="‚" horiz-adv-x="451" d="M68 -263q29 96 61 241.5t49 259.5h117l12 -20q-75 -265 -174 -481h-65z" />
|
||||
<glyph unicode="“" horiz-adv-x="614" d="M29 981q32 112 81.5 251t92.5 230h65q-30 -101 -64.5 -257t-45.5 -244h-117zM346 981q34 120 83 255t91 226h66q-30 -98 -63 -248.5t-48 -252.5h-117z" />
|
||||
<glyph unicode="”" horiz-adv-x="614" d="M29 961q29 96 61 241.5t49 259.5h117l12 -20q-75 -265 -174 -481h-65zM346 961q30 98 63 248.5t48 252.5h116l13 -20q-36 -128 -85 -261t-89 -220h-66z" />
|
||||
<glyph unicode="„" horiz-adv-x="768" d="M68 -263q29 96 61 241.5t49 259.5h117l12 -20q-75 -265 -174 -481h-65zM385 -263q30 98 63 248.5t48 252.5h116l13 -20q-36 -128 -85 -261t-89 -220h-66z" />
|
||||
<glyph unicode="•" horiz-adv-x="770" d="M231 748q0 89 40.5 134.5t113.5 45.5t113.5 -47t40.5 -133q0 -85 -41 -133t-113 -48t-113 47t-41 134z" />
|
||||
<glyph unicode="…" horiz-adv-x="1466" d="M162 78q0 98 80 98q82 0 82 -98t-82 -98q-80 0 -80 98zM651 78q0 98 80 98q82 0 82 -98t-82 -98q-80 0 -80 98zM1141 78q0 98 80 98q82 0 82 -98t-82 -98q-80 0 -80 98z" />
|
||||
<glyph unicode=" " horiz-adv-x="381" />
|
||||
<glyph unicode="‹" horiz-adv-x="524" d="M82 516v27l309 393l62 -43l-254 -363l254 -362l-62 -43z" />
|
||||
<glyph unicode="›" horiz-adv-x="524" d="M72 168l254 362l-254 363l61 43l309 -391v-27l-309 -393z" />
|
||||
<glyph unicode=" " horiz-adv-x="476" />
|
||||
<glyph unicode="€" d="M74 528v82h172q-4 38 -4 113l4 102h-172v82h184q39 272 183 425t362 153q88 0 161 -17t148 -57l-39 -86q-132 72 -270 72q-174 0 -288 -125.5t-155 -364.5h502v-82h-510l-4 -104v-24q0 -65 4 -87h449v-82h-443q30 -217 147.5 -338.5t301.5 -121.5q148 0 287 65v-94 q-81 -34 -150.5 -46.5t-140.5 -12.5q-228 0 -367.5 140t-181.5 408h-180z" />
|
||||
<glyph unicode="™" horiz-adv-x="1485" d="M10 1384v78h522v-78h-219v-643h-86v643h-217zM608 741v721h125l221 -606l224 606h125v-721h-86v398l4 207h-7l-227 -605h-74l-221 609h-6l4 -201v-408h-82z" />
|
||||
<glyph unicode="" horiz-adv-x="1085" d="M0 0v1085h1085v-1085h-1085z" />
|
||||
</font>
|
||||
</defs></svg>
|
||||
|
After Width: | Height: | Size: 54 KiB |
@@ -0,0 +1,146 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>
|
||||
This is a custom SVG webfont generated by Font Squirrel.
|
||||
Copyright : Digitized data copyright 20102011 Google Corporation
|
||||
Foundry : Ascender Corporation
|
||||
Foundry URL : httpwwwascendercorpcom
|
||||
</metadata>
|
||||
<defs>
|
||||
<font id="OpenSansRegular" horiz-adv-x="1171" >
|
||||
<font-face units-per-em="2048" ascent="1638" descent="-410" />
|
||||
<missing-glyph horiz-adv-x="532" />
|
||||
<glyph unicode=" " horiz-adv-x="532" />
|
||||
<glyph unicode="	" horiz-adv-x="532" />
|
||||
<glyph unicode=" " horiz-adv-x="532" />
|
||||
<glyph unicode="!" horiz-adv-x="547" d="M152 106q0 136 120 136q58 0 89.5 -35t31.5 -101q0 -64 -32 -99.5t-89 -35.5q-52 0 -86 31.5t-34 103.5zM170 1462h207l-51 -1059h-105z" />
|
||||
<glyph unicode=""" horiz-adv-x="821" d="M133 1462h186l-40 -528h-105zM502 1462h186l-41 -528h-104z" />
|
||||
<glyph unicode="#" horiz-adv-x="1323" d="M51 430v129h287l68 340h-277v127h299l82 436h139l-82 -436h305l84 436h134l-84 -436h264v-127h-289l-66 -340h283v-129h-307l-84 -430h-137l84 430h-303l-82 -430h-136l80 430h-262zM475 559h303l66 340h-303z" />
|
||||
<glyph unicode="$" d="M131 170v156q83 -37 191.5 -60.5t197.5 -23.5v440q-205 65 -287.5 151t-82.5 222q0 131 101.5 215t268.5 102v182h129v-180q184 -5 355 -74l-52 -131q-149 59 -303 70v-434q157 -50 235 -97.5t115 -109t37 -149.5q0 -136 -102 -224.5t-285 -111.5v-232h-129v223 q-112 0 -217 17.5t-172 48.5zM319 1057q0 -76 45 -122t156 -87v387q-99 -16 -150 -62.5t-51 -115.5zM649 252q217 30 217 184q0 72 -44.5 116.5t-172.5 88.5v-389z" />
|
||||
<glyph unicode="%" horiz-adv-x="1686" d="M104 1026q0 227 74.5 342t220.5 115q145 0 223 -119t78 -338q0 -228 -76.5 -344.5t-224.5 -116.5q-140 0 -217.5 119t-77.5 342zM242 1026q0 -170 37 -255t120 -85q164 0 164 340q0 338 -164 338q-83 0 -120 -84t-37 -254zM365 0l811 1462h147l-811 -1462h-147zM985 440 q0 227 74.5 342t220.5 115q142 0 221.5 -117.5t79.5 -339.5q0 -227 -76.5 -343.5t-224.5 -116.5q-142 0 -218.5 119t-76.5 341zM1122 440q0 -171 37 -255.5t121 -84.5t124 83.5t40 256.5q0 171 -40 253.5t-124 82.5t-121 -82.5t-37 -253.5z" />
|
||||
<glyph unicode="&" horiz-adv-x="1495" d="M113 379q0 130 69.5 230t249.5 202q-85 95 -115.5 144t-48.5 102t-18 110q0 150 98 234t273 84q162 0 255 -83.5t93 -232.5q0 -107 -68 -197.5t-225 -183.5l407 -391q56 62 89.5 145.5t56.5 182.5h168q-68 -286 -205 -434l299 -291h-229l-185 178q-118 -106 -240 -152 t-272 -46q-215 0 -333.5 106t-118.5 293zM285 383q0 -117 77.5 -185.5t206.5 -68.5q241 0 400 154l-437 424q-111 -68 -157 -112.5t-68 -95.5t-22 -116zM414 1171q0 -69 36 -131.5t123 -150.5q129 75 179.5 138.5t50.5 146.5q0 77 -51.5 125.5t-137.5 48.5q-89 0 -144.5 -48 t-55.5 -129z" />
|
||||
<glyph unicode="'" horiz-adv-x="453" d="M133 1462h186l-40 -528h-105z" />
|
||||
<glyph unicode="(" horiz-adv-x="606" d="M82 561q0 265 77.5 496t223.5 405h162q-144 -193 -216.5 -424t-72.5 -475q0 -240 74 -469t213 -418h-160q-147 170 -224 397t-77 488z" />
|
||||
<glyph unicode=")" horiz-adv-x="606" d="M61 1462h162q147 -175 224 -406.5t77 -494.5t-77.5 -490t-223.5 -395h-160q139 188 213 417.5t74 469.5q0 244 -72.5 475t-216.5 424z" />
|
||||
<glyph unicode="*" horiz-adv-x="1130" d="M86 1090l29 182l391 -111l-43 395h194l-43 -395l398 111l26 -182l-381 -31l248 -326l-172 -94l-176 362l-160 -362l-176 94l242 326z" />
|
||||
<glyph unicode="+" d="M104 653v138h410v428h139v-428h412v-138h-412v-426h-139v426h-410z" />
|
||||
<glyph unicode="," horiz-adv-x="502" d="M63 -264q27 104 59.5 257t45.5 245h182l15 -23q-26 -100 -75 -232.5t-102 -246.5h-125z" />
|
||||
<glyph unicode="-" horiz-adv-x="659" d="M84 473v152h491v-152h-491z" />
|
||||
<glyph unicode="." horiz-adv-x="545" d="M152 106q0 67 30.5 101.5t87.5 34.5q58 0 90.5 -34.5t32.5 -101.5q0 -65 -33 -100t-90 -35q-51 0 -84.5 31.5t-33.5 103.5z" />
|
||||
<glyph unicode="/" horiz-adv-x="752" d="M20 0l545 1462h166l-545 -1462h-166z" />
|
||||
<glyph unicode="0" d="M102 733q0 382 119 567t363 185q238 0 361.5 -193t123.5 -559q0 -379 -119.5 -566t-365.5 -187q-236 0 -359 191.5t-123 561.5zM270 733q0 -319 75 -464.5t239 -145.5q166 0 240.5 147.5t74.5 462.5t-74.5 461.5t-240.5 146.5q-164 0 -239 -144.5t-75 -463.5z" />
|
||||
<glyph unicode="1" d="M188 1163l387 299h140v-1462h-162v1042q0 130 8 246q-21 -21 -47 -44t-238 -195z" />
|
||||
<glyph unicode="2" d="M100 0v143l385 387q176 178 232 254t84 148t28 155q0 117 -71 185.5t-197 68.5q-91 0 -172.5 -30t-181.5 -109l-88 113q202 168 440 168q206 0 323 -105.5t117 -283.5q0 -139 -78 -275t-292 -344l-320 -313v-8h752v-154h-961z" />
|
||||
<glyph unicode="3" d="M94 59v158q95 -47 202.5 -71.5t203.5 -24.5q379 0 379 297q0 266 -418 266h-144v143h146q171 0 271 75.5t100 209.5q0 107 -73.5 168t-199.5 61q-96 0 -181 -26t-194 -96l-84 112q90 71 207.5 111.5t247.5 40.5q213 0 331 -97.5t118 -267.5q0 -140 -78.5 -229 t-222.5 -119v-8q176 -22 261 -112t85 -236q0 -209 -145 -321.5t-412 -112.5q-116 0 -212.5 17.5t-187.5 61.5z" />
|
||||
<glyph unicode="4" d="M43 336v145l694 989h176v-983h217v-151h-217v-336h-159v336h-711zM209 487h545v486q0 143 10 323h-8q-48 -96 -90 -159z" />
|
||||
<glyph unicode="5" d="M133 59v160q70 -45 174 -70.5t205 -25.5q176 0 273.5 83t97.5 240q0 306 -375 306q-95 0 -254 -29l-86 55l55 684h727v-153h-585l-37 -439q115 23 229 23q231 0 363.5 -114.5t132.5 -313.5q0 -227 -144.5 -356t-398.5 -129q-247 0 -377 79z" />
|
||||
<glyph unicode="6" d="M117 625q0 431 167.5 644.5t495.5 213.5q113 0 178 -19v-143q-77 25 -176 25q-235 0 -359 -146.5t-136 -460.5h12q110 172 348 172q197 0 310.5 -119t113.5 -323q0 -228 -124.5 -358.5t-336.5 -130.5q-227 0 -360 170.5t-133 474.5zM287 506q0 -103 40 -192t113.5 -141 t167.5 -52q142 0 220.5 89.5t78.5 258.5q0 145 -73 228t-218 83q-90 0 -165 -37t-119.5 -102t-44.5 -135z" />
|
||||
<glyph unicode="7" d="M94 1309v153h973v-133l-598 -1329h-184l606 1309h-797z" />
|
||||
<glyph unicode="8" d="M104 373q0 251 306 391q-138 78 -198 168.5t-60 202.5q0 159 117.5 253.5t314.5 94.5q200 0 317 -93t117 -257q0 -108 -67 -197t-214 -162q178 -85 253 -178.5t75 -216.5q0 -182 -127 -290.5t-348 -108.5q-234 0 -360 102.5t-126 290.5zM268 369q0 -120 83.5 -187 t234.5 -67q149 0 232 70t83 192q0 97 -78 172.5t-272 146.5q-149 -64 -216 -141.5t-67 -185.5zM315 1128q0 -92 59 -158t218 -132q143 60 202.5 129t59.5 161q0 101 -72.5 160.5t-199.5 59.5q-125 0 -196 -60t-71 -160z" />
|
||||
<glyph unicode="9" d="M106 991q0 228 127.5 360t335.5 132q149 0 260.5 -76.5t171.5 -223t60 -345.5q0 -858 -664 -858q-116 0 -184 20v143q80 -26 182 -26q240 0 362.5 148.5t133.5 455.5h-12q-55 -83 -146 -126.5t-205 -43.5q-194 0 -308 116t-114 324zM270 993q0 -144 72 -226.5t219 -82.5 q91 0 167.5 37t120.5 101t44 134q0 105 -41 194t-114.5 140t-168.5 51q-143 0 -221 -92t-78 -256z" />
|
||||
<glyph unicode=":" horiz-adv-x="545" d="M152 106q0 67 30.5 101.5t87.5 34.5q58 0 90.5 -34.5t32.5 -101.5q0 -65 -33 -100t-90 -35q-51 0 -84.5 31.5t-33.5 103.5zM152 989q0 135 118 135q123 0 123 -135q0 -65 -33 -100t-90 -35q-51 0 -84.5 31.5t-33.5 103.5z" />
|
||||
<glyph unicode=";" horiz-adv-x="545" d="M63 -264q27 104 59.5 257t45.5 245h182l15 -23q-26 -100 -75 -232.5t-102 -246.5h-125zM147 989q0 135 119 135q123 0 123 -135q0 -65 -33 -100t-90 -35q-58 0 -88.5 35t-30.5 100z" />
|
||||
<glyph unicode="<" d="M104 664v98l961 479v-149l-782 -371l782 -328v-151z" />
|
||||
<glyph unicode="=" d="M119 449v137h930v-137h-930zM119 858v137h930v-137h-930z" />
|
||||
<glyph unicode=">" d="M104 242v151l783 326l-783 373v149l961 -479v-98z" />
|
||||
<glyph unicode="?" horiz-adv-x="879" d="M27 1384q189 99 395 99q191 0 297 -94t106 -265q0 -73 -19.5 -128.5t-57.5 -105t-164 -159.5q-101 -86 -133.5 -143t-32.5 -152v-33h-129v54q0 117 36 192.5t134 159.5q136 115 171.5 173t35.5 140q0 102 -65.5 157.5t-188.5 55.5q-79 0 -154 -18.5t-172 -67.5zM240 106 q0 136 120 136q58 0 89.5 -35t31.5 -101q0 -64 -32 -99.5t-89 -35.5q-52 0 -86 31.5t-34 103.5z" />
|
||||
<glyph unicode="@" horiz-adv-x="1841" d="M121 571q0 260 107 463t305 314.5t454 111.5q215 0 382.5 -90.5t259 -257t91.5 -383.5q0 -142 -44 -260t-124 -183t-184 -65q-86 0 -145 52t-70 133h-8q-40 -87 -114.5 -136t-176.5 -49q-150 0 -234.5 102.5t-84.5 278.5q0 204 118 331.5t310 127.5q68 0 154 -12.5 t155 -34.5l-25 -470v-22q0 -178 133 -178q91 0 148 107.5t57 279.5q0 181 -74 317t-210.5 209.5t-313.5 73.5q-223 0 -388 -92.5t-252 -264t-87 -396.5q0 -305 161 -469t464 -164q210 0 436 86v-133q-192 -84 -436 -84q-363 0 -563.5 199.5t-200.5 557.5zM686 598 q0 -254 195 -254q207 0 225 313l14 261q-72 20 -157 20q-130 0 -203.5 -90t-73.5 -250z" />
|
||||
<glyph unicode="A" horiz-adv-x="1296" d="M0 0l578 1468h143l575 -1468h-176l-182 465h-586l-180 -465h-172zM412 618h473l-170 453q-33 86 -68 211q-22 -96 -63 -211z" />
|
||||
<glyph unicode="B" horiz-adv-x="1327" d="M201 0v1462h413q291 0 421 -87t130 -275q0 -130 -72.5 -214.5t-211.5 -109.5v-10q333 -57 333 -350q0 -196 -132.5 -306t-370.5 -110h-510zM371 145h305q177 0 266.5 68.5t89.5 214.5q0 136 -91.5 200t-278.5 64h-291v-547zM371 836h280q180 0 259 56.5t79 190.5 q0 123 -88 177.5t-280 54.5h-250v-479z" />
|
||||
<glyph unicode="C" horiz-adv-x="1292" d="M125 733q0 226 84.5 396t244 262t375.5 92q230 0 402 -84l-72 -146q-166 78 -332 78q-241 0 -380.5 -160.5t-139.5 -439.5q0 -287 134.5 -443.5t383.5 -156.5q153 0 349 55v-149q-152 -57 -375 -57q-323 0 -498.5 196t-175.5 557z" />
|
||||
<glyph unicode="D" horiz-adv-x="1493" d="M201 0v1462h448q341 0 530 -189t189 -528q0 -362 -196.5 -553.5t-565.5 -191.5h-405zM371 147h207q304 0 457 149.5t153 442.5q0 286 -143.5 431t-426.5 145h-247v-1168z" />
|
||||
<glyph unicode="E" horiz-adv-x="1139" d="M201 0v1462h815v-151h-645v-471h606v-150h-606v-538h645v-152h-815z" />
|
||||
<glyph unicode="F" horiz-adv-x="1057" d="M201 0v1462h815v-151h-645v-535h606v-151h-606v-625h-170z" />
|
||||
<glyph unicode="G" horiz-adv-x="1491" d="M125 731q0 228 91.5 399.5t263.5 262t403 90.5q234 0 436 -86l-66 -150q-198 84 -381 84q-267 0 -417 -159t-150 -441q0 -296 144.5 -449t424.5 -153q152 0 297 35v450h-327v152h497v-711q-116 -37 -236 -56t-278 -19q-332 0 -517 197.5t-185 553.5z" />
|
||||
<glyph unicode="H" horiz-adv-x="1511" d="M201 0v1462h170v-622h770v622h170v-1462h-170v688h-770v-688h-170z" />
|
||||
<glyph unicode="I" horiz-adv-x="571" d="M201 0v1462h170v-1462h-170z" />
|
||||
<glyph unicode="J" horiz-adv-x="547" d="M-160 -213q71 -20 148 -20q99 0 150.5 60t51.5 173v1462h170v-1448q0 -190 -96 -294.5t-276 -104.5q-94 0 -148 27v145z" />
|
||||
<glyph unicode="K" horiz-adv-x="1257" d="M201 0v1462h170v-725l663 725h201l-588 -635l610 -827h-200l-533 709l-153 -136v-573h-170z" />
|
||||
<glyph unicode="L" horiz-adv-x="1063" d="M201 0v1462h170v-1308h645v-154h-815z" />
|
||||
<glyph unicode="M" horiz-adv-x="1849" d="M201 0v1462h256l463 -1206h8l467 1206h254v-1462h-170v942q0 162 14 352h-8l-500 -1294h-137l-496 1296h-8q14 -154 14 -366v-930h-157z" />
|
||||
<glyph unicode="N" horiz-adv-x="1544" d="M201 0v1462h192l797 -1222h8q-2 28 -9 174q-5 114 -5 177v32v839h159v-1462h-194l-799 1227h-8q16 -216 16 -396v-831h-157z" />
|
||||
<glyph unicode="O" horiz-adv-x="1595" d="M125 735q0 357 176 553.5t500 196.5q315 0 492 -200t177 -552q0 -351 -177.5 -552t-493.5 -201q-323 0 -498.5 197.5t-175.5 557.5zM305 733q0 -297 126.5 -450.5t367.5 -153.5q243 0 367 153t124 451q0 295 -123.5 447.5t-365.5 152.5q-243 0 -369.5 -153.5 t-126.5 -446.5z" />
|
||||
<glyph unicode="P" horiz-adv-x="1233" d="M201 0v1462h379q548 0 548 -426q0 -222 -151.5 -341.5t-433.5 -119.5h-172v-575h-170zM371 721h153q226 0 327 73t101 234q0 145 -95 216t-296 71h-190v-594z" />
|
||||
<glyph unicode="Q" horiz-adv-x="1595" d="M125 735q0 357 176 553.5t500 196.5q315 0 492 -200t177 -552q0 -281 -113 -467t-319 -252l348 -362h-247l-285 330l-55 -2q-323 0 -498.5 197.5t-175.5 557.5zM305 733q0 -297 126.5 -450.5t367.5 -153.5q243 0 367 153t124 451q0 295 -123.5 447.5t-365.5 152.5 q-243 0 -369.5 -153.5t-126.5 -446.5z" />
|
||||
<glyph unicode="R" horiz-adv-x="1266" d="M201 0v1462h401q269 0 397.5 -103t128.5 -310q0 -290 -294 -392l397 -657h-201l-354 608h-305v-608h-170zM371 754h233q180 0 264 71.5t84 214.5q0 145 -85.5 209t-274.5 64h-221v-559z" />
|
||||
<glyph unicode="S" horiz-adv-x="1124" d="M106 47v164q90 -38 196 -60t210 -22q170 0 256 64.5t86 179.5q0 76 -30.5 124.5t-102 89.5t-217.5 93q-204 73 -291.5 173t-87.5 261q0 169 127 269t336 100q218 0 401 -80l-53 -148q-181 76 -352 76q-135 0 -211 -58t-76 -161q0 -76 28 -124.5t94.5 -89t203.5 -89.5 q230 -82 316.5 -176t86.5 -244q0 -193 -140 -301t-380 -108q-260 0 -400 67z" />
|
||||
<glyph unicode="T" horiz-adv-x="1133" d="M18 1311v151h1096v-151h-463v-1311h-170v1311h-463z" />
|
||||
<glyph unicode="U" horiz-adv-x="1491" d="M186 520v942h170v-954q0 -183 100 -281t294 -98q185 0 285 98.5t100 282.5v952h170v-946q0 -250 -151 -393t-415 -143t-408.5 144t-144.5 396z" />
|
||||
<glyph unicode="V" horiz-adv-x="1219" d="M0 1462h180l336 -946q58 -163 92 -317q36 162 94 323l334 940h183l-527 -1462h-168z" />
|
||||
<glyph unicode="W" horiz-adv-x="1896" d="M27 1462h180l231 -903q48 -190 70 -344q27 183 80 358l262 889h180l275 -897q48 -155 81 -350q19 142 72 346l230 901h180l-391 -1462h-168l-295 979q-21 65 -47 164t-27 119q-22 -132 -70 -289l-286 -973h-168z" />
|
||||
<glyph unicode="X" horiz-adv-x="1182" d="M8 0l486 764l-453 698h188l363 -579l366 579h181l-453 -692l488 -770h-193l-393 643l-400 -643h-180z" />
|
||||
<glyph unicode="Y" horiz-adv-x="1147" d="M0 1462h186l387 -731l390 731h184l-488 -895v-567h-172v559z" />
|
||||
<glyph unicode="Z" horiz-adv-x="1169" d="M82 0v133l776 1176h-752v153h959v-133l-776 -1175h798v-154h-1005z" />
|
||||
<glyph unicode="[" horiz-adv-x="674" d="M166 -324v1786h457v-141h-289v-1503h289v-142h-457z" />
|
||||
<glyph unicode="\" horiz-adv-x="752" d="M23 1462h163l547 -1462h-166z" />
|
||||
<glyph unicode="]" horiz-adv-x="674" d="M51 -182h289v1503h-289v141h457v-1786h-457v142z" />
|
||||
<glyph unicode="^" horiz-adv-x="1110" d="M49 551l434 922h99l477 -922h-152l-372 745l-334 -745h-152z" />
|
||||
<glyph unicode="_" horiz-adv-x="918" d="M-4 -184h926v-131h-926v131z" />
|
||||
<glyph unicode="`" horiz-adv-x="1182" d="M393 1548v21h203q32 -69 89 -159.5t101 -143.5v-25h-110q-65 52 -154 148t-129 159z" />
|
||||
<glyph unicode="a" horiz-adv-x="1139" d="M94 303q0 332 531 348l186 6v68q0 129 -55.5 190.5t-177.5 61.5q-137 0 -310 -84l-51 127q81 44 177.5 69t193.5 25q196 0 290.5 -87t94.5 -279v-748h-123l-33 156h-8q-82 -103 -163.5 -139.5t-203.5 -36.5q-163 0 -255.5 84t-92.5 239zM268 301q0 -90 54.5 -137 t152.5 -47q155 0 243.5 85t88.5 238v99l-166 -7q-198 -7 -285.5 -61.5t-87.5 -169.5z" />
|
||||
<glyph unicode="b" horiz-adv-x="1255" d="M176 0v1556h166v-378q0 -127 -8 -228h8q116 164 344 164q216 0 335.5 -147.5t119.5 -417.5t-120.5 -419.5t-334.5 -149.5q-107 0 -195.5 39.5t-148.5 121.5h-12l-35 -141h-119zM342 549q0 -231 77 -330.5t247 -99.5q153 0 228 111.5t75 320.5q0 214 -75 319t-232 105 q-170 0 -245 -97.5t-75 -328.5z" />
|
||||
<glyph unicode="c" horiz-adv-x="975" d="M115 541q0 275 132.5 425t377.5 150q79 0 158 -17t124 -40l-51 -141q-55 22 -120 36.5t-115 14.5q-334 0 -334 -426q0 -202 81.5 -310t241.5 -108q137 0 281 59v-147q-110 -57 -277 -57q-238 0 -368.5 146.5t-130.5 414.5z" />
|
||||
<glyph unicode="d" horiz-adv-x="1255" d="M115 545q0 271 120 421t334 150q223 0 342 -162h13l-7 79l-4 77v446h166v-1556h-135l-22 147h-9q-115 -167 -344 -167q-215 0 -334.5 147t-119.5 418zM287 543q0 -210 77 -317t226 -107q170 0 246.5 92.5t76.5 298.5v35q0 233 -77.5 332.5t-247.5 99.5 q-146 0 -223.5 -113.5t-77.5 -320.5z" />
|
||||
<glyph unicode="e" horiz-adv-x="1149" d="M115 539q0 265 130.5 421t350.5 156q206 0 326 -135.5t120 -357.5v-105h-755q5 -193 97.5 -293t260.5 -100q177 0 350 74v-148q-88 -38 -166.5 -54.5t-189.5 -16.5q-243 0 -383.5 148t-140.5 411zM291 653h573q0 157 -70 240.5t-200 83.5q-132 0 -210.5 -86t-92.5 -238z " />
|
||||
<glyph unicode="f" horiz-adv-x="694" d="M29 967v75l196 60v61q0 404 353 404q87 0 204 -35l-43 -133q-96 31 -164 31q-94 0 -139 -62.5t-45 -200.5v-71h279v-129h-279v-967h-166v967h-196z" />
|
||||
<glyph unicode="g" horiz-adv-x="1122" d="M39 -186q0 100 64 173t180 99q-42 19 -70.5 59t-28.5 93q0 60 32 105t101 87q-85 35 -138.5 119t-53.5 192q0 180 108 277.5t306 97.5q86 0 155 -20h379v-105l-203 -24q28 -35 50 -91.5t22 -127.5q0 -161 -110 -257t-302 -96q-49 0 -92 8q-106 -56 -106 -141 q0 -45 37 -66.5t127 -21.5h194q178 0 273.5 -75t95.5 -218q0 -182 -146 -277.5t-426 -95.5q-215 0 -331.5 80t-116.5 226zM199 -184q0 -89 75 -135t215 -46q209 0 309.5 62.5t100.5 169.5q0 89 -55 123.5t-207 34.5h-199q-113 0 -176 -54t-63 -155zM289 745q0 -115 65 -174 t181 -59q243 0 243 236q0 247 -246 247q-117 0 -180 -63t-63 -187z" />
|
||||
<glyph unicode="h" horiz-adv-x="1257" d="M176 0v1556h166v-471q0 -85 -8 -141h10q49 79 139.5 124.5t206.5 45.5q201 0 301.5 -95.5t100.5 -303.5v-715h-166v709q0 134 -61 200t-191 66q-173 0 -252.5 -94t-79.5 -308v-573h-166z" />
|
||||
<glyph unicode="i" horiz-adv-x="518" d="M162 1393q0 57 28 83.5t70 26.5q40 0 69 -27t29 -83t-29 -83.5t-69 -27.5q-42 0 -70 27.5t-28 83.5zM176 0v1096h166v-1096h-166z" />
|
||||
<glyph unicode="j" horiz-adv-x="518" d="M-111 -332q69 -20 136 -20q78 0 114.5 42.5t36.5 129.5v1276h166v-1264q0 -324 -299 -324q-95 0 -154 25v135zM162 1393q0 57 28 83.5t70 26.5q40 0 69 -27t29 -83t-29 -83.5t-69 -27.5q-42 0 -70 27.5t-28 83.5z" />
|
||||
<glyph unicode="k" horiz-adv-x="1075" d="M176 0v1556h164v-825q0 -55 -8 -170h8q43 61 131 160l354 375h197l-444 -467l475 -629h-201l-387 518l-125 -108v-410h-164z" />
|
||||
<glyph unicode="l" horiz-adv-x="518" d="M176 0v1556h166v-1556h-166z" />
|
||||
<glyph unicode="m" horiz-adv-x="1905" d="M176 0v1096h135l27 -150h8q47 80 132.5 125t191.5 45q257 0 336 -186h8q49 86 142 136t212 50q186 0 278.5 -95.5t92.5 -305.5v-715h-166v713q0 131 -56 196.5t-174 65.5q-155 0 -229 -89t-74 -274v-612h-166v713q0 131 -56 196.5t-175 65.5q-156 0 -228.5 -93.5 t-72.5 -306.5v-575h-166z" />
|
||||
<glyph unicode="n" horiz-adv-x="1257" d="M176 0v1096h135l27 -150h8q51 81 143 125.5t205 44.5q198 0 298 -95.5t100 -305.5v-715h-166v709q0 134 -61 200t-191 66q-172 0 -252 -93t-80 -307v-575h-166z" />
|
||||
<glyph unicode="o" horiz-adv-x="1237" d="M115 549q0 268 134 417.5t372 149.5q230 0 365.5 -153t135.5 -414q0 -268 -135 -418.5t-373 -150.5q-147 0 -261 69t-176 198t-62 302zM287 549q0 -210 84 -320t247 -110t247.5 109.5t84.5 320.5q0 209 -84.5 317.5t-249.5 108.5q-163 0 -246 -107t-83 -319z" />
|
||||
<glyph unicode="p" horiz-adv-x="1255" d="M176 -492v1588h135l23 -150h8q64 90 149 130t195 40q218 0 336.5 -149t118.5 -418q0 -270 -120.5 -419.5t-334.5 -149.5q-107 0 -195.5 39.5t-148.5 121.5h-12q12 -96 12 -182v-451h-166zM342 549q0 -231 77 -330.5t247 -99.5q142 0 222.5 115t80.5 317 q0 205 -80.5 314.5t-226.5 109.5q-168 0 -243 -93t-77 -296v-37z" />
|
||||
<glyph unicode="q" horiz-adv-x="1255" d="M115 545q0 269 120 420t334 151q225 0 346 -170h9l24 150h131v-1588h-166v469q0 100 11 170h-13q-115 -167 -346 -167q-212 0 -331 149t-119 416zM287 543q0 -207 76.5 -315.5t226.5 -108.5q166 0 242 89t81 300v37q0 230 -78 331t-247 101q-146 0 -223.5 -113.5 t-77.5 -320.5z" />
|
||||
<glyph unicode="r" horiz-adv-x="836" d="M176 0v1096h137l19 -203h8q61 107 147 165t189 58q73 0 131 -12l-23 -154q-68 15 -120 15q-133 0 -227.5 -108t-94.5 -269v-588h-166z" />
|
||||
<glyph unicode="s" horiz-adv-x="977" d="M106 827q0 134 109 211.5t299 77.5q177 0 346 -72l-59 -135q-165 68 -299 68q-118 0 -178 -37t-60 -102q0 -44 22.5 -75t72.5 -59t192 -81q195 -71 263.5 -143t68.5 -181q0 -153 -114 -236t-320 -83q-218 0 -340 69v154q79 -40 169.5 -63t174.5 -23q130 0 200 41.5 t70 126.5q0 64 -55.5 109.5t-216.5 107.5q-153 57 -217.5 99.5t-96 96.5t-31.5 129z" />
|
||||
<glyph unicode="t" horiz-adv-x="723" d="M31 967v80l157 69l70 234h96v-254h318v-129h-318v-645q0 -99 47 -152t129 -53q44 0 85 6.5t65 13.5v-127q-27 -13 -79.5 -21.5t-94.5 -8.5q-318 0 -318 335v652h-157z" />
|
||||
<glyph unicode="u" horiz-adv-x="1257" d="M164 379v717h168v-711q0 -134 61 -200t191 -66q172 0 251.5 94t79.5 307v576h166v-1096h-137l-24 147h-9q-51 -81 -141.5 -124t-206.5 -43q-200 0 -299.5 95t-99.5 304z" />
|
||||
<glyph unicode="v" horiz-adv-x="1026" d="M0 1096h178l236 -650q80 -228 94 -296h8q11 53 69.5 219.5t262.5 726.5h178l-416 -1096h-194z" />
|
||||
<glyph unicode="w" horiz-adv-x="1593" d="M23 1096h174q106 -413 161.5 -629t63.5 -291h8q11 57 35.5 147.5t42.5 143.5l201 629h180l196 -629q56 -172 76 -289h8q4 36 21.5 111t208.5 807h172l-303 -1096h-197l-201 643q-19 59 -71 268h-8q-40 -175 -70 -270l-207 -641h-192z" />
|
||||
<glyph unicode="x" horiz-adv-x="1073" d="M39 0l401 561l-381 535h189l289 -420l288 420h187l-381 -535l401 -561h-188l-307 444l-310 -444h-188z" />
|
||||
<glyph unicode="y" horiz-adv-x="1032" d="M2 1096h178l240 -625q79 -214 98 -309h8q13 51 54.5 174.5t271.5 759.5h178l-471 -1248q-70 -185 -163.5 -262.5t-229.5 -77.5q-76 0 -150 17v133q55 -12 123 -12q171 0 244 192l61 156z" />
|
||||
<glyph unicode="z" horiz-adv-x="958" d="M82 0v113l598 854h-561v129h743v-129l-590 -838h605v-129h-795z" />
|
||||
<glyph unicode="{" horiz-adv-x="776" d="M61 498v141q130 2 188 48t58 142v306q0 155 108 241t290 86v-139q-230 -6 -230 -199v-295q0 -215 -223 -254v-12q223 -39 223 -254v-297q0 -102 58.5 -148t171.5 -48v-140q-190 2 -294 87t-104 239v303q0 104 -63 148.5t-183 44.5z" />
|
||||
<glyph unicode="|" horiz-adv-x="1128" d="M494 -496v2052h141v-2052h-141z" />
|
||||
<glyph unicode="}" horiz-adv-x="776" d="M72 -184q111 2 169 48t58 148v297q0 114 55 174t168 80v12q-223 39 -223 254v295q0 193 -227 199v139q184 0 289.5 -87t105.5 -240v-306q0 -97 59 -142.5t189 -47.5v-141q-122 0 -185 -44.5t-63 -148.5v-303q0 -153 -102.5 -238.5t-292.5 -87.5v140z" />
|
||||
<glyph unicode="~" d="M104 592v151q100 109 244 109q68 0 124.5 -14t145.5 -52q66 -28 115 -41.5t96 -13.5q54 0 118 32t118 89v-150q-102 -110 -244 -110q-72 0 -135 16.5t-135 48.5q-75 32 -120 44t-93 12q-53 0 -116.5 -33.5t-117.5 -87.5z" />
|
||||
<glyph unicode="¢" d="M190 741q0 508 396 570v172h135v-164q75 -3 146 -19.5t120 -39.5l-49 -140q-133 51 -242 51q-172 0 -253 -105.5t-81 -322.5q0 -212 79.5 -313.5t246.5 -101.5q141 0 283 59v-147q-105 -54 -252 -60v-200h-133v206q-203 32 -299.5 168.5t-96.5 386.5z" />
|
||||
<glyph unicode="£" d="M63 0v141q205 47 205 291v223h-198v127h198v316q0 178 112 280.5t302 102.5t360 -84l-61 -133q-154 77 -297 77q-123 0 -185.5 -62t-62.5 -202v-295h422v-127h-422v-221q0 -100 -32.5 -168t-106.5 -112h795v-154h-1029z" />
|
||||
<glyph unicode="¥" d="M31 1462h178l375 -727l379 727h174l-416 -770h262v-127h-317v-170h317v-127h-317v-268h-164v268h-316v127h316v170h-316v127h256z" />
|
||||
<glyph unicode="©" horiz-adv-x="1704" d="M100 731q0 200 100 375t275 276t377 101q200 0 375 -100t276 -275t101 -377q0 -197 -97 -370t-272 -277t-383 -104q-207 0 -382 103.5t-272.5 276.5t-97.5 371zM205 731q0 -173 87 -323.5t237.5 -237t322.5 -86.5q174 0 323 87t236.5 235.5t87.5 324.5q0 174 -87 323 t-235.5 236.5t-324.5 87.5q-174 0 -323 -87t-236.5 -235.5t-87.5 -324.5zM481 731q0 209 110.5 332t301.5 123q128 0 246 -60l-58 -118q-108 51 -188 51q-125 0 -192.5 -87t-67.5 -241q0 -168 63.5 -249t194.5 -81q86 0 211 45v-124q-48 -20 -98.5 -34t-120.5 -14 q-194 0 -298 120.5t-104 336.5z" />
|
||||
<glyph unicode="­" horiz-adv-x="659" d="M84 473v152h491v-152h-491z" />
|
||||
<glyph unicode="®" horiz-adv-x="1704" d="M100 731q0 200 100 375t275 276t377 101q200 0 375 -100t276 -275t101 -377q0 -197 -97 -370t-272 -277t-383 -104q-207 0 -382 103.5t-272.5 276.5t-97.5 371zM205 731q0 -173 87 -323.5t237.5 -237t322.5 -86.5q174 0 323 87t236.5 235.5t87.5 324.5q0 174 -87 323 t-235.5 236.5t-324.5 87.5q-174 0 -323 -87t-236.5 -235.5t-87.5 -324.5zM575 285v891h261q166 0 243.5 -65t77.5 -198q0 -80 -42.5 -141.5t-119.5 -91.5l238 -395h-168l-207 354h-135v-354h-148zM723 762h108q80 0 128.5 41.5t48.5 105.5q0 75 -43 107.5t-136 32.5h-106 v-287z" />
|
||||
<glyph unicode="´" horiz-adv-x="1182" d="M393 1241v25q48 62 103.5 150t87.5 153h202v-21q-44 -65 -131 -160t-151 -147h-111z" />
|
||||
<glyph unicode=" " horiz-adv-x="784" />
|
||||
<glyph unicode=" " horiz-adv-x="1569" />
|
||||
<glyph unicode=" " horiz-adv-x="784" />
|
||||
<glyph unicode=" " horiz-adv-x="1569" />
|
||||
<glyph unicode=" " horiz-adv-x="523" />
|
||||
<glyph unicode=" " horiz-adv-x="392" />
|
||||
<glyph unicode=" " horiz-adv-x="261" />
|
||||
<glyph unicode=" " horiz-adv-x="261" />
|
||||
<glyph unicode=" " horiz-adv-x="196" />
|
||||
<glyph unicode=" " horiz-adv-x="313" />
|
||||
<glyph unicode=" " horiz-adv-x="87" />
|
||||
<glyph unicode="‐" horiz-adv-x="659" d="M84 473v152h491v-152h-491z" />
|
||||
<glyph unicode="‑" horiz-adv-x="659" d="M84 473v152h491v-152h-491z" />
|
||||
<glyph unicode="‒" horiz-adv-x="659" d="M84 473v152h491v-152h-491z" />
|
||||
<glyph unicode="–" horiz-adv-x="1024" d="M82 473v152h860v-152h-860z" />
|
||||
<glyph unicode="—" horiz-adv-x="2048" d="M82 473v152h1884v-152h-1884z" />
|
||||
<glyph unicode="‘" horiz-adv-x="348" d="M25 983q22 90 71 224t105 255h123q-66 -254 -103 -501h-184z" />
|
||||
<glyph unicode="’" horiz-adv-x="348" d="M25 961q70 285 102 501h182l15 -22q-26 -100 -75 -232.5t-102 -246.5h-122z" />
|
||||
<glyph unicode="“" horiz-adv-x="717" d="M25 983q22 90 71 224t105 255h123q-66 -254 -103 -501h-184zM391 983q56 215 178 479h123q-30 -115 -59.5 -259.5t-42.5 -241.5h-184z" />
|
||||
<glyph unicode="”" horiz-adv-x="717" d="M25 961q70 285 102 501h182l15 -22q-26 -100 -75 -232.5t-102 -246.5h-122zM391 961q26 100 59 254t46 247h182l14 -22q-24 -91 -72 -224t-104 -255h-125z" />
|
||||
<glyph unicode="•" horiz-adv-x="770" d="M164 748q0 121 56.5 184t164.5 63q105 0 163 -62t58 -185q0 -119 -57.5 -183.5t-163.5 -64.5q-107 0 -164 65.5t-57 182.5z" />
|
||||
<glyph unicode="…" horiz-adv-x="1606" d="M152 106q0 67 30.5 101.5t87.5 34.5q58 0 90.5 -34.5t32.5 -101.5q0 -65 -33 -100t-90 -35q-51 0 -84.5 31.5t-33.5 103.5zM682 106q0 67 30.5 101.5t87.5 34.5q58 0 90.5 -34.5t32.5 -101.5q0 -65 -33 -100t-90 -35q-51 0 -84.5 31.5t-33.5 103.5zM1213 106 q0 67 30.5 101.5t87.5 34.5q58 0 90.5 -34.5t32.5 -101.5q0 -65 -33 -100t-90 -35q-51 0 -84.5 31.5t-33.5 103.5z" />
|
||||
<glyph unicode=" " horiz-adv-x="313" />
|
||||
<glyph unicode=" " horiz-adv-x="392" />
|
||||
<glyph unicode="€" horiz-adv-x="1208" d="M63 506v129h152l-2 42v44l2 80h-152v129h164q39 261 185 407t383 146q201 0 366 -97l-71 -139q-166 86 -295 86q-319 0 -398 -403h510v-129h-524l-2 -57v-64l2 -45h463v-129h-447q37 -180 138.5 -278.5t271.5 -98.5q156 0 309 66v-150q-146 -65 -317 -65 q-237 0 -381.5 134.5t-190.5 391.5h-166z" />
|
||||
<glyph unicode="™" horiz-adv-x="1589" d="M37 1356v106h543v-106h-211v-615h-123v615h-209zM647 741v721h187l196 -559l203 559h180v-721h-127v420l6 137h-8l-211 -557h-104l-201 559h-8l6 -129v-430h-119z" />
|
||||
<glyph unicode="" horiz-adv-x="1095" d="M0 1095h1095v-1095h-1095v1095z" />
|
||||
</font>
|
||||
</defs></svg>
|
||||
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,146 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>
|
||||
This is a custom SVG webfont generated by Font Squirrel.
|
||||
Copyright : Digitized data copyright 2011 Google Corporation
|
||||
Foundry : Ascender Corporation
|
||||
Foundry URL : httpwwwascendercorpcom
|
||||
</metadata>
|
||||
<defs>
|
||||
<font id="OpenSansSemibold" horiz-adv-x="1169" >
|
||||
<font-face units-per-em="2048" ascent="1638" descent="-410" />
|
||||
<missing-glyph horiz-adv-x="532" />
|
||||
<glyph unicode=" " horiz-adv-x="532" />
|
||||
<glyph unicode="	" horiz-adv-x="532" />
|
||||
<glyph unicode=" " horiz-adv-x="532" />
|
||||
<glyph unicode="!" horiz-adv-x="565" d="M133 125q0 74 39 112.5t111 38.5q71 0 109 -40t38 -111t-38.5 -112.5t-108.5 -41.5q-71 0 -110.5 40t-39.5 114zM145 1462h277l-51 -1018h-174z" />
|
||||
<glyph unicode=""" horiz-adv-x="893" d="M133 1462h232l-41 -528h-150zM528 1462h232l-41 -528h-150z" />
|
||||
<glyph unicode="#" horiz-adv-x="1323" d="M47 418v168h283l57 284h-264v168h293l80 422h180l-80 -422h252l80 422h174l-80 -422h252v-168h-285l-55 -284h270v-168h-303l-80 -418h-178l80 418h-248l-80 -418h-174l76 418h-250zM506 586h250l57 284h-250z" />
|
||||
<glyph unicode="$" d="M111 168v211q86 -42 201 -70.5t206 -29.5v374l-84 31q-164 63 -239.5 150.5t-75.5 216.5q0 138 107.5 227t291.5 108v168h133v-165q203 -7 385 -82l-73 -183q-157 62 -312 74v-364l76 -29q190 -73 263 -154t73 -198q0 -145 -106 -239t-306 -116v-217h-133v211 q-248 4 -407 76zM354 1053q0 -57 35.5 -95t128.5 -75v311q-80 -12 -122 -49t-42 -92zM651 287q176 27 176 151q0 58 -40.5 95.5t-135.5 72.5v-319z" />
|
||||
<glyph unicode="%" horiz-adv-x="1765" d="M84 1026q0 457 319 457q157 0 241.5 -118.5t84.5 -338.5q0 -230 -82.5 -345.5t-243.5 -115.5q-152 0 -235.5 119.5t-83.5 341.5zM279 1024q0 -149 29 -222t95 -73q132 0 132 295t-132 295q-66 0 -95 -73t-29 -222zM379 0l811 1462h194l-811 -1462h-194zM1036 440 q0 457 320 457q154 0 239.5 -118t85.5 -339q0 -230 -83 -345t-242 -115q-152 0 -236 118.5t-84 341.5zM1231 440q0 -149 29.5 -223t95.5 -74q131 0 131 297q0 293 -131 293q-66 0 -95.5 -72t-29.5 -221z" />
|
||||
<glyph unicode="&" horiz-adv-x="1516" d="M96 387q0 131 64 228.5t231 193.5q-95 111 -129.5 187.5t-34.5 158.5q0 152 108.5 240t291.5 88q177 0 278 -85.5t101 -230.5q0 -114 -67.5 -207t-225.5 -186l346 -334q81 107 135 314h242q-70 -284 -224 -463l301 -291h-303l-149 145q-102 -82 -217.5 -123.5 t-255.5 -41.5q-230 0 -361 109t-131 298zM344 403q0 -98 69.5 -159.5t186.5 -61.5q183 0 313 107l-383 377q-106 -68 -146 -127.5t-40 -135.5zM451 1147q0 -63 33.5 -119t93.5 -119q113 64 158.5 119.5t45.5 124.5q0 65 -43.5 104t-115.5 39q-79 0 -125.5 -40.5 t-46.5 -108.5z" />
|
||||
<glyph unicode="'" horiz-adv-x="498" d="M133 1462h232l-41 -528h-150z" />
|
||||
<glyph unicode="(" horiz-adv-x="649" d="M82 561q0 265 77.5 496t223.5 405h205q-139 -188 -213 -421.5t-74 -477.5t74 -473t211 -414h-203q-147 170 -224 397t-77 488z" />
|
||||
<glyph unicode=")" horiz-adv-x="649" d="M61 1462h205q147 -175 224 -406.5t77 -494.5t-77.5 -490t-223.5 -395h-203q138 187 211.5 415t73.5 472q0 245 -74 477.5t-213 421.5z" />
|
||||
<glyph unicode="*" horiz-adv-x="1122" d="M74 1065l35 217l376 -108l-41 382h228l-41 -382l385 108l28 -217l-360 -29l236 -311l-199 -107l-166 338l-149 -338l-205 107l231 311z" />
|
||||
<glyph unicode="+" d="M96 633v178h398v408h180v-408h399v-178h-399v-406h-180v406h-398z" />
|
||||
<glyph unicode="," horiz-adv-x="547" d="M63 -264q69 270 103 502h231l15 -23q-48 -186 -176 -479h-173z" />
|
||||
<glyph unicode="-" horiz-adv-x="659" d="M72 449v200h514v-200h-514z" />
|
||||
<glyph unicode="." horiz-adv-x="563" d="M133 125q0 73 38 112t110 39q73 0 111 -40.5t38 -110.5q0 -71 -38.5 -112.5t-110.5 -41.5t-110 41t-38 113z" />
|
||||
<glyph unicode="/" horiz-adv-x="799" d="M16 0l545 1462h221l-544 -1462h-222z" />
|
||||
<glyph unicode="0" d="M88 731q0 387 122.5 570.5t373.5 183.5q245 0 371 -192t126 -562q0 -381 -122.5 -566t-374.5 -185q-244 0 -370 191t-126 560zM326 731q0 -299 61.5 -427t196.5 -128t197.5 130t62.5 425q0 294 -62.5 425.5t-197.5 131.5t-196.5 -129t-61.5 -428z" />
|
||||
<glyph unicode="1" d="M154 1124l430 338h196v-1462h-235v944q0 169 8 268q-23 -24 -56.5 -53t-224.5 -184z" />
|
||||
<glyph unicode="2" d="M90 0v178l377 379q167 171 221.5 242.5t79.5 134.5t25 135q0 99 -59.5 156t-164.5 57q-84 0 -162.5 -31t-181.5 -112l-127 155q122 103 237 146t245 43q204 0 327 -106.5t123 -286.5q0 -99 -35.5 -188t-109 -183.5t-244.5 -255.5l-254 -246v-10h694v-207h-991z" />
|
||||
<glyph unicode="3" d="M86 59v209q93 -46 197 -71t200 -25q170 0 254 63t84 195q0 117 -93 172t-292 55h-127v191h129q350 0 350 242q0 94 -61 145t-180 51q-83 0 -160 -23.5t-182 -91.5l-115 164q201 148 467 148q221 0 345 -95t124 -262q0 -139 -81 -231.5t-228 -124.5v-8q176 -22 264 -109.5 t88 -232.5q0 -211 -149 -325.5t-424 -114.5q-243 0 -410 79z" />
|
||||
<glyph unicode="4" d="M39 319v181l668 966h229v-952h197v-195h-197v-319h-229v319h-668zM258 514h449v367q0 196 10 321h-8q-28 -66 -88 -160z" />
|
||||
<glyph unicode="5" d="M117 59v213q81 -46 186 -71t195 -25q159 0 242 71t83 208q0 262 -334 262q-47 0 -116 -9.5t-121 -21.5l-105 62l56 714h760v-209h-553l-33 -362q35 6 85.5 14t123.5 8q221 0 350 -117t129 -319q0 -234 -146.5 -365.5t-416.5 -131.5q-245 0 -385 79z" />
|
||||
<glyph unicode="6" d="M94 623q0 858 699 858q110 0 186 -17v-196q-76 22 -176 22q-235 0 -353 -126t-128 -404h12q47 81 132 125.5t200 44.5q199 0 310 -122t111 -331q0 -230 -128.5 -363.5t-350.5 -133.5q-157 0 -273 75.5t-178.5 220t-62.5 347.5zM332 508q0 -141 76.5 -237.5t195.5 -96.5 q121 0 186.5 78t65.5 223q0 126 -61.5 198t-184.5 72q-76 0 -140 -32.5t-101 -89t-37 -115.5z" />
|
||||
<glyph unicode="7" d="M74 1253v207h1011v-164l-575 -1296h-254l578 1253h-760z" />
|
||||
<glyph unicode="8" d="M88 371q0 122 68.5 219.5t224.5 173.5q-134 80 -191 169t-57 200q0 159 125 253.5t326 94.5q208 0 329 -95.5t121 -255.5q0 -225 -270 -358q172 -86 244.5 -181t72.5 -212q0 -181 -133 -290t-360 -109q-238 0 -369 102t-131 289zM313 379q0 -104 73 -161.5t198 -57.5 q129 0 200.5 59.5t71.5 161.5q0 81 -66 148t-200 124l-29 13q-132 -58 -190 -127.5t-58 -159.5zM360 1116q0 -52 22 -93t64 -74.5t142 -80.5q120 53 169.5 111.5t49.5 136.5q0 85 -61.5 134.5t-163.5 49.5q-100 0 -161 -49.5t-61 -134.5z" />
|
||||
<glyph unicode="9" d="M86 981q0 229 128.5 364.5t350.5 135.5q156 0 272 -76t179 -220.5t63 -346.5q0 -432 -174 -645t-524 -213q-133 0 -191 16v197q89 -25 179 -25q238 0 355 128t128 402h-12q-59 -90 -142.5 -130t-195.5 -40q-194 0 -305 121t-111 332zM317 983q0 -125 60.5 -196.5 t183.5 -71.5q119 0 200 71t81 166q0 89 -34.5 166.5t-96.5 122.5t-142 45q-122 0 -187 -79.5t-65 -223.5z" />
|
||||
<glyph unicode=":" horiz-adv-x="563" d="M133 125q0 73 38 112t110 39q73 0 111 -40.5t38 -110.5q0 -71 -38.5 -112.5t-110.5 -41.5t-110 41t-38 113zM133 979q0 151 148 151q75 0 112 -40t37 -111t-38.5 -112.5t-110.5 -41.5t-110 41t-38 113z" />
|
||||
<glyph unicode=";" horiz-adv-x="569" d="M63 -264q69 270 103 502h231l15 -23q-48 -186 -176 -479h-173zM131 979q0 151 148 151q75 0 112 -40t37 -111t-38.5 -112.5t-110.5 -41.5t-110 41t-38 113z" />
|
||||
<glyph unicode="<" d="M96 651v121l977 488v-195l-733 -344l733 -303v-197z" />
|
||||
<glyph unicode="=" d="M102 432v178h963v-178h-963zM102 831v179h963v-179h-963z" />
|
||||
<glyph unicode=">" d="M96 221v197l733 303l-733 344v195l977 -488v-121z" />
|
||||
<glyph unicode="?" horiz-adv-x="928" d="M16 1370q203 113 435 113q196 0 311 -96t115 -265q0 -75 -22 -133.5t-66.5 -111.5t-153.5 -138q-93 -73 -124.5 -121t-31.5 -129v-45h-196v64q0 110 40 183t140 151q119 94 153.5 146t34.5 124q0 84 -56 129t-161 45q-95 0 -176 -27t-158 -65zM242 125q0 151 147 151 q72 0 110 -39.5t38 -111.5q0 -71 -38.5 -112.5t-109.5 -41.5t-109 40.5t-38 113.5z" />
|
||||
<glyph unicode="@" horiz-adv-x="1839" d="M111 586q0 261 112 464.5t310.5 311.5t449.5 108q217 0 386.5 -90t263 -256.5t93.5 -384.5q0 -143 -45 -261.5t-126.5 -184.5t-188.5 -66q-79 0 -137 42t-78 114h-12q-49 -78 -121 -117t-162 -39q-163 0 -256.5 105t-93.5 284q0 206 124 334.5t333 128.5 q76 0 168.5 -13.5t164.5 -37.5l-22 -465v-24q0 -160 104 -160q79 0 125.5 102t46.5 260q0 171 -70 300.5t-199 199.5t-296 70q-213 0 -370.5 -88t-240.5 -251.5t-83 -379.5q0 -290 155 -446t445 -156q221 0 461 90v-164q-210 -86 -457 -86q-370 0 -577 199.5t-207 556.5z M698 612q0 -233 183 -233q193 0 211 293l12 239q-63 17 -135 17q-128 0 -199.5 -85t-71.5 -231z" />
|
||||
<glyph unicode="A" horiz-adv-x="1354" d="M0 0l547 1468h260l547 -1468h-254l-146 406h-559l-143 -406h-252zM465 612h426l-137 398q-15 40 -41.5 126t-36.5 126q-27 -123 -79 -269z" />
|
||||
<glyph unicode="B" horiz-adv-x="1352" d="M193 0v1462h434q302 0 436.5 -88t134.5 -278q0 -128 -66 -213t-190 -107v-10q154 -29 226.5 -114.5t72.5 -231.5q0 -197 -137.5 -308.5t-382.5 -111.5h-528zM432 201h254q150 0 226.5 57.5t76.5 181.5q0 114 -78 169t-237 55h-242v-463zM432 858h230q150 0 219 47.5 t69 161.5q0 103 -74.5 149t-236.5 46h-207v-404z" />
|
||||
<glyph unicode="C" horiz-adv-x="1298" d="M121 731q0 228 83.5 399t241.5 262t371 91q224 0 414 -94l-86 -199q-74 35 -156.5 61.5t-173.5 26.5q-206 0 -324 -146t-118 -403q0 -269 113.5 -407t328.5 -138q93 0 180 18.5t181 47.5v-205q-172 -65 -390 -65q-321 0 -493 194.5t-172 556.5z" />
|
||||
<glyph unicode="D" horiz-adv-x="1503" d="M193 0v1462h452q349 0 543 -188t194 -529q0 -362 -201 -553.5t-579 -191.5h-409zM432 201h170q528 0 528 536q0 525 -491 525h-207v-1061z" />
|
||||
<glyph unicode="E" horiz-adv-x="1143" d="M193 0v1462h827v-202h-588v-398h551v-200h-551v-459h588v-203h-827z" />
|
||||
<glyph unicode="F" horiz-adv-x="1090" d="M193 0v1462h825v-202h-588v-457h551v-203h-551v-600h-237z" />
|
||||
<glyph unicode="G" horiz-adv-x="1487" d="M121 731q0 353 203 552.5t559 199.5q229 0 434 -88l-84 -199q-178 82 -356 82q-234 0 -370 -147t-136 -402q0 -268 122.5 -407.5t352.5 -139.5q116 0 248 29v377h-303v205h538v-734q-132 -43 -253.5 -61t-262.5 -18q-332 0 -512 196.5t-180 554.5z" />
|
||||
<glyph unicode="H" horiz-adv-x="1538" d="M193 0v1462h239v-598h674v598h240v-1462h-240v659h-674v-659h-239z" />
|
||||
<glyph unicode="I" horiz-adv-x="625" d="M193 0v1462h239v-1462h-239z" />
|
||||
<glyph unicode="J" horiz-adv-x="612" d="M-156 -182q84 -21 146 -21q196 0 196 248v1417h240v-1409q0 -224 -106.5 -342.5t-311.5 -118.5q-98 0 -164 25v201z" />
|
||||
<glyph unicode="K" horiz-adv-x="1309" d="M193 0v1462h239v-698q98 120 195 231l395 467h272q-383 -450 -549 -641l564 -821h-277l-459 662l-141 -115v-547h-239z" />
|
||||
<glyph unicode="L" horiz-adv-x="1110" d="M193 0v1462h239v-1257h619v-205h-858z" />
|
||||
<glyph unicode="M" horiz-adv-x="1890" d="M193 0v1462h337l406 -1163h6l418 1163h338v-1462h-230v723q0 109 5.5 284t9.5 212h-8l-439 -1219h-211l-424 1221h-8q17 -272 17 -510v-711h-217z" />
|
||||
<glyph unicode="N" horiz-adv-x="1604" d="M193 0v1462h290l717 -1159h6q-2 23 -8 167.5t-6 225.5v766h219v-1462h-293l-719 1165h-8l5 -65q14 -186 14 -340v-760h-217z" />
|
||||
<glyph unicode="O" horiz-adv-x="1612" d="M121 735q0 362 178.5 556t509.5 194q326 0 504 -197t178 -555q0 -357 -178.5 -555t-505.5 -198q-331 0 -508.5 196.5t-177.5 558.5zM375 733q0 -270 109 -409.5t323 -139.5q213 0 321.5 138t108.5 411q0 269 -107.5 408t-320.5 139q-215 0 -324.5 -139t-109.5 -408z" />
|
||||
<glyph unicode="P" horiz-adv-x="1260" d="M193 0v1462h421q274 0 410.5 -112t136.5 -330q0 -229 -150 -351t-427 -122h-152v-547h-239zM432 748h127q184 0 270 64t86 200q0 126 -77 188t-240 62h-166v-514z" />
|
||||
<glyph unicode="Q" horiz-adv-x="1612" d="M121 735q0 362 178.5 556t509.5 194q326 0 504 -197t178 -555q0 -266 -101.5 -448t-295.5 -256l350 -377h-322l-276 328h-39q-331 0 -508.5 196.5t-177.5 558.5zM375 733q0 -270 109 -409.5t323 -139.5q213 0 321.5 138t108.5 411q0 269 -107.5 408t-320.5 139 q-215 0 -324.5 -139t-109.5 -408z" />
|
||||
<glyph unicode="R" horiz-adv-x="1309" d="M193 0v1462h413q283 0 419 -106t136 -320q0 -273 -284 -389l413 -647h-272l-350 584h-236v-584h-239zM432 782h166q167 0 242 62t75 184q0 124 -81 178t-244 54h-158v-478z" />
|
||||
<glyph unicode="S" horiz-adv-x="1126" d="M100 57v226q100 -47 212.5 -74t209.5 -27q142 0 209.5 54t67.5 145q0 82 -62 139t-256 135q-200 81 -282 185t-82 250q0 183 130 288t349 105q210 0 418 -92l-76 -195q-195 82 -348 82q-116 0 -176 -50.5t-60 -133.5q0 -57 24 -97.5t79 -76.5t198 -95q161 -67 236 -125 t110 -131t35 -172q0 -195 -141 -306t-389 -111t-406 77z" />
|
||||
<glyph unicode="T" horiz-adv-x="1159" d="M29 1257v205h1099v-205h-430v-1257h-239v1257h-430z" />
|
||||
<glyph unicode="U" horiz-adv-x="1520" d="M180 520v942h240v-925q0 -181 84 -267t258 -86q338 0 338 355v923h239v-946q0 -162 -69.5 -283.5t-201 -187t-314.5 -65.5q-272 0 -423 144t-151 396z" />
|
||||
<glyph unicode="V" horiz-adv-x="1274" d="M0 1462h246l305 -909q24 -65 51 -167.5t35 -152.5q13 76 40 176t44 148l305 905h248l-512 -1462h-252z" />
|
||||
<glyph unicode="W" horiz-adv-x="1937" d="M12 1462h244l209 -852q49 -205 70 -362q11 85 33 190t40 170l238 854h237l244 -858q35 -119 74 -356q15 143 72 364l208 850h242l-381 -1462h-260l-248 872q-16 57 -40 164.5t-29 149.5q-10 -64 -32.5 -166t-37.5 -152l-242 -868h-260l-189 732z" />
|
||||
<glyph unicode="X" horiz-adv-x="1274" d="M4 0l485 758l-454 704h266l338 -553l338 553h258l-457 -708l492 -754h-275l-366 598l-369 -598h-256z" />
|
||||
<glyph unicode="Y" horiz-adv-x="1212" d="M0 1462h260l346 -667l346 667h260l-487 -895v-567h-240v559z" />
|
||||
<glyph unicode="Z" horiz-adv-x="1178" d="M66 0v166l737 1091h-717v205h1006v-168l-740 -1089h760v-205h-1046z" />
|
||||
<glyph unicode="[" horiz-adv-x="676" d="M154 -324v1786h471v-176h-256v-1433h256v-177h-471z" />
|
||||
<glyph unicode="\" horiz-adv-x="799" d="M16 1462h222l544 -1462h-221z" />
|
||||
<glyph unicode="]" horiz-adv-x="676" d="M51 -147h256v1433h-256v176h469v-1786h-469v177z" />
|
||||
<glyph unicode="^" horiz-adv-x="1100" d="M29 535l436 935h121l485 -935h-194l-349 694l-307 -694h-192z" />
|
||||
<glyph unicode="_" horiz-adv-x="879" d="M-4 -184h887v-135h-887v135z" />
|
||||
<glyph unicode="`" horiz-adv-x="1212" d="M362 1548v21h273q38 -70 103.5 -161t109.5 -142v-25h-158q-69 52 -174.5 150.5t-153.5 156.5z" />
|
||||
<glyph unicode="a" horiz-adv-x="1188" d="M90 317q0 171 127 258t387 95l191 6v59q0 106 -49.5 158.5t-153.5 52.5q-85 0 -163 -25t-150 -59l-76 168q90 47 197 71.5t202 24.5q211 0 318.5 -92t107.5 -289v-745h-168l-47 154h-8q-80 -101 -161 -137.5t-208 -36.5q-163 0 -254.5 88t-91.5 249zM334 315 q0 -74 44 -114.5t132 -40.5q128 0 205.5 71.5t77.5 200.5v96l-142 -6q-166 -6 -241.5 -55.5t-75.5 -151.5z" />
|
||||
<glyph unicode="b" horiz-adv-x="1276" d="M168 0v1556h235v-370q0 -41 -4 -122t-6 -103h10q112 165 330 165q207 0 322.5 -150t115.5 -421q0 -272 -117 -423.5t-325 -151.5q-210 0 -326 151h-16l-43 -131h-176zM403 555q0 -202 64 -292.5t209 -90.5q125 0 189.5 99t64.5 286q0 377 -258 377q-142 0 -204.5 -83.5 t-64.5 -279.5v-16z" />
|
||||
<glyph unicode="c" horiz-adv-x="1014" d="M102 547q0 279 136.5 429t394.5 150q175 0 315 -65l-71 -189q-149 58 -246 58q-287 0 -287 -381q0 -186 71.5 -279.5t209.5 -93.5q157 0 297 78v-205q-63 -37 -134.5 -53t-173.5 -16q-251 0 -381.5 146.5t-130.5 420.5z" />
|
||||
<glyph unicode="d" horiz-adv-x="1276" d="M102 551q0 272 117.5 423.5t325.5 151.5q218 0 332 -161h12q-17 119 -17 188v403h236v-1556h-184l-41 145h-11q-113 -165 -331 -165q-207 0 -323 150t-116 421zM344 547q0 -184 65 -280.5t195 -96.5q145 0 211 81.5t68 264.5v33q0 209 -68 297t-213 88 q-124 0 -191 -100.5t-67 -286.5z" />
|
||||
<glyph unicode="e" horiz-adv-x="1180" d="M102 545q0 271 135 426t371 155q219 0 346 -133t127 -366v-127h-737q5 -161 87 -247.5t231 -86.5q98 0 182.5 18.5t181.5 61.5v-191q-86 -41 -174 -58t-201 -17q-258 0 -403.5 150.5t-145.5 414.5zM348 670h502q-2 137 -66 207.5t-176 70.5t-179.5 -71t-80.5 -207z" />
|
||||
<glyph unicode="f" horiz-adv-x="743" d="M35 928v110l182 72v72q0 196 92 290.5t281 94.5q124 0 244 -41l-62 -178q-87 28 -166 28q-80 0 -116.5 -49.5t-36.5 -148.5v-72h270v-178h-270v-928h-236v928h-182z" />
|
||||
<glyph unicode="g" horiz-adv-x="1139" d="M23 -184q0 102 64.5 171.5t180.5 96.5q-47 20 -77.5 64.5t-30.5 93.5q0 62 35 105t104 85q-86 37 -139.5 120.5t-53.5 195.5q0 180 113.5 279t323.5 99q47 0 98.5 -6.5t77.5 -13.5h383v-129l-189 -35q26 -35 43 -86t17 -108q0 -171 -118 -269t-325 -98q-53 0 -96 8 q-76 -47 -76 -110q0 -38 35.5 -57t130.5 -19h193q183 0 278 -78t95 -225q0 -188 -155 -290t-448 -102q-226 0 -345 80t-119 228zM233 -172q0 -76 68.5 -117t192.5 -41q192 0 286 55t94 146q0 72 -51.5 102.5t-191.5 30.5h-178q-101 0 -160.5 -47.5t-59.5 -128.5zM334 748 q0 -104 53.5 -160t153.5 -56q204 0 204 218q0 108 -50.5 166.5t-153.5 58.5q-102 0 -154.5 -58t-52.5 -169z" />
|
||||
<glyph unicode="h" horiz-adv-x="1300" d="M168 0v1556h235v-395q0 -95 -12 -203h15q48 80 133.5 124t199.5 44q402 0 402 -405v-721h-236v680q0 128 -51.5 191t-163.5 63q-148 0 -217.5 -88.5t-69.5 -296.5v-549h-235z" />
|
||||
<glyph unicode="i" horiz-adv-x="571" d="M154 1399q0 63 34.5 97t98.5 34q62 0 96.5 -34t34.5 -97q0 -60 -34.5 -94.5t-96.5 -34.5q-64 0 -98.5 34.5t-34.5 94.5zM168 0v1106h235v-1106h-235z" />
|
||||
<glyph unicode="j" horiz-adv-x="571" d="M-121 -281q68 -18 139 -18q150 0 150 170v1235h235v-1251q0 -171 -89.5 -259t-258.5 -88q-106 0 -176 25v186zM154 1399q0 63 34.5 97t98.5 34q62 0 96.5 -34t34.5 -97q0 -60 -34.5 -94.5t-96.5 -34.5q-64 0 -98.5 34.5t-34.5 94.5z" />
|
||||
<glyph unicode="k" horiz-adv-x="1171" d="M168 0v1556h233v-759l-12 -213h6l133 166l334 356h271l-445 -475l473 -631h-276l-355 485l-129 -106v-379h-233z" />
|
||||
<glyph unicode="l" horiz-adv-x="571" d="M168 0v1556h235v-1556h-235z" />
|
||||
<glyph unicode="m" horiz-adv-x="1958" d="M168 0v1106h184l33 -145h12q46 79 133.5 122t192.5 43q255 0 338 -174h16q49 82 138 128t204 46q198 0 288.5 -100t90.5 -305v-721h-235v682q0 127 -48.5 189.5t-150.5 62.5q-137 0 -200.5 -85.5t-63.5 -262.5v-586h-236v682q0 127 -48 189.5t-150 62.5 q-136 0 -199.5 -88.5t-63.5 -294.5v-551h-235z" />
|
||||
<glyph unicode="n" horiz-adv-x="1300" d="M168 0v1106h184l33 -145h12q50 79 142 122t204 43q398 0 398 -405v-721h-236v680q0 128 -51.5 191t-163.5 63q-149 0 -218 -88t-69 -295v-551h-235z" />
|
||||
<glyph unicode="o" horiz-adv-x="1251" d="M102 555q0 269 138 420t389 151q240 0 380 -154.5t140 -416.5q0 -271 -139 -423t-387 -152q-155 0 -274 70t-183 201t-64 304zM344 555q0 -383 283 -383q280 0 280 383q0 379 -282 379q-148 0 -214.5 -98t-66.5 -281z" />
|
||||
<glyph unicode="p" horiz-adv-x="1276" d="M168 -492v1598h190q8 -31 33 -148h12q110 168 330 168q207 0 322.5 -150t115.5 -421t-117.5 -423t-324.5 -152q-210 0 -326 151h-14q14 -140 14 -170v-453h-235zM403 555q0 -202 64 -292.5t209 -90.5q122 0 188 100t66 285q0 186 -65.5 281.5t-192.5 95.5 q-140 0 -204.5 -82t-64.5 -262v-35z" />
|
||||
<glyph unicode="q" horiz-adv-x="1276" d="M102 551q0 270 118 422.5t325 152.5q104 0 186.5 -38.5t147.5 -126.5h8l26 145h195v-1598h-236v469q0 44 4 93t7 75h-13q-104 -165 -331 -165q-205 0 -321 150.5t-116 420.5zM344 547q0 -379 262 -379q148 0 212.5 85.5t64.5 258.5v37q0 205 -66.5 295t-214.5 90 q-126 0 -192 -100t-66 -287z" />
|
||||
<glyph unicode="r" horiz-adv-x="883" d="M168 0v1106h184l31 -195h12q55 99 143.5 157t190.5 58q71 0 117 -10l-23 -219q-50 12 -104 12q-141 0 -228.5 -92t-87.5 -239v-578h-235z" />
|
||||
<glyph unicode="s" horiz-adv-x="997" d="M98 827q0 142 114.5 220.5t311.5 78.5q195 0 369 -79l-76 -177q-179 74 -301 74q-186 0 -186 -106q0 -52 48.5 -88t211.5 -99q137 -53 199 -97t92 -101.5t30 -137.5q0 -162 -118 -248.5t-338 -86.5q-221 0 -355 67v203q195 -90 363 -90q217 0 217 131q0 42 -24 70t-79 58 t-153 68q-191 74 -258.5 148t-67.5 192z" />
|
||||
<glyph unicode="t" horiz-adv-x="805" d="M39 928v104l162 86l80 234h145v-246h315v-178h-315v-592q0 -85 42.5 -125.5t111.5 -40.5q86 0 172 27v-177q-39 -17 -100.5 -28.5t-127.5 -11.5q-334 0 -334 352v596h-151z" />
|
||||
<glyph unicode="u" horiz-adv-x="1300" d="M158 383v723h237v-682q0 -127 52 -190.5t163 -63.5q148 0 217.5 88.5t69.5 296.5v551h236v-1106h-185l-33 145h-12q-49 -77 -139.5 -121t-206.5 -44q-201 0 -300 100t-99 303z" />
|
||||
<glyph unicode="v" horiz-adv-x="1096" d="M0 1106h248l225 -643q58 -162 70 -262h8q9 72 70 262l225 643h250l-422 -1106h-254z" />
|
||||
<glyph unicode="w" horiz-adv-x="1673" d="M20 1106h240l141 -545q48 -202 68 -346h6q10 73 30.5 167.5t35.5 141.5l168 582h258l163 -582q15 -49 37.5 -150t26.5 -157h8q15 123 70 344l143 545h236l-312 -1106h-264l-143 516q-26 82 -94 381h-9q-58 -270 -92 -383l-147 -514h-260z" />
|
||||
<glyph unicode="x" horiz-adv-x="1128" d="M25 0l389 565l-371 541h268l252 -387l254 387h266l-372 -541l391 -565h-266l-273 414l-272 -414h-266z" />
|
||||
<glyph unicode="y" horiz-adv-x="1098" d="M0 1106h256l225 -627q51 -134 68 -252h8q9 55 33 133.5t254 745.5h254l-473 -1253q-129 -345 -430 -345q-78 0 -152 17v186q53 -12 121 -12q170 0 239 197l41 104z" />
|
||||
<glyph unicode="z" horiz-adv-x="979" d="M68 0v145l559 781h-525v180h789v-164l-547 -762h563v-180h-839z" />
|
||||
<glyph unicode="{" horiz-adv-x="791" d="M45 473v191q135 0 200.5 45.5t65.5 138.5v311q0 156 108.5 229.5t325.5 73.5v-182q-114 -5 -165.5 -46.5t-51.5 -123.5v-297q0 -199 -229 -238v-12q229 -36 229 -237v-299q0 -82 51 -124t166 -44v-183q-231 2 -332.5 78.5t-101.5 247.5v285q0 186 -266 186z" />
|
||||
<glyph unicode="|" horiz-adv-x="1128" d="M473 -481v2033h180v-2033h-180z" />
|
||||
<glyph unicode="}" horiz-adv-x="760" d="M45 -141q95 1 148 38.5t53 129.5v262q0 121 53 187t176 87v12q-229 39 -229 238v297q0 82 -45.5 123.5t-155.5 46.5v182q223 0 320.5 -76.5t97.5 -250.5v-287q0 -100 63.5 -142t188.5 -42v-191q-123 0 -187.5 -42.5t-64.5 -143.5v-307q0 -156 -99.5 -229t-318.5 -75v183z " />
|
||||
<glyph unicode="~" d="M96 571v191q99 108 250 108q66 0 125 -13t147 -50q131 -55 220 -55q52 0 114.5 31t120.5 89v-190q-105 -111 -250 -111q-65 0 -127.5 15.5t-146.5 50.5q-127 55 -219 55q-50 0 -111.5 -30t-122.5 -91z" />
|
||||
<glyph unicode="¢" d="M166 741q0 254 100.5 397t306.5 175v170h158v-162q152 -5 283 -66l-70 -188q-146 59 -250 59q-146 0 -216 -95t-70 -288q0 -194 72 -283t210 -89q75 0 142.5 15t154.5 52v-200q-119 -59 -258 -64v-194h-156v200q-207 31 -307 171t-100 390z" />
|
||||
<glyph unicode="£" d="M72 0v195q98 30 145 96t47 178v184h-188v172h188v256q0 188 113.5 294t312.5 106q194 0 375 -82l-76 -182q-162 71 -284 71q-205 0 -205 -219v-244h397v-172h-397v-182q0 -91 -33 -155t-113 -109h756v-207h-1038z" />
|
||||
<glyph unicode="¥" d="M18 1462h246l320 -665l321 665h244l-399 -760h227v-151h-281v-154h281v-153h-281v-244h-225v244h-283v153h283v154h-283v151h224z" />
|
||||
<glyph unicode="©" horiz-adv-x="1704" d="M100 731q0 200 100 375t275 276t377 101q200 0 375 -100t276 -275t101 -377q0 -197 -97 -370t-272 -277t-383 -104q-207 0 -382 103.5t-272.5 276.5t-97.5 371zM223 731q0 -170 84.5 -315.5t230.5 -229.5t314 -84q170 0 316 85.5t229.5 230t83.5 313.5q0 168 -84.5 314.5 t-231 230.5t-313.5 84q-168 0 -312.5 -83t-230.5 -229t-86 -317zM471 731q0 214 110 337.5t306 123.5q138 0 274 -70l-65 -143q-106 55 -203 55q-111 0 -171 -80.5t-60 -222.5q0 -147 54 -226t177 -79q55 0 118 15t109 36v-158q-115 -51 -235 -51q-197 0 -305.5 120.5 t-108.5 342.5z" />
|
||||
<glyph unicode="­" horiz-adv-x="659" d="M72 449v200h514v-200h-514z" />
|
||||
<glyph unicode="®" horiz-adv-x="1704" d="M100 731q0 200 100 375t275 276t377 101q200 0 375 -100t276 -275t101 -377q0 -197 -97 -370t-272 -277t-383 -104q-207 0 -382 103.5t-272.5 276.5t-97.5 371zM223 731q0 -170 84.5 -315.5t230.5 -229.5t314 -84q170 0 316 85.5t229.5 230t83.5 313.5q0 168 -84.5 314.5 t-231 230.5t-313.5 84q-168 0 -312.5 -83t-230.5 -229t-86 -317zM559 279v903h262q174 0 255 -68t81 -205q0 -171 -153 -233l237 -397h-211l-192 346h-90v-346h-189zM748 770h69q74 0 112 35t38 100q0 72 -36.5 100.5t-115.5 28.5h-67v-264z" />
|
||||
<glyph unicode="´" horiz-adv-x="1212" d="M362 1241v25q57 70 117.5 156t95.5 147h273v-21q-52 -61 -155.5 -157.5t-174.5 -149.5h-156z" />
|
||||
<glyph unicode=" " horiz-adv-x="784" />
|
||||
<glyph unicode=" " horiz-adv-x="1569" />
|
||||
<glyph unicode=" " horiz-adv-x="784" />
|
||||
<glyph unicode=" " horiz-adv-x="1569" />
|
||||
<glyph unicode=" " horiz-adv-x="523" />
|
||||
<glyph unicode=" " horiz-adv-x="392" />
|
||||
<glyph unicode=" " horiz-adv-x="261" />
|
||||
<glyph unicode=" " horiz-adv-x="261" />
|
||||
<glyph unicode=" " horiz-adv-x="196" />
|
||||
<glyph unicode=" " horiz-adv-x="313" />
|
||||
<glyph unicode=" " horiz-adv-x="87" />
|
||||
<glyph unicode="‐" horiz-adv-x="659" d="M72 449v200h514v-200h-514z" />
|
||||
<glyph unicode="‑" horiz-adv-x="659" d="M72 449v200h514v-200h-514z" />
|
||||
<glyph unicode="‒" horiz-adv-x="659" d="M72 449v200h514v-200h-514z" />
|
||||
<glyph unicode="–" horiz-adv-x="1024" d="M82 455v190h860v-190h-860z" />
|
||||
<glyph unicode="—" horiz-adv-x="2048" d="M82 455v190h1884v-190h-1884z" />
|
||||
<glyph unicode="‘" horiz-adv-x="395" d="M25 983q20 83 71 224t105 255h170q-64 -256 -101 -501h-233z" />
|
||||
<glyph unicode="’" horiz-adv-x="395" d="M25 961q69 289 100 501h231l15 -22q-53 -209 -176 -479h-170z" />
|
||||
<glyph unicode="“" horiz-adv-x="813" d="M25 983q20 83 71 224t105 255h170q-64 -256 -101 -501h-233zM440 983q53 203 178 479h170q-69 -296 -100 -501h-233z" />
|
||||
<glyph unicode="”" horiz-adv-x="813" d="M25 961q69 289 100 501h231l15 -22q-53 -209 -176 -479h-170zM440 961q69 271 103 501h231l14 -22q-53 -209 -176 -479h-172z" />
|
||||
<glyph unicode="•" horiz-adv-x="770" d="M131 748q0 138 66 210t188 72q121 0 187.5 -72.5t66.5 -209.5q0 -135 -67 -209t-187 -74t-187 72.5t-67 210.5z" />
|
||||
<glyph unicode="…" horiz-adv-x="1677" d="M133 125q0 73 38 112t110 39q73 0 111 -40.5t38 -110.5q0 -71 -38.5 -112.5t-110.5 -41.5t-110 41t-38 113zM690 125q0 73 38 112t110 39q73 0 111 -40.5t38 -110.5q0 -71 -38.5 -112.5t-110.5 -41.5t-110 41t-38 113zM1247 125q0 73 38 112t110 39q73 0 111 -40.5 t38 -110.5q0 -71 -38.5 -112.5t-110.5 -41.5t-110 41t-38 113z" />
|
||||
<glyph unicode=" " horiz-adv-x="313" />
|
||||
<glyph unicode=" " horiz-adv-x="392" />
|
||||
<glyph unicode="€" horiz-adv-x="1188" d="M63 494v153h136l-2 37v37l2 65h-136v154h150q38 251 191 394t395 143q200 0 358 -88l-84 -187q-154 76 -274 76q-141 0 -230.5 -84t-119.5 -254h456v-154h-471l-2 -45v-55l2 -39h408v-153h-391q64 -312 364 -312q143 0 293 62v-203q-131 -61 -305 -61q-241 0 -391.5 132 t-196.5 382h-152z" />
|
||||
<glyph unicode="™" horiz-adv-x="1561" d="M27 1333v129h553v-129h-205v-592h-146v592h-202zM635 741v721h217l178 -534l187 534h210v-721h-147v414l4 129h-6l-193 -543h-122l-185 543h-6l4 -119v-424h-141z" />
|
||||
<glyph unicode="" horiz-adv-x="1105" d="M0 1105h1105v-1105h-1105v1105z" />
|
||||
</font>
|
||||
</defs></svg>
|
||||
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 4.2 MiB |
@@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html ng-app="GScreen">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Greenscreen</title>
|
||||
<link rel="stylesheet" href="/css/app.css">
|
||||
<script type="text/javascript" src="https://www.gstatic.com/cv/js/sender/v1/cast_sender.js"></script>
|
||||
<script src="/js/vendor/castaway.js"></script>
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script src="/js/vendor/jquery.js"></script>
|
||||
<script src="/js/vendor/underscore.js"></script>
|
||||
<script src="/js/vendor/angular.js"></script>
|
||||
<script src="/js/vendor/angular-route.js"></script>
|
||||
<script src="/js/vendor/angular-resource.js"></script>
|
||||
<script src="/js/gscreen.js"></script>
|
||||
<script src="/js/gscreen-config.js"></script>
|
||||
</head>
|
||||
<body ng-controller="Main">
|
||||
<flash-container></flash-container>
|
||||
<header>
|
||||
<div class="row">
|
||||
<div class="column--mobile-1of2">
|
||||
<h1 class="logo">
|
||||
<a href="/"><span class="icon-logo-g"></span>reenscreen</a>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="column--mobile-1of2">
|
||||
<span class="user">{{ username }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div id="container" ng-view></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.*/
|
||||
|
||||
angular.module("GScreen").constant('CONFIG',{
|
||||
chromecastApplicationId: 'YOUR CHROMECAST APP ID HERE'
|
||||
});
|
||||
|
||||
@@ -0,0 +1,678 @@
|
||||
// Generated by CommonJS Everywhere 0.9.7
|
||||
(function (global) {
|
||||
function require(file, parentModule) {
|
||||
if ({}.hasOwnProperty.call(require.cache, file))
|
||||
return require.cache[file];
|
||||
var resolved = require.resolve(file);
|
||||
if (!resolved)
|
||||
throw new Error('Failed to resolve module ' + file);
|
||||
var module$ = {
|
||||
id: file,
|
||||
require: require,
|
||||
filename: file,
|
||||
exports: {},
|
||||
loaded: false,
|
||||
parent: parentModule,
|
||||
children: []
|
||||
};
|
||||
if (parentModule)
|
||||
parentModule.children.push(module$);
|
||||
var dirname = file.slice(0, file.lastIndexOf('/') + 1);
|
||||
require.cache[file] = module$.exports;
|
||||
resolved.call(module$.exports, module$, module$.exports, dirname, file);
|
||||
module$.loaded = true;
|
||||
return require.cache[file] = module$.exports;
|
||||
}
|
||||
require.modules = {};
|
||||
require.cache = {};
|
||||
require.resolve = function (file) {
|
||||
return {}.hasOwnProperty.call(require.modules, file) ? require.modules[file] : void 0;
|
||||
};
|
||||
require.define = function (file, fn) {
|
||||
require.modules[file] = fn;
|
||||
};
|
||||
var process = function () {
|
||||
var cwd = '/';
|
||||
return {
|
||||
title: 'browser',
|
||||
version: 'v0.10.28',
|
||||
browser: true,
|
||||
env: {},
|
||||
argv: [],
|
||||
nextTick: global.setImmediate || function (fn) {
|
||||
setTimeout(fn, 0);
|
||||
},
|
||||
cwd: function () {
|
||||
return cwd;
|
||||
},
|
||||
chdir: function (dir) {
|
||||
cwd = dir;
|
||||
}
|
||||
};
|
||||
}();
|
||||
require.define('/src/client/gscreen.coffee', function (module, exports, __dirname, __filename) {
|
||||
window.GScreen = angular.module('GScreen', [
|
||||
'ng',
|
||||
'ngResource',
|
||||
'ngRoute'
|
||||
]);
|
||||
require('/src/client/routes.coffee', module);
|
||||
require('/src/client/controllers/alert-form.coffee', module);
|
||||
require('/src/client/controllers/main.coffee', module);
|
||||
require('/src/client/controllers/channels.coffee', module);
|
||||
require('/src/client/controllers/channel-form.coffee', module);
|
||||
require('/src/client/controllers/chromecasts.coffee', module);
|
||||
require('/src/client/controllers/chromecast-form.coffee', module);
|
||||
require('/src/client/controllers/receiver.coffee', module);
|
||||
require('/src/client/controllers/screen.coffee', module);
|
||||
require('/src/client/controllers/takeover-form.coffee', module);
|
||||
require('/src/client/directives/flash-container.coffee', module);
|
||||
require('/src/client/directives/real-link.coffee', module);
|
||||
require('/src/client/resources/alert.coffee', module);
|
||||
require('/src/client/resources/channel.coffee', module);
|
||||
require('/src/client/resources/chromecast.coffee', module);
|
||||
require('/src/client/resources/takeover.coffee', module);
|
||||
require('/src/client/services/cast-away.coffee', module);
|
||||
require('/src/client/services/flash.coffee', module);
|
||||
require('/src/client/services/local-device.coffee', module);
|
||||
require('/src/client/services/sockets.coffee', module);
|
||||
});
|
||||
require.define('/src/client/services/sockets.coffee', function (module, exports, __dirname, __filename) {
|
||||
angular.module('GScreen').factory('sockets', function () {
|
||||
var addListenerForEvent, exports, listeners, socket;
|
||||
listeners = {};
|
||||
socket = io(window.location.origin);
|
||||
socket.on('connect', function () {
|
||||
var connected;
|
||||
return connected = true;
|
||||
});
|
||||
addListenerForEvent = function (name) {
|
||||
return socket.on(name, function (args) {
|
||||
var l;
|
||||
args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
|
||||
return function (accum$) {
|
||||
for (var i$ = 0, length$ = listeners[name].length; i$ < length$; ++i$) {
|
||||
l = listeners[name][i$];
|
||||
accum$.push(l.apply(null, [].slice.call(args)));
|
||||
}
|
||||
return accum$;
|
||||
}.call(this, []);
|
||||
});
|
||||
};
|
||||
return exports = {
|
||||
on: function (name, func) {
|
||||
if (listeners[name]) {
|
||||
return listeners[name].push(func);
|
||||
} else {
|
||||
listeners[name] = [func];
|
||||
return addListenerForEvent(name);
|
||||
}
|
||||
},
|
||||
emit: function (args) {
|
||||
args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
|
||||
return socket.emit.apply(socket, [].slice.call(args));
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
require.define('/src/client/services/local-device.coffee', function (module, exports, __dirname, __filename) {
|
||||
angular.module('GScreen').factory('localDevice', function (Chromecast, sockets) {
|
||||
var clearAlert, clearTimeoutId, createAlert, exports, listeners, loadChromecast, loadChromecastFromPersistence, updateAlert, updateChannelId, updateChromecast;
|
||||
listeners = { change: [] };
|
||||
clearTimeoutId = null;
|
||||
loadChromecast = function (id) {
|
||||
return Chromecast.get(id).$promise.then(function (c) {
|
||||
console.log('loadChromecast', c);
|
||||
updateChromecast(c);
|
||||
return createAlert(c.alert);
|
||||
});
|
||||
};
|
||||
updateChromecast = function (c) {
|
||||
var l;
|
||||
exports.chromecast = c;
|
||||
return function (accum$) {
|
||||
for (var i$ = 0, length$ = listeners.change.length; i$ < length$; ++i$) {
|
||||
l = listeners.change[i$];
|
||||
accum$.push(l(c));
|
||||
}
|
||||
return accum$;
|
||||
}.call(this, []);
|
||||
};
|
||||
updateChannelId = function (channelId) {
|
||||
var k, newCast, v;
|
||||
newCast = {};
|
||||
for (k in exports.chromecast) {
|
||||
v = exports.chromecast[k];
|
||||
newCast[k] = v;
|
||||
}
|
||||
newCast.channelId = channelId;
|
||||
return updateChromecast(newCast);
|
||||
};
|
||||
updateAlert = function (alert) {
|
||||
var k, newCast, v;
|
||||
newCast = {};
|
||||
for (k in exports.chromecast) {
|
||||
v = exports.chromecast[k];
|
||||
newCast[k] = v;
|
||||
}
|
||||
newCast.alert = alert;
|
||||
return updateChromecast(newCast);
|
||||
};
|
||||
loadChromecastFromPersistence = function () {
|
||||
var id;
|
||||
if (id = localStorage.getItem('chromecast-id'))
|
||||
return loadChromecast(id);
|
||||
};
|
||||
clearAlert = function () {
|
||||
console.log('Clearing the alert');
|
||||
if (clearTimeoutId)
|
||||
clearTimeout(clearTimeoutId);
|
||||
return updateAlert(null);
|
||||
};
|
||||
createAlert = function (alert) {
|
||||
var duration;
|
||||
if (alert) {
|
||||
duration = new Date(alert.expiresAt).getTime() - new Date().getTime();
|
||||
if (duration > 0) {
|
||||
updateAlert(alert);
|
||||
console.log('duration', duration);
|
||||
return clearTimeoutId = setTimeout(clearAlert, Math.ceil(duration));
|
||||
} else {
|
||||
return clearAlert();
|
||||
}
|
||||
}
|
||||
};
|
||||
sockets.on('receiver-updated', function (chromecast) {
|
||||
if (exports.chromecast.id === chromecast.id)
|
||||
return updateChromecast(chromecast);
|
||||
});
|
||||
sockets.on('takeover-created', function (takeover) {
|
||||
return updateChannelId(takeover.channelId);
|
||||
});
|
||||
sockets.on('takeover-updated', function (takeover) {
|
||||
return updateChannelId(takeover.channelId);
|
||||
});
|
||||
sockets.on('takeover-deleted', function () {
|
||||
return loadChromecastFromPersistence();
|
||||
});
|
||||
sockets.on('alert-created', function (alert) {
|
||||
console.log('Creating alert', alert);
|
||||
return createAlert(alert);
|
||||
});
|
||||
loadChromecastFromPersistence();
|
||||
exports = {
|
||||
setChromecastId: function (id) {
|
||||
loadChromecast(id);
|
||||
return localStorage.setItem('chromecast-id', id);
|
||||
},
|
||||
on: function (event, func) {
|
||||
return listeners[event].push(func);
|
||||
},
|
||||
chromecast: null
|
||||
};
|
||||
return exports;
|
||||
});
|
||||
});
|
||||
require.define('/src/client/services/flash.coffee', function (module, exports, __dirname, __filename) {
|
||||
angular.module('GScreen').factory('flash', function () {
|
||||
var exports, listeners;
|
||||
listeners = { message: [] };
|
||||
return exports = {
|
||||
on: function (key, func) {
|
||||
return listeners[key].push(func);
|
||||
},
|
||||
message: function (msg) {
|
||||
var l;
|
||||
return function (accum$) {
|
||||
for (var i$ = 0, length$ = listeners.message.length; i$ < length$; ++i$) {
|
||||
l = listeners.message[i$];
|
||||
accum$.push(l(msg));
|
||||
}
|
||||
return accum$;
|
||||
}.call(this, []);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
require.define('/src/client/services/cast-away.coffee', function (module, exports, __dirname, __filename) {
|
||||
angular.module('GScreen').factory('castAway', function (CONFIG) {
|
||||
var castAway, exports, listeners;
|
||||
castAway = null;
|
||||
listeners = { 'receivers:available': [] };
|
||||
exports = {
|
||||
available: false,
|
||||
on: function (key, func) {
|
||||
return listeners[key].push(func);
|
||||
},
|
||||
connect: function (cb) {
|
||||
if (!castAway)
|
||||
return;
|
||||
return castAway.requestSession(cb);
|
||||
},
|
||||
receiver: function () {
|
||||
if (!castAway)
|
||||
return;
|
||||
return castAway.receiver();
|
||||
}
|
||||
};
|
||||
if ('undefined' !== typeof chrome && null != chrome ? chrome.cast : void 0) {
|
||||
if (null != chrome.cast.timeout)
|
||||
chrome.cast.timeout;
|
||||
else
|
||||
chrome.cast.timeout = {};
|
||||
chrome.cast.timeout.requestSession = 2e4;
|
||||
castAway = window.castAway = new CastAway({
|
||||
applicationID: CONFIG.chromecastApplicationId,
|
||||
namespace: 'urn:x-cast:json'
|
||||
});
|
||||
castAway.on('receivers:available', function () {
|
||||
var l;
|
||||
console.log('receivers available');
|
||||
exports.available = true;
|
||||
for (var i$ = 0, length$ = listeners['receivers:available'].length; i$ < length$; ++i$) {
|
||||
l = listeners['receivers:available'][i$];
|
||||
l();
|
||||
}
|
||||
return $('#cast').click(function (ev) {
|
||||
return ev.preventDefault();
|
||||
});
|
||||
});
|
||||
castAway.initialize(function (err, data) {
|
||||
return console.log('initialized', err, data);
|
||||
});
|
||||
}
|
||||
return exports;
|
||||
});
|
||||
});
|
||||
require.define('/src/client/resources/takeover.coffee', function (module, exports, __dirname, __filename) {
|
||||
angular.module('GScreen').factory('Takeover', function ($resource) {
|
||||
var resource;
|
||||
resource = $resource('/api/takeover', null, {
|
||||
update: { method: 'PUT' },
|
||||
remove: {
|
||||
method: 'POST',
|
||||
url: '/api/takeover/remove'
|
||||
}
|
||||
});
|
||||
return {
|
||||
get: function (id) {
|
||||
return resource.get();
|
||||
},
|
||||
query: function (args) {
|
||||
args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
|
||||
return resource.query.apply(resource, [].slice.call(args));
|
||||
},
|
||||
save: function (data, cb) {
|
||||
if (null == cb)
|
||||
cb = function () {
|
||||
};
|
||||
return resource.save(data, cb);
|
||||
},
|
||||
remove: function (data, cb) {
|
||||
if (null == cb)
|
||||
cb = function () {
|
||||
};
|
||||
return resource.remove(cb);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
require.define('/src/client/resources/chromecast.coffee', function (module, exports, __dirname, __filename) {
|
||||
angular.module('GScreen').factory('Chromecast', function ($resource) {
|
||||
var resource;
|
||||
resource = $resource('/api/receivers/:id', null, {
|
||||
update: { method: 'PUT' },
|
||||
remove: { method: 'DELETE' }
|
||||
});
|
||||
return {
|
||||
get: function (id) {
|
||||
return resource.get({ id: id });
|
||||
},
|
||||
query: function (args) {
|
||||
args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
|
||||
return resource.query.apply(resource, [].slice.call(args));
|
||||
},
|
||||
save: function (data, cb) {
|
||||
if (null == cb)
|
||||
cb = function () {
|
||||
};
|
||||
if (data.id) {
|
||||
if (data.alert && new Date(data.alert.expiresAt).getTime() < new Date().getTime())
|
||||
data.alert = null;
|
||||
return resource.update({ id: data.id }, data, cb);
|
||||
} else {
|
||||
return resource.save(data, cb);
|
||||
}
|
||||
},
|
||||
remove: function (data, cb) {
|
||||
if (null == cb)
|
||||
cb = function () {
|
||||
};
|
||||
return resource.remove({ id: data.id }, cb);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
require.define('/src/client/resources/channel.coffee', function (module, exports, __dirname, __filename) {
|
||||
angular.module('GScreen').factory('Channel', function ($resource) {
|
||||
var resource;
|
||||
resource = $resource('/api/channels/:id', null, {
|
||||
update: { method: 'PUT' },
|
||||
remove: { method: 'DELETE' }
|
||||
});
|
||||
return {
|
||||
get: function (id) {
|
||||
return resource.get({ id: id });
|
||||
},
|
||||
query: function (args) {
|
||||
args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
|
||||
return resource.query.apply(resource, [].slice.call(args));
|
||||
},
|
||||
save: function (data, cb) {
|
||||
if (null == cb)
|
||||
cb = function () {
|
||||
};
|
||||
if (data.id) {
|
||||
return resource.update({ id: data.id }, data, cb);
|
||||
} else {
|
||||
return resource.save(data, cb);
|
||||
}
|
||||
},
|
||||
remove: function (data, cb) {
|
||||
if (null == cb)
|
||||
cb = function () {
|
||||
};
|
||||
return resource.remove({ id: data.id }, cb);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
require.define('/src/client/resources/alert.coffee', function (module, exports, __dirname, __filename) {
|
||||
angular.module('GScreen').factory('Alert', function ($resource) {
|
||||
var resource;
|
||||
resource = $resource('/api/alerts/:id', null, {
|
||||
update: { method: 'PUT' },
|
||||
remove: {
|
||||
method: 'POST',
|
||||
url: '/api/alerts/:id/remove',
|
||||
params: { id: '@id' }
|
||||
}
|
||||
});
|
||||
return {
|
||||
get: function (id) {
|
||||
return resource.get({ id: id });
|
||||
},
|
||||
query: function (args) {
|
||||
args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
|
||||
return resource.query.apply(resource, [].slice.call(args));
|
||||
},
|
||||
save: function (data, cb) {
|
||||
if (null == cb)
|
||||
cb = function () {
|
||||
};
|
||||
if (data.id) {
|
||||
return resource.update({ id: data.id }, data, cb);
|
||||
} else {
|
||||
return resource.save(data, cb);
|
||||
}
|
||||
},
|
||||
remove: function (data, cb) {
|
||||
if (null == cb)
|
||||
cb = function () {
|
||||
};
|
||||
return resource.remove({ id: data.id }, cb);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
require.define('/src/client/directives/real-link.coffee', function (module, exports, __dirname, __filename) {
|
||||
angular.module('GScreen').directive('realLink', function () {
|
||||
return function (scope, element, attrs) {
|
||||
var $el;
|
||||
$el = $(element);
|
||||
return $el.click(function (e) {
|
||||
return window.location.href = $el.attr('href');
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
require.define('/src/client/directives/flash-container.coffee', function (module, exports, __dirname, __filename) {
|
||||
angular.module('GScreen').directive('flashContainer', function (flash) {
|
||||
return {
|
||||
transclude: false,
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
template: "<div id='alerts'></div>",
|
||||
link: function (scope, element, attrs) {
|
||||
return flash.on('message', function (msg) {
|
||||
var el, remove;
|
||||
el = $("<div class='flash-msg'>" + msg + '</div>');
|
||||
$(element).append(el);
|
||||
remove = function () {
|
||||
return el.remove();
|
||||
};
|
||||
return setTimeout(remove, 5e3);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
require.define('/src/client/controllers/takeover-form.coffee', function (module, exports, __dirname, __filename) {
|
||||
angular.module('GScreen').controller('TakeoverForm', function ($scope, flash, Channel, Takeover) {
|
||||
$scope.takeover = Takeover.get();
|
||||
$scope.channels = Channel.query();
|
||||
$scope.onFormSubmit = function () {
|
||||
return Takeover.save($scope.takeover, function (t) {
|
||||
var channel;
|
||||
if (null != $scope.takeover.id)
|
||||
$scope.takeover.id;
|
||||
else
|
||||
$scope.takeover.id = t.id;
|
||||
channel = _($scope.channels).detect(function (ch) {
|
||||
return ch.id === $scope.takeover.channelId;
|
||||
});
|
||||
return flash.message("The '" + channel.name + "' channel has taken over.");
|
||||
});
|
||||
};
|
||||
return $scope.onStop = function ($event) {
|
||||
$event.stopPropagation();
|
||||
$event.preventDefault();
|
||||
return Takeover.remove($scope.takeover, function () {
|
||||
$scope.takeover = {};
|
||||
return flash.message('The takeover has stopped.');
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
require.define('/src/client/controllers/screen.coffee', function (module, exports, __dirname, __filename) {
|
||||
angular.module('GScreen').controller('Screen', function ($scope, $sce, $location, Channel) {
|
||||
var id, lastTimeoutId, mainContentUrlCounter, rotateMainContentUrl;
|
||||
id = $location.url().match(/\/channels\/([^\/\?]+)/)[1];
|
||||
$scope.channel = Channel.get(id);
|
||||
$scope.channel.$promise.then(function (channel) {
|
||||
return setTimeout(rotateMainContentUrl, 0);
|
||||
});
|
||||
mainContentUrlCounter = 0;
|
||||
lastTimeoutId = null;
|
||||
return rotateMainContentUrl = function () {
|
||||
var cell, seconds, urls;
|
||||
clearTimeout(lastTimeoutId);
|
||||
cell = $scope.channel.cells[0];
|
||||
urls = cell.urls;
|
||||
if (urls.length === 1) {
|
||||
$scope.mainContentUrl = $sce.trustAsResourceUrl(urls[0].url);
|
||||
} else {
|
||||
$scope.mainContentUrl = $sce.trustAsResourceUrl(urls[mainContentUrlCounter].url);
|
||||
mainContentUrlCounter++;
|
||||
if (mainContentUrlCounter >= urls.length)
|
||||
mainContentUrlCounter = 0;
|
||||
seconds = parseInt(cell.duration, 10);
|
||||
lastTimeoutId = setTimeout(rotateMainContentUrl, seconds * 1e3);
|
||||
}
|
||||
if (!$scope.$$phase)
|
||||
return $scope.$apply();
|
||||
};
|
||||
});
|
||||
});
|
||||
require.define('/src/client/controllers/receiver.coffee', function (module, exports, __dirname, __filename) {
|
||||
angular.module('GScreen').controller('Receiver', function ($scope, $sce, $location, localDevice) {
|
||||
var match;
|
||||
if (match = $location.url().match(/\/chromecasts\/([^\/\?#]+)/))
|
||||
localDevice.setChromecastId(match[1]);
|
||||
$scope.title = 'Setting Up Chromecast';
|
||||
$scope.chromecast = null;
|
||||
$scope.channelUrl = null;
|
||||
localDevice.on('change', function (c) {
|
||||
$scope.chromecast = c;
|
||||
$scope.title = 'Greenscreen - ' + c.name;
|
||||
$scope.channelUrl = $sce.trustAsResourceUrl('/channels/' + c.channelId);
|
||||
if (!$scope.$$phase)
|
||||
return $scope.$apply();
|
||||
});
|
||||
return window.onload = function () {
|
||||
var castAway, e, receiver;
|
||||
try {
|
||||
castAway = new CastAway;
|
||||
receiver = castAway.receive();
|
||||
return receiver.on('setChromecastId', function (id) {
|
||||
return localDevice.setChromecastId(id);
|
||||
});
|
||||
} catch (e$) {
|
||||
e = e$;
|
||||
return console.log('Cannot load CastAway', e);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
require.define('/src/client/controllers/chromecast-form.coffee', function (module, exports, __dirname, __filename) {
|
||||
angular.module('GScreen').controller('ChromecastForm', function ($scope, $routeParams, $location, castAway, flash, Chromecast, Channel) {
|
||||
var connect, session;
|
||||
$scope.chromecast = $routeParams.id ? Chromecast.get($routeParams.id) : {};
|
||||
$scope.channels = Channel.query();
|
||||
session = null;
|
||||
connect = function () {
|
||||
return castAway.connect(function (err, s) {
|
||||
if (err)
|
||||
return console.log('ERR', err);
|
||||
session = s;
|
||||
return $scope.$apply(function () {
|
||||
return $scope.chromecast.name = session.session.receiver.friendlyName;
|
||||
});
|
||||
});
|
||||
};
|
||||
if (!$routeParams.id)
|
||||
connect();
|
||||
$scope.reconnect = function () {
|
||||
return connect();
|
||||
};
|
||||
$scope.onFormSubmit = function () {
|
||||
return Chromecast.save($scope.chromecast, function (chromecast) {
|
||||
flash.message("Your changes to '" + $scope.chromecast.name + "' have been saved.");
|
||||
if (session)
|
||||
session.send('setChromecastId', chromecast.id);
|
||||
return $location.url('/chromecasts');
|
||||
});
|
||||
};
|
||||
return $scope.deleteChannel = function () {
|
||||
console.log('deleteing', $scope.chromecast);
|
||||
return Chromecast.remove($scope.chromecast, function () {
|
||||
flash.message("The Chromecast '" + $scope.chromecast.name + "' has been deleted.");
|
||||
return $location.url('/chromecasts');
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
require.define('/src/client/controllers/chromecasts.coffee', function (module, exports, __dirname, __filename) {
|
||||
angular.module('GScreen').controller('Chromecasts', function ($scope, Chromecast, castAway) {
|
||||
$scope.chromecasts = Chromecast.query();
|
||||
$scope.chromecastAvailable = castAway.available;
|
||||
console.log('Initial Chromecast available', $scope.chromecastAvailable);
|
||||
return castAway.on('receivers:available', function () {
|
||||
$scope.$apply(function () {
|
||||
return $scope.chromecastAvailable = true;
|
||||
});
|
||||
return console.log('Chromecast is available', $scope.chromecastAvailable);
|
||||
});
|
||||
});
|
||||
});
|
||||
require.define('/src/client/controllers/channel-form.coffee', function (module, exports, __dirname, __filename) {
|
||||
angular.module('GScreen').controller('ChannelForm', function ($scope, $routeParams, $location, flash, Channel) {
|
||||
$scope.mainUrls = [];
|
||||
$scope.channel = $routeParams.id ? Channel.get($routeParams.id) : {
|
||||
layout: 'single-cell',
|
||||
cells: [
|
||||
{
|
||||
urls: [{ url: '' }],
|
||||
duration: 60
|
||||
},
|
||||
{
|
||||
urls: [{ url: '' }],
|
||||
duration: 60
|
||||
}
|
||||
]
|
||||
};
|
||||
if (null != $scope.channel.$promise)
|
||||
$scope.channel.$promise.then(function (channel) {
|
||||
return console.log(channel);
|
||||
});
|
||||
$scope.onFormSubmit = function () {
|
||||
return Channel.save($scope.channel, function () {
|
||||
flash.message("Your changes to '" + $scope.channel.name + "' have been saved.");
|
||||
return $location.url('/channels');
|
||||
});
|
||||
};
|
||||
$scope.deleteChannel = function () {
|
||||
return Channel.remove($scope.channel, function () {
|
||||
flash.message("The channel '" + $scope.channel.name + "' has been deleted.");
|
||||
return $location.url('/channels');
|
||||
});
|
||||
};
|
||||
return $scope.removeUrlFromCell = function (url, cellIndex) {
|
||||
return $scope.channel.cells[cellIndex].urls = _.without($scope.channel.cells[cellIndex].urls, url);
|
||||
};
|
||||
});
|
||||
});
|
||||
require.define('/src/client/controllers/channels.coffee', function (module, exports, __dirname, __filename) {
|
||||
angular.module('GScreen').controller('Channels', function ($scope, Channel) {
|
||||
return $scope.channels = Channel.query();
|
||||
});
|
||||
});
|
||||
require.define('/src/client/controllers/main.coffee', function (module, exports, __dirname, __filename) {
|
||||
angular.module('GScreen').controller('Main', function ($scope) {
|
||||
return $scope.username = '';
|
||||
});
|
||||
});
|
||||
require.define('/src/client/controllers/alert-form.coffee', function (module, exports, __dirname, __filename) {
|
||||
angular.module('GScreen').controller('AlertForm', function ($scope, $location, flash, Alert) {
|
||||
$scope.maxTextLength = 140;
|
||||
$scope.alert = {
|
||||
style: 'default',
|
||||
duration: 60
|
||||
};
|
||||
return $scope.onFormSubmit = function () {
|
||||
var seconds;
|
||||
seconds = $scope.alert.duration;
|
||||
$scope.alert.expiresAt = new Date(new Date().getTime() + seconds * 1e3).toISOString();
|
||||
return Alert.save($scope.alert, function () {
|
||||
flash.message('Your alert has been saved.');
|
||||
return $location.url('/');
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
require.define('/src/client/routes.coffee', function (module, exports, __dirname, __filename) {
|
||||
GScreen.config(function ($routeProvider, $locationProvider) {
|
||||
$routeProvider.when('/', { templateUrl: '/templates/main.html' });
|
||||
$routeProvider.when('/channels', { templateUrl: '/templates/channels/index.html' });
|
||||
$routeProvider.when('/channels/new', { templateUrl: '/templates/channels/new.html' });
|
||||
$routeProvider.when('/channels/:id/edit', { templateUrl: '/templates/channels/edit.html' });
|
||||
$routeProvider.when('/chromecasts', { templateUrl: '/templates/chromecasts/index.html' });
|
||||
$routeProvider.when('/alert', { templateUrl: '/templates/alert/index.html' });
|
||||
$routeProvider.when('/takeover', { templateUrl: '/templates/takeover/index.html' });
|
||||
$routeProvider.when('/chromecasts/new', { templateUrl: '/templates/chromecasts/new.html' });
|
||||
$routeProvider.when('/chromecasts/:id/edit', { templateUrl: '/templates/chromecasts/edit.html' });
|
||||
return $locationProvider.html5Mode(true);
|
||||
});
|
||||
});
|
||||
require('/src/client/gscreen.coffee');
|
||||
}.call(this, this));
|
||||
@@ -0,0 +1,646 @@
|
||||
/**
|
||||
* @license AngularJS v1.3.0-beta.9
|
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, angular, undefined) {'use strict';
|
||||
|
||||
var $resourceMinErr = angular.$$minErr('$resource');
|
||||
|
||||
// Helper functions and regex to lookup a dotted path on an object
|
||||
// stopping at undefined/null. The path must be composed of ASCII
|
||||
// identifiers (just like $parse)
|
||||
var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/;
|
||||
|
||||
function isValidDottedPath(path) {
|
||||
return (path != null && path !== '' && path !== 'hasOwnProperty' &&
|
||||
MEMBER_NAME_REGEX.test('.' + path));
|
||||
}
|
||||
|
||||
function lookupDottedPath(obj, path) {
|
||||
if (!isValidDottedPath(path)) {
|
||||
throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path);
|
||||
}
|
||||
var keys = path.split('.');
|
||||
for (var i = 0, ii = keys.length; i < ii && obj !== undefined; i++) {
|
||||
var key = keys[i];
|
||||
obj = (obj !== null) ? obj[key] : undefined;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a shallow copy of an object and clear other fields from the destination
|
||||
*/
|
||||
function shallowClearAndCopy(src, dst) {
|
||||
dst = dst || {};
|
||||
|
||||
angular.forEach(dst, function(value, key){
|
||||
delete dst[key];
|
||||
});
|
||||
|
||||
for (var key in src) {
|
||||
if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) {
|
||||
dst[key] = src[key];
|
||||
}
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc module
|
||||
* @name ngResource
|
||||
* @description
|
||||
*
|
||||
* # ngResource
|
||||
*
|
||||
* The `ngResource` module provides interaction support with RESTful services
|
||||
* via the $resource service.
|
||||
*
|
||||
*
|
||||
* <div doc-module-components="ngResource"></div>
|
||||
*
|
||||
* See {@link ngResource.$resource `$resource`} for usage.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $resource
|
||||
* @requires $http
|
||||
*
|
||||
* @description
|
||||
* A factory which creates a resource object that lets you interact with
|
||||
* [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
|
||||
*
|
||||
* The returned resource object has action methods which provide high-level behaviors without
|
||||
* the need to interact with the low level {@link ng.$http $http} service.
|
||||
*
|
||||
* Requires the {@link ngResource `ngResource`} module to be installed.
|
||||
*
|
||||
* By default, trailing slashes will be stripped from the calculated URLs,
|
||||
* which can pose problems with server backends that do not expect that
|
||||
* behavior. This can be disabled by configuring the `$resourceProvider` like
|
||||
* this:
|
||||
*
|
||||
* ```js
|
||||
app.config(['$resourceProvider', function ($resourceProvider) {
|
||||
// Don't strip trailing slashes from calculated URLs
|
||||
$resourceProvider.defaults.stripTrailingSlashes = false;
|
||||
}]);
|
||||
* ```
|
||||
*
|
||||
* @param {string} url A parametrized URL template with parameters prefixed by `:` as in
|
||||
* `/user/:username`. If you are using a URL with a port number (e.g.
|
||||
* `http://example.com:8080/api`), it will be respected.
|
||||
*
|
||||
* If you are using a url with a suffix, just add the suffix, like this:
|
||||
* `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')`
|
||||
* or even `$resource('http://example.com/resource/:resource_id.:format')`
|
||||
* If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be
|
||||
* collapsed down to a single `.`. If you need this sequence to appear and not collapse then you
|
||||
* can escape it with `/\.`.
|
||||
*
|
||||
* @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
|
||||
* `actions` methods. If any of the parameter value is a function, it will be executed every time
|
||||
* when a param value needs to be obtained for a request (unless the param was overridden).
|
||||
*
|
||||
* Each key value in the parameter object is first bound to url template if present and then any
|
||||
* excess keys are appended to the url search query after the `?`.
|
||||
*
|
||||
* Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
|
||||
* URL `/path/greet?salutation=Hello`.
|
||||
*
|
||||
* If the parameter value is prefixed with `@` then the value of that parameter will be taken
|
||||
* from the corresponding key on the data object (useful for non-GET operations).
|
||||
*
|
||||
* @param {Object.<Object>=} actions Hash with declaration of custom action that should extend
|
||||
* the default set of resource actions. The declaration should be created in the format of {@link
|
||||
* ng.$http#usage_parameters $http.config}:
|
||||
*
|
||||
* {action1: {method:?, params:?, isArray:?, headers:?, ...},
|
||||
* action2: {method:?, params:?, isArray:?, headers:?, ...},
|
||||
* ...}
|
||||
*
|
||||
* Where:
|
||||
*
|
||||
* - **`action`** – {string} – The name of action. This name becomes the name of the method on
|
||||
* your resource object.
|
||||
* - **`method`** – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`,
|
||||
* `DELETE`, and `JSONP`.
|
||||
* - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of
|
||||
* the parameter value is a function, it will be executed every time when a param value needs to
|
||||
* be obtained for a request (unless the param was overridden).
|
||||
* - **`url`** – {string} – action specific `url` override. The url templating is supported just
|
||||
* like for the resource-level urls.
|
||||
* - **`isArray`** – {boolean=} – If true then the returned object for this action is an array,
|
||||
* see `returns` section.
|
||||
* - **`transformRequest`** –
|
||||
* `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
|
||||
* transform function or an array of such functions. The transform function takes the http
|
||||
* request body and headers and returns its transformed (typically serialized) version.
|
||||
* - **`transformResponse`** –
|
||||
* `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
|
||||
* transform function or an array of such functions. The transform function takes the http
|
||||
* response body and headers and returns its transformed (typically deserialized) version.
|
||||
* - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
|
||||
* GET request, otherwise if a cache instance built with
|
||||
* {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
|
||||
* caching.
|
||||
* - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that
|
||||
* should abort the request when resolved.
|
||||
* - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the
|
||||
* XHR object. See
|
||||
* [requests with credentials](https://developer.mozilla.org/en/http_access_control#section_5)
|
||||
* for more information.
|
||||
* - **`responseType`** - `{string}` - see
|
||||
* [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType).
|
||||
* - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods -
|
||||
* `response` and `responseError`. Both `response` and `responseError` interceptors get called
|
||||
* with `http response` object. See {@link ng.$http $http interceptors}.
|
||||
*
|
||||
* @param {Object} options Hash with custom settings that should extend the
|
||||
* default `$resourceProvider` behavior. The only supported option is
|
||||
*
|
||||
* Where:
|
||||
*
|
||||
* - **`stripTrailingSlashes`** – {boolean} – If true then the trailing
|
||||
* slashes from any calculated URL will be stripped. (Defaults to true.)
|
||||
*
|
||||
* @returns {Object} A resource "class" object with methods for the default set of resource actions
|
||||
* optionally extended with custom `actions`. The default set contains these actions:
|
||||
* ```js
|
||||
* { 'get': {method:'GET'},
|
||||
* 'save': {method:'POST'},
|
||||
* 'query': {method:'GET', isArray:true},
|
||||
* 'remove': {method:'DELETE'},
|
||||
* 'delete': {method:'DELETE'} };
|
||||
* ```
|
||||
*
|
||||
* Calling these methods invoke an {@link ng.$http} with the specified http method,
|
||||
* destination and parameters. When the data is returned from the server then the object is an
|
||||
* instance of the resource class. The actions `save`, `remove` and `delete` are available on it
|
||||
* as methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
|
||||
* read, update, delete) on server-side data like this:
|
||||
* ```js
|
||||
* var User = $resource('/user/:userId', {userId:'@id'});
|
||||
* var user = User.get({userId:123}, function() {
|
||||
* user.abc = true;
|
||||
* user.$save();
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* It is important to realize that invoking a $resource object method immediately returns an
|
||||
* empty reference (object or array depending on `isArray`). Once the data is returned from the
|
||||
* server the existing reference is populated with the actual data. This is a useful trick since
|
||||
* usually the resource is assigned to a model which is then rendered by the view. Having an empty
|
||||
* object results in no rendering, once the data arrives from the server then the object is
|
||||
* populated with the data and the view automatically re-renders itself showing the new data. This
|
||||
* means that in most cases one never has to write a callback function for the action methods.
|
||||
*
|
||||
* The action methods on the class object or instance object can be invoked with the following
|
||||
* parameters:
|
||||
*
|
||||
* - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])`
|
||||
* - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])`
|
||||
* - non-GET instance actions: `instance.$action([parameters], [success], [error])`
|
||||
*
|
||||
* Success callback is called with (value, responseHeaders) arguments. Error callback is called
|
||||
* with (httpResponse) argument.
|
||||
*
|
||||
* Class actions return empty instance (with additional properties below).
|
||||
* Instance actions return promise of the action.
|
||||
*
|
||||
* The Resource instances and collection have these additional properties:
|
||||
*
|
||||
* - `$promise`: the {@link ng.$q promise} of the original server interaction that created this
|
||||
* instance or collection.
|
||||
*
|
||||
* On success, the promise is resolved with the same resource instance or collection object,
|
||||
* updated with data from server. This makes it easy to use in
|
||||
* {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view
|
||||
* rendering until the resource(s) are loaded.
|
||||
*
|
||||
* On failure, the promise is resolved with the {@link ng.$http http response} object, without
|
||||
* the `resource` property.
|
||||
*
|
||||
* If an interceptor object was provided, the promise will instead be resolved with the value
|
||||
* returned by the interceptor.
|
||||
*
|
||||
* - `$resolved`: `true` after first server interaction is completed (either with success or
|
||||
* rejection), `false` before that. Knowing if the Resource has been resolved is useful in
|
||||
* data-binding.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* # Credit card resource
|
||||
*
|
||||
* ```js
|
||||
// Define CreditCard class
|
||||
var CreditCard = $resource('/user/:userId/card/:cardId',
|
||||
{userId:123, cardId:'@id'}, {
|
||||
charge: {method:'POST', params:{charge:true}}
|
||||
});
|
||||
|
||||
// We can retrieve a collection from the server
|
||||
var cards = CreditCard.query(function() {
|
||||
// GET: /user/123/card
|
||||
// server returns: [ {id:456, number:'1234', name:'Smith'} ];
|
||||
|
||||
var card = cards[0];
|
||||
// each item is an instance of CreditCard
|
||||
expect(card instanceof CreditCard).toEqual(true);
|
||||
card.name = "J. Smith";
|
||||
// non GET methods are mapped onto the instances
|
||||
card.$save();
|
||||
// POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
|
||||
// server returns: {id:456, number:'1234', name: 'J. Smith'};
|
||||
|
||||
// our custom method is mapped as well.
|
||||
card.$charge({amount:9.99});
|
||||
// POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
|
||||
});
|
||||
|
||||
// we can create an instance as well
|
||||
var newCard = new CreditCard({number:'0123'});
|
||||
newCard.name = "Mike Smith";
|
||||
newCard.$save();
|
||||
// POST: /user/123/card {number:'0123', name:'Mike Smith'}
|
||||
// server returns: {id:789, number:'0123', name: 'Mike Smith'};
|
||||
expect(newCard.id).toEqual(789);
|
||||
* ```
|
||||
*
|
||||
* The object returned from this function execution is a resource "class" which has "static" method
|
||||
* for each action in the definition.
|
||||
*
|
||||
* Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and
|
||||
* `headers`.
|
||||
* When the data is returned from the server then the object is an instance of the resource type and
|
||||
* all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
|
||||
* operations (create, read, update, delete) on server-side data.
|
||||
|
||||
```js
|
||||
var User = $resource('/user/:userId', {userId:'@id'});
|
||||
User.get({userId:123}, function(user) {
|
||||
user.abc = true;
|
||||
user.$save();
|
||||
});
|
||||
```
|
||||
*
|
||||
* It's worth noting that the success callback for `get`, `query` and other methods gets passed
|
||||
* in the response that came from the server as well as $http header getter function, so one
|
||||
* could rewrite the above example and get access to http headers as:
|
||||
*
|
||||
```js
|
||||
var User = $resource('/user/:userId', {userId:'@id'});
|
||||
User.get({userId:123}, function(u, getResponseHeaders){
|
||||
u.abc = true;
|
||||
u.$save(function(u, putResponseHeaders) {
|
||||
//u => saved user object
|
||||
//putResponseHeaders => $http header getter
|
||||
});
|
||||
});
|
||||
```
|
||||
*
|
||||
* You can also access the raw `$http` promise via the `$promise` property on the object returned
|
||||
*
|
||||
```
|
||||
var User = $resource('/user/:userId', {userId:'@id'});
|
||||
User.get({userId:123})
|
||||
.$promise.then(function(user) {
|
||||
$scope.user = user;
|
||||
});
|
||||
```
|
||||
|
||||
* # Creating a custom 'PUT' request
|
||||
* In this example we create a custom method on our resource to make a PUT request
|
||||
* ```js
|
||||
* var app = angular.module('app', ['ngResource', 'ngRoute']);
|
||||
*
|
||||
* // Some APIs expect a PUT request in the format URL/object/ID
|
||||
* // Here we are creating an 'update' method
|
||||
* app.factory('Notes', ['$resource', function($resource) {
|
||||
* return $resource('/notes/:id', null,
|
||||
* {
|
||||
* 'update': { method:'PUT' }
|
||||
* });
|
||||
* }]);
|
||||
*
|
||||
* // In our controller we get the ID from the URL using ngRoute and $routeParams
|
||||
* // We pass in $routeParams and our Notes factory along with $scope
|
||||
* app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes',
|
||||
function($scope, $routeParams, Notes) {
|
||||
* // First get a note object from the factory
|
||||
* var note = Notes.get({ id:$routeParams.id });
|
||||
* $id = note.id;
|
||||
*
|
||||
* // Now call update passing in the ID first then the object you are updating
|
||||
* Notes.update({ id:$id }, note);
|
||||
*
|
||||
* // This will PUT /notes/ID with the note object in the request payload
|
||||
* }]);
|
||||
* ```
|
||||
*/
|
||||
angular.module('ngResource', ['ng']).
|
||||
provider('$resource', function () {
|
||||
var provider = this;
|
||||
|
||||
this.defaults = {
|
||||
// Strip slashes by default
|
||||
stripTrailingSlashes: true,
|
||||
|
||||
// Default actions configuration
|
||||
actions: {
|
||||
'get': {method: 'GET'},
|
||||
'save': {method: 'POST'},
|
||||
'query': {method: 'GET', isArray: true},
|
||||
'remove': {method: 'DELETE'},
|
||||
'delete': {method: 'DELETE'}
|
||||
}
|
||||
};
|
||||
|
||||
this.$get = ['$http', '$q', function ($http, $q) {
|
||||
|
||||
var noop = angular.noop,
|
||||
forEach = angular.forEach,
|
||||
extend = angular.extend,
|
||||
copy = angular.copy,
|
||||
isFunction = angular.isFunction;
|
||||
|
||||
/**
|
||||
* We need our custom method because encodeURIComponent is too aggressive and doesn't follow
|
||||
* http://www.ietf.org/rfc/rfc3986.txt with regards to the character set
|
||||
* (pchar) allowed in path segments:
|
||||
* segment = *pchar
|
||||
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
||||
* pct-encoded = "%" HEXDIG HEXDIG
|
||||
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
||||
* / "*" / "+" / "," / ";" / "="
|
||||
*/
|
||||
function encodeUriSegment(val) {
|
||||
return encodeUriQuery(val, true).
|
||||
replace(/%26/gi, '&').
|
||||
replace(/%3D/gi, '=').
|
||||
replace(/%2B/gi, '+');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method is intended for encoding *key* or *value* parts of query component. We need a
|
||||
* custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't
|
||||
* have to be encoded per http://tools.ietf.org/html/rfc3986:
|
||||
* query = *( pchar / "/" / "?" )
|
||||
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
||||
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||
* pct-encoded = "%" HEXDIG HEXDIG
|
||||
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
||||
* / "*" / "+" / "," / ";" / "="
|
||||
*/
|
||||
function encodeUriQuery(val, pctEncodeSpaces) {
|
||||
return encodeURIComponent(val).
|
||||
replace(/%40/gi, '@').
|
||||
replace(/%3A/gi, ':').
|
||||
replace(/%24/g, '$').
|
||||
replace(/%2C/gi, ',').
|
||||
replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
|
||||
}
|
||||
|
||||
function Route(template, defaults) {
|
||||
this.template = template;
|
||||
this.defaults = extend({}, provider.defaults, defaults);
|
||||
this.urlParams = {};
|
||||
}
|
||||
|
||||
Route.prototype = {
|
||||
setUrlParams: function (config, params, actionUrl) {
|
||||
var self = this,
|
||||
url = actionUrl || self.template,
|
||||
val,
|
||||
encodedVal;
|
||||
|
||||
var urlParams = self.urlParams = {};
|
||||
forEach(url.split(/\W/), function (param) {
|
||||
if (param === 'hasOwnProperty') {
|
||||
throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name.");
|
||||
}
|
||||
if (!(new RegExp("^\\d+$").test(param)) && param &&
|
||||
(new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) {
|
||||
urlParams[param] = true;
|
||||
}
|
||||
});
|
||||
url = url.replace(/\\:/g, ':');
|
||||
|
||||
params = params || {};
|
||||
forEach(self.urlParams, function (_, urlParam) {
|
||||
val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam];
|
||||
if (angular.isDefined(val) && val !== null) {
|
||||
encodedVal = encodeUriSegment(val);
|
||||
url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), function (match, p1) {
|
||||
return encodedVal + p1;
|
||||
});
|
||||
} else {
|
||||
url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function (match,
|
||||
leadingSlashes, tail) {
|
||||
if (tail.charAt(0) == '/') {
|
||||
return tail;
|
||||
} else {
|
||||
return leadingSlashes + tail;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// strip trailing slashes and set the url (unless this behavior is specifically disabled)
|
||||
if (self.defaults.stripTrailingSlashes) {
|
||||
url = url.replace(/\/+$/, '') || '/';
|
||||
}
|
||||
|
||||
// then replace collapse `/.` if found in the last URL path segment before the query
|
||||
// E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x`
|
||||
url = url.replace(/\/\.(?=\w+($|\?))/, '.');
|
||||
// replace escaped `/\.` with `/.`
|
||||
config.url = url.replace(/\/\\\./, '/.');
|
||||
|
||||
|
||||
// set params - delegate param encoding to $http
|
||||
forEach(params, function (value, key) {
|
||||
if (!self.urlParams[key]) {
|
||||
config.params = config.params || {};
|
||||
config.params[key] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function resourceFactory(url, paramDefaults, actions, options) {
|
||||
var route = new Route(url, options);
|
||||
|
||||
actions = extend({}, provider.defaults.actions, actions);
|
||||
|
||||
function extractParams(data, actionParams) {
|
||||
var ids = {};
|
||||
actionParams = extend({}, paramDefaults, actionParams);
|
||||
forEach(actionParams, function (value, key) {
|
||||
if (isFunction(value)) { value = value(); }
|
||||
ids[key] = value && value.charAt && value.charAt(0) == '@' ?
|
||||
lookupDottedPath(data, value.substr(1)) : value;
|
||||
});
|
||||
return ids;
|
||||
}
|
||||
|
||||
function defaultResponseInterceptor(response) {
|
||||
return response.resource;
|
||||
}
|
||||
|
||||
function Resource(value) {
|
||||
shallowClearAndCopy(value || {}, this);
|
||||
}
|
||||
|
||||
forEach(actions, function (action, name) {
|
||||
var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method);
|
||||
|
||||
Resource[name] = function (a1, a2, a3, a4) {
|
||||
var params = {}, data, success, error;
|
||||
|
||||
/* jshint -W086 */ /* (purposefully fall through case statements) */
|
||||
switch (arguments.length) {
|
||||
case 4:
|
||||
error = a4;
|
||||
success = a3;
|
||||
//fallthrough
|
||||
case 3:
|
||||
case 2:
|
||||
if (isFunction(a2)) {
|
||||
if (isFunction(a1)) {
|
||||
success = a1;
|
||||
error = a2;
|
||||
break;
|
||||
}
|
||||
|
||||
success = a2;
|
||||
error = a3;
|
||||
//fallthrough
|
||||
} else {
|
||||
params = a1;
|
||||
data = a2;
|
||||
success = a3;
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
if (isFunction(a1)) success = a1;
|
||||
else if (hasBody) data = a1;
|
||||
else params = a1;
|
||||
break;
|
||||
case 0: break;
|
||||
default:
|
||||
throw $resourceMinErr('badargs',
|
||||
"Expected up to 4 arguments [params, data, success, error], got {0} arguments",
|
||||
arguments.length);
|
||||
}
|
||||
/* jshint +W086 */ /* (purposefully fall through case statements) */
|
||||
|
||||
var isInstanceCall = this instanceof Resource;
|
||||
var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data));
|
||||
var httpConfig = {};
|
||||
var responseInterceptor = action.interceptor && action.interceptor.response ||
|
||||
defaultResponseInterceptor;
|
||||
var responseErrorInterceptor = action.interceptor && action.interceptor.responseError ||
|
||||
undefined;
|
||||
|
||||
forEach(action, function (value, key) {
|
||||
if (key != 'params' && key != 'isArray' && key != 'interceptor') {
|
||||
httpConfig[key] = copy(value);
|
||||
}
|
||||
});
|
||||
|
||||
if (hasBody) httpConfig.data = data;
|
||||
route.setUrlParams(httpConfig,
|
||||
extend({}, extractParams(data, action.params || {}), params),
|
||||
action.url);
|
||||
|
||||
var promise = $http(httpConfig).then(function (response) {
|
||||
var data = response.data,
|
||||
promise = value.$promise;
|
||||
|
||||
if (data) {
|
||||
// Need to convert action.isArray to boolean in case it is undefined
|
||||
// jshint -W018
|
||||
if (angular.isArray(data) !== (!!action.isArray)) {
|
||||
throw $resourceMinErr('badcfg',
|
||||
'Error in resource configuration. Expected ' +
|
||||
'response to contain an {0} but got an {1}',
|
||||
action.isArray ? 'array' : 'object',
|
||||
angular.isArray(data) ? 'array' : 'object');
|
||||
}
|
||||
// jshint +W018
|
||||
if (action.isArray) {
|
||||
value.length = 0;
|
||||
forEach(data, function (item) {
|
||||
value.push(new Resource(item));
|
||||
});
|
||||
} else {
|
||||
shallowClearAndCopy(data, value);
|
||||
value.$promise = promise;
|
||||
}
|
||||
}
|
||||
|
||||
value.$resolved = true;
|
||||
|
||||
response.resource = value;
|
||||
|
||||
return response;
|
||||
}, function (response) {
|
||||
value.$resolved = true;
|
||||
|
||||
(error || noop)(response);
|
||||
|
||||
return $q.reject(response);
|
||||
});
|
||||
|
||||
promise = promise.then(
|
||||
function (response) {
|
||||
var value = responseInterceptor(response);
|
||||
(success || noop)(value, response.headers);
|
||||
return value;
|
||||
},
|
||||
responseErrorInterceptor);
|
||||
|
||||
if (!isInstanceCall) {
|
||||
// we are creating instance / collection
|
||||
// - set the initial promise
|
||||
// - return the instance / collection
|
||||
value.$promise = promise;
|
||||
value.$resolved = false;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// instance call
|
||||
return promise;
|
||||
};
|
||||
|
||||
|
||||
Resource.prototype['$' + name] = function (params, success, error) {
|
||||
if (isFunction(params)) {
|
||||
error = success; success = params; params = {};
|
||||
}
|
||||
var result = Resource[name].call(this, params, this, success, error);
|
||||
return result.$promise || result;
|
||||
};
|
||||
});
|
||||
|
||||
Resource.bind = function (additionalParamDefaults) {
|
||||
return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
|
||||
};
|
||||
|
||||
return Resource;
|
||||
}
|
||||
|
||||
return resourceFactory;
|
||||
}];
|
||||
});
|
||||
|
||||
|
||||
})(window, window.angular);
|
||||
@@ -0,0 +1,927 @@
|
||||
/**
|
||||
* @license AngularJS v1.3.0-beta.9
|
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, angular, undefined) {'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc module
|
||||
* @name ngRoute
|
||||
* @description
|
||||
*
|
||||
* # ngRoute
|
||||
*
|
||||
* The `ngRoute` module provides routing and deeplinking services and directives for angular apps.
|
||||
*
|
||||
* ## Example
|
||||
* See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`.
|
||||
*
|
||||
*
|
||||
* <div doc-module-components="ngRoute"></div>
|
||||
*/
|
||||
/* global -ngRouteModule */
|
||||
var ngRouteModule = angular.module('ngRoute', ['ng']).
|
||||
provider('$route', $RouteProvider);
|
||||
|
||||
/**
|
||||
* @ngdoc provider
|
||||
* @name $routeProvider
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* Used for configuring routes.
|
||||
*
|
||||
* ## Example
|
||||
* See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`.
|
||||
*
|
||||
* ## Dependencies
|
||||
* Requires the {@link ngRoute `ngRoute`} module to be installed.
|
||||
*/
|
||||
function $RouteProvider(){
|
||||
function inherit(parent, extra) {
|
||||
return angular.extend(new (angular.extend(function() {}, {prototype:parent}))(), extra);
|
||||
}
|
||||
|
||||
var routes = {};
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $routeProvider#when
|
||||
*
|
||||
* @param {string} path Route path (matched against `$location.path`). If `$location.path`
|
||||
* contains redundant trailing slash or is missing one, the route will still match and the
|
||||
* `$location.path` will be updated to add or drop the trailing slash to exactly match the
|
||||
* route definition.
|
||||
*
|
||||
* * `path` can contain named groups starting with a colon: e.g. `:name`. All characters up
|
||||
* to the next slash are matched and stored in `$routeParams` under the given `name`
|
||||
* when the route matches.
|
||||
* * `path` can contain named groups starting with a colon and ending with a star:
|
||||
* e.g.`:name*`. All characters are eagerly stored in `$routeParams` under the given `name`
|
||||
* when the route matches.
|
||||
* * `path` can contain optional named groups with a question mark: e.g.`:name?`.
|
||||
*
|
||||
* For example, routes like `/color/:color/largecode/:largecode*\/edit` will match
|
||||
* `/color/brown/largecode/code/with/slashes/edit` and extract:
|
||||
*
|
||||
* * `color: brown`
|
||||
* * `largecode: code/with/slashes`.
|
||||
*
|
||||
*
|
||||
* @param {Object} route Mapping information to be assigned to `$route.current` on route
|
||||
* match.
|
||||
*
|
||||
* Object properties:
|
||||
*
|
||||
* - `controller` – `{(string|function()=}` – Controller fn that should be associated with
|
||||
* newly created scope or the name of a {@link angular.Module#controller registered
|
||||
* controller} if passed as a string.
|
||||
* - `controllerAs` – `{string=}` – A controller alias name. If present the controller will be
|
||||
* published to scope under the `controllerAs` name.
|
||||
* - `template` – `{string=|function()=}` – html template as a string or a function that
|
||||
* returns an html template as a string which should be used by {@link
|
||||
* ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives.
|
||||
* This property takes precedence over `templateUrl`.
|
||||
*
|
||||
* If `template` is a function, it will be called with the following parameters:
|
||||
*
|
||||
* - `{Array.<Object>}` - route parameters extracted from the current
|
||||
* `$location.path()` by applying the current route
|
||||
*
|
||||
* - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html
|
||||
* template that should be used by {@link ngRoute.directive:ngView ngView}.
|
||||
*
|
||||
* If `templateUrl` is a function, it will be called with the following parameters:
|
||||
*
|
||||
* - `{Array.<Object>}` - route parameters extracted from the current
|
||||
* `$location.path()` by applying the current route
|
||||
*
|
||||
* - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should
|
||||
* be injected into the controller. If any of these dependencies are promises, the router
|
||||
* will wait for them all to be resolved or one to be rejected before the controller is
|
||||
* instantiated.
|
||||
* If all the promises are resolved successfully, the values of the resolved promises are
|
||||
* injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is
|
||||
* fired. If any of the promises are rejected the
|
||||
* {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object
|
||||
* is:
|
||||
*
|
||||
* - `key` – `{string}`: a name of a dependency to be injected into the controller.
|
||||
* - `factory` - `{string|function}`: If `string` then it is an alias for a service.
|
||||
* Otherwise if function, then it is {@link auto.$injector#invoke injected}
|
||||
* and the return value is treated as the dependency. If the result is a promise, it is
|
||||
* resolved before its value is injected into the controller. Be aware that
|
||||
* `ngRoute.$routeParams` will still refer to the previous route within these resolve
|
||||
* functions. Use `$route.current.params` to access the new route parameters, instead.
|
||||
*
|
||||
* - `redirectTo` – {(string|function())=} – value to update
|
||||
* {@link ng.$location $location} path with and trigger route redirection.
|
||||
*
|
||||
* If `redirectTo` is a function, it will be called with the following parameters:
|
||||
*
|
||||
* - `{Object.<string>}` - route parameters extracted from the current
|
||||
* `$location.path()` by applying the current route templateUrl.
|
||||
* - `{string}` - current `$location.path()`
|
||||
* - `{Object}` - current `$location.search()`
|
||||
*
|
||||
* The custom `redirectTo` function is expected to return a string which will be used
|
||||
* to update `$location.path()` and `$location.search()`.
|
||||
*
|
||||
* - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()`
|
||||
* or `$location.hash()` changes.
|
||||
*
|
||||
* If the option is set to `false` and url in the browser changes, then
|
||||
* `$routeUpdate` event is broadcasted on the root scope.
|
||||
*
|
||||
* - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive
|
||||
*
|
||||
* If the option is set to `true`, then the particular route can be matched without being
|
||||
* case sensitive
|
||||
*
|
||||
* @returns {Object} self
|
||||
*
|
||||
* @description
|
||||
* Adds a new route definition to the `$route` service.
|
||||
*/
|
||||
this.when = function(path, route) {
|
||||
routes[path] = angular.extend(
|
||||
{reloadOnSearch: true},
|
||||
route,
|
||||
path && pathRegExp(path, route)
|
||||
);
|
||||
|
||||
// create redirection for trailing slashes
|
||||
if (path) {
|
||||
var redirectPath = (path[path.length-1] == '/')
|
||||
? path.substr(0, path.length-1)
|
||||
: path +'/';
|
||||
|
||||
routes[redirectPath] = angular.extend(
|
||||
{redirectTo: path},
|
||||
pathRegExp(redirectPath, route)
|
||||
);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param path {string} path
|
||||
* @param opts {Object} options
|
||||
* @return {?Object}
|
||||
*
|
||||
* @description
|
||||
* Normalizes the given path, returning a regular expression
|
||||
* and the original path.
|
||||
*
|
||||
* Inspired by pathRexp in visionmedia/express/lib/utils.js.
|
||||
*/
|
||||
function pathRegExp(path, opts) {
|
||||
var insensitive = opts.caseInsensitiveMatch,
|
||||
ret = {
|
||||
originalPath: path,
|
||||
regexp: path
|
||||
},
|
||||
keys = ret.keys = [];
|
||||
|
||||
path = path
|
||||
.replace(/([().])/g, '\\$1')
|
||||
.replace(/(\/)?:(\w+)([\?\*])?/g, function(_, slash, key, option){
|
||||
var optional = option === '?' ? option : null;
|
||||
var star = option === '*' ? option : null;
|
||||
keys.push({ name: key, optional: !!optional });
|
||||
slash = slash || '';
|
||||
return ''
|
||||
+ (optional ? '' : slash)
|
||||
+ '(?:'
|
||||
+ (optional ? slash : '')
|
||||
+ (star && '(.+?)' || '([^/]+)')
|
||||
+ (optional || '')
|
||||
+ ')'
|
||||
+ (optional || '');
|
||||
})
|
||||
.replace(/([\/$\*])/g, '\\$1');
|
||||
|
||||
ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : '');
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $routeProvider#otherwise
|
||||
*
|
||||
* @description
|
||||
* Sets route definition that will be used on route change when no other route definition
|
||||
* is matched.
|
||||
*
|
||||
* @param {Object} params Mapping information to be assigned to `$route.current`.
|
||||
* @returns {Object} self
|
||||
*/
|
||||
this.otherwise = function(params) {
|
||||
this.when(null, params);
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
this.$get = ['$rootScope',
|
||||
'$location',
|
||||
'$routeParams',
|
||||
'$q',
|
||||
'$injector',
|
||||
'$http',
|
||||
'$templateCache',
|
||||
'$sce',
|
||||
function($rootScope, $location, $routeParams, $q, $injector, $http, $templateCache, $sce) {
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $route
|
||||
* @requires $location
|
||||
* @requires $routeParams
|
||||
*
|
||||
* @property {Object} current Reference to the current route definition.
|
||||
* The route definition contains:
|
||||
*
|
||||
* - `controller`: The controller constructor as define in route definition.
|
||||
* - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for
|
||||
* controller instantiation. The `locals` contain
|
||||
* the resolved values of the `resolve` map. Additionally the `locals` also contain:
|
||||
*
|
||||
* - `$scope` - The current route scope.
|
||||
* - `$template` - The current route template HTML.
|
||||
*
|
||||
* @property {Object} routes Object with all route configuration Objects as its properties.
|
||||
*
|
||||
* @description
|
||||
* `$route` is used for deep-linking URLs to controllers and views (HTML partials).
|
||||
* It watches `$location.url()` and tries to map the path to an existing route definition.
|
||||
*
|
||||
* Requires the {@link ngRoute `ngRoute`} module to be installed.
|
||||
*
|
||||
* You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API.
|
||||
*
|
||||
* The `$route` service is typically used in conjunction with the
|
||||
* {@link ngRoute.directive:ngView `ngView`} directive and the
|
||||
* {@link ngRoute.$routeParams `$routeParams`} service.
|
||||
*
|
||||
* @example
|
||||
* This example shows how changing the URL hash causes the `$route` to match a route against the
|
||||
* URL, and the `ngView` pulls in the partial.
|
||||
*
|
||||
* Note that this example is using {@link ng.directive:script inlined templates}
|
||||
* to get it working on jsfiddle as well.
|
||||
*
|
||||
* <example name="$route-service" module="ngRouteExample"
|
||||
* deps="angular-route.js" fixBase="true">
|
||||
* <file name="index.html">
|
||||
* <div ng-controller="MainController">
|
||||
* Choose:
|
||||
* <a href="Book/Moby">Moby</a> |
|
||||
* <a href="Book/Moby/ch/1">Moby: Ch1</a> |
|
||||
* <a href="Book/Gatsby">Gatsby</a> |
|
||||
* <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
|
||||
* <a href="Book/Scarlet">Scarlet Letter</a><br/>
|
||||
*
|
||||
* <div ng-view></div>
|
||||
*
|
||||
* <hr />
|
||||
*
|
||||
* <pre>$location.path() = {{$location.path()}}</pre>
|
||||
* <pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre>
|
||||
* <pre>$route.current.params = {{$route.current.params}}</pre>
|
||||
* <pre>$route.current.scope.name = {{$route.current.scope.name}}</pre>
|
||||
* <pre>$routeParams = {{$routeParams}}</pre>
|
||||
* </div>
|
||||
* </file>
|
||||
*
|
||||
* <file name="book.html">
|
||||
* controller: {{name}}<br />
|
||||
* Book Id: {{params.bookId}}<br />
|
||||
* </file>
|
||||
*
|
||||
* <file name="chapter.html">
|
||||
* controller: {{name}}<br />
|
||||
* Book Id: {{params.bookId}}<br />
|
||||
* Chapter Id: {{params.chapterId}}
|
||||
* </file>
|
||||
*
|
||||
* <file name="script.js">
|
||||
* angular.module('ngRouteExample', ['ngRoute'])
|
||||
*
|
||||
* .controller('MainController', function($scope, $route, $routeParams, $location) {
|
||||
* $scope.$route = $route;
|
||||
* $scope.$location = $location;
|
||||
* $scope.$routeParams = $routeParams;
|
||||
* })
|
||||
*
|
||||
* .controller('BookController', function($scope, $routeParams) {
|
||||
* $scope.name = "BookController";
|
||||
* $scope.params = $routeParams;
|
||||
* })
|
||||
*
|
||||
* .controller('ChapterController', function($scope, $routeParams) {
|
||||
* $scope.name = "ChapterController";
|
||||
* $scope.params = $routeParams;
|
||||
* })
|
||||
*
|
||||
* .config(function($routeProvider, $locationProvider) {
|
||||
* $routeProvider
|
||||
* .when('/Book/:bookId', {
|
||||
* templateUrl: 'book.html',
|
||||
* controller: 'BookController',
|
||||
* resolve: {
|
||||
* // I will cause a 1 second delay
|
||||
* delay: function($q, $timeout) {
|
||||
* var delay = $q.defer();
|
||||
* $timeout(delay.resolve, 1000);
|
||||
* return delay.promise;
|
||||
* }
|
||||
* }
|
||||
* })
|
||||
* .when('/Book/:bookId/ch/:chapterId', {
|
||||
* templateUrl: 'chapter.html',
|
||||
* controller: 'ChapterController'
|
||||
* });
|
||||
*
|
||||
* // configure html5 to get links working on jsfiddle
|
||||
* $locationProvider.html5Mode(true);
|
||||
* });
|
||||
*
|
||||
* </file>
|
||||
*
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* it('should load and compile correct template', function() {
|
||||
* element(by.linkText('Moby: Ch1')).click();
|
||||
* var content = element(by.css('[ng-view]')).getText();
|
||||
* expect(content).toMatch(/controller\: ChapterController/);
|
||||
* expect(content).toMatch(/Book Id\: Moby/);
|
||||
* expect(content).toMatch(/Chapter Id\: 1/);
|
||||
*
|
||||
* element(by.partialLinkText('Scarlet')).click();
|
||||
*
|
||||
* content = element(by.css('[ng-view]')).getText();
|
||||
* expect(content).toMatch(/controller\: BookController/);
|
||||
* expect(content).toMatch(/Book Id\: Scarlet/);
|
||||
* });
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc event
|
||||
* @name $route#$routeChangeStart
|
||||
* @eventType broadcast on root scope
|
||||
* @description
|
||||
* Broadcasted before a route change. At this point the route services starts
|
||||
* resolving all of the dependencies needed for the route change to occur.
|
||||
* Typically this involves fetching the view template as well as any dependencies
|
||||
* defined in `resolve` route property. Once all of the dependencies are resolved
|
||||
* `$routeChangeSuccess` is fired.
|
||||
*
|
||||
* @param {Object} angularEvent Synthetic event object.
|
||||
* @param {Route} next Future route information.
|
||||
* @param {Route} current Current route information.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc event
|
||||
* @name $route#$routeChangeSuccess
|
||||
* @eventType broadcast on root scope
|
||||
* @description
|
||||
* Broadcasted after a route dependencies are resolved.
|
||||
* {@link ngRoute.directive:ngView ngView} listens for the directive
|
||||
* to instantiate the controller and render the view.
|
||||
*
|
||||
* @param {Object} angularEvent Synthetic event object.
|
||||
* @param {Route} current Current route information.
|
||||
* @param {Route|Undefined} previous Previous route information, or undefined if current is
|
||||
* first route entered.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc event
|
||||
* @name $route#$routeChangeError
|
||||
* @eventType broadcast on root scope
|
||||
* @description
|
||||
* Broadcasted if any of the resolve promises are rejected.
|
||||
*
|
||||
* @param {Object} angularEvent Synthetic event object
|
||||
* @param {Route} current Current route information.
|
||||
* @param {Route} previous Previous route information.
|
||||
* @param {Route} rejection Rejection of the promise. Usually the error of the failed promise.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc event
|
||||
* @name $route#$routeUpdate
|
||||
* @eventType broadcast on root scope
|
||||
* @description
|
||||
*
|
||||
* The `reloadOnSearch` property has been set to false, and we are reusing the same
|
||||
* instance of the Controller.
|
||||
*/
|
||||
|
||||
var forceReload = false,
|
||||
$route = {
|
||||
routes: routes,
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $route#reload
|
||||
*
|
||||
* @description
|
||||
* Causes `$route` service to reload the current route even if
|
||||
* {@link ng.$location $location} hasn't changed.
|
||||
*
|
||||
* As a result of that, {@link ngRoute.directive:ngView ngView}
|
||||
* creates new scope, reinstantiates the controller.
|
||||
*/
|
||||
reload: function() {
|
||||
forceReload = true;
|
||||
$rootScope.$evalAsync(updateRoute);
|
||||
}
|
||||
};
|
||||
|
||||
$rootScope.$on('$locationChangeSuccess', updateRoute);
|
||||
|
||||
return $route;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* @param on {string} current url
|
||||
* @param route {Object} route regexp to match the url against
|
||||
* @return {?Object}
|
||||
*
|
||||
* @description
|
||||
* Check if the route matches the current url.
|
||||
*
|
||||
* Inspired by match in
|
||||
* visionmedia/express/lib/router/router.js.
|
||||
*/
|
||||
function switchRouteMatcher(on, route) {
|
||||
var keys = route.keys,
|
||||
params = {};
|
||||
|
||||
if (!route.regexp) return null;
|
||||
|
||||
var m = route.regexp.exec(on);
|
||||
if (!m) return null;
|
||||
|
||||
for (var i = 1, len = m.length; i < len; ++i) {
|
||||
var key = keys[i - 1];
|
||||
|
||||
var val = 'string' == typeof m[i]
|
||||
? decodeURIComponent(m[i])
|
||||
: m[i];
|
||||
|
||||
if (key && val) {
|
||||
params[key.name] = val;
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
function updateRoute() {
|
||||
var next = parseRoute(),
|
||||
last = $route.current;
|
||||
|
||||
if (next && last && next.$$route === last.$$route
|
||||
&& angular.equals(next.pathParams, last.pathParams)
|
||||
&& !next.reloadOnSearch && !forceReload) {
|
||||
last.params = next.params;
|
||||
angular.copy(last.params, $routeParams);
|
||||
$rootScope.$broadcast('$routeUpdate', last);
|
||||
} else if (next || last) {
|
||||
forceReload = false;
|
||||
$rootScope.$broadcast('$routeChangeStart', next, last);
|
||||
$route.current = next;
|
||||
if (next) {
|
||||
if (next.redirectTo) {
|
||||
if (angular.isString(next.redirectTo)) {
|
||||
$location.path(interpolate(next.redirectTo, next.params)).search(next.params)
|
||||
.replace();
|
||||
} else {
|
||||
$location.url(next.redirectTo(next.pathParams, $location.path(), $location.search()))
|
||||
.replace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$q.when(next).
|
||||
then(function() {
|
||||
if (next) {
|
||||
var locals = angular.extend({}, next.resolve),
|
||||
template, templateUrl;
|
||||
|
||||
angular.forEach(locals, function(value, key) {
|
||||
locals[key] = angular.isString(value) ?
|
||||
$injector.get(value) : $injector.invoke(value, null, null, key);
|
||||
});
|
||||
|
||||
if (angular.isDefined(template = next.template)) {
|
||||
if (angular.isFunction(template)) {
|
||||
template = template(next.params);
|
||||
}
|
||||
} else if (angular.isDefined(templateUrl = next.templateUrl)) {
|
||||
if (angular.isFunction(templateUrl)) {
|
||||
templateUrl = templateUrl(next.params);
|
||||
}
|
||||
templateUrl = $sce.getTrustedResourceUrl(templateUrl);
|
||||
if (angular.isDefined(templateUrl)) {
|
||||
next.loadedTemplateUrl = templateUrl;
|
||||
template = $http.get(templateUrl, {cache: $templateCache}).
|
||||
then(function(response) { return response.data; });
|
||||
}
|
||||
}
|
||||
if (angular.isDefined(template)) {
|
||||
locals['$template'] = template;
|
||||
}
|
||||
return $q.all(locals);
|
||||
}
|
||||
}).
|
||||
// after route change
|
||||
then(function(locals) {
|
||||
if (next == $route.current) {
|
||||
if (next) {
|
||||
next.locals = locals;
|
||||
angular.copy(next.params, $routeParams);
|
||||
}
|
||||
$rootScope.$broadcast('$routeChangeSuccess', next, last);
|
||||
}
|
||||
}, function(error) {
|
||||
if (next == $route.current) {
|
||||
$rootScope.$broadcast('$routeChangeError', next, last, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @returns {Object} the current active route, by matching it against the URL
|
||||
*/
|
||||
function parseRoute() {
|
||||
// Match a route
|
||||
var params, match;
|
||||
angular.forEach(routes, function(route, path) {
|
||||
if (!match && (params = switchRouteMatcher($location.path(), route))) {
|
||||
match = inherit(route, {
|
||||
params: angular.extend({}, $location.search(), params),
|
||||
pathParams: params});
|
||||
match.$$route = route;
|
||||
}
|
||||
});
|
||||
// No route matched; fallback to "otherwise" route
|
||||
return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} interpolation of the redirect path with the parameters
|
||||
*/
|
||||
function interpolate(string, params) {
|
||||
var result = [];
|
||||
angular.forEach((string||'').split(':'), function(segment, i) {
|
||||
if (i === 0) {
|
||||
result.push(segment);
|
||||
} else {
|
||||
var segmentMatch = segment.match(/(\w+)(.*)/);
|
||||
var key = segmentMatch[1];
|
||||
result.push(params[key]);
|
||||
result.push(segmentMatch[2] || '');
|
||||
delete params[key];
|
||||
}
|
||||
});
|
||||
return result.join('');
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
ngRouteModule.provider('$routeParams', $RouteParamsProvider);
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $routeParams
|
||||
* @requires $route
|
||||
*
|
||||
* @description
|
||||
* The `$routeParams` service allows you to retrieve the current set of route parameters.
|
||||
*
|
||||
* Requires the {@link ngRoute `ngRoute`} module to be installed.
|
||||
*
|
||||
* The route parameters are a combination of {@link ng.$location `$location`}'s
|
||||
* {@link ng.$location#search `search()`} and {@link ng.$location#path `path()`}.
|
||||
* The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched.
|
||||
*
|
||||
* In case of parameter name collision, `path` params take precedence over `search` params.
|
||||
*
|
||||
* The service guarantees that the identity of the `$routeParams` object will remain unchanged
|
||||
* (but its properties will likely change) even when a route change occurs.
|
||||
*
|
||||
* Note that the `$routeParams` are only updated *after* a route change completes successfully.
|
||||
* This means that you cannot rely on `$routeParams` being correct in route resolve functions.
|
||||
* Instead you can use `$route.current.params` to access the new route's parameters.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* // Given:
|
||||
* // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
|
||||
* // Route: /Chapter/:chapterId/Section/:sectionId
|
||||
* //
|
||||
* // Then
|
||||
* $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
|
||||
* ```
|
||||
*/
|
||||
function $RouteParamsProvider() {
|
||||
this.$get = function() { return {}; };
|
||||
}
|
||||
|
||||
ngRouteModule.directive('ngView', ngViewFactory);
|
||||
ngRouteModule.directive('ngView', ngViewFillContentFactory);
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngView
|
||||
* @restrict ECA
|
||||
*
|
||||
* @description
|
||||
* # Overview
|
||||
* `ngView` is a directive that complements the {@link ngRoute.$route $route} service by
|
||||
* including the rendered template of the current route into the main layout (`index.html`) file.
|
||||
* Every time the current route changes, the included view changes with it according to the
|
||||
* configuration of the `$route` service.
|
||||
*
|
||||
* Requires the {@link ngRoute `ngRoute`} module to be installed.
|
||||
*
|
||||
* @animations
|
||||
* enter - animation is used to bring new content into the browser.
|
||||
* leave - animation is used to animate existing content away.
|
||||
*
|
||||
* The enter and leave animation occur concurrently.
|
||||
*
|
||||
* @scope
|
||||
* @priority 400
|
||||
* @param {string=} onload Expression to evaluate whenever the view updates.
|
||||
*
|
||||
* @param {string=} autoscroll Whether `ngView` should call {@link ng.$anchorScroll
|
||||
* $anchorScroll} to scroll the viewport after the view is updated.
|
||||
*
|
||||
* - If the attribute is not set, disable scrolling.
|
||||
* - If the attribute is set without value, enable scrolling.
|
||||
* - Otherwise enable scrolling only if the `autoscroll` attribute value evaluated
|
||||
* as an expression yields a truthy value.
|
||||
* @example
|
||||
<example name="ngView-directive" module="ngViewExample"
|
||||
deps="angular-route.js;angular-animate.js"
|
||||
animations="true" fixBase="true">
|
||||
<file name="index.html">
|
||||
<div ng-controller="MainCtrl as main">
|
||||
Choose:
|
||||
<a href="Book/Moby">Moby</a> |
|
||||
<a href="Book/Moby/ch/1">Moby: Ch1</a> |
|
||||
<a href="Book/Gatsby">Gatsby</a> |
|
||||
<a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
|
||||
<a href="Book/Scarlet">Scarlet Letter</a><br/>
|
||||
|
||||
<div class="view-animate-container">
|
||||
<div ng-view class="view-animate"></div>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
<pre>$location.path() = {{main.$location.path()}}</pre>
|
||||
<pre>$route.current.templateUrl = {{main.$route.current.templateUrl}}</pre>
|
||||
<pre>$route.current.params = {{main.$route.current.params}}</pre>
|
||||
<pre>$route.current.scope.name = {{main.$route.current.scope.name}}</pre>
|
||||
<pre>$routeParams = {{main.$routeParams}}</pre>
|
||||
</div>
|
||||
</file>
|
||||
|
||||
<file name="book.html">
|
||||
<div>
|
||||
controller: {{book.name}}<br />
|
||||
Book Id: {{book.params.bookId}}<br />
|
||||
</div>
|
||||
</file>
|
||||
|
||||
<file name="chapter.html">
|
||||
<div>
|
||||
controller: {{chapter.name}}<br />
|
||||
Book Id: {{chapter.params.bookId}}<br />
|
||||
Chapter Id: {{chapter.params.chapterId}}
|
||||
</div>
|
||||
</file>
|
||||
|
||||
<file name="animations.css">
|
||||
.view-animate-container {
|
||||
position:relative;
|
||||
height:100px!important;
|
||||
position:relative;
|
||||
background:white;
|
||||
border:1px solid black;
|
||||
height:40px;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
.view-animate {
|
||||
padding:10px;
|
||||
}
|
||||
|
||||
.view-animate.ng-enter, .view-animate.ng-leave {
|
||||
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
|
||||
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
|
||||
|
||||
display:block;
|
||||
width:100%;
|
||||
border-left:1px solid black;
|
||||
|
||||
position:absolute;
|
||||
top:0;
|
||||
left:0;
|
||||
right:0;
|
||||
bottom:0;
|
||||
padding:10px;
|
||||
}
|
||||
|
||||
.view-animate.ng-enter {
|
||||
left:100%;
|
||||
}
|
||||
.view-animate.ng-enter.ng-enter-active {
|
||||
left:0;
|
||||
}
|
||||
.view-animate.ng-leave.ng-leave-active {
|
||||
left:-100%;
|
||||
}
|
||||
</file>
|
||||
|
||||
<file name="script.js">
|
||||
angular.module('ngViewExample', ['ngRoute', 'ngAnimate'])
|
||||
.config(['$routeProvider', '$locationProvider',
|
||||
function($routeProvider, $locationProvider) {
|
||||
$routeProvider
|
||||
.when('/Book/:bookId', {
|
||||
templateUrl: 'book.html',
|
||||
controller: 'BookCtrl',
|
||||
controllerAs: 'book'
|
||||
})
|
||||
.when('/Book/:bookId/ch/:chapterId', {
|
||||
templateUrl: 'chapter.html',
|
||||
controller: 'ChapterCtrl',
|
||||
controllerAs: 'chapter'
|
||||
});
|
||||
|
||||
// configure html5 to get links working on jsfiddle
|
||||
$locationProvider.html5Mode(true);
|
||||
}])
|
||||
.controller('MainCtrl', ['$route', '$routeParams', '$location',
|
||||
function($route, $routeParams, $location) {
|
||||
this.$route = $route;
|
||||
this.$location = $location;
|
||||
this.$routeParams = $routeParams;
|
||||
}])
|
||||
.controller('BookCtrl', ['$routeParams', function($routeParams) {
|
||||
this.name = "BookCtrl";
|
||||
this.params = $routeParams;
|
||||
}])
|
||||
.controller('ChapterCtrl', ['$routeParams', function($routeParams) {
|
||||
this.name = "ChapterCtrl";
|
||||
this.params = $routeParams;
|
||||
}]);
|
||||
|
||||
</file>
|
||||
|
||||
<file name="protractor.js" type="protractor">
|
||||
it('should load and compile correct template', function() {
|
||||
element(by.linkText('Moby: Ch1')).click();
|
||||
var content = element(by.css('[ng-view]')).getText();
|
||||
expect(content).toMatch(/controller\: ChapterCtrl/);
|
||||
expect(content).toMatch(/Book Id\: Moby/);
|
||||
expect(content).toMatch(/Chapter Id\: 1/);
|
||||
|
||||
element(by.partialLinkText('Scarlet')).click();
|
||||
|
||||
content = element(by.css('[ng-view]')).getText();
|
||||
expect(content).toMatch(/controller\: BookCtrl/);
|
||||
expect(content).toMatch(/Book Id\: Scarlet/);
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc event
|
||||
* @name ngView#$viewContentLoaded
|
||||
* @eventType emit on the current ngView scope
|
||||
* @description
|
||||
* Emitted every time the ngView content is reloaded.
|
||||
*/
|
||||
ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate'];
|
||||
function ngViewFactory( $route, $anchorScroll, $animate) {
|
||||
return {
|
||||
restrict: 'ECA',
|
||||
terminal: true,
|
||||
priority: 400,
|
||||
transclude: 'element',
|
||||
link: function(scope, $element, attr, ctrl, $transclude) {
|
||||
var currentScope,
|
||||
currentElement,
|
||||
previousElement,
|
||||
autoScrollExp = attr.autoscroll,
|
||||
onloadExp = attr.onload || '';
|
||||
|
||||
scope.$on('$routeChangeSuccess', update);
|
||||
update();
|
||||
|
||||
function cleanupLastView() {
|
||||
if(previousElement) {
|
||||
previousElement.remove();
|
||||
previousElement = null;
|
||||
}
|
||||
if(currentScope) {
|
||||
currentScope.$destroy();
|
||||
currentScope = null;
|
||||
}
|
||||
if(currentElement) {
|
||||
$animate.leave(currentElement, function() {
|
||||
previousElement = null;
|
||||
});
|
||||
previousElement = currentElement;
|
||||
currentElement = null;
|
||||
}
|
||||
}
|
||||
|
||||
function update() {
|
||||
var locals = $route.current && $route.current.locals,
|
||||
template = locals && locals.$template;
|
||||
|
||||
if (angular.isDefined(template)) {
|
||||
var newScope = scope.$new();
|
||||
var current = $route.current;
|
||||
|
||||
// Note: This will also link all children of ng-view that were contained in the original
|
||||
// html. If that content contains controllers, ... they could pollute/change the scope.
|
||||
// However, using ng-view on an element with additional content does not make sense...
|
||||
// Note: We can't remove them in the cloneAttchFn of $transclude as that
|
||||
// function is called before linking the content, which would apply child
|
||||
// directives to non existing elements.
|
||||
var clone = $transclude(newScope, function(clone) {
|
||||
$animate.enter(clone, null, currentElement || $element, function onNgViewEnter () {
|
||||
if (angular.isDefined(autoScrollExp)
|
||||
&& (!autoScrollExp || scope.$eval(autoScrollExp))) {
|
||||
$anchorScroll();
|
||||
}
|
||||
});
|
||||
cleanupLastView();
|
||||
});
|
||||
|
||||
currentElement = clone;
|
||||
currentScope = current.scope = newScope;
|
||||
currentScope.$emit('$viewContentLoaded');
|
||||
currentScope.$eval(onloadExp);
|
||||
} else {
|
||||
cleanupLastView();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// This directive is called during the $transclude call of the first `ngView` directive.
|
||||
// It will replace and compile the content of the element with the loaded template.
|
||||
// We need this directive so that the element content is already filled when
|
||||
// the link function of another directive on the same element as ngView
|
||||
// is called.
|
||||
ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route'];
|
||||
function ngViewFillContentFactory($compile, $controller, $route) {
|
||||
return {
|
||||
restrict: 'ECA',
|
||||
priority: -400,
|
||||
link: function(scope, $element) {
|
||||
var current = $route.current,
|
||||
locals = current.locals;
|
||||
|
||||
$element.html(locals.$template);
|
||||
|
||||
var link = $compile($element.contents());
|
||||
|
||||
if (current.controller) {
|
||||
locals.$scope = scope;
|
||||
var controller = $controller(current.controller, locals);
|
||||
if (current.controllerAs) {
|
||||
scope[current.controllerAs] = controller;
|
||||
}
|
||||
$element.data('$ngControllerController', controller);
|
||||
$element.children().data('$ngControllerController', controller);
|
||||
}
|
||||
|
||||
link(scope);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
})(window, window.angular);
|
||||
@@ -0,0 +1,941 @@
|
||||
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
||||
// Generated by CoffeeScript 1.7.1
|
||||
(function() {
|
||||
var CastAway, CustomReceiver, EventEmitter, MediaControls, Session,
|
||||
__hasProp = {}.hasOwnProperty,
|
||||
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
||||
__slice = [].slice;
|
||||
|
||||
EventEmitter = require('./event_emitter');
|
||||
|
||||
Session = require('./session');
|
||||
|
||||
MediaControls = require('./media-controls');
|
||||
|
||||
CustomReceiver = require('./custom-receiver');
|
||||
|
||||
CastAway = (function(_super) {
|
||||
__extends(CastAway, _super);
|
||||
|
||||
function CastAway(_arg) {
|
||||
var _ref;
|
||||
_ref = _arg != null ? _arg : {}, this.applicationID = _ref.applicationID, this.namespace = _ref.namespace;
|
||||
if (!((typeof chrome !== "undefined" && chrome !== null ? chrome.cast : void 0) || cast)) {
|
||||
throw "chrome.cast namespace not found";
|
||||
}
|
||||
this.cast = (typeof chrome !== "undefined" && chrome !== null ? chrome.cast : void 0) || cast;
|
||||
}
|
||||
|
||||
CastAway.prototype.initialize = function(cb) {
|
||||
var initializeCastApi, intervalId;
|
||||
initializeCastApi = (function(_this) {
|
||||
return function() {
|
||||
var apiConfig, app, error, sessionRequest, success;
|
||||
app = _this.applicationID || _this.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID;
|
||||
sessionRequest = new _this.cast.SessionRequest(app);
|
||||
apiConfig = new _this.cast.ApiConfig(sessionRequest, function() {
|
||||
var data;
|
||||
data = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
||||
return _this.sessionListener.apply(_this, data);
|
||||
}, function() {
|
||||
var data;
|
||||
data = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
||||
return _this.receiverListener.apply(_this, data);
|
||||
});
|
||||
success = function(data) {
|
||||
return cb(null, data);
|
||||
};
|
||||
error = function(err) {
|
||||
return cb(err);
|
||||
};
|
||||
return _this.cast.initialize(apiConfig, success, error);
|
||||
};
|
||||
})(this);
|
||||
return intervalId = setInterval((function() {
|
||||
if (chrome.cast && chrome.cast.isAvailable && chrome.cast.media && chrome.cast.SessionRequest) {
|
||||
clearInterval(intervalId);
|
||||
return initializeCastApi();
|
||||
}
|
||||
}), 15);
|
||||
};
|
||||
|
||||
CastAway.prototype.receive = function(config) {
|
||||
if (config == null) {
|
||||
config = {};
|
||||
}
|
||||
this.receiver = new CustomReceiver(config, this);
|
||||
this.receiver.start();
|
||||
return this.receiver;
|
||||
};
|
||||
|
||||
CastAway.prototype.sessionListener = function(session) {
|
||||
if (session.media.length !== 0) {
|
||||
this.currentSession = session;
|
||||
session.addUpdateListener(this.sessionUpdateListener);
|
||||
return this.emit('existingMediaFound', new Session(this.currentSession, this), new MediaControls(this.currentSession.media[0], this));
|
||||
}
|
||||
};
|
||||
|
||||
CastAway.prototype.receiverListener = function(receiver) {
|
||||
var available, state;
|
||||
available = this.cast.ReceiverAvailability.AVAILABLE;
|
||||
state = receiver === available ? 'available' : 'unavailable';
|
||||
return this.emit("receivers:" + state);
|
||||
};
|
||||
|
||||
CastAway.prototype.sessionUpdateListener = function(isAlive) {
|
||||
if (!isAlive) {
|
||||
return this.currentSession = null;
|
||||
}
|
||||
};
|
||||
|
||||
CastAway.prototype.requestSession = function(cb) {
|
||||
var onError, onSuccess;
|
||||
onSuccess = (function(_this) {
|
||||
return function(session) {
|
||||
return cb(null, new Session(session, _this));
|
||||
};
|
||||
})(this);
|
||||
onError = function(err) {
|
||||
return cb(err);
|
||||
};
|
||||
return this.cast.requestSession(onSuccess, onError);
|
||||
};
|
||||
|
||||
return CastAway;
|
||||
|
||||
})(EventEmitter);
|
||||
|
||||
window.CastAway = CastAway;
|
||||
|
||||
}).call(this);
|
||||
|
||||
},{"./custom-receiver":2,"./event_emitter":3,"./media-controls":4,"./session":5}],2:[function(require,module,exports){
|
||||
// Generated by CoffeeScript 1.7.1
|
||||
(function() {
|
||||
var CustomReceiver, DEFAULT_MAX_INACTIVITY, DEFAULT_STATUS_TEXT, EventEmitter,
|
||||
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
|
||||
__hasProp = {}.hasOwnProperty,
|
||||
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
|
||||
|
||||
EventEmitter = require("./event_emitter");
|
||||
|
||||
DEFAULT_STATUS_TEXT = "Ready";
|
||||
|
||||
DEFAULT_MAX_INACTIVITY = 60;
|
||||
|
||||
module.exports = CustomReceiver = (function(_super) {
|
||||
__extends(CustomReceiver, _super);
|
||||
|
||||
function CustomReceiver(config, castAway) {
|
||||
this.castAway = castAway;
|
||||
this.onMessage = __bind(this.onMessage, this);
|
||||
this.namespace = this.castAway.namespace || "urn:x-cast:json";
|
||||
this.config = new cast.receiver.CastReceiverManager.Config();
|
||||
this.config.statusText = config.statusText || DEFAULT_STATUS_TEXT;
|
||||
this.config.maxInactivity = config.maxInactivity || DEFAULT_MAX_INACTIVITY;
|
||||
}
|
||||
|
||||
CustomReceiver.prototype.start = function() {
|
||||
var bus, manager;
|
||||
manager = cast.receiver.CastReceiverManager.getInstance();
|
||||
bus = manager.getCastMessageBus(this.namespace);
|
||||
bus.onMessage = this.onMessage;
|
||||
return manager.start(this.config);
|
||||
};
|
||||
|
||||
CustomReceiver.prototype.onMessage = function(event) {
|
||||
var data, msgName, payload;
|
||||
data = JSON.parse(event.data);
|
||||
msgName = data._name;
|
||||
payload = data._payload;
|
||||
return this.emit(msgName, payload);
|
||||
};
|
||||
|
||||
return CustomReceiver;
|
||||
|
||||
})(EventEmitter);
|
||||
|
||||
}).call(this);
|
||||
|
||||
},{"./event_emitter":3}],3:[function(require,module,exports){
|
||||
/*!
|
||||
* EventEmitter v4.2.7 - git.io/ee
|
||||
* Oliver Caldwell
|
||||
* MIT license
|
||||
* @preserve
|
||||
*/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Class for managing events.
|
||||
* Can be extended to provide event functionality in other classes.
|
||||
*
|
||||
* @class EventEmitter Manages event registering and emitting.
|
||||
*/
|
||||
function EventEmitter() {}
|
||||
|
||||
// Shortcuts to improve speed and size
|
||||
var proto = EventEmitter.prototype;
|
||||
var exports = this;
|
||||
var originalGlobalValue = exports.EventEmitter;
|
||||
|
||||
/**
|
||||
* Finds the index of the listener for the event in it's storage array.
|
||||
*
|
||||
* @param {Function[]} listeners Array of listeners to search through.
|
||||
* @param {Function} listener Method to look for.
|
||||
* @return {Number} Index of the specified listener, -1 if not found
|
||||
* @api private
|
||||
*/
|
||||
function indexOfListener(listeners, listener) {
|
||||
var i = listeners.length;
|
||||
while (i--) {
|
||||
if (listeners[i].listener === listener) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias a method while keeping the context correct, to allow for overwriting of target method.
|
||||
*
|
||||
* @param {String} name The name of the target method.
|
||||
* @return {Function} The aliased method
|
||||
* @api private
|
||||
*/
|
||||
function alias(name) {
|
||||
return function aliasClosure() {
|
||||
return this[name].apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the listener array for the specified event.
|
||||
* Will initialise the event object and listener arrays if required.
|
||||
* Will return an object if you use a regex search. The object contains keys for each matched event. So /ba[rz]/ might return an object containing bar and baz. But only if you have either defined them with defineEvent or added some listeners to them.
|
||||
* Each property in the object response is an array of listener functions.
|
||||
*
|
||||
* @param {String|RegExp} evt Name of the event to return the listeners from.
|
||||
* @return {Function[]|Object} All listener functions for the event.
|
||||
*/
|
||||
proto.getListeners = function getListeners(evt) {
|
||||
var events = this._getEvents();
|
||||
var response;
|
||||
var key;
|
||||
|
||||
// Return a concatenated array of all matching events if
|
||||
// the selector is a regular expression.
|
||||
if (evt instanceof RegExp) {
|
||||
response = {};
|
||||
for (key in events) {
|
||||
if (events.hasOwnProperty(key) && evt.test(key)) {
|
||||
response[key] = events[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
response = events[evt] || (events[evt] = []);
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a list of listener objects and flattens it into a list of listener functions.
|
||||
*
|
||||
* @param {Object[]} listeners Raw listener objects.
|
||||
* @return {Function[]} Just the listener functions.
|
||||
*/
|
||||
proto.flattenListeners = function flattenListeners(listeners) {
|
||||
var flatListeners = [];
|
||||
var i;
|
||||
|
||||
for (i = 0; i < listeners.length; i += 1) {
|
||||
flatListeners.push(listeners[i].listener);
|
||||
}
|
||||
|
||||
return flatListeners;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches the requested listeners via getListeners but will always return the results inside an object. This is mainly for internal use but others may find it useful.
|
||||
*
|
||||
* @param {String|RegExp} evt Name of the event to return the listeners from.
|
||||
* @return {Object} All listener functions for an event in an object.
|
||||
*/
|
||||
proto.getListenersAsObject = function getListenersAsObject(evt) {
|
||||
var listeners = this.getListeners(evt);
|
||||
var response;
|
||||
|
||||
if (listeners instanceof Array) {
|
||||
response = {};
|
||||
response[evt] = listeners;
|
||||
}
|
||||
|
||||
return response || listeners;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a listener function to the specified event.
|
||||
* The listener will not be added if it is a duplicate.
|
||||
* If the listener returns true then it will be removed after it is called.
|
||||
* If you pass a regular expression as the event name then the listener will be added to all events that match it.
|
||||
*
|
||||
* @param {String|RegExp} evt Name of the event to attach the listener to.
|
||||
* @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
|
||||
* @return {Object} Current instance of EventEmitter for chaining.
|
||||
*/
|
||||
proto.addListener = function addListener(evt, listener) {
|
||||
var listeners = this.getListenersAsObject(evt);
|
||||
var listenerIsWrapped = typeof listener === 'object';
|
||||
var key;
|
||||
|
||||
for (key in listeners) {
|
||||
if (listeners.hasOwnProperty(key) && indexOfListener(listeners[key], listener) === -1) {
|
||||
listeners[key].push(listenerIsWrapped ? listener : {
|
||||
listener: listener,
|
||||
once: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Alias of addListener
|
||||
*/
|
||||
proto.on = alias('addListener');
|
||||
|
||||
/**
|
||||
* Semi-alias of addListener. It will add a listener that will be
|
||||
* automatically removed after it's first execution.
|
||||
*
|
||||
* @param {String|RegExp} evt Name of the event to attach the listener to.
|
||||
* @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
|
||||
* @return {Object} Current instance of EventEmitter for chaining.
|
||||
*/
|
||||
proto.addOnceListener = function addOnceListener(evt, listener) {
|
||||
return this.addListener(evt, {
|
||||
listener: listener,
|
||||
once: true
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Alias of addOnceListener.
|
||||
*/
|
||||
proto.once = alias('addOnceListener');
|
||||
|
||||
/**
|
||||
* Defines an event name. This is required if you want to use a regex to add a listener to multiple events at once. If you don't do this then how do you expect it to know what event to add to? Should it just add to every possible match for a regex? No. That is scary and bad.
|
||||
* You need to tell it what event names should be matched by a regex.
|
||||
*
|
||||
* @param {String} evt Name of the event to create.
|
||||
* @return {Object} Current instance of EventEmitter for chaining.
|
||||
*/
|
||||
proto.defineEvent = function defineEvent(evt) {
|
||||
this.getListeners(evt);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Uses defineEvent to define multiple events.
|
||||
*
|
||||
* @param {String[]} evts An array of event names to define.
|
||||
* @return {Object} Current instance of EventEmitter for chaining.
|
||||
*/
|
||||
proto.defineEvents = function defineEvents(evts) {
|
||||
for (var i = 0; i < evts.length; i += 1) {
|
||||
this.defineEvent(evts[i]);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a listener function from the specified event.
|
||||
* When passed a regular expression as the event name, it will remove the listener from all events that match it.
|
||||
*
|
||||
* @param {String|RegExp} evt Name of the event to remove the listener from.
|
||||
* @param {Function} listener Method to remove from the event.
|
||||
* @return {Object} Current instance of EventEmitter for chaining.
|
||||
*/
|
||||
proto.removeListener = function removeListener(evt, listener) {
|
||||
var listeners = this.getListenersAsObject(evt);
|
||||
var index;
|
||||
var key;
|
||||
|
||||
for (key in listeners) {
|
||||
if (listeners.hasOwnProperty(key)) {
|
||||
index = indexOfListener(listeners[key], listener);
|
||||
|
||||
if (index !== -1) {
|
||||
listeners[key].splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Alias of removeListener
|
||||
*/
|
||||
proto.off = alias('removeListener');
|
||||
|
||||
/**
|
||||
* Adds listeners in bulk using the manipulateListeners method.
|
||||
* If you pass an object as the second argument you can add to multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. You can also pass it an event name and an array of listeners to be added.
|
||||
* You can also pass it a regular expression to add the array of listeners to all events that match it.
|
||||
* Yeah, this function does quite a bit. That's probably a bad thing.
|
||||
*
|
||||
* @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add to multiple events at once.
|
||||
* @param {Function[]} [listeners] An optional array of listener functions to add.
|
||||
* @return {Object} Current instance of EventEmitter for chaining.
|
||||
*/
|
||||
proto.addListeners = function addListeners(evt, listeners) {
|
||||
// Pass through to manipulateListeners
|
||||
return this.manipulateListeners(false, evt, listeners);
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes listeners in bulk using the manipulateListeners method.
|
||||
* If you pass an object as the second argument you can remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays.
|
||||
* You can also pass it an event name and an array of listeners to be removed.
|
||||
* You can also pass it a regular expression to remove the listeners from all events that match it.
|
||||
*
|
||||
* @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to remove from multiple events at once.
|
||||
* @param {Function[]} [listeners] An optional array of listener functions to remove.
|
||||
* @return {Object} Current instance of EventEmitter for chaining.
|
||||
*/
|
||||
proto.removeListeners = function removeListeners(evt, listeners) {
|
||||
// Pass through to manipulateListeners
|
||||
return this.manipulateListeners(true, evt, listeners);
|
||||
};
|
||||
|
||||
/**
|
||||
* Edits listeners in bulk. The addListeners and removeListeners methods both use this to do their job. You should really use those instead, this is a little lower level.
|
||||
* The first argument will determine if the listeners are removed (true) or added (false).
|
||||
* If you pass an object as the second argument you can add/remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays.
|
||||
* You can also pass it an event name and an array of listeners to be added/removed.
|
||||
* You can also pass it a regular expression to manipulate the listeners of all events that match it.
|
||||
*
|
||||
* @param {Boolean} remove True if you want to remove listeners, false if you want to add.
|
||||
* @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add/remove from multiple events at once.
|
||||
* @param {Function[]} [listeners] An optional array of listener functions to add/remove.
|
||||
* @return {Object} Current instance of EventEmitter for chaining.
|
||||
*/
|
||||
proto.manipulateListeners = function manipulateListeners(remove, evt, listeners) {
|
||||
var i;
|
||||
var value;
|
||||
var single = remove ? this.removeListener : this.addListener;
|
||||
var multiple = remove ? this.removeListeners : this.addListeners;
|
||||
|
||||
// If evt is an object then pass each of it's properties to this method
|
||||
if (typeof evt === 'object' && !(evt instanceof RegExp)) {
|
||||
for (i in evt) {
|
||||
if (evt.hasOwnProperty(i) && (value = evt[i])) {
|
||||
// Pass the single listener straight through to the singular method
|
||||
if (typeof value === 'function') {
|
||||
single.call(this, i, value);
|
||||
}
|
||||
else {
|
||||
// Otherwise pass back to the multiple function
|
||||
multiple.call(this, i, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// So evt must be a string
|
||||
// And listeners must be an array of listeners
|
||||
// Loop over it and pass each one to the multiple method
|
||||
i = listeners.length;
|
||||
while (i--) {
|
||||
single.call(this, evt, listeners[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes all listeners from a specified event.
|
||||
* If you do not specify an event then all listeners will be removed.
|
||||
* That means every event will be emptied.
|
||||
* You can also pass a regex to remove all events that match it.
|
||||
*
|
||||
* @param {String|RegExp} [evt] Optional name of the event to remove all listeners for. Will remove from every event if not passed.
|
||||
* @return {Object} Current instance of EventEmitter for chaining.
|
||||
*/
|
||||
proto.removeEvent = function removeEvent(evt) {
|
||||
var type = typeof evt;
|
||||
var events = this._getEvents();
|
||||
var key;
|
||||
|
||||
// Remove different things depending on the state of evt
|
||||
if (type === 'string') {
|
||||
// Remove all listeners for the specified event
|
||||
delete events[evt];
|
||||
}
|
||||
else if (evt instanceof RegExp) {
|
||||
// Remove all events matching the regex.
|
||||
for (key in events) {
|
||||
if (events.hasOwnProperty(key) && evt.test(key)) {
|
||||
delete events[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Remove all listeners in all events
|
||||
delete this._events;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Alias of removeEvent.
|
||||
*
|
||||
* Added to mirror the node API.
|
||||
*/
|
||||
proto.removeAllListeners = alias('removeEvent');
|
||||
|
||||
/**
|
||||
* Emits an event of your choice.
|
||||
* When emitted, every listener attached to that event will be executed.
|
||||
* If you pass the optional argument array then those arguments will be passed to every listener upon execution.
|
||||
* Because it uses `apply`, your array of arguments will be passed as if you wrote them out separately.
|
||||
* So they will not arrive within the array on the other side, they will be separate.
|
||||
* You can also pass a regular expression to emit to all events that match it.
|
||||
*
|
||||
* @param {String|RegExp} evt Name of the event to emit and execute listeners for.
|
||||
* @param {Array} [args] Optional array of arguments to be passed to each listener.
|
||||
* @return {Object} Current instance of EventEmitter for chaining.
|
||||
*/
|
||||
proto.emitEvent = function emitEvent(evt, args) {
|
||||
var listeners = this.getListenersAsObject(evt);
|
||||
var listener;
|
||||
var i;
|
||||
var key;
|
||||
var response;
|
||||
|
||||
for (key in listeners) {
|
||||
if (listeners.hasOwnProperty(key)) {
|
||||
i = listeners[key].length;
|
||||
|
||||
while (i--) {
|
||||
// If the listener returns true then it shall be removed from the event
|
||||
// The function is executed either with a basic call or an apply if there is an args array
|
||||
listener = listeners[key][i];
|
||||
|
||||
if (listener.once === true) {
|
||||
this.removeListener(evt, listener.listener);
|
||||
}
|
||||
|
||||
response = listener.listener.apply(this, args || []);
|
||||
|
||||
if (response === this._getOnceReturnValue()) {
|
||||
this.removeListener(evt, listener.listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Alias of emitEvent
|
||||
*/
|
||||
proto.trigger = alias('emitEvent');
|
||||
|
||||
/**
|
||||
* Subtly different from emitEvent in that it will pass its arguments on to the listeners, as opposed to taking a single array of arguments to pass on.
|
||||
* As with emitEvent, you can pass a regex in place of the event name to emit to all events that match it.
|
||||
*
|
||||
* @param {String|RegExp} evt Name of the event to emit and execute listeners for.
|
||||
* @param {...*} Optional additional arguments to be passed to each listener.
|
||||
* @return {Object} Current instance of EventEmitter for chaining.
|
||||
*/
|
||||
proto.emit = function emit(evt) {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
return this.emitEvent(evt, args);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the current value to check against when executing listeners. If a
|
||||
* listeners return value matches the one set here then it will be removed
|
||||
* after execution. This value defaults to true.
|
||||
*
|
||||
* @param {*} value The new value to check for when executing listeners.
|
||||
* @return {Object} Current instance of EventEmitter for chaining.
|
||||
*/
|
||||
proto.setOnceReturnValue = function setOnceReturnValue(value) {
|
||||
this._onceReturnValue = value;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches the current value to check against when executing listeners. If
|
||||
* the listeners return value matches this one then it should be removed
|
||||
* automatically. It will return true by default.
|
||||
*
|
||||
* @return {*|Boolean} The current value to check for or the default, true.
|
||||
* @api private
|
||||
*/
|
||||
proto._getOnceReturnValue = function _getOnceReturnValue() {
|
||||
if (this.hasOwnProperty('_onceReturnValue')) {
|
||||
return this._onceReturnValue;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches the events object and creates one if required.
|
||||
*
|
||||
* @return {Object} The events storage object.
|
||||
* @api private
|
||||
*/
|
||||
proto._getEvents = function _getEvents() {
|
||||
return this._events || (this._events = {});
|
||||
};
|
||||
|
||||
/**
|
||||
* Reverts the global {@link EventEmitter} to its previous value and returns a reference to this version.
|
||||
*
|
||||
* @return {Function} Non conflicting EventEmitter class.
|
||||
*/
|
||||
EventEmitter.noConflict = function noConflict() {
|
||||
exports.EventEmitter = originalGlobalValue;
|
||||
return EventEmitter;
|
||||
};
|
||||
|
||||
// Expose the class either via AMD, CommonJS or the global object
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(function () {
|
||||
return EventEmitter;
|
||||
});
|
||||
}
|
||||
else if (typeof module === 'object' && module.exports){
|
||||
module.exports = EventEmitter;
|
||||
}
|
||||
else {
|
||||
this.EventEmitter = EventEmitter;
|
||||
}
|
||||
module.exports = EventEmitter;
|
||||
}.call(this));
|
||||
|
||||
},{}],4:[function(require,module,exports){
|
||||
// Generated by CoffeeScript 1.7.1
|
||||
(function() {
|
||||
var MediaControls;
|
||||
|
||||
MediaControls = (function() {
|
||||
function MediaControls(session, castAway) {
|
||||
this.session = session;
|
||||
this.castAway = castAway;
|
||||
if (!this.session) {
|
||||
throw "No session passed";
|
||||
}
|
||||
if (!this.castAway.cast) {
|
||||
throw "CastAway instance not found";
|
||||
}
|
||||
this.cast = this.castAway.cast;
|
||||
}
|
||||
|
||||
MediaControls.prototype.play = function(cb) {
|
||||
if (cb == null) {
|
||||
cb = function() {};
|
||||
}
|
||||
return this.session.play(null, function(data) {
|
||||
return cb(null, data);
|
||||
}, function(err) {
|
||||
return cb(err);
|
||||
});
|
||||
};
|
||||
|
||||
MediaControls.prototype.pause = function(cb) {
|
||||
if (cb == null) {
|
||||
cb = function() {};
|
||||
}
|
||||
return this.session.pause(null, function(data) {
|
||||
return cb(null, data);
|
||||
}, function(err) {
|
||||
return cb(err);
|
||||
});
|
||||
};
|
||||
|
||||
MediaControls.prototype.stop = function(cb) {
|
||||
if (cb == null) {
|
||||
cb = function() {};
|
||||
}
|
||||
return this.session.stop(null, function(data) {
|
||||
return cb(null, data);
|
||||
}, function(err) {
|
||||
return cb(err);
|
||||
});
|
||||
};
|
||||
|
||||
MediaControls.prototype.seek = function(time, cb) {
|
||||
var seekRequest;
|
||||
if (cb == null) {
|
||||
cb = function() {};
|
||||
}
|
||||
seekRequest = this.cast.session.SeekRequest(time);
|
||||
return this.session.seek(seekRequest, function(data) {
|
||||
return cb(null, data);
|
||||
}, function(err) {
|
||||
return cb(err);
|
||||
});
|
||||
};
|
||||
|
||||
return MediaControls;
|
||||
|
||||
})();
|
||||
|
||||
module.exports = MediaControls;
|
||||
|
||||
}).call(this);
|
||||
|
||||
},{}],5:[function(require,module,exports){
|
||||
// Generated by CoffeeScript 1.7.1
|
||||
(function() {
|
||||
var EventEmitter, MediaControls, Session, assignMetadata,
|
||||
__hasProp = {}.hasOwnProperty,
|
||||
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
|
||||
|
||||
EventEmitter = require('./event_emitter');
|
||||
|
||||
MediaControls = require('./media-controls');
|
||||
|
||||
Session = (function(_super) {
|
||||
__extends(Session, _super);
|
||||
|
||||
function Session(session, castAway) {
|
||||
this.session = session;
|
||||
this.castAway = castAway;
|
||||
if (!this.castAway.cast) {
|
||||
throw "CastAway instance not found";
|
||||
}
|
||||
this.cast = this.castAway.cast;
|
||||
this.namespace = this.castAway.namespace || "urn:x-cast:json";
|
||||
if (this.session.media[0]) {
|
||||
this.session.media[0].addUpdateListener((function(_this) {
|
||||
return function() {
|
||||
return _this.sessionUpdateListener();
|
||||
};
|
||||
})(this));
|
||||
}
|
||||
}
|
||||
|
||||
Session.prototype.displayName = function() {
|
||||
return this.session.displayName;
|
||||
};
|
||||
|
||||
Session.prototype.receiverName = function() {
|
||||
return this.session.receiver.friendlyName;
|
||||
};
|
||||
|
||||
Session.prototype.send = function(name, payload, cb) {
|
||||
var data, onError, onSuccess;
|
||||
if (payload == null) {
|
||||
payload = {};
|
||||
}
|
||||
if (cb == null) {
|
||||
cb = function() {};
|
||||
}
|
||||
onSuccess = function(data) {
|
||||
return cb(null, data);
|
||||
};
|
||||
onError = function(err) {
|
||||
return cb(err);
|
||||
};
|
||||
data = JSON.stringify({
|
||||
_name: name,
|
||||
_payload: payload
|
||||
});
|
||||
return this.session.sendMessage(this.namespace, data, onSuccess, onError);
|
||||
};
|
||||
|
||||
Session.prototype.load = function(mediaInfo, cb) {
|
||||
var onError, onSuccess, request;
|
||||
if (cb == null) {
|
||||
cb = function() {};
|
||||
}
|
||||
request = new chrome.cast.media.LoadRequest(mediaInfo);
|
||||
onSuccess = (function(_this) {
|
||||
return function(media) {
|
||||
var controls;
|
||||
media.addUpdateListener(function() {
|
||||
return _this.sessionUpdateListener();
|
||||
});
|
||||
controls = new MediaControls(media, _this.castAway);
|
||||
return cb(null, controls);
|
||||
};
|
||||
})(this);
|
||||
onError = function(err) {
|
||||
return cb(err);
|
||||
};
|
||||
return this.session.loadMedia(request, onSuccess, onError);
|
||||
};
|
||||
|
||||
Session.prototype.sessionUpdateListener = function() {
|
||||
var event, media;
|
||||
media = this.session.media[0];
|
||||
event = (function() {
|
||||
switch (media.playerState) {
|
||||
case 'PLAYING':
|
||||
return 'play';
|
||||
case 'PAUSED':
|
||||
return 'pause';
|
||||
case 'STOPPED':
|
||||
return 'stop';
|
||||
case 'SEEKING':
|
||||
return 'seek';
|
||||
case 'ERROR':
|
||||
return 'error';
|
||||
case 'IDLE':
|
||||
return 'idle';
|
||||
case 'LOADING':
|
||||
return 'load';
|
||||
}
|
||||
})();
|
||||
return this.emit(event);
|
||||
};
|
||||
|
||||
Session.prototype.music = function(config, cb) {
|
||||
var mediaInfo, metadata;
|
||||
if (config == null) {
|
||||
config = {};
|
||||
}
|
||||
if (cb == null) {
|
||||
cb = function() {};
|
||||
}
|
||||
if (!config.url) {
|
||||
throw "Url required for music";
|
||||
}
|
||||
if (!config.contentType) {
|
||||
throw "Content-type required for music";
|
||||
}
|
||||
mediaInfo = new chrome.cast.media.MediaInfo(config.url, config.contentType);
|
||||
metadata = new chrome.cast.media.MusicTrackMediaMetadata();
|
||||
metadata.metadataType = chrome.cast.media.MetadataType.MUSIC_TRACK;
|
||||
mediaInfo.metadata = assignMetadata(metadata, config);
|
||||
return this.load(mediaInfo, cb);
|
||||
};
|
||||
|
||||
Session.prototype.tvShow = function(config, cb) {
|
||||
var mediaInfo, metadata;
|
||||
if (config == null) {
|
||||
config = {};
|
||||
}
|
||||
if (cb == null) {
|
||||
cb = function() {};
|
||||
}
|
||||
if (!config.url) {
|
||||
throw "Url required for tv show";
|
||||
}
|
||||
if (!config.contentType) {
|
||||
throw "Content-type required for tv show";
|
||||
}
|
||||
mediaInfo = new chrome.cast.media.MediaInfo(config.url, config.contentType);
|
||||
metadata = new chrome.cast.media.TvShowMediaMetadata();
|
||||
metadata.metadataType = chrome.cast.media.MetadataType.TV_SHOW;
|
||||
mediaInfo.metadata = assignMetadata(metadata, config);
|
||||
return this.load(mediaInfo, cb);
|
||||
};
|
||||
|
||||
Session.prototype.movie = function(config, cb) {
|
||||
var mediaInfo, metadata;
|
||||
if (config == null) {
|
||||
config = {};
|
||||
}
|
||||
if (cb == null) {
|
||||
cb = function() {};
|
||||
}
|
||||
if (!config.url) {
|
||||
throw "Url required for movie";
|
||||
}
|
||||
if (!config.contentType) {
|
||||
throw "Content-type required for movie";
|
||||
}
|
||||
mediaInfo = new chrome.cast.media.MediaInfo(config.url, config.contentType);
|
||||
metadata = new chrome.cast.media.MovieMediaMetadata();
|
||||
metadata.metadataType = chrome.cast.media.MetadataType.MOVIE;
|
||||
mediaInfo.metadata = assignMetadata(metadata, config);
|
||||
return this.load(mediaInfo, cb);
|
||||
};
|
||||
|
||||
Session.prototype.photo = function(config, cb) {
|
||||
var mediaInfo, metadata;
|
||||
if (config == null) {
|
||||
config = {};
|
||||
}
|
||||
if (cb == null) {
|
||||
cb = function() {};
|
||||
}
|
||||
if (!config.url) {
|
||||
throw "Url required for photo";
|
||||
}
|
||||
if (!config.contentType) {
|
||||
throw "Content-type required for photo";
|
||||
}
|
||||
mediaInfo = new chrome.cast.media.MediaInfo(config.url, config.contentType);
|
||||
metadata = new chrome.cast.media.PhotoMediaMetadata();
|
||||
metadata.metadataType = chrome.cast.media.MetadataType.PHOTO;
|
||||
mediaInfo.metadata = assignMetadata(metadata, config);
|
||||
return this.load(mediaInfo, cb);
|
||||
};
|
||||
|
||||
Session.prototype.release = function(cb) {
|
||||
if (cb == null) {
|
||||
cb = function() {};
|
||||
}
|
||||
if (!this.session) {
|
||||
return;
|
||||
}
|
||||
this.emit('release');
|
||||
return this.session.stop((function(data) {
|
||||
return cb(null, data);
|
||||
}), (function(err) {
|
||||
return cb(err);
|
||||
}));
|
||||
};
|
||||
|
||||
return Session;
|
||||
|
||||
})(EventEmitter);
|
||||
|
||||
assignMetadata = function(metadata, config) {
|
||||
var image, key, value;
|
||||
for (key in config) {
|
||||
value = config[key];
|
||||
if (key === 'images') {
|
||||
value = (function() {
|
||||
var _i, _len, _results;
|
||||
_results = [];
|
||||
for (_i = 0, _len = value.length; _i < _len; _i++) {
|
||||
image = value[_i];
|
||||
_results.push(new chrome.cast.Image(image));
|
||||
}
|
||||
return _results;
|
||||
})();
|
||||
}
|
||||
metadata[key] = value;
|
||||
}
|
||||
return metadata;
|
||||
};
|
||||
|
||||
module.exports = Session;
|
||||
|
||||
}).call(this);
|
||||
|
||||
},{"./event_emitter":3,"./media-controls":4}]},{},[1])
|
||||
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html ng-app="GScreen" ng-controller="Receiver">
|
||||
<head>
|
||||
<title>{{ title }}</title>
|
||||
<link rel="stylesheet" href="/css/app.css">
|
||||
<link rel="stylesheet" href="/css/channel.css">
|
||||
<script src="//www.gstatic.com/cast/sdk/libs/receiver/2.0.0/cast_receiver.js"></script>
|
||||
<script src="/js/vendor/castaway.js"></script>
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script src="/js/vendor/jquery.js"></script>
|
||||
<script src="/js/vendor/underscore.js"></script>
|
||||
<script src="/js/vendor/angular.js"></script>
|
||||
<script src="/js/vendor/angular-route.js"></script>
|
||||
<script src="/js/vendor/angular-resource.js"></script>
|
||||
<script src="/js/gscreen.js"></script>
|
||||
</head>
|
||||
<body class="setup">
|
||||
<div class="template-full-screen widescreen">
|
||||
<div class="main-window">
|
||||
<h1 ng-hide="channelUrl">Setting Up Chromecast</h1>
|
||||
<iframe ng-show="channelUrl" src="{{ channelUrl }}"></iframe>
|
||||
|
||||
<div ng-show="chromecast.alert" class="alert {{ chromecast.alert.style }}">{{ chromecast.alert.text }}</div>
|
||||
|
||||
<div ng-show="chromecast.name" class="channel-overlay">{{ chromecast.name }}</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,67 @@
|
||||
<section class="alert" ng-controller="AlertForm">
|
||||
<h2>Push Alert</h2>
|
||||
|
||||
<form ng-submit="onFormSubmit()">
|
||||
|
||||
<div class="input-group">
|
||||
<label>Alert Text</label>
|
||||
<textarea rows="3" class="alert-textarea" maxlength="{{ maxTextLength }}" ng-model="alert.text" ng-trim="false" required></textarea>
|
||||
<span class="char-count">{{ maxTextLength - alert.text.length }}</span>
|
||||
</div>
|
||||
|
||||
<label>Alert Style</label>
|
||||
<div class="alert-styles">
|
||||
<label for="alert-style-default" class="alert-style-label">
|
||||
<input type="radio" name="alert-style" id="alert-style-default" value="default" class="radio" ng-model="alert.style">
|
||||
<div class="alert-sample-default">Default</div>
|
||||
</label>
|
||||
<label for="alert-style-green" class="alert-style-label">
|
||||
<input type="radio" name="alert-style" id="alert-style-green" class="radio" value="green" ng-model="alert.style">
|
||||
<div class="alert-sample-green">Green</div>
|
||||
</label>
|
||||
<label for="alert-style-urgent" class="alert-style-label">
|
||||
<input type="radio" name="alert-style" id="alert-style-urgent" value="urgent" class="radio" ng-model="alert.style">
|
||||
<div class="alert-sample-urgent">Urgent</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label>Alert Duration</label>
|
||||
<div class="alert-duration">
|
||||
<label for="duration-1m">
|
||||
<input type="radio" name="alert-duration" class="radio" value="60" id="duration-1m" ng-model="alert.duration">
|
||||
<span>1m</span>
|
||||
</label>
|
||||
<label for="duration-5m">
|
||||
<input type="radio" name="alert-duration" class="radio" value="300" id="duration-5m" ng-model="alert.duration">
|
||||
<span>5m</span>
|
||||
</label>
|
||||
<label for="duration-30m">
|
||||
<input type="radio" name="alert-duration" class="radio" value="1800" id="duration-30m" ng-model="alert.duration">
|
||||
<span>30m</span>
|
||||
</label>
|
||||
<label for="duration-1h">
|
||||
<input type="radio" name="alert-duration" class="radio" value="3600" id="duration-1h" ng-model="alert.duration">
|
||||
<span>1h</span>
|
||||
</label>
|
||||
<label for="duration-6h">
|
||||
<input type="radio" name="alert-duration" class="radio" value="21600" id="duration-6h" ng-model="alert.duration">
|
||||
<span>6h</span>
|
||||
</label>
|
||||
<label for="duration-12h">
|
||||
<input type="radio" name="alert-duration" class="radio" value="43200" id="duration-12h" ng-model="alert.duration">
|
||||
<span>12h</span>
|
||||
</label>
|
||||
<label for="duration-24h">
|
||||
<input type="radio" name="alert-duration" class="radio" value="86400" id="duration-24h" ng-model="alert.duration">
|
||||
<span>24h</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="submit-buttons">
|
||||
<input type="submit" name="submit" value="Push Alert">
|
||||
<a href="/" class="btn btn-cancel">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<section class="channels-edit" ng-controller="ChannelForm">
|
||||
<div class="flex">
|
||||
<h2>Edit Channel</h2>
|
||||
<button class="btn btn-red btn-delete" id='delete' ng-click="deleteChannel()">Delete Channel</button>
|
||||
</div>
|
||||
|
||||
<form name="channelForm" class="channel-form" ng-submit="onFormSubmit()">
|
||||
<div ng-include="'/templates/channels/fields.html'"></div>
|
||||
<div class="submit-buttons">
|
||||
<input type="submit" name="submit" value="Save Channel">
|
||||
<a href="/channels" class="btn btn-cancel">Cancel</a>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</section>
|
||||
@@ -0,0 +1,55 @@
|
||||
<label for="name">Name</label>
|
||||
<input type="text" name="name" id="name" ng-model="channel.name" required />
|
||||
|
||||
<label>Channel Layout</label>
|
||||
|
||||
<div class="layout-select row">
|
||||
<label for="layout-fullscreen" class="layout-label column">
|
||||
<input type="radio" name="layout" id="layout-fullscreen" class="radio" value="single-cell" ng-model="channel.layout">
|
||||
|
||||
<div class="sample-fullscreen">
|
||||
<span class="sample-main">1</span>
|
||||
</div>
|
||||
|
||||
<span class="sample-label">Full Screen</span>
|
||||
|
||||
</label>
|
||||
|
||||
</div>
|
||||
|
||||
<label>Section 1</label>
|
||||
<ul class="url-list">
|
||||
<li ng-repeat="url in channel.cells[0].urls" class="url-item">
|
||||
<label>URL {{ 1 + $index }}</label>
|
||||
<input type="url" name="cells0url{{ $index }}" ng-model="url.url" required />
|
||||
<a ng-show="channel.cells[0].urls.length > 1" ng-click="removeUrlFromCell(url, 0)" class="remove-link icon-times"></a>
|
||||
</li>
|
||||
<a ng-click="channel.cells[0].urls.push({url: ''})" class="new-url">Add Another URL</a>
|
||||
|
||||
<li ng-show="channel.cells[0].urls.length > 1">
|
||||
<label>Rotation Duration</label>
|
||||
<div class="channel-rotation">
|
||||
<label for="duration-0-15s">
|
||||
<input type="radio" name="duration[0]" id="duration-0-15s" value="15" class="radio" ng-model="channel.cells[0].duration">
|
||||
<span>15s</span>
|
||||
</label>
|
||||
<label for="duration-0-30s">
|
||||
<input type="radio" name="duration[0]" id="duration-0-30s" value="30" class="radio" ng-model="channel.cells[0].duration">
|
||||
<span>30s</span>
|
||||
</label>
|
||||
<label for="duration-0-1m">
|
||||
<input type="radio" name="duration[0]" id="duration-0-1m" value="60" class="radio" ng-model="channel.cells[0].duration">
|
||||
<span>1m</span>
|
||||
</label>
|
||||
<label for="duration-0-2m">
|
||||
<input type="radio" name="duration[0]" id="duration-0-2m" value="120" class="radio" ng-model="channel.cells[0].duration">
|
||||
<span>2m</span>
|
||||
</label>
|
||||
<label for="duration-0-5m">
|
||||
<input type="radio" name="duration[0]" id="duration-0-5m" value="300" class="radio" ng-model="channel.cells[0].duration">
|
||||
<span>5m</span>
|
||||
</label>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<section class="channels" ng-controller="Channels">
|
||||
<div class="flex flex-baseline">
|
||||
<h2>Channels</h2>
|
||||
<a href="/channels/new" class="btn btn-add" id="addChannel">Add a Channel</a>
|
||||
</div>
|
||||
|
||||
<table class="channel-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Edit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="channel in channels">
|
||||
<td>
|
||||
<a href="/channels/{{ channel.id }}/edit" name='{{ channel.name }}' id='{{ channel.id }}' class="channel-link">
|
||||
{{ channel.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="/channels/{{ channel.id }}" id='{{ channel.id }}-preview' class="channel-preview" target="_blank" >
|
||||
Preview
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<section class="channels-new" ng-controller="ChannelForm">
|
||||
<h2>New Channel</h2>
|
||||
|
||||
<form name="channelForm" class="channel-form" ng-submit="onFormSubmit()">
|
||||
<div ng-include="'/templates/channels/fields.html'"></div>
|
||||
<div class="submit-buttons">
|
||||
<input type="submit" name="submit" value="Add Channel">
|
||||
<a href="/channels" class="btn btn-cancel">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<section class="chromecasts-edit" ng-controller="ChromecastForm">
|
||||
|
||||
<div class="flex">
|
||||
<h2>Chromecast Settings</h2>
|
||||
<button class="btn btn-add"
|
||||
ng-controller="Chromecasts"
|
||||
ng-show="chromecastAvailable"
|
||||
ng-click="reconnect()">Reconnect</button>
|
||||
<button class="btn btn-red btn-delete" ng-click="deleteChannel()" id="delete">Delete</button>
|
||||
</div>
|
||||
|
||||
<form class="chromecast-form" ng-submit="onFormSubmit()">
|
||||
<label for="name">Name</label>
|
||||
<input type="text" name="name" id="name" disabled ng-model="chromecast.name" required>
|
||||
|
||||
<label for="location">Location</label>
|
||||
<input type="text" name="location" id="location" ng-model="chromecast.location" required>
|
||||
|
||||
<div class="input-group">
|
||||
<label for="channel">Channel</label>
|
||||
<select name="channel" id="channel" ng-model="chromecast.channelId" ng-options="channel.id as channel.name for channel in channels">
|
||||
<option value="">Select Channel...</option>
|
||||
</select>
|
||||
<span class="icon-down"></span>
|
||||
</div>
|
||||
|
||||
<input type="submit" name="submit" value="Save Chromecast">
|
||||
</form>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<section class="chromecasts" ng-controller="Chromecasts">
|
||||
<div class="flex flex-baseline">
|
||||
<h2>Chromecasts</h2>
|
||||
<a ng-show="chromecastAvailable" href="/chromecasts/new" id='addChromecast' class="btn btn-add">Add a Chromecast</a>
|
||||
</div>
|
||||
|
||||
<table class="chromecast-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Channel</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="chromecast in chromecasts">
|
||||
<td>
|
||||
<a href="/chromecasts/{{ chromecast.id }}/edit" id="{{ chromecast.id }}" name="{{ chromecast.name }}">
|
||||
{{ chromecast.name }}
|
||||
</a>
|
||||
<br>
|
||||
<span class="chromecast-location">
|
||||
{{ chromecast.location }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span>{{ chromecast.channelName }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span ng-show="false" class="icon-cancel non-editable"></span>
|
||||
<a ng-show="true" href="/chromecasts/{{ chromecast.id }}" target="_blank" id="{{ chromecast.id }}-preview"> Preview </a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
@@ -0,0 +1,22 @@
|
||||
<section class="chromecasts-new" ng-controller="ChromecastForm">
|
||||
<h2>New Chromecast</h2>
|
||||
|
||||
<form class="chromecast-form" ng-submit="onFormSubmit()">
|
||||
<label for="name">Name</label>
|
||||
<input type="text" name="name" id="name" disabled ng-model="chromecast.name" required>
|
||||
|
||||
<label for="location">Location</label>
|
||||
<input type="text" name="location" id="location" ng-model="chromecast.location" required>
|
||||
|
||||
<div class="input-group">
|
||||
<label for="channel">Channel</label>
|
||||
<select name="channel" id="channel" ng-model="chromecast.channelId" ng-options="channel.id as channel.name for channel in channels">
|
||||
<option value="">Select Channel...</option>
|
||||
</select>
|
||||
<span class="icon-down"></span>
|
||||
</div>
|
||||
|
||||
<input type="submit" name="submit" value="Add Chromecast">
|
||||
</form>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<section class="main">
|
||||
<ul class="main-nav">
|
||||
<li><a href="/chromecasts">Chromecasts</a></li>
|
||||
<li><a href="/channels">Channels</a></li>
|
||||
<!-- Admin only options -->
|
||||
<li><a href="/alert">Push Alert</a></li>
|
||||
<li><a href="/takeover">Channel Takeover</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<section class="alert" ng-controller="TakeoverForm">
|
||||
<h2>Channel Takeover</h2>
|
||||
|
||||
<form ng-submit="onFormSubmit()">
|
||||
<div class="input-group">
|
||||
<label for="takeover-channel">Channel</label>
|
||||
<select name="takeover-channel" id="channel" class="takeover-channel" ng-model="takeover.channelId" ng-options="channel.id as channel.name for channel in channels" required>
|
||||
<option value="">Select Channel...</option>
|
||||
</select>
|
||||
<span class="icon-down"></span>
|
||||
</div>
|
||||
|
||||
<p class="note">Selected channel will appear on all Chromecasts.</p>
|
||||
|
||||
<div class="submit-buttons">
|
||||
<input type="submit" name="submit" value="Start Takeover" ng-hide="takeover.id" />
|
||||
<input type="submit" name="submit" value="Save Changes" ng-show="takeover.id" />
|
||||
<input type="submit" name="submit" class="btn-red" value="Stop Takeover" ng-show="takeover.id" ng-click="onStop($event)">
|
||||
|
||||
<a href="/" class="btn btn-cancel">Cancel</a>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</section>
|
||||
|
||||
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 87 KiB |
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env coffee
|
||||
|
||||
Channel = require("../src/server/channel")
|
||||
Receiver = require("../src/server/receiver")
|
||||
db = require("../src/server/db")
|
||||
|
||||
new Channel({name: "Channel 1"}).save()
|
||||
new Channel({name: "Channel 2"}).save()
|
||||
|
||||
new Receiver({name: "Receiver 1", location: "Location 1"}).save()
|
||||
new Receiver({name: "Receiver 2", location: "Location 2"}).save()
|
||||
@@ -0,0 +1,24 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
angular.module("GScreen").controller "AlertForm", ($scope, $location, flash, Alert) ->
|
||||
$scope.maxTextLength = 140
|
||||
$scope.alert = style: "default", duration: 60
|
||||
|
||||
$scope.onFormSubmit = ->
|
||||
seconds = $scope.alert.duration
|
||||
$scope.alert.expiresAt = new Date(new Date().getTime() + (seconds * 1000)).toISOString()
|
||||
Alert.save $scope.alert, ->
|
||||
flash.message "Your alert has been saved."
|
||||
$location.url "/"
|
||||
@@ -0,0 +1,33 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
angular.module("GScreen").controller "ChannelForm", ($scope, $routeParams, $location, flash, Channel) ->
|
||||
$scope.mainUrls = []
|
||||
$scope.channel = if $routeParams.id then Channel.get($routeParams.id) else {layout: "single-cell", cells: [{urls: [{url: ""}], duration: 60}, {urls: [{url: ""}], duration: 60}]}
|
||||
|
||||
$scope.channel.$promise?.then (channel) -> console.log channel
|
||||
|
||||
$scope.onFormSubmit = ->
|
||||
Channel.save $scope.channel, ->
|
||||
flash.message "Your changes to '#{$scope.channel.name}' have been saved."
|
||||
$location.url "/channels"
|
||||
|
||||
$scope.deleteChannel = ->
|
||||
Channel.remove $scope.channel, ->
|
||||
flash.message "The channel '#{$scope.channel.name}' has been deleted."
|
||||
$location.url "/channels"
|
||||
|
||||
$scope.removeUrlFromCell = (url, cellIndex) ->
|
||||
$scope.channel.cells[cellIndex].urls = _.without $scope.channel.cells[cellIndex].urls, url
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
angular.module("GScreen").controller "Channels", ($scope, Channel) ->
|
||||
$scope.channels = Channel.query()
|
||||
@@ -0,0 +1,44 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
angular.module("GScreen").controller "ChromecastForm", ($scope, $routeParams, $location, castAway, flash, Chromecast, Channel) ->
|
||||
$scope.chromecast = if $routeParams.id then Chromecast.get($routeParams.id) else {}
|
||||
$scope.channels = Channel.query()
|
||||
|
||||
session = null
|
||||
|
||||
connect = ->
|
||||
castAway.connect (err, s) ->
|
||||
return console.log "ERR", err if err
|
||||
session = s
|
||||
$scope.$apply ->
|
||||
$scope.chromecast.name = session.session.receiver.friendlyName
|
||||
|
||||
if !$routeParams.id
|
||||
connect()
|
||||
|
||||
$scope.reconnect = ->
|
||||
connect()
|
||||
|
||||
$scope.onFormSubmit = ->
|
||||
Chromecast.save $scope.chromecast, (chromecast) ->
|
||||
flash.message "Your changes to '#{$scope.chromecast.name}' have been saved."
|
||||
session.send "setChromecastId", chromecast.id if session
|
||||
$location.url "/chromecasts"
|
||||
|
||||
$scope.deleteChannel = ->
|
||||
console.log "deleteing", $scope.chromecast
|
||||
Chromecast.remove $scope.chromecast, ->
|
||||
flash.message "The Chromecast '#{$scope.chromecast.name}' has been deleted."
|
||||
$location.url "/chromecasts"
|
||||
@@ -0,0 +1,22 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
angular.module("GScreen").controller "Chromecasts", ($scope, Chromecast, castAway) ->
|
||||
$scope.chromecasts = Chromecast.query()
|
||||
$scope.chromecastAvailable = castAway.available
|
||||
console.log "Initial Chromecast available", $scope.chromecastAvailable
|
||||
|
||||
castAway.on "receivers:available", ->
|
||||
$scope.$apply -> $scope.chromecastAvailable = true
|
||||
console.log "Chromecast is available", $scope.chromecastAvailable
|
||||
@@ -0,0 +1,16 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
angular.module("GScreen").controller "Main", ($scope) ->
|
||||
$scope.username = ""
|
||||
@@ -0,0 +1,36 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
angular.module("GScreen").controller "Receiver", ($scope, $sce, $location, localDevice) ->
|
||||
if match = $location.url().match /\/chromecasts\/([^\/\?#]+)/
|
||||
localDevice.setChromecastId match[1]
|
||||
|
||||
$scope.title = "Setting Up Chromecast"
|
||||
$scope.chromecast = null
|
||||
$scope.channelUrl = null
|
||||
|
||||
localDevice.on "change", (c) ->
|
||||
$scope.chromecast = c
|
||||
$scope.title = "Greenscreen - #{c.name}"
|
||||
$scope.channelUrl = $sce.trustAsResourceUrl("/channels/#{c.channelId}")
|
||||
$scope.$apply() unless $scope.$$phase
|
||||
|
||||
window.onload = ->
|
||||
try
|
||||
castAway = new CastAway()
|
||||
receiver = castAway.receive()
|
||||
receiver.on "setChromecastId", (id) ->
|
||||
localDevice.setChromecastId(id)
|
||||
catch e
|
||||
console.log "Cannot load CastAway", e
|
||||
@@ -0,0 +1,36 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
angular.module("GScreen").controller "Screen", ($scope, $sce, $location, Channel) ->
|
||||
id = $location.url().match(/\/channels\/([^\/\?]+)/)[1]
|
||||
|
||||
$scope.channel = Channel.get id
|
||||
$scope.channel.$promise.then (channel) ->
|
||||
setTimeout rotateMainContentUrl, 0
|
||||
|
||||
mainContentUrlCounter = 0
|
||||
lastTimeoutId = null
|
||||
rotateMainContentUrl = ->
|
||||
clearTimeout lastTimeoutId
|
||||
cell = $scope.channel.cells[0]
|
||||
urls = cell.urls
|
||||
if urls.length == 1
|
||||
$scope.mainContentUrl = $sce.trustAsResourceUrl(urls[0].url)
|
||||
else
|
||||
$scope.mainContentUrl = $sce.trustAsResourceUrl(urls[mainContentUrlCounter].url)
|
||||
mainContentUrlCounter++
|
||||
mainContentUrlCounter = 0 if mainContentUrlCounter >= urls.length
|
||||
seconds = parseInt cell.duration, 10
|
||||
lastTimeoutId = setTimeout rotateMainContentUrl, seconds * 1000
|
||||
$scope.$apply() unless $scope.$$phase
|
||||
@@ -0,0 +1,30 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
angular.module("GScreen").controller "TakeoverForm", ($scope, flash, Channel, Takeover) ->
|
||||
$scope.takeover = Takeover.get()
|
||||
$scope.channels = Channel.query()
|
||||
|
||||
$scope.onFormSubmit = ->
|
||||
Takeover.save $scope.takeover, (t) ->
|
||||
$scope.takeover.id ?= t.id
|
||||
channel = _($scope.channels).detect (ch) -> ch.id == $scope.takeover.channelId
|
||||
flash.message "The '#{channel.name}' channel has taken over."
|
||||
|
||||
$scope.onStop = ($event) ->
|
||||
$event.stopPropagation()
|
||||
$event.preventDefault()
|
||||
Takeover.remove $scope.takeover, ->
|
||||
$scope.takeover = {}
|
||||
flash.message "The takeover has stopped."
|
||||
@@ -0,0 +1,25 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
angular.module("GScreen").directive "flashContainer", (flash) ->
|
||||
transclude: false
|
||||
restrict: "E"
|
||||
replace: true
|
||||
template: "<div id='alerts'></div>"
|
||||
link: (scope, element, attrs) ->
|
||||
flash.on "message", (msg) ->
|
||||
el = $("<div class='flash-msg'>#{msg}</div>")
|
||||
$(element).append el
|
||||
remove = -> el.remove()
|
||||
setTimeout remove, 5000
|
||||
@@ -0,0 +1,19 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
angular.module("GScreen").directive "realLink", ->
|
||||
(scope, element, attrs) ->
|
||||
$el = $(element)
|
||||
$el.click (e) ->
|
||||
window.location.href = $el.attr "href"
|
||||
@@ -0,0 +1,36 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
window.GScreen = angular.module "GScreen", ["ng", "ngResource", "ngRoute"]
|
||||
|
||||
require "./routes"
|
||||
require "./controllers/alert-form"
|
||||
require "./controllers/main"
|
||||
require "./controllers/channels"
|
||||
require "./controllers/channel-form"
|
||||
require "./controllers/chromecasts"
|
||||
require "./controllers/chromecast-form"
|
||||
require "./controllers/receiver"
|
||||
require "./controllers/screen"
|
||||
require "./controllers/takeover-form"
|
||||
require "./directives/flash-container"
|
||||
require "./directives/real-link"
|
||||
require "./resources/alert"
|
||||
require "./resources/channel"
|
||||
require "./resources/chromecast"
|
||||
require "./resources/takeover"
|
||||
require "./services/cast-away"
|
||||
require "./services/flash"
|
||||
require "./services/local-device"
|
||||
require "./services/sockets"
|
||||
@@ -0,0 +1,38 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
angular.module("GScreen").factory "Alert", ($resource) ->
|
||||
resource = $resource "/api/alerts/:id", null,
|
||||
update:
|
||||
method: "PUT"
|
||||
remove:
|
||||
method: "POST"
|
||||
url: "/api/alerts/:id/remove"
|
||||
params:
|
||||
id: "@id"
|
||||
|
||||
get: (id) ->
|
||||
resource.get id: id
|
||||
|
||||
query: (args...) ->
|
||||
resource.query args...
|
||||
|
||||
save: (data, cb=->) ->
|
||||
if data.id
|
||||
resource.update {id: data.id}, data, cb
|
||||
else
|
||||
resource.save data, cb
|
||||
|
||||
remove: (data, cb=->) ->
|
||||
resource.remove id: data.id, cb
|
||||
@@ -0,0 +1,35 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
angular.module("GScreen").factory "Channel", ($resource) ->
|
||||
resource = $resource "/api/channels/:id", null,
|
||||
update:
|
||||
method: "PUT"
|
||||
remove:
|
||||
method: "DELETE"
|
||||
|
||||
get: (id) ->
|
||||
resource.get id: id
|
||||
|
||||
query: (args...) ->
|
||||
resource.query args...
|
||||
|
||||
save: (data, cb=->) ->
|
||||
if data.id
|
||||
resource.update {id: data.id}, data, cb
|
||||
else
|
||||
resource.save data, cb
|
||||
|
||||
remove: (data, cb=->) ->
|
||||
resource.remove id: data.id, cb
|
||||
@@ -0,0 +1,36 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
angular.module("GScreen").factory "Chromecast", ($resource) ->
|
||||
resource = $resource "/api/receivers/:id", null,
|
||||
update:
|
||||
method: "PUT"
|
||||
remove:
|
||||
method: "DELETE"
|
||||
|
||||
get: (id) ->
|
||||
resource.get id: id
|
||||
|
||||
query: (args...) ->
|
||||
resource.query args...
|
||||
|
||||
save: (data, cb=->) ->
|
||||
if data.id
|
||||
data.alert = null if data.alert and new Date(data.alert.expiresAt).getTime() < new Date().getTime()
|
||||
resource.update {id: data.id}, data, cb
|
||||
else
|
||||
resource.save data, cb
|
||||
|
||||
remove: (data, cb=->) ->
|
||||
resource.remove id: data.id, cb
|
||||
@@ -0,0 +1,33 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
angular.module("GScreen").factory "Takeover", ($resource) ->
|
||||
resource = $resource "/api/takeover", null,
|
||||
update:
|
||||
method: "PUT"
|
||||
remove:
|
||||
method: "POST"
|
||||
url: "/api/takeover/remove"
|
||||
|
||||
get: (id) ->
|
||||
resource.get()
|
||||
|
||||
query: (args...) ->
|
||||
resource.query args...
|
||||
|
||||
save: (data, cb=->) ->
|
||||
resource.save data, cb
|
||||
|
||||
remove: (data, cb=->) ->
|
||||
resource.remove cb
|
||||
@@ -0,0 +1,44 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
GScreen.config ($routeProvider, $locationProvider) ->
|
||||
$routeProvider.when "/",
|
||||
templateUrl: '/templates/main.html'
|
||||
|
||||
$routeProvider.when "/channels",
|
||||
templateUrl: "/templates/channels/index.html"
|
||||
|
||||
$routeProvider.when "/channels/new",
|
||||
templateUrl: "/templates/channels/new.html"
|
||||
|
||||
$routeProvider.when "/channels/:id/edit",
|
||||
templateUrl: "/templates/channels/edit.html"
|
||||
|
||||
$routeProvider.when "/chromecasts",
|
||||
templateUrl: "/templates/chromecasts/index.html"
|
||||
|
||||
$routeProvider.when "/alert",
|
||||
templateUrl: "/templates/alert/index.html"
|
||||
|
||||
$routeProvider.when "/takeover",
|
||||
templateUrl: "/templates/takeover/index.html"
|
||||
|
||||
$routeProvider.when "/chromecasts/new",
|
||||
templateUrl: "/templates/chromecasts/new.html"
|
||||
|
||||
$routeProvider.when "/chromecasts/:id/edit",
|
||||
templateUrl: "/templates/chromecasts/edit.html"
|
||||
|
||||
$locationProvider.html5Mode true
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
angular.module("GScreen").factory "castAway", (CONFIG) ->
|
||||
castAway = null
|
||||
listeners =
|
||||
"receivers:available": []
|
||||
|
||||
exports =
|
||||
available: false
|
||||
on: (key, func) ->
|
||||
listeners[key].push func
|
||||
connect: (cb) ->
|
||||
return unless castAway
|
||||
castAway.requestSession cb
|
||||
receiver: ->
|
||||
return unless castAway
|
||||
castAway.receiver()
|
||||
|
||||
if chrome?.cast
|
||||
chrome.cast.timeout ?= {}
|
||||
chrome.cast.timeout.requestSession = 20000
|
||||
|
||||
castAway = window.castAway = new CastAway
|
||||
applicationID: CONFIG.chromecastApplicationId
|
||||
namespace: "urn:x-cast:json"
|
||||
|
||||
castAway.on "receivers:available", ->
|
||||
console.log "receivers available"
|
||||
exports.available = true
|
||||
l() for l in listeners["receivers:available"]
|
||||
$("#cast").click (ev) ->
|
||||
ev.preventDefault()
|
||||
|
||||
castAway.initialize (err, data) ->
|
||||
console.log "initialized", err, data
|
||||
|
||||
exports
|
||||
@@ -0,0 +1,23 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
angular.module("GScreen").factory "flash", ->
|
||||
listeners =
|
||||
message: []
|
||||
|
||||
exports =
|
||||
on: (key, func) ->
|
||||
listeners[key].push func
|
||||
message: (msg) ->
|
||||
l(msg) for l in listeners.message
|
||||
@@ -0,0 +1,90 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
angular.module("GScreen").factory "localDevice", (Chromecast, sockets) ->
|
||||
listeners = {"change": []}
|
||||
|
||||
clearTimeoutId = null
|
||||
|
||||
loadChromecast = (id) ->
|
||||
Chromecast.get(id).$promise.then (c) ->
|
||||
console.log "loadChromecast", c
|
||||
updateChromecast(c)
|
||||
createAlert c.alert
|
||||
|
||||
updateChromecast = (c) ->
|
||||
exports.chromecast = c
|
||||
l(c) for l in listeners.change
|
||||
|
||||
updateChannelId = (channelId) ->
|
||||
newCast = {}
|
||||
newCast[k] = v for k,v of exports.chromecast
|
||||
newCast.channelId = channelId
|
||||
updateChromecast(newCast)
|
||||
|
||||
updateAlert = (alert) ->
|
||||
newCast = {}
|
||||
newCast[k] = v for k,v of exports.chromecast
|
||||
newCast.alert = alert
|
||||
updateChromecast(newCast)
|
||||
|
||||
loadChromecastFromPersistence = ->
|
||||
if id = localStorage.getItem "chromecast-id"
|
||||
loadChromecast(id)
|
||||
|
||||
clearAlert = ->
|
||||
console.log "Clearing the alert"
|
||||
clearTimeout clearTimeoutId if clearTimeoutId
|
||||
updateAlert null
|
||||
|
||||
createAlert = (alert) ->
|
||||
if alert
|
||||
duration = new Date(alert.expiresAt).getTime() - new Date().getTime()
|
||||
if duration > 0
|
||||
updateAlert alert
|
||||
console.log "duration", duration
|
||||
clearTimeoutId = setTimeout clearAlert, Math.ceil(duration)
|
||||
else
|
||||
clearAlert()
|
||||
|
||||
sockets.on "receiver-updated", (chromecast) ->
|
||||
if exports.chromecast.id == chromecast.id
|
||||
updateChromecast chromecast
|
||||
|
||||
sockets.on "takeover-created", (takeover) ->
|
||||
updateChannelId takeover.channelId
|
||||
|
||||
sockets.on "takeover-updated", (takeover) ->
|
||||
updateChannelId takeover.channelId
|
||||
|
||||
sockets.on "takeover-deleted", ->
|
||||
loadChromecastFromPersistence()
|
||||
|
||||
sockets.on "alert-created", (alert) ->
|
||||
console.log "Creating alert", alert
|
||||
createAlert alert
|
||||
|
||||
loadChromecastFromPersistence()
|
||||
|
||||
exports =
|
||||
setChromecastId: (id) ->
|
||||
loadChromecast(id)
|
||||
localStorage.setItem "chromecast-id", id
|
||||
|
||||
on: (event, func) ->
|
||||
listeners[event].push func
|
||||
|
||||
chromecast: null
|
||||
|
||||
exports
|
||||
@@ -0,0 +1,35 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
angular.module("GScreen").factory "sockets", ->
|
||||
listeners = {}
|
||||
|
||||
socket = io(window.location.origin)
|
||||
socket.on 'connect', ->
|
||||
connected = true
|
||||
|
||||
addListenerForEvent = (name) ->
|
||||
socket.on name, (args...) ->
|
||||
l(args...) for l in listeners[name]
|
||||
|
||||
exports =
|
||||
on: (name, func) ->
|
||||
if listeners[name]
|
||||
listeners[name].push func
|
||||
else
|
||||
listeners[name] = [func]
|
||||
addListenerForEvent name
|
||||
|
||||
emit: (args...) ->
|
||||
socket.emit args...
|
||||
@@ -0,0 +1,35 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
Model = require "./model"
|
||||
db = require "./db"
|
||||
|
||||
module.exports = class Alert extends Model
|
||||
@type: "alert"
|
||||
|
||||
@forReceiver: (receiver, cb) ->
|
||||
db.allWithType @type, (err, docs) ->
|
||||
return cb(err) if err
|
||||
return cb(null, null) if docs.length == 0
|
||||
doc = docs[docs.length - 1]
|
||||
cb null, doc
|
||||
|
||||
constructor: (data={}) ->
|
||||
@type = "alert"
|
||||
@id = data.id || data._id
|
||||
@rev = data._rev
|
||||
@style = data.style || "default"
|
||||
@duration = data.duration || 60
|
||||
@text = data.text
|
||||
@expiresAt = data.expiresAt
|
||||
@@ -0,0 +1,26 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
Model = require "./model"
|
||||
|
||||
module.exports = class Channel extends Model
|
||||
@type: "channel"
|
||||
|
||||
constructor: (data={}) ->
|
||||
@type = "channel"
|
||||
@id = data.id || data._id
|
||||
@rev = data._rev
|
||||
@name = data.name
|
||||
@layout = data.layout || "single-cell"
|
||||
@cells = data.cells || [{urls: [], duration: 60}]
|
||||
@@ -0,0 +1,21 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
fs = require "fs"
|
||||
|
||||
module.exports = try
|
||||
JSON.parse fs.readFileSync("#{__dirname}/../../config.json")
|
||||
catch e
|
||||
console.warn "Error loading config.json", e
|
||||
{}
|
||||
@@ -0,0 +1,41 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
# NOTE: This function is going to be stringified and sent to CouchDB
|
||||
# for execution, which means it cannot reference any code outside of
|
||||
# the function scope (ie no requires, etc.)
|
||||
#
|
||||
# Also, remember that oldDoc will be null if this is a new record.
|
||||
module.exports = (newDoc, oldDoc, usrCtx) ->
|
||||
raise = (error, reason) ->
|
||||
throw error: error, reason: reason
|
||||
|
||||
# If we're deleting, no need for validations
|
||||
return if newDoc._deleted
|
||||
|
||||
unless newDoc.type
|
||||
raise "missing type", "all documents must have a type"
|
||||
|
||||
if oldDoc && newDoc.type != oldDoc.type
|
||||
raise "invalid value", "cannot change a document's type"
|
||||
|
||||
requiredFieldsMap =
|
||||
channel: ["name", "layout", "cells"]
|
||||
receiver: ["name", "groups"]
|
||||
alert: ["text", "duration", "style"]
|
||||
|
||||
requiredFields = requiredFieldsMap[newDoc.type] || []
|
||||
for field in requiredFields
|
||||
raise "missing field", "documents with type='#{newDoc.type}' requires a '#{field}' value" unless newDoc[field]?
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
module.exports =
|
||||
byType:
|
||||
map: (doc) ->
|
||||
emit(doc.type, doc.id) if doc.type
|
||||
@@ -0,0 +1,50 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
cradle = require "cradle"
|
||||
views = require "./couch-db-views"
|
||||
validations = require "./couch-db-validations"
|
||||
|
||||
module.exports = class CouchDB
|
||||
constructor: (config) ->
|
||||
@couch = new cradle.Connection config.host, config.port
|
||||
@db = @couch.database config.db
|
||||
@ensureDbExists =>
|
||||
@installDesignDoc()
|
||||
|
||||
ensureDbExists: (cb) ->
|
||||
console.log "Ensuring DB Exists..."
|
||||
@db.exists (err, exists) =>
|
||||
return cb() if exists
|
||||
console.log "Creating DB..."
|
||||
@db.create cb
|
||||
|
||||
installDesignDoc: (cb) ->
|
||||
console.log "Creating Design Doc..."
|
||||
@db.save "_design/gscreen",
|
||||
views: views
|
||||
validate_doc_update: validations
|
||||
|
||||
allWithType: (type, cb) ->
|
||||
@db.view "gscreen/byType", key: type, include_docs: true, (err, rows) ->
|
||||
return cb(err) if err
|
||||
# This next line looks crazy, and it is. Believe it or not, the cradle lib
|
||||
# overrides the `map` function to map over the docs, not the rows. This means
|
||||
# that the result is an array of docs, rather than an array of rows, which is
|
||||
# what we want. Very strange design choice on cradle's part.
|
||||
cb(null, rows.map (doc) -> doc)
|
||||
|
||||
get: (args...) -> @db.get(args...)
|
||||
save: (args...) -> @db.save(args...)
|
||||
remove: (args...) -> @db.remove(args...)
|
||||
@@ -0,0 +1,18 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
CouchDB = require "./couch-db"
|
||||
config = require "./config"
|
||||
|
||||
module.exports = new CouchDB(config.couch)
|
||||
@@ -0,0 +1,52 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
db = require "./db"
|
||||
_ = require "underscore"
|
||||
|
||||
module.exports = class Model
|
||||
@all: (cb) ->
|
||||
db.allWithType @type, (err, docs) =>
|
||||
return cb(err) if err
|
||||
channels = docs.map (doc) => new this(doc)
|
||||
cb(null, channels)
|
||||
|
||||
@findById: (id, cb) ->
|
||||
db.get id, (err, doc) =>
|
||||
return cb(err) if err
|
||||
cb(null, new this(doc))
|
||||
|
||||
save: (cb=->) ->
|
||||
attrs = {}
|
||||
for own k,v of this
|
||||
if !_(["id", "rev"]).contains(k) && !/^\$/.test k
|
||||
attrs[k] = v
|
||||
if @id
|
||||
db.save @id, @rev, attrs, (err, res) ->
|
||||
return cb(err) if err
|
||||
@rev = res.rev
|
||||
cb()
|
||||
else
|
||||
db.save attrs, (err, res) =>
|
||||
return cb(err) if err
|
||||
@id = res.id
|
||||
@rev = res.rev
|
||||
cb()
|
||||
|
||||
update: (data, cb=->) ->
|
||||
this[k] = v for k,v of data
|
||||
@save(cb)
|
||||
|
||||
destroy: (cb=->) ->
|
||||
db.remove @id, @rev, cb
|
||||
@@ -0,0 +1,54 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
_ = require "underscore"
|
||||
Alert = require "./alert"
|
||||
Channel = require "./channel"
|
||||
Model = require "./model"
|
||||
Takeover = require "./takeover"
|
||||
db = require "./db"
|
||||
|
||||
module.exports = class Receiver extends Model
|
||||
@type: "receiver"
|
||||
|
||||
@all: (cb) ->
|
||||
db.allWithType @type, (err, docs) =>
|
||||
return cb(err) if err
|
||||
receivers = docs.map (doc) => new this(doc)
|
||||
Channel.all (err, channels) ->
|
||||
return cb(err) if err
|
||||
receivers.forEach (r) ->
|
||||
r.channelName = _(channels).detect((ch) -> ch.id == r.channelId)?.name
|
||||
cb null, receivers
|
||||
|
||||
@findById: (id, cb) ->
|
||||
db.get id, (err, doc) =>
|
||||
return cb(err) if err
|
||||
receiver = new Receiver(doc)
|
||||
Takeover.singleton (err, takeover) ->
|
||||
receiver.channelId = takeover.channelId if takeover
|
||||
Alert.forReceiver receiver, (err, alert) ->
|
||||
return cb(err) if err
|
||||
receiver.alert = alert
|
||||
cb null, receiver
|
||||
|
||||
constructor: (data={}) ->
|
||||
@type = "receiver"
|
||||
@id = data.id || data._id
|
||||
@rev = data._rev
|
||||
@name = data.name
|
||||
@location = data.location
|
||||
@groups = data.groups || []
|
||||
@channelId = data.channelId
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
pathLib = require "path"
|
||||
Alert = require "./alert"
|
||||
Channel = require "./channel"
|
||||
Receiver = require "./receiver"
|
||||
Takeover = require "./takeover"
|
||||
|
||||
module.exports = (app, sockets) ->
|
||||
addResources = (basePath, resourceClass) ->
|
||||
app.get "#{basePath}", (req, res) ->
|
||||
resourceClass.all (err, resources) ->
|
||||
if err then res.send err else res.send resources
|
||||
|
||||
app.get "#{basePath}/:id", (req, res) ->
|
||||
resourceClass.findById req.param("id"), (err, resource) ->
|
||||
return res.status(404).send(null) if err && err.error == "not_found"
|
||||
return res.status(500).send(err) if err
|
||||
res.send resource
|
||||
|
||||
app.post "#{basePath}", (req, res) ->
|
||||
data = req.body
|
||||
resource = new resourceClass(data)
|
||||
resource.save (err) ->
|
||||
return res.status(500).send(err) if err
|
||||
sockets.emit "#{resourceClass.type}-created", resource
|
||||
res.send resource
|
||||
|
||||
app.put "#{basePath}/:id", (req, res) ->
|
||||
resourceClass.findById req.param("id"), (err, resource) ->
|
||||
return res.status(404).send(null) if err && err.error == "not_found"
|
||||
return res.status(500).send(err) if err
|
||||
resource.update req.body, (err) ->
|
||||
return res.status(500).send(err) if err
|
||||
sockets.emit "#{resourceClass.type}-updated", resource
|
||||
res.send resource
|
||||
|
||||
app.delete "#{basePath}/:id", (req, res) ->
|
||||
resourceClass.findById req.param("id"), (err, resource) ->
|
||||
return res.status(404).send(null) if err && err.error == "not_found"
|
||||
return res.status(500).send(err) if err
|
||||
resource.destroy (err) ->
|
||||
return res.status(500).send(err) if err
|
||||
sockets.emit "#{resourceClass.type}-deleted", resource
|
||||
res.send status: "success"
|
||||
|
||||
addResource = (basePath, resourceClass, id) ->
|
||||
app.get "#{basePath}", (req, res) ->
|
||||
resourceClass.findById id, (err, resource) ->
|
||||
return res.status(404).send(null) if err && err.error == "not_found"
|
||||
return res.status(500).send(err) if err
|
||||
res.send resource
|
||||
|
||||
app.post "#{basePath}", (req, res) ->
|
||||
data = req.body
|
||||
data.id = id
|
||||
resource = new resourceClass(data)
|
||||
resource.save (err) ->
|
||||
return res.status(500).send(err) if err
|
||||
sockets.emit "#{resourceClass.type}-created", resource
|
||||
res.send resource
|
||||
|
||||
app.put "#{basePath}", (req, res) ->
|
||||
resourceClass.findById id, (err, resource) ->
|
||||
return res.status(404).send(null) if err && err.error == "not_found"
|
||||
return res.status(500).send(err) if err
|
||||
resource.update req.body, (err) ->
|
||||
return res.status(500).send(err) if err
|
||||
sockets.emit "#{resourceClass.type}-updated", resource
|
||||
res.send resource
|
||||
|
||||
app.post "#{basePath}/remove", (req, res) ->
|
||||
resourceClass.findById id, (err, resource) ->
|
||||
return res.status(404).send(null) if err && err.error == "not_found"
|
||||
return res.status(500).send(err) if err
|
||||
resource.destroy (err) ->
|
||||
return res.status(500).send(err) if err
|
||||
sockets.emit "#{resourceClass.type}-deleted", resource
|
||||
res.send status: "success"
|
||||
|
||||
addResources "/api/alerts", Alert
|
||||
addResources "/api/channels", Channel
|
||||
addResources "/api/receivers", Receiver
|
||||
addResource "/api/takeover", Takeover, "takeover-singleton"
|
||||
|
||||
app.get "/chromecasts/new", (req, res) ->
|
||||
res.sendfile pathLib.resolve("#{__dirname}/../../public/index.html")
|
||||
|
||||
app.get "/chromecasts/:id", (req, res) ->
|
||||
res.sendfile pathLib.resolve("#{__dirname}/../../public/setup-chromecast.html")
|
||||
|
||||
app.get "/channels/new", (req, res) ->
|
||||
res.sendfile pathLib.resolve("#{__dirname}/../../public/index.html")
|
||||
|
||||
app.get "/channels/:id", (req, res) ->
|
||||
res.sendfile pathLib.resolve("#{__dirname}/../../public/channel.html")
|
||||
|
||||
# Wildcard all GET routes, send to the Angular app
|
||||
app.get "*", (req, res) ->
|
||||
res.sendfile pathLib.resolve("#{__dirname}/../../public/index.html")
|
||||
@@ -0,0 +1,39 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
http = require "http"
|
||||
express = require "express"
|
||||
morgan = require "morgan"
|
||||
bodyParser = require "body-parser"
|
||||
methodOverride = require "method-override"
|
||||
config = require "./config"
|
||||
routes = require "./routes"
|
||||
socketIO = require "socket.io"
|
||||
|
||||
app = express()
|
||||
server = http.createServer app
|
||||
sockets = socketIO(server)
|
||||
|
||||
app.use morgan('dev')
|
||||
app.use bodyParser.urlencoded
|
||||
extended: true
|
||||
app.use bodyParser.json()
|
||||
app.use methodOverride()
|
||||
app.use express.static("#{__dirname}/../../public")
|
||||
|
||||
routes(app, sockets)
|
||||
|
||||
port = process.env.PORT || config.server?.port || 4994
|
||||
server.listen port
|
||||
console.log "GScreen is listening to localhost:#{port}"
|
||||
@@ -0,0 +1,30 @@
|
||||
###
|
||||
Copyright (c) 2014, Groupon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.###
|
||||
|
||||
_ = require "underscore"
|
||||
Model = require "./model"
|
||||
db = require "./db"
|
||||
|
||||
module.exports = class Takeover extends Model
|
||||
@type: "takeover"
|
||||
|
||||
@singleton: (cb) ->
|
||||
db.get "takeover-singleton", cb
|
||||
|
||||
constructor: (data={}) ->
|
||||
@type = "takeover"
|
||||
@id = data.id || data._id
|
||||
@rev = data._rev
|
||||
@channelId = data.channelId
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
var Channel = require("../../src/server/channel"),
|
||||
db = require("../../src/server/db");
|
||||
|
||||
describe('Channels Index', function() {
|
||||
|
||||
var addChannelButton = element(by.id('addChannel')),
|
||||
channel1,
|
||||
channel2;
|
||||
|
||||
beforeEach(function(){
|
||||
channel1 = new Channel({
|
||||
name: "Test Channel 1"
|
||||
});
|
||||
channel2 = new Channel({
|
||||
name: "Test Channel 2"
|
||||
});
|
||||
|
||||
channel1.save();
|
||||
channel2.save();
|
||||
|
||||
browser.get('http://localhost:4994/channels')
|
||||
});
|
||||
|
||||
afterEach(function(){
|
||||
channel1.destroy();
|
||||
channel2.destroy();
|
||||
});
|
||||
|
||||
function expectUrl(url){
|
||||
expect(browser.getCurrentUrl()).toEqual(url);
|
||||
}
|
||||
|
||||
it('should take you the new form', function(){
|
||||
var expectedUrl = 'http://localhost:4994/channels/new';
|
||||
|
||||
addChannelButton.click();
|
||||
expectUrl(expectedUrl);
|
||||
});
|
||||
|
||||
it('should show you available channels', function(){
|
||||
expect(element(by.id(channel1.id)).isPresent()).toBe(true);
|
||||
expect(element(by.id(channel2.id)).isPresent()).toBe(true);
|
||||
});
|
||||
|
||||
it('should take you to the edit form', function(){
|
||||
|
||||
var channel1Button = element(by.id(channel1.id)),
|
||||
channel2Button = element(by.id(channel2.id)),
|
||||
expectedUrl1 = 'http://localhost:4994/channels/' + channel1.id + '/edit',
|
||||
expectedUrl2 = 'http://localhost:4994/channels/' + channel2.id + '/edit';
|
||||
|
||||
channel1Button.click();
|
||||
expectUrl(expectedUrl1);
|
||||
|
||||
browser.navigate().back();
|
||||
channel2Button.click();
|
||||
expectUrl(expectedUrl2);
|
||||
|
||||
});
|
||||
|
||||
it('should show you preview links', function(){
|
||||
expect(element(by.id(channel1.id + '-preview')).isPresent()).toBe(true);
|
||||
expect(element(by.id(channel2.id + '-preview')).isPresent()).toBe(true)
|
||||
});
|
||||
|
||||
it('should lead you to the channel previews', function(){
|
||||
var channel1Button = element(by.id(channel1.id + '-preview')),
|
||||
channel2Button = element(by.id(channel2.id + '-preview')),
|
||||
expectedUrl1 = 'http://localhost:4994/channels/' + channel1.id,
|
||||
expectedUrl2 = 'http://localhost:4994/channels/' + channel2.id;
|
||||
|
||||
expect(channel1Button.getAttribute('href')).toEqual(expectedUrl1);
|
||||
expect(channel2Button.getAttribute('href')).toEqual(expectedUrl2);
|
||||
|
||||
})
|
||||
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
var Channel = require("../../src/server/channel"),
|
||||
db = require("../../src/server/db");
|
||||
|
||||
describe('Delete Channel', function() {
|
||||
|
||||
var mockChannel;
|
||||
|
||||
beforeEach(function(){
|
||||
mockChannel = new Channel({
|
||||
name: "MockChannel",
|
||||
cells: [{
|
||||
urls: [{
|
||||
url: 'http://github.com'
|
||||
}]
|
||||
}]
|
||||
});
|
||||
mockChannel.save();
|
||||
browser.get('http://localhost:4994/channels');
|
||||
});
|
||||
|
||||
afterEach(function(){
|
||||
mockChannel.destroy();
|
||||
});
|
||||
|
||||
function deleteByName(name){
|
||||
browser.get('http://localhost:4994/channels');
|
||||
element(by.name(name)).click();
|
||||
element(by.id('delete')).click();
|
||||
}
|
||||
|
||||
it('should delete a channel', function(){
|
||||
expect(element(by.name('MockChannel')).isPresent()).toBe(true);
|
||||
deleteByName('MockChannel');
|
||||
expect(element(by.name('MockChannel')).isPresent()).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
var Channel = require("../../src/server/channel"),
|
||||
db = require("../../src/server/db");
|
||||
|
||||
describe('Edit Channel Page', function() {
|
||||
|
||||
var submitButton = element(by.name('submit')),
|
||||
nameInput = element(by.id('name')),
|
||||
firstUrlInput = element(by.name('cells0url0')),
|
||||
mockChannel;
|
||||
|
||||
beforeEach(function(){
|
||||
mockChannel = new Channel({
|
||||
name: "MockChannel",
|
||||
cells: [{
|
||||
urls: [{
|
||||
url: 'http://github.com'
|
||||
}]
|
||||
}]
|
||||
});
|
||||
mockChannel.save();
|
||||
|
||||
browser.get('http://localhost:4994/channels');
|
||||
});
|
||||
|
||||
afterEach(function(){
|
||||
mockChannel.destroy();
|
||||
});
|
||||
|
||||
// While on a channel edit page, edit the inputs to
|
||||
// the given name, url
|
||||
function editChannel(name, url){
|
||||
if (name){
|
||||
nameInput.clear();
|
||||
nameInput.sendKeys(name);
|
||||
}
|
||||
if (url){
|
||||
firstUrlInput.clear();
|
||||
firstUrlInput.sendKeys(url);
|
||||
}
|
||||
submitButton.click();
|
||||
}
|
||||
|
||||
// Needed for manual cleanup of channels
|
||||
function deleteByName(name){
|
||||
browser.get('http://localhost:4994/channels');
|
||||
element(by.name(name)).click();
|
||||
element(by.id('delete')).click();
|
||||
}
|
||||
|
||||
it('should redirect you to the channels page after saving changes', function(){
|
||||
var expectedUrl = 'http://localhost:4994/channels';
|
||||
|
||||
element(by.name('MockChannel')).click();
|
||||
submitButton.click();
|
||||
expect(browser.getCurrentUrl()).toEqual(expectedUrl);
|
||||
|
||||
deleteByName('MockChannel');
|
||||
});
|
||||
|
||||
it('should let you edit a channel name', function(){
|
||||
element(by.name('MockChannel')).click();
|
||||
editChannel('MockChannel-Edit', null);
|
||||
|
||||
expect(element(by.name('MockChannel-Edit')).isPresent()).toBe(true);
|
||||
expect(element(by.name('MockChannel')).isPresent()).toBe(false);
|
||||
|
||||
deleteByName('MockChannel-Edit');
|
||||
});
|
||||
|
||||
it('should let you edit a channel url', function(){
|
||||
var expectedUrl = 'http://example.com'
|
||||
|
||||
element(by.name('MockChannel')).click();
|
||||
editChannel(null, expectedUrl);
|
||||
element(by.name('MockChannel')).click();
|
||||
expect(firstUrlInput.getAttribute('value')).toEqual(expectedUrl);
|
||||
|
||||
deleteByName('MockChannel')
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
var Channel = require("../../src/server/channel"),
|
||||
db = require("../../src/server/db");
|
||||
|
||||
describe('New Channel Page', function() {
|
||||
|
||||
var submitButton = element(by.name('submit')),
|
||||
nameInput = element(by.id('name')),
|
||||
firstUrlInput = element(by.name('cells0url0')),
|
||||
mockNames = [];
|
||||
|
||||
beforeEach(function(){
|
||||
browser.get('http://localhost:4994/channels/new');
|
||||
});
|
||||
|
||||
afterEach(function(){
|
||||
browser.get('http://localhost:4994/channels');
|
||||
mockNames.forEach(function(name){
|
||||
deleteByName(name);
|
||||
});
|
||||
mockNames = []
|
||||
});
|
||||
|
||||
function createChannel(name, url){
|
||||
browser.get('http://localhost:4994/channels/new');
|
||||
nameInput.sendKeys(name);
|
||||
firstUrlInput.sendKeys(url);
|
||||
submitButton.click();
|
||||
mockNames.push(name)
|
||||
}
|
||||
|
||||
// Needed for manual cleanup of Channels
|
||||
function deleteByName(name){
|
||||
browser.get('http://localhost:4994/channels');
|
||||
element(by.name(name)).click();
|
||||
element(by.id('delete')).click();
|
||||
}
|
||||
|
||||
it('should redirect to channels after creating a new channel', function(){
|
||||
var expectedUrl = 'http://localhost:4994/channels';
|
||||
createChannel('mock-foo', 'http://github.com');
|
||||
expect(browser.getCurrentUrl()).toEqual(expectedUrl);
|
||||
});
|
||||
|
||||
it('should create a new channel', function(){
|
||||
createChannel('mock-foo', 'http://github.com');
|
||||
expect(element(by.name('mock-foo'))).not.toBe(null);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
var Receiver = require('../../src/server/receiver'),
|
||||
db = require('../../src/server/db');
|
||||
|
||||
describe('Chromecasts Index', function(){
|
||||
|
||||
var mockReceiver1,
|
||||
mockReceiver2;
|
||||
|
||||
beforeEach(function(){
|
||||
browser.get('http://localhost:4994/chromecasts');
|
||||
mockReceiver1 = new Receiver({
|
||||
name: 'MockReceiver1',
|
||||
location: "MockLocation1"
|
||||
});
|
||||
mockReceiver2 = new Receiver({
|
||||
name: 'MockReceiver2',
|
||||
location: "MockLocation2"
|
||||
});
|
||||
|
||||
mockReceiver1.save();
|
||||
mockReceiver2.save();
|
||||
});
|
||||
|
||||
afterEach(function(){
|
||||
mockReceiver1.destroy();
|
||||
mockReceiver2.destroy();
|
||||
});
|
||||
|
||||
function modelElementVal(model) {
|
||||
return element(by.model(model)).getAttribute('value')
|
||||
}
|
||||
|
||||
it('should show you the available chromecasts', function(){
|
||||
expect(element(by.id(mockReceiver1.id)).isPresent()).toBe(true);
|
||||
expect(element(by.id(mockReceiver2.id)).isPresent()).toBe(true);
|
||||
});
|
||||
|
||||
it('should take you to the edit form', function(){
|
||||
|
||||
var mockReceiver1Button = element(by.id(mockReceiver1.id)),
|
||||
mockReceiver2Button = element(by.id(mockReceiver2.id)),
|
||||
expectedUrl1 = 'http://localhost:4994/chromecasts/' + mockReceiver1.id + '/edit',
|
||||
expectedUrl2 = 'http://localhost:4994/chromecasts/' + mockReceiver2.id + '/edit';
|
||||
|
||||
mockReceiver1Button.click();
|
||||
expect(browser.getCurrentUrl()).toEqual(expectedUrl1);
|
||||
expect(modelElementVal('chromecast.name')).toEqual('MockReceiver1');
|
||||
expect(modelElementVal('chromecast.location')).toEqual('MockLocation1');
|
||||
|
||||
|
||||
browser.navigate().back();
|
||||
mockReceiver2Button.click();
|
||||
expect(browser.getCurrentUrl()).toEqual(expectedUrl2);
|
||||
expect(modelElementVal('chromecast.name')).toEqual('MockReceiver2');
|
||||
expect(modelElementVal('chromecast.location')).toEqual('MockLocation2');
|
||||
|
||||
});
|
||||
|
||||
it('should show you to the preview links', function(){
|
||||
expect(element(by.id(mockReceiver1.id + '-preview')).isPresent()).toBe(true);
|
||||
expect(element(by.id(mockReceiver2.id + '-preview')).isPresent()).toBe(true)
|
||||
});
|
||||
|
||||
it('should lead you to the channel previews', function(){
|
||||
|
||||
var mockReceiver1Button = element(by.id(mockReceiver1.id + '-preview')),
|
||||
mockReceiver2Button = element(by.id(mockReceiver2.id + '-preview')),
|
||||
expectedBaseUrl = 'http://localhost:4994/chromecasts/',
|
||||
expectedUrl1 = expectedBaseUrl + mockReceiver1.id,
|
||||
expectedUrl2 = expectedBaseUrl + mockReceiver2.id;
|
||||
|
||||
expect(mockReceiver1Button.getAttribute('href')).toEqual(expectedUrl1);
|
||||
expect(mockReceiver2Button.getAttribute('href')).toEqual(expectedUrl2);
|
||||
})
|
||||
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
var Receiver = require('../../src/server/receiver'),
|
||||
db = require('../../src/server/db');
|
||||
|
||||
describe('Delete Chromecast', function(){
|
||||
|
||||
var mockReceiver;
|
||||
|
||||
beforeEach(function(){
|
||||
browser.get('http://localhost:4994/chromecasts');
|
||||
mockReceiver = new Receiver({
|
||||
name: 'MockReceiver',
|
||||
location: "MockLocation1"
|
||||
});
|
||||
mockReceiver.save();
|
||||
});
|
||||
|
||||
afterEach(function(){
|
||||
mockReceiver.destroy();
|
||||
});
|
||||
|
||||
function deleteByName(name){
|
||||
browser.get('http://localhost:4994/chromecasts');
|
||||
element(by.name(name)).click();
|
||||
element(by.id('delete')).click();
|
||||
}
|
||||
|
||||
it('should delete a channel', function(){
|
||||
expect(element(by.name('MockReceiver')).isPresent()).toBe(true);
|
||||
deleteByName('MockReceiver');
|
||||
expect(element(by.name('MockReceiver')).isPresent()).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,92 @@
|
||||
var Receiver = require('../../src/server/receiver'),
|
||||
Channel = require('../../src/server/channel'),
|
||||
db = require('../../src/server/db');
|
||||
|
||||
describe('Edit Chromecast Page', function(){
|
||||
|
||||
var submitButton = element(by.name('submit')),
|
||||
nameInput = element(by.id('name')),
|
||||
locationInput = element(by.id('location')),
|
||||
mockReceiver,
|
||||
mockChannel;
|
||||
|
||||
beforeEach(function(){
|
||||
browser.get('http://localhost:4994/chromecasts');
|
||||
mockReceiver = new Receiver({
|
||||
name: 'MockReceiver',
|
||||
location: "MockLocation"
|
||||
});
|
||||
mockChannel = new Channel({
|
||||
name: "MockChannel",
|
||||
cells: [{
|
||||
urls: [{
|
||||
url: 'http://github.com'
|
||||
}]
|
||||
}]
|
||||
});
|
||||
|
||||
mockChannel.save();
|
||||
mockReceiver.save();
|
||||
});
|
||||
|
||||
afterEach(function(){
|
||||
mockReceiver.destroy();
|
||||
mockChannel.destroy();
|
||||
});
|
||||
|
||||
function deleteByName(name){
|
||||
browser.get('http://localhost:4994/chromecasts');
|
||||
element(by.name(name)).click();
|
||||
element(by.id('delete')).click();
|
||||
}
|
||||
|
||||
function editChromecast(location){
|
||||
locationInput.clear();
|
||||
locationInput.sendKeys(location);
|
||||
submitButton.click();
|
||||
}
|
||||
|
||||
function expectUrl(url){
|
||||
expect(browser.getCurrentUrl()).toEqual(url);
|
||||
}
|
||||
|
||||
it('should redirect you to the chromecasts page after saving changes', function(){
|
||||
element(by.name('MockReceiver')).click();
|
||||
submitButton.click();
|
||||
expectUrl('http://localhost:4994/chromecasts');
|
||||
|
||||
deleteByName('MockReceiver');
|
||||
});
|
||||
|
||||
it('should not let you edit a chromecast name', function(){
|
||||
element(by.name('MockReceiver')).click();
|
||||
expect(nameInput.getAttribute('disabled')).toEqual('true');
|
||||
});
|
||||
|
||||
it('should let you edit a chromecast location', function(){
|
||||
element(by.name('MockReceiver')).click();
|
||||
editChromecast('MockLocation-Edit');
|
||||
|
||||
expect(element(by.name('MockReceiver')).isPresent()).toBe(true);
|
||||
element(by.name('MockReceiver')).click();
|
||||
|
||||
expect(locationInput.getAttribute('value')).toEqual('MockLocation-Edit');
|
||||
|
||||
deleteByName('MockReceiver');
|
||||
|
||||
});
|
||||
|
||||
it('should let you edit a chromecast channel', function(){
|
||||
element(by.name('MockReceiver')).click();
|
||||
|
||||
var channel = element(by.css("select option[value='0']"));
|
||||
|
||||
channel.click();
|
||||
submitButton.click();
|
||||
|
||||
element(by.name('MockReceiver')).click();
|
||||
expect(channel.getAttribute('value')).toEqual('0');
|
||||
|
||||
deleteByName('MockReceiver');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
// conf.js
|
||||
exports.config = {
|
||||
seleniumAddress: 'http://localhost:4444/wd/hub',
|
||||
specs: [
|
||||
'channels/channels-test.js',
|
||||
'channels/delete-channel-test.js',
|
||||
'channels/edit-channel-test.js',
|
||||
'channels/new-channel-test.js',
|
||||
'chromecasts/chromecasts-test.js',
|
||||
'chromecasts/delete-chromecast-test.js',
|
||||
'chromecasts/edit-chromecast-test.js'
|
||||
]
|
||||
};
|
||||