diff --git a/public/images/icons/app-icon-144.png b/public/images/icons/app-icon-144.png
new file mode 100644
index 00000000..f54d5b33
Binary files /dev/null and b/public/images/icons/app-icon-144.png differ
diff --git a/public/images/icons/app-icon-192.png b/public/images/icons/app-icon-192.png
new file mode 100644
index 00000000..86e679ca
Binary files /dev/null and b/public/images/icons/app-icon-192.png differ
diff --git a/public/images/icons/app-icon-96.png b/public/images/icons/app-icon-96.png
new file mode 100644
index 00000000..9b1806a0
Binary files /dev/null and b/public/images/icons/app-icon-96.png differ
diff --git a/public/manifest.json b/public/manifest.json
new file mode 100644
index 00000000..0e256ba4
--- /dev/null
+++ b/public/manifest.json
@@ -0,0 +1,24 @@
+{
+ "short_name": "Pony.fm",
+ "name": "Pony.fm",
+ "icons": [
+ {
+ "src": "images/icons/app-icon-96.png",
+ "sizes": "96x96",
+ "type": "image/png"
+ },
+ {
+ "src": "images/icons/app-icon-144.png",
+ "sizes": "144x144",
+ "type": "image/png"
+ },
+ {
+ "src": "images/icons/app-icon-192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ }
+ ],
+ "start_url": "/",
+ "display": "standalone",
+ "orientation": "portrait"
+}
diff --git a/public/service-worker.js b/public/service-worker.js
new file mode 100644
index 00000000..5f0a0d39
--- /dev/null
+++ b/public/service-worker.js
@@ -0,0 +1,47 @@
+// Pony.fm - A community for pony fan music.
+// Copyright (C) 2016 Josef Citrine
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+var urlsToCache = [
+ '/',
+ '/build/styles/app.css',
+ '/build/scripts/app.js',
+ '/build/scripts/templates.js',
+ '/templates/directives/player.html',
+ '/templates/directives/search.html',
+ '/templates/directives/tracks-list.html',
+ '/templates/directives/users-list.html',
+ '/templates/directives/albums-list.html',
+ '/templates/directives/playlists-list.html',
+ '/templates/home/index.html',
+
+];
+
+var CACHE_NAME = 'pfm-cache-v1';
+
+// Set the callback for the install step
+self.addEventListener('install', function(event) {
+ // Doesn't do anything right now
+ // Could never get offline to fully
+ // work without bugs :(
+
+ // Perform install steps
+ event.waitUntil(
+ caches.open(CACHE_NAME)
+ .then(function(cache) {
+ console.log('Opened cache');
+ })
+ );
+});
diff --git a/resources/assets/scripts/app/controllers/application.coffee b/resources/assets/scripts/app/controllers/application.coffee
index 9bcfc1cd..a8010494 100644
--- a/resources/assets/scripts/app/controllers/application.coffee
+++ b/resources/assets/scripts/app/controllers/application.coffee
@@ -21,9 +21,20 @@ module.exports = angular.module('ponyfm').controller "application", [
$scope.$state = $state
$scope.$stateParams = $stateParams
$scope.isPinnedPlaylistSelected = false
+ $scope.menuActive = false
$loadingElement = null
loadingStateName = null
+ if 'serviceWorker' of navigator
+ console.log 'Service Worker is supported'
+ navigator.serviceWorker.register('service-worker.js').then((reg) ->
+ console.log 'SW registered', reg
+ ).catch (err) ->
+ console.log 'SW register failed', err
+
+ $scope.menuToggle = () ->
+ $scope.menuActive = !$scope.menuActive
+
if window.pfm.error
$state.transitionTo 'errors-' + window.pfm.error
@@ -60,6 +71,7 @@ module.exports = angular.module('ponyfm').controller "application", [
statesPreloaded = {}
$scope.$on '$stateChangeStart', (e, newState, newParams, oldState, oldParams) ->
+ $scope.menuActive = false
$scope.isPinnedPlaylistSelected = false
if newState.name == 'content.playlist'
diff --git a/resources/assets/scripts/shared/layout.coffee b/resources/assets/scripts/shared/layout.coffee
index 47f1d7d1..b32c7897 100644
--- a/resources/assets/scripts/shared/layout.coffee
+++ b/resources/assets/scripts/shared/layout.coffee
@@ -16,8 +16,14 @@
window.handleResize = () ->
windowHeight = $(window).height()
+ windowWidth = $(window).width()
+ isMobile = windowWidth <= 480
$siteBody = $ '.site-body'
- $siteBody.height windowHeight - $('header').height()
+
+ if isMobile
+ $siteBody.height windowHeight - $('.now-playing').height() * 2
+ else
+ $siteBody.height windowHeight - $('header').height()
$('.dropdown-menu').each () ->
$this = $ this
@@ -26,10 +32,11 @@ window.handleResize = () ->
'max-height': newMaxHeight
$('.stretch-to-bottom').each () ->
- $this = $ this
- newHeight = windowHeight - $this.offset().top
- if newHeight > 0
- $this.height newHeight
+ if !isMobile
+ $this = $ this
+ newHeight = windowHeight - $this.offset().top
+ if newHeight > 0
+ $this.height newHeight
$('.revealable').each () ->
$this = $ this
diff --git a/resources/assets/styles/app.less b/resources/assets/styles/app.less
index d952709d..0ee068c4 100644
--- a/resources/assets/styles/app.less
+++ b/resources/assets/styles/app.less
@@ -36,3 +36,4 @@
@import 'components/uploader';
@import 'components/search';
@import 'components/track-editor';
+@import 'mobile';
diff --git a/resources/assets/styles/layout.less b/resources/assets/styles/layout.less
index 5886807c..fbd1f155 100644
--- a/resources/assets/styles/layout.less
+++ b/resources/assets/styles/layout.less
@@ -31,35 +31,7 @@ html body {
}
header {
- > a {
- display: block;
- float: left;
- width: (@pfm-sidebar-size);
- height: 64px;
- line-height: 42px;
- background: #84528A;
- color: #fff;
- font-size: 24pt;
- font-weight: lighter;
- position: relative;
- z-index: 600;
- font-family: 'Josefin Sans', sans-serif;
- position: relative;
- &:hover {
- background: darken(#84528A, 25%);
- color: #fff;
- text-decoration: none;
- }
-
- img.logo {
- padding-left: 6px;
- width: 100%;
- max-width: 90%;
- padding-top: 13px;
- max-height: 45%
- }
- }
.user-details {
float: right;
@@ -116,15 +88,46 @@ header {
}
.sidebar {
+ > a {
+ display: block;
+ float: left;
+ width: (@pfm-sidebar-size);
+ height: 64px;
+ line-height: 42px;
+ background: #84528A;
+ color: #fff;
+ font-size: 24pt;
+ font-weight: lighter;
+ position: relative;
+ z-index: 600;
+ font-family: 'Josefin Sans', sans-serif;
+ position: relative;
+
+ &:hover {
+ background: darken(#84528A, 25%);
+ color: #fff;
+ text-decoration: none;
+ }
+
+ img.logo {
+ padding-left: 6px;
+ width: 100%;
+ max-width: 90%;
+ padding-top: 13px;
+ max-height: 45%
+ }
+ }
+
background: #515151;
width: @pfm-sidebar-size;
height: 100%;
- float: left;
list-style: none;
padding: 0px;
margin: 0px;
font-size: 10pt;
- position: relative;
+ position: absolute;
+ bottom: 0;
+ left: 0;
li {
margin: 0px;
@@ -251,3 +254,41 @@ header {
.chart-btn-container {
margin-bottom: 10px;
}
+
+.mobile-header {
+ display: none;
+}
+
+.burger-wrapper {
+ top:20px;
+ left: 20px;
+ position: absolute;
+}
+
+.burger {
+ width: 25px;
+ height: 20px;
+ top: 20px;
+ left: 20px;
+}
+
+.bun-top,.meat,.bun-bottom {
+ position: absolute;
+ display: block;
+ border-radius: 4px;
+ background: white;
+ width: 25px;
+ height: 2px;
+}
+
+.bun-top {
+ top: 0;
+}
+
+.meat {
+ top: 9px;
+}
+
+.bun-bottom {
+ bottom: 0;
+}
diff --git a/resources/assets/styles/mobile.less b/resources/assets/styles/mobile.less
new file mode 100644
index 00000000..a9b34a53
--- /dev/null
+++ b/resources/assets/styles/mobile.less
@@ -0,0 +1,127 @@
+@media only screen and (max-width: 480px) {
+ html, body {
+ overflow: hidden !important;
+ }
+
+ body > header {
+ margin-top: 64px;
+ }
+
+ .site-content {
+ margin-left: 0px;
+ overflow: scroll;
+ }
+
+ .now-playing {
+ margin-left: 0px;
+ position: fixed;
+ width: 100%;
+ bottom: 0;
+ z-index: 1010;
+ box-shadow: 0 0 8px rgba(0,0,0,0.5);
+ }
+
+ .user-details {
+ position: fixed;
+ top: 0;
+ right: 0;
+ z-index: 1002;
+ }
+
+ .mobile-header {
+ display: block !important;
+ position: absolute;
+ z-index: 1001;
+ background: #84528A;
+ height: 64px;
+ width: 100%;
+ top: 0;
+
+ .logo {
+ text-align: center;
+ vertical-align: middle;
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+ height: 40px;
+ width: 160px;
+ padding-top: 16px;
+
+ > img {
+ max-height: 75%;
+ }
+ }
+ }
+
+ .sidebar {
+ left: -170px;
+ transition: transform 0.5s ease;
+ transform: translateX(0px) translateZ(0);
+ perspective: 1000;
+ backface-visibility: hidden;
+ will-change: transform;
+ z-index: 1011;
+ height: ~"calc(100% - 64px)";
+ box-shadow: 0 8px 8px rgba(0,0,0,0.5);
+
+ > a {
+ display: none;
+ }
+ }
+
+ .sidebar.active {
+ transform: translateX(170px) translateZ(0);
+ }
+
+ .user-details > .avatar {
+ border: 0 !important;
+ box-shadow: initial !important;
+ background: transparent !important;
+
+ > img {
+ border-radius: 20px;
+ }
+
+ > span {
+ display: none !important;
+ }
+ }
+
+ .track-player {
+ margin-right: 0px !important;
+ .buttons {
+ li.volume, li.status {
+ display: none;
+ }
+ }
+ }
+
+ input.search-input {
+ border: 0;
+ }
+
+ .dropdowns {
+ margin-bottom: 0px;
+ > li {
+ margin-bottom: 10px;
+ > .btn {
+ font-size: 10pt;
+ }
+ }
+ }
+ .details-columns {
+ .cboxElement {
+ display: none;
+ }
+ .right {
+ float: none;
+ padding: 0px;
+ width: auto;
+ }
+ .left {
+ margin-right: 0px;
+ margin-left: 0px;
+ margin-top: 10px;
+ }
+ }
+}
diff --git a/resources/views/shared/_app_layout.blade.php b/resources/views/shared/_app_layout.blade.php
index 1017000a..515112d9 100644
--- a/resources/views/shared/_app_layout.blade.php
+++ b/resources/views/shared/_app_layout.blade.php
@@ -42,9 +42,16 @@