From 6072072b61d61075d0949baccf9fe8c339056215 Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Wed, 28 Feb 2018 16:09:34 +0000 Subject: [PATCH 01/34] Initial commit --- __init__.py | 0 apps.py | 5 +++++ tests.py | 3 +++ views.py | 3 +++ 4 files changed, 11 insertions(+) create mode 100644 __init__.py create mode 100644 apps.py create mode 100644 tests.py create mode 100644 views.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps.py b/apps.py new file mode 100644 index 00000000..5569d303 --- /dev/null +++ b/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class AssetsConfig(AppConfig): + name = 'assets' diff --git a/tests.py b/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/views.py b/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From 37d5ecf89a556eee5d9804277793a4fe4e516252 Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Wed, 28 Feb 2018 16:10:02 +0000 Subject: [PATCH 02/34] Initial Models --- admin.py | 26 ++++++++++++ migrations/0001_initial.py | 86 ++++++++++++++++++++++++++++++++++++++ migrations/__init__.py | 0 models.py | 39 +++++++++++++++++ urls.py | 5 +++ 5 files changed, 156 insertions(+) create mode 100644 admin.py create mode 100644 migrations/0001_initial.py create mode 100644 migrations/__init__.py create mode 100644 models.py create mode 100644 urls.py diff --git a/admin.py b/admin.py new file mode 100644 index 00000000..d0f14bff --- /dev/null +++ b/admin.py @@ -0,0 +1,26 @@ +from django.contrib import admin +from assets import models as assets + +@admin.register(assets.AssetCategory) +class AssetCategoryAdmin(admin.ModelAdmin): + pass + + +@admin.register(assets.AssetStatus) +class AssetStatusAdmin(admin.ModelAdmin): + pass + + +@admin.register(assets.Supplier) +class SupplierAdmin(admin.ModelAdmin): + pass + + +@admin.register(assets.Collection) +class CollectionAdmin(admin.ModelAdmin): + pass + + +@admin.register(assets.Asset) +class AssetAdmin(admin.ModelAdmin): + pass diff --git a/migrations/0001_initial.py b/migrations/0001_initial.py new file mode 100644 index 00000000..c1951b19 --- /dev/null +++ b/migrations/0001_initial.py @@ -0,0 +1,86 @@ +# Generated by Django 2.0.2 on 2018-02-28 16:06 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Asset', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('asset_id', models.IntegerField()), + ('description', models.CharField(max_length=120)), + ('serial_number', models.CharField(blank=True, max_length=150, null=True)), + ('date_acquired', models.DateField()), + ('date_sold', models.DateField(blank=True, null=True)), + ('purchase_price', models.IntegerField()), + ('salvage_value', models.IntegerField(blank=True, null=True)), + ('comments', models.TextField(blank=True, null=True)), + ('next_sched_maint', models.DateField(blank=True, null=True)), + ], + ), + migrations.CreateModel( + name='AssetCategory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=80)), + ], + options={ + 'verbose_name': 'Asset Category', + 'verbose_name_plural': 'Asset Categories', + }, + ), + migrations.CreateModel( + name='AssetStatus', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=80)), + ], + options={ + 'verbose_name': 'Asset Status', + 'verbose_name_plural': 'Asset Statuses', + }, + ), + migrations.CreateModel( + name='Collection', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=80)), + ], + ), + migrations.CreateModel( + name='Supplier', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=80)), + ], + ), + migrations.AddField( + model_name='asset', + name='category', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.AssetCategory'), + ), + migrations.AddField( + model_name='asset', + name='collection', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Collection'), + ), + migrations.AddField( + model_name='asset', + name='purchased_from', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.Supplier'), + ), + migrations.AddField( + model_name='asset', + name='status', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.AssetStatus'), + ), + ] diff --git a/migrations/__init__.py b/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/models.py b/models.py new file mode 100644 index 00000000..ff5ff9bf --- /dev/null +++ b/models.py @@ -0,0 +1,39 @@ +from django.db import models + +class AssetCategory(models.Model): + class Meta: + verbose_name = 'Asset Category' + verbose_name_plural = 'Asset Categories' + name = models.CharField(max_length=80) + + +class AssetStatus(models.Model): + class Meta: + verbose_name = 'Asset Status' + verbose_name_plural = 'Asset Statuses' + name = models.CharField(max_length=80) + + +class Supplier(models.Model): + name = models.CharField(max_length=80) + + +class Collection(models.Model): + name = models.CharField(max_length=80) + + +class Asset(models.Model): + asset_id = models.IntegerField() + description = models.CharField(max_length=120) + category = models.ForeignKey(to=AssetCategory, on_delete=models.CASCADE) + status = models.ForeignKey(to=AssetStatus, on_delete=models.CASCADE) + serial_number = models.CharField(max_length=150, blank=True, null=True) + purchased_from = models.ForeignKey(to=Supplier, on_delete=models.CASCADE, blank=True, null=True) + date_acquired = models.DateField() + date_sold = models.DateField(blank=True, null=True) + purchase_price = models.IntegerField() + salvage_value = models.IntegerField(blank=True, null=True) + comments = models.TextField(blank=True, null=True) + next_sched_maint = models.DateField(blank=True, null=True) + + collection = models.ForeignKey(to=Collection, on_delete=models.CASCADE) diff --git a/urls.py b/urls.py new file mode 100644 index 00000000..96f8167f --- /dev/null +++ b/urls.py @@ -0,0 +1,5 @@ +from django.urls import path + +urlpatterns = [ + +] From 680570696c67eb90ba26a7ea74fe94595f5b43db Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Wed, 28 Feb 2018 16:10:18 +0000 Subject: [PATCH 03/34] User creation management commands --- management/__init__.py | 0 management/commands/__init__.py | 0 management/commands/createBaseUsers.py | 26 ++++++++++++++++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 management/__init__.py create mode 100644 management/commands/__init__.py create mode 100644 management/commands/createBaseUsers.py diff --git a/management/__init__.py b/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/management/commands/__init__.py b/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/management/commands/createBaseUsers.py b/management/commands/createBaseUsers.py new file mode 100644 index 00000000..8bf0437a --- /dev/null +++ b/management/commands/createBaseUsers.py @@ -0,0 +1,26 @@ +from django.core.management.base import BaseCommand, CommandError +from django.contrib.auth.models import User + +from django.conf import settings + + +class Command(BaseCommand): + help = 'Creates a super user' + + def handle(self, *args, **options): + if not (settings.DEBUG or settings.STAGING): + raise CommandError('You cannot run this command in production') + + self.create_user_object('super', True, True) + self.create_user_object('staff', True) + self.create_user_object('basic') + + def create_user_object(self, name, staff=False, superuser=False): + user, created = User.objects.get_or_create( + username=name, defaults={'email': '{}@{}.com'.format(name, name), + 'first_name': name.title(), 'last_name': 'User', 'is_superuser': superuser, + 'is_staff': staff}) + + if created: + user.set_password(name) + user.save() \ No newline at end of file From e2a37beb832a91f4501a444555ca0010521a5a83 Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Tue, 6 Mar 2018 14:00:11 +0000 Subject: [PATCH 04/34] Added sample data commands --- management/commands/createSampleData.py | 67 +++++++++++++++++++++++++ management/commands/deleteSampleData.py | 29 +++++++++++ 2 files changed, 96 insertions(+) create mode 100644 management/commands/createSampleData.py create mode 100644 management/commands/deleteSampleData.py diff --git a/management/commands/createSampleData.py b/management/commands/createSampleData.py new file mode 100644 index 00000000..9a5af2e2 --- /dev/null +++ b/management/commands/createSampleData.py @@ -0,0 +1,67 @@ +from django.core.management.base import BaseCommand, CommandError +from django.utils import timezone +import random + +from assets import models + + +class Command(BaseCommand): + help = 'Creates some sample data for testing' + + def handle(self, *args, **kwargs): + from django.conf import settings + + if not (settings.DEBUG): + raise CommandError('You cannot run this command in production') + + random.seed('Some object to see the random number generator') + + self.create_categories() + self.create_statuses() + self.create_suppliers() + self.create_collections() + self.create_assets() + + def create_categories(self): + categories = ['Case', 'Video', 'General', 'Sound', 'Lighting', 'Rigging'] + + for cat in categories: + models.AssetCategory.objects.create(name=cat) + + def create_statuses(self): + statuses = ['In Service', 'Lost', 'Binned', 'Sold', 'Broken'] + + for stat in statuses: + models.AssetStatus.objects.create(name=stat) + + def create_suppliers(self): + suppliers = ["Acme, inc.","Widget Corp","123 Warehousing","Demo Company","Smith and Co.","Foo Bars","ABC Telecom","Fake Brothers","QWERTY Logistics","Demo, inc.","Sample Company","Sample, inc","Acme Corp","Allied Biscuit","Ankh-Sto Associates","Extensive Enterprise","Galaxy Corp","Globo-Chem","Mr. Sparkle","Globex Corporation","LexCorp","LuthorCorp","North Central Positronics","Omni Consimer Products","Praxis Corporation","Sombra Corporation","Sto Plains Holdings","Tessier-Ashpool","Wayne Enterprises","Wentworth Industries","ZiffCorp","Bluth Company","Strickland Propane","Thatherton Fuels","Three Waters","Water and Power","Western Gas & Electric","Mammoth Pictures","Mooby Corp","Gringotts","Thrift Bank","Flowers By Irene","The Legitimate Businessmens Club","Osato Chemicals","Transworld Consortium","Universal Export","United Fried Chicken","Virtucon","Kumatsu Motors","Keedsler Motors","Powell Motors","Industrial Automation","Sirius Cybernetics Corporation","U.S. Robotics and Mechanical Men","Colonial Movers","Corellian Engineering Corporation","Incom Corporation","General Products","Leeding Engines Ltd.","Blammo","Input, Inc.","Mainway Toys","Videlectrix","Zevo Toys","Ajax","Axis Chemical Co.","Barrytron","Carrys Candles","Cogswell Cogs","Spacely Sprockets","General Forge and Foundry","Duff Brewing Company","Dunder Mifflin","General Services Corporation","Monarch Playing Card Co.","Krustyco","Initech","Roboto Industries","Primatech","Sonky Rubber Goods","St. Anky Beer","Stay Puft Corporation","Vandelay Industries","Wernham Hogg","Gadgetron","Burleigh and Stronginthearm","BLAND Corporation","Nordyne Defense Dynamics","Petrox Oil Company","Roxxon","McMahon and Tate","Sixty Second Avenue","Charles Townsend Agency","Spade and Archer","Megadodo Publications","Rouster and Sideways","C.H. Lavatory and Sons","Globo Gym American Corp","The New Firm","SpringShield","Compuglobalhypermeganet","Data Systems","Gizmonic Institute","Initrode","Taggart Transcontinental","Atlantic Northern","Niagular","Plow King","Big Kahuna Burger","Big T Burgers and Fries","Chez Quis","Chotchkies","The Frying Dutchman","Klimpys","The Krusty Krab","Monks Diner","Milliways","Minuteman Cafe","Taco Grande","Tip Top Cafe","Moes Tavern","Central Perk","Chasers"] + + for supplier in suppliers: + models.Supplier.objects.create(name=supplier) + + def create_collections(self): + collections = ['An amp rack', 'Some video thing', 'Ampy patch boxes', 'The noise dept', 'Cable fun zone'] + + for collection in collections: + models.Collection.objects.create(name=collection) + + def create_assets(self): + assest_description = ['Large cable', 'Shiny thing', 'New lights', 'Really expensive microphone', 'Box of fuse flaps', 'Expensive tool we didn\'t agree to buy', 'Cable drums', 'Boring amount of tape', 'Video stuff no one knows how to use', 'More amplifiers', 'Heatshrink'] + + categories = models.AssetCategory.objects.all() + statuses = models.AssetStatus.objects.all() + suppliers = models.Supplier.objects.all() + + for i in range(100): + asset = models.Asset.objects.create( + description=random.choice(assest_description), + category=random.choice(categories), + status=random.choice(statuses), + date_acquired=timezone.now().date(), + ) + + if i % 3 == 0: + asset.purchased_from = random.choice(suppliers) + + asset.save() diff --git a/management/commands/deleteSampleData.py b/management/commands/deleteSampleData.py new file mode 100644 index 00000000..d58bee5f --- /dev/null +++ b/management/commands/deleteSampleData.py @@ -0,0 +1,29 @@ +from django.core.management.base import BaseCommand, CommandError + +from assets import models + + +class Command(BaseCommand): + help = 'Deletes testing sample data' + + def handle(self, *args, **kwargs): + from django.conf import settings + + if not (settings.DEBUG): + raise CommandError('You cannot run this command in production') + + # self.delete_categories() + # self.create_statuses() + # self.create_suppliers() + # self.create_collections() + # self.create_assets() + + self.delete_objects(models.AssetCategory) + self.delete_objects(models.AssetStatus) + self.delete_objects(models.Supplier) + self.delete_objects(models.Collection) + self.delete_objects(models.Asset) + + def delete_objects(self, model): + for object in model.objects.all(): + object.delete() From 9fb74ecc9ef7e3b88de1d716fc8fd0583e0dc131 Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Tue, 6 Mar 2018 14:01:22 +0000 Subject: [PATCH 05/34] Updated models and added post-save hook --- migrations/0002_auto_20180301_1654.py | 18 ++++++++++++ migrations/0003_auto_20180301_1700.py | 18 ++++++++++++ migrations/0004_auto_20180301_1711.py | 19 +++++++++++++ migrations/0005_auto_20180301_1725.py | 18 ++++++++++++ models.py | 40 +++++++++++++++++++++++++-- 5 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 migrations/0002_auto_20180301_1654.py create mode 100644 migrations/0003_auto_20180301_1700.py create mode 100644 migrations/0004_auto_20180301_1711.py create mode 100644 migrations/0005_auto_20180301_1725.py diff --git a/migrations/0002_auto_20180301_1654.py b/migrations/0002_auto_20180301_1654.py new file mode 100644 index 00000000..915a7151 --- /dev/null +++ b/migrations/0002_auto_20180301_1654.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.2 on 2018-03-01 16:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='asset', + name='asset_id', + field=models.IntegerField(blank=True), + ), + ] diff --git a/migrations/0003_auto_20180301_1700.py b/migrations/0003_auto_20180301_1700.py new file mode 100644 index 00000000..feef9a6c --- /dev/null +++ b/migrations/0003_auto_20180301_1700.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.2 on 2018-03-01 17:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0002_auto_20180301_1654'), + ] + + operations = [ + migrations.AlterField( + model_name='asset', + name='purchase_price', + field=models.IntegerField(blank=True, null=True), + ), + ] diff --git a/migrations/0004_auto_20180301_1711.py b/migrations/0004_auto_20180301_1711.py new file mode 100644 index 00000000..4c884e93 --- /dev/null +++ b/migrations/0004_auto_20180301_1711.py @@ -0,0 +1,19 @@ +# Generated by Django 2.0.2 on 2018-03-01 17:11 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0003_auto_20180301_1700'), + ] + + operations = [ + migrations.AlterField( + model_name='asset', + name='collection', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.Collection'), + ), + ] diff --git a/migrations/0005_auto_20180301_1725.py b/migrations/0005_auto_20180301_1725.py new file mode 100644 index 00000000..49fae9c6 --- /dev/null +++ b/migrations/0005_auto_20180301_1725.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.2 on 2018-03-01 17:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0004_auto_20180301_1711'), + ] + + operations = [ + migrations.AlterField( + model_name='asset', + name='asset_id', + field=models.IntegerField(blank=True, null=True), + ), + ] diff --git a/models.py b/models.py index ff5ff9bf..1a771819 100644 --- a/models.py +++ b/models.py @@ -1,4 +1,8 @@ from django.db import models +from django.db.models.signals import post_save +from django.dispatch import receiver +from django.urls import reverse + class AssetCategory(models.Model): class Meta: @@ -6,6 +10,9 @@ class AssetCategory(models.Model): verbose_name_plural = 'Asset Categories' name = models.CharField(max_length=80) + def __str__(self): + return self.name + class AssetStatus(models.Model): class Meta: @@ -13,17 +20,26 @@ class AssetStatus(models.Model): verbose_name_plural = 'Asset Statuses' name = models.CharField(max_length=80) + def __str__(self): + return self.name + class Supplier(models.Model): name = models.CharField(max_length=80) + def __str__(self): + return self.name + class Collection(models.Model): name = models.CharField(max_length=80) + def __str__(self): + return self.name + class Asset(models.Model): - asset_id = models.IntegerField() + asset_id = models.IntegerField(blank=True, null=True) description = models.CharField(max_length=120) category = models.ForeignKey(to=AssetCategory, on_delete=models.CASCADE) status = models.ForeignKey(to=AssetStatus, on_delete=models.CASCADE) @@ -31,9 +47,27 @@ class Asset(models.Model): purchased_from = models.ForeignKey(to=Supplier, on_delete=models.CASCADE, blank=True, null=True) date_acquired = models.DateField() date_sold = models.DateField(blank=True, null=True) - purchase_price = models.IntegerField() + purchase_price = models.IntegerField(blank=True, null=True) salvage_value = models.IntegerField(blank=True, null=True) comments = models.TextField(blank=True, null=True) next_sched_maint = models.DateField(blank=True, null=True) - collection = models.ForeignKey(to=Collection, on_delete=models.CASCADE) + collection = models.ForeignKey(to=Collection, on_delete=models.CASCADE, blank=True, null=True) + + def get_absolute_url(self): + return reverse('asset_detail', kwargs={'pk': self.pk}) + + def __str__(self): + return str(self.asset_id) + ' - ' + self.description + + +# Automatically updates Asset.asset_id to Asset.pk if none is given by the user +@receiver(post_save, sender=Asset) +def update_asset_id(sender, instance, **kwargs): + post_save.disconnect(update_asset_id, sender=sender) + + if not instance.asset_id: + instance.asset_id = instance.pk + instance.save() + + post_save.connect(update_asset_id, sender=sender) From 0e8a23733fdd3f16cfee653c5cad5b57e8ebc351 Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Tue, 6 Mar 2018 14:02:24 +0000 Subject: [PATCH 06/34] Working CRUD views --- urls.py | 8 +++++++- views.py | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/urls.py b/urls.py index 96f8167f..bfc532c9 100644 --- a/urls.py +++ b/urls.py @@ -1,5 +1,11 @@ from django.urls import path +from assets import views urlpatterns = [ - + path('', views.Index.as_view(), name='index'), + path('assets/', views.AssetList.as_view(), name='asset_list'), + path('assets//', views.AssetDetail.as_view(), name='asset_detail'), + path('assets/create', views.AssetCreate.as_view(), name='asset_create'), + path('assets//update', views.AssetUpdate.as_view(), name='asset_update'), + path('assets//delete', views.AssetDelete.as_view(), name='asset_delete'), ] diff --git a/views.py b/views.py index 91ea44a2..ef648c8a 100644 --- a/views.py +++ b/views.py @@ -1,3 +1,37 @@ from django.shortcuts import render +from django.views import generic +from django.urls import reverse_lazy +from assets import models, forms -# Create your views here. + +class Index(generic.TemplateView): + template_name = 'index.html' + + +class AssetList(generic.ListView): + model = models.Asset + template_name = 'assets/asset_list.html' + + +class AssetDetail(generic.DetailView): + model = models.Asset + template_name = 'assets/asset_detail.html' + + +class AssetCreate(generic.CreateView): + model = models.Asset + fields = '__all__' + template_name = 'assets/asset_form.html' + # success_url = reverse_lazy('asset_list') + + +class AssetUpdate(generic.UpdateView): + model = models.Asset + fields = '__all__' + template_name = 'assets/asset_form.html' + + +class AssetDelete(generic.DeleteView): + model = models.Asset + template_name = 'confirm_delete.html' + success_url = reverse_lazy('asset_list') From 116b228de30e81059c317e90567a04038b2323d4 Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Mon, 16 Jul 2018 00:27:47 +0100 Subject: [PATCH 07/34] Added materialize sass --- static/fonts/Roboto/Roboto-Black.ttf | Bin 0 -> 171480 bytes static/fonts/Roboto/Roboto-BlackItalic.ttf | Bin 0 -> 177552 bytes static/fonts/Roboto/Roboto-Bold.ttf | Bin 0 -> 170760 bytes static/fonts/Roboto/Roboto-BoldItalic.ttf | Bin 0 -> 174952 bytes static/fonts/Roboto/Roboto-Italic.ttf | Bin 0 -> 173932 bytes static/fonts/Roboto/Roboto-Light.ttf | Bin 0 -> 170420 bytes static/fonts/Roboto/Roboto-LightItalic.ttf | Bin 0 -> 176616 bytes static/fonts/Roboto/Roboto-Medium.ttf | Bin 0 -> 172064 bytes static/fonts/Roboto/Roboto-MediumItalic.ttf | Bin 0 -> 176864 bytes static/fonts/Roboto/Roboto-Regular.ttf | Bin 0 -> 171676 bytes static/fonts/Roboto/Roboto-Thin.ttf | Bin 0 -> 171904 bytes static/fonts/Roboto/Roboto-ThinItalic.ttf | Bin 0 -> 176300 bytes static/js/anime.min.js | 34 + static/js/autocomplete.js | 450 + static/js/bin/materialize.js | 12360 ++++++++++++++++ static/js/bin/materialize.min.js | 6 + static/js/buttons.js | 354 + static/js/cards.js | 40 + static/js/carousel.js | 717 + static/js/cash.js | 960 ++ static/js/characterCounter.js | 136 + static/js/chips.js | 481 + static/js/collapsible.js | 275 + static/js/component.js | 44 + static/js/datepicker.js | 975 ++ static/js/dropdown.js | 615 + static/js/forms.js | 275 + static/js/global.js | 425 + static/js/materialbox.js | 453 + static/js/modal.js | 382 + static/js/parallax.js | 138 + static/js/pushpin.js | 145 + static/js/range.js | 263 + static/js/scrollspy.js | 295 + static/js/select.js | 422 + static/js/sidenav.js | 580 + static/js/slider.js | 359 + static/js/tabs.js | 402 + static/js/tapTarget.js | 314 + static/js/timepicker.js | 647 + static/js/toasts.js | 310 + static/js/tooltip.js | 303 + static/js/waves.js | 335 + static/sass/components/_badges.scss | 55 + static/sass/components/_buttons.scss | 322 + static/sass/components/_cards.scss | 195 + static/sass/components/_carousel.scss | 90 + static/sass/components/_chips.scss | 90 + static/sass/components/_collapsible.scss | 91 + static/sass/components/_color-classes.scss | 32 + static/sass/components/_color-variables.scss | 370 + static/sass/components/_datepicker.scss | 191 + static/sass/components/_dropdown.scss | 85 + static/sass/components/_global.scss | 769 + static/sass/components/_grid.scss | 156 + .../components/_icons-material-design.scss | 5 + static/sass/components/_materialbox.scss | 43 + static/sass/components/_modal.scss | 94 + static/sass/components/_navbar.scss | 208 + static/sass/components/_normalize.scss | 447 + static/sass/components/_preloader.scss | 334 + static/sass/components/_pulse.scss | 34 + static/sass/components/_sidenav.scss | 216 + static/sass/components/_slider.scss | 92 + .../sass/components/_table_of_contents.scss | 33 + static/sass/components/_tabs.scss | 99 + static/sass/components/_tapTarget.scss | 103 + static/sass/components/_timepicker.scss | 183 + static/sass/components/_toast.scss | 58 + static/sass/components/_tooltip.scss | 32 + static/sass/components/_transitions.scss | 13 + static/sass/components/_typography.scss | 60 + static/sass/components/_variables.scss | 349 + static/sass/components/_waves.scss | 114 + static/sass/components/forms/_checkboxes.scss | 200 + static/sass/components/forms/_file-input.scss | 44 + static/sass/components/forms/_forms.scss | 22 + .../sass/components/forms/_input-fields.scss | 354 + .../sass/components/forms/_radio-buttons.scss | 115 + static/sass/components/forms/_range.scss | 161 + static/sass/components/forms/_select.scss | 180 + static/sass/components/forms/_switches.scss | 89 + static/sass/main.scss | 42 + static/sass/materialize.scss | 49 + 84 files changed, 29714 insertions(+) create mode 100755 static/fonts/Roboto/Roboto-Black.ttf create mode 100755 static/fonts/Roboto/Roboto-BlackItalic.ttf create mode 100755 static/fonts/Roboto/Roboto-Bold.ttf create mode 100755 static/fonts/Roboto/Roboto-BoldItalic.ttf create mode 100755 static/fonts/Roboto/Roboto-Italic.ttf create mode 100755 static/fonts/Roboto/Roboto-Light.ttf create mode 100755 static/fonts/Roboto/Roboto-LightItalic.ttf create mode 100755 static/fonts/Roboto/Roboto-Medium.ttf create mode 100755 static/fonts/Roboto/Roboto-MediumItalic.ttf create mode 100755 static/fonts/Roboto/Roboto-Regular.ttf create mode 100755 static/fonts/Roboto/Roboto-Thin.ttf create mode 100755 static/fonts/Roboto/Roboto-ThinItalic.ttf create mode 100644 static/js/anime.min.js create mode 100644 static/js/autocomplete.js create mode 100644 static/js/bin/materialize.js create mode 100644 static/js/bin/materialize.min.js create mode 100644 static/js/buttons.js create mode 100644 static/js/cards.js create mode 100644 static/js/carousel.js create mode 100644 static/js/cash.js create mode 100644 static/js/characterCounter.js create mode 100644 static/js/chips.js create mode 100644 static/js/collapsible.js create mode 100644 static/js/component.js create mode 100644 static/js/datepicker.js create mode 100644 static/js/dropdown.js create mode 100644 static/js/forms.js create mode 100644 static/js/global.js create mode 100644 static/js/materialbox.js create mode 100644 static/js/modal.js create mode 100644 static/js/parallax.js create mode 100644 static/js/pushpin.js create mode 100644 static/js/range.js create mode 100644 static/js/scrollspy.js create mode 100644 static/js/select.js create mode 100644 static/js/sidenav.js create mode 100644 static/js/slider.js create mode 100644 static/js/tabs.js create mode 100644 static/js/tapTarget.js create mode 100644 static/js/timepicker.js create mode 100644 static/js/toasts.js create mode 100644 static/js/tooltip.js create mode 100644 static/js/waves.js create mode 100644 static/sass/components/_badges.scss create mode 100644 static/sass/components/_buttons.scss create mode 100644 static/sass/components/_cards.scss create mode 100644 static/sass/components/_carousel.scss create mode 100644 static/sass/components/_chips.scss create mode 100644 static/sass/components/_collapsible.scss create mode 100644 static/sass/components/_color-classes.scss create mode 100644 static/sass/components/_color-variables.scss create mode 100644 static/sass/components/_datepicker.scss create mode 100644 static/sass/components/_dropdown.scss create mode 100644 static/sass/components/_global.scss create mode 100644 static/sass/components/_grid.scss create mode 100644 static/sass/components/_icons-material-design.scss create mode 100644 static/sass/components/_materialbox.scss create mode 100644 static/sass/components/_modal.scss create mode 100644 static/sass/components/_navbar.scss create mode 100644 static/sass/components/_normalize.scss create mode 100644 static/sass/components/_preloader.scss create mode 100644 static/sass/components/_pulse.scss create mode 100644 static/sass/components/_sidenav.scss create mode 100644 static/sass/components/_slider.scss create mode 100644 static/sass/components/_table_of_contents.scss create mode 100644 static/sass/components/_tabs.scss create mode 100644 static/sass/components/_tapTarget.scss create mode 100644 static/sass/components/_timepicker.scss create mode 100644 static/sass/components/_toast.scss create mode 100644 static/sass/components/_tooltip.scss create mode 100644 static/sass/components/_transitions.scss create mode 100644 static/sass/components/_typography.scss create mode 100644 static/sass/components/_variables.scss create mode 100644 static/sass/components/_waves.scss create mode 100644 static/sass/components/forms/_checkboxes.scss create mode 100644 static/sass/components/forms/_file-input.scss create mode 100644 static/sass/components/forms/_forms.scss create mode 100644 static/sass/components/forms/_input-fields.scss create mode 100644 static/sass/components/forms/_radio-buttons.scss create mode 100644 static/sass/components/forms/_range.scss create mode 100644 static/sass/components/forms/_select.scss create mode 100644 static/sass/components/forms/_switches.scss create mode 100644 static/sass/main.scss create mode 100644 static/sass/materialize.scss diff --git a/static/fonts/Roboto/Roboto-Black.ttf b/static/fonts/Roboto/Roboto-Black.ttf new file mode 100755 index 0000000000000000000000000000000000000000..689fe5cb3c715f2944fec30e43ccb8a2b10625d3 GIT binary patch literal 171480 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

~M8)?dE#I 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 Xk2aT?%S{K<(OhA!BxPw6dg^}wc$YKS literal 0 HcmV?d00001 diff --git a/static/fonts/Roboto/Roboto-BlackItalic.ttf b/static/fonts/Roboto/Roboto-BlackItalic.ttf new file mode 100755 index 0000000000000000000000000000000000000000..0b4e0ee108899ddfef739f48d2aa9475b8b41a03 GIT binary patch literal 177552 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�_SD^G{wdrrHYdIGDG`C$ZMlG79#yZF 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 literal 0 HcmV?d00001 diff --git a/static/fonts/Roboto/Roboto-Bold.ttf b/static/fonts/Roboto/Roboto-Bold.ttf new file mode 100755 index 0000000000000000000000000000000000000000..d3f01ad245b628f386ac95786f53167038720eb2 GIT binary patch literal 170760 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�!3r53e9=KAt@g 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

'),this.$slides.each(function(t,e){var i=s('
  • ');n.$indicators.append(i[0])}),this.$el.append(this.$indicators[0]),this.$indicators=this.$indicators.children("li.indicator-item"))}},{key:"_removeIndicators",value:function(){this.$el.find("ul.indicators").remove()}},{key:"set",value:function(t){var e=this;if(t>=this.$slides.length?t=0:t<0&&(t=this.$slides.length-1),this.activeIndex!=t){this.$active=this.$slides.eq(this.activeIndex);var i=this.$active.find(".caption");this.$active.removeClass("active"),o({targets:this.$active[0],opacity:0,duration:this.options.duration,easing:"easeOutQuad",complete:function(){e.$slides.not(".active").each(function(t){o({targets:t,opacity:0,translateX:0,translateY:0,duration:0,easing:"easeOutQuad"})})}}),this._animateCaptionIn(i[0],this.options.duration),this.options.indicators&&(this.$indicators.eq(this.activeIndex).removeClass("active"),this.$indicators.eq(t).addClass("active")),o({targets:this.$slides.eq(t)[0],opacity:1,duration:this.options.duration,easing:"easeOutQuad"}),o({targets:this.$slides.eq(t).find(".caption")[0],opacity:1,translateX:0,translateY:0,duration:this.options.duration,delay:this.options.duration,easing:"easeOutQuad"}),this.$slides.eq(t).addClass("active"),this.activeIndex=t,this.start()}}},{key:"pause",value:function(){clearInterval(this.interval)}},{key:"start",value:function(){clearInterval(this.interval),this.interval=setInterval(this._handleIntervalBound,this.options.duration+this.options.interval)}},{key:"next",value:function(){var t=this.activeIndex+1;t>=this.$slides.length?t=0:t<0&&(t=this.$slides.length-1),this.set(t)}},{key:"prev",value:function(){var t=this.activeIndex-1;t>=this.$slides.length?t=0:t<0&&(t=this.$slides.length-1),this.set(t)}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Slider}},{key:"defaults",get:function(){return e}}]),n}();M.Slider=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"slider","M_Slider")}(cash,M.anime),function(n,s){n(document).on("click",".card",function(t){if(n(this).children(".card-reveal").length){var i=n(t.target).closest(".card");void 0===i.data("initialOverflow")&&i.data("initialOverflow",void 0===i.css("overflow")?"":i.css("overflow"));var e=n(this).find(".card-reveal");n(t.target).is(n(".card-reveal .card-title"))||n(t.target).is(n(".card-reveal .card-title i"))?s({targets:e[0],translateY:0,duration:225,easing:"easeInOutQuad",complete:function(t){var e=t.animatables[0].target;n(e).css({display:"none"}),i.css("overflow",i.data("initialOverflow"))}}):(n(t.target).is(n(".card .activator"))||n(t.target).is(n(".card .activator i")))&&(i.css("overflow","hidden"),e.css({display:"block"}),s({targets:e[0],translateY:"-100%",duration:300,easing:"easeInOutQuad"}))}})}(cash,M.anime),function(h){"use strict";var e={data:[],placeholder:"",secondaryPlaceholder:"",autocompleteOptions:{},limit:1/0,onChipAdd:null,onChipSelect:null,onChipDelete:null},t=function(t){function l(t,e){_classCallCheck(this,l);var i=_possibleConstructorReturn(this,(l.__proto__||Object.getPrototypeOf(l)).call(this,l,t,e));return(i.el.M_Chips=i).options=h.extend({},l.defaults,e),i.$el.addClass("chips input-field"),i.chipsData=[],i.$chips=h(),i._setupInput(),i.hasAutocomplete=0"),this.$el.append(this.$input)),this.$input.addClass("input")}},{key:"_setupLabel",value:function(){this.$label=this.$el.find("label"),this.$label.length&&this.$label.setAttribute("for",this.$input.attr("id"))}},{key:"_setPlaceholder",value:function(){void 0!==this.chipsData&&!this.chipsData.length&&this.options.placeholder?h(this.$input).prop("placeholder",this.options.placeholder):(void 0===this.chipsData||this.chipsData.length)&&this.options.secondaryPlaceholder&&h(this.$input).prop("placeholder",this.options.secondaryPlaceholder)}},{key:"_isValid",value:function(t){if(t.hasOwnProperty("tag")&&""!==t.tag){for(var e=!1,i=0;i=this.options.limit)){var e=this._renderChip(t);this.$chips.add(e),this.chipsData.push(t),h(this.$input).before(e),this._setPlaceholder(),"function"==typeof this.options.onChipAdd&&this.options.onChipAdd.call(this,this.$el,e)}}},{key:"deleteChip",value:function(t){var e=this.$chips.eq(t);this.$chips.eq(t).remove(),this.$chips=this.$chips.filter(function(t){return 0<=h(t).index()}),this.chipsData.splice(t,1),this._setPlaceholder(),"function"==typeof this.options.onChipDelete&&this.options.onChipDelete.call(this,this.$el,e[0])}},{key:"selectChip",value:function(t){var e=this.$chips.eq(t);(this._selectedChip=e)[0].focus(),"function"==typeof this.options.onChipSelect&&this.options.onChipSelect.call(this,this.$el,e[0])}}],[{key:"init",value:function(t,e){return _get(l.__proto__||Object.getPrototypeOf(l),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Chips}},{key:"_handleChipsKeydown",value:function(t){l._keydown=!0;var e=h(t.target).closest(".chips"),i=t.target&&e.length;if(!h(t.target).is("input, textarea")&&i){var n=e[0].M_Chips;if(8===t.keyCode||46===t.keyCode){t.preventDefault();var s=n.chipsData.length;if(n._selectedChip){var o=n._selectedChip.index();n.deleteChip(o),n._selectedChip=null,s=Math.max(o-1,0)}n.chipsData.length&&n.selectChip(s)}else if(37===t.keyCode){if(n._selectedChip){var a=n._selectedChip.index()-1;if(a<0)return;n.selectChip(a)}}else if(39===t.keyCode&&n._selectedChip){var r=n._selectedChip.index()+1;r>=n.chipsData.length?n.$input[0].focus():n.selectChip(r)}}}},{key:"_handleChipsKeyup",value:function(t){l._keydown=!1}},{key:"_handleChipsBlur",value:function(t){l._keydown||(h(t.target).closest(".chips")[0].M_Chips._selectedChip=null)}},{key:"defaults",get:function(){return e}}]),l}();t._keydown=!1,M.Chips=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"chips","M_Chips"),h(document).ready(function(){h(document.body).on("click",".chip .close",function(){var t=h(this).closest(".chips");t.length&&t[0].M_Chips||h(this).closest(".chip").remove()})})}(cash),function(s){"use strict";var e={top:0,bottom:1/0,offset:0,onPositionChange:null},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_Pushpin=i).options=s.extend({},n.defaults,e),i.originalOffset=i.el.offsetTop,n._pushpins.push(i),i._setupEventHandlers(),i._updatePosition(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this.el.style.top=null,this._removePinClasses(),this._removeEventHandlers();var t=n._pushpins.indexOf(this);n._pushpins.splice(t,1)}},{key:"_setupEventHandlers",value:function(){document.addEventListener("scroll",n._updateElements)}},{key:"_removeEventHandlers",value:function(){document.removeEventListener("scroll",n._updateElements)}},{key:"_updatePosition",value:function(){var t=M.getDocumentScrollTop()+this.options.offset;this.options.top<=t&&this.options.bottom>=t&&!this.el.classList.contains("pinned")&&(this._removePinClasses(),this.el.style.top=this.options.offset+"px",this.el.classList.add("pinned"),"function"==typeof this.options.onPositionChange&&this.options.onPositionChange.call(this,"pinned")),tthis.options.bottom&&!this.el.classList.contains("pin-bottom")&&(this._removePinClasses(),this.el.classList.add("pin-bottom"),this.el.style.top=this.options.bottom-this.originalOffset+"px","function"==typeof this.options.onPositionChange&&this.options.onPositionChange.call(this,"pin-bottom"))}},{key:"_removePinClasses",value:function(){this.el.classList.remove("pin-top"),this.el.classList.remove("pinned"),this.el.classList.remove("pin-bottom")}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Pushpin}},{key:"_updateElements",value:function(){for(var t in n._pushpins){n._pushpins[t]._updatePosition()}}},{key:"defaults",get:function(){return e}}]),n}();t._pushpins=[],M.Pushpin=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"pushpin","M_Pushpin")}(cash),function(r,s){"use strict";var e={direction:"top",hoverEnabled:!0,toolbarEnabled:!1};r.fn.reverse=[].reverse;var t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_FloatingActionButton=i).options=r.extend({},n.defaults,e),i.isOpen=!1,i.$anchor=i.$el.children("a").first(),i.$menu=i.$el.children("ul").first(),i.$floatingBtns=i.$el.find("ul .btn-floating"),i.$floatingBtnsReverse=i.$el.find("ul .btn-floating").reverse(),i.offsetY=0,i.offsetX=0,i.$el.addClass("direction-"+i.options.direction),"top"===i.options.direction?i.offsetY=40:"right"===i.options.direction?i.offsetX=-40:"bottom"===i.options.direction?i.offsetY=-40:i.offsetX=40,i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.M_FloatingActionButton=void 0}},{key:"_setupEventHandlers",value:function(){this._handleFABClickBound=this._handleFABClick.bind(this),this._handleOpenBound=this.open.bind(this),this._handleCloseBound=this.close.bind(this),this.options.hoverEnabled&&!this.options.toolbarEnabled?(this.el.addEventListener("mouseenter",this._handleOpenBound),this.el.addEventListener("mouseleave",this._handleCloseBound)):this.el.addEventListener("click",this._handleFABClickBound)}},{key:"_removeEventHandlers",value:function(){this.options.hoverEnabled&&!this.options.toolbarEnabled?(this.el.removeEventListener("mouseenter",this._handleOpenBound),this.el.removeEventListener("mouseleave",this._handleCloseBound)):this.el.removeEventListener("click",this._handleFABClickBound)}},{key:"_handleFABClick",value:function(){this.isOpen?this.close():this.open()}},{key:"_handleDocumentClick",value:function(t){r(t.target).closest(this.$menu).length||this.close()}},{key:"open",value:function(){this.isOpen||(this.options.toolbarEnabled?this._animateInToolbar():this._animateInFAB(),this.isOpen=!0)}},{key:"close",value:function(){this.isOpen&&(this.options.toolbarEnabled?(window.removeEventListener("scroll",this._handleCloseBound,!0),document.body.removeEventListener("click",this._handleDocumentClickBound,!0),this._animateOutToolbar()):this._animateOutFAB(),this.isOpen=!1)}},{key:"_animateInFAB",value:function(){var e=this;this.$el.addClass("active");var i=0;this.$floatingBtnsReverse.each(function(t){s({targets:t,opacity:1,scale:[.4,1],translateY:[e.offsetY,0],translateX:[e.offsetX,0],duration:275,delay:i,easing:"easeInOutQuad"}),i+=40})}},{key:"_animateOutFAB",value:function(){var e=this;this.$floatingBtnsReverse.each(function(t){s.remove(t),s({targets:t,opacity:0,scale:.4,translateY:e.offsetY,translateX:e.offsetX,duration:175,easing:"easeOutQuad",complete:function(){e.$el.removeClass("active")}})})}},{key:"_animateInToolbar",value:function(){var t,e=this,i=window.innerWidth,n=window.innerHeight,s=this.el.getBoundingClientRect(),o=r('
    '),a=this.$anchor.css("background-color");this.$anchor.append(o),this.offsetX=s.left-i/2+s.width/2,this.offsetY=n-s.bottom,t=i/o[0].clientWidth,this.btnBottom=s.bottom,this.btnLeft=s.left,this.btnWidth=s.width,this.$el.addClass("active"),this.$el.css({"text-align":"center",width:"100%",bottom:0,left:0,transform:"translateX("+this.offsetX+"px)",transition:"none"}),this.$anchor.css({transform:"translateY("+-this.offsetY+"px)",transition:"none"}),o.css({"background-color":a}),setTimeout(function(){e.$el.css({transform:"",transition:"transform .2s cubic-bezier(0.550, 0.085, 0.680, 0.530), background-color 0s linear .2s"}),e.$anchor.css({overflow:"visible",transform:"",transition:"transform .2s"}),setTimeout(function(){e.$el.css({overflow:"hidden","background-color":a}),o.css({transform:"scale("+t+")",transition:"transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)"}),e.$menu.children("li").children("a").css({opacity:1}),e._handleDocumentClickBound=e._handleDocumentClick.bind(e),window.addEventListener("scroll",e._handleCloseBound,!0),document.body.addEventListener("click",e._handleDocumentClickBound,!0)},100)},0)}},{key:"_animateOutToolbar",value:function(){var t=this,e=window.innerWidth,i=window.innerHeight,n=this.$el.find(".fab-backdrop"),s=this.$anchor.css("background-color");this.offsetX=this.btnLeft-e/2+this.btnWidth/2,this.offsetY=i-this.btnBottom,this.$el.removeClass("active"),this.$el.css({"background-color":"transparent",transition:"none"}),this.$anchor.css({transition:"none"}),n.css({transform:"scale(0)","background-color":s}),this.$menu.children("li").children("a").css({opacity:""}),setTimeout(function(){n.remove(),t.$el.css({"text-align":"",width:"",bottom:"",left:"",overflow:"","background-color":"",transform:"translate3d("+-t.offsetX+"px,0,0)"}),t.$anchor.css({overflow:"",transform:"translate3d(0,"+t.offsetY+"px,0)"}),setTimeout(function(){t.$el.css({transform:"translate3d(0,0,0)",transition:"transform .2s"}),t.$anchor.css({transform:"translate3d(0,0,0)",transition:"transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)"})},20)},200)}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_FloatingActionButton}},{key:"defaults",get:function(){return e}}]),n}();M.FloatingActionButton=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"floatingActionButton","M_FloatingActionButton")}(cash,M.anime),function(g){"use strict";var e={autoClose:!1,format:"mmm dd, yyyy",parse:null,defaultDate:null,setDefaultDate:!1,disableWeekends:!1,disableDayFn:null,firstDay:0,minDate:null,maxDate:null,yearRange:10,minYear:0,maxYear:9999,minMonth:void 0,maxMonth:void 0,startRange:null,endRange:null,isRTL:!1,showMonthAfterYear:!1,showDaysInNextAndPreviousMonths:!1,container:null,showClearBtn:!1,i18n:{cancel:"Cancel",clear:"Clear",done:"Ok",previousMonth:"‹",nextMonth:"›",months:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],weekdays:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],weekdaysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],weekdaysAbbrev:["S","M","T","W","T","F","S"]},events:[],onSelect:null,onOpen:null,onClose:null,onDraw:null},t=function(t){function B(t,e){_classCallCheck(this,B);var i=_possibleConstructorReturn(this,(B.__proto__||Object.getPrototypeOf(B)).call(this,B,t,e));(i.el.M_Datepicker=i).options=g.extend({},B.defaults,e),e&&e.hasOwnProperty("i18n")&&"object"==typeof e.i18n&&(i.options.i18n=g.extend({},B.defaults.i18n,e.i18n)),i.options.minDate&&i.options.minDate.setHours(0,0,0,0),i.options.maxDate&&i.options.maxDate.setHours(0,0,0,0),i.id=M.guid(),i._setupVariables(),i._insertHTMLIntoDOM(),i._setupModal(),i._setupEventHandlers(),i.options.defaultDate||(i.options.defaultDate=new Date(Date.parse(i.el.value)));var n=i.options.defaultDate;return B._isDate(n)?i.options.setDefaultDate?(i.setDate(n,!0),i.setInputValue()):i.gotoDate(n):i.gotoDate(new Date),i.isOpen=!1,i}return _inherits(B,Component),_createClass(B,[{key:"destroy",value:function(){this._removeEventHandlers(),this.modal.destroy(),g(this.modalEl).remove(),this.destroySelects(),this.el.M_Datepicker=void 0}},{key:"destroySelects",value:function(){var t=this.calendarEl.querySelector(".orig-select-year");t&&M.FormSelect.getInstance(t).destroy();var e=this.calendarEl.querySelector(".orig-select-month");e&&M.FormSelect.getInstance(e).destroy()}},{key:"_insertHTMLIntoDOM",value:function(){this.options.showClearBtn&&(g(this.clearBtn).css({visibility:""}),this.clearBtn.innerHTML=this.options.i18n.clear),this.doneBtn.innerHTML=this.options.i18n.done,this.cancelBtn.innerHTML=this.options.i18n.cancel,this.options.container?this.$modalEl.appendTo(this.options.container):this.$modalEl.insertBefore(this.el)}},{key:"_setupModal",value:function(){var t=this;this.modalEl.id="modal-"+this.id,this.modal=M.Modal.init(this.modalEl,{onCloseEnd:function(){t.isOpen=!1}})}},{key:"toString",value:function(t){var e=this;return t=t||this.options.format,B._isDate(this.date)?t.split(/(d{1,4}|m{1,4}|y{4}|yy|!.)/g).map(function(t){return e.formats[t]?e.formats[t]():t}).join(""):""}},{key:"setDate",value:function(t,e){if(!t)return this.date=null,this._renderDateDisplay(),this.draw();if("string"==typeof t&&(t=new Date(Date.parse(t))),B._isDate(t)){var i=this.options.minDate,n=this.options.maxDate;B._isDate(i)&&tn.maxDate||n.disableWeekends&&B._isWeekend(y)||n.disableDayFn&&n.disableDayFn(y),isEmpty:C,isStartRange:x,isEndRange:L,isInRange:T,showDaysInNextAndPreviousMonths:n.showDaysInNextAndPreviousMonths};l.push(this.renderDay($)),7==++_&&(r.push(this.renderRow(l,n.isRTL,m)),_=0,m=!(l=[]))}return this.renderTable(n,r,i)}},{key:"renderDay",value:function(t){var e=[],i="false";if(t.isEmpty){if(!t.showDaysInNextAndPreviousMonths)return'';e.push("is-outside-current-month"),e.push("is-selection-disabled")}return t.isDisabled&&e.push("is-disabled"),t.isToday&&e.push("is-today"),t.isSelected&&(e.push("is-selected"),i="true"),t.hasEvent&&e.push("has-event"),t.isInRange&&e.push("is-inrange"),t.isStartRange&&e.push("is-startrange"),t.isEndRange&&e.push("is-endrange"),'"}},{key:"renderRow",value:function(t,e,i){return''+(e?t.reverse():t).join("")+""}},{key:"renderTable",value:function(t,e,i){return'
    '+this.renderHead(t)+this.renderBody(e)+"
    "}},{key:"renderHead",value:function(t){var e=void 0,i=[];for(e=0;e<7;e++)i.push(''+this.renderDayName(t,e,!0)+"");return""+(t.isRTL?i.reverse():i).join("")+""}},{key:"renderBody",value:function(t){return""+t.join("")+""}},{key:"renderTitle",value:function(t,e,i,n,s,o){var a,r,l=void 0,h=void 0,d=void 0,u=this.options,c=i===u.minYear,p=i===u.maxYear,v='
    ',f=!0,m=!0;for(d=[],l=0;l<12;l++)d.push('");for(a='",g.isArray(u.yearRange)?(l=u.yearRange[0],h=u.yearRange[1]+1):(l=i-u.yearRange,h=1+i+u.yearRange),d=[];l=u.minYear&&d.push('");r='";v+='',v+='
    ',u.showMonthAfterYear?v+=r+a:v+=a+r,v+="
    ",c&&(0===n||u.minMonth>=n)&&(f=!1),p&&(11===n||u.maxMonth<=n)&&(m=!1);return(v+='')+"
    "}},{key:"draw",value:function(t){if(this.isOpen||t){var e,i=this.options,n=i.minYear,s=i.maxYear,o=i.minMonth,a=i.maxMonth,r="";this._y<=n&&(this._y=n,!isNaN(o)&&this._m=s&&(this._y=s,!isNaN(a)&&this._m>a&&(this._m=a)),e="datepicker-title-"+Math.random().toString(36).replace(/[^a-z]+/g,"").substr(0,2);for(var l=0;l<1;l++)this._renderDateDisplay(),r+=this.renderTitle(this,l,this.calendars[l].year,this.calendars[l].month,this.calendars[0].year,e)+this.render(this.calendars[l].year,this.calendars[l].month,e);this.destroySelects(),this.calendarEl.innerHTML=r;var h=this.calendarEl.querySelector(".orig-select-year"),d=this.calendarEl.querySelector(".orig-select-month");M.FormSelect.init(h,{classes:"select-year",dropdownOptions:{container:document.body,constrainWidth:!1}}),M.FormSelect.init(d,{classes:"select-month",dropdownOptions:{container:document.body,constrainWidth:!1}}),h.addEventListener("change",this._handleYearChange.bind(this)),d.addEventListener("change",this._handleMonthChange.bind(this)),"function"==typeof this.options.onDraw&&this.options.onDraw(this)}}},{key:"_setupEventHandlers",value:function(){this._handleInputKeydownBound=this._handleInputKeydown.bind(this),this._handleInputClickBound=this._handleInputClick.bind(this),this._handleInputChangeBound=this._handleInputChange.bind(this),this._handleCalendarClickBound=this._handleCalendarClick.bind(this),this._finishSelectionBound=this._finishSelection.bind(this),this._handleMonthChange=this._handleMonthChange.bind(this),this._closeBound=this.close.bind(this),this.el.addEventListener("click",this._handleInputClickBound),this.el.addEventListener("keydown",this._handleInputKeydownBound),this.el.addEventListener("change",this._handleInputChangeBound),this.calendarEl.addEventListener("click",this._handleCalendarClickBound),this.doneBtn.addEventListener("click",this._finishSelectionBound),this.cancelBtn.addEventListener("click",this._closeBound),this.options.showClearBtn&&(this._handleClearClickBound=this._handleClearClick.bind(this),this.clearBtn.addEventListener("click",this._handleClearClickBound))}},{key:"_setupVariables",value:function(){var e=this;this.$modalEl=g(B._template),this.modalEl=this.$modalEl[0],this.calendarEl=this.modalEl.querySelector(".datepicker-calendar"),this.yearTextEl=this.modalEl.querySelector(".year-text"),this.dateTextEl=this.modalEl.querySelector(".date-text"),this.options.showClearBtn&&(this.clearBtn=this.modalEl.querySelector(".datepicker-clear")),this.doneBtn=this.modalEl.querySelector(".datepicker-done"),this.cancelBtn=this.modalEl.querySelector(".datepicker-cancel"),this.formats={d:function(){return e.date.getDate()},dd:function(){var t=e.date.getDate();return(t<10?"0":"")+t},ddd:function(){return e.options.i18n.weekdaysShort[e.date.getDay()]},dddd:function(){return e.options.i18n.weekdays[e.date.getDay()]},m:function(){return e.date.getMonth()+1},mm:function(){var t=e.date.getMonth()+1;return(t<10?"0":"")+t},mmm:function(){return e.options.i18n.monthsShort[e.date.getMonth()]},mmmm:function(){return e.options.i18n.months[e.date.getMonth()]},yy:function(){return(""+e.date.getFullYear()).slice(2)},yyyy:function(){return e.date.getFullYear()}}}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("click",this._handleInputClickBound),this.el.removeEventListener("keydown",this._handleInputKeydownBound),this.el.removeEventListener("change",this._handleInputChangeBound),this.calendarEl.removeEventListener("click",this._handleCalendarClickBound)}},{key:"_handleInputClick",value:function(){this.open()}},{key:"_handleInputKeydown",value:function(t){t.which===M.keys.ENTER&&(t.preventDefault(),this.open())}},{key:"_handleCalendarClick",value:function(t){if(this.isOpen){var e=g(t.target);e.hasClass("is-disabled")||(!e.hasClass("datepicker-day-button")||e.hasClass("is-empty")||e.parent().hasClass("is-disabled")?e.closest(".month-prev").length?this.prevMonth():e.closest(".month-next").length&&this.nextMonth():(this.setDate(new Date(t.target.getAttribute("data-year"),t.target.getAttribute("data-month"),t.target.getAttribute("data-day"))),this.options.autoClose&&this._finishSelection()))}}},{key:"_handleClearClick",value:function(){this.date=null,this.setInputValue(),this.close()}},{key:"_handleMonthChange",value:function(t){this.gotoMonth(t.target.value)}},{key:"_handleYearChange",value:function(t){this.gotoYear(t.target.value)}},{key:"gotoMonth",value:function(t){isNaN(t)||(this.calendars[0].month=parseInt(t,10),this.adjustCalendars())}},{key:"gotoYear",value:function(t){isNaN(t)||(this.calendars[0].year=parseInt(t,10),this.adjustCalendars())}},{key:"_handleInputChange",value:function(t){var e=void 0;t.firedBy!==this&&(e=this.options.parse?this.options.parse(this.el.value,this.options.format):new Date(Date.parse(this.el.value)),B._isDate(e)&&this.setDate(e))}},{key:"renderDayName",value:function(t,e,i){for(e+=t.firstDay;7<=e;)e-=7;return i?t.i18n.weekdaysAbbrev[e]:t.i18n.weekdays[e]}},{key:"_finishSelection",value:function(){this.setInputValue(),this.close()}},{key:"open",value:function(){if(!this.isOpen)return this.isOpen=!0,"function"==typeof this.options.onOpen&&this.options.onOpen.call(this),this.draw(),this.modal.open(),this}},{key:"close",value:function(){if(this.isOpen)return this.isOpen=!1,"function"==typeof this.options.onClose&&this.options.onClose.call(this),this.modal.close(),this}}],[{key:"init",value:function(t,e){return _get(B.__proto__||Object.getPrototypeOf(B),"init",this).call(this,this,t,e)}},{key:"_isDate",value:function(t){return/Date/.test(Object.prototype.toString.call(t))&&!isNaN(t.getTime())}},{key:"_isWeekend",value:function(t){var e=t.getDay();return 0===e||6===e}},{key:"_setToStartOfDay",value:function(t){B._isDate(t)&&t.setHours(0,0,0,0)}},{key:"_getDaysInMonth",value:function(t,e){return[31,B._isLeapYear(t)?29:28,31,30,31,30,31,31,30,31,30,31][e]}},{key:"_isLeapYear",value:function(t){return t%4==0&&t%100!=0||t%400==0}},{key:"_compareDates",value:function(t,e){return t.getTime()===e.getTime()}},{key:"_setToStartOfDay",value:function(t){B._isDate(t)&&t.setHours(0,0,0,0)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Datepicker}},{key:"defaults",get:function(){return e}}]),B}();t._template=['"].join(""),M.Datepicker=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"datepicker","M_Datepicker")}(cash),function(h){"use strict";var e={dialRadius:135,outerRadius:105,innerRadius:70,tickRadius:20,duration:350,container:null,defaultTime:"now",fromNow:0,showClearBtn:!1,i18n:{cancel:"Cancel",clear:"Clear",done:"Ok"},autoClose:!1,twelveHour:!0,vibrate:!0,onOpenStart:null,onOpenEnd:null,onCloseStart:null,onCloseEnd:null,onSelect:null},t=function(t){function f(t,e){_classCallCheck(this,f);var i=_possibleConstructorReturn(this,(f.__proto__||Object.getPrototypeOf(f)).call(this,f,t,e));return(i.el.M_Timepicker=i).options=h.extend({},f.defaults,e),i.id=M.guid(),i._insertHTMLIntoDOM(),i._setupModal(),i._setupVariables(),i._setupEventHandlers(),i._clockSetup(),i._pickerSetup(),i}return _inherits(f,Component),_createClass(f,[{key:"destroy",value:function(){this._removeEventHandlers(),this.modal.destroy(),h(this.modalEl).remove(),this.el.M_Timepicker=void 0}},{key:"_setupEventHandlers",value:function(){this._handleInputKeydownBound=this._handleInputKeydown.bind(this),this._handleInputClickBound=this._handleInputClick.bind(this),this._handleClockClickStartBound=this._handleClockClickStart.bind(this),this._handleDocumentClickMoveBound=this._handleDocumentClickMove.bind(this),this._handleDocumentClickEndBound=this._handleDocumentClickEnd.bind(this),this.el.addEventListener("click",this._handleInputClickBound),this.el.addEventListener("keydown",this._handleInputKeydownBound),this.plate.addEventListener("mousedown",this._handleClockClickStartBound),this.plate.addEventListener("touchstart",this._handleClockClickStartBound),h(this.spanHours).on("click",this.showView.bind(this,"hours")),h(this.spanMinutes).on("click",this.showView.bind(this,"minutes"))}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("click",this._handleInputClickBound),this.el.removeEventListener("keydown",this._handleInputKeydownBound)}},{key:"_handleInputClick",value:function(){this.open()}},{key:"_handleInputKeydown",value:function(t){t.which===M.keys.ENTER&&(t.preventDefault(),this.open())}},{key:"_handleClockClickStart",value:function(t){t.preventDefault();var e=this.plate.getBoundingClientRect(),i=e.left,n=e.top;this.x0=i+this.options.dialRadius,this.y0=n+this.options.dialRadius,this.moved=!1;var s=f._Pos(t);this.dx=s.x-this.x0,this.dy=s.y-this.y0,this.setHand(this.dx,this.dy,!1),document.addEventListener("mousemove",this._handleDocumentClickMoveBound),document.addEventListener("touchmove",this._handleDocumentClickMoveBound),document.addEventListener("mouseup",this._handleDocumentClickEndBound),document.addEventListener("touchend",this._handleDocumentClickEndBound)}},{key:"_handleDocumentClickMove",value:function(t){t.preventDefault();var e=f._Pos(t),i=e.x-this.x0,n=e.y-this.y0;this.moved=!0,this.setHand(i,n,!1,!0)}},{key:"_handleDocumentClickEnd",value:function(t){var e=this;t.preventDefault(),document.removeEventListener("mouseup",this._handleDocumentClickEndBound),document.removeEventListener("touchend",this._handleDocumentClickEndBound);var i=f._Pos(t),n=i.x-this.x0,s=i.y-this.y0;this.moved&&n===this.dx&&s===this.dy&&this.setHand(n,s),"hours"===this.currentView?this.showView("minutes",this.options.duration/2):this.options.autoClose&&(h(this.minutesView).addClass("timepicker-dial-out"),setTimeout(function(){e.done()},this.options.duration/2)),"function"==typeof this.options.onSelect&&this.options.onSelect.call(this,this.hours,this.minutes),document.removeEventListener("mousemove",this._handleDocumentClickMoveBound),document.removeEventListener("touchmove",this._handleDocumentClickMoveBound)}},{key:"_insertHTMLIntoDOM",value:function(){this.$modalEl=h(f._template),this.modalEl=this.$modalEl[0],this.modalEl.id="modal-"+this.id;var t=document.querySelector(this.options.container);this.options.container&&t?this.$modalEl.appendTo(t):this.$modalEl.insertBefore(this.el)}},{key:"_setupModal",value:function(){var t=this;this.modal=M.Modal.init(this.modalEl,{onOpenStart:this.options.onOpenStart,onOpenEnd:this.options.onOpenEnd,onCloseStart:this.options.onCloseStart,onCloseEnd:function(){"function"==typeof t.options.onCloseEnd&&t.options.onCloseEnd.call(t),t.isOpen=!1}})}},{key:"_setupVariables",value:function(){this.currentView="hours",this.vibrate=navigator.vibrate?"vibrate":navigator.webkitVibrate?"webkitVibrate":null,this._canvas=this.modalEl.querySelector(".timepicker-canvas"),this.plate=this.modalEl.querySelector(".timepicker-plate"),this.hoursView=this.modalEl.querySelector(".timepicker-hours"),this.minutesView=this.modalEl.querySelector(".timepicker-minutes"),this.spanHours=this.modalEl.querySelector(".timepicker-span-hours"),this.spanMinutes=this.modalEl.querySelector(".timepicker-span-minutes"),this.spanAmPm=this.modalEl.querySelector(".timepicker-span-am-pm"),this.footer=this.modalEl.querySelector(".timepicker-footer"),this.amOrPm="PM"}},{key:"_pickerSetup",value:function(){var t=h('").appendTo(this.footer).on("click",this.clear.bind(this));this.options.showClearBtn&&t.css({visibility:""});var e=h('
    ');h('").appendTo(e).on("click",this.close.bind(this)),h('").appendTo(e).on("click",this.done.bind(this)),e.appendTo(this.footer)}},{key:"_clockSetup",value:function(){this.options.twelveHour&&(this.$amBtn=h('
    AM
    '),this.$pmBtn=h('
    PM
    '),this.$amBtn.on("click",this._handleAmPmClick.bind(this)).appendTo(this.spanAmPm),this.$pmBtn.on("click",this._handleAmPmClick.bind(this)).appendTo(this.spanAmPm)),this._buildHoursView(),this._buildMinutesView(),this._buildSVGClock()}},{key:"_buildSVGClock",value:function(){var t=this.options.dialRadius,e=this.options.tickRadius,i=2*t,n=f._createSVGEl("svg");n.setAttribute("class","timepicker-svg"),n.setAttribute("width",i),n.setAttribute("height",i);var s=f._createSVGEl("g");s.setAttribute("transform","translate("+t+","+t+")");var o=f._createSVGEl("circle");o.setAttribute("class","timepicker-canvas-bearing"),o.setAttribute("cx",0),o.setAttribute("cy",0),o.setAttribute("r",4);var a=f._createSVGEl("line");a.setAttribute("x1",0),a.setAttribute("y1",0);var r=f._createSVGEl("circle");r.setAttribute("class","timepicker-canvas-bg"),r.setAttribute("r",e),s.appendChild(a),s.appendChild(r),s.appendChild(o),n.appendChild(s),this._canvas.appendChild(n),this.hand=a,this.bg=r,this.bearing=o,this.g=s}},{key:"_buildHoursView",value:function(){var t=h('
    ');if(this.options.twelveHour)for(var e=1;e<13;e+=1){var i=t.clone(),n=e/6*Math.PI,s=this.options.outerRadius;i.css({left:this.options.dialRadius+Math.sin(n)*s-this.options.tickRadius+"px",top:this.options.dialRadius-Math.cos(n)*s-this.options.tickRadius+"px"}),i.html(0===e?"00":e),this.hoursView.appendChild(i[0])}else for(var o=0;o<24;o+=1){var a=t.clone(),r=o/6*Math.PI,l=0'),e=0;e<60;e+=5){var i=t.clone(),n=e/30*Math.PI;i.css({left:this.options.dialRadius+Math.sin(n)*this.options.outerRadius-this.options.tickRadius+"px",top:this.options.dialRadius-Math.cos(n)*this.options.outerRadius-this.options.tickRadius+"px"}),i.html(f._addLeadingZero(e)),this.minutesView.appendChild(i[0])}}},{key:"_handleAmPmClick",value:function(t){var e=h(t.target);this.amOrPm=e.hasClass("am-btn")?"AM":"PM",this._updateAmPmView()}},{key:"_updateAmPmView",value:function(){this.options.twelveHour&&(this.$amBtn.toggleClass("text-primary","AM"===this.amOrPm),this.$pmBtn.toggleClass("text-primary","PM"===this.amOrPm))}},{key:"_updateTimeFromInput",value:function(){var t=((this.el.value||this.options.defaultTime||"")+"").split(":");if(this.options.twelveHour&&void 0!==t[1]&&(0','",""].join(""),M.Timepicker=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"timepicker","M_Timepicker")}(cash),function(s){"use strict";var e={},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_CharacterCounter=i).options=s.extend({},n.defaults,e),i.isInvalid=!1,i.isValidLength=!1,i._setupCounter(),i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.CharacterCounter=void 0,this._removeCounter()}},{key:"_setupEventHandlers",value:function(){this._handleUpdateCounterBound=this.updateCounter.bind(this),this.el.addEventListener("focus",this._handleUpdateCounterBound,!0),this.el.addEventListener("input",this._handleUpdateCounterBound,!0)}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("focus",this._handleUpdateCounterBound,!0),this.el.removeEventListener("input",this._handleUpdateCounterBound,!0)}},{key:"_setupCounter",value:function(){this.counterEl=document.createElement("span"),s(this.counterEl).addClass("character-counter").css({float:"right","font-size":"12px",height:1}),this.$el.parent().append(this.counterEl)}},{key:"_removeCounter",value:function(){s(this.counterEl).remove()}},{key:"updateCounter",value:function(){var t=+this.$el.attr("data-length"),e=this.el.value.length;this.isValidLength=e<=t;var i=e;t&&(i+="/"+t,this._validateInput()),s(this.counterEl).html(i)}},{key:"_validateInput",value:function(){this.isValidLength&&this.isInvalid?(this.isInvalid=!1,this.$el.removeClass("invalid")):this.isValidLength||this.isInvalid||(this.isInvalid=!0,this.$el.removeClass("valid"),this.$el.addClass("invalid"))}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_CharacterCounter}},{key:"defaults",get:function(){return e}}]),n}();M.CharacterCounter=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"characterCounter","M_CharacterCounter")}(cash),function(b){"use strict";var e={duration:200,dist:-100,shift:0,padding:0,numVisible:5,fullWidth:!1,indicators:!1,noWrap:!1,onCycleTo:null},t=function(t){function i(t,e){_classCallCheck(this,i);var n=_possibleConstructorReturn(this,(i.__proto__||Object.getPrototypeOf(i)).call(this,i,t,e));return(n.el.M_Carousel=n).options=b.extend({},i.defaults,e),n.hasMultipleSlides=1'),n.$el.find(".carousel-item").each(function(t,e){if(n.images.push(t),n.showIndicators){var i=b('
  • ');0===e&&i[0].classList.add("active"),n.$indicators.append(i)}}),n.showIndicators&&n.$el.append(n.$indicators),n.count=n.images.length,n.options.numVisible=Math.min(n.count,n.options.numVisible),n.xform="transform",["webkit","Moz","O","ms"].every(function(t){var e=t+"Transform";return void 0===document.body.style[e]||(n.xform=e,!1)}),n._setupEventHandlers(),n._scroll(n.offset),n}return _inherits(i,Component),_createClass(i,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.M_Carousel=void 0}},{key:"_setupEventHandlers",value:function(){var i=this;this._handleCarouselTapBound=this._handleCarouselTap.bind(this),this._handleCarouselDragBound=this._handleCarouselDrag.bind(this),this._handleCarouselReleaseBound=this._handleCarouselRelease.bind(this),this._handleCarouselClickBound=this._handleCarouselClick.bind(this),void 0!==window.ontouchstart&&(this.el.addEventListener("touchstart",this._handleCarouselTapBound),this.el.addEventListener("touchmove",this._handleCarouselDragBound),this.el.addEventListener("touchend",this._handleCarouselReleaseBound)),this.el.addEventListener("mousedown",this._handleCarouselTapBound),this.el.addEventListener("mousemove",this._handleCarouselDragBound),this.el.addEventListener("mouseup",this._handleCarouselReleaseBound),this.el.addEventListener("mouseleave",this._handleCarouselReleaseBound),this.el.addEventListener("click",this._handleCarouselClickBound),this.showIndicators&&this.$indicators&&(this._handleIndicatorClickBound=this._handleIndicatorClick.bind(this),this.$indicators.find(".indicator-item").each(function(t,e){t.addEventListener("click",i._handleIndicatorClickBound)}));var t=M.throttle(this._handleResize,200);this._handleThrottledResizeBound=t.bind(this),window.addEventListener("resize",this._handleThrottledResizeBound)}},{key:"_removeEventHandlers",value:function(){var i=this;void 0!==window.ontouchstart&&(this.el.removeEventListener("touchstart",this._handleCarouselTapBound),this.el.removeEventListener("touchmove",this._handleCarouselDragBound),this.el.removeEventListener("touchend",this._handleCarouselReleaseBound)),this.el.removeEventListener("mousedown",this._handleCarouselTapBound),this.el.removeEventListener("mousemove",this._handleCarouselDragBound),this.el.removeEventListener("mouseup",this._handleCarouselReleaseBound),this.el.removeEventListener("mouseleave",this._handleCarouselReleaseBound),this.el.removeEventListener("click",this._handleCarouselClickBound),this.showIndicators&&this.$indicators&&this.$indicators.find(".indicator-item").each(function(t,e){t.removeEventListener("click",i._handleIndicatorClickBound)}),window.removeEventListener("resize",this._handleThrottledResizeBound)}},{key:"_handleCarouselTap",value:function(t){"mousedown"===t.type&&b(t.target).is("img")&&t.preventDefault(),this.pressed=!0,this.dragged=!1,this.verticalDragged=!1,this.reference=this._xpos(t),this.referenceY=this._ypos(t),this.velocity=this.amplitude=0,this.frame=this.offset,this.timestamp=Date.now(),clearInterval(this.ticker),this.ticker=setInterval(this._trackBound,100)}},{key:"_handleCarouselDrag",value:function(t){var e=void 0,i=void 0,n=void 0;if(this.pressed)if(e=this._xpos(t),i=this._ypos(t),n=this.reference-e,Math.abs(this.referenceY-i)<30&&!this.verticalDragged)(2=this.dim*(this.count-1)?this.target=this.dim*(this.count-1):this.target<0&&(this.target=0)),this.amplitude=this.target-this.offset,this.timestamp=Date.now(),requestAnimationFrame(this._autoScrollBound),this.dragged&&(t.preventDefault(),t.stopPropagation()),!1}},{key:"_handleCarouselClick",value:function(t){if(this.dragged)return t.preventDefault(),t.stopPropagation(),!1;if(!this.options.fullWidth){var e=b(t.target).closest(".carousel-item").index();0!==this._wrap(this.center)-e&&(t.preventDefault(),t.stopPropagation()),this._cycleTo(e)}}},{key:"_handleIndicatorClick",value:function(t){t.stopPropagation();var e=b(t.target).closest(".indicator-item");e.length&&this._cycleTo(e.index())}},{key:"_handleResize",value:function(t){this.options.fullWidth?(this.itemWidth=this.$el.find(".carousel-item").first().innerWidth(),this.imageHeight=this.$el.find(".carousel-item.active").height(),this.dim=2*this.itemWidth+this.options.padding,this.offset=2*this.center*this.itemWidth,this.target=this.offset,this._setCarouselHeight(!0)):this._scroll()}},{key:"_setCarouselHeight",value:function(t){var i=this,e=this.$el.find(".carousel-item.active").length?this.$el.find(".carousel-item.active").first():this.$el.find(".carousel-item").first(),n=e.find("img").first();if(n.length)if(n[0].complete){var s=n.height();if(0=this.count?t%this.count:t<0?this._wrap(this.count+t%this.count):t}},{key:"_track",value:function(){var t,e,i,n;e=(t=Date.now())-this.timestamp,this.timestamp=t,i=this.offset-this.frame,this.frame=this.offset,n=1e3*i/(1+e),this.velocity=.8*n+.2*this.velocity}},{key:"_autoScroll",value:function(){var t=void 0,e=void 0;this.amplitude&&(t=Date.now()-this.timestamp,2<(e=this.amplitude*Math.exp(-t/this.options.duration))||e<-2?(this._scroll(this.target-e),requestAnimationFrame(this._autoScrollBound)):this._scroll(this.target))}},{key:"_scroll",value:function(t){var e=this;this.$el.hasClass("scrolling")||this.el.classList.add("scrolling"),null!=this.scrollingTimeout&&window.clearTimeout(this.scrollingTimeout),this.scrollingTimeout=window.setTimeout(function(){e.$el.removeClass("scrolling")},this.options.duration);var i,n,s,o,a=void 0,r=void 0,l=void 0,h=void 0,d=void 0,u=void 0,c=this.center,p=1/this.options.numVisible;if(this.offset="number"==typeof t?t:this.offset,this.center=Math.floor((this.offset+this.dim/2)/this.dim),o=-(s=(n=this.offset-this.center*this.dim)<0?1:-1)*n*2/this.dim,i=this.count>>1,this.options.fullWidth?(l="translateX(0)",u=1):(l="translateX("+(this.el.clientWidth-this.itemWidth)/2+"px) ",l+="translateY("+(this.el.clientHeight-this.itemHeight)/2+"px)",u=1-p*o),this.showIndicators){var v=this.center%this.count,f=this.$indicators.find(".indicator-item.active");f.index()!==v&&(f.removeClass("active"),this.$indicators.find(".indicator-item").eq(v)[0].classList.add("active"))}if(!this.noWrap||0<=this.center&&this.center=this.count||e<0){if(this.noWrap)return;e=this._wrap(e)}this._cycleTo(e)}},{key:"prev",value:function(t){(void 0===t||isNaN(t))&&(t=1);var e=this.center-t;if(e>=this.count||e<0){if(this.noWrap)return;e=this._wrap(e)}this._cycleTo(e)}},{key:"set",value:function(t,e){if((void 0===t||isNaN(t))&&(t=0),t>this.count||t<0){if(this.noWrap)return;t=this._wrap(t)}this._cycleTo(t,e)}}],[{key:"init",value:function(t,e){return _get(i.__proto__||Object.getPrototypeOf(i),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Carousel}},{key:"defaults",get:function(){return e}}]),i}();M.Carousel=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"carousel","M_Carousel")}(cash),function(S){"use strict";var e={onOpen:void 0,onClose:void 0},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_TapTarget=i).options=S.extend({},n.defaults,e),i.isOpen=!1,i.$origin=S("#"+i.$el.attr("data-target")),i._setup(),i._calculatePositioning(),i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.TapTarget=void 0}},{key:"_setupEventHandlers",value:function(){this._handleDocumentClickBound=this._handleDocumentClick.bind(this),this._handleTargetClickBound=this._handleTargetClick.bind(this),this._handleOriginClickBound=this._handleOriginClick.bind(this),this.el.addEventListener("click",this._handleTargetClickBound),this.originEl.addEventListener("click",this._handleOriginClickBound);var t=M.throttle(this._handleResize,200);this._handleThrottledResizeBound=t.bind(this),window.addEventListener("resize",this._handleThrottledResizeBound)}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("click",this._handleTargetClickBound),this.originEl.removeEventListener("click",this._handleOriginClickBound),window.removeEventListener("resize",this._handleThrottledResizeBound)}},{key:"_handleTargetClick",value:function(t){this.open()}},{key:"_handleOriginClick",value:function(t){this.close()}},{key:"_handleResize",value:function(t){this._calculatePositioning()}},{key:"_handleDocumentClick",value:function(t){S(t.target).closest(".tap-target-wrapper").length||(this.close(),t.preventDefault(),t.stopPropagation())}},{key:"_setup",value:function(){this.wrapper=this.$el.parent()[0],this.waveEl=S(this.wrapper).find(".tap-target-wave")[0],this.originEl=S(this.wrapper).find(".tap-target-origin")[0],this.contentEl=this.$el.find(".tap-target-content")[0],S(this.wrapper).hasClass(".tap-target-wrapper")||(this.wrapper=document.createElement("div"),this.wrapper.classList.add("tap-target-wrapper"),this.$el.before(S(this.wrapper)),this.wrapper.append(this.el)),this.contentEl||(this.contentEl=document.createElement("div"),this.contentEl.classList.add("tap-target-content"),this.$el.append(this.contentEl)),this.waveEl||(this.waveEl=document.createElement("div"),this.waveEl.classList.add("tap-target-wave"),this.originEl||(this.originEl=this.$origin.clone(!0,!0),this.originEl.addClass("tap-target-origin"),this.originEl.removeAttr("id"),this.originEl.removeAttr("style"),this.originEl=this.originEl[0],this.waveEl.append(this.originEl)),this.wrapper.append(this.waveEl))}},{key:"_calculatePositioning",value:function(){var t="fixed"===this.$origin.css("position");if(!t)for(var e=this.$origin.parents(),i=0;i'+t.getAttribute("label")+"")[0]),i.each(function(t){var e=n._appendOptionWithIcon(n.$el,t,"optgroup-option");n._addOptionToValueDict(t,e)})}}),this.$el.after(this.dropdownOptions),this.input=document.createElement("input"),d(this.input).addClass("select-dropdown dropdown-trigger"),this.input.setAttribute("type","text"),this.input.setAttribute("readonly","true"),this.input.setAttribute("data-target",this.dropdownOptions.id),this.el.disabled&&d(this.input).prop("disabled","true"),this.$el.before(this.input),this._setValueToInput();var t=d('');if(this.$el.before(t[0]),!this.el.disabled){var e=d.extend({},this.options.dropdownOptions);e.onOpenEnd=function(t){var e=d(n.dropdownOptions).find(".selected").first();if(n.dropdown.isScrollable&&e.length){var i=e[0].getBoundingClientRect().top-n.dropdownOptions.getBoundingClientRect().top;i-=n.dropdownOptions.clientHeight/2,n.dropdownOptions.scrollTop=i}},this.isMultiple&&(e.closeOnClick=!1),this.dropdown=M.Dropdown.init(this.input,e)}this._setSelectedStates()}},{key:"_addOptionToValueDict",value:function(t,e){var i=Object.keys(this._valueDict).length,n=this.dropdownOptions.id+i,s={};e.id=n,s.el=t,s.optionEl=e,this._valueDict[n]=s}},{key:"_removeDropdown",value:function(){d(this.wrapper).find(".caret").remove(),d(this.input).remove(),d(this.dropdownOptions).remove(),d(this.wrapper).before(this.$el),d(this.wrapper).remove()}},{key:"_appendOptionWithIcon",value:function(t,e,i){var n=e.disabled?"disabled ":"",s="optgroup-option"===i?"optgroup-option ":"",o=this.isMultiple?'":e.innerHTML,a=d("
  • "),r=d("");r.html(o),a.addClass(n+" "+s),a.append(r);var l=e.getAttribute("data-icon");if(l){var h=d('');a.prepend(h)}return d(this.dropdownOptions).append(a[0]),a[0]}},{key:"_toggleEntryFromArray",value:function(t){var e=!this._keysSelected.hasOwnProperty(t),i=d(this._valueDict[t].optionEl);return e?this._keysSelected[t]=!0:delete this._keysSelected[t],i.toggleClass("selected",e),i.find('input[type="checkbox"]').prop("checked",e),i.prop("selected",e),e}},{key:"_setValueToInput",value:function(){var i=[];if(this.$el.find("option").each(function(t){if(d(t).prop("selected")){var e=d(t).text();i.push(e)}}),!i.length){var t=this.$el.find("option:disabled").eq(0);t.length&&""===t[0].value&&i.push(t.text())}this.input.value=i.join(", ")}},{key:"_setSelectedStates",value:function(){for(var t in this._keysSelected={},this._valueDict){var e=this._valueDict[t],i=d(e.el).prop("selected");d(e.optionEl).find('input[type="checkbox"]').prop("checked",i),i?(this._activateOption(d(this.dropdownOptions),d(e.optionEl)),this._keysSelected[t]=!0):d(e.optionEl).removeClass("selected")}}},{key:"_activateOption",value:function(t,e){e&&(this.isMultiple||t.find("li.selected").removeClass("selected"),d(e).addClass("selected"))}},{key:"getSelectedValues",value:function(){var t=[];for(var e in this._keysSelected)t.push(this._valueDict[e].el.value);return t}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_FormSelect}},{key:"defaults",get:function(){return e}}]),n}();M.FormSelect=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"formSelect","M_FormSelect")}(cash),function(s,e){"use strict";var i={},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_Range=i).options=s.extend({},n.defaults,e),i._mousedown=!1,i._setupThumb(),i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this._removeThumb(),this.el.M_Range=void 0}},{key:"_setupEventHandlers",value:function(){this._handleRangeChangeBound=this._handleRangeChange.bind(this),this._handleRangeMousedownTouchstartBound=this._handleRangeMousedownTouchstart.bind(this),this._handleRangeInputMousemoveTouchmoveBound=this._handleRangeInputMousemoveTouchmove.bind(this),this._handleRangeMouseupTouchendBound=this._handleRangeMouseupTouchend.bind(this),this._handleRangeBlurMouseoutTouchleaveBound=this._handleRangeBlurMouseoutTouchleave.bind(this),this.el.addEventListener("change",this._handleRangeChangeBound),this.el.addEventListener("mousedown",this._handleRangeMousedownTouchstartBound),this.el.addEventListener("touchstart",this._handleRangeMousedownTouchstartBound),this.el.addEventListener("input",this._handleRangeInputMousemoveTouchmoveBound),this.el.addEventListener("mousemove",this._handleRangeInputMousemoveTouchmoveBound),this.el.addEventListener("touchmove",this._handleRangeInputMousemoveTouchmoveBound),this.el.addEventListener("mouseup",this._handleRangeMouseupTouchendBound),this.el.addEventListener("touchend",this._handleRangeMouseupTouchendBound),this.el.addEventListener("blur",this._handleRangeBlurMouseoutTouchleaveBound),this.el.addEventListener("mouseout",this._handleRangeBlurMouseoutTouchleaveBound),this.el.addEventListener("touchleave",this._handleRangeBlurMouseoutTouchleaveBound)}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("change",this._handleRangeChangeBound),this.el.removeEventListener("mousedown",this._handleRangeMousedownTouchstartBound),this.el.removeEventListener("touchstart",this._handleRangeMousedownTouchstartBound),this.el.removeEventListener("input",this._handleRangeInputMousemoveTouchmoveBound),this.el.removeEventListener("mousemove",this._handleRangeInputMousemoveTouchmoveBound),this.el.removeEventListener("touchmove",this._handleRangeInputMousemoveTouchmoveBound),this.el.removeEventListener("mouseup",this._handleRangeMouseupTouchendBound),this.el.removeEventListener("touchend",this._handleRangeMouseupTouchendBound),this.el.removeEventListener("blur",this._handleRangeBlurMouseoutTouchleaveBound),this.el.removeEventListener("mouseout",this._handleRangeBlurMouseoutTouchleaveBound),this.el.removeEventListener("touchleave",this._handleRangeBlurMouseoutTouchleaveBound)}},{key:"_handleRangeChange",value:function(){s(this.value).html(this.$el.val()),s(this.thumb).hasClass("active")||this._showRangeBubble();var t=this._calcRangeOffset();s(this.thumb).addClass("active").css("left",t+"px")}},{key:"_handleRangeMousedownTouchstart",value:function(t){if(s(this.value).html(this.$el.val()),this._mousedown=!0,this.$el.addClass("active"),s(this.thumb).hasClass("active")||this._showRangeBubble(),"input"!==t.type){var e=this._calcRangeOffset();s(this.thumb).addClass("active").css("left",e+"px")}}},{key:"_handleRangeInputMousemoveTouchmove",value:function(){if(this._mousedown){s(this.thumb).hasClass("active")||this._showRangeBubble();var t=this._calcRangeOffset();s(this.thumb).addClass("active").css("left",t+"px"),s(this.value).html(this.$el.val())}}},{key:"_handleRangeMouseupTouchend",value:function(){this._mousedown=!1,this.$el.removeClass("active")}},{key:"_handleRangeBlurMouseoutTouchleave",value:function(){if(!this._mousedown){var t=7+parseInt(this.$el.css("padding-left"))+"px";s(this.thumb).hasClass("active")&&(e.remove(this.thumb),e({targets:this.thumb,height:0,width:0,top:10,easing:"easeOutQuad",marginLeft:t,duration:100})),s(this.thumb).removeClass("active")}}},{key:"_setupThumb",value:function(){this.thumb=document.createElement("span"),this.value=document.createElement("span"),s(this.thumb).addClass("thumb"),s(this.value).addClass("value"),s(this.thumb).append(this.value),this.$el.after(this.thumb)}},{key:"_removeThumb",value:function(){s(this.thumb).remove()}},{key:"_showRangeBubble",value:function(){var t=-7+parseInt(s(this.thumb).parent().css("padding-left"))+"px";e.remove(this.thumb),e({targets:this.thumb,height:30,width:30,top:-30,marginLeft:t,duration:300,easing:"easeOutQuint"})}},{key:"_calcRangeOffset",value:function(){var t=this.$el.width()-15,e=parseFloat(this.$el.attr("max"))||100,i=parseFloat(this.$el.attr("min"))||0;return(parseFloat(this.$el.val())-i)/(e-i)*t}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Range}},{key:"defaults",get:function(){return i}}]),n}();M.Range=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"range","M_Range"),t.init(s("input[type=range]"))}(cash,M.anime); \ No newline at end of file diff --git a/static/js/buttons.js b/static/js/buttons.js new file mode 100644 index 00000000..fb2ba286 --- /dev/null +++ b/static/js/buttons.js @@ -0,0 +1,354 @@ +(function($, anim) { + 'use strict'; + + let _defaults = { + direction: 'top', + hoverEnabled: true, + toolbarEnabled: false + }; + + $.fn.reverse = [].reverse; + + /** + * @class + * + */ + class FloatingActionButton extends Component { + /** + * Construct FloatingActionButton instance + * @constructor + * @param {Element} el + * @param {Object} options + */ + constructor(el, options) { + super(FloatingActionButton, el, options); + + this.el.M_FloatingActionButton = this; + + /** + * Options for the fab + * @member FloatingActionButton#options + * @prop {Boolean} [direction] - Direction fab menu opens + * @prop {Boolean} [hoverEnabled=true] - Enable hover vs click + * @prop {Boolean} [toolbarEnabled=false] - Enable toolbar transition + */ + this.options = $.extend({}, FloatingActionButton.defaults, options); + + this.isOpen = false; + this.$anchor = this.$el.children('a').first(); + this.$menu = this.$el.children('ul').first(); + this.$floatingBtns = this.$el.find('ul .btn-floating'); + this.$floatingBtnsReverse = this.$el.find('ul .btn-floating').reverse(); + this.offsetY = 0; + this.offsetX = 0; + + this.$el.addClass(`direction-${this.options.direction}`); + if (this.options.direction === 'top') { + this.offsetY = 40; + } else if (this.options.direction === 'right') { + this.offsetX = -40; + } else if (this.options.direction === 'bottom') { + this.offsetY = -40; + } else { + this.offsetX = 40; + } + this._setupEventHandlers(); + } + + static get defaults() { + return _defaults; + } + + static init(els, options) { + return super.init(this, els, options); + } + + /** + * Get Instance + */ + static getInstance(el) { + let domElem = !!el.jquery ? el[0] : el; + return domElem.M_FloatingActionButton; + } + + /** + * Teardown component + */ + destroy() { + this._removeEventHandlers(); + this.el.M_FloatingActionButton = undefined; + } + + /** + * Setup Event Handlers + */ + _setupEventHandlers() { + this._handleFABClickBound = this._handleFABClick.bind(this); + this._handleOpenBound = this.open.bind(this); + this._handleCloseBound = this.close.bind(this); + + if (this.options.hoverEnabled && !this.options.toolbarEnabled) { + this.el.addEventListener('mouseenter', this._handleOpenBound); + this.el.addEventListener('mouseleave', this._handleCloseBound); + } else { + this.el.addEventListener('click', this._handleFABClickBound); + } + } + + /** + * Remove Event Handlers + */ + _removeEventHandlers() { + if (this.options.hoverEnabled && !this.options.toolbarEnabled) { + this.el.removeEventListener('mouseenter', this._handleOpenBound); + this.el.removeEventListener('mouseleave', this._handleCloseBound); + } else { + this.el.removeEventListener('click', this._handleFABClickBound); + } + } + + /** + * Handle FAB Click + */ + _handleFABClick() { + if (this.isOpen) { + this.close(); + } else { + this.open(); + } + } + + /** + * Handle Document Click + * @param {Event} e + */ + _handleDocumentClick(e) { + if (!$(e.target).closest(this.$menu).length) { + this.close(); + } + } + + /** + * Open FAB + */ + open() { + if (this.isOpen) { + return; + } + + if (this.options.toolbarEnabled) { + this._animateInToolbar(); + } else { + this._animateInFAB(); + } + this.isOpen = true; + } + + /** + * Close FAB + */ + close() { + if (!this.isOpen) { + return; + } + + if (this.options.toolbarEnabled) { + window.removeEventListener('scroll', this._handleCloseBound, true); + document.body.removeEventListener('click', this._handleDocumentClickBound, true); + this._animateOutToolbar(); + } else { + this._animateOutFAB(); + } + this.isOpen = false; + } + + /** + * Classic FAB Menu open + */ + _animateInFAB() { + this.$el.addClass('active'); + + let time = 0; + this.$floatingBtnsReverse.each((el) => { + anim({ + targets: el, + opacity: 1, + scale: [0.4, 1], + translateY: [this.offsetY, 0], + translateX: [this.offsetX, 0], + duration: 275, + delay: time, + easing: 'easeInOutQuad' + }); + time += 40; + }); + } + + /** + * Classic FAB Menu close + */ + _animateOutFAB() { + this.$floatingBtnsReverse.each((el) => { + anim.remove(el); + anim({ + targets: el, + opacity: 0, + scale: 0.4, + translateY: this.offsetY, + translateX: this.offsetX, + duration: 175, + easing: 'easeOutQuad', + complete: () => { + this.$el.removeClass('active'); + } + }); + }); + } + + /** + * Toolbar transition Menu open + */ + _animateInToolbar() { + let scaleFactor; + let windowWidth = window.innerWidth; + let windowHeight = window.innerHeight; + let btnRect = this.el.getBoundingClientRect(); + let backdrop = $('
    '); + let fabColor = this.$anchor.css('background-color'); + this.$anchor.append(backdrop); + + this.offsetX = btnRect.left - windowWidth / 2 + btnRect.width / 2; + this.offsetY = windowHeight - btnRect.bottom; + scaleFactor = windowWidth / backdrop[0].clientWidth; + this.btnBottom = btnRect.bottom; + this.btnLeft = btnRect.left; + this.btnWidth = btnRect.width; + + // Set initial state + this.$el.addClass('active'); + this.$el.css({ + 'text-align': 'center', + width: '100%', + bottom: 0, + left: 0, + transform: 'translateX(' + this.offsetX + 'px)', + transition: 'none' + }); + this.$anchor.css({ + transform: 'translateY(' + -this.offsetY + 'px)', + transition: 'none' + }); + backdrop.css({ + 'background-color': fabColor + }); + + setTimeout(() => { + this.$el.css({ + transform: '', + transition: + 'transform .2s cubic-bezier(0.550, 0.085, 0.680, 0.530), background-color 0s linear .2s' + }); + this.$anchor.css({ + overflow: 'visible', + transform: '', + transition: 'transform .2s' + }); + + setTimeout(() => { + this.$el.css({ + overflow: 'hidden', + 'background-color': fabColor + }); + backdrop.css({ + transform: 'scale(' + scaleFactor + ')', + transition: 'transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)' + }); + this.$menu + .children('li') + .children('a') + .css({ + opacity: 1 + }); + + // Scroll to close. + this._handleDocumentClickBound = this._handleDocumentClick.bind(this); + window.addEventListener('scroll', this._handleCloseBound, true); + document.body.addEventListener('click', this._handleDocumentClickBound, true); + }, 100); + }, 0); + } + + /** + * Toolbar transition Menu close + */ + _animateOutToolbar() { + let windowWidth = window.innerWidth; + let windowHeight = window.innerHeight; + let backdrop = this.$el.find('.fab-backdrop'); + let fabColor = this.$anchor.css('background-color'); + + this.offsetX = this.btnLeft - windowWidth / 2 + this.btnWidth / 2; + this.offsetY = windowHeight - this.btnBottom; + + // Hide backdrop + this.$el.removeClass('active'); + this.$el.css({ + 'background-color': 'transparent', + transition: 'none' + }); + this.$anchor.css({ + transition: 'none' + }); + backdrop.css({ + transform: 'scale(0)', + 'background-color': fabColor + }); + this.$menu + .children('li') + .children('a') + .css({ + opacity: '' + }); + + setTimeout(() => { + backdrop.remove(); + + // Set initial state. + this.$el.css({ + 'text-align': '', + width: '', + bottom: '', + left: '', + overflow: '', + 'background-color': '', + transform: 'translate3d(' + -this.offsetX + 'px,0,0)' + }); + this.$anchor.css({ + overflow: '', + transform: 'translate3d(0,' + this.offsetY + 'px,0)' + }); + + setTimeout(() => { + this.$el.css({ + transform: 'translate3d(0,0,0)', + transition: 'transform .2s' + }); + this.$anchor.css({ + transform: 'translate3d(0,0,0)', + transition: 'transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)' + }); + }, 20); + }, 200); + } + } + + M.FloatingActionButton = FloatingActionButton; + + if (M.jQueryLoaded) { + M.initializeJqueryWrapper( + FloatingActionButton, + 'floatingActionButton', + 'M_FloatingActionButton' + ); + } +})(cash, M.anime); diff --git a/static/js/cards.js b/static/js/cards.js new file mode 100644 index 00000000..0c1ad737 --- /dev/null +++ b/static/js/cards.js @@ -0,0 +1,40 @@ +(function($, anim) { + $(document).on('click', '.card', function(e) { + if ($(this).children('.card-reveal').length) { + var $card = $(e.target).closest('.card'); + if ($card.data('initialOverflow') === undefined) { + $card.data( + 'initialOverflow', + $card.css('overflow') === undefined ? '' : $card.css('overflow') + ); + } + let $cardReveal = $(this).find('.card-reveal'); + if ( + $(e.target).is($('.card-reveal .card-title')) || + $(e.target).is($('.card-reveal .card-title i')) + ) { + // Make Reveal animate down and display none + anim({ + targets: $cardReveal[0], + translateY: 0, + duration: 225, + easing: 'easeInOutQuad', + complete: function(anim) { + let el = anim.animatables[0].target; + $(el).css({ display: 'none' }); + $card.css('overflow', $card.data('initialOverflow')); + } + }); + } else if ($(e.target).is($('.card .activator')) || $(e.target).is($('.card .activator i'))) { + $card.css('overflow', 'hidden'); + $cardReveal.css({ display: 'block' }); + anim({ + targets: $cardReveal[0], + translateY: '-100%', + duration: 300, + easing: 'easeInOutQuad' + }); + } + } + }); +})(cash, M.anime); diff --git a/static/js/carousel.js b/static/js/carousel.js new file mode 100644 index 00000000..be98dc88 --- /dev/null +++ b/static/js/carousel.js @@ -0,0 +1,717 @@ +(function($) { + 'use strict'; + + let _defaults = { + duration: 200, // ms + dist: -100, // zoom scale TODO: make this more intuitive as an option + shift: 0, // spacing for center image + padding: 0, // Padding between non center items + numVisible: 5, // Number of visible items in carousel + fullWidth: false, // Change to full width styles + indicators: false, // Toggle indicators + noWrap: false, // Don't wrap around and cycle through items. + onCycleTo: null // Callback for when a new slide is cycled to. + }; + + /** + * @class + * + */ + class Carousel extends Component { + /** + * Construct Carousel instance + * @constructor + * @param {Element} el + * @param {Object} options + */ + constructor(el, options) { + super(Carousel, el, options); + + this.el.M_Carousel = this; + + /** + * Options for the carousel + * @member Carousel#options + * @prop {Number} duration + * @prop {Number} dist + * @prop {Number} shift + * @prop {Number} padding + * @prop {Number} numVisible + * @prop {Boolean} fullWidth + * @prop {Boolean} indicators + * @prop {Boolean} noWrap + * @prop {Function} onCycleTo + */ + this.options = $.extend({}, Carousel.defaults, options); + + // Setup + this.hasMultipleSlides = this.$el.find('.carousel-item').length > 1; + this.showIndicators = this.options.indicators && this.hasMultipleSlides; + this.noWrap = this.options.noWrap || !this.hasMultipleSlides; + this.pressed = false; + this.dragged = false; + this.offset = this.target = 0; + this.images = []; + this.itemWidth = this.$el + .find('.carousel-item') + .first() + .innerWidth(); + this.itemHeight = this.$el + .find('.carousel-item') + .first() + .innerHeight(); + this.dim = this.itemWidth * 2 + this.options.padding || 1; // Make sure dim is non zero for divisions. + this._autoScrollBound = this._autoScroll.bind(this); + this._trackBound = this._track.bind(this); + + // Full Width carousel setup + if (this.options.fullWidth) { + this.options.dist = 0; + this._setCarouselHeight(); + + // Offset fixed items when indicators. + if (this.showIndicators) { + this.$el.find('.carousel-fixed-item').addClass('with-indicators'); + } + } + + // Iterate through slides + this.$indicators = $('
      '); + this.$el.find('.carousel-item').each((el, i) => { + this.images.push(el); + if (this.showIndicators) { + let $indicator = $('
    • '); + + // Add active to first by default. + if (i === 0) { + $indicator[0].classList.add('active'); + } + + this.$indicators.append($indicator); + } + }); + if (this.showIndicators) { + this.$el.append(this.$indicators); + } + this.count = this.images.length; + + // Cap numVisible at count + this.options.numVisible = Math.min(this.count, this.options.numVisible); + + // Setup cross browser string + this.xform = 'transform'; + ['webkit', 'Moz', 'O', 'ms'].every((prefix) => { + var e = prefix + 'Transform'; + if (typeof document.body.style[e] !== 'undefined') { + this.xform = e; + return false; + } + return true; + }); + + this._setupEventHandlers(); + this._scroll(this.offset); + } + + static get defaults() { + return _defaults; + } + + static init(els, options) { + return super.init(this, els, options); + } + + /** + * Get Instance + */ + static getInstance(el) { + let domElem = !!el.jquery ? el[0] : el; + return domElem.M_Carousel; + } + + /** + * Teardown component + */ + destroy() { + this._removeEventHandlers(); + this.el.M_Carousel = undefined; + } + + /** + * Setup Event Handlers + */ + _setupEventHandlers() { + this._handleCarouselTapBound = this._handleCarouselTap.bind(this); + this._handleCarouselDragBound = this._handleCarouselDrag.bind(this); + this._handleCarouselReleaseBound = this._handleCarouselRelease.bind(this); + this._handleCarouselClickBound = this._handleCarouselClick.bind(this); + + if (typeof window.ontouchstart !== 'undefined') { + this.el.addEventListener('touchstart', this._handleCarouselTapBound); + this.el.addEventListener('touchmove', this._handleCarouselDragBound); + this.el.addEventListener('touchend', this._handleCarouselReleaseBound); + } + + this.el.addEventListener('mousedown', this._handleCarouselTapBound); + this.el.addEventListener('mousemove', this._handleCarouselDragBound); + this.el.addEventListener('mouseup', this._handleCarouselReleaseBound); + this.el.addEventListener('mouseleave', this._handleCarouselReleaseBound); + this.el.addEventListener('click', this._handleCarouselClickBound); + + if (this.showIndicators && this.$indicators) { + this._handleIndicatorClickBound = this._handleIndicatorClick.bind(this); + this.$indicators.find('.indicator-item').each((el, i) => { + el.addEventListener('click', this._handleIndicatorClickBound); + }); + } + + // Resize + let throttledResize = M.throttle(this._handleResize, 200); + this._handleThrottledResizeBound = throttledResize.bind(this); + + window.addEventListener('resize', this._handleThrottledResizeBound); + } + + /** + * Remove Event Handlers + */ + _removeEventHandlers() { + if (typeof window.ontouchstart !== 'undefined') { + this.el.removeEventListener('touchstart', this._handleCarouselTapBound); + this.el.removeEventListener('touchmove', this._handleCarouselDragBound); + this.el.removeEventListener('touchend', this._handleCarouselReleaseBound); + } + this.el.removeEventListener('mousedown', this._handleCarouselTapBound); + this.el.removeEventListener('mousemove', this._handleCarouselDragBound); + this.el.removeEventListener('mouseup', this._handleCarouselReleaseBound); + this.el.removeEventListener('mouseleave', this._handleCarouselReleaseBound); + this.el.removeEventListener('click', this._handleCarouselClickBound); + + if (this.showIndicators && this.$indicators) { + this.$indicators.find('.indicator-item').each((el, i) => { + el.removeEventListener('click', this._handleIndicatorClickBound); + }); + } + + window.removeEventListener('resize', this._handleThrottledResizeBound); + } + + /** + * Handle Carousel Tap + * @param {Event} e + */ + _handleCarouselTap(e) { + // Fixes firefox draggable image bug + if (e.type === 'mousedown' && $(e.target).is('img')) { + e.preventDefault(); + } + this.pressed = true; + this.dragged = false; + this.verticalDragged = false; + this.reference = this._xpos(e); + this.referenceY = this._ypos(e); + + this.velocity = this.amplitude = 0; + this.frame = this.offset; + this.timestamp = Date.now(); + clearInterval(this.ticker); + this.ticker = setInterval(this._trackBound, 100); + } + + /** + * Handle Carousel Drag + * @param {Event} e + */ + _handleCarouselDrag(e) { + let x, y, delta, deltaY; + if (this.pressed) { + x = this._xpos(e); + y = this._ypos(e); + delta = this.reference - x; + deltaY = Math.abs(this.referenceY - y); + if (deltaY < 30 && !this.verticalDragged) { + // If vertical scrolling don't allow dragging. + if (delta > 2 || delta < -2) { + this.dragged = true; + this.reference = x; + this._scroll(this.offset + delta); + } + } else if (this.dragged) { + // If dragging don't allow vertical scroll. + e.preventDefault(); + e.stopPropagation(); + return false; + } else { + // Vertical scrolling. + this.verticalDragged = true; + } + } + + if (this.dragged) { + // If dragging don't allow vertical scroll. + e.preventDefault(); + e.stopPropagation(); + return false; + } + } + + /** + * Handle Carousel Release + * @param {Event} e + */ + _handleCarouselRelease(e) { + if (this.pressed) { + this.pressed = false; + } else { + return; + } + + clearInterval(this.ticker); + this.target = this.offset; + if (this.velocity > 10 || this.velocity < -10) { + this.amplitude = 0.9 * this.velocity; + this.target = this.offset + this.amplitude; + } + this.target = Math.round(this.target / this.dim) * this.dim; + + // No wrap of items. + if (this.noWrap) { + if (this.target >= this.dim * (this.count - 1)) { + this.target = this.dim * (this.count - 1); + } else if (this.target < 0) { + this.target = 0; + } + } + this.amplitude = this.target - this.offset; + this.timestamp = Date.now(); + requestAnimationFrame(this._autoScrollBound); + + if (this.dragged) { + e.preventDefault(); + e.stopPropagation(); + } + return false; + } + + /** + * Handle Carousel CLick + * @param {Event} e + */ + _handleCarouselClick(e) { + // Disable clicks if carousel was dragged. + if (this.dragged) { + e.preventDefault(); + e.stopPropagation(); + return false; + } else if (!this.options.fullWidth) { + let clickedIndex = $(e.target) + .closest('.carousel-item') + .index(); + let diff = this._wrap(this.center) - clickedIndex; + + // Disable clicks if carousel was shifted by click + if (diff !== 0) { + e.preventDefault(); + e.stopPropagation(); + } + this._cycleTo(clickedIndex); + } + } + + /** + * Handle Indicator CLick + * @param {Event} e + */ + _handleIndicatorClick(e) { + e.stopPropagation(); + + let indicator = $(e.target).closest('.indicator-item'); + if (indicator.length) { + this._cycleTo(indicator.index()); + } + } + + /** + * Handle Throttle Resize + * @param {Event} e + */ + _handleResize(e) { + if (this.options.fullWidth) { + this.itemWidth = this.$el + .find('.carousel-item') + .first() + .innerWidth(); + this.imageHeight = this.$el.find('.carousel-item.active').height(); + this.dim = this.itemWidth * 2 + this.options.padding; + this.offset = this.center * 2 * this.itemWidth; + this.target = this.offset; + this._setCarouselHeight(true); + } else { + this._scroll(); + } + } + + /** + * Set carousel height based on first slide + * @param {Booleam} imageOnly - true for image slides + */ + _setCarouselHeight(imageOnly) { + let firstSlide = this.$el.find('.carousel-item.active').length + ? this.$el.find('.carousel-item.active').first() + : this.$el.find('.carousel-item').first(); + let firstImage = firstSlide.find('img').first(); + if (firstImage.length) { + if (firstImage[0].complete) { + // If image won't trigger the load event + let imageHeight = firstImage.height(); + if (imageHeight > 0) { + this.$el.css('height', imageHeight + 'px'); + } else { + // If image still has no height, use the natural dimensions to calculate + let naturalWidth = firstImage[0].naturalWidth; + let naturalHeight = firstImage[0].naturalHeight; + let adjustedHeight = this.$el.width() / naturalWidth * naturalHeight; + this.$el.css('height', adjustedHeight + 'px'); + } + } else { + // Get height when image is loaded normally + firstImage.one('load', (el, i) => { + this.$el.css('height', el.offsetHeight + 'px'); + }); + } + } else if (!imageOnly) { + let slideHeight = firstSlide.height(); + this.$el.css('height', slideHeight + 'px'); + } + } + + /** + * Get x position from event + * @param {Event} e + */ + _xpos(e) { + // touch event + if (e.targetTouches && e.targetTouches.length >= 1) { + return e.targetTouches[0].clientX; + } + + // mouse event + return e.clientX; + } + + /** + * Get y position from event + * @param {Event} e + */ + _ypos(e) { + // touch event + if (e.targetTouches && e.targetTouches.length >= 1) { + return e.targetTouches[0].clientY; + } + + // mouse event + return e.clientY; + } + + /** + * Wrap index + * @param {Number} x + */ + _wrap(x) { + return x >= this.count ? x % this.count : x < 0 ? this._wrap(this.count + x % this.count) : x; + } + + /** + * Tracks scrolling information + */ + _track() { + let now, elapsed, delta, v; + + now = Date.now(); + elapsed = now - this.timestamp; + this.timestamp = now; + delta = this.offset - this.frame; + this.frame = this.offset; + + v = 1000 * delta / (1 + elapsed); + this.velocity = 0.8 * v + 0.2 * this.velocity; + } + + /** + * Auto scrolls to nearest carousel item. + */ + _autoScroll() { + let elapsed, delta; + + if (this.amplitude) { + elapsed = Date.now() - this.timestamp; + delta = this.amplitude * Math.exp(-elapsed / this.options.duration); + if (delta > 2 || delta < -2) { + this._scroll(this.target - delta); + requestAnimationFrame(this._autoScrollBound); + } else { + this._scroll(this.target); + } + } + } + + /** + * Scroll to target + * @param {Number} x + */ + _scroll(x) { + // Track scrolling state + if (!this.$el.hasClass('scrolling')) { + this.el.classList.add('scrolling'); + } + if (this.scrollingTimeout != null) { + window.clearTimeout(this.scrollingTimeout); + } + this.scrollingTimeout = window.setTimeout(() => { + this.$el.removeClass('scrolling'); + }, this.options.duration); + + // Start actual scroll + let i, + half, + delta, + dir, + tween, + el, + alignment, + zTranslation, + tweenedOpacity, + centerTweenedOpacity; + let lastCenter = this.center; + let numVisibleOffset = 1 / this.options.numVisible; + + this.offset = typeof x === 'number' ? x : this.offset; + this.center = Math.floor((this.offset + this.dim / 2) / this.dim); + delta = this.offset - this.center * this.dim; + dir = delta < 0 ? 1 : -1; + tween = -dir * delta * 2 / this.dim; + half = this.count >> 1; + + if (this.options.fullWidth) { + alignment = 'translateX(0)'; + centerTweenedOpacity = 1; + } else { + alignment = 'translateX(' + (this.el.clientWidth - this.itemWidth) / 2 + 'px) '; + alignment += 'translateY(' + (this.el.clientHeight - this.itemHeight) / 2 + 'px)'; + centerTweenedOpacity = 1 - numVisibleOffset * tween; + } + + // Set indicator active + if (this.showIndicators) { + let diff = this.center % this.count; + let activeIndicator = this.$indicators.find('.indicator-item.active'); + if (activeIndicator.index() !== diff) { + activeIndicator.removeClass('active'); + this.$indicators + .find('.indicator-item') + .eq(diff)[0] + .classList.add('active'); + } + } + + // center + // Don't show wrapped items. + if (!this.noWrap || (this.center >= 0 && this.center < this.count)) { + el = this.images[this._wrap(this.center)]; + + // Add active class to center item. + if (!$(el).hasClass('active')) { + this.$el.find('.carousel-item').removeClass('active'); + el.classList.add('active'); + } + let transformString = `${alignment} translateX(${-delta / 2}px) translateX(${dir * + this.options.shift * + tween * + i}px) translateZ(${this.options.dist * tween}px)`; + this._updateItemStyle(el, centerTweenedOpacity, 0, transformString); + } + + for (i = 1; i <= half; ++i) { + // right side + if (this.options.fullWidth) { + zTranslation = this.options.dist; + tweenedOpacity = i === half && delta < 0 ? 1 - tween : 1; + } else { + zTranslation = this.options.dist * (i * 2 + tween * dir); + tweenedOpacity = 1 - numVisibleOffset * (i * 2 + tween * dir); + } + // Don't show wrapped items. + if (!this.noWrap || this.center + i < this.count) { + el = this.images[this._wrap(this.center + i)]; + let transformString = `${alignment} translateX(${this.options.shift + + (this.dim * i - delta) / 2}px) translateZ(${zTranslation}px)`; + this._updateItemStyle(el, tweenedOpacity, -i, transformString); + } + + // left side + if (this.options.fullWidth) { + zTranslation = this.options.dist; + tweenedOpacity = i === half && delta > 0 ? 1 - tween : 1; + } else { + zTranslation = this.options.dist * (i * 2 - tween * dir); + tweenedOpacity = 1 - numVisibleOffset * (i * 2 - tween * dir); + } + // Don't show wrapped items. + if (!this.noWrap || this.center - i >= 0) { + el = this.images[this._wrap(this.center - i)]; + let transformString = `${alignment} translateX(${-this.options.shift + + (-this.dim * i - delta) / 2}px) translateZ(${zTranslation}px)`; + this._updateItemStyle(el, tweenedOpacity, -i, transformString); + } + } + + // center + // Don't show wrapped items. + if (!this.noWrap || (this.center >= 0 && this.center < this.count)) { + el = this.images[this._wrap(this.center)]; + let transformString = `${alignment} translateX(${-delta / 2}px) translateX(${dir * + this.options.shift * + tween}px) translateZ(${this.options.dist * tween}px)`; + this._updateItemStyle(el, centerTweenedOpacity, 0, transformString); + } + + // onCycleTo callback + let $currItem = this.$el.find('.carousel-item').eq(this._wrap(this.center)); + if (lastCenter !== this.center && typeof this.options.onCycleTo === 'function') { + this.options.onCycleTo.call(this, $currItem[0], this.dragged); + } + + // One time callback + if (typeof this.oneTimeCallback === 'function') { + this.oneTimeCallback.call(this, $currItem[0], this.dragged); + this.oneTimeCallback = null; + } + } + + /** + * Cycle to target + * @param {Element} el + * @param {Number} opacity + * @param {Number} zIndex + * @param {String} transform + */ + _updateItemStyle(el, opacity, zIndex, transform) { + el.style[this.xform] = transform; + el.style.zIndex = zIndex; + el.style.opacity = opacity; + el.style.visibility = 'visible'; + } + + /** + * Cycle to target + * @param {Number} n + * @param {Function} callback + */ + _cycleTo(n, callback) { + let diff = this.center % this.count - n; + + // Account for wraparound. + if (!this.noWrap) { + if (diff < 0) { + if (Math.abs(diff + this.count) < Math.abs(diff)) { + diff += this.count; + } + } else if (diff > 0) { + if (Math.abs(diff - this.count) < diff) { + diff -= this.count; + } + } + } + + this.target = this.dim * Math.round(this.offset / this.dim); + // Next + if (diff < 0) { + this.target += this.dim * Math.abs(diff); + + // Prev + } else if (diff > 0) { + this.target -= this.dim * diff; + } + + // Set one time callback + if (typeof callback === 'function') { + this.oneTimeCallback = callback; + } + + // Scroll + if (this.offset !== this.target) { + this.amplitude = this.target - this.offset; + this.timestamp = Date.now(); + requestAnimationFrame(this._autoScrollBound); + } + } + + /** + * Cycle to next item + * @param {Number} [n] + */ + next(n) { + if (n === undefined || isNaN(n)) { + n = 1; + } + + let index = this.center + n; + if (index >= this.count || index < 0) { + if (this.noWrap) { + return; + } + + index = this._wrap(index); + } + this._cycleTo(index); + } + + /** + * Cycle to previous item + * @param {Number} [n] + */ + prev(n) { + if (n === undefined || isNaN(n)) { + n = 1; + } + + let index = this.center - n; + if (index >= this.count || index < 0) { + if (this.noWrap) { + return; + } + + index = this._wrap(index); + } + + this._cycleTo(index); + } + + /** + * Cycle to nth item + * @param {Number} [n] + * @param {Function} callback + */ + set(n, callback) { + if (n === undefined || isNaN(n)) { + n = 0; + } + + if (n > this.count || n < 0) { + if (this.noWrap) { + return; + } + + n = this._wrap(n); + } + + this._cycleTo(n, callback); + } + } + + M.Carousel = Carousel; + + if (M.jQueryLoaded) { + M.initializeJqueryWrapper(Carousel, 'carousel', 'M_Carousel'); + } +})(cash); diff --git a/static/js/cash.js b/static/js/cash.js new file mode 100644 index 00000000..17d95acd --- /dev/null +++ b/static/js/cash.js @@ -0,0 +1,960 @@ +/*! cash-dom 1.3.5, https://github.com/kenwheeler/cash @license MIT */ +(function (factory) { + window.cash = factory(); +})(function () { + var doc = document, win = window, ArrayProto = Array.prototype, slice = ArrayProto.slice, filter = ArrayProto.filter, push = ArrayProto.push; + + var noop = function () {}, isFunction = function (item) { + // @see https://crbug.com/568448 + return typeof item === typeof noop && item.call; + }, isString = function (item) { + return typeof item === typeof ""; + }; + + var idMatch = /^#[\w-]*$/, classMatch = /^\.[\w-]*$/, htmlMatch = /<.+>/, singlet = /^\w+$/; + + function find(selector, context) { + context = context || doc; + var elems = (classMatch.test(selector) ? context.getElementsByClassName(selector.slice(1)) : singlet.test(selector) ? context.getElementsByTagName(selector) : context.querySelectorAll(selector)); + return elems; + } + + var frag; + function parseHTML(str) { + if (!frag) { + frag = doc.implementation.createHTMLDocument(null); + var base = frag.createElement("base"); + base.href = doc.location.href; + frag.head.appendChild(base); + } + + frag.body.innerHTML = str; + + return frag.body.childNodes; + } + + function onReady(fn) { + if (doc.readyState !== "loading") { + fn(); + } else { + doc.addEventListener("DOMContentLoaded", fn); + } + } + + function Init(selector, context) { + if (!selector) { + return this; + } + + // If already a cash collection, don't do any further processing + if (selector.cash && selector !== win) { + return selector; + } + + var elems = selector, i = 0, length; + + if (isString(selector)) { + elems = (idMatch.test(selector) ? + // If an ID use the faster getElementById check + doc.getElementById(selector.slice(1)) : htmlMatch.test(selector) ? + // If HTML, parse it into real elements + parseHTML(selector) : + // else use `find` + find(selector, context)); + + // If function, use as shortcut for DOM ready + } else if (isFunction(selector)) { + onReady(selector);return this; + } + + if (!elems) { + return this; + } + + // If a single DOM element is passed in or received via ID, return the single element + if (elems.nodeType || elems === win) { + this[0] = elems; + this.length = 1; + } else { + // Treat like an array and loop through each item. + length = this.length = elems.length; + for (; i < length; i++) { + this[i] = elems[i]; + } + } + + return this; + } + + function cash(selector, context) { + return new Init(selector, context); + } + + var fn = cash.fn = cash.prototype = Init.prototype = { // jshint ignore:line + cash: true, + length: 0, + push: push, + splice: ArrayProto.splice, + map: ArrayProto.map, + init: Init + }; + + Object.defineProperty(fn, "constructor", { value: cash }); + + cash.parseHTML = parseHTML; + cash.noop = noop; + cash.isFunction = isFunction; + cash.isString = isString; + + cash.extend = fn.extend = function (target) { + target = target || {}; + + var args = slice.call(arguments), length = args.length, i = 1; + + if (args.length === 1) { + target = this; + i = 0; + } + + for (; i < length; i++) { + if (!args[i]) { + continue; + } + for (var key in args[i]) { + if (args[i].hasOwnProperty(key)) { + target[key] = args[i][key]; + } + } + } + + return target; + }; + + function each(collection, callback) { + var l = collection.length, i = 0; + + for (; i < l; i++) { + if (callback.call(collection[i], collection[i], i, collection) === false) { + break; + } + } + } + + function matches(el, selector) { + var m = el && (el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector || el.oMatchesSelector); + return !!m && m.call(el, selector); + } + + function getCompareFunction(selector) { + return ( + /* Use browser's `matches` function if string */ + isString(selector) ? matches : + /* Match a cash element */ + selector.cash ? function (el) { + return selector.is(el); + } : + /* Direct comparison */ + function (el, selector) { + return el === selector; + }); + } + + function unique(collection) { + return cash(slice.call(collection).filter(function (item, index, self) { + return self.indexOf(item) === index; + })); + } + + cash.extend({ + merge: function (first, second) { + var len = +second.length, i = first.length, j = 0; + + for (; j < len; i++, j++) { + first[i] = second[j]; + } + + first.length = i; + return first; + }, + + each: each, + matches: matches, + unique: unique, + isArray: Array.isArray, + isNumeric: function (n) { + return !isNaN(parseFloat(n)) && isFinite(n); + } + + }); + + var uid = cash.uid = "_cash" + Date.now(); + + function getDataCache(node) { + return (node[uid] = node[uid] || {}); + } + + function setData(node, key, value) { + return (getDataCache(node)[key] = value); + } + + function getData(node, key) { + var c = getDataCache(node); + if (c[key] === undefined) { + c[key] = node.dataset ? node.dataset[key] : cash(node).attr("data-" + key); + } + return c[key]; + } + + function removeData(node, key) { + var c = getDataCache(node); + if (c) { + delete c[key]; + } else if (node.dataset) { + delete node.dataset[key]; + } else { + cash(node).removeAttr("data-" + name); + } + } + + fn.extend({ + data: function (name, value) { + if (isString(name)) { + return (value === undefined ? getData(this[0], name) : this.each(function (v) { + return setData(v, name, value); + })); + } + + for (var key in name) { + this.data(key, name[key]); + } + + return this; + }, + + removeData: function (key) { + return this.each(function (v) { + return removeData(v, key); + }); + } + + }); + + var notWhiteMatch = /\S+/g; + + function getClasses(c) { + return isString(c) && c.match(notWhiteMatch); + } + + function hasClass(v, c) { + return (v.classList ? v.classList.contains(c) : new RegExp("(^| )" + c + "( |$)", "gi").test(v.className)); + } + + function addClass(v, c, spacedName) { + if (v.classList) { + v.classList.add(c); + } else if (spacedName.indexOf(" " + c + " ")) { + v.className += " " + c; + } + } + + function removeClass(v, c) { + if (v.classList) { + v.classList.remove(c); + } else { + v.className = v.className.replace(c, ""); + } + } + + fn.extend({ + addClass: function (c) { + var classes = getClasses(c); + + return (classes ? this.each(function (v) { + var spacedName = " " + v.className + " "; + each(classes, function (c) { + addClass(v, c, spacedName); + }); + }) : this); + }, + + attr: function (name, value) { + if (!name) { + return undefined; + } + + if (isString(name)) { + if (value === undefined) { + return this[0] ? this[0].getAttribute ? this[0].getAttribute(name) : this[0][name] : undefined; + } + + return this.each(function (v) { + if (v.setAttribute) { + v.setAttribute(name, value); + } else { + v[name] = value; + } + }); + } + + for (var key in name) { + this.attr(key, name[key]); + } + + return this; + }, + + hasClass: function (c) { + var check = false, classes = getClasses(c); + if (classes && classes.length) { + this.each(function (v) { + check = hasClass(v, classes[0]); + return !check; + }); + } + return check; + }, + + prop: function (name, value) { + if (isString(name)) { + return (value === undefined ? this[0][name] : this.each(function (v) { + v[name] = value; + })); + } + + for (var key in name) { + this.prop(key, name[key]); + } + + return this; + }, + + removeAttr: function (name) { + return this.each(function (v) { + if (v.removeAttribute) { + v.removeAttribute(name); + } else { + delete v[name]; + } + }); + }, + + removeClass: function (c) { + if (!arguments.length) { + return this.attr("class", ""); + } + var classes = getClasses(c); + return (classes ? this.each(function (v) { + each(classes, function (c) { + removeClass(v, c); + }); + }) : this); + }, + + removeProp: function (name) { + return this.each(function (v) { + delete v[name]; + }); + }, + + toggleClass: function (c, state) { + if (state !== undefined) { + return this[state ? "addClass" : "removeClass"](c); + } + var classes = getClasses(c); + return (classes ? this.each(function (v) { + var spacedName = " " + v.className + " "; + each(classes, function (c) { + if (hasClass(v, c)) { + removeClass(v, c); + } else { + addClass(v, c, spacedName); + } + }); + }) : this); + } }); + + fn.extend({ + add: function (selector, context) { + return unique(cash.merge(this, cash(selector, context))); + }, + + each: function (callback) { + each(this, callback); + return this; + }, + + eq: function (index) { + return cash(this.get(index)); + }, + + filter: function (selector) { + if (!selector) { + return this; + } + + var comparator = (isFunction(selector) ? selector : getCompareFunction(selector)); + + return cash(filter.call(this, function (e) { + return comparator(e, selector); + })); + }, + + first: function () { + return this.eq(0); + }, + + get: function (index) { + if (index === undefined) { + return slice.call(this); + } + return (index < 0 ? this[index + this.length] : this[index]); + }, + + index: function (elem) { + var child = elem ? cash(elem)[0] : this[0], collection = elem ? this : cash(child).parent().children(); + return slice.call(collection).indexOf(child); + }, + + last: function () { + return this.eq(-1); + } + + }); + + var camelCase = (function () { + var camelRegex = /(?:^\w|[A-Z]|\b\w)/g, whiteSpace = /[\s-_]+/g; + return function (str) { + return str.replace(camelRegex, function (letter, index) { + return letter[index === 0 ? "toLowerCase" : "toUpperCase"](); + }).replace(whiteSpace, ""); + }; + }()); + + var getPrefixedProp = (function () { + var cache = {}, doc = document, div = doc.createElement("div"), style = div.style; + + return function (prop) { + prop = camelCase(prop); + if (cache[prop]) { + return cache[prop]; + } + + var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1), prefixes = ["webkit", "moz", "ms", "o"], props = (prop + " " + (prefixes).join(ucProp + " ") + ucProp).split(" "); + + each(props, function (p) { + if (p in style) { + cache[p] = prop = cache[prop] = p; + return false; + } + }); + + return cache[prop]; + }; + }()); + + cash.prefixedProp = getPrefixedProp; + cash.camelCase = camelCase; + + fn.extend({ + css: function (prop, value) { + if (isString(prop)) { + prop = getPrefixedProp(prop); + return (arguments.length > 1 ? this.each(function (v) { + return v.style[prop] = value; + }) : win.getComputedStyle(this[0])[prop]); + } + + for (var key in prop) { + this.css(key, prop[key]); + } + + return this; + } + + }); + + function compute(el, prop) { + return parseInt(win.getComputedStyle(el[0], null)[prop], 10) || 0; + } + + each(["Width", "Height"], function (v) { + var lower = v.toLowerCase(); + + fn[lower] = function () { + return this[0].getBoundingClientRect()[lower]; + }; + + fn["inner" + v] = function () { + return this[0]["client" + v]; + }; + + fn["outer" + v] = function (margins) { + return this[0]["offset" + v] + (margins ? compute(this, "margin" + (v === "Width" ? "Left" : "Top")) + compute(this, "margin" + (v === "Width" ? "Right" : "Bottom")) : 0); + }; + }); + + function registerEvent(node, eventName, callback) { + var eventCache = getData(node, "_cashEvents") || setData(node, "_cashEvents", {}); + eventCache[eventName] = eventCache[eventName] || []; + eventCache[eventName].push(callback); + node.addEventListener(eventName, callback); + } + + function removeEvent(node, eventName, callback) { + var events = getData(node, "_cashEvents"), eventCache = (events && events[eventName]), index; + + if (!eventCache) { + return; + } + + if (callback) { + node.removeEventListener(eventName, callback); + index = eventCache.indexOf(callback); + if (index >= 0) { + eventCache.splice(index, 1); + } + } else { + each(eventCache, function (event) { + node.removeEventListener(eventName, event); + }); + eventCache = []; + } + } + + fn.extend({ + off: function (eventName, callback) { + return this.each(function (v) { + return removeEvent(v, eventName, callback); + }); + }, + + on: function (eventName, delegate, callback, runOnce) { + // jshint ignore:line + var originalCallback; + if (!isString(eventName)) { + for (var key in eventName) { + this.on(key, delegate, eventName[key]); + } + return this; + } + + if (isFunction(delegate)) { + callback = delegate; + delegate = null; + } + + if (eventName === "ready") { + onReady(callback); + return this; + } + + if (delegate) { + originalCallback = callback; + callback = function (e) { + var t = e.target; + while (!matches(t, delegate)) { + if (t === this || t === null) { + return (t = false); + } + + t = t.parentNode; + } + + if (t) { + originalCallback.call(t, e); + } + }; + } + + return this.each(function (v) { + var finalCallback = callback; + if (runOnce) { + finalCallback = function () { + callback.apply(this, arguments); + removeEvent(v, eventName, finalCallback); + }; + } + registerEvent(v, eventName, finalCallback); + }); + }, + + one: function (eventName, delegate, callback) { + return this.on(eventName, delegate, callback, true); + }, + + ready: onReady, + + /** + * Modified + * Triggers browser event + * @param String eventName + * @param Object data - Add properties to event object + */ + trigger: function (eventName, data) { + if (document.createEvent) { + let evt = document.createEvent('HTMLEvents'); + evt.initEvent(eventName, true, false); + evt = this.extend(evt, data); + return this.each(function (v) { + return v.dispatchEvent(evt); + }); + } + } + + }); + + function encode(name, value) { + return "&" + encodeURIComponent(name) + "=" + encodeURIComponent(value).replace(/%20/g, "+"); + } + + function getSelectMultiple_(el) { + var values = []; + each(el.options, function (o) { + if (o.selected) { + values.push(o.value); + } + }); + return values.length ? values : null; + } + + function getSelectSingle_(el) { + var selectedIndex = el.selectedIndex; + return selectedIndex >= 0 ? el.options[selectedIndex].value : null; + } + + function getValue(el) { + var type = el.type; + if (!type) { + return null; + } + switch (type.toLowerCase()) { + case "select-one": + return getSelectSingle_(el); + case "select-multiple": + return getSelectMultiple_(el); + case "radio": + return (el.checked) ? el.value : null; + case "checkbox": + return (el.checked) ? el.value : null; + default: + return el.value ? el.value : null; + } + } + + fn.extend({ + serialize: function () { + var query = ""; + + each(this[0].elements || this, function (el) { + if (el.disabled || el.tagName === "FIELDSET") { + return; + } + var name = el.name; + switch (el.type.toLowerCase()) { + case "file": + case "reset": + case "submit": + case "button": + break; + case "select-multiple": + var values = getValue(el); + if (values !== null) { + each(values, function (value) { + query += encode(name, value); + }); + } + break; + default: + var value = getValue(el); + if (value !== null) { + query += encode(name, value); + } + } + }); + + return query.substr(1); + }, + + val: function (value) { + if (value === undefined) { + return getValue(this[0]); + } + + return this.each(function (v) { + return v.value = value; + }); + } + + }); + + function insertElement(el, child, prepend) { + if (prepend) { + var first = el.childNodes[0]; + el.insertBefore(child, first); + } else { + el.appendChild(child); + } + } + + function insertContent(parent, child, prepend) { + var str = isString(child); + + if (!str && child.length) { + each(child, function (v) { + return insertContent(parent, v, prepend); + }); + return; + } + + each(parent, str ? function (v) { + return v.insertAdjacentHTML(prepend ? "afterbegin" : "beforeend", child); + } : function (v, i) { + return insertElement(v, (i === 0 ? child : child.cloneNode(true)), prepend); + }); + } + + fn.extend({ + after: function (selector) { + cash(selector).insertAfter(this); + return this; + }, + + append: function (content) { + insertContent(this, content); + return this; + }, + + appendTo: function (parent) { + insertContent(cash(parent), this); + return this; + }, + + before: function (selector) { + cash(selector).insertBefore(this); + return this; + }, + + clone: function () { + return cash(this.map(function (v) { + return v.cloneNode(true); + })); + }, + + empty: function () { + this.html(""); + return this; + }, + + html: function (content) { + if (content === undefined) { + return this[0].innerHTML; + } + var source = (content.nodeType ? content[0].outerHTML : content); + return this.each(function (v) { + return v.innerHTML = source; + }); + }, + + insertAfter: function (selector) { + var _this = this; + + + cash(selector).each(function (el, i) { + var parent = el.parentNode, sibling = el.nextSibling; + _this.each(function (v) { + parent.insertBefore((i === 0 ? v : v.cloneNode(true)), sibling); + }); + }); + + return this; + }, + + insertBefore: function (selector) { + var _this2 = this; + cash(selector).each(function (el, i) { + var parent = el.parentNode; + _this2.each(function (v) { + parent.insertBefore((i === 0 ? v : v.cloneNode(true)), el); + }); + }); + return this; + }, + + prepend: function (content) { + insertContent(this, content, true); + return this; + }, + + prependTo: function (parent) { + insertContent(cash(parent), this, true); + return this; + }, + + remove: function () { + return this.each(function (v) { + if (!!v.parentNode) { + return v.parentNode.removeChild(v); + } + }); + }, + + text: function (content) { + if (content === undefined) { + return this[0].textContent; + } + return this.each(function (v) { + return v.textContent = content; + }); + } + + }); + + var docEl = doc.documentElement; + + fn.extend({ + position: function () { + var el = this[0]; + return { + left: el.offsetLeft, + top: el.offsetTop + }; + }, + + offset: function () { + var rect = this[0].getBoundingClientRect(); + return { + top: rect.top + win.pageYOffset - docEl.clientTop, + left: rect.left + win.pageXOffset - docEl.clientLeft + }; + }, + + offsetParent: function () { + return cash(this[0].offsetParent); + } + + }); + + fn.extend({ + children: function (selector) { + var elems = []; + this.each(function (el) { + push.apply(elems, el.children); + }); + elems = unique(elems); + + return (!selector ? elems : elems.filter(function (v) { + return matches(v, selector); + })); + }, + + closest: function (selector) { + if (!selector || this.length < 1) { + return cash(); + } + if (this.is(selector)) { + return this.filter(selector); + } + return this.parent().closest(selector); + }, + + is: function (selector) { + if (!selector) { + return false; + } + + var match = false, comparator = getCompareFunction(selector); + + this.each(function (el) { + match = comparator(el, selector); + return !match; + }); + + return match; + }, + + find: function (selector) { + if (!selector || selector.nodeType) { + return cash(selector && this.has(selector).length ? selector : null); + } + + var elems = []; + this.each(function (el) { + push.apply(elems, find(selector, el)); + }); + + return unique(elems); + }, + + has: function (selector) { + var comparator = (isString(selector) ? function (el) { + return find(selector, el).length !== 0; + } : function (el) { + return el.contains(selector); + }); + + return this.filter(comparator); + }, + + next: function () { + return cash(this[0].nextElementSibling); + }, + + not: function (selector) { + if (!selector) { + return this; + } + + var comparator = getCompareFunction(selector); + + return this.filter(function (el) { + return !comparator(el, selector); + }); + }, + + parent: function () { + var result = []; + + this.each(function (item) { + if (item && item.parentNode) { + result.push(item.parentNode); + } + }); + + return unique(result); + }, + + parents: function (selector) { + var last, result = []; + + this.each(function (item) { + last = item; + + while (last && last.parentNode && last !== doc.body.parentNode) { + last = last.parentNode; + + if (!selector || (selector && matches(last, selector))) { + result.push(last); + } + } + }); + + return unique(result); + }, + + prev: function () { + return cash(this[0].previousElementSibling); + }, + + siblings: function (selector) { + var collection = this.parent().children(selector), el = this[0]; + + return collection.filter(function (i) { + return i !== el; + }); + } + + }); + + + return cash; +}); diff --git a/static/js/characterCounter.js b/static/js/characterCounter.js new file mode 100644 index 00000000..761d1bfc --- /dev/null +++ b/static/js/characterCounter.js @@ -0,0 +1,136 @@ +(function($) { + 'use strict'; + + let _defaults = {}; + + /** + * @class + * + */ + class CharacterCounter extends Component { + /** + * Construct CharacterCounter instance + * @constructor + * @param {Element} el + * @param {Object} options + */ + constructor(el, options) { + super(CharacterCounter, el, options); + + this.el.M_CharacterCounter = this; + + /** + * Options for the character counter + */ + this.options = $.extend({}, CharacterCounter.defaults, options); + + this.isInvalid = false; + this.isValidLength = false; + this._setupCounter(); + this._setupEventHandlers(); + } + + static get defaults() { + return _defaults; + } + + static init(els, options) { + return super.init(this, els, options); + } + + /** + * Get Instance + */ + static getInstance(el) { + let domElem = !!el.jquery ? el[0] : el; + return domElem.M_CharacterCounter; + } + + /** + * Teardown component + */ + destroy() { + this._removeEventHandlers(); + this.el.CharacterCounter = undefined; + this._removeCounter(); + } + + /** + * Setup Event Handlers + */ + _setupEventHandlers() { + this._handleUpdateCounterBound = this.updateCounter.bind(this); + + this.el.addEventListener('focus', this._handleUpdateCounterBound, true); + this.el.addEventListener('input', this._handleUpdateCounterBound, true); + } + + /** + * Remove Event Handlers + */ + _removeEventHandlers() { + this.el.removeEventListener('focus', this._handleUpdateCounterBound, true); + this.el.removeEventListener('input', this._handleUpdateCounterBound, true); + } + + /** + * Setup counter element + */ + _setupCounter() { + this.counterEl = document.createElement('span'); + $(this.counterEl) + .addClass('character-counter') + .css({ + float: 'right', + 'font-size': '12px', + height: 1 + }); + + this.$el.parent().append(this.counterEl); + } + + /** + * Remove counter element + */ + _removeCounter() { + $(this.counterEl).remove(); + } + + /** + * Update counter + */ + updateCounter() { + let maxLength = +this.$el.attr('data-length'), + actualLength = this.el.value.length; + this.isValidLength = actualLength <= maxLength; + let counterString = actualLength; + + if (maxLength) { + counterString += '/' + maxLength; + this._validateInput(); + } + + $(this.counterEl).html(counterString); + } + + /** + * Add validation classes + */ + _validateInput() { + if (this.isValidLength && this.isInvalid) { + this.isInvalid = false; + this.$el.removeClass('invalid'); + } else if (!this.isValidLength && !this.isInvalid) { + this.isInvalid = true; + this.$el.removeClass('valid'); + this.$el.addClass('invalid'); + } + } + } + + M.CharacterCounter = CharacterCounter; + + if (M.jQueryLoaded) { + M.initializeJqueryWrapper(CharacterCounter, 'characterCounter', 'M_CharacterCounter'); + } +})(cash); diff --git a/static/js/chips.js b/static/js/chips.js new file mode 100644 index 00000000..84fd9341 --- /dev/null +++ b/static/js/chips.js @@ -0,0 +1,481 @@ +(function($) { + 'use strict'; + + let _defaults = { + data: [], + placeholder: '', + secondaryPlaceholder: '', + autocompleteOptions: {}, + limit: Infinity, + onChipAdd: null, + onChipSelect: null, + onChipDelete: null + }; + + /** + * @typedef {Object} chip + * @property {String} tag chip tag string + * @property {String} [image] chip avatar image string + */ + + /** + * @class + * + */ + class Chips extends Component { + /** + * Construct Chips instance and set up overlay + * @constructor + * @param {Element} el + * @param {Object} options + */ + constructor(el, options) { + super(Chips, el, options); + + this.el.M_Chips = this; + + /** + * Options for the modal + * @member Chips#options + * @prop {Array} data + * @prop {String} placeholder + * @prop {String} secondaryPlaceholder + * @prop {Object} autocompleteOptions + */ + this.options = $.extend({}, Chips.defaults, options); + + this.$el.addClass('chips input-field'); + this.chipsData = []; + this.$chips = $(); + this._setupInput(); + this.hasAutocomplete = Object.keys(this.options.autocompleteOptions).length > 0; + + // Set input id + if (!this.$input.attr('id')) { + this.$input.attr('id', M.guid()); + } + + // Render initial chips + if (this.options.data.length) { + this.chipsData = this.options.data; + this._renderChips(this.chipsData); + } + + // Setup autocomplete if needed + if (this.hasAutocomplete) { + this._setupAutocomplete(); + } + + this._setPlaceholder(); + this._setupLabel(); + this._setupEventHandlers(); + } + + static get defaults() { + return _defaults; + } + + static init(els, options) { + return super.init(this, els, options); + } + + /** + * Get Instance + */ + static getInstance(el) { + let domElem = !!el.jquery ? el[0] : el; + return domElem.M_Chips; + } + + /** + * Get Chips Data + */ + getData() { + return this.chipsData; + } + + /** + * Teardown component + */ + destroy() { + this._removeEventHandlers(); + this.$chips.remove(); + this.el.M_Chips = undefined; + } + + /** + * Setup Event Handlers + */ + _setupEventHandlers() { + this._handleChipClickBound = this._handleChipClick.bind(this); + this._handleInputKeydownBound = this._handleInputKeydown.bind(this); + this._handleInputFocusBound = this._handleInputFocus.bind(this); + this._handleInputBlurBound = this._handleInputBlur.bind(this); + + this.el.addEventListener('click', this._handleChipClickBound); + document.addEventListener('keydown', Chips._handleChipsKeydown); + document.addEventListener('keyup', Chips._handleChipsKeyup); + this.el.addEventListener('blur', Chips._handleChipsBlur, true); + this.$input[0].addEventListener('focus', this._handleInputFocusBound); + this.$input[0].addEventListener('blur', this._handleInputBlurBound); + this.$input[0].addEventListener('keydown', this._handleInputKeydownBound); + } + + /** + * Remove Event Handlers + */ + _removeEventHandlers() { + this.el.removeEventListener('click', this._handleChipClickBound); + document.removeEventListener('keydown', Chips._handleChipsKeydown); + document.removeEventListener('keyup', Chips._handleChipsKeyup); + this.el.removeEventListener('blur', Chips._handleChipsBlur, true); + this.$input[0].removeEventListener('focus', this._handleInputFocusBound); + this.$input[0].removeEventListener('blur', this._handleInputBlurBound); + this.$input[0].removeEventListener('keydown', this._handleInputKeydownBound); + } + + /** + * Handle Chip Click + * @param {Event} e + */ + _handleChipClick(e) { + let $chip = $(e.target).closest('.chip'); + let clickedClose = $(e.target).is('.close'); + if ($chip.length) { + let index = $chip.index(); + if (clickedClose) { + // delete chip + this.deleteChip(index); + this.$input[0].focus(); + } else { + // select chip + this.selectChip(index); + } + + // Default handle click to focus on input + } else { + this.$input[0].focus(); + } + } + + /** + * Handle Chips Keydown + * @param {Event} e + */ + static _handleChipsKeydown(e) { + Chips._keydown = true; + + let $chips = $(e.target).closest('.chips'); + let chipsKeydown = e.target && $chips.length; + + // Don't handle keydown inputs on input and textarea + if ($(e.target).is('input, textarea') || !chipsKeydown) { + return; + } + + let currChips = $chips[0].M_Chips; + + // backspace and delete + if (e.keyCode === 8 || e.keyCode === 46) { + e.preventDefault(); + + let selectIndex = currChips.chipsData.length; + if (currChips._selectedChip) { + let index = currChips._selectedChip.index(); + currChips.deleteChip(index); + currChips._selectedChip = null; + + // Make sure selectIndex doesn't go negative + selectIndex = Math.max(index - 1, 0); + } + + if (currChips.chipsData.length) { + currChips.selectChip(selectIndex); + } + + // left arrow key + } else if (e.keyCode === 37) { + if (currChips._selectedChip) { + let selectIndex = currChips._selectedChip.index() - 1; + if (selectIndex < 0) { + return; + } + currChips.selectChip(selectIndex); + } + + // right arrow key + } else if (e.keyCode === 39) { + if (currChips._selectedChip) { + let selectIndex = currChips._selectedChip.index() + 1; + + if (selectIndex >= currChips.chipsData.length) { + currChips.$input[0].focus(); + } else { + currChips.selectChip(selectIndex); + } + } + } + } + + /** + * Handle Chips Keyup + * @param {Event} e + */ + static _handleChipsKeyup(e) { + Chips._keydown = false; + } + + /** + * Handle Chips Blur + * @param {Event} e + */ + static _handleChipsBlur(e) { + if (!Chips._keydown) { + let $chips = $(e.target).closest('.chips'); + let currChips = $chips[0].M_Chips; + + currChips._selectedChip = null; + } + } + + /** + * Handle Input Focus + */ + _handleInputFocus() { + this.$el.addClass('focus'); + } + + /** + * Handle Input Blur + */ + _handleInputBlur() { + this.$el.removeClass('focus'); + } + + /** + * Handle Input Keydown + * @param {Event} e + */ + _handleInputKeydown(e) { + Chips._keydown = true; + + // enter + if (e.keyCode === 13) { + // Override enter if autocompleting. + if (this.hasAutocomplete && this.autocomplete && this.autocomplete.isOpen) { + return; + } + + e.preventDefault(); + this.addChip({ + tag: this.$input[0].value + }); + this.$input[0].value = ''; + + // delete or left + } else if ( + (e.keyCode === 8 || e.keyCode === 37) && + this.$input[0].value === '' && + this.chipsData.length + ) { + e.preventDefault(); + this.selectChip(this.chipsData.length - 1); + } + } + + /** + * Render Chip + * @param {chip} chip + * @return {Element} + */ + _renderChip(chip) { + if (!chip.tag) { + return; + } + + let renderedChip = document.createElement('div'); + let closeIcon = document.createElement('i'); + renderedChip.classList.add('chip'); + renderedChip.textContent = chip.tag; + renderedChip.setAttribute('tabindex', 0); + $(closeIcon).addClass('material-icons close'); + closeIcon.textContent = 'close'; + + // attach image if needed + if (chip.image) { + let img = document.createElement('img'); + img.setAttribute('src', chip.image); + renderedChip.insertBefore(img, renderedChip.firstChild); + } + + renderedChip.appendChild(closeIcon); + return renderedChip; + } + + /** + * Render Chips + */ + _renderChips() { + this.$chips.remove(); + for (let i = 0; i < this.chipsData.length; i++) { + let chipEl = this._renderChip(this.chipsData[i]); + this.$el.append(chipEl); + this.$chips.add(chipEl); + } + + // move input to end + this.$el.append(this.$input[0]); + } + + /** + * Setup Autocomplete + */ + _setupAutocomplete() { + this.options.autocompleteOptions.onAutocomplete = (val) => { + this.addChip({ + tag: val + }); + this.$input[0].value = ''; + this.$input[0].focus(); + }; + + this.autocomplete = M.Autocomplete.init(this.$input[0], this.options.autocompleteOptions); + } + + /** + * Setup Input + */ + _setupInput() { + this.$input = this.$el.find('input'); + if (!this.$input.length) { + this.$input = $(''); + this.$el.append(this.$input); + } + + this.$input.addClass('input'); + } + + /** + * Setup Label + */ + _setupLabel() { + this.$label = this.$el.find('label'); + if (this.$label.length) { + this.$label.setAttribute('for', this.$input.attr('id')); + } + } + + /** + * Set placeholder + */ + _setPlaceholder() { + if (this.chipsData !== undefined && !this.chipsData.length && this.options.placeholder) { + $(this.$input).prop('placeholder', this.options.placeholder); + } else if ( + (this.chipsData === undefined || !!this.chipsData.length) && + this.options.secondaryPlaceholder + ) { + $(this.$input).prop('placeholder', this.options.secondaryPlaceholder); + } + } + + /** + * Check if chip is valid + * @param {chip} chip + */ + _isValid(chip) { + if (chip.hasOwnProperty('tag') && chip.tag !== '') { + let exists = false; + for (let i = 0; i < this.chipsData.length; i++) { + if (this.chipsData[i].tag === chip.tag) { + exists = true; + break; + } + } + return !exists; + } + + return false; + } + + /** + * Add chip + * @param {chip} chip + */ + addChip(chip) { + if (!this._isValid(chip) || this.chipsData.length >= this.options.limit) { + return; + } + + let renderedChip = this._renderChip(chip); + this.$chips.add(renderedChip); + this.chipsData.push(chip); + $(this.$input).before(renderedChip); + this._setPlaceholder(); + + // fire chipAdd callback + if (typeof this.options.onChipAdd === 'function') { + this.options.onChipAdd.call(this, this.$el, renderedChip); + } + } + + /** + * Delete chip + * @param {Number} chip + */ + deleteChip(chipIndex) { + let $chip = this.$chips.eq(chipIndex); + this.$chips.eq(chipIndex).remove(); + this.$chips = this.$chips.filter(function(el) { + return $(el).index() >= 0; + }); + this.chipsData.splice(chipIndex, 1); + this._setPlaceholder(); + + // fire chipDelete callback + if (typeof this.options.onChipDelete === 'function') { + this.options.onChipDelete.call(this, this.$el, $chip[0]); + } + } + + /** + * Select chip + * @param {Number} chip + */ + selectChip(chipIndex) { + let $chip = this.$chips.eq(chipIndex); + this._selectedChip = $chip; + $chip[0].focus(); + + // fire chipSelect callback + if (typeof this.options.onChipSelect === 'function') { + this.options.onChipSelect.call(this, this.$el, $chip[0]); + } + } + } + + /** + * @static + * @memberof Chips + */ + Chips._keydown = false; + + M.Chips = Chips; + + if (M.jQueryLoaded) { + M.initializeJqueryWrapper(Chips, 'chips', 'M_Chips'); + } + + $(document).ready(function() { + // Handle removal of static chips. + $(document.body).on('click', '.chip .close', function() { + let $chips = $(this).closest('.chips'); + if ($chips.length && $chips[0].M_Chips) { + return; + } + $(this) + .closest('.chip') + .remove(); + }); + }); +})(cash); diff --git a/static/js/collapsible.js b/static/js/collapsible.js new file mode 100644 index 00000000..6c491261 --- /dev/null +++ b/static/js/collapsible.js @@ -0,0 +1,275 @@ +(function($, anim) { + 'use strict'; + + let _defaults = { + accordion: true, + onOpenStart: undefined, + onOpenEnd: undefined, + onCloseStart: undefined, + onCloseEnd: undefined, + inDuration: 300, + outDuration: 300 + }; + + /** + * @class + * + */ + class Collapsible extends Component { + /** + * Construct Collapsible instance + * @constructor + * @param {Element} el + * @param {Object} options + */ + constructor(el, options) { + super(Collapsible, el, options); + + this.el.M_Collapsible = this; + + /** + * Options for the collapsible + * @member Collapsible#options + * @prop {Boolean} [accordion=false] - Type of the collapsible + * @prop {Function} onOpenStart - Callback function called before collapsible is opened + * @prop {Function} onOpenEnd - Callback function called after collapsible is opened + * @prop {Function} onCloseStart - Callback function called before collapsible is closed + * @prop {Function} onCloseEnd - Callback function called after collapsible is closed + * @prop {Number} inDuration - Transition in duration in milliseconds. + * @prop {Number} outDuration - Transition duration in milliseconds. + */ + this.options = $.extend({}, Collapsible.defaults, options); + + // Setup tab indices + this.$headers = this.$el.children('li').children('.collapsible-header'); + this.$headers.attr('tabindex', 0); + + this._setupEventHandlers(); + + // Open first active + let $activeBodies = this.$el.children('li.active').children('.collapsible-body'); + if (this.options.accordion) { + // Handle Accordion + $activeBodies.first().css('display', 'block'); + } else { + // Handle Expandables + $activeBodies.css('display', 'block'); + } + } + + static get defaults() { + return _defaults; + } + + static init(els, options) { + return super.init(this, els, options); + } + + /** + * Get Instance + */ + static getInstance(el) { + let domElem = !!el.jquery ? el[0] : el; + return domElem.M_Collapsible; + } + + /** + * Teardown component + */ + destroy() { + this._removeEventHandlers(); + this.el.M_Collapsible = undefined; + } + + /** + * Setup Event Handlers + */ + _setupEventHandlers() { + this._handleCollapsibleClickBound = this._handleCollapsibleClick.bind(this); + this._handleCollapsibleKeydownBound = this._handleCollapsibleKeydown.bind(this); + this.el.addEventListener('click', this._handleCollapsibleClickBound); + this.$headers.each((header) => { + header.addEventListener('keydown', this._handleCollapsibleKeydownBound); + }); + } + + /** + * Remove Event Handlers + */ + _removeEventHandlers() { + this.el.removeEventListener('click', this._handleCollapsibleClickBound); + this.$headers.each((header) => { + header.removeEventListener('keydown', this._handleCollapsibleKeydownBound); + }); + } + + /** + * Handle Collapsible Click + * @param {Event} e + */ + _handleCollapsibleClick(e) { + let $header = $(e.target).closest('.collapsible-header'); + if (e.target && $header.length) { + let $collapsible = $header.closest('.collapsible'); + if ($collapsible[0] === this.el) { + let $collapsibleLi = $header.closest('li'); + let $collapsibleLis = $collapsible.children('li'); + let isActive = $collapsibleLi[0].classList.contains('active'); + let index = $collapsibleLis.index($collapsibleLi); + + if (isActive) { + this.close(index); + } else { + this.open(index); + } + } + } + } + + /** + * Handle Collapsible Keydown + * @param {Event} e + */ + _handleCollapsibleKeydown(e) { + if (e.keyCode === 13) { + this._handleCollapsibleClickBound(e); + } + } + + /** + * Animate in collapsible slide + * @param {Number} index - 0th index of slide + */ + _animateIn(index) { + let $collapsibleLi = this.$el.children('li').eq(index); + if ($collapsibleLi.length) { + let $body = $collapsibleLi.children('.collapsible-body'); + + anim.remove($body[0]); + $body.css({ + display: 'block', + overflow: 'hidden', + height: 0, + paddingTop: '', + paddingBottom: '' + }); + + let pTop = $body.css('padding-top'); + let pBottom = $body.css('padding-bottom'); + let finalHeight = $body[0].scrollHeight; + $body.css({ + paddingTop: 0, + paddingBottom: 0 + }); + + anim({ + targets: $body[0], + height: finalHeight, + paddingTop: pTop, + paddingBottom: pBottom, + duration: this.options.inDuration, + easing: 'easeInOutCubic', + complete: (anim) => { + $body.css({ + overflow: '', + paddingTop: '', + paddingBottom: '', + height: '' + }); + + // onOpenEnd callback + if (typeof this.options.onOpenEnd === 'function') { + this.options.onOpenEnd.call(this, $collapsibleLi[0]); + } + } + }); + } + } + + /** + * Animate out collapsible slide + * @param {Number} index - 0th index of slide to open + */ + _animateOut(index) { + let $collapsibleLi = this.$el.children('li').eq(index); + if ($collapsibleLi.length) { + let $body = $collapsibleLi.children('.collapsible-body'); + anim.remove($body[0]); + $body.css('overflow', 'hidden'); + anim({ + targets: $body[0], + height: 0, + paddingTop: 0, + paddingBottom: 0, + duration: this.options.outDuration, + easing: 'easeInOutCubic', + complete: () => { + $body.css({ + height: '', + overflow: '', + padding: '', + display: '' + }); + + // onCloseEnd callback + if (typeof this.options.onCloseEnd === 'function') { + this.options.onCloseEnd.call(this, $collapsibleLi[0]); + } + } + }); + } + } + + /** + * Open Collapsible + * @param {Number} index - 0th index of slide + */ + open(index) { + let $collapsibleLi = this.$el.children('li').eq(index); + if ($collapsibleLi.length && !$collapsibleLi[0].classList.contains('active')) { + // onOpenStart callback + if (typeof this.options.onOpenStart === 'function') { + this.options.onOpenStart.call(this, $collapsibleLi[0]); + } + + // Handle accordion behavior + if (this.options.accordion) { + let $collapsibleLis = this.$el.children('li'); + let $activeLis = this.$el.children('li.active'); + $activeLis.each((el) => { + let index = $collapsibleLis.index($(el)); + this.close(index); + }); + } + + // Animate in + $collapsibleLi[0].classList.add('active'); + this._animateIn(index); + } + } + + /** + * Close Collapsible + * @param {Number} index - 0th index of slide + */ + close(index) { + let $collapsibleLi = this.$el.children('li').eq(index); + if ($collapsibleLi.length && $collapsibleLi[0].classList.contains('active')) { + // onCloseStart callback + if (typeof this.options.onCloseStart === 'function') { + this.options.onCloseStart.call(this, $collapsibleLi[0]); + } + + // Animate out + $collapsibleLi[0].classList.remove('active'); + this._animateOut(index); + } + } + } + + M.Collapsible = Collapsible; + + if (M.jQueryLoaded) { + M.initializeJqueryWrapper(Collapsible, 'collapsible', 'M_Collapsible'); + } +})(cash, M.anime); diff --git a/static/js/component.js b/static/js/component.js new file mode 100644 index 00000000..19eea9fd --- /dev/null +++ b/static/js/component.js @@ -0,0 +1,44 @@ +class Component { + /** + * Generic constructor for all components + * @constructor + * @param {Element} el + * @param {Object} options + */ + constructor(classDef, el, options) { + // Display error if el is valid HTML Element + if (!(el instanceof Element)) { + console.error(Error(el + ' is not an HTML Element')); + } + + // If exists, destroy and reinitialize in child + let ins = classDef.getInstance(el); + if (!!ins) { + ins.destroy(); + } + + this.el = el; + this.$el = cash(el); + } + + /** + * Initializes components + * @param {class} classDef + * @param {Element | NodeList | jQuery} els + * @param {Object} options + */ + static init(classDef, els, options) { + let instances = null; + if (els instanceof Element) { + instances = new classDef(els, options); + } else if (!!els && (els.jquery || els.cash || els instanceof NodeList)) { + let instancesArr = []; + for (let i = 0; i < els.length; i++) { + instancesArr.push(new classDef(els[i], options)); + } + instances = instancesArr; + } + + return instances; + } +} diff --git a/static/js/datepicker.js b/static/js/datepicker.js new file mode 100644 index 00000000..7199dd27 --- /dev/null +++ b/static/js/datepicker.js @@ -0,0 +1,975 @@ +(function($) { + 'use strict'; + + let _defaults = { + // Close when date is selected + autoClose: false, + + // the default output format for the input field value + format: 'mmm dd, yyyy', + + // Used to create date object from current input string + parse: null, + + // The initial date to view when first opened + defaultDate: null, + + // Make the `defaultDate` the initial selected value + setDefaultDate: false, + + disableWeekends: false, + + disableDayFn: null, + + // First day of week (0: Sunday, 1: Monday etc) + firstDay: 0, + + // The earliest date that can be selected + minDate: null, + // Thelatest date that can be selected + maxDate: null, + + // Number of years either side, or array of upper/lower range + yearRange: 10, + + // used internally (don't config outside) + minYear: 0, + maxYear: 9999, + minMonth: undefined, + maxMonth: undefined, + + startRange: null, + endRange: null, + + isRTL: false, + + // Render the month after year in the calendar title + showMonthAfterYear: false, + + // Render days of the calendar grid that fall in the next or previous month + showDaysInNextAndPreviousMonths: false, + + // Specify a DOM element to render the calendar in + container: null, + + // Show clear button + showClearBtn: false, + + // internationalization + i18n: { + cancel: 'Cancel', + clear: 'Clear', + done: 'Ok', + previousMonth: '‹', + nextMonth: '›', + months: [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' + ], + monthsShort: [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec' + ], + weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + weekdaysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + weekdaysAbbrev: ['S', 'M', 'T', 'W', 'T', 'F', 'S'] + }, + + // events array + events: [], + + // callback function + onSelect: null, + onOpen: null, + onClose: null, + onDraw: null + }; + + /** + * @class + * + */ + class Datepicker extends Component { + /** + * Construct Datepicker instance and set up overlay + * @constructor + * @param {Element} el + * @param {Object} options + */ + constructor(el, options) { + super(Datepicker, el, options); + + this.el.M_Datepicker = this; + + this.options = $.extend({}, Datepicker.defaults, options); + + // make sure i18n defaults are not lost when only few i18n option properties are passed + if (!!options && options.hasOwnProperty('i18n') && typeof options.i18n === 'object') { + this.options.i18n = $.extend({}, Datepicker.defaults.i18n, options.i18n); + } + + // Remove time component from minDate and maxDate options + if (this.options.minDate) this.options.minDate.setHours(0, 0, 0, 0); + if (this.options.maxDate) this.options.maxDate.setHours(0, 0, 0, 0); + + this.id = M.guid(); + + this._setupVariables(); + this._insertHTMLIntoDOM(); + this._setupModal(); + + this._setupEventHandlers(); + + if (!this.options.defaultDate) { + this.options.defaultDate = new Date(Date.parse(this.el.value)); + } + + let defDate = this.options.defaultDate; + if (Datepicker._isDate(defDate)) { + if (this.options.setDefaultDate) { + this.setDate(defDate, true); + this.setInputValue(); + } else { + this.gotoDate(defDate); + } + } else { + this.gotoDate(new Date()); + } + + /** + * Describes open/close state of datepicker + * @type {Boolean} + */ + this.isOpen = false; + } + + static get defaults() { + return _defaults; + } + + static init(els, options) { + return super.init(this, els, options); + } + + static _isDate(obj) { + return /Date/.test(Object.prototype.toString.call(obj)) && !isNaN(obj.getTime()); + } + + static _isWeekend(date) { + let day = date.getDay(); + return day === 0 || day === 6; + } + + static _setToStartOfDay(date) { + if (Datepicker._isDate(date)) date.setHours(0, 0, 0, 0); + } + + static _getDaysInMonth(year, month) { + return [31, Datepicker._isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][ + month + ]; + } + + static _isLeapYear(year) { + // solution by Matti Virkkunen: http://stackoverflow.com/a/4881951 + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; + } + + static _compareDates(a, b) { + // weak date comparison (use setToStartOfDay(date) to ensure correct result) + return a.getTime() === b.getTime(); + } + + static _setToStartOfDay(date) { + if (Datepicker._isDate(date)) date.setHours(0, 0, 0, 0); + } + + /** + * Get Instance + */ + static getInstance(el) { + let domElem = !!el.jquery ? el[0] : el; + return domElem.M_Datepicker; + } + + /** + * Teardown component + */ + destroy() { + this._removeEventHandlers(); + this.modal.destroy(); + $(this.modalEl).remove(); + this.destroySelects(); + this.el.M_Datepicker = undefined; + } + + destroySelects() { + let oldYearSelect = this.calendarEl.querySelector('.orig-select-year'); + if (oldYearSelect) { + M.FormSelect.getInstance(oldYearSelect).destroy(); + } + let oldMonthSelect = this.calendarEl.querySelector('.orig-select-month'); + if (oldMonthSelect) { + M.FormSelect.getInstance(oldMonthSelect).destroy(); + } + } + + _insertHTMLIntoDOM() { + if (this.options.showClearBtn) { + $(this.clearBtn).css({ visibility: '' }); + this.clearBtn.innerHTML = this.options.i18n.clear; + } + + this.doneBtn.innerHTML = this.options.i18n.done; + this.cancelBtn.innerHTML = this.options.i18n.cancel; + + if (this.options.container) { + this.$modalEl.appendTo(this.options.container); + } else { + this.$modalEl.insertBefore(this.el); + } + } + + _setupModal() { + this.modalEl.id = 'modal-' + this.id; + this.modal = M.Modal.init(this.modalEl, { + onCloseEnd: () => { + this.isOpen = false; + } + }); + } + + toString(format) { + format = format || this.options.format; + if (!Datepicker._isDate(this.date)) { + return ''; + } + + let formatArray = format.split(/(d{1,4}|m{1,4}|y{4}|yy|!.)/g); + let formattedDate = formatArray + .map((label) => { + if (this.formats[label]) { + return this.formats[label](); + } + + return label; + }) + .join(''); + return formattedDate; + } + + setDate(date, preventOnSelect) { + if (!date) { + this.date = null; + this._renderDateDisplay(); + return this.draw(); + } + if (typeof date === 'string') { + date = new Date(Date.parse(date)); + } + if (!Datepicker._isDate(date)) { + return; + } + + let min = this.options.minDate, + max = this.options.maxDate; + + if (Datepicker._isDate(min) && date < min) { + date = min; + } else if (Datepicker._isDate(max) && date > max) { + date = max; + } + + this.date = new Date(date.getTime()); + + this._renderDateDisplay(); + + Datepicker._setToStartOfDay(this.date); + this.gotoDate(this.date); + + if (!preventOnSelect && typeof this.options.onSelect === 'function') { + this.options.onSelect.call(this, this.date); + } + } + + setInputValue() { + this.el.value = this.toString(); + this.$el.trigger('change', { firedBy: this }); + } + + _renderDateDisplay() { + let displayDate = Datepicker._isDate(this.date) ? this.date : new Date(); + let i18n = this.options.i18n; + let day = i18n.weekdaysShort[displayDate.getDay()]; + let month = i18n.monthsShort[displayDate.getMonth()]; + let date = displayDate.getDate(); + this.yearTextEl.innerHTML = displayDate.getFullYear(); + this.dateTextEl.innerHTML = `${day}, ${month} ${date}`; + } + + /** + * change view to a specific date + */ + gotoDate(date) { + let newCalendar = true; + + if (!Datepicker._isDate(date)) { + return; + } + + if (this.calendars) { + let firstVisibleDate = new Date(this.calendars[0].year, this.calendars[0].month, 1), + lastVisibleDate = new Date( + this.calendars[this.calendars.length - 1].year, + this.calendars[this.calendars.length - 1].month, + 1 + ), + visibleDate = date.getTime(); + // get the end of the month + lastVisibleDate.setMonth(lastVisibleDate.getMonth() + 1); + lastVisibleDate.setDate(lastVisibleDate.getDate() - 1); + newCalendar = + visibleDate < firstVisibleDate.getTime() || lastVisibleDate.getTime() < visibleDate; + } + + if (newCalendar) { + this.calendars = [ + { + month: date.getMonth(), + year: date.getFullYear() + } + ]; + } + + this.adjustCalendars(); + } + + adjustCalendars() { + this.calendars[0] = this.adjustCalendar(this.calendars[0]); + this.draw(); + } + + adjustCalendar(calendar) { + if (calendar.month < 0) { + calendar.year -= Math.ceil(Math.abs(calendar.month) / 12); + calendar.month += 12; + } + if (calendar.month > 11) { + calendar.year += Math.floor(Math.abs(calendar.month) / 12); + calendar.month -= 12; + } + return calendar; + } + + nextMonth() { + this.calendars[0].month++; + this.adjustCalendars(); + } + + prevMonth() { + this.calendars[0].month--; + this.adjustCalendars(); + } + + render(year, month, randId) { + let opts = this.options, + now = new Date(), + days = Datepicker._getDaysInMonth(year, month), + before = new Date(year, month, 1).getDay(), + data = [], + row = []; + Datepicker._setToStartOfDay(now); + if (opts.firstDay > 0) { + before -= opts.firstDay; + if (before < 0) { + before += 7; + } + } + let previousMonth = month === 0 ? 11 : month - 1, + nextMonth = month === 11 ? 0 : month + 1, + yearOfPreviousMonth = month === 0 ? year - 1 : year, + yearOfNextMonth = month === 11 ? year + 1 : year, + daysInPreviousMonth = Datepicker._getDaysInMonth(yearOfPreviousMonth, previousMonth); + let cells = days + before, + after = cells; + while (after > 7) { + after -= 7; + } + cells += 7 - after; + let isWeekSelected = false; + for (let i = 0, r = 0; i < cells; i++) { + let day = new Date(year, month, 1 + (i - before)), + isSelected = Datepicker._isDate(this.date) + ? Datepicker._compareDates(day, this.date) + : false, + isToday = Datepicker._compareDates(day, now), + hasEvent = opts.events.indexOf(day.toDateString()) !== -1 ? true : false, + isEmpty = i < before || i >= days + before, + dayNumber = 1 + (i - before), + monthNumber = month, + yearNumber = year, + isStartRange = opts.startRange && Datepicker._compareDates(opts.startRange, day), + isEndRange = opts.endRange && Datepicker._compareDates(opts.endRange, day), + isInRange = + opts.startRange && opts.endRange && opts.startRange < day && day < opts.endRange, + isDisabled = + (opts.minDate && day < opts.minDate) || + (opts.maxDate && day > opts.maxDate) || + (opts.disableWeekends && Datepicker._isWeekend(day)) || + (opts.disableDayFn && opts.disableDayFn(day)); + + if (isEmpty) { + if (i < before) { + dayNumber = daysInPreviousMonth + dayNumber; + monthNumber = previousMonth; + yearNumber = yearOfPreviousMonth; + } else { + dayNumber = dayNumber - days; + monthNumber = nextMonth; + yearNumber = yearOfNextMonth; + } + } + + let dayConfig = { + day: dayNumber, + month: monthNumber, + year: yearNumber, + hasEvent: hasEvent, + isSelected: isSelected, + isToday: isToday, + isDisabled: isDisabled, + isEmpty: isEmpty, + isStartRange: isStartRange, + isEndRange: isEndRange, + isInRange: isInRange, + showDaysInNextAndPreviousMonths: opts.showDaysInNextAndPreviousMonths + }; + + row.push(this.renderDay(dayConfig)); + + if (++r === 7) { + data.push(this.renderRow(row, opts.isRTL, isWeekSelected)); + row = []; + r = 0; + isWeekSelected = false; + } + } + return this.renderTable(opts, data, randId); + } + + renderDay(opts) { + let arr = []; + let ariaSelected = 'false'; + if (opts.isEmpty) { + if (opts.showDaysInNextAndPreviousMonths) { + arr.push('is-outside-current-month'); + arr.push('is-selection-disabled'); + } else { + return ''; + } + } + if (opts.isDisabled) { + arr.push('is-disabled'); + } + + if (opts.isToday) { + arr.push('is-today'); + } + if (opts.isSelected) { + arr.push('is-selected'); + ariaSelected = 'true'; + } + if (opts.hasEvent) { + arr.push('has-event'); + } + if (opts.isInRange) { + arr.push('is-inrange'); + } + if (opts.isStartRange) { + arr.push('is-startrange'); + } + if (opts.isEndRange) { + arr.push('is-endrange'); + } + return ( + `` + + `` + + '' + ); + } + + renderRow(days, isRTL, isRowSelected) { + return ( + '' + + (isRTL ? days.reverse() : days).join('') + + '' + ); + } + + renderTable(opts, data, randId) { + return ( + '
      ' + + this.renderHead(opts) + + this.renderBody(data) + + '
      ' + ); + } + + renderHead(opts) { + let i, + arr = []; + for (i = 0; i < 7; i++) { + arr.push( + `${this.renderDayName( + opts, + i, + true + )}` + ); + } + return '' + (opts.isRTL ? arr.reverse() : arr).join('') + ''; + } + + renderBody(rows) { + return '' + rows.join('') + ''; + } + + renderTitle(instance, c, year, month, refYear, randId) { + let i, + j, + arr, + opts = this.options, + isMinYear = year === opts.minYear, + isMaxYear = year === opts.maxYear, + html = + '
      ', + monthHtml, + yearHtml, + prev = true, + next = true; + + for (arr = [], i = 0; i < 12; i++) { + arr.push( + '' + ); + } + + monthHtml = + ''; + + if ($.isArray(opts.yearRange)) { + i = opts.yearRange[0]; + j = opts.yearRange[1] + 1; + } else { + i = year - opts.yearRange; + j = 1 + year + opts.yearRange; + } + + for (arr = []; i < j && i <= opts.maxYear; i++) { + if (i >= opts.minYear) { + arr.push(``); + } + } + + yearHtml = ``; + + let leftArrow = + ''; + html += ``; + + html += '
      '; + if (opts.showMonthAfterYear) { + html += yearHtml + monthHtml; + } else { + html += monthHtml + yearHtml; + } + html += '
      '; + + if (isMinYear && (month === 0 || opts.minMonth >= month)) { + prev = false; + } + + if (isMaxYear && (month === 11 || opts.maxMonth <= month)) { + next = false; + } + + let rightArrow = + ''; + html += ``; + + return (html += '
      '); + } + + /** + * refresh the HTML + */ + draw(force) { + if (!this.isOpen && !force) { + return; + } + let opts = this.options, + minYear = opts.minYear, + maxYear = opts.maxYear, + minMonth = opts.minMonth, + maxMonth = opts.maxMonth, + html = '', + randId; + + if (this._y <= minYear) { + this._y = minYear; + if (!isNaN(minMonth) && this._m < minMonth) { + this._m = minMonth; + } + } + if (this._y >= maxYear) { + this._y = maxYear; + if (!isNaN(maxMonth) && this._m > maxMonth) { + this._m = maxMonth; + } + } + + randId = + 'datepicker-title-' + + Math.random() + .toString(36) + .replace(/[^a-z]+/g, '') + .substr(0, 2); + + for (let c = 0; c < 1; c++) { + this._renderDateDisplay(); + html += + this.renderTitle( + this, + c, + this.calendars[c].year, + this.calendars[c].month, + this.calendars[0].year, + randId + ) + this.render(this.calendars[c].year, this.calendars[c].month, randId); + } + + this.destroySelects(); + + this.calendarEl.innerHTML = html; + + // Init Materialize Select + let yearSelect = this.calendarEl.querySelector('.orig-select-year'); + let monthSelect = this.calendarEl.querySelector('.orig-select-month'); + M.FormSelect.init(yearSelect, { + classes: 'select-year', + dropdownOptions: { container: document.body, constrainWidth: false } + }); + M.FormSelect.init(monthSelect, { + classes: 'select-month', + dropdownOptions: { container: document.body, constrainWidth: false } + }); + + // Add change handlers for select + yearSelect.addEventListener('change', this._handleYearChange.bind(this)); + monthSelect.addEventListener('change', this._handleMonthChange.bind(this)); + + if (typeof this.options.onDraw === 'function') { + this.options.onDraw(this); + } + } + + /** + * Setup Event Handlers + */ + _setupEventHandlers() { + this._handleInputKeydownBound = this._handleInputKeydown.bind(this); + this._handleInputClickBound = this._handleInputClick.bind(this); + this._handleInputChangeBound = this._handleInputChange.bind(this); + this._handleCalendarClickBound = this._handleCalendarClick.bind(this); + this._finishSelectionBound = this._finishSelection.bind(this); + this._handleMonthChange = this._handleMonthChange.bind(this); + this._closeBound = this.close.bind(this); + + this.el.addEventListener('click', this._handleInputClickBound); + this.el.addEventListener('keydown', this._handleInputKeydownBound); + this.el.addEventListener('change', this._handleInputChangeBound); + this.calendarEl.addEventListener('click', this._handleCalendarClickBound); + this.doneBtn.addEventListener('click', this._finishSelectionBound); + this.cancelBtn.addEventListener('click', this._closeBound); + + if (this.options.showClearBtn) { + this._handleClearClickBound = this._handleClearClick.bind(this); + this.clearBtn.addEventListener('click', this._handleClearClickBound); + } + } + + _setupVariables() { + this.$modalEl = $(Datepicker._template); + this.modalEl = this.$modalEl[0]; + + this.calendarEl = this.modalEl.querySelector('.datepicker-calendar'); + + this.yearTextEl = this.modalEl.querySelector('.year-text'); + this.dateTextEl = this.modalEl.querySelector('.date-text'); + if (this.options.showClearBtn) { + this.clearBtn = this.modalEl.querySelector('.datepicker-clear'); + } + this.doneBtn = this.modalEl.querySelector('.datepicker-done'); + this.cancelBtn = this.modalEl.querySelector('.datepicker-cancel'); + + this.formats = { + d: () => { + return this.date.getDate(); + }, + dd: () => { + let d = this.date.getDate(); + return (d < 10 ? '0' : '') + d; + }, + ddd: () => { + return this.options.i18n.weekdaysShort[this.date.getDay()]; + }, + dddd: () => { + return this.options.i18n.weekdays[this.date.getDay()]; + }, + m: () => { + return this.date.getMonth() + 1; + }, + mm: () => { + let m = this.date.getMonth() + 1; + return (m < 10 ? '0' : '') + m; + }, + mmm: () => { + return this.options.i18n.monthsShort[this.date.getMonth()]; + }, + mmmm: () => { + return this.options.i18n.months[this.date.getMonth()]; + }, + yy: () => { + return ('' + this.date.getFullYear()).slice(2); + }, + yyyy: () => { + return this.date.getFullYear(); + } + }; + } + + /** + * Remove Event Handlers + */ + _removeEventHandlers() { + this.el.removeEventListener('click', this._handleInputClickBound); + this.el.removeEventListener('keydown', this._handleInputKeydownBound); + this.el.removeEventListener('change', this._handleInputChangeBound); + this.calendarEl.removeEventListener('click', this._handleCalendarClickBound); + } + + _handleInputClick() { + this.open(); + } + + _handleInputKeydown(e) { + if (e.which === M.keys.ENTER) { + e.preventDefault(); + this.open(); + } + } + + _handleCalendarClick(e) { + if (!this.isOpen) { + return; + } + + let $target = $(e.target); + if (!$target.hasClass('is-disabled')) { + if ( + $target.hasClass('datepicker-day-button') && + !$target.hasClass('is-empty') && + !$target.parent().hasClass('is-disabled') + ) { + this.setDate( + new Date( + e.target.getAttribute('data-year'), + e.target.getAttribute('data-month'), + e.target.getAttribute('data-day') + ) + ); + if (this.options.autoClose) { + this._finishSelection(); + } + } else if ($target.closest('.month-prev').length) { + this.prevMonth(); + } else if ($target.closest('.month-next').length) { + this.nextMonth(); + } + } + } + + _handleClearClick() { + this.date = null; + this.setInputValue(); + this.close(); + } + + _handleMonthChange(e) { + this.gotoMonth(e.target.value); + } + + _handleYearChange(e) { + this.gotoYear(e.target.value); + } + + /** + * change view to a specific month (zero-index, e.g. 0: January) + */ + gotoMonth(month) { + if (!isNaN(month)) { + this.calendars[0].month = parseInt(month, 10); + this.adjustCalendars(); + } + } + + /** + * change view to a specific full year (e.g. "2012") + */ + gotoYear(year) { + if (!isNaN(year)) { + this.calendars[0].year = parseInt(year, 10); + this.adjustCalendars(); + } + } + + _handleInputChange(e) { + let date; + + // Prevent change event from being fired when triggered by the plugin + if (e.firedBy === this) { + return; + } + if (this.options.parse) { + date = this.options.parse(this.el.value, this.options.format); + } else { + date = new Date(Date.parse(this.el.value)); + } + + if (Datepicker._isDate(date)) { + this.setDate(date); + } + } + + renderDayName(opts, day, abbr) { + day += opts.firstDay; + while (day >= 7) { + day -= 7; + } + return abbr ? opts.i18n.weekdaysAbbrev[day] : opts.i18n.weekdays[day]; + } + + /** + * Set input value to the selected date and close Datepicker + */ + _finishSelection() { + this.setInputValue(); + this.close(); + } + + /** + * Open Datepicker + */ + open() { + if (this.isOpen) { + return; + } + + this.isOpen = true; + if (typeof this.options.onOpen === 'function') { + this.options.onOpen.call(this); + } + this.draw(); + this.modal.open(); + return this; + } + + /** + * Close Datepicker + */ + close() { + if (!this.isOpen) { + return; + } + + this.isOpen = false; + if (typeof this.options.onClose === 'function') { + this.options.onClose.call(this); + } + this.modal.close(); + return this; + } + } + + Datepicker._template = [ + '' + ].join(''); + + M.Datepicker = Datepicker; + + if (M.jQueryLoaded) { + M.initializeJqueryWrapper(Datepicker, 'datepicker', 'M_Datepicker'); + } +})(cash); diff --git a/static/js/dropdown.js b/static/js/dropdown.js new file mode 100644 index 00000000..201c18e1 --- /dev/null +++ b/static/js/dropdown.js @@ -0,0 +1,615 @@ +(function($, anim) { + 'use strict'; + + let _defaults = { + alignment: 'left', + autoFocus: true, + constrainWidth: true, + container: null, + coverTrigger: true, + closeOnClick: true, + hover: false, + inDuration: 150, + outDuration: 250, + onOpenStart: null, + onOpenEnd: null, + onCloseStart: null, + onCloseEnd: null, + onItemClick: null + }; + + /** + * @class + */ + class Dropdown extends Component { + constructor(el, options) { + super(Dropdown, el, options); + + this.el.M_Dropdown = this; + Dropdown._dropdowns.push(this); + + this.id = M.getIdFromTrigger(el); + this.dropdownEl = document.getElementById(this.id); + this.$dropdownEl = $(this.dropdownEl); + + /** + * Options for the dropdown + * @member Dropdown#options + * @prop {String} [alignment='left'] - Edge which the dropdown is aligned to + * @prop {Boolean} [autoFocus=true] - Automatically focus dropdown el for keyboard + * @prop {Boolean} [constrainWidth=true] - Constrain width to width of the button + * @prop {Element} container - Container element to attach dropdown to (optional) + * @prop {Boolean} [coverTrigger=true] - Place dropdown over trigger + * @prop {Boolean} [closeOnClick=true] - Close on click of dropdown item + * @prop {Boolean} [hover=false] - Open dropdown on hover + * @prop {Number} [inDuration=150] - Duration of open animation in ms + * @prop {Number} [outDuration=250] - Duration of close animation in ms + * @prop {Function} onOpenStart - Function called when dropdown starts opening + * @prop {Function} onOpenEnd - Function called when dropdown finishes opening + * @prop {Function} onCloseStart - Function called when dropdown starts closing + * @prop {Function} onCloseEnd - Function called when dropdown finishes closing + */ + this.options = $.extend({}, Dropdown.defaults, options); + + /** + * Describes open/close state of dropdown + * @type {Boolean} + */ + this.isOpen = false; + + /** + * Describes if dropdown content is scrollable + * @type {Boolean} + */ + this.isScrollable = false; + + /** + * Describes if touch moving on dropdown content + * @type {Boolean} + */ + this.isTouchMoving = false; + + this.focusedIndex = -1; + this.filterQuery = []; + + // Move dropdown-content after dropdown-trigger + if (!!this.options.container) { + $(this.options.container).append(this.dropdownEl); + } else { + this.$el.after(this.dropdownEl); + } + + this._makeDropdownFocusable(); + this._resetFilterQueryBound = this._resetFilterQuery.bind(this); + this._handleDocumentClickBound = this._handleDocumentClick.bind(this); + this._handleDocumentTouchmoveBound = this._handleDocumentTouchmove.bind(this); + this._handleDropdownClickBound = this._handleDropdownClick.bind(this); + this._handleDropdownKeydownBound = this._handleDropdownKeydown.bind(this); + this._handleTriggerKeydownBound = this._handleTriggerKeydown.bind(this); + this._setupEventHandlers(); + } + + static get defaults() { + return _defaults; + } + + static init(els, options) { + return super.init(this, els, options); + } + + /** + * Get Instance + */ + static getInstance(el) { + let domElem = !!el.jquery ? el[0] : el; + return domElem.M_Dropdown; + } + + /** + * Teardown component + */ + destroy() { + this._resetDropdownStyles(); + this._removeEventHandlers(); + Dropdown._dropdowns.splice(Dropdown._dropdowns.indexOf(this), 1); + this.el.M_Dropdown = undefined; + } + + /** + * Setup Event Handlers + */ + _setupEventHandlers() { + // Trigger keydown handler + this.el.addEventListener('keydown', this._handleTriggerKeydownBound); + + // Item click handler + this.dropdownEl.addEventListener('click', this._handleDropdownClickBound); + + // Hover event handlers + if (this.options.hover) { + this._handleMouseEnterBound = this._handleMouseEnter.bind(this); + this.el.addEventListener('mouseenter', this._handleMouseEnterBound); + this._handleMouseLeaveBound = this._handleMouseLeave.bind(this); + this.el.addEventListener('mouseleave', this._handleMouseLeaveBound); + this.dropdownEl.addEventListener('mouseleave', this._handleMouseLeaveBound); + + // Click event handlers + } else { + this._handleClickBound = this._handleClick.bind(this); + this.el.addEventListener('click', this._handleClickBound); + } + } + + /** + * Remove Event Handlers + */ + _removeEventHandlers() { + this.el.removeEventListener('keydown', this._handleTriggerKeydownBound); + this.dropdownEl.removeEventListener('click', this._handleDropdownClickBound); + + if (this.options.hover) { + this.el.removeEventListener('mouseenter', this._handleMouseEnterBound); + this.el.removeEventListener('mouseleave', this._handleMouseLeaveBound); + this.dropdownEl.removeEventListener('mouseleave', this._handleMouseLeaveBound); + } else { + this.el.removeEventListener('click', this._handleClickBound); + } + } + + _setupTemporaryEventHandlers() { + // Use capture phase event handler to prevent click + document.body.addEventListener('click', this._handleDocumentClickBound, true); + document.body.addEventListener('touchend', this._handleDocumentClickBound); + document.body.addEventListener('touchmove', this._handleDocumentTouchmoveBound); + this.dropdownEl.addEventListener('keydown', this._handleDropdownKeydownBound); + } + + _removeTemporaryEventHandlers() { + // Use capture phase event handler to prevent click + document.body.removeEventListener('click', this._handleDocumentClickBound, true); + document.body.removeEventListener('touchend', this._handleDocumentClickBound); + document.body.removeEventListener('touchmove', this._handleDocumentTouchmoveBound); + this.dropdownEl.removeEventListener('keydown', this._handleDropdownKeydownBound); + } + + _handleClick(e) { + e.preventDefault(); + this.open(); + } + + _handleMouseEnter() { + this.open(); + } + + _handleMouseLeave(e) { + let toEl = e.toElement || e.relatedTarget; + let leaveToDropdownContent = !!$(toEl).closest('.dropdown-content').length; + let leaveToActiveDropdownTrigger = false; + + let $closestTrigger = $(toEl).closest('.dropdown-trigger'); + if ( + $closestTrigger.length && + !!$closestTrigger[0].M_Dropdown && + $closestTrigger[0].M_Dropdown.isOpen + ) { + leaveToActiveDropdownTrigger = true; + } + + // Close hover dropdown if mouse did not leave to either active dropdown-trigger or dropdown-content + if (!leaveToActiveDropdownTrigger && !leaveToDropdownContent) { + this.close(); + } + } + + _handleDocumentClick(e) { + let $target = $(e.target); + if ( + this.options.closeOnClick && + $target.closest('.dropdown-content').length && + !this.isTouchMoving + ) { + // isTouchMoving to check if scrolling on mobile. + setTimeout(() => { + this.close(); + }, 0); + } else if ( + $target.closest('.dropdown-trigger').length || + !$target.closest('.dropdown-content').length + ) { + setTimeout(() => { + this.close(); + }, 0); + } + this.isTouchMoving = false; + } + + _handleTriggerKeydown(e) { + // ARROW DOWN OR ENTER WHEN SELECT IS CLOSED - open Dropdown + if ((e.which === M.keys.ARROW_DOWN || e.which === M.keys.ENTER) && !this.isOpen) { + e.preventDefault(); + this.open(); + } + } + + /** + * Handle Document Touchmove + * @param {Event} e + */ + _handleDocumentTouchmove(e) { + let $target = $(e.target); + if ($target.closest('.dropdown-content').length) { + this.isTouchMoving = true; + } + } + + /** + * Handle Dropdown Click + * @param {Event} e + */ + _handleDropdownClick(e) { + // onItemClick callback + if (typeof this.options.onItemClick === 'function') { + let itemEl = $(e.target).closest('li')[0]; + this.options.onItemClick.call(this, itemEl); + } + } + + /** + * Handle Dropdown Keydown + * @param {Event} e + */ + _handleDropdownKeydown(e) { + if (e.which === M.keys.TAB) { + e.preventDefault(); + this.close(); + + // Navigate down dropdown list + } else if ((e.which === M.keys.ARROW_DOWN || e.which === M.keys.ARROW_UP) && this.isOpen) { + e.preventDefault(); + let direction = e.which === M.keys.ARROW_DOWN ? 1 : -1; + let newFocusedIndex = this.focusedIndex; + let foundNewIndex = false; + do { + newFocusedIndex = newFocusedIndex + direction; + + if ( + !!this.dropdownEl.children[newFocusedIndex] && + this.dropdownEl.children[newFocusedIndex].tabIndex !== -1 + ) { + foundNewIndex = true; + break; + } + } while (newFocusedIndex < this.dropdownEl.children.length && newFocusedIndex >= 0); + + if (foundNewIndex) { + this.focusedIndex = newFocusedIndex; + this._focusFocusedItem(); + } + + // ENTER selects choice on focused item + } else if (e.which === M.keys.ENTER && this.isOpen) { + // Search for
      and ` + ) + .appendTo(this.footer) + .on('click', this.clear.bind(this)); + if (this.options.showClearBtn) { + $clearBtn.css({ visibility: '' }); + } + + let confirmationBtnsContainer = $('
      '); + $( + '' + ) + .appendTo(confirmationBtnsContainer) + .on('click', this.close.bind(this)); + $( + '' + ) + .appendTo(confirmationBtnsContainer) + .on('click', this.done.bind(this)); + confirmationBtnsContainer.appendTo(this.footer); + } + + _clockSetup() { + if (this.options.twelveHour) { + this.$amBtn = $('
      AM
      '); + this.$pmBtn = $('
      PM
      '); + this.$amBtn.on('click', this._handleAmPmClick.bind(this)).appendTo(this.spanAmPm); + this.$pmBtn.on('click', this._handleAmPmClick.bind(this)).appendTo(this.spanAmPm); + } + + this._buildHoursView(); + this._buildMinutesView(); + this._buildSVGClock(); + } + + _buildSVGClock() { + // Draw clock hands and others + let dialRadius = this.options.dialRadius; + let tickRadius = this.options.tickRadius; + let diameter = dialRadius * 2; + + let svg = Timepicker._createSVGEl('svg'); + svg.setAttribute('class', 'timepicker-svg'); + svg.setAttribute('width', diameter); + svg.setAttribute('height', diameter); + let g = Timepicker._createSVGEl('g'); + g.setAttribute('transform', 'translate(' + dialRadius + ',' + dialRadius + ')'); + let bearing = Timepicker._createSVGEl('circle'); + bearing.setAttribute('class', 'timepicker-canvas-bearing'); + bearing.setAttribute('cx', 0); + bearing.setAttribute('cy', 0); + bearing.setAttribute('r', 4); + let hand = Timepicker._createSVGEl('line'); + hand.setAttribute('x1', 0); + hand.setAttribute('y1', 0); + let bg = Timepicker._createSVGEl('circle'); + bg.setAttribute('class', 'timepicker-canvas-bg'); + bg.setAttribute('r', tickRadius); + g.appendChild(hand); + g.appendChild(bg); + g.appendChild(bearing); + svg.appendChild(g); + this._canvas.appendChild(svg); + + this.hand = hand; + this.bg = bg; + this.bearing = bearing; + this.g = g; + } + + _buildHoursView() { + let $tick = $('
      '); + // Hours view + if (this.options.twelveHour) { + for (let i = 1; i < 13; i += 1) { + let tick = $tick.clone(); + let radian = i / 6 * Math.PI; + let radius = this.options.outerRadius; + tick.css({ + left: + this.options.dialRadius + Math.sin(radian) * radius - this.options.tickRadius + 'px', + top: + this.options.dialRadius - Math.cos(radian) * radius - this.options.tickRadius + 'px' + }); + tick.html(i === 0 ? '00' : i); + this.hoursView.appendChild(tick[0]); + // tick.on(mousedownEvent, mousedown); + } + } else { + for (let i = 0; i < 24; i += 1) { + let tick = $tick.clone(); + let radian = i / 6 * Math.PI; + let inner = i > 0 && i < 13; + let radius = inner ? this.options.innerRadius : this.options.outerRadius; + tick.css({ + left: + this.options.dialRadius + Math.sin(radian) * radius - this.options.tickRadius + 'px', + top: + this.options.dialRadius - Math.cos(radian) * radius - this.options.tickRadius + 'px' + }); + tick.html(i === 0 ? '00' : i); + this.hoursView.appendChild(tick[0]); + // tick.on(mousedownEvent, mousedown); + } + } + } + + _buildMinutesView() { + let $tick = $('
      '); + // Minutes view + for (let i = 0; i < 60; i += 5) { + let tick = $tick.clone(); + let radian = i / 30 * Math.PI; + tick.css({ + left: + this.options.dialRadius + + Math.sin(radian) * this.options.outerRadius - + this.options.tickRadius + + 'px', + top: + this.options.dialRadius - + Math.cos(radian) * this.options.outerRadius - + this.options.tickRadius + + 'px' + }); + tick.html(Timepicker._addLeadingZero(i)); + this.minutesView.appendChild(tick[0]); + } + } + + _handleAmPmClick(e) { + let $btnClicked = $(e.target); + this.amOrPm = $btnClicked.hasClass('am-btn') ? 'AM' : 'PM'; + this._updateAmPmView(); + } + + _updateAmPmView() { + if (this.options.twelveHour) { + this.$amBtn.toggleClass('text-primary', this.amOrPm === 'AM'); + this.$pmBtn.toggleClass('text-primary', this.amOrPm === 'PM'); + } + } + + _updateTimeFromInput() { + // Get the time + let value = ((this.el.value || this.options.defaultTime || '') + '').split(':'); + if (this.options.twelveHour && !(typeof value[1] === 'undefined')) { + if (value[1].toUpperCase().indexOf('AM') > 0) { + this.amOrPm = 'AM'; + } else { + this.amOrPm = 'PM'; + } + value[1] = value[1].replace('AM', '').replace('PM', ''); + } + if (value[0] === 'now') { + let now = new Date(+new Date() + this.options.fromNow); + value = [now.getHours(), now.getMinutes()]; + if (this.options.twelveHour) { + this.amOrPm = value[0] >= 12 && value[0] < 24 ? 'PM' : 'AM'; + } + } + this.hours = +value[0] || 0; + this.minutes = +value[1] || 0; + this.spanHours.innerHTML = this.hours; + this.spanMinutes.innerHTML = Timepicker._addLeadingZero(this.minutes); + + this._updateAmPmView(); + } + + showView(view, delay) { + if (view === 'minutes' && $(this.hoursView).css('visibility') === 'visible') { + // raiseCallback(this.options.beforeHourSelect); + } + let isHours = view === 'hours', + nextView = isHours ? this.hoursView : this.minutesView, + hideView = isHours ? this.minutesView : this.hoursView; + this.currentView = view; + + $(this.spanHours).toggleClass('text-primary', isHours); + $(this.spanMinutes).toggleClass('text-primary', !isHours); + + // Transition view + hideView.classList.add('timepicker-dial-out'); + $(nextView) + .css('visibility', 'visible') + .removeClass('timepicker-dial-out'); + + // Reset clock hand + this.resetClock(delay); + + // After transitions ended + clearTimeout(this.toggleViewTimer); + this.toggleViewTimer = setTimeout(() => { + $(hideView).css('visibility', 'hidden'); + }, this.options.duration); + } + + resetClock(delay) { + let view = this.currentView, + value = this[view], + isHours = view === 'hours', + unit = Math.PI / (isHours ? 6 : 30), + radian = value * unit, + radius = + isHours && value > 0 && value < 13 ? this.options.innerRadius : this.options.outerRadius, + x = Math.sin(radian) * radius, + y = -Math.cos(radian) * radius, + self = this; + + if (delay) { + $(this.canvas).addClass('timepicker-canvas-out'); + setTimeout(() => { + $(self.canvas).removeClass('timepicker-canvas-out'); + self.setHand(x, y); + }, delay); + } else { + this.setHand(x, y); + } + } + + setHand(x, y, roundBy5) { + let radian = Math.atan2(x, -y), + isHours = this.currentView === 'hours', + unit = Math.PI / (isHours || roundBy5 ? 6 : 30), + z = Math.sqrt(x * x + y * y), + inner = isHours && z < (this.options.outerRadius + this.options.innerRadius) / 2, + radius = inner ? this.options.innerRadius : this.options.outerRadius; + + if (this.options.twelveHour) { + radius = this.options.outerRadius; + } + + // Radian should in range [0, 2PI] + if (radian < 0) { + radian = Math.PI * 2 + radian; + } + + // Get the round value + let value = Math.round(radian / unit); + + // Get the round radian + radian = value * unit; + + // Correct the hours or minutes + if (this.options.twelveHour) { + if (isHours) { + if (value === 0) value = 12; + } else { + if (roundBy5) value *= 5; + if (value === 60) value = 0; + } + } else { + if (isHours) { + if (value === 12) { + value = 0; + } + value = inner ? (value === 0 ? 12 : value) : value === 0 ? 0 : value + 12; + } else { + if (roundBy5) { + value *= 5; + } + if (value === 60) { + value = 0; + } + } + } + + // Once hours or minutes changed, vibrate the device + if (this[this.currentView] !== value) { + if (this.vibrate && this.options.vibrate) { + // Do not vibrate too frequently + if (!this.vibrateTimer) { + navigator[this.vibrate](10); + this.vibrateTimer = setTimeout(() => { + this.vibrateTimer = null; + }, 100); + } + } + } + + this[this.currentView] = value; + if (isHours) { + this['spanHours'].innerHTML = value; + } else { + this['spanMinutes'].innerHTML = Timepicker._addLeadingZero(value); + } + + // Set clock hand and others' position + let cx1 = Math.sin(radian) * (radius - this.options.tickRadius), + cy1 = -Math.cos(radian) * (radius - this.options.tickRadius), + cx2 = Math.sin(radian) * radius, + cy2 = -Math.cos(radian) * radius; + this.hand.setAttribute('x2', cx1); + this.hand.setAttribute('y2', cy1); + this.bg.setAttribute('cx', cx2); + this.bg.setAttribute('cy', cy2); + } + + open() { + if (this.isOpen) { + return; + } + + this.isOpen = true; + this._updateTimeFromInput(); + this.showView('hours'); + + this.modal.open(); + } + + close() { + if (!this.isOpen) { + return; + } + + this.isOpen = false; + this.modal.close(); + } + + /** + * Finish timepicker selection. + */ + done(e, clearValue) { + // Set input value + let last = this.el.value; + let value = clearValue + ? '' + : Timepicker._addLeadingZero(this.hours) + ':' + Timepicker._addLeadingZero(this.minutes); + this.time = value; + if (!clearValue && this.options.twelveHour) { + value = `${value} ${this.amOrPm}`; + } + this.el.value = value; + + // Trigger change event + if (value !== last) { + this.$el.trigger('change'); + } + + this.close(); + this.el.focus(); + } + + clear() { + this.done(null, true); + } + } + + Timepicker._template = [ + '' + ].join(''); + + M.Timepicker = Timepicker; + + if (M.jQueryLoaded) { + M.initializeJqueryWrapper(Timepicker, 'timepicker', 'M_Timepicker'); + } +})(cash); diff --git a/static/js/toasts.js b/static/js/toasts.js new file mode 100644 index 00000000..b0e4b83a --- /dev/null +++ b/static/js/toasts.js @@ -0,0 +1,310 @@ +(function($, anim) { + 'use strict'; + + let _defaults = { + html: '', + displayLength: 4000, + inDuration: 300, + outDuration: 375, + classes: '', + completeCallback: null, + activationPercent: 0.8 + }; + + class Toast { + constructor(options) { + /** + * Options for the toast + * @member Toast#options + */ + this.options = $.extend({}, Toast.defaults, options); + this.message = this.options.html; + + /** + * Describes current pan state toast + * @type {Boolean} + */ + this.panning = false; + + /** + * Time remaining until toast is removed + */ + this.timeRemaining = this.options.displayLength; + + if (Toast._toasts.length === 0) { + Toast._createContainer(); + } + + // Create new toast + Toast._toasts.push(this); + let toastElement = this._createToast(); + toastElement.M_Toast = this; + this.el = toastElement; + this.$el = $(toastElement); + this._animateIn(); + this._setTimer(); + } + + static get defaults() { + return _defaults; + } + + /** + * Get Instance + */ + static getInstance(el) { + let domElem = !!el.jquery ? el[0] : el; + return domElem.M_Toast; + } + + /** + * Append toast container and add event handlers + */ + static _createContainer() { + let container = document.createElement('div'); + container.setAttribute('id', 'toast-container'); + + // Add event handler + container.addEventListener('touchstart', Toast._onDragStart); + container.addEventListener('touchmove', Toast._onDragMove); + container.addEventListener('touchend', Toast._onDragEnd); + + container.addEventListener('mousedown', Toast._onDragStart); + document.addEventListener('mousemove', Toast._onDragMove); + document.addEventListener('mouseup', Toast._onDragEnd); + + document.body.appendChild(container); + Toast._container = container; + } + + /** + * Remove toast container and event handlers + */ + static _removeContainer() { + // Add event handler + document.removeEventListener('mousemove', Toast._onDragMove); + document.removeEventListener('mouseup', Toast._onDragEnd); + + $(Toast._container).remove(); + Toast._container = null; + } + + /** + * Begin drag handler + * @param {Event} e + */ + static _onDragStart(e) { + if (e.target && $(e.target).closest('.toast').length) { + let $toast = $(e.target).closest('.toast'); + let toast = $toast[0].M_Toast; + toast.panning = true; + Toast._draggedToast = toast; + toast.el.classList.add('panning'); + toast.el.style.transition = ''; + toast.startingXPos = Toast._xPos(e); + toast.time = Date.now(); + toast.xPos = Toast._xPos(e); + } + } + + /** + * Drag move handler + * @param {Event} e + */ + static _onDragMove(e) { + if (!!Toast._draggedToast) { + e.preventDefault(); + let toast = Toast._draggedToast; + toast.deltaX = Math.abs(toast.xPos - Toast._xPos(e)); + toast.xPos = Toast._xPos(e); + toast.velocityX = toast.deltaX / (Date.now() - toast.time); + toast.time = Date.now(); + + let totalDeltaX = toast.xPos - toast.startingXPos; + let activationDistance = toast.el.offsetWidth * toast.options.activationPercent; + toast.el.style.transform = `translateX(${totalDeltaX}px)`; + toast.el.style.opacity = 1 - Math.abs(totalDeltaX / activationDistance); + } + } + + /** + * End drag handler + */ + static _onDragEnd() { + if (!!Toast._draggedToast) { + let toast = Toast._draggedToast; + toast.panning = false; + toast.el.classList.remove('panning'); + + let totalDeltaX = toast.xPos - toast.startingXPos; + let activationDistance = toast.el.offsetWidth * toast.options.activationPercent; + let shouldBeDismissed = Math.abs(totalDeltaX) > activationDistance || toast.velocityX > 1; + + // Remove toast + if (shouldBeDismissed) { + toast.wasSwiped = true; + toast.dismiss(); + + // Animate toast back to original position + } else { + toast.el.style.transition = 'transform .2s, opacity .2s'; + toast.el.style.transform = ''; + toast.el.style.opacity = ''; + } + Toast._draggedToast = null; + } + } + + /** + * Get x position of mouse or touch event + * @param {Event} e + */ + static _xPos(e) { + if (e.targetTouches && e.targetTouches.length >= 1) { + return e.targetTouches[0].clientX; + } + // mouse event + return e.clientX; + } + + /** + * Remove all toasts + */ + static dismissAll() { + for (let toastIndex in Toast._toasts) { + Toast._toasts[toastIndex].dismiss(); + } + } + + /** + * Create toast and append it to toast container + */ + _createToast() { + let toast = document.createElement('div'); + toast.classList.add('toast'); + + // Add custom classes onto toast + if (!!this.options.classes.length) { + $(toast).addClass(this.options.classes); + } + + // Set content + if ( + typeof HTMLElement === 'object' + ? this.message instanceof HTMLElement + : this.message && + typeof this.message === 'object' && + this.message !== null && + this.message.nodeType === 1 && + typeof this.message.nodeName === 'string' + ) { + toast.appendChild(this.message); + + // Check if it is jQuery object + } else if (!!this.message.jquery) { + $(toast).append(this.message[0]); + + // Insert as html; + } else { + toast.innerHTML = this.message; + } + + // Append toasft + Toast._container.appendChild(toast); + return toast; + } + + /** + * Animate in toast + */ + _animateIn() { + // Animate toast in + anim({ + targets: this.el, + top: 0, + opacity: 1, + duration: this.options.inDuration, + easing: 'easeOutCubic' + }); + } + + /** + * Create setInterval which automatically removes toast when timeRemaining >= 0 + * has been reached + */ + _setTimer() { + if (this.timeRemaining !== Infinity) { + this.counterInterval = setInterval(() => { + // If toast is not being dragged, decrease its time remaining + if (!this.panning) { + this.timeRemaining -= 20; + } + + // Animate toast out + if (this.timeRemaining <= 0) { + this.dismiss(); + } + }, 20); + } + } + + /** + * Dismiss toast with animation + */ + dismiss() { + window.clearInterval(this.counterInterval); + let activationDistance = this.el.offsetWidth * this.options.activationPercent; + + if (this.wasSwiped) { + this.el.style.transition = 'transform .05s, opacity .05s'; + this.el.style.transform = `translateX(${activationDistance}px)`; + this.el.style.opacity = 0; + } + + anim({ + targets: this.el, + opacity: 0, + marginTop: -40, + duration: this.options.outDuration, + easing: 'easeOutExpo', + complete: () => { + // Call the optional callback + if (typeof this.options.completeCallback === 'function') { + this.options.completeCallback(); + } + // Remove toast from DOM + this.$el.remove(); + Toast._toasts.splice(Toast._toasts.indexOf(this), 1); + if (Toast._toasts.length === 0) { + Toast._removeContainer(); + } + } + }); + } + } + + /** + * @static + * @memberof Toast + * @type {Array.} + */ + Toast._toasts = []; + + /** + * @static + * @memberof Toast + */ + Toast._container = null; + + /** + * @static + * @memberof Toast + * @type {Toast} + */ + Toast._draggedToast = null; + + M.Toast = Toast; + M.toast = function(options) { + return new Toast(options); + }; +})(cash, M.anime); diff --git a/static/js/tooltip.js b/static/js/tooltip.js new file mode 100644 index 00000000..b30dce2b --- /dev/null +++ b/static/js/tooltip.js @@ -0,0 +1,303 @@ +(function($, anim) { + 'use strict'; + + let _defaults = { + exitDelay: 200, + enterDelay: 0, + html: null, + margin: 5, + inDuration: 250, + outDuration: 200, + position: 'bottom', + transitionMovement: 10 + }; + + /** + * @class + * + */ + class Tooltip extends Component { + /** + * Construct Tooltip instance + * @constructor + * @param {Element} el + * @param {Object} options + */ + constructor(el, options) { + super(Tooltip, el, options); + + this.el.M_Tooltip = this; + this.options = $.extend({}, Tooltip.defaults, options); + + this.isOpen = false; + this.isHovered = false; + this.isFocused = false; + this._appendTooltipEl(); + this._setupEventHandlers(); + } + + static get defaults() { + return _defaults; + } + + static init(els, options) { + return super.init(this, els, options); + } + + /** + * Get Instance + */ + static getInstance(el) { + let domElem = !!el.jquery ? el[0] : el; + return domElem.M_Tooltip; + } + + /** + * Teardown component + */ + destroy() { + $(this.tooltipEl).remove(); + this._removeEventHandlers(); + this.el.M_Tooltip = undefined; + } + + _appendTooltipEl() { + let tooltipEl = document.createElement('div'); + tooltipEl.classList.add('material-tooltip'); + this.tooltipEl = tooltipEl; + + let tooltipContentEl = document.createElement('div'); + tooltipContentEl.classList.add('tooltip-content'); + tooltipContentEl.innerHTML = this.options.html; + tooltipEl.appendChild(tooltipContentEl); + document.body.appendChild(tooltipEl); + } + + _updateTooltipContent() { + this.tooltipEl.querySelector('.tooltip-content').innerHTML = this.options.html; + } + + _setupEventHandlers() { + this._handleMouseEnterBound = this._handleMouseEnter.bind(this); + this._handleMouseLeaveBound = this._handleMouseLeave.bind(this); + this._handleFocusBound = this._handleFocus.bind(this); + this._handleBlurBound = this._handleBlur.bind(this); + this.el.addEventListener('mouseenter', this._handleMouseEnterBound); + this.el.addEventListener('mouseleave', this._handleMouseLeaveBound); + this.el.addEventListener('focus', this._handleFocusBound, true); + this.el.addEventListener('blur', this._handleBlurBound, true); + } + + _removeEventHandlers() { + this.el.removeEventListener('mouseenter', this._handleMouseEnterBound); + this.el.removeEventListener('mouseleave', this._handleMouseLeaveBound); + this.el.removeEventListener('focus', this._handleFocusBound, true); + this.el.removeEventListener('blur', this._handleBlurBound, true); + } + + open(isManual) { + if (this.isOpen) { + return; + } + isManual = isManual === undefined ? true : undefined; // Default value true + this.isOpen = true; + // Update tooltip content with HTML attribute options + this.options = $.extend({}, this.options, this._getAttributeOptions()); + this._updateTooltipContent(); + this._setEnterDelayTimeout(isManual); + } + + close() { + if (!this.isOpen) { + return; + } + + this.isHovered = false; + this.isFocused = false; + this.isOpen = false; + this._setExitDelayTimeout(); + } + + /** + * Create timeout which delays when the tooltip closes + */ + _setExitDelayTimeout() { + clearTimeout(this._exitDelayTimeout); + + this._exitDelayTimeout = setTimeout(() => { + if (this.isHovered || this.isFocused) { + return; + } + + this._animateOut(); + }, this.options.exitDelay); + } + + /** + * Create timeout which delays when the toast closes + */ + _setEnterDelayTimeout(isManual) { + clearTimeout(this._enterDelayTimeout); + + this._enterDelayTimeout = setTimeout(() => { + if (!this.isHovered && !this.isFocused && !isManual) { + return; + } + + this._animateIn(); + }, this.options.enterDelay); + } + + _positionTooltip() { + let origin = this.el, + tooltip = this.tooltipEl, + originHeight = origin.offsetHeight, + originWidth = origin.offsetWidth, + tooltipHeight = tooltip.offsetHeight, + tooltipWidth = tooltip.offsetWidth, + newCoordinates, + margin = this.options.margin, + targetTop, + targetLeft; + + (this.xMovement = 0), (this.yMovement = 0); + + targetTop = origin.getBoundingClientRect().top + M.getDocumentScrollTop(); + targetLeft = origin.getBoundingClientRect().left + M.getDocumentScrollLeft(); + + if (this.options.position === 'top') { + targetTop += -tooltipHeight - margin; + targetLeft += originWidth / 2 - tooltipWidth / 2; + this.yMovement = -this.options.transitionMovement; + } else if (this.options.position === 'right') { + targetTop += originHeight / 2 - tooltipHeight / 2; + targetLeft += originWidth + margin; + this.xMovement = this.options.transitionMovement; + } else if (this.options.position === 'left') { + targetTop += originHeight / 2 - tooltipHeight / 2; + targetLeft += -tooltipWidth - margin; + this.xMovement = -this.options.transitionMovement; + } else { + targetTop += originHeight + margin; + targetLeft += originWidth / 2 - tooltipWidth / 2; + this.yMovement = this.options.transitionMovement; + } + + newCoordinates = this._repositionWithinScreen( + targetLeft, + targetTop, + tooltipWidth, + tooltipHeight + ); + $(tooltip).css({ + top: newCoordinates.y + 'px', + left: newCoordinates.x + 'px' + }); + } + + _repositionWithinScreen(x, y, width, height) { + let scrollLeft = M.getDocumentScrollLeft(); + let scrollTop = M.getDocumentScrollTop(); + let newX = x - scrollLeft; + let newY = y - scrollTop; + + let bounding = { + left: newX, + top: newY, + width: width, + height: height + }; + + let offset = this.options.margin + this.options.transitionMovement; + let edges = M.checkWithinContainer(document.body, bounding, offset); + + if (edges.left) { + newX = offset; + } else if (edges.right) { + newX -= newX + width - window.innerWidth; + } + + if (edges.top) { + newY = offset; + } else if (edges.bottom) { + newY -= newY + height - window.innerHeight; + } + + return { + x: newX + scrollLeft, + y: newY + scrollTop + }; + } + + _animateIn() { + this._positionTooltip(); + this.tooltipEl.style.visibility = 'visible'; + anim.remove(this.tooltipEl); + anim({ + targets: this.tooltipEl, + opacity: 1, + translateX: this.xMovement, + translateY: this.yMovement, + duration: this.options.inDuration, + easing: 'easeOutCubic' + }); + } + + _animateOut() { + anim.remove(this.tooltipEl); + anim({ + targets: this.tooltipEl, + opacity: 0, + translateX: 0, + translateY: 0, + duration: this.options.outDuration, + easing: 'easeOutCubic' + }); + } + + _handleMouseEnter() { + this.isHovered = true; + this.isFocused = false; // Allows close of tooltip when opened by focus. + this.open(false); + } + + _handleMouseLeave() { + this.isHovered = false; + this.isFocused = false; // Allows close of tooltip when opened by focus. + this.close(); + } + + _handleFocus() { + if (M.tabPressed) { + this.isFocused = true; + this.open(false); + } + } + + _handleBlur() { + this.isFocused = false; + this.close(); + } + + _getAttributeOptions() { + let attributeOptions = {}; + let tooltipTextOption = this.el.getAttribute('data-tooltip'); + let positionOption = this.el.getAttribute('data-position'); + + if (tooltipTextOption) { + attributeOptions.html = tooltipTextOption; + } + + if (positionOption) { + attributeOptions.position = positionOption; + } + return attributeOptions; + } + } + + M.Tooltip = Tooltip; + + if (M.jQueryLoaded) { + M.initializeJqueryWrapper(Tooltip, 'tooltip', 'M_Tooltip'); + } +})(cash, M.anime); diff --git a/static/js/waves.js b/static/js/waves.js new file mode 100644 index 00000000..b56cc2cb --- /dev/null +++ b/static/js/waves.js @@ -0,0 +1,335 @@ +/*! + * Waves v0.6.4 + * http://fian.my.id/Waves + * + * Copyright 2014 Alfiana E. Sibuea and other contributors + * Released under the MIT license + * https://github.com/fians/Waves/blob/master/LICENSE + */ + +;(function(window) { + 'use strict'; + + var Waves = Waves || {}; + var $$ = document.querySelectorAll.bind(document); + + // Find exact position of element + function isWindow(obj) { + return obj !== null && obj === obj.window; + } + + function getWindow(elem) { + return isWindow(elem) ? elem : elem.nodeType === 9 && elem.defaultView; + } + + function offset(elem) { + var docElem, win, + box = {top: 0, left: 0}, + doc = elem && elem.ownerDocument; + + docElem = doc.documentElement; + + if (typeof elem.getBoundingClientRect !== typeof undefined) { + box = elem.getBoundingClientRect(); + } + win = getWindow(doc); + return { + top: box.top + win.pageYOffset - docElem.clientTop, + left: box.left + win.pageXOffset - docElem.clientLeft + }; + } + + function convertStyle(obj) { + var style = ''; + + for (var a in obj) { + if (obj.hasOwnProperty(a)) { + style += (a + ':' + obj[a] + ';'); + } + } + + return style; + } + + var Effect = { + + // Effect delay + duration: 750, + + show: function(e, element) { + + // Disable right click + if (e.button === 2) { + return false; + } + + var el = element || this; + + // Create ripple + var ripple = document.createElement('div'); + ripple.className = 'waves-ripple'; + el.appendChild(ripple); + + // Get click coordinate and element witdh + var pos = offset(el); + var relativeY = (e.pageY - pos.top); + var relativeX = (e.pageX - pos.left); + var scale = 'scale('+((el.clientWidth / 100) * 10)+')'; + + // Support for touch devices + if ('touches' in e) { + relativeY = (e.touches[0].pageY - pos.top); + relativeX = (e.touches[0].pageX - pos.left); + } + + // Attach data to element + ripple.setAttribute('data-hold', Date.now()); + ripple.setAttribute('data-scale', scale); + ripple.setAttribute('data-x', relativeX); + ripple.setAttribute('data-y', relativeY); + + // Set ripple position + var rippleStyle = { + 'top': relativeY+'px', + 'left': relativeX+'px' + }; + + ripple.className = ripple.className + ' waves-notransition'; + ripple.setAttribute('style', convertStyle(rippleStyle)); + ripple.className = ripple.className.replace('waves-notransition', ''); + + // Scale the ripple + rippleStyle['-webkit-transform'] = scale; + rippleStyle['-moz-transform'] = scale; + rippleStyle['-ms-transform'] = scale; + rippleStyle['-o-transform'] = scale; + rippleStyle.transform = scale; + rippleStyle.opacity = '1'; + + rippleStyle['-webkit-transition-duration'] = Effect.duration + 'ms'; + rippleStyle['-moz-transition-duration'] = Effect.duration + 'ms'; + rippleStyle['-o-transition-duration'] = Effect.duration + 'ms'; + rippleStyle['transition-duration'] = Effect.duration + 'ms'; + + rippleStyle['-webkit-transition-timing-function'] = 'cubic-bezier(0.250, 0.460, 0.450, 0.940)'; + rippleStyle['-moz-transition-timing-function'] = 'cubic-bezier(0.250, 0.460, 0.450, 0.940)'; + rippleStyle['-o-transition-timing-function'] = 'cubic-bezier(0.250, 0.460, 0.450, 0.940)'; + rippleStyle['transition-timing-function'] = 'cubic-bezier(0.250, 0.460, 0.450, 0.940)'; + + ripple.setAttribute('style', convertStyle(rippleStyle)); + }, + + hide: function(e) { + TouchHandler.touchup(e); + + var el = this; + var width = el.clientWidth * 1.4; + + // Get first ripple + var ripple = null; + var ripples = el.getElementsByClassName('waves-ripple'); + if (ripples.length > 0) { + ripple = ripples[ripples.length - 1]; + } else { + return false; + } + + var relativeX = ripple.getAttribute('data-x'); + var relativeY = ripple.getAttribute('data-y'); + var scale = ripple.getAttribute('data-scale'); + + // Get delay beetween mousedown and mouse leave + var diff = Date.now() - Number(ripple.getAttribute('data-hold')); + var delay = 350 - diff; + + if (delay < 0) { + delay = 0; + } + + // Fade out ripple after delay + setTimeout(function() { + var style = { + 'top': relativeY+'px', + 'left': relativeX+'px', + 'opacity': '0', + + // Duration + '-webkit-transition-duration': Effect.duration + 'ms', + '-moz-transition-duration': Effect.duration + 'ms', + '-o-transition-duration': Effect.duration + 'ms', + 'transition-duration': Effect.duration + 'ms', + '-webkit-transform': scale, + '-moz-transform': scale, + '-ms-transform': scale, + '-o-transform': scale, + 'transform': scale, + }; + + ripple.setAttribute('style', convertStyle(style)); + + setTimeout(function() { + try { + el.removeChild(ripple); + } catch(e) { + return false; + } + }, Effect.duration); + }, delay); + }, + + // Little hack to make can perform waves effect + wrapInput: function(elements) { + for (var a = 0; a < elements.length; a++) { + var el = elements[a]; + + if (el.tagName.toLowerCase() === 'input') { + var parent = el.parentNode; + + // If input already have parent just pass through + if (parent.tagName.toLowerCase() === 'i' && parent.className.indexOf('waves-effect') !== -1) { + continue; + } + + // Put element class and style to the specified parent + var wrapper = document.createElement('i'); + wrapper.className = el.className + ' waves-input-wrapper'; + + var elementStyle = el.getAttribute('style'); + + if (!elementStyle) { + elementStyle = ''; + } + + wrapper.setAttribute('style', elementStyle); + + el.className = 'waves-button-input'; + el.removeAttribute('style'); + + // Put element as child + parent.replaceChild(wrapper, el); + wrapper.appendChild(el); + } + } + } + }; + + + /** + * Disable mousedown event for 500ms during and after touch + */ + var TouchHandler = { + /* uses an integer rather than bool so there's no issues with + * needing to clear timeouts if another touch event occurred + * within the 500ms. Cannot mouseup between touchstart and + * touchend, nor in the 500ms after touchend. */ + touches: 0, + allowEvent: function(e) { + var allow = true; + + if (e.type === 'touchstart') { + TouchHandler.touches += 1; //push + } else if (e.type === 'touchend' || e.type === 'touchcancel') { + setTimeout(function() { + if (TouchHandler.touches > 0) { + TouchHandler.touches -= 1; //pop after 500ms + } + }, 500); + } else if (e.type === 'mousedown' && TouchHandler.touches > 0) { + allow = false; + } + + return allow; + }, + touchup: function(e) { + TouchHandler.allowEvent(e); + } + }; + + + /** + * Delegated click handler for .waves-effect element. + * returns null when .waves-effect element not in "click tree" + */ + function getWavesEffectElement(e) { + if (TouchHandler.allowEvent(e) === false) { + return null; + } + + var element = null; + var target = e.target || e.srcElement; + + while (target.parentNode !== null) { + if (!(target instanceof SVGElement) && target.className.indexOf('waves-effect') !== -1) { + element = target; + break; + } + target = target.parentNode; + } + return element; + } + + /** + * Bubble the click and show effect if .waves-effect elem was found + */ + function showEffect(e) { + var element = getWavesEffectElement(e); + + if (element !== null) { + Effect.show(e, element); + + if ('ontouchstart' in window) { + element.addEventListener('touchend', Effect.hide, false); + element.addEventListener('touchcancel', Effect.hide, false); + } + + element.addEventListener('mouseup', Effect.hide, false); + element.addEventListener('mouseleave', Effect.hide, false); + element.addEventListener('dragend', Effect.hide, false); + } + } + + Waves.displayEffect = function(options) { + options = options || {}; + + if ('duration' in options) { + Effect.duration = options.duration; + } + + //Wrap input inside tag + Effect.wrapInput($$('.waves-effect')); + + if ('ontouchstart' in window) { + document.body.addEventListener('touchstart', showEffect, false); + } + + document.body.addEventListener('mousedown', showEffect, false); + }; + + /** + * Attach Waves to an input element (or any element which doesn't + * bubble mouseup/mousedown events). + * Intended to be used with dynamically loaded forms/inputs, or + * where the user doesn't want a delegated click handler. + */ + Waves.attach = function(element) { + //FUTURE: automatically add waves classes and allow users + // to specify them with an options param? Eg. light/classic/button + if (element.tagName.toLowerCase() === 'input') { + Effect.wrapInput([element]); + element = element.parentNode; + } + + if ('ontouchstart' in window) { + element.addEventListener('touchstart', showEffect, false); + } + + element.addEventListener('mousedown', showEffect, false); + }; + + window.Waves = Waves; + + document.addEventListener('DOMContentLoaded', function() { + Waves.displayEffect(); + }, false); + +})(window); diff --git a/static/sass/components/_badges.scss b/static/sass/components/_badges.scss new file mode 100644 index 00000000..ffed87dc --- /dev/null +++ b/static/sass/components/_badges.scss @@ -0,0 +1,55 @@ +// Badges +span.badge { + min-width: 3rem; + padding: 0 6px; + margin-left: 14px; + text-align: center; + font-size: 1rem; + line-height: $badge-height; + height: $badge-height; + color: color('grey', 'darken-1'); + float: right; + box-sizing: border-box; + + &.new { + font-weight: 300; + font-size: 0.8rem; + color: #fff; + background-color: $badge-bg-color; + border-radius: 2px; + } + &.new:after { + content: " new"; + } + + &[data-badge-caption]::after { + content: " " attr(data-badge-caption); + } +} + +// Special cases +nav ul a span.badge { + display: inline-block; + float: none; + margin-left: 4px; + line-height: $badge-height; + height: $badge-height; + -webkit-font-smoothing: auto; +} + +// Line height centering +.collection-item span.badge { + margin-top: calc(#{$collection-line-height / 2} - #{$badge-height / 2}); +} +.collapsible span.badge { + margin-left: auto; +} +.sidenav span.badge { + margin-top: calc(#{$sidenav-line-height / 2} - #{$badge-height / 2}); +} + +table span.badge { + display: inline-block; + float: none; + margin-left: auto; +} diff --git a/static/sass/components/_buttons.scss b/static/sass/components/_buttons.scss new file mode 100644 index 00000000..44b80c8d --- /dev/null +++ b/static/sass/components/_buttons.scss @@ -0,0 +1,322 @@ +// shared styles +.btn, +.btn-flat { + border: $button-border; + border-radius: $button-radius; + display: inline-block; + height: $button-height; + line-height: $button-height; + padding: $button-padding; + text-transform: uppercase; + vertical-align: middle; + -webkit-tap-highlight-color: transparent; // Gets rid of tap active state +} + +// Disabled shared style +.btn.disabled, +.btn-floating.disabled, +.btn-large.disabled, +.btn-small.disabled, +.btn-flat.disabled, +.btn:disabled, +.btn-floating:disabled, +.btn-large:disabled, +.btn-small:disabled, +.btn-flat:disabled, +.btn[disabled], +.btn-floating[disabled], +.btn-large[disabled], +.btn-small[disabled], +.btn-flat[disabled] { + pointer-events: none; + background-color: $button-disabled-background !important; + box-shadow: none; + color: $button-disabled-color !important; + cursor: default; + &:hover { + background-color: $button-disabled-background !important; + color: $button-disabled-color !important; + } +} + +// Shared icon styles +.btn, +.btn-floating, +.btn-large, +.btn-small, +.btn-flat { + font-size: $button-font-size; + outline: 0; + i { + font-size: $button-icon-font-size; + line-height: inherit; + } +} + +// Shared focus button style +.btn, +.btn-floating { + &:focus { + background-color: darken($button-raised-background, 10%); + } +} + +// Raised Button +.btn { + text-decoration: none; + color: $button-raised-color; + background-color: $button-raised-background; + text-align: center; + letter-spacing: .5px; + @extend .z-depth-1; + transition: background-color .2s ease-out; + cursor: pointer; + &:hover { + background-color: $button-raised-background-hover; + @extend .z-depth-1-half; + } +} + +// Floating button +.btn-floating { + &:hover { + background-color: $button-floating-background-hover; + @extend .z-depth-1-half; + } + &:before { + border-radius: 0; + } + &.btn-large { + &.halfway-fab { + bottom: -$button-floating-large-size / 2; + } + width: $button-floating-large-size; + height: $button-floating-large-size; + padding: 0; + i { + line-height: $button-floating-large-size; + } + } + + &.btn-small { + &.halfway-fab { + bottom: -$button-floating-small-size / 2; + } + width: $button-floating-small-size; + height: $button-floating-small-size; + i { + line-height: $button-floating-small-size; + } + } + + &.halfway-fab { + &.left { + right: auto; + left: 24px; + } + position: absolute; + right: 24px; + bottom: -$button-floating-size / 2; + } + display: inline-block; + color: $button-floating-color; + position: relative; + overflow: hidden; + z-index: 1; + width: $button-floating-size; + height: $button-floating-size; + line-height: $button-floating-size; + padding: 0; + background-color: $button-floating-background; + border-radius: $button-floating-radius; + @extend .z-depth-1; + transition: background-color .3s; + cursor: pointer; + vertical-align: middle; + i { + width: inherit; + display: inline-block; + text-align: center; + color: $button-floating-color; + font-size: $button-large-icon-font-size; + line-height: $button-floating-size; + } +} + +// button fix +button.btn-floating { + border: $button-border; +} + +// Fixed Action Button +.fixed-action-btn { + &.active { + ul { + visibility: visible; + } + } + + // Directions + &.direction-left, + &.direction-right { + padding: 0 0 0 15px; + ul { + text-align: right; + right: 64px; + top: 50%; + transform: translateY(-50%); + height: 100%; + left: auto; + /*width 100% only goes to width of button container */ + width: 500px; + li { + display: inline-block; + margin: 7.5px 15px 0 0; + } + } + } + &.direction-right { + padding: 0 15px 0 0; + ul { + text-align: left; + direction: rtl; + left: 64px; + right: auto; + li { + margin: 7.5px 0 0 15px; + } + } + } + &.direction-bottom { + padding: 0 0 15px 0; + ul { + top: 64px; + bottom: auto; + display: flex; + flex-direction: column-reverse; + li { + margin: 15px 0 0 0; + } + } + } + &.toolbar { + &.active { + &>a i { + opacity: 0; + } + } + padding: 0; + height: $button-floating-large-size; + ul { + display: flex; + top: 0; + bottom: 0; + z-index: 1; + li { + flex: 1; + display: inline-block; + margin: 0; + height: 100%; + transition: none; + a { + display: block; + overflow: hidden; + position: relative; + width: 100%; + height: 100%; + background-color: transparent; + box-shadow: none; + color: #fff; + line-height: $button-floating-large-size; + z-index: 1; + i { + line-height: inherit; + } + } + } + } + } + position: fixed; + right: 23px; + bottom: 23px; + padding-top: 15px; + margin-bottom: 0; + z-index: 997; + ul { + left: 0; + right: 0; + text-align: center; + position: absolute; + bottom: 64px; + margin: 0; + visibility: hidden; + li { + margin-bottom: 15px; + } + a.btn-floating { + opacity: 0; + } + } + .fab-backdrop { + position: absolute; + top: 0; + left: 0; + z-index: -1; + width: $button-floating-size; + height: $button-floating-size; + background-color: $button-floating-background; + border-radius: $button-floating-radius; + transform: scale(0); + } +} + +// Flat button +.btn-flat { + box-shadow: none; + background-color: transparent; + color: $button-flat-color; + cursor: pointer; + transition: background-color .2s; + &:focus, + &:hover { + box-shadow: none; + } + &:focus { + background-color: rgba(0, 0, 0, .1); + } + &.disabled, + &.btn-flat[disabled] { + background-color: transparent !important; + color: $button-flat-disabled-color !important; + cursor: default; + } +} + +// Large button +.btn-large { + @extend .btn; + height: $button-large-height; + line-height: $button-large-height; + font-size: $button-large-font-size; + padding: 0 28px; + + i { + font-size: $button-large-icon-font-size; + } +} + +// Small button +.btn-small { + @extend .btn; + height: $button-small-height; + line-height: $button-small-height; + font-size: $button-small-font-size; + i { + font-size: $button-small-icon-font-size; + } +} + +// Block button +.btn-block { + display: block; +} diff --git a/static/sass/components/_cards.scss b/static/sass/components/_cards.scss new file mode 100644 index 00000000..fcbf28ec --- /dev/null +++ b/static/sass/components/_cards.scss @@ -0,0 +1,195 @@ + + +.card-panel { + transition: box-shadow .25s; + padding: $card-padding; + margin: $element-top-margin 0 $element-bottom-margin 0; + border-radius: 2px; + @extend .z-depth-1; + background-color: $card-bg-color; +} + +.card { + position: relative; + margin: $element-top-margin 0 $element-bottom-margin 0; + background-color: $card-bg-color; + transition: box-shadow .25s; + border-radius: 2px; + @extend .z-depth-1; + + + .card-title { + font-size: 24px; + font-weight: 300; + &.activator { + cursor: pointer; + } + } + + // Card Sizes + &.small, &.medium, &.large { + position: relative; + + .card-image { + max-height: 60%; + overflow: hidden; + } + .card-image + .card-content { + max-height: 40%; + } + .card-content { + max-height: 100%; + overflow: hidden; + } + .card-action { + position: absolute; + bottom: 0; + left: 0; + right: 0; + } + } + + &.small { + height: 300px; + } + + &.medium { + height: 400px; + } + + &.large { + height: 500px; + } + + // Horizontal Cards + &.horizontal { + &.small, &.medium, &.large { + .card-image { + height: 100%; + max-height: none; + overflow: visible; + + img { + height: 100%; + } + } + } + + display: flex; + + .card-image { + max-width: 50%; + img { + border-radius: 2px 0 0 2px; + max-width: 100%; + width: auto; + } + } + + .card-stacked { + display: flex; + flex-direction: column; + flex: 1; + position: relative; + + .card-content { + flex-grow: 1; + } + } + } + + // Sticky Action Section + &.sticky-action { + .card-action { + z-index: 2; + } + + .card-reveal { + z-index: 1; + padding-bottom: 64px; + } + } + + + + + .card-image { + position: relative; + + // Image background for content + img { + display: block; + border-radius: 2px 2px 0 0; + position: relative; + left: 0; + right: 0; + top: 0; + bottom: 0; + width: 100%; + } + + .card-title { + color: $card-bg-color; + position: absolute; + bottom: 0; + left: 0; + max-width: 100%; + padding: $card-padding; + } + } + + .card-content { + padding: $card-padding; + border-radius: 0 0 2px 2px; + + p { + margin: 0; + } + .card-title { + display: block; + line-height: 32px; + margin-bottom: 8px; + + i { + line-height: 32px; + } + } + } + + .card-action { + &:last-child { + border-radius: 0 0 2px 2px; + } + background-color: inherit; // Use inherit to inherit color classes + border-top: 1px solid rgba(160,160,160,.2); + position: relative; + padding: 16px $card-padding; + + a:not(.btn):not(.btn-large):not(.btn-floating) { + color: $card-link-color; + margin-right: $card-padding; + transition: color .3s ease; + text-transform: uppercase; + + &:hover { color: $card-link-color-light; } + } + } + + .card-reveal { + padding: $card-padding; + position: absolute; + background-color: $card-bg-color; + width: 100%; + overflow-y: auto; + left: 0; + top: 100%; + height: 100%; + z-index: 3; + display: none; + + .card-title { + cursor: pointer; + display: block; + } + } +} diff --git a/static/sass/components/_carousel.scss b/static/sass/components/_carousel.scss new file mode 100644 index 00000000..cc36d4b3 --- /dev/null +++ b/static/sass/components/_carousel.scss @@ -0,0 +1,90 @@ +.carousel { + &.carousel-slider { + top: 0; + left: 0; + + .carousel-fixed-item { + &.with-indicators { + bottom: 68px; + } + + position: absolute; + left: 0; + right: 0; + bottom: 20px; + z-index: 1; + } + + .carousel-item { + width: 100%; + height: 100%; + min-height: $carousel-height; + position: absolute; + top: 0; + left: 0; + + h2 { + font-size: 24px; + font-weight: 500; + line-height: 32px; + } + + p { + font-size: 15px; + } + } + } + + overflow: hidden; + position: relative; + width: 100%; + height: $carousel-height; + perspective: 500px; + transform-style: preserve-3d; + transform-origin: 0% 50%; + + .carousel-item { + visibility: hidden; + width: $carousel-item-width; + height: $carousel-item-height; + position: absolute; + top: 0; + left: 0; + + & > img { + width: 100%; + } + } + + .indicators { + position: absolute; + text-align: center; + left: 0; + right: 0; + bottom: 0; + margin: 0; + + .indicator-item { + &.active { + background-color: #fff; + } + + display: inline-block; + position: relative; + cursor: pointer; + height: 8px; + width: 8px; + margin: 24px 4px; + background-color: rgba(255,255,255,.5); + + transition: background-color .3s; + border-radius: 50%; + } + } + + // Materialbox compatibility + &.scrolling .carousel-item .materialboxed, + .carousel-item:not(.active) .materialboxed { + pointer-events: none; + } +} diff --git a/static/sass/components/_chips.scss b/static/sass/components/_chips.scss new file mode 100644 index 00000000..27744a8b --- /dev/null +++ b/static/sass/components/_chips.scss @@ -0,0 +1,90 @@ +.chip { + &:focus { + outline: none; + background-color: $chip-selected-color; + color: #fff; + } + + display: inline-block; + height: 32px; + font-size: 13px; + font-weight: 500; + color: rgba(0,0,0,.6); + line-height: 32px; + padding: 0 12px; + border-radius: 16px; + background-color: $chip-bg-color; + margin-bottom: $chip-margin; + margin-right: $chip-margin; + + > img { + float: left; + margin: 0 8px 0 -12px; + height: 32px; + width: 32px; + border-radius: 50%; + } + + .close { + cursor: pointer; + float: right; + font-size: 16px; + line-height: 32px; + padding-left: 8px; + } +} + +.chips { + border: none; + border-bottom: 1px solid $chip-border-color; + box-shadow: none; + margin: $input-margin; + min-height: 45px; + outline: none; + transition: all .3s; + + &.focus { + border-bottom: 1px solid $chip-selected-color; + box-shadow: 0 1px 0 0 $chip-selected-color; + } + + &:hover { + cursor: text; + } + + .input { + background: none; + border: 0; + color: rgba(0,0,0,.6); + display: inline-block; + font-size: $input-font-size; + height: $input-height; + line-height: 32px; + outline: 0; + margin: 0; + padding: 0 !important; + width: 120px !important; + } + + .input:focus { + border: 0 !important; + box-shadow: none !important; + } + + // Autocomplete + .autocomplete-content { + margin-top: 0; + margin-bottom: 0; + } +} + +// Form prefix +.prefix ~ .chips { + margin-left: 3rem; + width: 92%; + width: calc(100% - 3rem); +} +.chips:empty ~ label { + font-size: 0.8rem; + transform: translateY(-140%); +} diff --git a/static/sass/components/_collapsible.scss b/static/sass/components/_collapsible.scss new file mode 100644 index 00000000..024324fd --- /dev/null +++ b/static/sass/components/_collapsible.scss @@ -0,0 +1,91 @@ +.collapsible { + border-top: 1px solid $collapsible-border-color; + border-right: 1px solid $collapsible-border-color; + border-left: 1px solid $collapsible-border-color; + margin: $element-top-margin 0 $element-bottom-margin 0; + @extend .z-depth-1; +} + +.collapsible-header { + &:focus { + outline: 0 + } + + display: flex; + cursor: pointer; + -webkit-tap-highlight-color: transparent; + line-height: 1.5; + padding: 1rem; + background-color: $collapsible-header-color; + border-bottom: 1px solid $collapsible-border-color; + + i { + width: 2rem; + font-size: 1.6rem; + display: inline-block; + text-align: center; + margin-right: 1rem; + } +} +.keyboard-focused .collapsible-header:focus { + background-color: #eee; +} + +.collapsible-body { + display: none; + border-bottom: 1px solid $collapsible-border-color; + box-sizing: border-box; + padding: 2rem; +} + +// Sidenav collapsible styling +.sidenav, +.sidenav.fixed { + + .collapsible { + border: none; + box-shadow: none; + + li { padding: 0; } + } + + .collapsible-header { + background-color: transparent; + border: none; + line-height: inherit; + height: inherit; + padding: 0 $sidenav-padding; + + &:hover { background-color: rgba(0,0,0,.05); } + i { line-height: inherit; } + } + + .collapsible-body { + border: 0; + background-color: $collapsible-header-color; + + li a { + padding: 0 (7.5px + $sidenav-padding) + 0 (15px + $sidenav-padding); + } + } + +} + +// Popout Collapsible + +.collapsible.popout { + border: none; + box-shadow: none; + > li { + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12); + // transform: scaleX(.92); + margin: 0 24px; + transition: margin .35s cubic-bezier(0.250, 0.460, 0.450, 0.940); + } + > li.active { + box-shadow: 0 5px 11px 0 rgba(0, 0, 0, 0.18), 0 4px 15px 0 rgba(0, 0, 0, 0.15); + margin: 16px 0; + // transform: scaleX(1); + } +} diff --git a/static/sass/components/_color-classes.scss b/static/sass/components/_color-classes.scss new file mode 100644 index 00000000..155cecd1 --- /dev/null +++ b/static/sass/components/_color-classes.scss @@ -0,0 +1,32 @@ +// Color Classes + +@each $color_name, $color in $colors { + @each $color_type, $color_value in $color { + @if $color_type == "base" { + .#{$color_name} { + background-color: $color_value !important; + } + .#{$color_name}-text { + color: $color_value !important; + } + } + @else if $color_name != "shades" { + .#{$color_name}.#{$color_type} { + background-color: $color_value !important; + } + .#{$color_name}-text.text-#{$color_type} { + color: $color_value !important; + } + } + } +} + +// Shade classes +@each $color, $color_value in $shades { + .#{$color} { + background-color: $color_value !important; + } + .#{$color}-text { + color: $color_value !important; + } +} diff --git a/static/sass/components/_color-variables.scss b/static/sass/components/_color-variables.scss new file mode 100644 index 00000000..062f6a56 --- /dev/null +++ b/static/sass/components/_color-variables.scss @@ -0,0 +1,370 @@ +// Google Color Palette defined: http://www.google.com/design/spec/style/color.html + +$materialize-red: ( + "base": #e51c23, + "lighten-5": #fdeaeb, + "lighten-4": #f8c1c3, + "lighten-3": #f3989b, + "lighten-2": #ee6e73, + "lighten-1": #ea454b, + "darken-1": #d0181e, + "darken-2": #b9151b, + "darken-3": #a21318, + "darken-4": #8b1014, +); + +$red: ( + "base": #F44336, + "lighten-5": #FFEBEE, + "lighten-4": #FFCDD2, + "lighten-3": #EF9A9A, + "lighten-2": #E57373, + "lighten-1": #EF5350, + "darken-1": #E53935, + "darken-2": #D32F2F, + "darken-3": #C62828, + "darken-4": #B71C1C, + "accent-1": #FF8A80, + "accent-2": #FF5252, + "accent-3": #FF1744, + "accent-4": #D50000 +); + +$pink: ( + "base": #e91e63, + "lighten-5": #fce4ec, + "lighten-4": #f8bbd0, + "lighten-3": #f48fb1, + "lighten-2": #f06292, + "lighten-1": #ec407a, + "darken-1": #d81b60, + "darken-2": #c2185b, + "darken-3": #ad1457, + "darken-4": #880e4f, + "accent-1": #ff80ab, + "accent-2": #ff4081, + "accent-3": #f50057, + "accent-4": #c51162 +); + +$purple: ( + "base": #9c27b0, + "lighten-5": #f3e5f5, + "lighten-4": #e1bee7, + "lighten-3": #ce93d8, + "lighten-2": #ba68c8, + "lighten-1": #ab47bc, + "darken-1": #8e24aa, + "darken-2": #7b1fa2, + "darken-3": #6a1b9a, + "darken-4": #4a148c, + "accent-1": #ea80fc, + "accent-2": #e040fb, + "accent-3": #d500f9, + "accent-4": #aa00ff +); + +$deep-purple: ( + "base": #673ab7, + "lighten-5": #ede7f6, + "lighten-4": #d1c4e9, + "lighten-3": #b39ddb, + "lighten-2": #9575cd, + "lighten-1": #7e57c2, + "darken-1": #5e35b1, + "darken-2": #512da8, + "darken-3": #4527a0, + "darken-4": #311b92, + "accent-1": #b388ff, + "accent-2": #7c4dff, + "accent-3": #651fff, + "accent-4": #6200ea +); + +$indigo: ( + "base": #3f51b5, + "lighten-5": #e8eaf6, + "lighten-4": #c5cae9, + "lighten-3": #9fa8da, + "lighten-2": #7986cb, + "lighten-1": #5c6bc0, + "darken-1": #3949ab, + "darken-2": #303f9f, + "darken-3": #283593, + "darken-4": #1a237e, + "accent-1": #8c9eff, + "accent-2": #536dfe, + "accent-3": #3d5afe, + "accent-4": #304ffe +); + +$blue: ( + "base": #2196F3, + "lighten-5": #E3F2FD, + "lighten-4": #BBDEFB, + "lighten-3": #90CAF9, + "lighten-2": #64B5F6, + "lighten-1": #42A5F5, + "darken-1": #1E88E5, + "darken-2": #1976D2, + "darken-3": #1565C0, + "darken-4": #0D47A1, + "accent-1": #82B1FF, + "accent-2": #448AFF, + "accent-3": #2979FF, + "accent-4": #2962FF +); + +$light-blue: ( + "base": #03a9f4, + "lighten-5": #e1f5fe, + "lighten-4": #b3e5fc, + "lighten-3": #81d4fa, + "lighten-2": #4fc3f7, + "lighten-1": #29b6f6, + "darken-1": #039be5, + "darken-2": #0288d1, + "darken-3": #0277bd, + "darken-4": #01579b, + "accent-1": #80d8ff, + "accent-2": #40c4ff, + "accent-3": #00b0ff, + "accent-4": #0091ea +); + +$cyan: ( + "base": #00bcd4, + "lighten-5": #e0f7fa, + "lighten-4": #b2ebf2, + "lighten-3": #80deea, + "lighten-2": #4dd0e1, + "lighten-1": #26c6da, + "darken-1": #00acc1, + "darken-2": #0097a7, + "darken-3": #00838f, + "darken-4": #006064, + "accent-1": #84ffff, + "accent-2": #18ffff, + "accent-3": #00e5ff, + "accent-4": #00b8d4 +); + +$teal: ( + "base": #009688, + "lighten-5": #e0f2f1, + "lighten-4": #b2dfdb, + "lighten-3": #80cbc4, + "lighten-2": #4db6ac, + "lighten-1": #26a69a, + "darken-1": #00897b, + "darken-2": #00796b, + "darken-3": #00695c, + "darken-4": #004d40, + "accent-1": #a7ffeb, + "accent-2": #64ffda, + "accent-3": #1de9b6, + "accent-4": #00bfa5 +); + +$green: ( + "base": #4CAF50, + "lighten-5": #E8F5E9, + "lighten-4": #C8E6C9, + "lighten-3": #A5D6A7, + "lighten-2": #81C784, + "lighten-1": #66BB6A, + "darken-1": #43A047, + "darken-2": #388E3C, + "darken-3": #2E7D32, + "darken-4": #1B5E20, + "accent-1": #B9F6CA, + "accent-2": #69F0AE, + "accent-3": #00E676, + "accent-4": #00C853 +); + +$light-green: ( + "base": #8bc34a, + "lighten-5": #f1f8e9, + "lighten-4": #dcedc8, + "lighten-3": #c5e1a5, + "lighten-2": #aed581, + "lighten-1": #9ccc65, + "darken-1": #7cb342, + "darken-2": #689f38, + "darken-3": #558b2f, + "darken-4": #33691e, + "accent-1": #ccff90, + "accent-2": #b2ff59, + "accent-3": #76ff03, + "accent-4": #64dd17 +); + +$lime: ( + "base": #cddc39, + "lighten-5": #f9fbe7, + "lighten-4": #f0f4c3, + "lighten-3": #e6ee9c, + "lighten-2": #dce775, + "lighten-1": #d4e157, + "darken-1": #c0ca33, + "darken-2": #afb42b, + "darken-3": #9e9d24, + "darken-4": #827717, + "accent-1": #f4ff81, + "accent-2": #eeff41, + "accent-3": #c6ff00, + "accent-4": #aeea00 +); + +$yellow: ( + "base": #ffeb3b, + "lighten-5": #fffde7, + "lighten-4": #fff9c4, + "lighten-3": #fff59d, + "lighten-2": #fff176, + "lighten-1": #ffee58, + "darken-1": #fdd835, + "darken-2": #fbc02d, + "darken-3": #f9a825, + "darken-4": #f57f17, + "accent-1": #ffff8d, + "accent-2": #ffff00, + "accent-3": #ffea00, + "accent-4": #ffd600 +); + +$amber: ( + "base": #ffc107, + "lighten-5": #fff8e1, + "lighten-4": #ffecb3, + "lighten-3": #ffe082, + "lighten-2": #ffd54f, + "lighten-1": #ffca28, + "darken-1": #ffb300, + "darken-2": #ffa000, + "darken-3": #ff8f00, + "darken-4": #ff6f00, + "accent-1": #ffe57f, + "accent-2": #ffd740, + "accent-3": #ffc400, + "accent-4": #ffab00 +); + +$orange: ( + "base": #ff9800, + "lighten-5": #fff3e0, + "lighten-4": #ffe0b2, + "lighten-3": #ffcc80, + "lighten-2": #ffb74d, + "lighten-1": #ffa726, + "darken-1": #fb8c00, + "darken-2": #f57c00, + "darken-3": #ef6c00, + "darken-4": #e65100, + "accent-1": #ffd180, + "accent-2": #ffab40, + "accent-3": #ff9100, + "accent-4": #ff6d00 +); + +$deep-orange: ( + "base": #ff5722, + "lighten-5": #fbe9e7, + "lighten-4": #ffccbc, + "lighten-3": #ffab91, + "lighten-2": #ff8a65, + "lighten-1": #ff7043, + "darken-1": #f4511e, + "darken-2": #e64a19, + "darken-3": #d84315, + "darken-4": #bf360c, + "accent-1": #ff9e80, + "accent-2": #ff6e40, + "accent-3": #ff3d00, + "accent-4": #dd2c00 +); + +$brown: ( + "base": #795548, + "lighten-5": #efebe9, + "lighten-4": #d7ccc8, + "lighten-3": #bcaaa4, + "lighten-2": #a1887f, + "lighten-1": #8d6e63, + "darken-1": #6d4c41, + "darken-2": #5d4037, + "darken-3": #4e342e, + "darken-4": #3e2723 +); + +$blue-grey: ( + "base": #607d8b, + "lighten-5": #eceff1, + "lighten-4": #cfd8dc, + "lighten-3": #b0bec5, + "lighten-2": #90a4ae, + "lighten-1": #78909c, + "darken-1": #546e7a, + "darken-2": #455a64, + "darken-3": #37474f, + "darken-4": #263238 +); + +$grey: ( + "base": #9e9e9e, + "lighten-5": #fafafa, + "lighten-4": #f5f5f5, + "lighten-3": #eeeeee, + "lighten-2": #e0e0e0, + "lighten-1": #bdbdbd, + "darken-1": #757575, + "darken-2": #616161, + "darken-3": #424242, + "darken-4": #212121 +); + +$shades: ( + "black": #000000, + "white": #FFFFFF, + "transparent": transparent +); + +$colors: ( + "materialize-red": $materialize-red, + "red": $red, + "pink": $pink, + "purple": $purple, + "deep-purple": $deep-purple, + "indigo": $indigo, + "blue": $blue, + "light-blue": $light-blue, + "cyan": $cyan, + "teal": $teal, + "green": $green, + "light-green": $light-green, + "lime": $lime, + "yellow": $yellow, + "amber": $amber, + "orange": $orange, + "deep-orange": $deep-orange, + "brown": $brown, + "blue-grey": $blue-grey, + "grey": $grey, + "shades": $shades +) !default; + + +// usage: color("name_of_color", "type_of_color") +// to avoid to repeating map-get($colors, ...) + +@function color($color, $type) { + @if map-has-key($colors, $color) { + $curr_color: map-get($colors, $color); + @if map-has-key($curr_color, $type) { + @return map-get($curr_color, $type); + } + } + @warn "Unknown `#{$color}` - `#{$type}` in $colors."; + @return null; +} diff --git a/static/sass/components/_datepicker.scss b/static/sass/components/_datepicker.scss new file mode 100644 index 00000000..d2c920b2 --- /dev/null +++ b/static/sass/components/_datepicker.scss @@ -0,0 +1,191 @@ +/* Modal */ +.datepicker-modal { + max-width: 325px; + min-width: 300px; + max-height: none; +} + +.datepicker-container.modal-content { + display: flex; + flex-direction: column; + padding: 0; +} + +.datepicker-controls { + display: flex; + justify-content: space-between; + width: 280px; + margin: 0 auto; + + .selects-container { + display: flex; + } + + .select-wrapper { + input { + &:focus { + border-bottom: none; + } + border-bottom: none; + text-align: center; + margin: 0; + } + + .caret { + display: none; + } + } + + .select-year input { + width: 50px; + } + + .select-month input { + width: 70px; + } +} + +.month-prev, .month-next { + margin-top: 4px; + cursor: pointer; + background-color: transparent; + border: none; +} + + +/* Date Display */ +.datepicker-date-display { + flex: 1 auto; + background-color: $secondary-color; + color: #fff; + padding: 20px 22px; + font-weight: 500; + + .year-text { + display: block; + font-size: 1.5rem; + line-height: 25px; + color: $datepicker-year; + } + + .date-text { + display: block; + font-size: 2.8rem; + line-height: 47px; + font-weight: 500; + } +} + + +/* Calendar */ +.datepicker-calendar-container { + flex: 2.5 auto; +} + +.datepicker-table { + width: 280px; + font-size: 1rem; + margin: 0 auto; + + thead { + border-bottom: none; + } + + th { + padding: 10px 5px; + text-align: center; + } + + tr { + border: none; + } + + abbr { + text-decoration: none; + color: $datepicker-calendar-header-color; + } + + td { + &.is-today { + color: $secondary-color; + } + + &.is-selected { + background-color: $secondary-color; + color: #fff; + } + + &.is-outside-current-month, + &.is-disabled { + color: $datepicker-disabled-day-color; + pointer-events: none; + } + + border-radius: 50%; + padding: 0; + } +} + +.datepicker-day-button { + &:focus { + background-color: $datepicker-day-focus; + } + + background-color: transparent; + border: none; + line-height: 38px; + display: block; + width: 100%; + border-radius: 50%; + padding: 0 5px; + cursor: pointer; + color: inherit; +} + + +/* Footer */ +.datepicker-footer { + width: 280px; + margin: 0 auto; + padding-bottom: 5px; + display: flex; + justify-content: space-between; +} + +.datepicker-cancel, +.datepicker-clear, +.datepicker-today, +.datepicker-done { + color: $secondary-color; + padding: 0 1rem; +} + +.datepicker-clear { + color: $error-color; +} + + +/* Media Queries */ +@media #{$medium-and-up} { + .datepicker-modal { + max-width: 625px; + } + + .datepicker-container.modal-content { + flex-direction: row; + } + + .datepicker-date-display { + flex: 0 1 270px; + } + + .datepicker-controls, + .datepicker-table, + .datepicker-footer { + width: 320px; + } + + .datepicker-day-button { + line-height: 44px; + } +} diff --git a/static/sass/components/_dropdown.scss b/static/sass/components/_dropdown.scss new file mode 100644 index 00000000..0caae65d --- /dev/null +++ b/static/sass/components/_dropdown.scss @@ -0,0 +1,85 @@ +.dropdown-content { + &:focus { + outline: 0; + } + + + @extend .z-depth-1; + background-color: $dropdown-bg-color; + margin: 0; + display: none; + min-width: 100px; + overflow-y: auto; + opacity: 0; + position: absolute; + left: 0; + top: 0; + z-index: 9999; // TODO: Check if this doesn't break other things + transform-origin: 0 0; + + + li { + &:hover, &.active { + background-color: $dropdown-hover-bg-color; + } + + &:focus { + outline: none; + } + + &.divider { + min-height: 0; + height: 1px; + } + + & > a, & > span { + font-size: 16px; + color: $dropdown-color; + display: block; + line-height: 22px; + padding: (($dropdown-item-height - 22) / 2) 16px; + } + + & > span > label { + top: 1px; + left: 0; + height: 18px; + } + + // Icon alignment override + & > a > i { + height: inherit; + line-height: inherit; + float: left; + margin: 0 24px 0 0; + width: 24px; + } + + + clear: both; + color: $off-black; + cursor: pointer; + min-height: $dropdown-item-height; + line-height: 1.5rem; + width: 100%; + text-align: left; + } +} + +body.keyboard-focused { + .dropdown-content li:focus { + background-color: darken($dropdown-hover-bg-color, 8%); + } +} + +// Input field specificity bugfix +.input-field.col .dropdown-content [type="checkbox"] + label { + top: 1px; + left: 0; + height: 18px; + transform: none; +} + +.dropdown-trigger { + cursor: pointer; +} \ No newline at end of file diff --git a/static/sass/components/_global.scss b/static/sass/components/_global.scss new file mode 100644 index 00000000..39f33db0 --- /dev/null +++ b/static/sass/components/_global.scss @@ -0,0 +1,769 @@ +//Default styles + +html { + box-sizing: border-box; +} +*, *:before, *:after { + box-sizing: inherit; +} + +body { + // display: flex; + // min-height: 100vh; + // flex-direction: column; +} + +main { + // flex: 1 0 auto; +} + +button, +input, +optgroup, +select, +textarea { + font-family: $font-stack; +} + +ul { + &:not(.browser-default) { + padding-left: 0; + list-style-type: none; + + & > li { + list-style-type: none; + } + } +} + +a { + color: $link-color; + text-decoration: none; + + // Gets rid of tap active state + -webkit-tap-highlight-color: transparent; +} + + +// Positioning +.valign-wrapper { + display: flex; + align-items: center; +} + + +// classic clearfix +.clearfix { + clear: both; +} + + +// Z-levels +.z-depth-0 { + box-shadow: none !important; +} + +/* 2dp elevation modified*/ +.z-depth-1 { + box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), + 0 3px 1px -2px rgba(0,0,0,0.12), + 0 1px 5px 0 rgba(0,0,0,0.2); +} +.z-depth-1-half { + box-shadow: 0 3px 3px 0 rgba(0, 0, 0, 0.14), 0 1px 7px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -1px rgba(0, 0, 0, 0.2); +} + +/* 6dp elevation modified*/ +.z-depth-2 { + box-shadow: 0 4px 5px 0 rgba(0,0,0,0.14), + 0 1px 10px 0 rgba(0,0,0,0.12), + 0 2px 4px -1px rgba(0,0,0,0.3); +} + +/* 12dp elevation modified*/ +.z-depth-3 { + box-shadow: 0 8px 17px 2px rgba(0,0,0,0.14), + 0 3px 14px 2px rgba(0,0,0,0.12), + 0 5px 5px -3px rgba(0, 0, 0, 0.2); +} + +/* 16dp elevation */ +.z-depth-4 { + box-shadow: 0 16px 24px 2px rgba(0,0,0,0.14), + 0 6px 30px 5px rgba(0,0,0,0.12), + 0 8px 10px -7px rgba(0,0,0,0.2); +} + +/* 24dp elevation */ +.z-depth-5 { + box-shadow: 0 24px 38px 3px rgba(0,0,0,0.14), + 0 9px 46px 8px rgba(0,0,0,0.12), + 0 11px 15px -7px rgba(0,0,0,0.2); +} + +.hoverable { + transition: box-shadow .25s; + + &:hover { + box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); + } +} + +// Dividers + +.divider { + height: 1px; + overflow: hidden; + background-color: color("grey", "lighten-2"); +} + + +// Blockquote + +blockquote { + margin: 20px 0; + padding-left: 1.5rem; + border-left: 5px solid $primary-color; +} + +// Icon Styles + +i { + line-height: inherit; + + &.left { + float: left; + margin-right: 15px; + } + &.right { + float: right; + margin-left: 15px; + } + &.tiny { + font-size: 1rem; + } + &.small { + font-size: 2rem; + } + &.medium { + font-size: 4rem; + } + &.large { + font-size: 6rem; + } +} + +// Images +img.responsive-img, +video.responsive-video { + max-width: 100%; + height: auto; +} + + +// Pagination + +.pagination { + + li { + display: inline-block; + border-radius: 2px; + text-align: center; + vertical-align: top; + height: 30px; + + a { + color: #444; + display: inline-block; + font-size: 1.2rem; + padding: 0 10px; + line-height: 30px; + } + + &.active a { color: #fff; } + + &.active { background-color: $primary-color; } + + &.disabled a { + cursor: default; + color: #999; + } + + i { + font-size: 2rem; + } + } + + + li.pages ul li { + display: inline-block; + float: none; + } +} +@media #{$medium-and-down} { + .pagination { + width: 100%; + + li.prev, + li.next { + width: 10%; + } + + li.pages { + width: 80%; + overflow: hidden; + white-space: nowrap; + } + } +} + +// Breadcrumbs +.breadcrumb { + font-size: 18px; + color: rgba(255,255,255, .7); + + i, + [class^="mdi-"], [class*="mdi-"], + i.material-icons { + display: inline-block; + float: left; + font-size: 24px; + } + + &:before { + content: '\E5CC'; + color: rgba(255,255,255, .7); + vertical-align: top; + display: inline-block; + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 25px; + margin: 0 10px 0 8px; + -webkit-font-smoothing: antialiased; + } + + &:first-child:before { + display: none; + } + + &:last-child { + color: #fff; + } +} + +// Parallax +.parallax-container { + position: relative; + overflow: hidden; + height: 500px; + + .parallax { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: -1; + + img { + opacity: 0; + position: absolute; + left: 50%; + bottom: 0; + min-width: 100%; + min-height: 100%; + transform: translate3d(0,0,0); + transform: translateX(-50%); + } + } +} + +// Pushpin +.pin-top, .pin-bottom { + position: relative; +} +.pinned { + position: fixed !important; +} + +/********************* + Transition Classes +**********************/ + +ul.staggered-list li { + opacity: 0; +} + +.fade-in { + opacity: 0; + transform-origin: 0 50%; +} + + +/********************* + Media Query Classes +**********************/ +.hide-on-small-only, .hide-on-small-and-down { + @media #{$small-and-down} { + display: none !important; + } +} +.hide-on-med-and-down { + @media #{$medium-and-down} { + display: none !important; + } +} +.hide-on-med-and-up { + @media #{$medium-and-up} { + display: none !important; + } +} +.hide-on-med-only { + @media only screen and (min-width: $small-screen) and (max-width: $medium-screen) { + display: none !important; + } +} +.hide-on-large-only { + @media #{$large-and-up} { + display: none !important; + } +} +.hide-on-extra-large-only { + @media #{$extra-large-and-up} { + display: none !important; + } +} +.show-on-extra-large { + @media #{$extra-large-and-up} { + display: block !important; + } +} +.show-on-large { + @media #{$large-and-up} { + display: block !important; + } +} +.show-on-medium { + @media only screen and (min-width: $small-screen) and (max-width: $medium-screen) { + display: block !important; + } +} +.show-on-small { + @media #{$small-and-down} { + display: block !important; + } +} +.show-on-medium-and-up { + @media #{$medium-and-up} { + display: block !important; + } +} +.show-on-medium-and-down { + @media #{$medium-and-down} { + display: block !important; + } +} + + +// Center text on mobile +.center-on-small-only { + @media #{$small-and-down} { + text-align: center; + } +} + +// Footer +.page-footer { + padding-top: 20px; + color: $footer-font-color; + background-color: $footer-bg-color; + + .footer-copyright { + overflow: hidden; + min-height: 50px; + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 0px; + color: $footer-copyright-font-color; + background-color: $footer-copyright-bg-color; + } +} + +// Tables +table, th, td { + border: none; +} + +table { + width:100%; + display: table; + border-collapse: collapse; + border-spacing: 0; + + &.striped { + tr { + border-bottom: none; + } + + > tbody { + > tr:nth-child(odd) { + background-color: $table-striped-color; + } + + > tr > td { + border-radius: 0; + } + } + } + + &.highlight > tbody > tr { + transition: background-color .25s ease; + &:hover { + background-color: $table-striped-color; + } + } + + &.centered { + thead tr th, tbody tr td { + text-align: center; + } + } +} + +tr { + border-bottom: 1px solid $table-border-color; +} + +td, th{ + padding: 15px 5px; + display: table-cell; + text-align: left; + vertical-align: middle; + border-radius: 2px; +} + +// Responsive Table +@media #{$medium-and-down} { + + table.responsive-table { + width: 100%; + border-collapse: collapse; + border-spacing: 0; + display: block; + position: relative; + + td:empty:before { + content: '\00a0'; + } + + th, + td { + margin: 0; + vertical-align: top; + } + + th { text-align: left; } + thead { + display: block; + float: left; + + tr { + display: block; + padding: 0 10px 0 0; + + th::before { + content: "\00a0"; + } + } + } + tbody { + display: block; + width: auto; + position: relative; + overflow-x: auto; + white-space: nowrap; + + tr { + display: inline-block; + vertical-align: top; + } + } + th { + display: block; + text-align: right; + } + td { + display: block; + min-height: 1.25em; + text-align: left; + } + tr { + border-bottom: none; + padding: 0 10px; + } + + /* sort out borders */ + thead { + border: 0; + border-right: 1px solid $table-border-color; + } + } + +} + + +// Collections +.collection { + margin: $element-top-margin 0 $element-bottom-margin 0; + border: 1px solid $collection-border-color; + border-radius: 2px; + overflow: hidden; + position: relative; + + .collection-item { + background-color: $collection-bg-color; + line-height: $collection-line-height; + padding: 10px 20px; + margin: 0; + border-bottom: 1px solid $collection-border-color; + + // Avatar Collection + &.avatar { + min-height: 84px; + padding-left: 72px; + position: relative; + + // Don't style circles inside preloader classes. + &:not(.circle-clipper) > .circle, + :not(.circle-clipper) > .circle { + position: absolute; + width: 42px; + height: 42px; + overflow: hidden; + left: 15px; + display: inline-block; + vertical-align: middle; + } + i.circle { + font-size: 18px; + line-height: 42px; + color: #fff; + background-color: #999; + text-align: center; + } + + + .title { + font-size: 16px; + } + + p { + margin: 0; + } + + .secondary-content { + position: absolute; + top: 16px; + right: 16px; + } + + } + + + &:last-child { + border-bottom: none; + } + + &.active { + background-color: $collection-active-bg-color; + color: $collection-active-color; + + .secondary-content { + color: #fff; + } + } + } + a.collection-item{ + display: block; + transition: .25s; + color: $collection-link-color; + &:not(.active) { + &:hover { + background-color: $collection-hover-bg-color; + } + } + } + + &.with-header { + .collection-header { + background-color: $collection-bg-color; + border-bottom: 1px solid $collection-border-color; + padding: 10px 20px; + } + .collection-item { + padding-left: 30px; + } + .collection-item.avatar { + padding-left: 72px; + } + } + +} +// Made less specific to allow easier overriding +.secondary-content { + float: right; + color: $secondary-color; +} +.collapsible .collection { + margin: 0; + border: none; +} + + + +// Responsive Videos +.video-container { + position: relative; + padding-bottom: 56.25%; + height: 0; + overflow: hidden; + + iframe, object, embed { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } +} + +// Progress Bar +.progress { + position: relative; + height: 4px; + display: block; + width: 100%; + background-color: lighten($progress-bar-color, 40%); + border-radius: 2px; + margin: $element-top-margin 0 $element-bottom-margin 0; + overflow: hidden; + .determinate { + position: absolute; + top: 0; + left: 0; + bottom: 0; + background-color: $progress-bar-color; + transition: width .3s linear; + } + .indeterminate { + background-color: $progress-bar-color; + &:before { + content: ''; + position: absolute; + background-color: inherit; + top: 0; + left:0; + bottom: 0; + will-change: left, right; + // Custom bezier + animation: indeterminate 2.1s cubic-bezier(0.650, 0.815, 0.735, 0.395) infinite; + + } + &:after { + content: ''; + position: absolute; + background-color: inherit; + top: 0; + left:0; + bottom: 0; + will-change: left, right; + // Custom bezier + animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.840, 0.440, 1.000) infinite; + animation-delay: 1.15s; + } + } +} +@keyframes indeterminate { + 0% { + left: -35%; + right:100%; + } + 60% { + left: 100%; + right: -90%; + } + 100% { + left: 100%; + right: -90%; + } +} + +@keyframes indeterminate-short { + 0% { + left: -200%; + right: 100%; + } + 60% { + left: 107%; + right: -8%; + } + 100% { + left: 107%; + right: -8%; + } +} + + +/******************* + Utility Classes +*******************/ + +.hide { + display: none !important; +} + +// Text Align +.left-align { + text-align: left; +} +.right-align { + text-align: right +} +.center, .center-align { + text-align: center; +} + +.left { + float: left !important; +} +.right { + float: right !important; +} + +// No Text Select +.no-select { + user-select: none; +} + +.circle { + border-radius: 50%; +} + +.center-block { + display: block; + margin-left: auto; + margin-right: auto; +} + +.truncate { + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.no-padding { + padding: 0 !important; +} diff --git a/static/sass/components/_grid.scss b/static/sass/components/_grid.scss new file mode 100644 index 00000000..8892f050 --- /dev/null +++ b/static/sass/components/_grid.scss @@ -0,0 +1,156 @@ +.container { + margin: 0 auto; + max-width: 1280px; + width: 90%; +} +@media #{$medium-and-up} { + .container { + width: 85%; + } +} +@media #{$large-and-up} { + .container { + width: 70%; + } +} +.col .row { + margin-left: (-1 * $gutter-width / 2); + margin-right: (-1 * $gutter-width / 2); +} + +.section { + padding-top: 1rem; + padding-bottom: 1rem; + + &.no-pad { + padding: 0; + } + &.no-pad-bot { + padding-bottom: 0; + } + &.no-pad-top { + padding-top: 0; + } +} + + +// Mixins to eliminate code repitition +@mixin reset-offset { + margin-left: auto; + left: auto; + right: auto; +} +@mixin grid-classes($size, $i, $perc) { + &.offset-#{$size}#{$i} { + margin-left: $perc; + } + &.pull-#{$size}#{$i} { + right: $perc; + } + &.push-#{$size}#{$i} { + left: $perc; + } +} + + +.row { + margin-left: auto; + margin-right: auto; + margin-bottom: 20px; + + // Clear floating children + &:after { + content: ""; + display: table; + clear: both; + } + + .col { + float: left; + box-sizing: border-box; + padding: 0 $gutter-width / 2; + min-height: 1px; + + &[class*="push-"], + &[class*="pull-"] { + position: relative; + } + + $i: 1; + @while $i <= $num-cols { + $perc: unquote((100 / ($num-cols / $i)) + "%"); + &.s#{$i} { + width: $perc; + @include reset-offset; + } + $i: $i + 1; + } + + $i: 1; + @while $i <= $num-cols { + $perc: unquote((100 / ($num-cols / $i)) + "%"); + @include grid-classes("s", $i, $perc); + $i: $i + 1; + } + + @media #{$medium-and-up} { + + $i: 1; + @while $i <= $num-cols { + $perc: unquote((100 / ($num-cols / $i)) + "%"); + &.m#{$i} { + width: $perc; + @include reset-offset; + } + $i: $i + 1 + } + + $i: 1; + @while $i <= $num-cols { + $perc: unquote((100 / ($num-cols / $i)) + "%"); + @include grid-classes("m", $i, $perc); + $i: $i + 1; + } + } + + @media #{$large-and-up} { + + $i: 1; + @while $i <= $num-cols { + $perc: unquote((100 / ($num-cols / $i)) + "%"); + &.l#{$i} { + width: $perc; + @include reset-offset; + } + $i: $i + 1; + } + + $i: 1; + @while $i <= $num-cols { + $perc: unquote((100 / ($num-cols / $i)) + "%"); + @include grid-classes("l", $i, $perc); + $i: $i + 1; + } + } + + @media #{$extra-large-and-up} { + + $i: 1; + @while $i <= $num-cols { + $perc: unquote((100 / ($num-cols / $i)) + "%"); + &.xl#{$i} { + width: $perc; + @include reset-offset; + } + $i: $i + 1; + } + + $i: 1; + @while $i <= $num-cols { + $perc: unquote((100 / ($num-cols / $i)) + "%"); + @include grid-classes("xl", $i, $perc); + $i: $i + 1; + } + } + } +} diff --git a/static/sass/components/_icons-material-design.scss b/static/sass/components/_icons-material-design.scss new file mode 100644 index 00000000..2aa6a4ae --- /dev/null +++ b/static/sass/components/_icons-material-design.scss @@ -0,0 +1,5 @@ +/* This is needed for some mobile phones to display the Google Icon font properly */ +.material-icons { + text-rendering: optimizeLegibility; + font-feature-settings: 'liga'; +} diff --git a/static/sass/components/_materialbox.scss b/static/sass/components/_materialbox.scss new file mode 100644 index 00000000..30276672 --- /dev/null +++ b/static/sass/components/_materialbox.scss @@ -0,0 +1,43 @@ +.materialboxed { + &:hover { + &:not(.active) { + opacity: .8; + } + } + + display: block; + cursor: zoom-in; + position: relative; + transition: opacity .4s; + -webkit-backface-visibility: hidden; + + &.active { + cursor: zoom-out; + } +} + +#materialbox-overlay { + position:fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: #292929; + z-index: 1000; + will-change: opacity; +} + +.materialbox-caption { + position: fixed; + display: none; + color: #fff; + line-height: 50px; + bottom: 0; + left: 0; + width: 100%; + text-align: center; + padding: 0% 15%; + height: 50px; + z-index: 1000; + -webkit-font-smoothing: antialiased; +} \ No newline at end of file diff --git a/static/sass/components/_modal.scss b/static/sass/components/_modal.scss new file mode 100644 index 00000000..38cf3ce8 --- /dev/null +++ b/static/sass/components/_modal.scss @@ -0,0 +1,94 @@ +.modal { + &:focus { + outline: none; + } + + @extend .z-depth-5; + + display: none; + position: fixed; + left: 0; + right: 0; + background-color: #fafafa; + padding: 0; + max-height: 70%; + width: 55%; + margin: auto; + overflow-y: auto; + + border-radius: 2px; + will-change: top, opacity; + + @media #{$medium-and-down} { + width: 80%; + } + + h1,h2,h3,h4 { + margin-top: 0; + } + + .modal-content { + padding: 24px; + } + .modal-close { + cursor: pointer; + } + + .modal-footer { + border-radius: 0 0 2px 2px; + background-color: #fafafa; + padding: 4px 6px; + height: 56px; + width: 100%; + text-align: right; + + .btn, .btn-flat { + margin: 6px 0; + } + } +} +.modal-overlay { + position: fixed; + z-index: 999; + top: -25%; + left: 0; + bottom: 0; + right: 0; + height: 125%; + width: 100%; + background: #000; + display: none; + + will-change: opacity; +} + +// Modal with fixed action footer +.modal.modal-fixed-footer { + padding: 0; + height: 70%; + + .modal-content { + position: absolute; + height: calc(100% - 56px); + max-height: 100%; + width: 100%; + overflow-y: auto; + } + + .modal-footer { + border-top: 1px solid rgba(0,0,0,.1); + position: absolute; + bottom: 0; + } +} + +// Modal Bottom Sheet Style +.modal.bottom-sheet { + top: auto; + bottom: -100%; + margin: 0; + width: 100%; + max-height: 45%; + border-radius: 0; + will-change: bottom, opacity; +} diff --git a/static/sass/components/_navbar.scss b/static/sass/components/_navbar.scss new file mode 100644 index 00000000..0317bb26 --- /dev/null +++ b/static/sass/components/_navbar.scss @@ -0,0 +1,208 @@ +nav { + &.nav-extended { + height: auto; + + .nav-wrapper { + min-height: $navbar-height-mobile; + height: auto; + } + + .nav-content { + position: relative; + line-height: normal; + } + } + + color: $navbar-font-color; + @extend .z-depth-1; + background-color: $primary-color; + width: 100%; + height: $navbar-height-mobile; + line-height: $navbar-line-height-mobile; + + a { color: $navbar-font-color; } + + i, + [class^="mdi-"], [class*="mdi-"], + i.material-icons { + display: block; + font-size: 24px; + height: $navbar-height-mobile; + line-height: $navbar-line-height-mobile; + } + + .nav-wrapper { + position: relative; + height: 100%; + } + + @media #{$large-and-up} { + a.sidenav-trigger { display: none; } + } + + + // Collapse button + .sidenav-trigger { + float: left; + position: relative; + z-index: 1; + height: $navbar-height-mobile; + margin: 0 18px; + + i { + height: $navbar-height-mobile; + line-height: $navbar-line-height-mobile; + } + } + + + // Logo + .brand-logo { + position: absolute; + color: $navbar-font-color; + display: inline-block; + font-size: $navbar-brand-font-size; + padding: 0; + + &.center { + left: 50%; + transform: translateX(-50%); + } + + @media #{$medium-and-down} { + left: 50%; + transform: translateX(-50%); + + &.left, &.right { + padding: 0; + transform: none; + } + + &.left { left: 0.5rem; } + &.right { + right: 0.5rem; + left: auto; + } + } + + &.right { + right: 0.5rem; + padding: 0; + } + + i, + [class^="mdi-"], [class*="mdi-"], + i.material-icons { + float: left; + margin-right: 15px; + } + } + + + // Title + .nav-title { + display: inline-block; + font-size: 32px; + padding: 28px 0; + } + + + // Navbar Links + ul { + margin: 0; + + li { + transition: background-color .3s; + float: left; + padding: 0; + + &.active { + background-color: rgba(0,0,0,.1); + } + } + a { + transition: background-color .3s; + font-size: $navbar-font-size; + color: $navbar-font-color; + display: block; + padding: 0 15px; + cursor: pointer; + + &.btn, &.btn-large, &.btn-flat, &.btn-floating { + margin-top: -2px; + margin-left: 15px; + margin-right: 15px; + + & > .material-icons { + height: inherit; + line-height: inherit; + } + } + + &:hover { + background-color: rgba(0,0,0,.1); + } + } + + &.left { + float: left; + } + } + + // Navbar Search Form + form { + height: 100%; + } + + .input-field { + margin: 0; + height: 100%; + + input { + height: 100%; + font-size: 1.2rem; + border: none; + padding-left: 2rem; + + &:focus, &[type=text]:valid, &[type=password]:valid, + &[type=email]:valid, &[type=url]:valid, &[type=date]:valid { + border: none; + box-shadow: none; + } + } + + label { + top: 0; + left: 0; + + i { + color: rgba(255,255,255,.7); + transition: color .3s; + } + &.active i { color: $navbar-font-color; } + } + } +} + +// Fixed Navbar +.navbar-fixed { + position: relative; + height: $navbar-height-mobile; + z-index: 997; + + nav { + position: fixed; + } +} +@media #{$medium-and-up} { + nav.nav-extended .nav-wrapper { + min-height: $navbar-height; + } + nav, nav .nav-wrapper i, nav a.sidenav-trigger, nav a.sidenav-trigger i { + height: $navbar-height; + line-height: $navbar-line-height; + } + .navbar-fixed { + height: $navbar-height; + } +} diff --git a/static/sass/components/_normalize.scss b/static/sass/components/_normalize.scss new file mode 100644 index 00000000..fa4e73dd --- /dev/null +++ b/static/sass/components/_normalize.scss @@ -0,0 +1,447 @@ +/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */ + +/* Document + ========================================================================== */ + +/** + * 1. Correct the line height in all browsers. + * 2. Prevent adjustments of font size after orientation changes in + * IE on Windows Phone and in iOS. + */ + +html { + line-height: 1.15; /* 1 */ + -ms-text-size-adjust: 100%; /* 2 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/* Sections + ========================================================================== */ + +/** + * Remove the margin in all browsers (opinionated). + */ + +body { + margin: 0; +} + +/** + * Add the correct display in IE 9-. + */ + +article, +aside, +footer, +header, +nav, +section { + display: block; +} + +/** + * Correct the font size and margin on `h1` elements within `section` and + * `article` contexts in Chrome, Firefox, and Safari. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/* Grouping content + ========================================================================== */ + +/** + * Add the correct display in IE 9-. + * 1. Add the correct display in IE. + */ + +figcaption, +figure, +main { /* 1 */ + display: block; +} + +/** + * Add the correct margin in IE 8. + */ + +figure { + margin: 1em 40px; +} + +/** + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. + */ + +hr { + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +pre { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/* Text-level semantics + ========================================================================== */ + +/** + * 1. Remove the gray background on active links in IE 10. + * 2. Remove gaps in links underline in iOS 8+ and Safari 8+. + */ + +a { + background-color: transparent; /* 1 */ + -webkit-text-decoration-skip: objects; /* 2 */ +} + +/** + * 1. Remove the bottom border in Chrome 57- and Firefox 39-. + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + */ + +abbr[title] { + border-bottom: none; /* 1 */ + text-decoration: underline; /* 2 */ + text-decoration: underline dotted; /* 2 */ +} + +/** + * Prevent the duplicate application of `bolder` by the next rule in Safari 6. + */ + +b, +strong { + font-weight: inherit; +} + +/** + * Add the correct font weight in Chrome, Edge, and Safari. + */ + +b, +strong { + font-weight: bolder; +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +code, +kbd, +samp { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/** + * Add the correct font style in Android 4.3-. + */ + +dfn { + font-style: italic; +} + +/** + * Add the correct background and color in IE 9-. + */ + +mark { + background-color: #ff0; + color: #000; +} + +/** + * Add the correct font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` elements from affecting the line height in + * all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Add the correct display in IE 9-. + */ + +audio, +video { + display: inline-block; +} + +/** + * Add the correct display in iOS 4-7. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Remove the border on images inside links in IE 10-. + */ + +img { + border-style: none; +} + +/** + * Hide the overflow in IE. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* Forms + ========================================================================== */ + +/** + * 1. Change the font styles in all browsers (opinionated). + * 2. Remove the margin in Firefox and Safari. + */ + +button, +input, +optgroup, +select, +textarea { + font-family: sans-serif; /* 1 */ + font-size: 100%; /* 1 */ + line-height: 1.15; /* 1 */ + margin: 0; /* 2 */ +} + +/** + * Show the overflow in IE. + * 1. Show the overflow in Edge. + */ + +button, +input { /* 1 */ + overflow: visible; +} + +/** + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. + */ + +button, +select { /* 1 */ + text-transform: none; +} + +/** + * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` + * controls in Android 4. + * 2. Correct the inability to style clickable types in iOS and Safari. + */ + +button, +html [type="button"], /* 1 */ +[type="reset"], +[type="submit"] { + -webkit-appearance: button; /* 2 */ +} + +/** + * Remove the inner border and padding in Firefox. + */ + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; +} + +/** + * Restore the focus styles unset by the previous rule. + */ + +button:-moz-focusring, +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; +} + +/** + * Correct the padding in Firefox. + */ + +fieldset { + padding: 0.35em 0.75em 0.625em; +} + +/** + * 1. Correct the text wrapping in Edge and IE. + * 2. Correct the color inheritance from `fieldset` elements in IE. + * 3. Remove the padding so developers are not caught out when they zero out + * `fieldset` elements in all browsers. + */ + +legend { + box-sizing: border-box; /* 1 */ + color: inherit; /* 2 */ + display: table; /* 1 */ + max-width: 100%; /* 1 */ + padding: 0; /* 3 */ + white-space: normal; /* 1 */ +} + +/** + * 1. Add the correct display in IE 9-. + * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ + +progress { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ +} + +/** + * Remove the default vertical scrollbar in IE. + */ + +textarea { + overflow: auto; +} + +/** + * 1. Add the correct box sizing in IE 10-. + * 2. Remove the padding in IE 10-. + */ + +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. + */ + +[type="search"] { + -webkit-appearance: textfield; /* 1 */ + outline-offset: -2px; /* 2 */ +} + +/** + * Remove the inner padding and cancel buttons in Chrome and Safari on macOS. + */ + +[type="search"]::-webkit-search-cancel-button, +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. + */ + +::-webkit-file-upload-button { + -webkit-appearance: button; /* 1 */ + font: inherit; /* 2 */ +} + +/* Interactive + ========================================================================== */ + +/* + * Add the correct display in IE 9-. + * 1. Add the correct display in Edge, IE, and Firefox. + */ + +details, /* 1 */ +menu { + display: block; +} + +/* + * Add the correct display in all browsers. + */ + +summary { + display: list-item; +} + +/* Scripting + ========================================================================== */ + +/** + * Add the correct display in IE 9-. + */ + +canvas { + display: inline-block; +} + +/** + * Add the correct display in IE. + */ + +template { + display: none; +} + +/* Hidden + ========================================================================== */ + +/** + * Add the correct display in IE 10-. + */ + +[hidden] { + display: none; +} diff --git a/static/sass/components/_preloader.scss b/static/sass/components/_preloader.scss new file mode 100644 index 00000000..cfe29939 --- /dev/null +++ b/static/sass/components/_preloader.scss @@ -0,0 +1,334 @@ +/* + @license + Copyright (c) 2014 The Polymer Project Authors. All rights reserved. + This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt + The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt + The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt + Code distributed by Google as part of the polymer project is also + subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt + */ + +/**************************/ +/* STYLES FOR THE SPINNER */ +/**************************/ + +/* + * Constants: + * STROKEWIDTH = 3px + * ARCSIZE = 270 degrees (amount of circle the arc takes up) + * ARCTIME = 1333ms (time it takes to expand and contract arc) + * ARCSTARTROT = 216 degrees (how much the start location of the arc + * should rotate each time, 216 gives us a + * 5 pointed star shape (it's 360/5 * 3). + * For a 7 pointed star, we might do + * 360/7 * 3 = 154.286) + * CONTAINERWIDTH = 28px + * SHRINK_TIME = 400ms + */ + + +.preloader-wrapper { + display: inline-block; + position: relative; + width: 50px; + height: 50px; + + &.small { + width: 36px; + height: 36px; + } + + &.big { + width: 64px; + height: 64px; + } + + &.active { + /* duration: 360 * ARCTIME / (ARCSTARTROT + (360-ARCSIZE)) */ + -webkit-animation: container-rotate 1568ms linear infinite; + animation: container-rotate 1568ms linear infinite; + } +} + +@-webkit-keyframes container-rotate { + to { -webkit-transform: rotate(360deg) } +} + +@keyframes container-rotate { + to { transform: rotate(360deg) } +} + +.spinner-layer { + position: absolute; + width: 100%; + height: 100%; + opacity: 0; + border-color: $spinner-default-color; +} + +.spinner-blue, +.spinner-blue-only { + border-color: #4285f4; +} + +.spinner-red, +.spinner-red-only { + border-color: #db4437; +} + +.spinner-yellow, +.spinner-yellow-only { + border-color: #f4b400; +} + +.spinner-green, +.spinner-green-only { + border-color: #0f9d58; +} + +/** + * IMPORTANT NOTE ABOUT CSS ANIMATION PROPERTIES (keanulee): + * + * iOS Safari (tested on iOS 8.1) does not handle animation-delay very well - it doesn't + * guarantee that the animation will start _exactly_ after that value. So we avoid using + * animation-delay and instead set custom keyframes for each color (as redundant as it + * seems). + * + * We write out each animation in full (instead of separating animation-name, + * animation-duration, etc.) because under the polyfill, Safari does not recognize those + * specific properties properly, treats them as -webkit-animation, and overrides the + * other animation rules. See https://github.com/Polymer/platform/issues/53. + */ +.active .spinner-layer.spinner-blue { + /* durations: 4 * ARCTIME */ + -webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, blue-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; + animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, blue-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; +} + +.active .spinner-layer.spinner-red { + /* durations: 4 * ARCTIME */ + -webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, red-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; + animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, red-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; +} + +.active .spinner-layer.spinner-yellow { + /* durations: 4 * ARCTIME */ + -webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, yellow-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; + animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, yellow-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; +} + +.active .spinner-layer.spinner-green { + /* durations: 4 * ARCTIME */ + -webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, green-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; + animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, green-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; +} + +.active .spinner-layer, +.active .spinner-layer.spinner-blue-only, +.active .spinner-layer.spinner-red-only, +.active .spinner-layer.spinner-yellow-only, +.active .spinner-layer.spinner-green-only { + /* durations: 4 * ARCTIME */ + opacity: 1; + -webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; + animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; +} + +@-webkit-keyframes fill-unfill-rotate { + 12.5% { -webkit-transform: rotate(135deg); } /* 0.5 * ARCSIZE */ + 25% { -webkit-transform: rotate(270deg); } /* 1 * ARCSIZE */ + 37.5% { -webkit-transform: rotate(405deg); } /* 1.5 * ARCSIZE */ + 50% { -webkit-transform: rotate(540deg); } /* 2 * ARCSIZE */ + 62.5% { -webkit-transform: rotate(675deg); } /* 2.5 * ARCSIZE */ + 75% { -webkit-transform: rotate(810deg); } /* 3 * ARCSIZE */ + 87.5% { -webkit-transform: rotate(945deg); } /* 3.5 * ARCSIZE */ + to { -webkit-transform: rotate(1080deg); } /* 4 * ARCSIZE */ +} + +@keyframes fill-unfill-rotate { + 12.5% { transform: rotate(135deg); } /* 0.5 * ARCSIZE */ + 25% { transform: rotate(270deg); } /* 1 * ARCSIZE */ + 37.5% { transform: rotate(405deg); } /* 1.5 * ARCSIZE */ + 50% { transform: rotate(540deg); } /* 2 * ARCSIZE */ + 62.5% { transform: rotate(675deg); } /* 2.5 * ARCSIZE */ + 75% { transform: rotate(810deg); } /* 3 * ARCSIZE */ + 87.5% { transform: rotate(945deg); } /* 3.5 * ARCSIZE */ + to { transform: rotate(1080deg); } /* 4 * ARCSIZE */ +} + +@-webkit-keyframes blue-fade-in-out { + from { opacity: 1; } + 25% { opacity: 1; } + 26% { opacity: 0; } + 89% { opacity: 0; } + 90% { opacity: 1; } + 100% { opacity: 1; } +} + +@keyframes blue-fade-in-out { + from { opacity: 1; } + 25% { opacity: 1; } + 26% { opacity: 0; } + 89% { opacity: 0; } + 90% { opacity: 1; } + 100% { opacity: 1; } +} + +@-webkit-keyframes red-fade-in-out { + from { opacity: 0; } + 15% { opacity: 0; } + 25% { opacity: 1; } + 50% { opacity: 1; } + 51% { opacity: 0; } +} + +@keyframes red-fade-in-out { + from { opacity: 0; } + 15% { opacity: 0; } + 25% { opacity: 1; } + 50% { opacity: 1; } + 51% { opacity: 0; } +} + +@-webkit-keyframes yellow-fade-in-out { + from { opacity: 0; } + 40% { opacity: 0; } + 50% { opacity: 1; } + 75% { opacity: 1; } + 76% { opacity: 0; } +} + +@keyframes yellow-fade-in-out { + from { opacity: 0; } + 40% { opacity: 0; } + 50% { opacity: 1; } + 75% { opacity: 1; } + 76% { opacity: 0; } +} + +@-webkit-keyframes green-fade-in-out { + from { opacity: 0; } + 65% { opacity: 0; } + 75% { opacity: 1; } + 90% { opacity: 1; } + 100% { opacity: 0; } +} + +@keyframes green-fade-in-out { + from { opacity: 0; } + 65% { opacity: 0; } + 75% { opacity: 1; } + 90% { opacity: 1; } + 100% { opacity: 0; } +} + +/** + * Patch the gap that appear between the two adjacent div.circle-clipper while the + * spinner is rotating (appears on Chrome 38, Safari 7.1, and IE 11). + */ +.gap-patch { + position: absolute; + top: 0; + left: 45%; + width: 10%; + height: 100%; + overflow: hidden; + border-color: inherit; +} + +.gap-patch .circle { + width: 1000%; + left: -450%; +} + +.circle-clipper { + display: inline-block; + position: relative; + width: 50%; + height: 100%; + overflow: hidden; + border-color: inherit; + + .circle { + width: 200%; + height: 100%; + border-width: 3px; /* STROKEWIDTH */ + border-style: solid; + border-color: inherit; + border-bottom-color: transparent !important; + border-radius: 50%; + -webkit-animation: none; + animation: none; + position: absolute; + top: 0; + right: 0; + bottom: 0; + } + + &.left .circle { + left: 0; + border-right-color: transparent !important; + -webkit-transform: rotate(129deg); + transform: rotate(129deg); + } + &.right .circle { + left: -100%; + border-left-color: transparent !important; + -webkit-transform: rotate(-129deg); + transform: rotate(-129deg); + } +} + + + +.active .circle-clipper.left .circle { + /* duration: ARCTIME */ + -webkit-animation: left-spin 1333ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; + animation: left-spin 1333ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; +} + +.active .circle-clipper.right .circle { + /* duration: ARCTIME */ + -webkit-animation: right-spin 1333ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; + animation: right-spin 1333ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; +} + +@-webkit-keyframes left-spin { + from { -webkit-transform: rotate(130deg); } + 50% { -webkit-transform: rotate(-5deg); } + to { -webkit-transform: rotate(130deg); } +} + +@keyframes left-spin { + from { transform: rotate(130deg); } + 50% { transform: rotate(-5deg); } + to { transform: rotate(130deg); } +} + +@-webkit-keyframes right-spin { + from { -webkit-transform: rotate(-130deg); } + 50% { -webkit-transform: rotate(5deg); } + to { -webkit-transform: rotate(-130deg); } +} + +@keyframes right-spin { + from { transform: rotate(-130deg); } + 50% { transform: rotate(5deg); } + to { transform: rotate(-130deg); } +} + +#spinnerContainer.cooldown { + /* duration: SHRINK_TIME */ + -webkit-animation: container-rotate 1568ms linear infinite, fade-out 400ms cubic-bezier(0.4, 0.0, 0.2, 1); + animation: container-rotate 1568ms linear infinite, fade-out 400ms cubic-bezier(0.4, 0.0, 0.2, 1); +} + +@-webkit-keyframes fade-out { + from { opacity: 1; } + to { opacity: 0; } +} + +@keyframes fade-out { + from { opacity: 1; } + to { opacity: 0; } +} diff --git a/static/sass/components/_pulse.scss b/static/sass/components/_pulse.scss new file mode 100644 index 00000000..a690f367 --- /dev/null +++ b/static/sass/components/_pulse.scss @@ -0,0 +1,34 @@ +.pulse { + &::before { + content: ''; + display: block; + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + background-color: inherit; + border-radius: inherit; + transition: opacity .3s, transform .3s; + animation: pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite; + z-index: -1; + } + + overflow: visible; + position: relative; +} + +@keyframes pulse-animation { + 0% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0; + transform: scale(1.5); + } + 100% { + opacity: 0; + transform: scale(1.5); + } +} diff --git a/static/sass/components/_sidenav.scss b/static/sass/components/_sidenav.scss new file mode 100644 index 00000000..98a71d8a --- /dev/null +++ b/static/sass/components/_sidenav.scss @@ -0,0 +1,216 @@ +.sidenav { + position: fixed; + width: $sidenav-width; + left: 0; + top: 0; + margin: 0; + transform: translateX(-100%); + height: 100%; + height: calc(100% + 60px); + height: -moz-calc(100%); //Temporary Firefox Fix + padding-bottom: 60px; + background-color: $sidenav-bg-color; + z-index: 999; + overflow-y: auto; + will-change: transform; + backface-visibility: hidden; + transform: translateX(-105%); + + @extend .z-depth-1; + + // Right Align + &.right-aligned { + right: 0; + transform: translateX(105%); + left: auto; + transform: translateX(100%); + } + + .collapsible { + margin: 0; + } + + + li { + float: none; + line-height: $sidenav-line-height; + + &.active { background-color: rgba(0,0,0,.05); } + } + + li > a { + color: $sidenav-font-color; + display: block; + font-size: $sidenav-font-size; + font-weight: 500; + height: $sidenav-item-height; + line-height: $sidenav-line-height; + padding: 0 ($sidenav-padding * 2); + + &:hover { background-color: rgba(0,0,0,.05);} + + &.btn, &.btn-large, &.btn-flat, &.btn-floating { + margin: 10px 15px; + } + + &.btn, + &.btn-large, + &.btn-floating { color: $button-raised-color; } + &.btn-flat { color: $button-flat-color; } + + &.btn:hover, + &.btn-large:hover { background-color: lighten($button-raised-background, 5%); } + &.btn-floating:hover { background-color: $button-raised-background; } + + & > i, + & > [class^="mdi-"], li > a > [class*="mdi-"], + & > i.material-icons { + float: left; + height: $sidenav-item-height; + line-height: $sidenav-line-height; + margin: 0 ($sidenav-padding * 2) 0 0; + width: $sidenav-item-height / 2; + color: rgba(0,0,0,.54); + } + } + + + .divider { + margin: ($sidenav-padding / 2) 0 0 0; + } + + .subheader { + &:hover { + background-color: transparent; + } + + cursor: initial; + pointer-events: none; + color: rgba(0,0,0,.54); + font-size: $sidenav-font-size; + font-weight: 500; + line-height: $sidenav-line-height; + } + + .user-view { + position: relative; + padding: ($sidenav-padding * 2) ($sidenav-padding * 2) 0; + margin-bottom: $sidenav-padding / 2; + + & > a { + &:hover { background-color: transparent; } + height: auto; + padding: 0; + } + + .background { + overflow: hidden; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: -1; + } + + .circle, .name, .email { + display: block; + } + + .circle { + height: 64px; + width: 64px; + } + + .name, + .email { + font-size: $sidenav-font-size; + line-height: $sidenav-line-height / 2; + } + + .name { + margin-top: 16px; + font-weight: 500; + } + + .email { + padding-bottom: 16px; + font-weight: 400; + } + } +} + + +// Touch interaction +.drag-target { + // Right Align + &.right-aligned { + right: 0; + } + + height: 100%; + width: 10px; + position: fixed; + top: 0; + z-index: 998; +} + + +// Fixed Sidenav shown +.sidenav.sidenav-fixed { + // Right Align + &.right-aligned { + right: 0; + left: auto; + } + + left: 0; + transform: translateX(0); + position: fixed; +} + +// Fixed Sidenav hide on smaller +@media #{$medium-and-down} { + .sidenav { + &.sidenav-fixed { + transform: translateX(-105%); + + &.right-aligned { + transform: translateX(105%); + } + } + + > a { + padding: 0 $sidenav-padding; + } + + .user-view { + padding: $sidenav-padding $sidenav-padding 0; + } + } +} + + +.sidenav .collapsible-body > ul:not(.collapsible) > li.active, +.sidenav.sidenav-fixed .collapsible-body > ul:not(.collapsible) > li.active { + background-color: $primary-color; + a { + color: $sidenav-bg-color; + } +} +.sidenav .collapsible-body { + padding: 0; +} + + +.sidenav-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + opacity: 0; + height: 120vh; + background-color: rgba(0,0,0,.5); + z-index: 997; + display: none; +} diff --git a/static/sass/components/_slider.scss b/static/sass/components/_slider.scss new file mode 100644 index 00000000..5d7c27ed --- /dev/null +++ b/static/sass/components/_slider.scss @@ -0,0 +1,92 @@ +.slider { + position: relative; + height: 400px; + width: 100%; + + // Fullscreen slider + &.fullscreen { + height: 100%; + width: 100%; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + + ul.slides { + height: 100%; + } + + ul.indicators { + z-index: 2; + bottom: 30px; + } + } + + .slides { + background-color: $slider-bg-color; + margin: 0; + height: 400px; + + li { + opacity: 0; + position: absolute; + top: 0; + left: 0; + z-index: 1; + width: 100%; + height: inherit; + overflow: hidden; + + img { + height: 100%; + width: 100%; + background-size: cover; + background-position: center; + } + + .caption { + color: #fff; + position: absolute; + top: 15%; + left: 15%; + width: 70%; + opacity: 0; + + p { color: $slider-bg-color-light; } + } + + &.active { + z-index: 2; + } + } + } + + + .indicators { + position: absolute; + text-align: center; + left: 0; + right: 0; + bottom: 0; + margin: 0; + + .indicator-item { + display: inline-block; + position: relative; + cursor: pointer; + height: 16px; + width: 16px; + margin: 0 12px; + background-color: $slider-bg-color-light; + + transition: background-color .3s; + border-radius: 50%; + + &.active { + background-color: $slider-indicator-color; + } + } + } + +} \ No newline at end of file diff --git a/static/sass/components/_table_of_contents.scss b/static/sass/components/_table_of_contents.scss new file mode 100644 index 00000000..638009d9 --- /dev/null +++ b/static/sass/components/_table_of_contents.scss @@ -0,0 +1,33 @@ +/*************** + Nav List +***************/ +.table-of-contents { + &.fixed { + position: fixed; + } + + li { + padding: 2px 0; + } + a { + display: inline-block; + font-weight: 300; + color: #757575; + padding-left: 16px; + height: 1.5rem; + line-height: 1.5rem; + letter-spacing: .4; + display: inline-block; + + &:hover { + color: lighten(#757575, 20%); + padding-left: 15px; + border-left: 1px solid $primary-color; + } + &.active { + font-weight: 500; + padding-left: 14px; + border-left: 2px solid $primary-color; + } + } +} diff --git a/static/sass/components/_tabs.scss b/static/sass/components/_tabs.scss new file mode 100644 index 00000000..072d4b6c --- /dev/null +++ b/static/sass/components/_tabs.scss @@ -0,0 +1,99 @@ +.tabs { + &.tabs-transparent { + background-color: transparent; + + .tab a, + .tab.disabled a, + .tab.disabled a:hover { + color: rgba(255,255,255,0.7); + } + + .tab a:hover, + .tab a.active { + color: #fff; + } + + .indicator { + background-color: #fff; + } + } + + &.tabs-fixed-width { + display: flex; + + .tab { + flex-grow: 1; + } + } + + position: relative; + overflow-x: auto; + overflow-y: hidden; + height: 48px; + width: 100%; + background-color: $tabs-bg-color; + margin: 0 auto; + white-space: nowrap; + + .tab { + display: inline-block; + text-align: center; + line-height: 48px; + height: 48px; + padding: 0; + margin: 0; + text-transform: uppercase; + + a { + &:focus, + &:focus.active { + background-color: transparentize($tabs-underline-color, .8); + outline: none; + } + + &:hover, + &.active { + background-color: transparent; + color: $tabs-text-color; + } + + color: rgba($tabs-text-color, .7); + display: block; + width: 100%; + height: 100%; + padding: 0 24px; + font-size: 14px; + text-overflow: ellipsis; + overflow: hidden; + transition: color .28s ease, background-color .28s ease; + } + + &.disabled a, + &.disabled a:hover { + color: rgba($tabs-text-color, .4); + cursor: default; + } + } + .indicator { + position: absolute; + bottom: 0; + height: 2px; + background-color: $tabs-underline-color; + will-change: left, right; + } +} + +// Fixed Sidenav hide on smaller +@media #{$medium-and-down} { + .tabs { + display: flex; + + .tab { + flex-grow: 1; + + a { + padding: 0 12px; + } + } + } +} diff --git a/static/sass/components/_tapTarget.scss b/static/sass/components/_tapTarget.scss new file mode 100644 index 00000000..49aecd56 --- /dev/null +++ b/static/sass/components/_tapTarget.scss @@ -0,0 +1,103 @@ +.tap-target-wrapper { + width: 800px; + height: 800px; + position: fixed; + z-index: 1000; + visibility: hidden; + transition: visibility 0s .3s; +} + +.tap-target-wrapper.open { + visibility: visible; + transition: visibility 0s; + + .tap-target { + transform: scale(1); + opacity: .95; + transition: + transform .3s cubic-bezier(.42,0,.58,1), + opacity .3s cubic-bezier(.42,0,.58,1); + } + + .tap-target-wave::before { + transform: scale(1); + } + .tap-target-wave::after { + visibility: visible; + animation: pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite; + transition: + opacity .3s, + transform .3s, + visibility 0s 1s; + } +} + +.tap-target { + position: absolute; + font-size: 1rem; + border-radius: 50%; + background-color: $primary-color; + box-shadow: 0 20px 20px 0 rgba(0,0,0,0.14), 0 10px 50px 0 rgba(0,0,0,0.12), 0 30px 10px -20px rgba(0,0,0,0.2); + width: 100%; + height: 100%; + opacity: 0; + transform: scale(0); + transition: + transform .3s cubic-bezier(.42,0,.58,1), + opacity .3s cubic-bezier(.42,0,.58,1); +} + +.tap-target-content { + position: relative; + display: table-cell; +} + +.tap-target-wave { + &::before, + &::after { + content: ''; + display: block; + position: absolute; + width: 100%; + height: 100%; + border-radius: 50%; + background-color: #ffffff; + } + &::before { + transform: scale(0); + transition: transform .3s; + } + &::after { + visibility: hidden; + transition: + opacity .3s, + transform .3s, + visibility 0s; + z-index: -1; + } + + position: absolute; + border-radius: 50%; + z-index: 10001; +} + +.tap-target-origin { + &:not(.btn), + &:not(.btn):hover { + background: none; + } + + top: 50%; + left: 50%; + transform: translate(-50%,-50%); + + z-index: 10002; + position: absolute !important; +} + +@media only screen and (max-width: 600px) { + .tap-target, .tap-target-wrapper { + width: 600px; + height: 600px; + } +} diff --git a/static/sass/components/_timepicker.scss b/static/sass/components/_timepicker.scss new file mode 100644 index 00000000..fa602fbb --- /dev/null +++ b/static/sass/components/_timepicker.scss @@ -0,0 +1,183 @@ +/* Timepicker Containers */ +.timepicker-modal { + max-width: 325px; + max-height: none; +} + +.timepicker-container.modal-content { + display: flex; + flex-direction: column; + padding: 0; +} + +.text-primary { + color: rgba(255, 255, 255, 1); +} + + +/* Clock Digital Display */ +.timepicker-digital-display { + flex: 1 auto; + background-color: $secondary-color; + padding: 10px; + font-weight: 300; +} + +.timepicker-text-container { + font-size: 4rem; + font-weight: bold; + text-align: center; + color: rgba(255, 255, 255, 0.6); + font-weight: 400; + position: relative; + user-select: none; +} + +.timepicker-span-hours, +.timepicker-span-minutes, +.timepicker-span-am-pm div { + cursor: pointer; +} + +.timepicker-span-hours { + margin-right: 3px; +} + +.timepicker-span-minutes { + margin-left: 3px; +} + +.timepicker-display-am-pm { + font-size: 1.3rem; + position: absolute; + right: 1rem; + bottom: 1rem; + font-weight: 400; +} + + +/* Analog Clock Display */ +.timepicker-analog-display { + flex: 2.5 auto; +} + +.timepicker-plate { + background-color: $timepicker-clock-plate-bg; + border-radius: 50%; + width: 270px; + height: 270px; + overflow: visible; + position: relative; + margin: auto; + margin-top: 25px; + margin-bottom: 5px; + user-select: none; +} + +.timepicker-canvas, +.timepicker-dial { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; +} +.timepicker-minutes { + visibility: hidden; +} + +.timepicker-tick { + border-radius: 50%; + color: $timepicker-clock-color; + line-height: 40px; + text-align: center; + width: 40px; + height: 40px; + position: absolute; + cursor: pointer; + font-size: 15px; +} + +.timepicker-tick.active, +.timepicker-tick:hover { + background-color: transparentize($secondary-color, .75); +} +.timepicker-dial { + transition: transform 350ms, opacity 350ms; +} +.timepicker-dial-out { + &.timepicker-hours { + transform: scale(1.1, 1.1); + } + + &.timepicker-minutes { + transform: scale(.8, .8); + } + + opacity: 0; +} +.timepicker-canvas { + transition: opacity 175ms; + + line { + stroke: $secondary-color; + stroke-width: 4; + stroke-linecap: round; + } +} +.timepicker-canvas-out { + opacity: 0.25; +} +.timepicker-canvas-bearing { + stroke: none; + fill: $secondary-color; +} +.timepicker-canvas-bg { + stroke: none; + fill: $secondary-color; +} + + +/* Footer */ +.timepicker-footer { + margin: 0 auto; + padding: 5px 1rem; + display: flex; + justify-content: space-between; +} + +.timepicker-clear { + color: $error-color; +} + +.timepicker-close { + color: $secondary-color; +} + +.timepicker-clear, +.timepicker-close { + padding: 0 20px; +} + +/* Media Queries */ +@media #{$medium-and-up} { + .timepicker-modal { + max-width: 600px; + } + + .timepicker-container.modal-content { + flex-direction: row; + } + + .timepicker-text-container { + top: 32%; + } + + .timepicker-display-am-pm { + position: relative; + right: auto; + bottom: auto; + text-align: center; + margin-top: 1.2rem; + } +} diff --git a/static/sass/components/_toast.scss b/static/sass/components/_toast.scss new file mode 100644 index 00000000..412400ff --- /dev/null +++ b/static/sass/components/_toast.scss @@ -0,0 +1,58 @@ +#toast-container { + display:block; + position: fixed; + z-index: 10000; + + @media #{$small-and-down} { + min-width: 100%; + bottom: 0%; + } + @media #{$medium-only} { + left: 5%; + bottom: 7%; + max-width: 90%; + } + @media #{$large-and-up} { + top: 10%; + right: 7%; + max-width: 86%; + } +} + +.toast { + @extend .z-depth-1; + border-radius: 2px; + top: 35px; + width: auto; + margin-top: 10px; + position: relative; + max-width:100%; + height: auto; + min-height: $toast-height; + line-height: 1.5em; + background-color: $toast-color; + padding: 10px 25px; + font-size: 1.1rem; + font-weight: 300; + color: $toast-text-color; + display: flex; + align-items: center; + justify-content: space-between; + cursor: default; + + .toast-action { + color: $toast-action-color; + font-weight: 500; + margin-right: -25px; + margin-left: 3rem; + } + + &.rounded{ + border-radius: 24px; + } + + @media #{$small-and-down} { + width: 100%; + border-radius: 0; + } +} diff --git a/static/sass/components/_tooltip.scss b/static/sass/components/_tooltip.scss new file mode 100644 index 00000000..5ec4299b --- /dev/null +++ b/static/sass/components/_tooltip.scss @@ -0,0 +1,32 @@ +.material-tooltip { + padding: 10px 8px; + font-size: 1rem; + z-index: 2000; + background-color: transparent; + border-radius: 2px; + color: #fff; + min-height: 36px; + line-height: 120%; + opacity: 0; + position: absolute; + text-align: center; + max-width: calc(100% - 4px); + overflow: hidden; + left: 0; + top: 0; + pointer-events: none; + visibility: hidden; + background-color: #323232; +} + +.backdrop { + position: absolute; + opacity: 0; + height: 7px; + width: 14px; + border-radius: 0 0 50% 50%; + background-color: #323232; + z-index: -1; + transform-origin: 50% 0%; + visibility: hidden; +} diff --git a/static/sass/components/_transitions.scss b/static/sass/components/_transitions.scss new file mode 100644 index 00000000..cb9f60db --- /dev/null +++ b/static/sass/components/_transitions.scss @@ -0,0 +1,13 @@ +// Scale transition +.scale-transition { + &.scale-out { + transform: scale(0); + transition: transform .2s !important; + } + + &.scale-in { + transform: scale(1); + } + + transition: transform .3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important; +} \ No newline at end of file diff --git a/static/sass/components/_typography.scss b/static/sass/components/_typography.scss new file mode 100644 index 00000000..b9b93b3e --- /dev/null +++ b/static/sass/components/_typography.scss @@ -0,0 +1,60 @@ + +a { + text-decoration: none; +} + +html{ + line-height: 1.5; + + @media only screen and (min-width: 0) { + font-size: 14px; + } + + @media only screen and (min-width: $medium-screen) { + font-size: 14.5px; + } + + @media only screen and (min-width: $large-screen) { + font-size: 15px; + } + + font-family: $font-stack; + font-weight: normal; + color: $off-black; +} +h1, h2, h3, h4, h5, h6 { + font-weight: 400; + line-height: 1.3; +} + +// Header Styles +h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { font-weight: inherit; } +h1 { font-size: $h1-fontsize; line-height: 110%; margin: ($h1-fontsize / 1.5) 0 ($h1-fontsize / 2.5) 0;} +h2 { font-size: $h2-fontsize; line-height: 110%; margin: ($h2-fontsize / 1.5) 0 ($h2-fontsize / 2.5) 0;} +h3 { font-size: $h3-fontsize; line-height: 110%; margin: ($h3-fontsize / 1.5) 0 ($h3-fontsize / 2.5) 0;} +h4 { font-size: $h4-fontsize; line-height: 110%; margin: ($h4-fontsize / 1.5) 0 ($h4-fontsize / 2.5) 0;} +h5 { font-size: $h5-fontsize; line-height: 110%; margin: ($h5-fontsize / 1.5) 0 ($h5-fontsize / 2.5) 0;} +h6 { font-size: $h6-fontsize; line-height: 110%; margin: ($h6-fontsize / 1.5) 0 ($h6-fontsize / 2.5) 0;} + +// Text Styles +em { font-style: italic; } +strong { font-weight: 500; } +small { font-size: 75%; } +.light { font-weight: 300; } +.thin { font-weight: 200; } + + +.flow-text{ + $i: 0; + @while $i <= $intervals { + @media only screen and (min-width : 360 + ($i * $interval-size)) { + font-size: 1.2rem * (1 + (.02 * $i)); + } + $i: $i + 1; + } + + // Handle below 360px screen + @media only screen and (max-width: 360px) { + font-size: 1.2rem; + } +} diff --git a/static/sass/components/_variables.scss b/static/sass/components/_variables.scss new file mode 100644 index 00000000..4c59c127 --- /dev/null +++ b/static/sass/components/_variables.scss @@ -0,0 +1,349 @@ +// ========================================================================== +// Materialize variables +// ========================================================================== +// +// Table of Contents: +// +// 1. Colors +// 2. Badges +// 3. Buttons +// 4. Cards +// 5. Carousel +// 6. Collapsible +// 7. Chips +// 8. Date + Time Picker +// 9. Dropdown +// 10. Forms +// 11. Global +// 12. Grid +// 13. Navigation Bar +// 14. Side Navigation +// 15. Photo Slider +// 16. Spinners | Loaders +// 17. Tabs +// 18. Tables +// 19. Toasts +// 20. Typography +// 21. Footer +// 22. Flow Text +// 23. Collections +// 24. Progress Bar + + + +// 1. Colors +// ========================================================================== + +$primary-color: color("materialize-red", "lighten-2") !default; +$primary-color-light: lighten($primary-color, 15%) !default; +$primary-color-dark: darken($primary-color, 15%) !default; + +$secondary-color: color("teal", "lighten-1") !default; +$success-color: color("green", "base") !default; +$error-color: color("red", "base") !default; +$link-color: color("light-blue", "darken-1") !default; + + +// 2. Badges +// ========================================================================== + +$badge-bg-color: $secondary-color !default; +$badge-height: 22px !default; + + +// 3. Buttons +// ========================================================================== + +// Shared styles +$button-border: none !default; +$button-background-focus: lighten($secondary-color, 4%) !default; +$button-font-size: 14px !default; +$button-icon-font-size: 1.3rem !default; +$button-height: 36px !default; +$button-padding: 0 16px !default; +$button-radius: 2px !default; + +// Disabled styles +$button-disabled-background: #DFDFDF !default; +$button-disabled-color: #9F9F9F !default; + +// Raised buttons +$button-raised-background: $secondary-color !default; +$button-raised-background-hover: lighten($button-raised-background, 5%) !default; +$button-raised-color: #fff !default; + +// Large buttons +$button-large-font-size: 15px !default; +$button-large-icon-font-size: 1.6rem !default; +$button-large-height: $button-height * 1.5 !default; +$button-floating-large-size: 56px !default; + +// Small buttons +$button-small-font-size: 13px !default; +$button-small-icon-font-size: 1.2rem !default; +$button-small-height: $button-height * .9 !default; +$button-floating-small-size: $button-height * .9 !default; + +// Flat buttons +$button-flat-color: #343434 !default; +$button-flat-disabled-color: lighten(#999, 10%) !default; + +// Floating buttons +$button-floating-background: $secondary-color !default; +$button-floating-background-hover: $button-floating-background !default; +$button-floating-color: #fff !default; +$button-floating-size: 40px !default; +$button-floating-radius: 50% !default; + + +// 4. Cards +// ========================================================================== + +$card-padding: 24px !default; +$card-bg-color: #fff !default; +$card-link-color: color("orange", "accent-2") !default; +$card-link-color-light: lighten($card-link-color, 20%) !default; + + +// 5. Carousel +// ========================================================================== + +$carousel-height: 400px !default; +$carousel-item-height: $carousel-height / 2 !default; +$carousel-item-width: $carousel-item-height !default; + + +// 6. Collapsible +// ========================================================================== + +$collapsible-height: 3rem !default; +$collapsible-line-height: $collapsible-height !default; +$collapsible-header-color: #fff !default; +$collapsible-border-color: #ddd !default; + + +// 7. Chips +// ========================================================================== + +$chip-bg-color: #e4e4e4 !default; +$chip-border-color: #9e9e9e !default; +$chip-selected-color: #26a69a !default; +$chip-margin: 5px !default; + + +// 8. Date + Time Picker +// ========================================================================== + +$datepicker-display-font-size: 2.8rem; +$datepicker-calendar-header-color: #999; +$datepicker-weekday-color: rgba(0, 0, 0, .87) !default; +$datepicker-weekday-bg: darken($secondary-color, 7%) !default; +$datepicker-date-bg: $secondary-color !default; +$datepicker-year: rgba(255, 255, 255, .7) !default; +$datepicker-focus: rgba(0,0,0, .05) !default; +$datepicker-selected: $secondary-color !default; +$datepicker-selected-outfocus: desaturate(lighten($secondary-color, 35%), 15%) !default; +$datepicker-day-focus: transparentize(desaturate($secondary-color, 5%), .75) !default; +$datepicker-disabled-day-color: rgba(0, 0, 0, .3) !default; + +$timepicker-clock-color: rgba(0, 0, 0, .87) !default; +$timepicker-clock-plate-bg: #eee !default; + + +// 9. Dropdown +// ========================================================================== + +$dropdown-bg-color: #fff !default; +$dropdown-hover-bg-color: #eee !default; +$dropdown-color: $secondary-color !default; +$dropdown-item-height: 50px !default; + + +// 10. Forms +// ========================================================================== + +// Text Inputs + Textarea +$input-height: 3rem !default; +$input-border-color: color("grey", "base") !default; +$input-border: 1px solid $input-border-color !default; +$input-background: #fff !default; +$input-error-color: $error-color !default; +$input-success-color: $success-color !default; +$input-focus-color: $secondary-color !default; +$input-font-size: 16px !default; +$input-margin-bottom: 8px; +$input-margin: 0 0 $input-margin-bottom 0 !default; +$input-padding: 0 !default; +$label-font-size: .8rem !default; +$input-disabled-color: rgba(0,0,0, .42) !default; +$input-disabled-solid-color: #949494 !default; +$input-disabled-border: 1px dotted $input-disabled-color !default; +$input-invalid-border: 1px solid $input-error-color !default; +$input-icon-size: 2rem; +$placeholder-text-color: lighten($input-border-color, 20%) !default; + +// Radio Buttons +$radio-fill-color: $secondary-color !default; +$radio-empty-color: #5a5a5a !default; +$radio-border: 2px solid $radio-fill-color !default; + +// Range +$range-height: 14px !default; +$range-width: 14px !default; +$track-height: 3px !default; + +// Select +$select-border: 1px solid #f2f2f2 !default; +$select-background: rgba(255, 255, 255, 0.90) !default; +$select-focus: 1px solid lighten($secondary-color, 47%) !default; +$select-option-hover: rgba(0,0,0,.08) !default; +$select-option-focus: rgba(0,0,0,.08) !default; +$select-option-selected: rgba(0,0,0,.03) !default; +$select-padding: 5px !default; +$select-radius: 2px !default; +$select-disabled-color: rgba(0,0,0,.3) !default; + +// Switches +$switch-bg-color: $secondary-color !default; +$switch-checked-lever-bg: desaturate(lighten($switch-bg-color, 25%), 25%) !default; +$switch-unchecked-bg: #F1F1F1 !default; +$switch-unchecked-lever-bg: rgba(0,0,0,.38) !default; +$switch-radius: 15px !default; + + +// 11. Global +// ========================================================================== + +// Media Query Ranges +$small-screen-up: 601px !default; +$medium-screen-up: 993px !default; +$large-screen-up: 1201px !default; +$small-screen: 600px !default; +$medium-screen: 992px !default; +$large-screen: 1200px !default; + +$medium-and-up: "only screen and (min-width : #{$small-screen-up})" !default; +$large-and-up: "only screen and (min-width : #{$medium-screen-up})" !default; +$extra-large-and-up: "only screen and (min-width : #{$large-screen-up})" !default; +$small-and-down: "only screen and (max-width : #{$small-screen})" !default; +$medium-and-down: "only screen and (max-width : #{$medium-screen})" !default; +$medium-only: "only screen and (min-width : #{$small-screen-up}) and (max-width : #{$medium-screen})" !default; + + +// 12. Grid +// ========================================================================== + +$num-cols: 12 !default; +$gutter-width: 1.5rem !default; +$element-top-margin: $gutter-width/3 !default; +$element-bottom-margin: ($gutter-width*2)/3 !default; + + +// 13. Navigation Bar +// ========================================================================== + +$navbar-height: 64px !default; +$navbar-line-height: $navbar-height !default; +$navbar-height-mobile: 56px !default; +$navbar-line-height-mobile: $navbar-height-mobile !default; +$navbar-font-size: 1rem !default; +$navbar-font-color: #fff !default; +$navbar-brand-font-size: 2.1rem !default; + +// 14. Side Navigation +// ========================================================================== + +$sidenav-width: 300px !default; +$sidenav-font-size: 14px !default; +$sidenav-font-color: rgba(0,0,0,.87) !default; +$sidenav-bg-color: #fff !default; +$sidenav-padding: 16px !default; +$sidenav-item-height: 48px !default; +$sidenav-line-height: $sidenav-item-height !default; + + +// 15. Photo Slider +// ========================================================================== + +$slider-bg-color: color('grey', 'base') !default; +$slider-bg-color-light: color('grey', 'lighten-2') !default; +$slider-indicator-color: color('green', 'base') !default; + + +// 16. Spinners | Loaders +// ========================================================================== + +$spinner-default-color: $secondary-color !default; + + +// 17. Tabs +// ========================================================================== + +$tabs-underline-color: $primary-color-light !default; +$tabs-text-color: $primary-color !default; +$tabs-bg-color: #fff !default; + + +// 18. Tables +// ========================================================================== + +$table-border-color: rgba(0,0,0,.12) !default; +$table-striped-color: rgba(242, 242, 242, 0.5) !default; + + +// 19. Toasts +// ========================================================================== + +$toast-height: 48px !default; +$toast-color: #323232 !default; +$toast-text-color: #fff !default; +$toast-action-color: #eeff41; + + +// 20. Typography +// ========================================================================== + +$font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif !default; +$off-black: rgba(0, 0, 0, 0.87) !default; +// Header Styles +$h1-fontsize: 4.2rem !default; +$h2-fontsize: 3.56rem !default; +$h3-fontsize: 2.92rem !default; +$h4-fontsize: 2.28rem !default; +$h5-fontsize: 1.64rem !default; +$h6-fontsize: 1.15rem !default; + + +// 21. Footer +// ========================================================================== + +$footer-font-color: #fff !default; +$footer-bg-color: $primary-color !default; +$footer-copyright-font-color: rgba(255,255,255,.8) !default; +$footer-copyright-bg-color: rgba(51,51,51,.08) !default; + + +// 22. Flow Text +// ========================================================================== + +$range : $large-screen - $small-screen !default; +$intervals: 20 !default; +$interval-size: $range / $intervals !default; + + +// 23. Collections +// ========================================================================== + +$collection-border-color: #e0e0e0 !default; +$collection-bg-color: #fff !default; +$collection-active-bg-color: $secondary-color !default; +$collection-active-color: lighten($secondary-color, 55%) !default; +$collection-hover-bg-color: #ddd !default; +$collection-link-color: $secondary-color !default; +$collection-line-height: 1.5rem !default; + + +// 24. Progress Bar +// ========================================================================== + +$progress-bar-color: $secondary-color !default; diff --git a/static/sass/components/_waves.scss b/static/sass/components/_waves.scss new file mode 100644 index 00000000..b36c7181 --- /dev/null +++ b/static/sass/components/_waves.scss @@ -0,0 +1,114 @@ + +/*! + * Waves v0.6.0 + * http://fian.my.id/Waves + * + * Copyright 2014 Alfiana E. Sibuea and other contributors + * Released under the MIT license + * https://github.com/fians/Waves/blob/master/LICENSE + */ + + +.waves-effect { + position: relative; + cursor: pointer; + display: inline-block; + overflow: hidden; + user-select: none; + -webkit-tap-highlight-color: transparent; + vertical-align: middle; + z-index: 1; + transition: .3s ease-out; + + .waves-ripple { + position: absolute; + border-radius: 50%; + width: 20px; + height: 20px; + margin-top:-10px; + margin-left:-10px; + opacity: 0; + + background: rgba(0,0,0,0.2); + transition: all 0.7s ease-out; + transition-property: transform, opacity; + transform: scale(0); + pointer-events: none; + } + + // Waves Colors + &.waves-light .waves-ripple { + background-color: rgba(255, 255, 255, 0.45); + } + &.waves-red .waves-ripple { + background-color: rgba(244, 67, 54, .70); + } + &.waves-yellow .waves-ripple { + background-color: rgba(255, 235, 59, .70); + } + &.waves-orange .waves-ripple { + background-color: rgba(255, 152, 0, .70); + } + &.waves-purple .waves-ripple { + background-color: rgba(156, 39, 176, 0.70); + } + &.waves-green .waves-ripple { + background-color: rgba(76, 175, 80, 0.70); + } + &.waves-teal .waves-ripple { + background-color: rgba(0, 150, 136, 0.70); + } + + // Style input button bug. + input[type="button"], input[type="reset"], input[type="submit"] { + border: 0; + font-style: normal; + font-size: inherit; + text-transform: inherit; + background: none; + } + + img { + position: relative; + z-index: -1; + } +} + +.waves-notransition { + transition: none #{"!important"}; +} + +.waves-circle { + transform: translateZ(0); + -webkit-mask-image: -webkit-radial-gradient(circle, white 100%, black 100%); +} + +.waves-input-wrapper { + border-radius: 0.2em; + vertical-align: bottom; + + .waves-button-input { + position: relative; + top: 0; + left: 0; + z-index: 1; + } +} + +.waves-circle { + text-align: center; + width: 2.5em; + height: 2.5em; + line-height: 2.5em; + border-radius: 50%; + -webkit-mask-image: none; +} + +.waves-block { + display: block; +} + +/* Firefox Bug: link not triggered */ +.waves-effect .waves-ripple { + z-index: -1; +} \ No newline at end of file diff --git a/static/sass/components/forms/_checkboxes.scss b/static/sass/components/forms/_checkboxes.scss new file mode 100644 index 00000000..ddc7d961 --- /dev/null +++ b/static/sass/components/forms/_checkboxes.scss @@ -0,0 +1,200 @@ +/* Checkboxes + ========================================================================== */ + +/* Remove default checkbox */ +[type="checkbox"]:not(:checked), +[type="checkbox"]:checked { + position: absolute; + opacity: 0; + pointer-events: none; +} + +// Checkbox Styles +[type="checkbox"] { + // Text Label Style + + span:not(.lever) { + position: relative; + padding-left: 35px; + cursor: pointer; + display: inline-block; + height: 25px; + line-height: 25px; + font-size: 1rem; + user-select: none; + } + + /* checkbox aspect */ + + span:not(.lever):before, + &:not(.filled-in) + span:not(.lever):after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 18px; + height: 18px; + z-index: 0; + border: 2px solid $radio-empty-color; + border-radius: 1px; + margin-top: 3px; + transition: .2s; + } + + &:not(.filled-in) + span:not(.lever):after { + border: 0; + transform: scale(0); + } + + &:not(:checked):disabled + span:not(.lever):before { + border: none; + background-color: $input-disabled-color; + } + + // Focused styles + &.tabbed:focus + span:not(.lever):after { + transform: scale(1); + border: 0; + border-radius: 50%; + box-shadow: 0 0 0 10px rgba(0,0,0,.1); + background-color: rgba(0,0,0,.1); + } +} + +[type="checkbox"]:checked { + + span:not(.lever):before { + top: -4px; + left: -5px; + width: 12px; + height: 22px; + border-top: 2px solid transparent; + border-left: 2px solid transparent; + border-right: $radio-border; + border-bottom: $radio-border; + transform: rotate(40deg); + backface-visibility: hidden; + transform-origin: 100% 100%; + } + + &:disabled + span:before { + border-right: 2px solid $input-disabled-color; + border-bottom: 2px solid $input-disabled-color; + } +} + +/* Indeterminate checkbox */ +[type="checkbox"]:indeterminate { + + span:not(.lever):before { + top: -11px; + left: -12px; + width: 10px; + height: 22px; + border-top: none; + border-left: none; + border-right: $radio-border; + border-bottom: none; + transform: rotate(90deg); + backface-visibility: hidden; + transform-origin: 100% 100%; + } + + // Disabled indeterminate + &:disabled + span:not(.lever):before { + border-right: 2px solid $input-disabled-color; + background-color: transparent; + } +} + +// Filled in Style +[type="checkbox"].filled-in { + // General + + span:not(.lever):after { + border-radius: 2px; + } + + + span:not(.lever):before, + + span:not(.lever):after { + content: ''; + left: 0; + position: absolute; + /* .1s delay is for check animation */ + transition: border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s; + z-index: 1; + } + + // Unchecked style + &:not(:checked) + span:not(.lever):before { + width: 0; + height: 0; + border: 3px solid transparent; + left: 6px; + top: 10px; + transform: rotateZ(37deg); + transform-origin: 100% 100%; + } + + &:not(:checked) + span:not(.lever):after { + height: 20px; + width: 20px; + background-color: transparent; + border: 2px solid $radio-empty-color; + top: 0px; + z-index: 0; + } + + // Checked style + &:checked { + + span:not(.lever):before { + top: 0; + left: 1px; + width: 8px; + height: 13px; + border-top: 2px solid transparent; + border-left: 2px solid transparent; + border-right: 2px solid $input-background; + border-bottom: 2px solid $input-background; + transform: rotateZ(37deg); + transform-origin: 100% 100%; + } + + + span:not(.lever):after { + top: 0; + width: 20px; + height: 20px; + border: 2px solid $secondary-color; + background-color: $secondary-color; + z-index: 0; + } + } + + // Focused styles + &.tabbed:focus + span:not(.lever):after { + border-radius: 2px; + border-color: $radio-empty-color; + background-color: rgba(0,0,0,.1); + } + + &.tabbed:checked:focus + span:not(.lever):after { + border-radius: 2px; + background-color: $secondary-color; + border-color: $secondary-color; + } + + // Disabled style + &:disabled:not(:checked) + span:not(.lever):before { + background-color: transparent; + border: 2px solid transparent; + } + + &:disabled:not(:checked) + span:not(.lever):after { + border-color: transparent; + background-color: $input-disabled-solid-color; + } + + &:disabled:checked + span:not(.lever):before { + background-color: transparent; + } + + &:disabled:checked + span:not(.lever):after { + background-color: $input-disabled-solid-color; + border-color: $input-disabled-solid-color; + } +} diff --git a/static/sass/components/forms/_file-input.scss b/static/sass/components/forms/_file-input.scss new file mode 100644 index 00000000..e0f7ef73 --- /dev/null +++ b/static/sass/components/forms/_file-input.scss @@ -0,0 +1,44 @@ +/* File Input + ========================================================================== */ + +.file-field { + position: relative; + + .file-path-wrapper { + overflow: hidden; + padding-left: 10px; + } + + input.file-path { width: 100%; } + + .btn { + float: left; + height: $input-height; + line-height: $input-height; + } + + span { + cursor: pointer; + } + + input[type=file] { + + // Needed to override webkit button + &::-webkit-file-upload-button { + display: none; + } + + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; + width: 100%; + margin: 0; + padding: 0; + font-size: 20px; + cursor: pointer; + opacity: 0; + filter: alpha(opacity=0); + } +} diff --git a/static/sass/components/forms/_forms.scss b/static/sass/components/forms/_forms.scss new file mode 100644 index 00000000..4c19f4c8 --- /dev/null +++ b/static/sass/components/forms/_forms.scss @@ -0,0 +1,22 @@ +// Remove Focus Boxes +select:focus { + outline: $select-focus; +} + +button:focus { + outline: none; + background-color: $button-background-focus; +} + +label { + font-size: $label-font-size; + color: $input-border-color; +} + +@import 'input-fields'; +@import 'radio-buttons'; +@import 'checkboxes'; +@import 'switches'; +@import 'select'; +@import 'file-input'; +@import 'range'; diff --git a/static/sass/components/forms/_input-fields.scss b/static/sass/components/forms/_input-fields.scss new file mode 100644 index 00000000..605bac04 --- /dev/null +++ b/static/sass/components/forms/_input-fields.scss @@ -0,0 +1,354 @@ +/* Text Inputs + Textarea + ========================================================================== */ + +/* Style Placeholders */ + +::placeholder { + color: $placeholder-text-color; +} + +/* Text inputs */ + +input:not([type]), +input[type=text]:not(.browser-default), +input[type=password]:not(.browser-default), +input[type=email]:not(.browser-default), +input[type=url]:not(.browser-default), +input[type=time]:not(.browser-default), +input[type=date]:not(.browser-default), +input[type=datetime]:not(.browser-default), +input[type=datetime-local]:not(.browser-default), +input[type=tel]:not(.browser-default), +input[type=number]:not(.browser-default), +input[type=search]:not(.browser-default), +textarea.materialize-textarea { + + // General Styles + background-color: transparent; + border: none; + border-bottom: $input-border; + border-radius: 0; + outline: none; + height: $input-height; + width: 100%; + font-size: $input-font-size; + margin: $input-margin; + padding: $input-padding; + box-shadow: none; + box-sizing: content-box; + transition: box-shadow .3s, border .3s; + + // Disabled input style + &:disabled, + &[readonly="readonly"] { + color: $input-disabled-color; + border-bottom: $input-disabled-border; + } + + // Disabled label style + &:disabled+label, + &[readonly="readonly"]+label { + color: $input-disabled-color; + } + + // Focused input style + &:focus:not([readonly]) { + border-bottom: 1px solid $input-focus-color; + box-shadow: 0 1px 0 0 $input-focus-color; + } + + // Focused label style + &:focus:not([readonly])+label { + color: $input-focus-color; + } + + // Hide helper text on data message + &.valid ~ .helper-text[data-success], + &:focus.valid ~ .helper-text[data-success], + &.invalid ~ .helper-text[data-error], + &:focus.invalid ~ .helper-text[data-error] { + @extend %hidden-text; + } + + // Valid Input Style + &.valid, + &:focus.valid { + @extend %valid-input-style; + } + + // Custom Success Message + &.valid ~ .helper-text:after, + &:focus.valid ~ .helper-text:after { + @extend %custom-success-message; + } + &:focus.valid ~ label { + color: $input-success-color; + } + + // Invalid Input Style + &.invalid, + &:focus.invalid { + @extend %invalid-input-style; + } + + // Custom Error message + &.invalid ~ .helper-text:after, + &:focus.invalid ~ .helper-text:after { + @extend %custom-error-message; + } + &:focus.invalid ~ label { + color: $input-error-color; + } + + // Full width label when using validate for error messages + &.validate + label { + width: 100%; + } + + // Form Message Shared Styles + & + label:after { + @extend %input-after-style; + } +} + + +/* Validation Sass Placeholders */ +%valid-input-style { + border-bottom: 1px solid $input-success-color; + box-shadow: 0 1px 0 0 $input-success-color; +} +%invalid-input-style { + border-bottom: $input-invalid-border; + box-shadow: 0 1px 0 0 $input-error-color; +} +%hidden-text { + color: transparent; + user-select: none; + pointer-events: none; +} +%custom-success-message { + content: attr(data-success); + color: $input-success-color; +} +%custom-error-message { + content: attr(data-error); + color: $input-error-color; +} +%input-after-style { + display: block; + content: ""; + position: absolute; + top: 100%; + left: 0; + opacity: 0; + transition: .2s opacity ease-out, .2s color ease-out; +} + + +// Styling for input field wrapper +.input-field { + // Inline styles + &.inline { + display: inline-block; + vertical-align: middle; + margin-left: 5px; + + input, + .select-dropdown { + margin-bottom: 1rem; + } + } + + // Gutter spacing + &.col { + label { + left: $gutter-width / 2; + } + + .prefix ~ label, + .prefix ~ .validate ~ label { + width: calc(100% - 3rem - #{$gutter-width}); + } + } + + position: relative; + margin-top: 1rem; + margin-bottom: 1rem; + + & > label { + color: $input-border-color; + position: absolute; + top: 0; + left: 0; + font-size: 1rem; + cursor: text; + transition: transform .2s ease-out, color .2s ease-out; + transform-origin: 0% 100%; + text-align: initial; + transform: translateY(12px); + + &:not(.label-icon).active { + transform: translateY(-14px) scale(.8); + transform-origin: 0 0; + } + } + + // Autofill + date + time inputs + & > input[type]:-webkit-autofill:not(.browser-default) + label, + & > input[type=date]:not(.browser-default) + label, + & > input[type=time]:not(.browser-default) + label { + transform: translateY(-14px) scale(.8); + transform-origin: 0 0; + } + + .helper-text { + &::after { + opacity: 1; + position: absolute; + top: 0; + left: 0; + } + + position: relative; + min-height: 18px; + display: block; + font-size: 12px; + color: rgba(0,0,0,.54); + } + + // Prefix Icons + .prefix { + position: absolute; + width: $input-height; + font-size: $input-icon-size; + transition: color .2s; + top: ($input-height - $input-icon-size) / 2; + + &.active { color: $input-focus-color; } + } + + .prefix ~ input, + .prefix ~ textarea, + .prefix ~ label, + .prefix ~ .validate ~ label, + .prefix ~ .helper-text, + .prefix ~ .autocomplete-content { + margin-left: 3rem; + width: 92%; + width: calc(100% - 3rem); + } + + .prefix ~ label { margin-left: 3rem; } + + @media #{$medium-and-down} { + .prefix ~ input { + width: 86%; + width: calc(100% - 3rem); + } + } + + @media #{$small-and-down} { + .prefix ~ input { + width: 80%; + width: calc(100% - 3rem); + } + } +} + + +/* Search Field */ + +.input-field input[type=search] { + display: block; + line-height: inherit; + transition: .3s background-color; + + .nav-wrapper & { + height: inherit; + padding-left: 4rem; + width: calc(100% - 4rem); + border: 0; + box-shadow: none; + } + + &:focus:not(.browser-default) { + background-color: $input-background; + border: 0; + box-shadow: none; + color: #444; + + & + label i, + & ~ .mdi-navigation-close, + & ~ .material-icons { + color: #444; + } + } + + & + .label-icon { + transform: none; + left: 1rem; + } + + & ~ .mdi-navigation-close, + & ~ .material-icons { + position: absolute; + top: 0; + right: 1rem; + color: transparent; + cursor: pointer; + font-size: $input-icon-size; + transition: .3s color; + } +} + + +/* Textarea */ + +// Default textarea +textarea { + width: 100%; + height: $input-height; + background-color: transparent; + + &.materialize-textarea { + line-height: normal; + overflow-y: hidden; /* prevents scroll bar flash */ + padding: .8rem 0 .8rem 0; /* prevents text jump on Enter keypress */ + resize: none; + min-height: $input-height; + box-sizing: border-box; + } +} + +// For textarea autoresize +.hiddendiv { + visibility: hidden; + white-space: pre-wrap; + word-wrap: break-word; + overflow-wrap: break-word; /* future version of deprecated 'word-wrap' */ + padding-top: 1.2rem; /* prevents text jump on Enter keypress */ + + // Reduces repaints + position: absolute; + top: 0; + z-index: -1; +} + + +/* Autocomplete */ +.autocomplete-content { + li { + .highlight { color: #444; } + + img { + height: $dropdown-item-height - 10; + width: $dropdown-item-height - 10; + margin: 5px 15px; + } + } +} + +/* Character Counter */ +.character-counter { + min-height: 18px; +} diff --git a/static/sass/components/forms/_radio-buttons.scss b/static/sass/components/forms/_radio-buttons.scss new file mode 100644 index 00000000..c9f72962 --- /dev/null +++ b/static/sass/components/forms/_radio-buttons.scss @@ -0,0 +1,115 @@ +/* Radio Buttons + ========================================================================== */ + +// Remove default Radio Buttons +[type="radio"]:not(:checked), +[type="radio"]:checked { + position: absolute; + opacity: 0; + pointer-events: none; +} + +[type="radio"]:not(:checked) + span, +[type="radio"]:checked + span { + position: relative; + padding-left: 35px; + cursor: pointer; + display: inline-block; + height: 25px; + line-height: 25px; + font-size: 1rem; + transition: .28s ease; + user-select: none; +} + +[type="radio"] + span:before, +[type="radio"] + span:after { + content: ''; + position: absolute; + left: 0; + top: 0; + margin: 4px; + width: 16px; + height: 16px; + z-index: 0; + transition: .28s ease; +} + +/* Unchecked styles */ +[type="radio"]:not(:checked) + span:before, +[type="radio"]:not(:checked) + span:after, +[type="radio"]:checked + span:before, +[type="radio"]:checked + span:after, +[type="radio"].with-gap:checked + span:before, +[type="radio"].with-gap:checked + span:after { + border-radius: 50%; +} + +[type="radio"]:not(:checked) + span:before, +[type="radio"]:not(:checked) + span:after { + border: 2px solid $radio-empty-color; +} + +[type="radio"]:not(:checked) + span:after { + transform: scale(0); +} + +/* Checked styles */ +[type="radio"]:checked + span:before { + border: 2px solid transparent; +} + +[type="radio"]:checked + span:after, +[type="radio"].with-gap:checked + span:before, +[type="radio"].with-gap:checked + span:after { + border: $radio-border; +} + +[type="radio"]:checked + span:after, +[type="radio"].with-gap:checked + span:after { + background-color: $radio-fill-color; +} + +[type="radio"]:checked + span:after { + transform: scale(1.02); +} + +/* Radio With gap */ +[type="radio"].with-gap:checked + span:after { + transform: scale(.5); +} + +/* Focused styles */ +[type="radio"].tabbed:focus + span:before { + box-shadow: 0 0 0 10px rgba(0,0,0,.1); +} + +/* Disabled Radio With gap */ +[type="radio"].with-gap:disabled:checked + span:before { + border: 2px solid $input-disabled-color; +} + +[type="radio"].with-gap:disabled:checked + span:after { + border: none; + background-color: $input-disabled-color; +} + +/* Disabled style */ +[type="radio"]:disabled:not(:checked) + span:before, +[type="radio"]:disabled:checked + span:before { + background-color: transparent; + border-color: $input-disabled-color; +} + +[type="radio"]:disabled + span { + color: $input-disabled-color; +} + +[type="radio"]:disabled:not(:checked) + span:before { + border-color: $input-disabled-color; +} + +[type="radio"]:disabled:checked + span:after { + background-color: $input-disabled-color; + border-color: $input-disabled-solid-color; +} diff --git a/static/sass/components/forms/_range.scss b/static/sass/components/forms/_range.scss new file mode 100644 index 00000000..18607f5d --- /dev/null +++ b/static/sass/components/forms/_range.scss @@ -0,0 +1,161 @@ +/* Range + ========================================================================== */ + +.range-field { + position: relative; +} + +input[type=range], +input[type=range] + .thumb { + @extend .no-select; + cursor: pointer; +} + +input[type=range] { + position: relative; + background-color: transparent; + border: none; + outline: none; + width: 100%; + margin: 15px 0; + padding: 0; + + &:focus { + outline: none; + } +} + +input[type=range] + .thumb { + position: absolute; + top: 10px; + left: 0; + border: none; + height: 0; + width: 0; + border-radius: 50%; + background-color: $radio-fill-color; + margin-left: 7px; + + transform-origin: 50% 50%; + transform: rotate(-45deg); + + .value { + display: block; + width: 30px; + text-align: center; + color: $radio-fill-color; + font-size: 0; + transform: rotate(45deg); + } + + &.active { + border-radius: 50% 50% 50% 0; + + .value { + color: $input-background; + margin-left: -1px; + margin-top: 8px; + font-size: 10px; + } + } +} + +// Shared +@mixin range-track { + height: $track-height; + background: #c2c0c2; + border: none; +} + +@mixin range-thumb { + border: none; + height: $range-height; + width: $range-width; + border-radius: 50%; + background: $radio-fill-color; + transition: box-shadow .3s; +} + +// WebKit +input[type=range] { + -webkit-appearance: none; +} + +input[type=range]::-webkit-slider-runnable-track { + @include range-track; +} + +input[type=range]::-webkit-slider-thumb { + @include range-thumb; + -webkit-appearance: none; + background-color: $radio-fill-color; + transform-origin: 50% 50%; + margin: -5px 0 0 0; + +} + +.keyboard-focused input[type=range]:focus:not(.active)::-webkit-slider-thumb { + box-shadow: 0 0 0 10px rgba($radio-fill-color, .26); +} + +// FireFox +input[type=range] { + /* fix for FF unable to apply focus style bug */ + border: 1px solid white; + + /*required for proper track sizing in FF*/ +} + +input[type=range]::-moz-range-track { + @include range-track; +} + +input[type=range]::-moz-focus-inner { + border: 0; +} + +input[type=range]::-moz-range-thumb { + @include range-thumb; + margin-top: -5px; +} + +// hide the outline behind the border +input[type=range]:-moz-focusring { + outline: 1px solid #fff; + outline-offset: -1px; +} + +.keyboard-focused input[type=range]:focus:not(.active)::-moz-range-thumb { + box-shadow: 0 0 0 10px rgba($radio-fill-color, .26); +} + +// IE 10+ +input[type=range]::-ms-track { + height: $track-height; + + // remove bg colour from the track, we'll use ms-fill-lower and ms-fill-upper instead + background: transparent; + + // leave room for the larger thumb to overflow with a transparent border */ + border-color: transparent; + border-width: 6px 0; + + /*remove default tick marks*/ + color: transparent; +} + +input[type=range]::-ms-fill-lower { + background: #777; +} + +input[type=range]::-ms-fill-upper { + background: #ddd; +} + +input[type=range]::-ms-thumb { + @include range-thumb; +} + +.keyboard-focused input[type=range]:focus:not(.active)::-ms-thumb { + box-shadow: 0 0 0 10px rgba($radio-fill-color, .26); +} diff --git a/static/sass/components/forms/_select.scss b/static/sass/components/forms/_select.scss new file mode 100644 index 00000000..2fd04d3c --- /dev/null +++ b/static/sass/components/forms/_select.scss @@ -0,0 +1,180 @@ +/* Select Field + ========================================================================== */ + +select { display: none; } +select.browser-default { display: block; } + +select { + background-color: $select-background; + width: 100%; + padding: $select-padding; + border: $select-border; + border-radius: $select-radius; + height: $input-height; +} + +.select-label { + position: absolute; +} + +.select-wrapper { + &.valid .helper-text[data-success], + &.invalid ~ .helper-text[data-error] { + @extend %hidden-text; + } + + &.valid { + & > input.select-dropdown { + @extend %valid-input-style; + } + + & ~ .helper-text:after { + @extend %custom-success-message; + } + } + + &.invalid { + & > input.select-dropdown, + & > input.select-dropdown:focus { + @extend %invalid-input-style; + } + + & ~ .helper-text:after { + @extend %custom-error-message; + } + } + + &.valid + label, + &.invalid + label { + width: 100%; + pointer-events: none; + } + + & + label:after { + @extend %input-after-style; + } + + position: relative; + + input.select-dropdown { + &:focus { + border-bottom: 1px solid $input-focus-color; + } + position: relative; + cursor: pointer; + background-color: transparent; + border: none; + border-bottom: $input-border; + outline: none; + height: $input-height; + line-height: $input-height; + width: 100%; + font-size: $input-font-size; + margin: $input-margin; + padding: 0; + display: block; + user-select:none; + z-index: 1; + } + + .caret { + position: absolute; + right: 0; + top: 0; + bottom: 0; + margin: auto 0; + z-index: 0; + fill: rgba(0,0,0,.87); + } + + & + label { + position: absolute; + top: -26px; + font-size: $label-font-size; + } +} + +// Disabled styles +select:disabled { + color: $input-disabled-color; +} + +.select-wrapper.disabled { + + label { + color: $input-disabled-color; + } + .caret { + fill: $input-disabled-color; + } +} + +.select-wrapper input.select-dropdown:disabled { + color: $input-disabled-color; + cursor: default; + user-select: none; +} + +.select-wrapper i { + color: $select-disabled-color; +} + +.select-dropdown li.disabled, +.select-dropdown li.disabled > span, +.select-dropdown li.optgroup { + color: $select-disabled-color; + background-color: transparent; +} + +body.keyboard-focused { + .select-dropdown.dropdown-content li:focus { + background-color: $select-option-focus; + } +} + +.select-dropdown.dropdown-content { + li { + &:hover { + background-color: $select-option-hover; + } + + &.selected { + background-color: $select-option-selected; + } + } +} + +// Prefix Icons +.prefix ~ .select-wrapper { + margin-left: 3rem; + width: 92%; + width: calc(100% - 3rem); +} + +.prefix ~ label { margin-left: 3rem; } + +// Icons +.select-dropdown li { + img { + height: $dropdown-item-height - 10; + width: $dropdown-item-height - 10; + margin: 5px 15px; + float: right; + } +} + +// Optgroup styles +.select-dropdown li.optgroup { + border-top: 1px solid $dropdown-hover-bg-color; + + &.selected > span { + color: rgba(0, 0, 0, .7); + } + + & > span { + color: rgba(0, 0, 0, .4); + } + + & ~ li.optgroup-option { + padding-left: 1rem; + } +} diff --git a/static/sass/components/forms/_switches.scss b/static/sass/components/forms/_switches.scss new file mode 100644 index 00000000..3296b12c --- /dev/null +++ b/static/sass/components/forms/_switches.scss @@ -0,0 +1,89 @@ +/* Switch + ========================================================================== */ + +.switch, +.switch * { + -webkit-tap-highlight-color: transparent; + user-select: none; +} + +.switch label { + cursor: pointer; +} + +.switch label input[type=checkbox] { + opacity: 0; + width: 0; + height: 0; + + &:checked + .lever { + background-color: $switch-checked-lever-bg; + + &:before, &:after { + left: 18px; + } + + &:after { + background-color: $switch-bg-color; + } + } +} + +.switch label .lever { + content: ""; + display: inline-block; + position: relative; + width: 36px; + height: 14px; + background-color: $switch-unchecked-lever-bg; + border-radius: $switch-radius; + margin-right: 10px; + transition: background 0.3s ease; + vertical-align: middle; + margin: 0 16px; + + &:before, &:after { + content: ""; + position: absolute; + display: inline-block; + width: 20px; + height: 20px; + border-radius: 50%; + left: 0; + top: -3px; + transition: left 0.3s ease, background .3s ease, box-shadow 0.1s ease, transform .1s ease; + } + + &:before { + background-color: transparentize($switch-bg-color, .85); + } + + &:after { + background-color: $switch-unchecked-bg; + box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12); + } +} + +// Switch active style +input[type=checkbox]:checked:not(:disabled) ~ .lever:active::before, +input[type=checkbox]:checked:not(:disabled).tabbed:focus ~ .lever::before { + transform: scale(2.4); + background-color: transparentize($switch-bg-color, .85); +} + +input[type=checkbox]:not(:disabled) ~ .lever:active:before, +input[type=checkbox]:not(:disabled).tabbed:focus ~ .lever::before { + transform: scale(2.4); + background-color: rgba(0,0,0,.08); +} + +// Disabled Styles +.switch input[type=checkbox][disabled] + .lever { + cursor: default; + background-color: rgba(0,0,0,.12); +} + +.switch label input[type=checkbox][disabled] + .lever:after, +.switch label input[type=checkbox][disabled]:checked + .lever:after { + background-color: $input-disabled-solid-color; +} diff --git a/static/sass/main.scss b/static/sass/main.scss new file mode 100644 index 00000000..cc102c43 --- /dev/null +++ b/static/sass/main.scss @@ -0,0 +1,42 @@ +@charset "UTF-8"; +//@import "materialize"; + +// Mixins +@import "components/color-classes"; +@import "components/color-variables"; + +// Variables +@import "components/variables"; +@import "components/normalize"; + +// components +@import "components/global"; + +@import "components/badges"; +@import "components/icons-material-design"; +@import "components/grid"; +@import "components/navbar"; +//@import "materialize/components/roboto"; +//@import "materialize/components/typography"; +//@import "materialize/components/transitions"; +//@import "materialize/components/cards"; +//@import "materialize/components/toast"; +//@import "materialize/components/tabs"; +//@import "materialize/components/tooltip"; +//@import "materialize/components/buttons"; +//@import "materialize/components/dropdown"; +//@import "materialize/components/waves"; +//@import "materialize/components/modal"; +//@import "materialize/components/collapsible"; +//@import "materialize/components/chips"; +//@import "materialize/components/materialbox"; +//@import "materialize/components/forms/forms"; +//@import "materialize/components/table_of_contents"; +//@import "materialize/components/sideNav"; +//@import "materialize/components/preloader"; +//@import "materialize/components/slider"; +//@import "materialize/components/carousel"; +//@import "materialize/components/date_picker/default"; +//@import "materialize/components/date_picker/default.date"; +//@import "materialize/components/date_picker/default.time"; + diff --git a/static/sass/materialize.scss b/static/sass/materialize.scss new file mode 100644 index 00000000..37c64290 --- /dev/null +++ b/static/sass/materialize.scss @@ -0,0 +1,49 @@ +@charset "UTF-8"; + +// Color +@import "components/color-variables"; +@import "components/color-classes"; + +// Variables; +@import "components/variables"; + +// Reset +@import "components/normalize"; + +// components +@import "components/global"; +@import "components/badges"; +@import "components/icons-material-design"; +@import "components/grid"; +@import "components/navbar"; +@import "components/typography"; +@import "components/transitions"; +@import "components/cards"; +@import "components/toast"; +@import "components/tabs"; +@import "components/tooltip"; +@import "components/buttons"; +@import "components/dropdown"; +@import "components/waves"; +@import "components/modal"; +@import "components/collapsible"; +@import "components/chips"; +@import "components/materialbox"; +@import "components/forms/forms"; +@import "components/table_of_contents"; +@import "components/sidenav"; +@import "components/preloader"; +@import "components/slider"; +@import "components/carousel"; +@import "components/tapTarget"; +@import "components/pulse"; +@import "components/datepicker"; +@import "components/timepicker"; + +.container { + width: 80% !important; +} + +.nav-wrapper { + padding: 0 10%; +} \ No newline at end of file From 44d84f10c9613894ba82f571fb246e76cf513411 Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Mon, 16 Jul 2018 00:28:42 +0100 Subject: [PATCH 08/34] Moved some templates around and added styling --- templates/asset_detail.html | 20 ++++++++++++++++++++ templates/asset_form.html | 15 +++++++++++++++ templates/asset_list.html | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 templates/asset_detail.html create mode 100644 templates/asset_form.html create mode 100644 templates/asset_list.html diff --git a/templates/asset_detail.html b/templates/asset_detail.html new file mode 100644 index 00000000..9bc7896f --- /dev/null +++ b/templates/asset_detail.html @@ -0,0 +1,20 @@ +{% extends 'base.html' %} + + +{% block main %} + +

      Asset Detail

      + + {{ object.description }} +
      + {{ object.category.name }} +
      + {{ object.status.name }} +
      + {{ object.purchased_from.name }} +
      +
      Edit +
      + Delete + +{% endblock %} diff --git a/templates/asset_form.html b/templates/asset_form.html new file mode 100644 index 00000000..65c21816 --- /dev/null +++ b/templates/asset_form.html @@ -0,0 +1,15 @@ +{% extends 'base.html' %} + + +{% block main %} + +

      Asset {{ form_header|default:'Create' }}

      + +
      + {{ form }} + {% csrf_token %} + + +
      + +{% endblock %} diff --git a/templates/asset_list.html b/templates/asset_list.html new file mode 100644 index 00000000..95f48b43 --- /dev/null +++ b/templates/asset_list.html @@ -0,0 +1,34 @@ +{% extends 'base.html' %} + +{% block main %} + +

      Asset List

      + + + + + + + + + + + + + {% for item in object_list %} +{#
    • {{ item.asset_id }} - {{ item.description }}
    • #} + + + + + + + + {% endfor %} + +
      Asset IDDescriptionCategoryStatusQuick Links
      {{ item.asset_id }}{{ item.description }}{{ item.category }}{{ item.status }} + visibility + edit +
      + +{% endblock %} \ No newline at end of file From 47c46f4cc40916295dbac84a9cd489153d75ca18 Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Mon, 16 Jul 2018 00:30:39 +0100 Subject: [PATCH 09/34] Updated new template locations --- views.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/views.py b/views.py index ef648c8a..79a45dc6 100644 --- a/views.py +++ b/views.py @@ -1,7 +1,7 @@ from django.shortcuts import render from django.views import generic from django.urls import reverse_lazy -from assets import models, forms +from assets import models class Index(generic.TemplateView): @@ -10,25 +10,30 @@ class Index(generic.TemplateView): class AssetList(generic.ListView): model = models.Asset - template_name = 'assets/asset_list.html' + template_name = 'asset_list.html' class AssetDetail(generic.DetailView): model = models.Asset - template_name = 'assets/asset_detail.html' + template_name = 'asset_detail.html' class AssetCreate(generic.CreateView): model = models.Asset fields = '__all__' - template_name = 'assets/asset_form.html' + template_name = 'asset_form.html' # success_url = reverse_lazy('asset_list') class AssetUpdate(generic.UpdateView): model = models.Asset fields = '__all__' - template_name = 'assets/asset_form.html' + template_name = 'asset_form.html' + + def get_context_data(self, **kwargs): + context = super(AssetUpdate, self).get_context_data(**kwargs) + context['form_header'] = 'Update' + return context class AssetDelete(generic.DeleteView): From 4c11371f0c8c22c6c0547793151f07327b5ef430 Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Mon, 16 Jul 2018 14:40:51 +0100 Subject: [PATCH 10/34] Remove unnecessary JS files --- static/js/anime.min.js | 34 - static/js/autocomplete.js | 450 ------------ static/js/buttons.js | 354 --------- static/js/cards.js | 40 - static/js/carousel.js | 717 ------------------ static/js/cash.js | 960 ------------------------ static/js/characterCounter.js | 136 ---- static/js/chips.js | 481 ------------ static/js/collapsible.js | 275 ------- static/js/component.js | 44 -- static/js/datepicker.js | 975 ------------------------- static/js/dropdown.js | 615 ---------------- static/js/forms.js | 275 ------- static/js/global.js | 425 ----------- static/js/materialbox.js | 453 ------------ static/js/{bin => }/materialize.js | 0 static/js/{bin => }/materialize.min.js | 0 static/js/modal.js | 382 ---------- static/js/parallax.js | 138 ---- static/js/pushpin.js | 145 ---- static/js/range.js | 263 ------- static/js/scrollspy.js | 295 -------- static/js/select.js | 422 ----------- static/js/sidenav.js | 580 --------------- static/js/slider.js | 359 --------- static/js/tabs.js | 402 ---------- static/js/tapTarget.js | 314 -------- static/js/timepicker.js | 647 ---------------- static/js/toasts.js | 310 -------- static/js/tooltip.js | 303 -------- static/js/waves.js | 335 --------- 31 files changed, 11129 deletions(-) delete mode 100644 static/js/anime.min.js delete mode 100644 static/js/autocomplete.js delete mode 100644 static/js/buttons.js delete mode 100644 static/js/cards.js delete mode 100644 static/js/carousel.js delete mode 100644 static/js/cash.js delete mode 100644 static/js/characterCounter.js delete mode 100644 static/js/chips.js delete mode 100644 static/js/collapsible.js delete mode 100644 static/js/component.js delete mode 100644 static/js/datepicker.js delete mode 100644 static/js/dropdown.js delete mode 100644 static/js/forms.js delete mode 100644 static/js/global.js delete mode 100644 static/js/materialbox.js rename static/js/{bin => }/materialize.js (100%) rename static/js/{bin => }/materialize.min.js (100%) delete mode 100644 static/js/modal.js delete mode 100644 static/js/parallax.js delete mode 100644 static/js/pushpin.js delete mode 100644 static/js/range.js delete mode 100644 static/js/scrollspy.js delete mode 100644 static/js/select.js delete mode 100644 static/js/sidenav.js delete mode 100644 static/js/slider.js delete mode 100644 static/js/tabs.js delete mode 100644 static/js/tapTarget.js delete mode 100644 static/js/timepicker.js delete mode 100644 static/js/toasts.js delete mode 100644 static/js/tooltip.js delete mode 100644 static/js/waves.js diff --git a/static/js/anime.min.js b/static/js/anime.min.js deleted file mode 100644 index fb578584..00000000 --- a/static/js/anime.min.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - v2.2.0 - 2017 Julian Garnier - Released under the MIT license -*/ -var $jscomp={scope:{}};$jscomp.defineProperty="function"==typeof Object.defineProperties?Object.defineProperty:function(e,r,p){if(p.get||p.set)throw new TypeError("ES3 does not support getters and setters.");e!=Array.prototype&&e!=Object.prototype&&(e[r]=p.value)};$jscomp.getGlobal=function(e){return"undefined"!=typeof window&&window===e?e:"undefined"!=typeof global&&null!=global?global:e};$jscomp.global=$jscomp.getGlobal(this);$jscomp.SYMBOL_PREFIX="jscomp_symbol_"; -$jscomp.initSymbol=function(){$jscomp.initSymbol=function(){};$jscomp.global.Symbol||($jscomp.global.Symbol=$jscomp.Symbol)};$jscomp.symbolCounter_=0;$jscomp.Symbol=function(e){return $jscomp.SYMBOL_PREFIX+(e||"")+$jscomp.symbolCounter_++}; -$jscomp.initSymbolIterator=function(){$jscomp.initSymbol();var e=$jscomp.global.Symbol.iterator;e||(e=$jscomp.global.Symbol.iterator=$jscomp.global.Symbol("iterator"));"function"!=typeof Array.prototype[e]&&$jscomp.defineProperty(Array.prototype,e,{configurable:!0,writable:!0,value:function(){return $jscomp.arrayIterator(this)}});$jscomp.initSymbolIterator=function(){}};$jscomp.arrayIterator=function(e){var r=0;return $jscomp.iteratorPrototype(function(){return rb&&(b+=1);1b?c:b<2/3?a+(c-a)*(2/3-b)*6:a}var d=/hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.exec(a)||/hsla\((\d+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)/g.exec(a);a=parseInt(d[1])/360;var b=parseInt(d[2])/100,f=parseInt(d[3])/100,d=d[4]||1;if(0==b)f=b=a=f;else{var n=.5>f?f*(1+b):f+b-f*b,k=2*f-n,f=c(k,n,a+1/3),b=c(k,n,a);a=c(k,n,a-1/3)}return"rgba("+ -255*f+","+255*b+","+255*a+","+d+")"}function y(a){if(a=/([\+\-]?[0-9#\.]+)(%|px|pt|em|rem|in|cm|mm|ex|ch|pc|vw|vh|vmin|vmax|deg|rad|turn)?$/.exec(a))return a[2]}function V(a){if(-1=g.currentTime)for(var G=0;G=w||!k)g.began||(g.began=!0,f("begin")),f("run");if(q>n&&q=k&&r!==k||!k)b(k),x||e();f("update");a>=k&&(g.remaining?(t=h,"alternate"===g.direction&&(g.reversed=!g.reversed)):(g.pause(),g.completed||(g.completed=!0,f("complete"),"Promise"in window&&(p(),m=c()))),l=0)}a=void 0===a?{}:a;var h,t,l=0,p=null,m=c(),g=fa(a);g.reset=function(){var a=g.direction,c=g.loop;g.currentTime= -0;g.progress=0;g.paused=!0;g.began=!1;g.completed=!1;g.reversed="reverse"===a;g.remaining="alternate"===a&&1===c?2:c;b(0);for(a=g.children.length;a--;)g.children[a].reset()};g.tick=function(a){h=a;t||(t=h);k((l+h-t)*q.speed)};g.seek=function(a){k(d(a))};g.pause=function(){var a=v.indexOf(g);-1=c&&0<=b&&1>=b){var e=new Float32Array(11);if(c!==d||b!==f)for(var k=0;11>k;++k)e[k]=a(.1*k,c,b);return function(k){if(c===d&&b===f)return k;if(0===k)return 0;if(1===k)return 1;for(var h=0,l=1;10!==l&&e[l]<=k;++l)h+=.1;--l;var l=h+(k-e[l])/(e[l+1]-e[l])*.1,n=3*(1-3*b+3*c)*l*l+2*(3*b-6*c)*l+3*c;if(.001<=n){for(h=0;4>h;++h){n=3*(1-3*b+3*c)*l*l+2*(3*b-6*c)*l+3*c;if(0===n)break;var m=a(l,c,b)-k,l=l-m/n}k=l}else if(0=== -n)k=l;else{var l=h,h=h+.1,g=0;do m=l+(h-l)/2,n=a(m,c,b)-k,0++g);k=m}return a(k,d,f)}}}}(),Q=function(){function a(a,b){return 0===a||1===a?a:-Math.pow(2,10*(a-1))*Math.sin(2*(a-1-b/(2*Math.PI)*Math.asin(1))*Math.PI/b)}var c="Quad Cubic Quart Quint Sine Expo Circ Back Elastic".split(" "),d={In:[[.55,.085,.68,.53],[.55,.055,.675,.19],[.895,.03,.685,.22],[.755,.05,.855,.06],[.47,0,.745,.715],[.95,.05,.795,.035],[.6,.04,.98,.335],[.6,-.28,.735,.045],a],Out:[[.25, -.46,.45,.94],[.215,.61,.355,1],[.165,.84,.44,1],[.23,1,.32,1],[.39,.575,.565,1],[.19,1,.22,1],[.075,.82,.165,1],[.175,.885,.32,1.275],function(b,c){return 1-a(1-b,c)}],InOut:[[.455,.03,.515,.955],[.645,.045,.355,1],[.77,0,.175,1],[.86,0,.07,1],[.445,.05,.55,.95],[1,0,0,1],[.785,.135,.15,.86],[.68,-.55,.265,1.55],function(b,c){return.5>b?a(2*b,c)/2:1-a(-2*b+2,c)/2}]},b={linear:A(.25,.25,.75,.75)},f={},e;for(e in d)f.type=e,d[f.type].forEach(function(a){return function(d,f){b["ease"+a.type+c[f]]=h.fnc(d)? -d:A.apply($jscomp$this,d)}}(f)),f={type:f.type};return b}(),ha={css:function(a,c,d){return a.style[c]=d},attribute:function(a,c,d){return a.setAttribute(c,d)},object:function(a,c,d){return a[c]=d},transform:function(a,c,d,b,f){b[f]||(b[f]=[]);b[f].push(c+"("+d+")")}},v=[],B=0,ia=function(){function a(){B=requestAnimationFrame(c)}function c(c){var b=v.length;if(b){for(var d=0;db&&(c.duration=d.duration);c.children.push(d)});c.seek(0);c.reset();c.autoplay&&c.restart();return c};return c};q.random=function(a,c){return Math.floor(Math.random()*(c-a+1))+a};return q}); diff --git a/static/js/autocomplete.js b/static/js/autocomplete.js deleted file mode 100644 index 6366736c..00000000 --- a/static/js/autocomplete.js +++ /dev/null @@ -1,450 +0,0 @@ -(function($) { - 'use strict'; - - let _defaults = { - data: {}, // Autocomplete data set - limit: Infinity, // Limit of results the autocomplete shows - onAutocomplete: null, // Callback for when autocompleted - minLength: 1, // Min characters before autocomplete starts - sortFunction: function(a, b, inputString) { - // Sort function for sorting autocomplete results - return a.indexOf(inputString) - b.indexOf(inputString); - } - }; - - /** - * @class - * - */ - class Autocomplete extends Component { - /** - * Construct Autocomplete instance - * @constructor - * @param {Element} el - * @param {Object} options - */ - constructor(el, options) { - super(Autocomplete, el, options); - - this.el.M_Autocomplete = this; - - /** - * Options for the autocomplete - * @member Autocomplete#options - * @prop {Number} duration - * @prop {Number} dist - * @prop {number} shift - * @prop {number} padding - * @prop {Boolean} fullWidth - * @prop {Boolean} indicators - * @prop {Boolean} noWrap - * @prop {Function} onCycleTo - */ - this.options = $.extend({}, Autocomplete.defaults, options); - - // Setup - this.isOpen = false; - this.count = 0; - this.activeIndex = -1; - this.oldVal; - this.$inputField = this.$el.closest('.input-field'); - this.$active = $(); - this._mousedown = false; - this._setupDropdown(); - - this._setupEventHandlers(); - } - - static get defaults() { - return _defaults; - } - - static init(els, options) { - return super.init(this, els, options); - } - - /** - * Get Instance - */ - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_Autocomplete; - } - - /** - * Teardown component - */ - destroy() { - this._removeEventHandlers(); - this._removeDropdown(); - this.el.M_Autocomplete = undefined; - } - - /** - * Setup Event Handlers - */ - _setupEventHandlers() { - this._handleInputBlurBound = this._handleInputBlur.bind(this); - this._handleInputKeyupAndFocusBound = this._handleInputKeyupAndFocus.bind(this); - this._handleInputKeydownBound = this._handleInputKeydown.bind(this); - this._handleInputClickBound = this._handleInputClick.bind(this); - this._handleContainerMousedownAndTouchstartBound = this._handleContainerMousedownAndTouchstart.bind( - this - ); - this._handleContainerMouseupAndTouchendBound = this._handleContainerMouseupAndTouchend.bind( - this - ); - - this.el.addEventListener('blur', this._handleInputBlurBound); - this.el.addEventListener('keyup', this._handleInputKeyupAndFocusBound); - this.el.addEventListener('focus', this._handleInputKeyupAndFocusBound); - this.el.addEventListener('keydown', this._handleInputKeydownBound); - this.el.addEventListener('click', this._handleInputClickBound); - this.container.addEventListener( - 'mousedown', - this._handleContainerMousedownAndTouchstartBound - ); - this.container.addEventListener('mouseup', this._handleContainerMouseupAndTouchendBound); - - if (typeof window.ontouchstart !== 'undefined') { - this.container.addEventListener( - 'touchstart', - this._handleContainerMousedownAndTouchstartBound - ); - this.container.addEventListener('touchend', this._handleContainerMouseupAndTouchendBound); - } - } - - /** - * Remove Event Handlers - */ - _removeEventHandlers() { - this.el.removeEventListener('blur', this._handleInputBlurBound); - this.el.removeEventListener('keyup', this._handleInputKeyupAndFocusBound); - this.el.removeEventListener('focus', this._handleInputKeyupAndFocusBound); - this.el.removeEventListener('keydown', this._handleInputKeydownBound); - this.el.removeEventListener('click', this._handleInputClickBound); - this.container.removeEventListener( - 'mousedown', - this._handleContainerMousedownAndTouchstartBound - ); - this.container.removeEventListener('mouseup', this._handleContainerMouseupAndTouchendBound); - - if (typeof window.ontouchstart !== 'undefined') { - this.container.removeEventListener( - 'touchstart', - this._handleContainerMousedownAndTouchstartBound - ); - this.container.removeEventListener( - 'touchend', - this._handleContainerMouseupAndTouchendBound - ); - } - } - - /** - * Setup dropdown - */ - _setupDropdown() { - this.container = document.createElement('ul'); - this.container.id = `autocomplete-options-${M.guid()}`; - $(this.container).addClass('autocomplete-content dropdown-content'); - this.$inputField.append(this.container); - this.el.setAttribute('data-target', this.container.id); - - this.dropdown = M.Dropdown.init(this.el, { - autoFocus: false, - closeOnClick: false, - coverTrigger: false, - onItemClick: (itemEl) => { - this.selectOption($(itemEl)); - } - }); - - // Sketchy removal of dropdown click handler - this.el.removeEventListener('click', this.dropdown._handleClickBound); - } - - /** - * Remove dropdown - */ - _removeDropdown() { - this.container.parentNode.removeChild(this.container); - } - - /** - * Handle Input Blur - */ - _handleInputBlur() { - if (!this._mousedown) { - this.close(); - this._resetAutocomplete(); - } - } - - /** - * Handle Input Keyup and Focus - * @param {Event} e - */ - _handleInputKeyupAndFocus(e) { - if (e.type === 'keyup') { - Autocomplete._keydown = false; - } - - this.count = 0; - let val = this.el.value.toLowerCase(); - - // Don't capture enter or arrow key usage. - if (e.keyCode === 13 || e.keyCode === 38 || e.keyCode === 40) { - return; - } - - // Check if the input isn't empty - // Check if focus triggered by tab - if (this.oldVal !== val && (M.tabPressed || e.type !== 'focus')) { - this.open(); - } - - // Update oldVal - this.oldVal = val; - } - - /** - * Handle Input Keydown - * @param {Event} e - */ - _handleInputKeydown(e) { - Autocomplete._keydown = true; - - // Arrow keys and enter key usage - let keyCode = e.keyCode, - liElement, - numItems = $(this.container).children('li').length; - - // select element on Enter - if (keyCode === M.keys.ENTER && this.activeIndex >= 0) { - liElement = $(this.container) - .children('li') - .eq(this.activeIndex); - if (liElement.length) { - this.selectOption(liElement); - e.preventDefault(); - } - return; - } - - // Capture up and down key - if (keyCode === M.keys.ARROW_UP || keyCode === M.keys.ARROW_DOWN) { - e.preventDefault(); - - if (keyCode === M.keys.ARROW_UP && this.activeIndex > 0) { - this.activeIndex--; - } - - if (keyCode === M.keys.ARROW_DOWN && this.activeIndex < numItems - 1) { - this.activeIndex++; - } - - this.$active.removeClass('active'); - if (this.activeIndex >= 0) { - this.$active = $(this.container) - .children('li') - .eq(this.activeIndex); - this.$active.addClass('active'); - } - } - } - - /** - * Handle Input Click - * @param {Event} e - */ - _handleInputClick(e) { - this.open(); - } - - /** - * Handle Container Mousedown and Touchstart - * @param {Event} e - */ - _handleContainerMousedownAndTouchstart(e) { - this._mousedown = true; - } - - /** - * Handle Container Mouseup and Touchend - * @param {Event} e - */ - _handleContainerMouseupAndTouchend(e) { - this._mousedown = false; - } - - /** - * Highlight partial match - */ - _highlight(string, $el) { - let img = $el.find('img'); - let matchStart = $el - .text() - .toLowerCase() - .indexOf('' + string.toLowerCase() + ''), - matchEnd = matchStart + string.length - 1, - beforeMatch = $el.text().slice(0, matchStart), - matchText = $el.text().slice(matchStart, matchEnd + 1), - afterMatch = $el.text().slice(matchEnd + 1); - $el.html( - `${beforeMatch}${matchText}${afterMatch}` - ); - if (img.length) { - $el.prepend(img); - } - } - - /** - * Reset current element position - */ - _resetCurrentElement() { - this.activeIndex = -1; - this.$active.removeClass('active'); - } - - /** - * Reset autocomplete elements - */ - _resetAutocomplete() { - $(this.container).empty(); - this._resetCurrentElement(); - this.oldVal = null; - this.isOpen = false; - this._mousedown = false; - } - - /** - * Select autocomplete option - * @param {Element} el Autocomplete option list item element - */ - selectOption(el) { - let text = el.text().trim(); - this.el.value = text; - this.$el.trigger('change'); - this._resetAutocomplete(); - this.close(); - - // Handle onAutocomplete callback. - if (typeof this.options.onAutocomplete === 'function') { - this.options.onAutocomplete.call(this, text); - } - } - - /** - * Render dropdown content - * @param {Object} data data set - * @param {String} val current input value - */ - _renderDropdown(data, val) { - this._resetAutocomplete(); - - let matchingData = []; - - // Gather all matching data - for (let key in data) { - if (data.hasOwnProperty(key) && key.toLowerCase().indexOf(val) !== -1) { - // Break if past limit - if (this.count >= this.options.limit) { - break; - } - - let entry = { - data: data[key], - key: key - }; - matchingData.push(entry); - - this.count++; - } - } - - // Sort - if (this.options.sortFunction) { - let sortFunctionBound = (a, b) => { - return this.options.sortFunction( - a.key.toLowerCase(), - b.key.toLowerCase(), - val.toLowerCase() - ); - }; - matchingData.sort(sortFunctionBound); - } - - // Render - for (let i = 0; i < matchingData.length; i++) { - let entry = matchingData[i]; - let $autocompleteOption = $('
    • '); - if (!!entry.data) { - $autocompleteOption.append( - `${entry.key}` - ); - } else { - $autocompleteOption.append('' + entry.key + ''); - } - - $(this.container).append($autocompleteOption); - this._highlight(val, $autocompleteOption); - } - } - - /** - * Open Autocomplete Dropdown - */ - open() { - let val = this.el.value.toLowerCase(); - - this._resetAutocomplete(); - - if (val.length >= this.options.minLength) { - this.isOpen = true; - this._renderDropdown(this.options.data, val); - } - - // Open dropdown - if (!this.dropdown.isOpen) { - this.dropdown.open(); - } else { - // Recalculate dropdown when its already open - this.dropdown.recalculateDimensions(); - } - } - - /** - * Close Autocomplete Dropdown - */ - close() { - this.dropdown.close(); - } - - /** - * Update Data - * @param {Object} data - */ - updateData(data) { - let val = this.el.value.toLowerCase(); - this.options.data = data; - - if (this.isOpen) { - this._renderDropdown(data, val); - } - } - } - - /** - * @static - * @memberof Autocomplete - */ - Autocomplete._keydown = false; - - M.Autocomplete = Autocomplete; - - if (M.jQueryLoaded) { - M.initializeJqueryWrapper(Autocomplete, 'autocomplete', 'M_Autocomplete'); - } -})(cash); diff --git a/static/js/buttons.js b/static/js/buttons.js deleted file mode 100644 index fb2ba286..00000000 --- a/static/js/buttons.js +++ /dev/null @@ -1,354 +0,0 @@ -(function($, anim) { - 'use strict'; - - let _defaults = { - direction: 'top', - hoverEnabled: true, - toolbarEnabled: false - }; - - $.fn.reverse = [].reverse; - - /** - * @class - * - */ - class FloatingActionButton extends Component { - /** - * Construct FloatingActionButton instance - * @constructor - * @param {Element} el - * @param {Object} options - */ - constructor(el, options) { - super(FloatingActionButton, el, options); - - this.el.M_FloatingActionButton = this; - - /** - * Options for the fab - * @member FloatingActionButton#options - * @prop {Boolean} [direction] - Direction fab menu opens - * @prop {Boolean} [hoverEnabled=true] - Enable hover vs click - * @prop {Boolean} [toolbarEnabled=false] - Enable toolbar transition - */ - this.options = $.extend({}, FloatingActionButton.defaults, options); - - this.isOpen = false; - this.$anchor = this.$el.children('a').first(); - this.$menu = this.$el.children('ul').first(); - this.$floatingBtns = this.$el.find('ul .btn-floating'); - this.$floatingBtnsReverse = this.$el.find('ul .btn-floating').reverse(); - this.offsetY = 0; - this.offsetX = 0; - - this.$el.addClass(`direction-${this.options.direction}`); - if (this.options.direction === 'top') { - this.offsetY = 40; - } else if (this.options.direction === 'right') { - this.offsetX = -40; - } else if (this.options.direction === 'bottom') { - this.offsetY = -40; - } else { - this.offsetX = 40; - } - this._setupEventHandlers(); - } - - static get defaults() { - return _defaults; - } - - static init(els, options) { - return super.init(this, els, options); - } - - /** - * Get Instance - */ - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_FloatingActionButton; - } - - /** - * Teardown component - */ - destroy() { - this._removeEventHandlers(); - this.el.M_FloatingActionButton = undefined; - } - - /** - * Setup Event Handlers - */ - _setupEventHandlers() { - this._handleFABClickBound = this._handleFABClick.bind(this); - this._handleOpenBound = this.open.bind(this); - this._handleCloseBound = this.close.bind(this); - - if (this.options.hoverEnabled && !this.options.toolbarEnabled) { - this.el.addEventListener('mouseenter', this._handleOpenBound); - this.el.addEventListener('mouseleave', this._handleCloseBound); - } else { - this.el.addEventListener('click', this._handleFABClickBound); - } - } - - /** - * Remove Event Handlers - */ - _removeEventHandlers() { - if (this.options.hoverEnabled && !this.options.toolbarEnabled) { - this.el.removeEventListener('mouseenter', this._handleOpenBound); - this.el.removeEventListener('mouseleave', this._handleCloseBound); - } else { - this.el.removeEventListener('click', this._handleFABClickBound); - } - } - - /** - * Handle FAB Click - */ - _handleFABClick() { - if (this.isOpen) { - this.close(); - } else { - this.open(); - } - } - - /** - * Handle Document Click - * @param {Event} e - */ - _handleDocumentClick(e) { - if (!$(e.target).closest(this.$menu).length) { - this.close(); - } - } - - /** - * Open FAB - */ - open() { - if (this.isOpen) { - return; - } - - if (this.options.toolbarEnabled) { - this._animateInToolbar(); - } else { - this._animateInFAB(); - } - this.isOpen = true; - } - - /** - * Close FAB - */ - close() { - if (!this.isOpen) { - return; - } - - if (this.options.toolbarEnabled) { - window.removeEventListener('scroll', this._handleCloseBound, true); - document.body.removeEventListener('click', this._handleDocumentClickBound, true); - this._animateOutToolbar(); - } else { - this._animateOutFAB(); - } - this.isOpen = false; - } - - /** - * Classic FAB Menu open - */ - _animateInFAB() { - this.$el.addClass('active'); - - let time = 0; - this.$floatingBtnsReverse.each((el) => { - anim({ - targets: el, - opacity: 1, - scale: [0.4, 1], - translateY: [this.offsetY, 0], - translateX: [this.offsetX, 0], - duration: 275, - delay: time, - easing: 'easeInOutQuad' - }); - time += 40; - }); - } - - /** - * Classic FAB Menu close - */ - _animateOutFAB() { - this.$floatingBtnsReverse.each((el) => { - anim.remove(el); - anim({ - targets: el, - opacity: 0, - scale: 0.4, - translateY: this.offsetY, - translateX: this.offsetX, - duration: 175, - easing: 'easeOutQuad', - complete: () => { - this.$el.removeClass('active'); - } - }); - }); - } - - /** - * Toolbar transition Menu open - */ - _animateInToolbar() { - let scaleFactor; - let windowWidth = window.innerWidth; - let windowHeight = window.innerHeight; - let btnRect = this.el.getBoundingClientRect(); - let backdrop = $('
      '); - let fabColor = this.$anchor.css('background-color'); - this.$anchor.append(backdrop); - - this.offsetX = btnRect.left - windowWidth / 2 + btnRect.width / 2; - this.offsetY = windowHeight - btnRect.bottom; - scaleFactor = windowWidth / backdrop[0].clientWidth; - this.btnBottom = btnRect.bottom; - this.btnLeft = btnRect.left; - this.btnWidth = btnRect.width; - - // Set initial state - this.$el.addClass('active'); - this.$el.css({ - 'text-align': 'center', - width: '100%', - bottom: 0, - left: 0, - transform: 'translateX(' + this.offsetX + 'px)', - transition: 'none' - }); - this.$anchor.css({ - transform: 'translateY(' + -this.offsetY + 'px)', - transition: 'none' - }); - backdrop.css({ - 'background-color': fabColor - }); - - setTimeout(() => { - this.$el.css({ - transform: '', - transition: - 'transform .2s cubic-bezier(0.550, 0.085, 0.680, 0.530), background-color 0s linear .2s' - }); - this.$anchor.css({ - overflow: 'visible', - transform: '', - transition: 'transform .2s' - }); - - setTimeout(() => { - this.$el.css({ - overflow: 'hidden', - 'background-color': fabColor - }); - backdrop.css({ - transform: 'scale(' + scaleFactor + ')', - transition: 'transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)' - }); - this.$menu - .children('li') - .children('a') - .css({ - opacity: 1 - }); - - // Scroll to close. - this._handleDocumentClickBound = this._handleDocumentClick.bind(this); - window.addEventListener('scroll', this._handleCloseBound, true); - document.body.addEventListener('click', this._handleDocumentClickBound, true); - }, 100); - }, 0); - } - - /** - * Toolbar transition Menu close - */ - _animateOutToolbar() { - let windowWidth = window.innerWidth; - let windowHeight = window.innerHeight; - let backdrop = this.$el.find('.fab-backdrop'); - let fabColor = this.$anchor.css('background-color'); - - this.offsetX = this.btnLeft - windowWidth / 2 + this.btnWidth / 2; - this.offsetY = windowHeight - this.btnBottom; - - // Hide backdrop - this.$el.removeClass('active'); - this.$el.css({ - 'background-color': 'transparent', - transition: 'none' - }); - this.$anchor.css({ - transition: 'none' - }); - backdrop.css({ - transform: 'scale(0)', - 'background-color': fabColor - }); - this.$menu - .children('li') - .children('a') - .css({ - opacity: '' - }); - - setTimeout(() => { - backdrop.remove(); - - // Set initial state. - this.$el.css({ - 'text-align': '', - width: '', - bottom: '', - left: '', - overflow: '', - 'background-color': '', - transform: 'translate3d(' + -this.offsetX + 'px,0,0)' - }); - this.$anchor.css({ - overflow: '', - transform: 'translate3d(0,' + this.offsetY + 'px,0)' - }); - - setTimeout(() => { - this.$el.css({ - transform: 'translate3d(0,0,0)', - transition: 'transform .2s' - }); - this.$anchor.css({ - transform: 'translate3d(0,0,0)', - transition: 'transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)' - }); - }, 20); - }, 200); - } - } - - M.FloatingActionButton = FloatingActionButton; - - if (M.jQueryLoaded) { - M.initializeJqueryWrapper( - FloatingActionButton, - 'floatingActionButton', - 'M_FloatingActionButton' - ); - } -})(cash, M.anime); diff --git a/static/js/cards.js b/static/js/cards.js deleted file mode 100644 index 0c1ad737..00000000 --- a/static/js/cards.js +++ /dev/null @@ -1,40 +0,0 @@ -(function($, anim) { - $(document).on('click', '.card', function(e) { - if ($(this).children('.card-reveal').length) { - var $card = $(e.target).closest('.card'); - if ($card.data('initialOverflow') === undefined) { - $card.data( - 'initialOverflow', - $card.css('overflow') === undefined ? '' : $card.css('overflow') - ); - } - let $cardReveal = $(this).find('.card-reveal'); - if ( - $(e.target).is($('.card-reveal .card-title')) || - $(e.target).is($('.card-reveal .card-title i')) - ) { - // Make Reveal animate down and display none - anim({ - targets: $cardReveal[0], - translateY: 0, - duration: 225, - easing: 'easeInOutQuad', - complete: function(anim) { - let el = anim.animatables[0].target; - $(el).css({ display: 'none' }); - $card.css('overflow', $card.data('initialOverflow')); - } - }); - } else if ($(e.target).is($('.card .activator')) || $(e.target).is($('.card .activator i'))) { - $card.css('overflow', 'hidden'); - $cardReveal.css({ display: 'block' }); - anim({ - targets: $cardReveal[0], - translateY: '-100%', - duration: 300, - easing: 'easeInOutQuad' - }); - } - } - }); -})(cash, M.anime); diff --git a/static/js/carousel.js b/static/js/carousel.js deleted file mode 100644 index be98dc88..00000000 --- a/static/js/carousel.js +++ /dev/null @@ -1,717 +0,0 @@ -(function($) { - 'use strict'; - - let _defaults = { - duration: 200, // ms - dist: -100, // zoom scale TODO: make this more intuitive as an option - shift: 0, // spacing for center image - padding: 0, // Padding between non center items - numVisible: 5, // Number of visible items in carousel - fullWidth: false, // Change to full width styles - indicators: false, // Toggle indicators - noWrap: false, // Don't wrap around and cycle through items. - onCycleTo: null // Callback for when a new slide is cycled to. - }; - - /** - * @class - * - */ - class Carousel extends Component { - /** - * Construct Carousel instance - * @constructor - * @param {Element} el - * @param {Object} options - */ - constructor(el, options) { - super(Carousel, el, options); - - this.el.M_Carousel = this; - - /** - * Options for the carousel - * @member Carousel#options - * @prop {Number} duration - * @prop {Number} dist - * @prop {Number} shift - * @prop {Number} padding - * @prop {Number} numVisible - * @prop {Boolean} fullWidth - * @prop {Boolean} indicators - * @prop {Boolean} noWrap - * @prop {Function} onCycleTo - */ - this.options = $.extend({}, Carousel.defaults, options); - - // Setup - this.hasMultipleSlides = this.$el.find('.carousel-item').length > 1; - this.showIndicators = this.options.indicators && this.hasMultipleSlides; - this.noWrap = this.options.noWrap || !this.hasMultipleSlides; - this.pressed = false; - this.dragged = false; - this.offset = this.target = 0; - this.images = []; - this.itemWidth = this.$el - .find('.carousel-item') - .first() - .innerWidth(); - this.itemHeight = this.$el - .find('.carousel-item') - .first() - .innerHeight(); - this.dim = this.itemWidth * 2 + this.options.padding || 1; // Make sure dim is non zero for divisions. - this._autoScrollBound = this._autoScroll.bind(this); - this._trackBound = this._track.bind(this); - - // Full Width carousel setup - if (this.options.fullWidth) { - this.options.dist = 0; - this._setCarouselHeight(); - - // Offset fixed items when indicators. - if (this.showIndicators) { - this.$el.find('.carousel-fixed-item').addClass('with-indicators'); - } - } - - // Iterate through slides - this.$indicators = $('
        '); - this.$el.find('.carousel-item').each((el, i) => { - this.images.push(el); - if (this.showIndicators) { - let $indicator = $('
      • '); - - // Add active to first by default. - if (i === 0) { - $indicator[0].classList.add('active'); - } - - this.$indicators.append($indicator); - } - }); - if (this.showIndicators) { - this.$el.append(this.$indicators); - } - this.count = this.images.length; - - // Cap numVisible at count - this.options.numVisible = Math.min(this.count, this.options.numVisible); - - // Setup cross browser string - this.xform = 'transform'; - ['webkit', 'Moz', 'O', 'ms'].every((prefix) => { - var e = prefix + 'Transform'; - if (typeof document.body.style[e] !== 'undefined') { - this.xform = e; - return false; - } - return true; - }); - - this._setupEventHandlers(); - this._scroll(this.offset); - } - - static get defaults() { - return _defaults; - } - - static init(els, options) { - return super.init(this, els, options); - } - - /** - * Get Instance - */ - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_Carousel; - } - - /** - * Teardown component - */ - destroy() { - this._removeEventHandlers(); - this.el.M_Carousel = undefined; - } - - /** - * Setup Event Handlers - */ - _setupEventHandlers() { - this._handleCarouselTapBound = this._handleCarouselTap.bind(this); - this._handleCarouselDragBound = this._handleCarouselDrag.bind(this); - this._handleCarouselReleaseBound = this._handleCarouselRelease.bind(this); - this._handleCarouselClickBound = this._handleCarouselClick.bind(this); - - if (typeof window.ontouchstart !== 'undefined') { - this.el.addEventListener('touchstart', this._handleCarouselTapBound); - this.el.addEventListener('touchmove', this._handleCarouselDragBound); - this.el.addEventListener('touchend', this._handleCarouselReleaseBound); - } - - this.el.addEventListener('mousedown', this._handleCarouselTapBound); - this.el.addEventListener('mousemove', this._handleCarouselDragBound); - this.el.addEventListener('mouseup', this._handleCarouselReleaseBound); - this.el.addEventListener('mouseleave', this._handleCarouselReleaseBound); - this.el.addEventListener('click', this._handleCarouselClickBound); - - if (this.showIndicators && this.$indicators) { - this._handleIndicatorClickBound = this._handleIndicatorClick.bind(this); - this.$indicators.find('.indicator-item').each((el, i) => { - el.addEventListener('click', this._handleIndicatorClickBound); - }); - } - - // Resize - let throttledResize = M.throttle(this._handleResize, 200); - this._handleThrottledResizeBound = throttledResize.bind(this); - - window.addEventListener('resize', this._handleThrottledResizeBound); - } - - /** - * Remove Event Handlers - */ - _removeEventHandlers() { - if (typeof window.ontouchstart !== 'undefined') { - this.el.removeEventListener('touchstart', this._handleCarouselTapBound); - this.el.removeEventListener('touchmove', this._handleCarouselDragBound); - this.el.removeEventListener('touchend', this._handleCarouselReleaseBound); - } - this.el.removeEventListener('mousedown', this._handleCarouselTapBound); - this.el.removeEventListener('mousemove', this._handleCarouselDragBound); - this.el.removeEventListener('mouseup', this._handleCarouselReleaseBound); - this.el.removeEventListener('mouseleave', this._handleCarouselReleaseBound); - this.el.removeEventListener('click', this._handleCarouselClickBound); - - if (this.showIndicators && this.$indicators) { - this.$indicators.find('.indicator-item').each((el, i) => { - el.removeEventListener('click', this._handleIndicatorClickBound); - }); - } - - window.removeEventListener('resize', this._handleThrottledResizeBound); - } - - /** - * Handle Carousel Tap - * @param {Event} e - */ - _handleCarouselTap(e) { - // Fixes firefox draggable image bug - if (e.type === 'mousedown' && $(e.target).is('img')) { - e.preventDefault(); - } - this.pressed = true; - this.dragged = false; - this.verticalDragged = false; - this.reference = this._xpos(e); - this.referenceY = this._ypos(e); - - this.velocity = this.amplitude = 0; - this.frame = this.offset; - this.timestamp = Date.now(); - clearInterval(this.ticker); - this.ticker = setInterval(this._trackBound, 100); - } - - /** - * Handle Carousel Drag - * @param {Event} e - */ - _handleCarouselDrag(e) { - let x, y, delta, deltaY; - if (this.pressed) { - x = this._xpos(e); - y = this._ypos(e); - delta = this.reference - x; - deltaY = Math.abs(this.referenceY - y); - if (deltaY < 30 && !this.verticalDragged) { - // If vertical scrolling don't allow dragging. - if (delta > 2 || delta < -2) { - this.dragged = true; - this.reference = x; - this._scroll(this.offset + delta); - } - } else if (this.dragged) { - // If dragging don't allow vertical scroll. - e.preventDefault(); - e.stopPropagation(); - return false; - } else { - // Vertical scrolling. - this.verticalDragged = true; - } - } - - if (this.dragged) { - // If dragging don't allow vertical scroll. - e.preventDefault(); - e.stopPropagation(); - return false; - } - } - - /** - * Handle Carousel Release - * @param {Event} e - */ - _handleCarouselRelease(e) { - if (this.pressed) { - this.pressed = false; - } else { - return; - } - - clearInterval(this.ticker); - this.target = this.offset; - if (this.velocity > 10 || this.velocity < -10) { - this.amplitude = 0.9 * this.velocity; - this.target = this.offset + this.amplitude; - } - this.target = Math.round(this.target / this.dim) * this.dim; - - // No wrap of items. - if (this.noWrap) { - if (this.target >= this.dim * (this.count - 1)) { - this.target = this.dim * (this.count - 1); - } else if (this.target < 0) { - this.target = 0; - } - } - this.amplitude = this.target - this.offset; - this.timestamp = Date.now(); - requestAnimationFrame(this._autoScrollBound); - - if (this.dragged) { - e.preventDefault(); - e.stopPropagation(); - } - return false; - } - - /** - * Handle Carousel CLick - * @param {Event} e - */ - _handleCarouselClick(e) { - // Disable clicks if carousel was dragged. - if (this.dragged) { - e.preventDefault(); - e.stopPropagation(); - return false; - } else if (!this.options.fullWidth) { - let clickedIndex = $(e.target) - .closest('.carousel-item') - .index(); - let diff = this._wrap(this.center) - clickedIndex; - - // Disable clicks if carousel was shifted by click - if (diff !== 0) { - e.preventDefault(); - e.stopPropagation(); - } - this._cycleTo(clickedIndex); - } - } - - /** - * Handle Indicator CLick - * @param {Event} e - */ - _handleIndicatorClick(e) { - e.stopPropagation(); - - let indicator = $(e.target).closest('.indicator-item'); - if (indicator.length) { - this._cycleTo(indicator.index()); - } - } - - /** - * Handle Throttle Resize - * @param {Event} e - */ - _handleResize(e) { - if (this.options.fullWidth) { - this.itemWidth = this.$el - .find('.carousel-item') - .first() - .innerWidth(); - this.imageHeight = this.$el.find('.carousel-item.active').height(); - this.dim = this.itemWidth * 2 + this.options.padding; - this.offset = this.center * 2 * this.itemWidth; - this.target = this.offset; - this._setCarouselHeight(true); - } else { - this._scroll(); - } - } - - /** - * Set carousel height based on first slide - * @param {Booleam} imageOnly - true for image slides - */ - _setCarouselHeight(imageOnly) { - let firstSlide = this.$el.find('.carousel-item.active').length - ? this.$el.find('.carousel-item.active').first() - : this.$el.find('.carousel-item').first(); - let firstImage = firstSlide.find('img').first(); - if (firstImage.length) { - if (firstImage[0].complete) { - // If image won't trigger the load event - let imageHeight = firstImage.height(); - if (imageHeight > 0) { - this.$el.css('height', imageHeight + 'px'); - } else { - // If image still has no height, use the natural dimensions to calculate - let naturalWidth = firstImage[0].naturalWidth; - let naturalHeight = firstImage[0].naturalHeight; - let adjustedHeight = this.$el.width() / naturalWidth * naturalHeight; - this.$el.css('height', adjustedHeight + 'px'); - } - } else { - // Get height when image is loaded normally - firstImage.one('load', (el, i) => { - this.$el.css('height', el.offsetHeight + 'px'); - }); - } - } else if (!imageOnly) { - let slideHeight = firstSlide.height(); - this.$el.css('height', slideHeight + 'px'); - } - } - - /** - * Get x position from event - * @param {Event} e - */ - _xpos(e) { - // touch event - if (e.targetTouches && e.targetTouches.length >= 1) { - return e.targetTouches[0].clientX; - } - - // mouse event - return e.clientX; - } - - /** - * Get y position from event - * @param {Event} e - */ - _ypos(e) { - // touch event - if (e.targetTouches && e.targetTouches.length >= 1) { - return e.targetTouches[0].clientY; - } - - // mouse event - return e.clientY; - } - - /** - * Wrap index - * @param {Number} x - */ - _wrap(x) { - return x >= this.count ? x % this.count : x < 0 ? this._wrap(this.count + x % this.count) : x; - } - - /** - * Tracks scrolling information - */ - _track() { - let now, elapsed, delta, v; - - now = Date.now(); - elapsed = now - this.timestamp; - this.timestamp = now; - delta = this.offset - this.frame; - this.frame = this.offset; - - v = 1000 * delta / (1 + elapsed); - this.velocity = 0.8 * v + 0.2 * this.velocity; - } - - /** - * Auto scrolls to nearest carousel item. - */ - _autoScroll() { - let elapsed, delta; - - if (this.amplitude) { - elapsed = Date.now() - this.timestamp; - delta = this.amplitude * Math.exp(-elapsed / this.options.duration); - if (delta > 2 || delta < -2) { - this._scroll(this.target - delta); - requestAnimationFrame(this._autoScrollBound); - } else { - this._scroll(this.target); - } - } - } - - /** - * Scroll to target - * @param {Number} x - */ - _scroll(x) { - // Track scrolling state - if (!this.$el.hasClass('scrolling')) { - this.el.classList.add('scrolling'); - } - if (this.scrollingTimeout != null) { - window.clearTimeout(this.scrollingTimeout); - } - this.scrollingTimeout = window.setTimeout(() => { - this.$el.removeClass('scrolling'); - }, this.options.duration); - - // Start actual scroll - let i, - half, - delta, - dir, - tween, - el, - alignment, - zTranslation, - tweenedOpacity, - centerTweenedOpacity; - let lastCenter = this.center; - let numVisibleOffset = 1 / this.options.numVisible; - - this.offset = typeof x === 'number' ? x : this.offset; - this.center = Math.floor((this.offset + this.dim / 2) / this.dim); - delta = this.offset - this.center * this.dim; - dir = delta < 0 ? 1 : -1; - tween = -dir * delta * 2 / this.dim; - half = this.count >> 1; - - if (this.options.fullWidth) { - alignment = 'translateX(0)'; - centerTweenedOpacity = 1; - } else { - alignment = 'translateX(' + (this.el.clientWidth - this.itemWidth) / 2 + 'px) '; - alignment += 'translateY(' + (this.el.clientHeight - this.itemHeight) / 2 + 'px)'; - centerTweenedOpacity = 1 - numVisibleOffset * tween; - } - - // Set indicator active - if (this.showIndicators) { - let diff = this.center % this.count; - let activeIndicator = this.$indicators.find('.indicator-item.active'); - if (activeIndicator.index() !== diff) { - activeIndicator.removeClass('active'); - this.$indicators - .find('.indicator-item') - .eq(diff)[0] - .classList.add('active'); - } - } - - // center - // Don't show wrapped items. - if (!this.noWrap || (this.center >= 0 && this.center < this.count)) { - el = this.images[this._wrap(this.center)]; - - // Add active class to center item. - if (!$(el).hasClass('active')) { - this.$el.find('.carousel-item').removeClass('active'); - el.classList.add('active'); - } - let transformString = `${alignment} translateX(${-delta / 2}px) translateX(${dir * - this.options.shift * - tween * - i}px) translateZ(${this.options.dist * tween}px)`; - this._updateItemStyle(el, centerTweenedOpacity, 0, transformString); - } - - for (i = 1; i <= half; ++i) { - // right side - if (this.options.fullWidth) { - zTranslation = this.options.dist; - tweenedOpacity = i === half && delta < 0 ? 1 - tween : 1; - } else { - zTranslation = this.options.dist * (i * 2 + tween * dir); - tweenedOpacity = 1 - numVisibleOffset * (i * 2 + tween * dir); - } - // Don't show wrapped items. - if (!this.noWrap || this.center + i < this.count) { - el = this.images[this._wrap(this.center + i)]; - let transformString = `${alignment} translateX(${this.options.shift + - (this.dim * i - delta) / 2}px) translateZ(${zTranslation}px)`; - this._updateItemStyle(el, tweenedOpacity, -i, transformString); - } - - // left side - if (this.options.fullWidth) { - zTranslation = this.options.dist; - tweenedOpacity = i === half && delta > 0 ? 1 - tween : 1; - } else { - zTranslation = this.options.dist * (i * 2 - tween * dir); - tweenedOpacity = 1 - numVisibleOffset * (i * 2 - tween * dir); - } - // Don't show wrapped items. - if (!this.noWrap || this.center - i >= 0) { - el = this.images[this._wrap(this.center - i)]; - let transformString = `${alignment} translateX(${-this.options.shift + - (-this.dim * i - delta) / 2}px) translateZ(${zTranslation}px)`; - this._updateItemStyle(el, tweenedOpacity, -i, transformString); - } - } - - // center - // Don't show wrapped items. - if (!this.noWrap || (this.center >= 0 && this.center < this.count)) { - el = this.images[this._wrap(this.center)]; - let transformString = `${alignment} translateX(${-delta / 2}px) translateX(${dir * - this.options.shift * - tween}px) translateZ(${this.options.dist * tween}px)`; - this._updateItemStyle(el, centerTweenedOpacity, 0, transformString); - } - - // onCycleTo callback - let $currItem = this.$el.find('.carousel-item').eq(this._wrap(this.center)); - if (lastCenter !== this.center && typeof this.options.onCycleTo === 'function') { - this.options.onCycleTo.call(this, $currItem[0], this.dragged); - } - - // One time callback - if (typeof this.oneTimeCallback === 'function') { - this.oneTimeCallback.call(this, $currItem[0], this.dragged); - this.oneTimeCallback = null; - } - } - - /** - * Cycle to target - * @param {Element} el - * @param {Number} opacity - * @param {Number} zIndex - * @param {String} transform - */ - _updateItemStyle(el, opacity, zIndex, transform) { - el.style[this.xform] = transform; - el.style.zIndex = zIndex; - el.style.opacity = opacity; - el.style.visibility = 'visible'; - } - - /** - * Cycle to target - * @param {Number} n - * @param {Function} callback - */ - _cycleTo(n, callback) { - let diff = this.center % this.count - n; - - // Account for wraparound. - if (!this.noWrap) { - if (diff < 0) { - if (Math.abs(diff + this.count) < Math.abs(diff)) { - diff += this.count; - } - } else if (diff > 0) { - if (Math.abs(diff - this.count) < diff) { - diff -= this.count; - } - } - } - - this.target = this.dim * Math.round(this.offset / this.dim); - // Next - if (diff < 0) { - this.target += this.dim * Math.abs(diff); - - // Prev - } else if (diff > 0) { - this.target -= this.dim * diff; - } - - // Set one time callback - if (typeof callback === 'function') { - this.oneTimeCallback = callback; - } - - // Scroll - if (this.offset !== this.target) { - this.amplitude = this.target - this.offset; - this.timestamp = Date.now(); - requestAnimationFrame(this._autoScrollBound); - } - } - - /** - * Cycle to next item - * @param {Number} [n] - */ - next(n) { - if (n === undefined || isNaN(n)) { - n = 1; - } - - let index = this.center + n; - if (index >= this.count || index < 0) { - if (this.noWrap) { - return; - } - - index = this._wrap(index); - } - this._cycleTo(index); - } - - /** - * Cycle to previous item - * @param {Number} [n] - */ - prev(n) { - if (n === undefined || isNaN(n)) { - n = 1; - } - - let index = this.center - n; - if (index >= this.count || index < 0) { - if (this.noWrap) { - return; - } - - index = this._wrap(index); - } - - this._cycleTo(index); - } - - /** - * Cycle to nth item - * @param {Number} [n] - * @param {Function} callback - */ - set(n, callback) { - if (n === undefined || isNaN(n)) { - n = 0; - } - - if (n > this.count || n < 0) { - if (this.noWrap) { - return; - } - - n = this._wrap(n); - } - - this._cycleTo(n, callback); - } - } - - M.Carousel = Carousel; - - if (M.jQueryLoaded) { - M.initializeJqueryWrapper(Carousel, 'carousel', 'M_Carousel'); - } -})(cash); diff --git a/static/js/cash.js b/static/js/cash.js deleted file mode 100644 index 17d95acd..00000000 --- a/static/js/cash.js +++ /dev/null @@ -1,960 +0,0 @@ -/*! cash-dom 1.3.5, https://github.com/kenwheeler/cash @license MIT */ -(function (factory) { - window.cash = factory(); -})(function () { - var doc = document, win = window, ArrayProto = Array.prototype, slice = ArrayProto.slice, filter = ArrayProto.filter, push = ArrayProto.push; - - var noop = function () {}, isFunction = function (item) { - // @see https://crbug.com/568448 - return typeof item === typeof noop && item.call; - }, isString = function (item) { - return typeof item === typeof ""; - }; - - var idMatch = /^#[\w-]*$/, classMatch = /^\.[\w-]*$/, htmlMatch = /<.+>/, singlet = /^\w+$/; - - function find(selector, context) { - context = context || doc; - var elems = (classMatch.test(selector) ? context.getElementsByClassName(selector.slice(1)) : singlet.test(selector) ? context.getElementsByTagName(selector) : context.querySelectorAll(selector)); - return elems; - } - - var frag; - function parseHTML(str) { - if (!frag) { - frag = doc.implementation.createHTMLDocument(null); - var base = frag.createElement("base"); - base.href = doc.location.href; - frag.head.appendChild(base); - } - - frag.body.innerHTML = str; - - return frag.body.childNodes; - } - - function onReady(fn) { - if (doc.readyState !== "loading") { - fn(); - } else { - doc.addEventListener("DOMContentLoaded", fn); - } - } - - function Init(selector, context) { - if (!selector) { - return this; - } - - // If already a cash collection, don't do any further processing - if (selector.cash && selector !== win) { - return selector; - } - - var elems = selector, i = 0, length; - - if (isString(selector)) { - elems = (idMatch.test(selector) ? - // If an ID use the faster getElementById check - doc.getElementById(selector.slice(1)) : htmlMatch.test(selector) ? - // If HTML, parse it into real elements - parseHTML(selector) : - // else use `find` - find(selector, context)); - - // If function, use as shortcut for DOM ready - } else if (isFunction(selector)) { - onReady(selector);return this; - } - - if (!elems) { - return this; - } - - // If a single DOM element is passed in or received via ID, return the single element - if (elems.nodeType || elems === win) { - this[0] = elems; - this.length = 1; - } else { - // Treat like an array and loop through each item. - length = this.length = elems.length; - for (; i < length; i++) { - this[i] = elems[i]; - } - } - - return this; - } - - function cash(selector, context) { - return new Init(selector, context); - } - - var fn = cash.fn = cash.prototype = Init.prototype = { // jshint ignore:line - cash: true, - length: 0, - push: push, - splice: ArrayProto.splice, - map: ArrayProto.map, - init: Init - }; - - Object.defineProperty(fn, "constructor", { value: cash }); - - cash.parseHTML = parseHTML; - cash.noop = noop; - cash.isFunction = isFunction; - cash.isString = isString; - - cash.extend = fn.extend = function (target) { - target = target || {}; - - var args = slice.call(arguments), length = args.length, i = 1; - - if (args.length === 1) { - target = this; - i = 0; - } - - for (; i < length; i++) { - if (!args[i]) { - continue; - } - for (var key in args[i]) { - if (args[i].hasOwnProperty(key)) { - target[key] = args[i][key]; - } - } - } - - return target; - }; - - function each(collection, callback) { - var l = collection.length, i = 0; - - for (; i < l; i++) { - if (callback.call(collection[i], collection[i], i, collection) === false) { - break; - } - } - } - - function matches(el, selector) { - var m = el && (el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector || el.oMatchesSelector); - return !!m && m.call(el, selector); - } - - function getCompareFunction(selector) { - return ( - /* Use browser's `matches` function if string */ - isString(selector) ? matches : - /* Match a cash element */ - selector.cash ? function (el) { - return selector.is(el); - } : - /* Direct comparison */ - function (el, selector) { - return el === selector; - }); - } - - function unique(collection) { - return cash(slice.call(collection).filter(function (item, index, self) { - return self.indexOf(item) === index; - })); - } - - cash.extend({ - merge: function (first, second) { - var len = +second.length, i = first.length, j = 0; - - for (; j < len; i++, j++) { - first[i] = second[j]; - } - - first.length = i; - return first; - }, - - each: each, - matches: matches, - unique: unique, - isArray: Array.isArray, - isNumeric: function (n) { - return !isNaN(parseFloat(n)) && isFinite(n); - } - - }); - - var uid = cash.uid = "_cash" + Date.now(); - - function getDataCache(node) { - return (node[uid] = node[uid] || {}); - } - - function setData(node, key, value) { - return (getDataCache(node)[key] = value); - } - - function getData(node, key) { - var c = getDataCache(node); - if (c[key] === undefined) { - c[key] = node.dataset ? node.dataset[key] : cash(node).attr("data-" + key); - } - return c[key]; - } - - function removeData(node, key) { - var c = getDataCache(node); - if (c) { - delete c[key]; - } else if (node.dataset) { - delete node.dataset[key]; - } else { - cash(node).removeAttr("data-" + name); - } - } - - fn.extend({ - data: function (name, value) { - if (isString(name)) { - return (value === undefined ? getData(this[0], name) : this.each(function (v) { - return setData(v, name, value); - })); - } - - for (var key in name) { - this.data(key, name[key]); - } - - return this; - }, - - removeData: function (key) { - return this.each(function (v) { - return removeData(v, key); - }); - } - - }); - - var notWhiteMatch = /\S+/g; - - function getClasses(c) { - return isString(c) && c.match(notWhiteMatch); - } - - function hasClass(v, c) { - return (v.classList ? v.classList.contains(c) : new RegExp("(^| )" + c + "( |$)", "gi").test(v.className)); - } - - function addClass(v, c, spacedName) { - if (v.classList) { - v.classList.add(c); - } else if (spacedName.indexOf(" " + c + " ")) { - v.className += " " + c; - } - } - - function removeClass(v, c) { - if (v.classList) { - v.classList.remove(c); - } else { - v.className = v.className.replace(c, ""); - } - } - - fn.extend({ - addClass: function (c) { - var classes = getClasses(c); - - return (classes ? this.each(function (v) { - var spacedName = " " + v.className + " "; - each(classes, function (c) { - addClass(v, c, spacedName); - }); - }) : this); - }, - - attr: function (name, value) { - if (!name) { - return undefined; - } - - if (isString(name)) { - if (value === undefined) { - return this[0] ? this[0].getAttribute ? this[0].getAttribute(name) : this[0][name] : undefined; - } - - return this.each(function (v) { - if (v.setAttribute) { - v.setAttribute(name, value); - } else { - v[name] = value; - } - }); - } - - for (var key in name) { - this.attr(key, name[key]); - } - - return this; - }, - - hasClass: function (c) { - var check = false, classes = getClasses(c); - if (classes && classes.length) { - this.each(function (v) { - check = hasClass(v, classes[0]); - return !check; - }); - } - return check; - }, - - prop: function (name, value) { - if (isString(name)) { - return (value === undefined ? this[0][name] : this.each(function (v) { - v[name] = value; - })); - } - - for (var key in name) { - this.prop(key, name[key]); - } - - return this; - }, - - removeAttr: function (name) { - return this.each(function (v) { - if (v.removeAttribute) { - v.removeAttribute(name); - } else { - delete v[name]; - } - }); - }, - - removeClass: function (c) { - if (!arguments.length) { - return this.attr("class", ""); - } - var classes = getClasses(c); - return (classes ? this.each(function (v) { - each(classes, function (c) { - removeClass(v, c); - }); - }) : this); - }, - - removeProp: function (name) { - return this.each(function (v) { - delete v[name]; - }); - }, - - toggleClass: function (c, state) { - if (state !== undefined) { - return this[state ? "addClass" : "removeClass"](c); - } - var classes = getClasses(c); - return (classes ? this.each(function (v) { - var spacedName = " " + v.className + " "; - each(classes, function (c) { - if (hasClass(v, c)) { - removeClass(v, c); - } else { - addClass(v, c, spacedName); - } - }); - }) : this); - } }); - - fn.extend({ - add: function (selector, context) { - return unique(cash.merge(this, cash(selector, context))); - }, - - each: function (callback) { - each(this, callback); - return this; - }, - - eq: function (index) { - return cash(this.get(index)); - }, - - filter: function (selector) { - if (!selector) { - return this; - } - - var comparator = (isFunction(selector) ? selector : getCompareFunction(selector)); - - return cash(filter.call(this, function (e) { - return comparator(e, selector); - })); - }, - - first: function () { - return this.eq(0); - }, - - get: function (index) { - if (index === undefined) { - return slice.call(this); - } - return (index < 0 ? this[index + this.length] : this[index]); - }, - - index: function (elem) { - var child = elem ? cash(elem)[0] : this[0], collection = elem ? this : cash(child).parent().children(); - return slice.call(collection).indexOf(child); - }, - - last: function () { - return this.eq(-1); - } - - }); - - var camelCase = (function () { - var camelRegex = /(?:^\w|[A-Z]|\b\w)/g, whiteSpace = /[\s-_]+/g; - return function (str) { - return str.replace(camelRegex, function (letter, index) { - return letter[index === 0 ? "toLowerCase" : "toUpperCase"](); - }).replace(whiteSpace, ""); - }; - }()); - - var getPrefixedProp = (function () { - var cache = {}, doc = document, div = doc.createElement("div"), style = div.style; - - return function (prop) { - prop = camelCase(prop); - if (cache[prop]) { - return cache[prop]; - } - - var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1), prefixes = ["webkit", "moz", "ms", "o"], props = (prop + " " + (prefixes).join(ucProp + " ") + ucProp).split(" "); - - each(props, function (p) { - if (p in style) { - cache[p] = prop = cache[prop] = p; - return false; - } - }); - - return cache[prop]; - }; - }()); - - cash.prefixedProp = getPrefixedProp; - cash.camelCase = camelCase; - - fn.extend({ - css: function (prop, value) { - if (isString(prop)) { - prop = getPrefixedProp(prop); - return (arguments.length > 1 ? this.each(function (v) { - return v.style[prop] = value; - }) : win.getComputedStyle(this[0])[prop]); - } - - for (var key in prop) { - this.css(key, prop[key]); - } - - return this; - } - - }); - - function compute(el, prop) { - return parseInt(win.getComputedStyle(el[0], null)[prop], 10) || 0; - } - - each(["Width", "Height"], function (v) { - var lower = v.toLowerCase(); - - fn[lower] = function () { - return this[0].getBoundingClientRect()[lower]; - }; - - fn["inner" + v] = function () { - return this[0]["client" + v]; - }; - - fn["outer" + v] = function (margins) { - return this[0]["offset" + v] + (margins ? compute(this, "margin" + (v === "Width" ? "Left" : "Top")) + compute(this, "margin" + (v === "Width" ? "Right" : "Bottom")) : 0); - }; - }); - - function registerEvent(node, eventName, callback) { - var eventCache = getData(node, "_cashEvents") || setData(node, "_cashEvents", {}); - eventCache[eventName] = eventCache[eventName] || []; - eventCache[eventName].push(callback); - node.addEventListener(eventName, callback); - } - - function removeEvent(node, eventName, callback) { - var events = getData(node, "_cashEvents"), eventCache = (events && events[eventName]), index; - - if (!eventCache) { - return; - } - - if (callback) { - node.removeEventListener(eventName, callback); - index = eventCache.indexOf(callback); - if (index >= 0) { - eventCache.splice(index, 1); - } - } else { - each(eventCache, function (event) { - node.removeEventListener(eventName, event); - }); - eventCache = []; - } - } - - fn.extend({ - off: function (eventName, callback) { - return this.each(function (v) { - return removeEvent(v, eventName, callback); - }); - }, - - on: function (eventName, delegate, callback, runOnce) { - // jshint ignore:line - var originalCallback; - if (!isString(eventName)) { - for (var key in eventName) { - this.on(key, delegate, eventName[key]); - } - return this; - } - - if (isFunction(delegate)) { - callback = delegate; - delegate = null; - } - - if (eventName === "ready") { - onReady(callback); - return this; - } - - if (delegate) { - originalCallback = callback; - callback = function (e) { - var t = e.target; - while (!matches(t, delegate)) { - if (t === this || t === null) { - return (t = false); - } - - t = t.parentNode; - } - - if (t) { - originalCallback.call(t, e); - } - }; - } - - return this.each(function (v) { - var finalCallback = callback; - if (runOnce) { - finalCallback = function () { - callback.apply(this, arguments); - removeEvent(v, eventName, finalCallback); - }; - } - registerEvent(v, eventName, finalCallback); - }); - }, - - one: function (eventName, delegate, callback) { - return this.on(eventName, delegate, callback, true); - }, - - ready: onReady, - - /** - * Modified - * Triggers browser event - * @param String eventName - * @param Object data - Add properties to event object - */ - trigger: function (eventName, data) { - if (document.createEvent) { - let evt = document.createEvent('HTMLEvents'); - evt.initEvent(eventName, true, false); - evt = this.extend(evt, data); - return this.each(function (v) { - return v.dispatchEvent(evt); - }); - } - } - - }); - - function encode(name, value) { - return "&" + encodeURIComponent(name) + "=" + encodeURIComponent(value).replace(/%20/g, "+"); - } - - function getSelectMultiple_(el) { - var values = []; - each(el.options, function (o) { - if (o.selected) { - values.push(o.value); - } - }); - return values.length ? values : null; - } - - function getSelectSingle_(el) { - var selectedIndex = el.selectedIndex; - return selectedIndex >= 0 ? el.options[selectedIndex].value : null; - } - - function getValue(el) { - var type = el.type; - if (!type) { - return null; - } - switch (type.toLowerCase()) { - case "select-one": - return getSelectSingle_(el); - case "select-multiple": - return getSelectMultiple_(el); - case "radio": - return (el.checked) ? el.value : null; - case "checkbox": - return (el.checked) ? el.value : null; - default: - return el.value ? el.value : null; - } - } - - fn.extend({ - serialize: function () { - var query = ""; - - each(this[0].elements || this, function (el) { - if (el.disabled || el.tagName === "FIELDSET") { - return; - } - var name = el.name; - switch (el.type.toLowerCase()) { - case "file": - case "reset": - case "submit": - case "button": - break; - case "select-multiple": - var values = getValue(el); - if (values !== null) { - each(values, function (value) { - query += encode(name, value); - }); - } - break; - default: - var value = getValue(el); - if (value !== null) { - query += encode(name, value); - } - } - }); - - return query.substr(1); - }, - - val: function (value) { - if (value === undefined) { - return getValue(this[0]); - } - - return this.each(function (v) { - return v.value = value; - }); - } - - }); - - function insertElement(el, child, prepend) { - if (prepend) { - var first = el.childNodes[0]; - el.insertBefore(child, first); - } else { - el.appendChild(child); - } - } - - function insertContent(parent, child, prepend) { - var str = isString(child); - - if (!str && child.length) { - each(child, function (v) { - return insertContent(parent, v, prepend); - }); - return; - } - - each(parent, str ? function (v) { - return v.insertAdjacentHTML(prepend ? "afterbegin" : "beforeend", child); - } : function (v, i) { - return insertElement(v, (i === 0 ? child : child.cloneNode(true)), prepend); - }); - } - - fn.extend({ - after: function (selector) { - cash(selector).insertAfter(this); - return this; - }, - - append: function (content) { - insertContent(this, content); - return this; - }, - - appendTo: function (parent) { - insertContent(cash(parent), this); - return this; - }, - - before: function (selector) { - cash(selector).insertBefore(this); - return this; - }, - - clone: function () { - return cash(this.map(function (v) { - return v.cloneNode(true); - })); - }, - - empty: function () { - this.html(""); - return this; - }, - - html: function (content) { - if (content === undefined) { - return this[0].innerHTML; - } - var source = (content.nodeType ? content[0].outerHTML : content); - return this.each(function (v) { - return v.innerHTML = source; - }); - }, - - insertAfter: function (selector) { - var _this = this; - - - cash(selector).each(function (el, i) { - var parent = el.parentNode, sibling = el.nextSibling; - _this.each(function (v) { - parent.insertBefore((i === 0 ? v : v.cloneNode(true)), sibling); - }); - }); - - return this; - }, - - insertBefore: function (selector) { - var _this2 = this; - cash(selector).each(function (el, i) { - var parent = el.parentNode; - _this2.each(function (v) { - parent.insertBefore((i === 0 ? v : v.cloneNode(true)), el); - }); - }); - return this; - }, - - prepend: function (content) { - insertContent(this, content, true); - return this; - }, - - prependTo: function (parent) { - insertContent(cash(parent), this, true); - return this; - }, - - remove: function () { - return this.each(function (v) { - if (!!v.parentNode) { - return v.parentNode.removeChild(v); - } - }); - }, - - text: function (content) { - if (content === undefined) { - return this[0].textContent; - } - return this.each(function (v) { - return v.textContent = content; - }); - } - - }); - - var docEl = doc.documentElement; - - fn.extend({ - position: function () { - var el = this[0]; - return { - left: el.offsetLeft, - top: el.offsetTop - }; - }, - - offset: function () { - var rect = this[0].getBoundingClientRect(); - return { - top: rect.top + win.pageYOffset - docEl.clientTop, - left: rect.left + win.pageXOffset - docEl.clientLeft - }; - }, - - offsetParent: function () { - return cash(this[0].offsetParent); - } - - }); - - fn.extend({ - children: function (selector) { - var elems = []; - this.each(function (el) { - push.apply(elems, el.children); - }); - elems = unique(elems); - - return (!selector ? elems : elems.filter(function (v) { - return matches(v, selector); - })); - }, - - closest: function (selector) { - if (!selector || this.length < 1) { - return cash(); - } - if (this.is(selector)) { - return this.filter(selector); - } - return this.parent().closest(selector); - }, - - is: function (selector) { - if (!selector) { - return false; - } - - var match = false, comparator = getCompareFunction(selector); - - this.each(function (el) { - match = comparator(el, selector); - return !match; - }); - - return match; - }, - - find: function (selector) { - if (!selector || selector.nodeType) { - return cash(selector && this.has(selector).length ? selector : null); - } - - var elems = []; - this.each(function (el) { - push.apply(elems, find(selector, el)); - }); - - return unique(elems); - }, - - has: function (selector) { - var comparator = (isString(selector) ? function (el) { - return find(selector, el).length !== 0; - } : function (el) { - return el.contains(selector); - }); - - return this.filter(comparator); - }, - - next: function () { - return cash(this[0].nextElementSibling); - }, - - not: function (selector) { - if (!selector) { - return this; - } - - var comparator = getCompareFunction(selector); - - return this.filter(function (el) { - return !comparator(el, selector); - }); - }, - - parent: function () { - var result = []; - - this.each(function (item) { - if (item && item.parentNode) { - result.push(item.parentNode); - } - }); - - return unique(result); - }, - - parents: function (selector) { - var last, result = []; - - this.each(function (item) { - last = item; - - while (last && last.parentNode && last !== doc.body.parentNode) { - last = last.parentNode; - - if (!selector || (selector && matches(last, selector))) { - result.push(last); - } - } - }); - - return unique(result); - }, - - prev: function () { - return cash(this[0].previousElementSibling); - }, - - siblings: function (selector) { - var collection = this.parent().children(selector), el = this[0]; - - return collection.filter(function (i) { - return i !== el; - }); - } - - }); - - - return cash; -}); diff --git a/static/js/characterCounter.js b/static/js/characterCounter.js deleted file mode 100644 index 761d1bfc..00000000 --- a/static/js/characterCounter.js +++ /dev/null @@ -1,136 +0,0 @@ -(function($) { - 'use strict'; - - let _defaults = {}; - - /** - * @class - * - */ - class CharacterCounter extends Component { - /** - * Construct CharacterCounter instance - * @constructor - * @param {Element} el - * @param {Object} options - */ - constructor(el, options) { - super(CharacterCounter, el, options); - - this.el.M_CharacterCounter = this; - - /** - * Options for the character counter - */ - this.options = $.extend({}, CharacterCounter.defaults, options); - - this.isInvalid = false; - this.isValidLength = false; - this._setupCounter(); - this._setupEventHandlers(); - } - - static get defaults() { - return _defaults; - } - - static init(els, options) { - return super.init(this, els, options); - } - - /** - * Get Instance - */ - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_CharacterCounter; - } - - /** - * Teardown component - */ - destroy() { - this._removeEventHandlers(); - this.el.CharacterCounter = undefined; - this._removeCounter(); - } - - /** - * Setup Event Handlers - */ - _setupEventHandlers() { - this._handleUpdateCounterBound = this.updateCounter.bind(this); - - this.el.addEventListener('focus', this._handleUpdateCounterBound, true); - this.el.addEventListener('input', this._handleUpdateCounterBound, true); - } - - /** - * Remove Event Handlers - */ - _removeEventHandlers() { - this.el.removeEventListener('focus', this._handleUpdateCounterBound, true); - this.el.removeEventListener('input', this._handleUpdateCounterBound, true); - } - - /** - * Setup counter element - */ - _setupCounter() { - this.counterEl = document.createElement('span'); - $(this.counterEl) - .addClass('character-counter') - .css({ - float: 'right', - 'font-size': '12px', - height: 1 - }); - - this.$el.parent().append(this.counterEl); - } - - /** - * Remove counter element - */ - _removeCounter() { - $(this.counterEl).remove(); - } - - /** - * Update counter - */ - updateCounter() { - let maxLength = +this.$el.attr('data-length'), - actualLength = this.el.value.length; - this.isValidLength = actualLength <= maxLength; - let counterString = actualLength; - - if (maxLength) { - counterString += '/' + maxLength; - this._validateInput(); - } - - $(this.counterEl).html(counterString); - } - - /** - * Add validation classes - */ - _validateInput() { - if (this.isValidLength && this.isInvalid) { - this.isInvalid = false; - this.$el.removeClass('invalid'); - } else if (!this.isValidLength && !this.isInvalid) { - this.isInvalid = true; - this.$el.removeClass('valid'); - this.$el.addClass('invalid'); - } - } - } - - M.CharacterCounter = CharacterCounter; - - if (M.jQueryLoaded) { - M.initializeJqueryWrapper(CharacterCounter, 'characterCounter', 'M_CharacterCounter'); - } -})(cash); diff --git a/static/js/chips.js b/static/js/chips.js deleted file mode 100644 index 84fd9341..00000000 --- a/static/js/chips.js +++ /dev/null @@ -1,481 +0,0 @@ -(function($) { - 'use strict'; - - let _defaults = { - data: [], - placeholder: '', - secondaryPlaceholder: '', - autocompleteOptions: {}, - limit: Infinity, - onChipAdd: null, - onChipSelect: null, - onChipDelete: null - }; - - /** - * @typedef {Object} chip - * @property {String} tag chip tag string - * @property {String} [image] chip avatar image string - */ - - /** - * @class - * - */ - class Chips extends Component { - /** - * Construct Chips instance and set up overlay - * @constructor - * @param {Element} el - * @param {Object} options - */ - constructor(el, options) { - super(Chips, el, options); - - this.el.M_Chips = this; - - /** - * Options for the modal - * @member Chips#options - * @prop {Array} data - * @prop {String} placeholder - * @prop {String} secondaryPlaceholder - * @prop {Object} autocompleteOptions - */ - this.options = $.extend({}, Chips.defaults, options); - - this.$el.addClass('chips input-field'); - this.chipsData = []; - this.$chips = $(); - this._setupInput(); - this.hasAutocomplete = Object.keys(this.options.autocompleteOptions).length > 0; - - // Set input id - if (!this.$input.attr('id')) { - this.$input.attr('id', M.guid()); - } - - // Render initial chips - if (this.options.data.length) { - this.chipsData = this.options.data; - this._renderChips(this.chipsData); - } - - // Setup autocomplete if needed - if (this.hasAutocomplete) { - this._setupAutocomplete(); - } - - this._setPlaceholder(); - this._setupLabel(); - this._setupEventHandlers(); - } - - static get defaults() { - return _defaults; - } - - static init(els, options) { - return super.init(this, els, options); - } - - /** - * Get Instance - */ - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_Chips; - } - - /** - * Get Chips Data - */ - getData() { - return this.chipsData; - } - - /** - * Teardown component - */ - destroy() { - this._removeEventHandlers(); - this.$chips.remove(); - this.el.M_Chips = undefined; - } - - /** - * Setup Event Handlers - */ - _setupEventHandlers() { - this._handleChipClickBound = this._handleChipClick.bind(this); - this._handleInputKeydownBound = this._handleInputKeydown.bind(this); - this._handleInputFocusBound = this._handleInputFocus.bind(this); - this._handleInputBlurBound = this._handleInputBlur.bind(this); - - this.el.addEventListener('click', this._handleChipClickBound); - document.addEventListener('keydown', Chips._handleChipsKeydown); - document.addEventListener('keyup', Chips._handleChipsKeyup); - this.el.addEventListener('blur', Chips._handleChipsBlur, true); - this.$input[0].addEventListener('focus', this._handleInputFocusBound); - this.$input[0].addEventListener('blur', this._handleInputBlurBound); - this.$input[0].addEventListener('keydown', this._handleInputKeydownBound); - } - - /** - * Remove Event Handlers - */ - _removeEventHandlers() { - this.el.removeEventListener('click', this._handleChipClickBound); - document.removeEventListener('keydown', Chips._handleChipsKeydown); - document.removeEventListener('keyup', Chips._handleChipsKeyup); - this.el.removeEventListener('blur', Chips._handleChipsBlur, true); - this.$input[0].removeEventListener('focus', this._handleInputFocusBound); - this.$input[0].removeEventListener('blur', this._handleInputBlurBound); - this.$input[0].removeEventListener('keydown', this._handleInputKeydownBound); - } - - /** - * Handle Chip Click - * @param {Event} e - */ - _handleChipClick(e) { - let $chip = $(e.target).closest('.chip'); - let clickedClose = $(e.target).is('.close'); - if ($chip.length) { - let index = $chip.index(); - if (clickedClose) { - // delete chip - this.deleteChip(index); - this.$input[0].focus(); - } else { - // select chip - this.selectChip(index); - } - - // Default handle click to focus on input - } else { - this.$input[0].focus(); - } - } - - /** - * Handle Chips Keydown - * @param {Event} e - */ - static _handleChipsKeydown(e) { - Chips._keydown = true; - - let $chips = $(e.target).closest('.chips'); - let chipsKeydown = e.target && $chips.length; - - // Don't handle keydown inputs on input and textarea - if ($(e.target).is('input, textarea') || !chipsKeydown) { - return; - } - - let currChips = $chips[0].M_Chips; - - // backspace and delete - if (e.keyCode === 8 || e.keyCode === 46) { - e.preventDefault(); - - let selectIndex = currChips.chipsData.length; - if (currChips._selectedChip) { - let index = currChips._selectedChip.index(); - currChips.deleteChip(index); - currChips._selectedChip = null; - - // Make sure selectIndex doesn't go negative - selectIndex = Math.max(index - 1, 0); - } - - if (currChips.chipsData.length) { - currChips.selectChip(selectIndex); - } - - // left arrow key - } else if (e.keyCode === 37) { - if (currChips._selectedChip) { - let selectIndex = currChips._selectedChip.index() - 1; - if (selectIndex < 0) { - return; - } - currChips.selectChip(selectIndex); - } - - // right arrow key - } else if (e.keyCode === 39) { - if (currChips._selectedChip) { - let selectIndex = currChips._selectedChip.index() + 1; - - if (selectIndex >= currChips.chipsData.length) { - currChips.$input[0].focus(); - } else { - currChips.selectChip(selectIndex); - } - } - } - } - - /** - * Handle Chips Keyup - * @param {Event} e - */ - static _handleChipsKeyup(e) { - Chips._keydown = false; - } - - /** - * Handle Chips Blur - * @param {Event} e - */ - static _handleChipsBlur(e) { - if (!Chips._keydown) { - let $chips = $(e.target).closest('.chips'); - let currChips = $chips[0].M_Chips; - - currChips._selectedChip = null; - } - } - - /** - * Handle Input Focus - */ - _handleInputFocus() { - this.$el.addClass('focus'); - } - - /** - * Handle Input Blur - */ - _handleInputBlur() { - this.$el.removeClass('focus'); - } - - /** - * Handle Input Keydown - * @param {Event} e - */ - _handleInputKeydown(e) { - Chips._keydown = true; - - // enter - if (e.keyCode === 13) { - // Override enter if autocompleting. - if (this.hasAutocomplete && this.autocomplete && this.autocomplete.isOpen) { - return; - } - - e.preventDefault(); - this.addChip({ - tag: this.$input[0].value - }); - this.$input[0].value = ''; - - // delete or left - } else if ( - (e.keyCode === 8 || e.keyCode === 37) && - this.$input[0].value === '' && - this.chipsData.length - ) { - e.preventDefault(); - this.selectChip(this.chipsData.length - 1); - } - } - - /** - * Render Chip - * @param {chip} chip - * @return {Element} - */ - _renderChip(chip) { - if (!chip.tag) { - return; - } - - let renderedChip = document.createElement('div'); - let closeIcon = document.createElement('i'); - renderedChip.classList.add('chip'); - renderedChip.textContent = chip.tag; - renderedChip.setAttribute('tabindex', 0); - $(closeIcon).addClass('material-icons close'); - closeIcon.textContent = 'close'; - - // attach image if needed - if (chip.image) { - let img = document.createElement('img'); - img.setAttribute('src', chip.image); - renderedChip.insertBefore(img, renderedChip.firstChild); - } - - renderedChip.appendChild(closeIcon); - return renderedChip; - } - - /** - * Render Chips - */ - _renderChips() { - this.$chips.remove(); - for (let i = 0; i < this.chipsData.length; i++) { - let chipEl = this._renderChip(this.chipsData[i]); - this.$el.append(chipEl); - this.$chips.add(chipEl); - } - - // move input to end - this.$el.append(this.$input[0]); - } - - /** - * Setup Autocomplete - */ - _setupAutocomplete() { - this.options.autocompleteOptions.onAutocomplete = (val) => { - this.addChip({ - tag: val - }); - this.$input[0].value = ''; - this.$input[0].focus(); - }; - - this.autocomplete = M.Autocomplete.init(this.$input[0], this.options.autocompleteOptions); - } - - /** - * Setup Input - */ - _setupInput() { - this.$input = this.$el.find('input'); - if (!this.$input.length) { - this.$input = $(''); - this.$el.append(this.$input); - } - - this.$input.addClass('input'); - } - - /** - * Setup Label - */ - _setupLabel() { - this.$label = this.$el.find('label'); - if (this.$label.length) { - this.$label.setAttribute('for', this.$input.attr('id')); - } - } - - /** - * Set placeholder - */ - _setPlaceholder() { - if (this.chipsData !== undefined && !this.chipsData.length && this.options.placeholder) { - $(this.$input).prop('placeholder', this.options.placeholder); - } else if ( - (this.chipsData === undefined || !!this.chipsData.length) && - this.options.secondaryPlaceholder - ) { - $(this.$input).prop('placeholder', this.options.secondaryPlaceholder); - } - } - - /** - * Check if chip is valid - * @param {chip} chip - */ - _isValid(chip) { - if (chip.hasOwnProperty('tag') && chip.tag !== '') { - let exists = false; - for (let i = 0; i < this.chipsData.length; i++) { - if (this.chipsData[i].tag === chip.tag) { - exists = true; - break; - } - } - return !exists; - } - - return false; - } - - /** - * Add chip - * @param {chip} chip - */ - addChip(chip) { - if (!this._isValid(chip) || this.chipsData.length >= this.options.limit) { - return; - } - - let renderedChip = this._renderChip(chip); - this.$chips.add(renderedChip); - this.chipsData.push(chip); - $(this.$input).before(renderedChip); - this._setPlaceholder(); - - // fire chipAdd callback - if (typeof this.options.onChipAdd === 'function') { - this.options.onChipAdd.call(this, this.$el, renderedChip); - } - } - - /** - * Delete chip - * @param {Number} chip - */ - deleteChip(chipIndex) { - let $chip = this.$chips.eq(chipIndex); - this.$chips.eq(chipIndex).remove(); - this.$chips = this.$chips.filter(function(el) { - return $(el).index() >= 0; - }); - this.chipsData.splice(chipIndex, 1); - this._setPlaceholder(); - - // fire chipDelete callback - if (typeof this.options.onChipDelete === 'function') { - this.options.onChipDelete.call(this, this.$el, $chip[0]); - } - } - - /** - * Select chip - * @param {Number} chip - */ - selectChip(chipIndex) { - let $chip = this.$chips.eq(chipIndex); - this._selectedChip = $chip; - $chip[0].focus(); - - // fire chipSelect callback - if (typeof this.options.onChipSelect === 'function') { - this.options.onChipSelect.call(this, this.$el, $chip[0]); - } - } - } - - /** - * @static - * @memberof Chips - */ - Chips._keydown = false; - - M.Chips = Chips; - - if (M.jQueryLoaded) { - M.initializeJqueryWrapper(Chips, 'chips', 'M_Chips'); - } - - $(document).ready(function() { - // Handle removal of static chips. - $(document.body).on('click', '.chip .close', function() { - let $chips = $(this).closest('.chips'); - if ($chips.length && $chips[0].M_Chips) { - return; - } - $(this) - .closest('.chip') - .remove(); - }); - }); -})(cash); diff --git a/static/js/collapsible.js b/static/js/collapsible.js deleted file mode 100644 index 6c491261..00000000 --- a/static/js/collapsible.js +++ /dev/null @@ -1,275 +0,0 @@ -(function($, anim) { - 'use strict'; - - let _defaults = { - accordion: true, - onOpenStart: undefined, - onOpenEnd: undefined, - onCloseStart: undefined, - onCloseEnd: undefined, - inDuration: 300, - outDuration: 300 - }; - - /** - * @class - * - */ - class Collapsible extends Component { - /** - * Construct Collapsible instance - * @constructor - * @param {Element} el - * @param {Object} options - */ - constructor(el, options) { - super(Collapsible, el, options); - - this.el.M_Collapsible = this; - - /** - * Options for the collapsible - * @member Collapsible#options - * @prop {Boolean} [accordion=false] - Type of the collapsible - * @prop {Function} onOpenStart - Callback function called before collapsible is opened - * @prop {Function} onOpenEnd - Callback function called after collapsible is opened - * @prop {Function} onCloseStart - Callback function called before collapsible is closed - * @prop {Function} onCloseEnd - Callback function called after collapsible is closed - * @prop {Number} inDuration - Transition in duration in milliseconds. - * @prop {Number} outDuration - Transition duration in milliseconds. - */ - this.options = $.extend({}, Collapsible.defaults, options); - - // Setup tab indices - this.$headers = this.$el.children('li').children('.collapsible-header'); - this.$headers.attr('tabindex', 0); - - this._setupEventHandlers(); - - // Open first active - let $activeBodies = this.$el.children('li.active').children('.collapsible-body'); - if (this.options.accordion) { - // Handle Accordion - $activeBodies.first().css('display', 'block'); - } else { - // Handle Expandables - $activeBodies.css('display', 'block'); - } - } - - static get defaults() { - return _defaults; - } - - static init(els, options) { - return super.init(this, els, options); - } - - /** - * Get Instance - */ - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_Collapsible; - } - - /** - * Teardown component - */ - destroy() { - this._removeEventHandlers(); - this.el.M_Collapsible = undefined; - } - - /** - * Setup Event Handlers - */ - _setupEventHandlers() { - this._handleCollapsibleClickBound = this._handleCollapsibleClick.bind(this); - this._handleCollapsibleKeydownBound = this._handleCollapsibleKeydown.bind(this); - this.el.addEventListener('click', this._handleCollapsibleClickBound); - this.$headers.each((header) => { - header.addEventListener('keydown', this._handleCollapsibleKeydownBound); - }); - } - - /** - * Remove Event Handlers - */ - _removeEventHandlers() { - this.el.removeEventListener('click', this._handleCollapsibleClickBound); - this.$headers.each((header) => { - header.removeEventListener('keydown', this._handleCollapsibleKeydownBound); - }); - } - - /** - * Handle Collapsible Click - * @param {Event} e - */ - _handleCollapsibleClick(e) { - let $header = $(e.target).closest('.collapsible-header'); - if (e.target && $header.length) { - let $collapsible = $header.closest('.collapsible'); - if ($collapsible[0] === this.el) { - let $collapsibleLi = $header.closest('li'); - let $collapsibleLis = $collapsible.children('li'); - let isActive = $collapsibleLi[0].classList.contains('active'); - let index = $collapsibleLis.index($collapsibleLi); - - if (isActive) { - this.close(index); - } else { - this.open(index); - } - } - } - } - - /** - * Handle Collapsible Keydown - * @param {Event} e - */ - _handleCollapsibleKeydown(e) { - if (e.keyCode === 13) { - this._handleCollapsibleClickBound(e); - } - } - - /** - * Animate in collapsible slide - * @param {Number} index - 0th index of slide - */ - _animateIn(index) { - let $collapsibleLi = this.$el.children('li').eq(index); - if ($collapsibleLi.length) { - let $body = $collapsibleLi.children('.collapsible-body'); - - anim.remove($body[0]); - $body.css({ - display: 'block', - overflow: 'hidden', - height: 0, - paddingTop: '', - paddingBottom: '' - }); - - let pTop = $body.css('padding-top'); - let pBottom = $body.css('padding-bottom'); - let finalHeight = $body[0].scrollHeight; - $body.css({ - paddingTop: 0, - paddingBottom: 0 - }); - - anim({ - targets: $body[0], - height: finalHeight, - paddingTop: pTop, - paddingBottom: pBottom, - duration: this.options.inDuration, - easing: 'easeInOutCubic', - complete: (anim) => { - $body.css({ - overflow: '', - paddingTop: '', - paddingBottom: '', - height: '' - }); - - // onOpenEnd callback - if (typeof this.options.onOpenEnd === 'function') { - this.options.onOpenEnd.call(this, $collapsibleLi[0]); - } - } - }); - } - } - - /** - * Animate out collapsible slide - * @param {Number} index - 0th index of slide to open - */ - _animateOut(index) { - let $collapsibleLi = this.$el.children('li').eq(index); - if ($collapsibleLi.length) { - let $body = $collapsibleLi.children('.collapsible-body'); - anim.remove($body[0]); - $body.css('overflow', 'hidden'); - anim({ - targets: $body[0], - height: 0, - paddingTop: 0, - paddingBottom: 0, - duration: this.options.outDuration, - easing: 'easeInOutCubic', - complete: () => { - $body.css({ - height: '', - overflow: '', - padding: '', - display: '' - }); - - // onCloseEnd callback - if (typeof this.options.onCloseEnd === 'function') { - this.options.onCloseEnd.call(this, $collapsibleLi[0]); - } - } - }); - } - } - - /** - * Open Collapsible - * @param {Number} index - 0th index of slide - */ - open(index) { - let $collapsibleLi = this.$el.children('li').eq(index); - if ($collapsibleLi.length && !$collapsibleLi[0].classList.contains('active')) { - // onOpenStart callback - if (typeof this.options.onOpenStart === 'function') { - this.options.onOpenStart.call(this, $collapsibleLi[0]); - } - - // Handle accordion behavior - if (this.options.accordion) { - let $collapsibleLis = this.$el.children('li'); - let $activeLis = this.$el.children('li.active'); - $activeLis.each((el) => { - let index = $collapsibleLis.index($(el)); - this.close(index); - }); - } - - // Animate in - $collapsibleLi[0].classList.add('active'); - this._animateIn(index); - } - } - - /** - * Close Collapsible - * @param {Number} index - 0th index of slide - */ - close(index) { - let $collapsibleLi = this.$el.children('li').eq(index); - if ($collapsibleLi.length && $collapsibleLi[0].classList.contains('active')) { - // onCloseStart callback - if (typeof this.options.onCloseStart === 'function') { - this.options.onCloseStart.call(this, $collapsibleLi[0]); - } - - // Animate out - $collapsibleLi[0].classList.remove('active'); - this._animateOut(index); - } - } - } - - M.Collapsible = Collapsible; - - if (M.jQueryLoaded) { - M.initializeJqueryWrapper(Collapsible, 'collapsible', 'M_Collapsible'); - } -})(cash, M.anime); diff --git a/static/js/component.js b/static/js/component.js deleted file mode 100644 index 19eea9fd..00000000 --- a/static/js/component.js +++ /dev/null @@ -1,44 +0,0 @@ -class Component { - /** - * Generic constructor for all components - * @constructor - * @param {Element} el - * @param {Object} options - */ - constructor(classDef, el, options) { - // Display error if el is valid HTML Element - if (!(el instanceof Element)) { - console.error(Error(el + ' is not an HTML Element')); - } - - // If exists, destroy and reinitialize in child - let ins = classDef.getInstance(el); - if (!!ins) { - ins.destroy(); - } - - this.el = el; - this.$el = cash(el); - } - - /** - * Initializes components - * @param {class} classDef - * @param {Element | NodeList | jQuery} els - * @param {Object} options - */ - static init(classDef, els, options) { - let instances = null; - if (els instanceof Element) { - instances = new classDef(els, options); - } else if (!!els && (els.jquery || els.cash || els instanceof NodeList)) { - let instancesArr = []; - for (let i = 0; i < els.length; i++) { - instancesArr.push(new classDef(els[i], options)); - } - instances = instancesArr; - } - - return instances; - } -} diff --git a/static/js/datepicker.js b/static/js/datepicker.js deleted file mode 100644 index 7199dd27..00000000 --- a/static/js/datepicker.js +++ /dev/null @@ -1,975 +0,0 @@ -(function($) { - 'use strict'; - - let _defaults = { - // Close when date is selected - autoClose: false, - - // the default output format for the input field value - format: 'mmm dd, yyyy', - - // Used to create date object from current input string - parse: null, - - // The initial date to view when first opened - defaultDate: null, - - // Make the `defaultDate` the initial selected value - setDefaultDate: false, - - disableWeekends: false, - - disableDayFn: null, - - // First day of week (0: Sunday, 1: Monday etc) - firstDay: 0, - - // The earliest date that can be selected - minDate: null, - // Thelatest date that can be selected - maxDate: null, - - // Number of years either side, or array of upper/lower range - yearRange: 10, - - // used internally (don't config outside) - minYear: 0, - maxYear: 9999, - minMonth: undefined, - maxMonth: undefined, - - startRange: null, - endRange: null, - - isRTL: false, - - // Render the month after year in the calendar title - showMonthAfterYear: false, - - // Render days of the calendar grid that fall in the next or previous month - showDaysInNextAndPreviousMonths: false, - - // Specify a DOM element to render the calendar in - container: null, - - // Show clear button - showClearBtn: false, - - // internationalization - i18n: { - cancel: 'Cancel', - clear: 'Clear', - done: 'Ok', - previousMonth: '‹', - nextMonth: '›', - months: [ - 'January', - 'February', - 'March', - 'April', - 'May', - 'June', - 'July', - 'August', - 'September', - 'October', - 'November', - 'December' - ], - monthsShort: [ - 'Jan', - 'Feb', - 'Mar', - 'Apr', - 'May', - 'Jun', - 'Jul', - 'Aug', - 'Sep', - 'Oct', - 'Nov', - 'Dec' - ], - weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], - weekdaysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], - weekdaysAbbrev: ['S', 'M', 'T', 'W', 'T', 'F', 'S'] - }, - - // events array - events: [], - - // callback function - onSelect: null, - onOpen: null, - onClose: null, - onDraw: null - }; - - /** - * @class - * - */ - class Datepicker extends Component { - /** - * Construct Datepicker instance and set up overlay - * @constructor - * @param {Element} el - * @param {Object} options - */ - constructor(el, options) { - super(Datepicker, el, options); - - this.el.M_Datepicker = this; - - this.options = $.extend({}, Datepicker.defaults, options); - - // make sure i18n defaults are not lost when only few i18n option properties are passed - if (!!options && options.hasOwnProperty('i18n') && typeof options.i18n === 'object') { - this.options.i18n = $.extend({}, Datepicker.defaults.i18n, options.i18n); - } - - // Remove time component from minDate and maxDate options - if (this.options.minDate) this.options.minDate.setHours(0, 0, 0, 0); - if (this.options.maxDate) this.options.maxDate.setHours(0, 0, 0, 0); - - this.id = M.guid(); - - this._setupVariables(); - this._insertHTMLIntoDOM(); - this._setupModal(); - - this._setupEventHandlers(); - - if (!this.options.defaultDate) { - this.options.defaultDate = new Date(Date.parse(this.el.value)); - } - - let defDate = this.options.defaultDate; - if (Datepicker._isDate(defDate)) { - if (this.options.setDefaultDate) { - this.setDate(defDate, true); - this.setInputValue(); - } else { - this.gotoDate(defDate); - } - } else { - this.gotoDate(new Date()); - } - - /** - * Describes open/close state of datepicker - * @type {Boolean} - */ - this.isOpen = false; - } - - static get defaults() { - return _defaults; - } - - static init(els, options) { - return super.init(this, els, options); - } - - static _isDate(obj) { - return /Date/.test(Object.prototype.toString.call(obj)) && !isNaN(obj.getTime()); - } - - static _isWeekend(date) { - let day = date.getDay(); - return day === 0 || day === 6; - } - - static _setToStartOfDay(date) { - if (Datepicker._isDate(date)) date.setHours(0, 0, 0, 0); - } - - static _getDaysInMonth(year, month) { - return [31, Datepicker._isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][ - month - ]; - } - - static _isLeapYear(year) { - // solution by Matti Virkkunen: http://stackoverflow.com/a/4881951 - return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; - } - - static _compareDates(a, b) { - // weak date comparison (use setToStartOfDay(date) to ensure correct result) - return a.getTime() === b.getTime(); - } - - static _setToStartOfDay(date) { - if (Datepicker._isDate(date)) date.setHours(0, 0, 0, 0); - } - - /** - * Get Instance - */ - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_Datepicker; - } - - /** - * Teardown component - */ - destroy() { - this._removeEventHandlers(); - this.modal.destroy(); - $(this.modalEl).remove(); - this.destroySelects(); - this.el.M_Datepicker = undefined; - } - - destroySelects() { - let oldYearSelect = this.calendarEl.querySelector('.orig-select-year'); - if (oldYearSelect) { - M.FormSelect.getInstance(oldYearSelect).destroy(); - } - let oldMonthSelect = this.calendarEl.querySelector('.orig-select-month'); - if (oldMonthSelect) { - M.FormSelect.getInstance(oldMonthSelect).destroy(); - } - } - - _insertHTMLIntoDOM() { - if (this.options.showClearBtn) { - $(this.clearBtn).css({ visibility: '' }); - this.clearBtn.innerHTML = this.options.i18n.clear; - } - - this.doneBtn.innerHTML = this.options.i18n.done; - this.cancelBtn.innerHTML = this.options.i18n.cancel; - - if (this.options.container) { - this.$modalEl.appendTo(this.options.container); - } else { - this.$modalEl.insertBefore(this.el); - } - } - - _setupModal() { - this.modalEl.id = 'modal-' + this.id; - this.modal = M.Modal.init(this.modalEl, { - onCloseEnd: () => { - this.isOpen = false; - } - }); - } - - toString(format) { - format = format || this.options.format; - if (!Datepicker._isDate(this.date)) { - return ''; - } - - let formatArray = format.split(/(d{1,4}|m{1,4}|y{4}|yy|!.)/g); - let formattedDate = formatArray - .map((label) => { - if (this.formats[label]) { - return this.formats[label](); - } - - return label; - }) - .join(''); - return formattedDate; - } - - setDate(date, preventOnSelect) { - if (!date) { - this.date = null; - this._renderDateDisplay(); - return this.draw(); - } - if (typeof date === 'string') { - date = new Date(Date.parse(date)); - } - if (!Datepicker._isDate(date)) { - return; - } - - let min = this.options.minDate, - max = this.options.maxDate; - - if (Datepicker._isDate(min) && date < min) { - date = min; - } else if (Datepicker._isDate(max) && date > max) { - date = max; - } - - this.date = new Date(date.getTime()); - - this._renderDateDisplay(); - - Datepicker._setToStartOfDay(this.date); - this.gotoDate(this.date); - - if (!preventOnSelect && typeof this.options.onSelect === 'function') { - this.options.onSelect.call(this, this.date); - } - } - - setInputValue() { - this.el.value = this.toString(); - this.$el.trigger('change', { firedBy: this }); - } - - _renderDateDisplay() { - let displayDate = Datepicker._isDate(this.date) ? this.date : new Date(); - let i18n = this.options.i18n; - let day = i18n.weekdaysShort[displayDate.getDay()]; - let month = i18n.monthsShort[displayDate.getMonth()]; - let date = displayDate.getDate(); - this.yearTextEl.innerHTML = displayDate.getFullYear(); - this.dateTextEl.innerHTML = `${day}, ${month} ${date}`; - } - - /** - * change view to a specific date - */ - gotoDate(date) { - let newCalendar = true; - - if (!Datepicker._isDate(date)) { - return; - } - - if (this.calendars) { - let firstVisibleDate = new Date(this.calendars[0].year, this.calendars[0].month, 1), - lastVisibleDate = new Date( - this.calendars[this.calendars.length - 1].year, - this.calendars[this.calendars.length - 1].month, - 1 - ), - visibleDate = date.getTime(); - // get the end of the month - lastVisibleDate.setMonth(lastVisibleDate.getMonth() + 1); - lastVisibleDate.setDate(lastVisibleDate.getDate() - 1); - newCalendar = - visibleDate < firstVisibleDate.getTime() || lastVisibleDate.getTime() < visibleDate; - } - - if (newCalendar) { - this.calendars = [ - { - month: date.getMonth(), - year: date.getFullYear() - } - ]; - } - - this.adjustCalendars(); - } - - adjustCalendars() { - this.calendars[0] = this.adjustCalendar(this.calendars[0]); - this.draw(); - } - - adjustCalendar(calendar) { - if (calendar.month < 0) { - calendar.year -= Math.ceil(Math.abs(calendar.month) / 12); - calendar.month += 12; - } - if (calendar.month > 11) { - calendar.year += Math.floor(Math.abs(calendar.month) / 12); - calendar.month -= 12; - } - return calendar; - } - - nextMonth() { - this.calendars[0].month++; - this.adjustCalendars(); - } - - prevMonth() { - this.calendars[0].month--; - this.adjustCalendars(); - } - - render(year, month, randId) { - let opts = this.options, - now = new Date(), - days = Datepicker._getDaysInMonth(year, month), - before = new Date(year, month, 1).getDay(), - data = [], - row = []; - Datepicker._setToStartOfDay(now); - if (opts.firstDay > 0) { - before -= opts.firstDay; - if (before < 0) { - before += 7; - } - } - let previousMonth = month === 0 ? 11 : month - 1, - nextMonth = month === 11 ? 0 : month + 1, - yearOfPreviousMonth = month === 0 ? year - 1 : year, - yearOfNextMonth = month === 11 ? year + 1 : year, - daysInPreviousMonth = Datepicker._getDaysInMonth(yearOfPreviousMonth, previousMonth); - let cells = days + before, - after = cells; - while (after > 7) { - after -= 7; - } - cells += 7 - after; - let isWeekSelected = false; - for (let i = 0, r = 0; i < cells; i++) { - let day = new Date(year, month, 1 + (i - before)), - isSelected = Datepicker._isDate(this.date) - ? Datepicker._compareDates(day, this.date) - : false, - isToday = Datepicker._compareDates(day, now), - hasEvent = opts.events.indexOf(day.toDateString()) !== -1 ? true : false, - isEmpty = i < before || i >= days + before, - dayNumber = 1 + (i - before), - monthNumber = month, - yearNumber = year, - isStartRange = opts.startRange && Datepicker._compareDates(opts.startRange, day), - isEndRange = opts.endRange && Datepicker._compareDates(opts.endRange, day), - isInRange = - opts.startRange && opts.endRange && opts.startRange < day && day < opts.endRange, - isDisabled = - (opts.minDate && day < opts.minDate) || - (opts.maxDate && day > opts.maxDate) || - (opts.disableWeekends && Datepicker._isWeekend(day)) || - (opts.disableDayFn && opts.disableDayFn(day)); - - if (isEmpty) { - if (i < before) { - dayNumber = daysInPreviousMonth + dayNumber; - monthNumber = previousMonth; - yearNumber = yearOfPreviousMonth; - } else { - dayNumber = dayNumber - days; - monthNumber = nextMonth; - yearNumber = yearOfNextMonth; - } - } - - let dayConfig = { - day: dayNumber, - month: monthNumber, - year: yearNumber, - hasEvent: hasEvent, - isSelected: isSelected, - isToday: isToday, - isDisabled: isDisabled, - isEmpty: isEmpty, - isStartRange: isStartRange, - isEndRange: isEndRange, - isInRange: isInRange, - showDaysInNextAndPreviousMonths: opts.showDaysInNextAndPreviousMonths - }; - - row.push(this.renderDay(dayConfig)); - - if (++r === 7) { - data.push(this.renderRow(row, opts.isRTL, isWeekSelected)); - row = []; - r = 0; - isWeekSelected = false; - } - } - return this.renderTable(opts, data, randId); - } - - renderDay(opts) { - let arr = []; - let ariaSelected = 'false'; - if (opts.isEmpty) { - if (opts.showDaysInNextAndPreviousMonths) { - arr.push('is-outside-current-month'); - arr.push('is-selection-disabled'); - } else { - return ''; - } - } - if (opts.isDisabled) { - arr.push('is-disabled'); - } - - if (opts.isToday) { - arr.push('is-today'); - } - if (opts.isSelected) { - arr.push('is-selected'); - ariaSelected = 'true'; - } - if (opts.hasEvent) { - arr.push('has-event'); - } - if (opts.isInRange) { - arr.push('is-inrange'); - } - if (opts.isStartRange) { - arr.push('is-startrange'); - } - if (opts.isEndRange) { - arr.push('is-endrange'); - } - return ( - `` + - `` + - '' - ); - } - - renderRow(days, isRTL, isRowSelected) { - return ( - '' + - (isRTL ? days.reverse() : days).join('') + - '' - ); - } - - renderTable(opts, data, randId) { - return ( - '
        ' + - this.renderHead(opts) + - this.renderBody(data) + - '
        ' - ); - } - - renderHead(opts) { - let i, - arr = []; - for (i = 0; i < 7; i++) { - arr.push( - `${this.renderDayName( - opts, - i, - true - )}` - ); - } - return '' + (opts.isRTL ? arr.reverse() : arr).join('') + ''; - } - - renderBody(rows) { - return '' + rows.join('') + ''; - } - - renderTitle(instance, c, year, month, refYear, randId) { - let i, - j, - arr, - opts = this.options, - isMinYear = year === opts.minYear, - isMaxYear = year === opts.maxYear, - html = - '
        ', - monthHtml, - yearHtml, - prev = true, - next = true; - - for (arr = [], i = 0; i < 12; i++) { - arr.push( - '' - ); - } - - monthHtml = - ''; - - if ($.isArray(opts.yearRange)) { - i = opts.yearRange[0]; - j = opts.yearRange[1] + 1; - } else { - i = year - opts.yearRange; - j = 1 + year + opts.yearRange; - } - - for (arr = []; i < j && i <= opts.maxYear; i++) { - if (i >= opts.minYear) { - arr.push(``); - } - } - - yearHtml = ``; - - let leftArrow = - ''; - html += ``; - - html += '
        '; - if (opts.showMonthAfterYear) { - html += yearHtml + monthHtml; - } else { - html += monthHtml + yearHtml; - } - html += '
        '; - - if (isMinYear && (month === 0 || opts.minMonth >= month)) { - prev = false; - } - - if (isMaxYear && (month === 11 || opts.maxMonth <= month)) { - next = false; - } - - let rightArrow = - ''; - html += ``; - - return (html += '
        '); - } - - /** - * refresh the HTML - */ - draw(force) { - if (!this.isOpen && !force) { - return; - } - let opts = this.options, - minYear = opts.minYear, - maxYear = opts.maxYear, - minMonth = opts.minMonth, - maxMonth = opts.maxMonth, - html = '', - randId; - - if (this._y <= minYear) { - this._y = minYear; - if (!isNaN(minMonth) && this._m < minMonth) { - this._m = minMonth; - } - } - if (this._y >= maxYear) { - this._y = maxYear; - if (!isNaN(maxMonth) && this._m > maxMonth) { - this._m = maxMonth; - } - } - - randId = - 'datepicker-title-' + - Math.random() - .toString(36) - .replace(/[^a-z]+/g, '') - .substr(0, 2); - - for (let c = 0; c < 1; c++) { - this._renderDateDisplay(); - html += - this.renderTitle( - this, - c, - this.calendars[c].year, - this.calendars[c].month, - this.calendars[0].year, - randId - ) + this.render(this.calendars[c].year, this.calendars[c].month, randId); - } - - this.destroySelects(); - - this.calendarEl.innerHTML = html; - - // Init Materialize Select - let yearSelect = this.calendarEl.querySelector('.orig-select-year'); - let monthSelect = this.calendarEl.querySelector('.orig-select-month'); - M.FormSelect.init(yearSelect, { - classes: 'select-year', - dropdownOptions: { container: document.body, constrainWidth: false } - }); - M.FormSelect.init(monthSelect, { - classes: 'select-month', - dropdownOptions: { container: document.body, constrainWidth: false } - }); - - // Add change handlers for select - yearSelect.addEventListener('change', this._handleYearChange.bind(this)); - monthSelect.addEventListener('change', this._handleMonthChange.bind(this)); - - if (typeof this.options.onDraw === 'function') { - this.options.onDraw(this); - } - } - - /** - * Setup Event Handlers - */ - _setupEventHandlers() { - this._handleInputKeydownBound = this._handleInputKeydown.bind(this); - this._handleInputClickBound = this._handleInputClick.bind(this); - this._handleInputChangeBound = this._handleInputChange.bind(this); - this._handleCalendarClickBound = this._handleCalendarClick.bind(this); - this._finishSelectionBound = this._finishSelection.bind(this); - this._handleMonthChange = this._handleMonthChange.bind(this); - this._closeBound = this.close.bind(this); - - this.el.addEventListener('click', this._handleInputClickBound); - this.el.addEventListener('keydown', this._handleInputKeydownBound); - this.el.addEventListener('change', this._handleInputChangeBound); - this.calendarEl.addEventListener('click', this._handleCalendarClickBound); - this.doneBtn.addEventListener('click', this._finishSelectionBound); - this.cancelBtn.addEventListener('click', this._closeBound); - - if (this.options.showClearBtn) { - this._handleClearClickBound = this._handleClearClick.bind(this); - this.clearBtn.addEventListener('click', this._handleClearClickBound); - } - } - - _setupVariables() { - this.$modalEl = $(Datepicker._template); - this.modalEl = this.$modalEl[0]; - - this.calendarEl = this.modalEl.querySelector('.datepicker-calendar'); - - this.yearTextEl = this.modalEl.querySelector('.year-text'); - this.dateTextEl = this.modalEl.querySelector('.date-text'); - if (this.options.showClearBtn) { - this.clearBtn = this.modalEl.querySelector('.datepicker-clear'); - } - this.doneBtn = this.modalEl.querySelector('.datepicker-done'); - this.cancelBtn = this.modalEl.querySelector('.datepicker-cancel'); - - this.formats = { - d: () => { - return this.date.getDate(); - }, - dd: () => { - let d = this.date.getDate(); - return (d < 10 ? '0' : '') + d; - }, - ddd: () => { - return this.options.i18n.weekdaysShort[this.date.getDay()]; - }, - dddd: () => { - return this.options.i18n.weekdays[this.date.getDay()]; - }, - m: () => { - return this.date.getMonth() + 1; - }, - mm: () => { - let m = this.date.getMonth() + 1; - return (m < 10 ? '0' : '') + m; - }, - mmm: () => { - return this.options.i18n.monthsShort[this.date.getMonth()]; - }, - mmmm: () => { - return this.options.i18n.months[this.date.getMonth()]; - }, - yy: () => { - return ('' + this.date.getFullYear()).slice(2); - }, - yyyy: () => { - return this.date.getFullYear(); - } - }; - } - - /** - * Remove Event Handlers - */ - _removeEventHandlers() { - this.el.removeEventListener('click', this._handleInputClickBound); - this.el.removeEventListener('keydown', this._handleInputKeydownBound); - this.el.removeEventListener('change', this._handleInputChangeBound); - this.calendarEl.removeEventListener('click', this._handleCalendarClickBound); - } - - _handleInputClick() { - this.open(); - } - - _handleInputKeydown(e) { - if (e.which === M.keys.ENTER) { - e.preventDefault(); - this.open(); - } - } - - _handleCalendarClick(e) { - if (!this.isOpen) { - return; - } - - let $target = $(e.target); - if (!$target.hasClass('is-disabled')) { - if ( - $target.hasClass('datepicker-day-button') && - !$target.hasClass('is-empty') && - !$target.parent().hasClass('is-disabled') - ) { - this.setDate( - new Date( - e.target.getAttribute('data-year'), - e.target.getAttribute('data-month'), - e.target.getAttribute('data-day') - ) - ); - if (this.options.autoClose) { - this._finishSelection(); - } - } else if ($target.closest('.month-prev').length) { - this.prevMonth(); - } else if ($target.closest('.month-next').length) { - this.nextMonth(); - } - } - } - - _handleClearClick() { - this.date = null; - this.setInputValue(); - this.close(); - } - - _handleMonthChange(e) { - this.gotoMonth(e.target.value); - } - - _handleYearChange(e) { - this.gotoYear(e.target.value); - } - - /** - * change view to a specific month (zero-index, e.g. 0: January) - */ - gotoMonth(month) { - if (!isNaN(month)) { - this.calendars[0].month = parseInt(month, 10); - this.adjustCalendars(); - } - } - - /** - * change view to a specific full year (e.g. "2012") - */ - gotoYear(year) { - if (!isNaN(year)) { - this.calendars[0].year = parseInt(year, 10); - this.adjustCalendars(); - } - } - - _handleInputChange(e) { - let date; - - // Prevent change event from being fired when triggered by the plugin - if (e.firedBy === this) { - return; - } - if (this.options.parse) { - date = this.options.parse(this.el.value, this.options.format); - } else { - date = new Date(Date.parse(this.el.value)); - } - - if (Datepicker._isDate(date)) { - this.setDate(date); - } - } - - renderDayName(opts, day, abbr) { - day += opts.firstDay; - while (day >= 7) { - day -= 7; - } - return abbr ? opts.i18n.weekdaysAbbrev[day] : opts.i18n.weekdays[day]; - } - - /** - * Set input value to the selected date and close Datepicker - */ - _finishSelection() { - this.setInputValue(); - this.close(); - } - - /** - * Open Datepicker - */ - open() { - if (this.isOpen) { - return; - } - - this.isOpen = true; - if (typeof this.options.onOpen === 'function') { - this.options.onOpen.call(this); - } - this.draw(); - this.modal.open(); - return this; - } - - /** - * Close Datepicker - */ - close() { - if (!this.isOpen) { - return; - } - - this.isOpen = false; - if (typeof this.options.onClose === 'function') { - this.options.onClose.call(this); - } - this.modal.close(); - return this; - } - } - - Datepicker._template = [ - '' - ].join(''); - - M.Datepicker = Datepicker; - - if (M.jQueryLoaded) { - M.initializeJqueryWrapper(Datepicker, 'datepicker', 'M_Datepicker'); - } -})(cash); diff --git a/static/js/dropdown.js b/static/js/dropdown.js deleted file mode 100644 index 201c18e1..00000000 --- a/static/js/dropdown.js +++ /dev/null @@ -1,615 +0,0 @@ -(function($, anim) { - 'use strict'; - - let _defaults = { - alignment: 'left', - autoFocus: true, - constrainWidth: true, - container: null, - coverTrigger: true, - closeOnClick: true, - hover: false, - inDuration: 150, - outDuration: 250, - onOpenStart: null, - onOpenEnd: null, - onCloseStart: null, - onCloseEnd: null, - onItemClick: null - }; - - /** - * @class - */ - class Dropdown extends Component { - constructor(el, options) { - super(Dropdown, el, options); - - this.el.M_Dropdown = this; - Dropdown._dropdowns.push(this); - - this.id = M.getIdFromTrigger(el); - this.dropdownEl = document.getElementById(this.id); - this.$dropdownEl = $(this.dropdownEl); - - /** - * Options for the dropdown - * @member Dropdown#options - * @prop {String} [alignment='left'] - Edge which the dropdown is aligned to - * @prop {Boolean} [autoFocus=true] - Automatically focus dropdown el for keyboard - * @prop {Boolean} [constrainWidth=true] - Constrain width to width of the button - * @prop {Element} container - Container element to attach dropdown to (optional) - * @prop {Boolean} [coverTrigger=true] - Place dropdown over trigger - * @prop {Boolean} [closeOnClick=true] - Close on click of dropdown item - * @prop {Boolean} [hover=false] - Open dropdown on hover - * @prop {Number} [inDuration=150] - Duration of open animation in ms - * @prop {Number} [outDuration=250] - Duration of close animation in ms - * @prop {Function} onOpenStart - Function called when dropdown starts opening - * @prop {Function} onOpenEnd - Function called when dropdown finishes opening - * @prop {Function} onCloseStart - Function called when dropdown starts closing - * @prop {Function} onCloseEnd - Function called when dropdown finishes closing - */ - this.options = $.extend({}, Dropdown.defaults, options); - - /** - * Describes open/close state of dropdown - * @type {Boolean} - */ - this.isOpen = false; - - /** - * Describes if dropdown content is scrollable - * @type {Boolean} - */ - this.isScrollable = false; - - /** - * Describes if touch moving on dropdown content - * @type {Boolean} - */ - this.isTouchMoving = false; - - this.focusedIndex = -1; - this.filterQuery = []; - - // Move dropdown-content after dropdown-trigger - if (!!this.options.container) { - $(this.options.container).append(this.dropdownEl); - } else { - this.$el.after(this.dropdownEl); - } - - this._makeDropdownFocusable(); - this._resetFilterQueryBound = this._resetFilterQuery.bind(this); - this._handleDocumentClickBound = this._handleDocumentClick.bind(this); - this._handleDocumentTouchmoveBound = this._handleDocumentTouchmove.bind(this); - this._handleDropdownClickBound = this._handleDropdownClick.bind(this); - this._handleDropdownKeydownBound = this._handleDropdownKeydown.bind(this); - this._handleTriggerKeydownBound = this._handleTriggerKeydown.bind(this); - this._setupEventHandlers(); - } - - static get defaults() { - return _defaults; - } - - static init(els, options) { - return super.init(this, els, options); - } - - /** - * Get Instance - */ - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_Dropdown; - } - - /** - * Teardown component - */ - destroy() { - this._resetDropdownStyles(); - this._removeEventHandlers(); - Dropdown._dropdowns.splice(Dropdown._dropdowns.indexOf(this), 1); - this.el.M_Dropdown = undefined; - } - - /** - * Setup Event Handlers - */ - _setupEventHandlers() { - // Trigger keydown handler - this.el.addEventListener('keydown', this._handleTriggerKeydownBound); - - // Item click handler - this.dropdownEl.addEventListener('click', this._handleDropdownClickBound); - - // Hover event handlers - if (this.options.hover) { - this._handleMouseEnterBound = this._handleMouseEnter.bind(this); - this.el.addEventListener('mouseenter', this._handleMouseEnterBound); - this._handleMouseLeaveBound = this._handleMouseLeave.bind(this); - this.el.addEventListener('mouseleave', this._handleMouseLeaveBound); - this.dropdownEl.addEventListener('mouseleave', this._handleMouseLeaveBound); - - // Click event handlers - } else { - this._handleClickBound = this._handleClick.bind(this); - this.el.addEventListener('click', this._handleClickBound); - } - } - - /** - * Remove Event Handlers - */ - _removeEventHandlers() { - this.el.removeEventListener('keydown', this._handleTriggerKeydownBound); - this.dropdownEl.removeEventListener('click', this._handleDropdownClickBound); - - if (this.options.hover) { - this.el.removeEventListener('mouseenter', this._handleMouseEnterBound); - this.el.removeEventListener('mouseleave', this._handleMouseLeaveBound); - this.dropdownEl.removeEventListener('mouseleave', this._handleMouseLeaveBound); - } else { - this.el.removeEventListener('click', this._handleClickBound); - } - } - - _setupTemporaryEventHandlers() { - // Use capture phase event handler to prevent click - document.body.addEventListener('click', this._handleDocumentClickBound, true); - document.body.addEventListener('touchend', this._handleDocumentClickBound); - document.body.addEventListener('touchmove', this._handleDocumentTouchmoveBound); - this.dropdownEl.addEventListener('keydown', this._handleDropdownKeydownBound); - } - - _removeTemporaryEventHandlers() { - // Use capture phase event handler to prevent click - document.body.removeEventListener('click', this._handleDocumentClickBound, true); - document.body.removeEventListener('touchend', this._handleDocumentClickBound); - document.body.removeEventListener('touchmove', this._handleDocumentTouchmoveBound); - this.dropdownEl.removeEventListener('keydown', this._handleDropdownKeydownBound); - } - - _handleClick(e) { - e.preventDefault(); - this.open(); - } - - _handleMouseEnter() { - this.open(); - } - - _handleMouseLeave(e) { - let toEl = e.toElement || e.relatedTarget; - let leaveToDropdownContent = !!$(toEl).closest('.dropdown-content').length; - let leaveToActiveDropdownTrigger = false; - - let $closestTrigger = $(toEl).closest('.dropdown-trigger'); - if ( - $closestTrigger.length && - !!$closestTrigger[0].M_Dropdown && - $closestTrigger[0].M_Dropdown.isOpen - ) { - leaveToActiveDropdownTrigger = true; - } - - // Close hover dropdown if mouse did not leave to either active dropdown-trigger or dropdown-content - if (!leaveToActiveDropdownTrigger && !leaveToDropdownContent) { - this.close(); - } - } - - _handleDocumentClick(e) { - let $target = $(e.target); - if ( - this.options.closeOnClick && - $target.closest('.dropdown-content').length && - !this.isTouchMoving - ) { - // isTouchMoving to check if scrolling on mobile. - setTimeout(() => { - this.close(); - }, 0); - } else if ( - $target.closest('.dropdown-trigger').length || - !$target.closest('.dropdown-content').length - ) { - setTimeout(() => { - this.close(); - }, 0); - } - this.isTouchMoving = false; - } - - _handleTriggerKeydown(e) { - // ARROW DOWN OR ENTER WHEN SELECT IS CLOSED - open Dropdown - if ((e.which === M.keys.ARROW_DOWN || e.which === M.keys.ENTER) && !this.isOpen) { - e.preventDefault(); - this.open(); - } - } - - /** - * Handle Document Touchmove - * @param {Event} e - */ - _handleDocumentTouchmove(e) { - let $target = $(e.target); - if ($target.closest('.dropdown-content').length) { - this.isTouchMoving = true; - } - } - - /** - * Handle Dropdown Click - * @param {Event} e - */ - _handleDropdownClick(e) { - // onItemClick callback - if (typeof this.options.onItemClick === 'function') { - let itemEl = $(e.target).closest('li')[0]; - this.options.onItemClick.call(this, itemEl); - } - } - - /** - * Handle Dropdown Keydown - * @param {Event} e - */ - _handleDropdownKeydown(e) { - if (e.which === M.keys.TAB) { - e.preventDefault(); - this.close(); - - // Navigate down dropdown list - } else if ((e.which === M.keys.ARROW_DOWN || e.which === M.keys.ARROW_UP) && this.isOpen) { - e.preventDefault(); - let direction = e.which === M.keys.ARROW_DOWN ? 1 : -1; - let newFocusedIndex = this.focusedIndex; - let foundNewIndex = false; - do { - newFocusedIndex = newFocusedIndex + direction; - - if ( - !!this.dropdownEl.children[newFocusedIndex] && - this.dropdownEl.children[newFocusedIndex].tabIndex !== -1 - ) { - foundNewIndex = true; - break; - } - } while (newFocusedIndex < this.dropdownEl.children.length && newFocusedIndex >= 0); - - if (foundNewIndex) { - this.focusedIndex = newFocusedIndex; - this._focusFocusedItem(); - } - - // ENTER selects choice on focused item - } else if (e.which === M.keys.ENTER && this.isOpen) { - // Search for and ` - ) - .appendTo(this.footer) - .on('click', this.clear.bind(this)); - if (this.options.showClearBtn) { - $clearBtn.css({ visibility: '' }); - } - - let confirmationBtnsContainer = $('
        '); - $( - '' - ) - .appendTo(confirmationBtnsContainer) - .on('click', this.close.bind(this)); - $( - '' - ) - .appendTo(confirmationBtnsContainer) - .on('click', this.done.bind(this)); - confirmationBtnsContainer.appendTo(this.footer); - } - - _clockSetup() { - if (this.options.twelveHour) { - this.$amBtn = $('
        AM
        '); - this.$pmBtn = $('
        PM
        '); - this.$amBtn.on('click', this._handleAmPmClick.bind(this)).appendTo(this.spanAmPm); - this.$pmBtn.on('click', this._handleAmPmClick.bind(this)).appendTo(this.spanAmPm); - } - - this._buildHoursView(); - this._buildMinutesView(); - this._buildSVGClock(); - } - - _buildSVGClock() { - // Draw clock hands and others - let dialRadius = this.options.dialRadius; - let tickRadius = this.options.tickRadius; - let diameter = dialRadius * 2; - - let svg = Timepicker._createSVGEl('svg'); - svg.setAttribute('class', 'timepicker-svg'); - svg.setAttribute('width', diameter); - svg.setAttribute('height', diameter); - let g = Timepicker._createSVGEl('g'); - g.setAttribute('transform', 'translate(' + dialRadius + ',' + dialRadius + ')'); - let bearing = Timepicker._createSVGEl('circle'); - bearing.setAttribute('class', 'timepicker-canvas-bearing'); - bearing.setAttribute('cx', 0); - bearing.setAttribute('cy', 0); - bearing.setAttribute('r', 4); - let hand = Timepicker._createSVGEl('line'); - hand.setAttribute('x1', 0); - hand.setAttribute('y1', 0); - let bg = Timepicker._createSVGEl('circle'); - bg.setAttribute('class', 'timepicker-canvas-bg'); - bg.setAttribute('r', tickRadius); - g.appendChild(hand); - g.appendChild(bg); - g.appendChild(bearing); - svg.appendChild(g); - this._canvas.appendChild(svg); - - this.hand = hand; - this.bg = bg; - this.bearing = bearing; - this.g = g; - } - - _buildHoursView() { - let $tick = $('
        '); - // Hours view - if (this.options.twelveHour) { - for (let i = 1; i < 13; i += 1) { - let tick = $tick.clone(); - let radian = i / 6 * Math.PI; - let radius = this.options.outerRadius; - tick.css({ - left: - this.options.dialRadius + Math.sin(radian) * radius - this.options.tickRadius + 'px', - top: - this.options.dialRadius - Math.cos(radian) * radius - this.options.tickRadius + 'px' - }); - tick.html(i === 0 ? '00' : i); - this.hoursView.appendChild(tick[0]); - // tick.on(mousedownEvent, mousedown); - } - } else { - for (let i = 0; i < 24; i += 1) { - let tick = $tick.clone(); - let radian = i / 6 * Math.PI; - let inner = i > 0 && i < 13; - let radius = inner ? this.options.innerRadius : this.options.outerRadius; - tick.css({ - left: - this.options.dialRadius + Math.sin(radian) * radius - this.options.tickRadius + 'px', - top: - this.options.dialRadius - Math.cos(radian) * radius - this.options.tickRadius + 'px' - }); - tick.html(i === 0 ? '00' : i); - this.hoursView.appendChild(tick[0]); - // tick.on(mousedownEvent, mousedown); - } - } - } - - _buildMinutesView() { - let $tick = $('
        '); - // Minutes view - for (let i = 0; i < 60; i += 5) { - let tick = $tick.clone(); - let radian = i / 30 * Math.PI; - tick.css({ - left: - this.options.dialRadius + - Math.sin(radian) * this.options.outerRadius - - this.options.tickRadius + - 'px', - top: - this.options.dialRadius - - Math.cos(radian) * this.options.outerRadius - - this.options.tickRadius + - 'px' - }); - tick.html(Timepicker._addLeadingZero(i)); - this.minutesView.appendChild(tick[0]); - } - } - - _handleAmPmClick(e) { - let $btnClicked = $(e.target); - this.amOrPm = $btnClicked.hasClass('am-btn') ? 'AM' : 'PM'; - this._updateAmPmView(); - } - - _updateAmPmView() { - if (this.options.twelveHour) { - this.$amBtn.toggleClass('text-primary', this.amOrPm === 'AM'); - this.$pmBtn.toggleClass('text-primary', this.amOrPm === 'PM'); - } - } - - _updateTimeFromInput() { - // Get the time - let value = ((this.el.value || this.options.defaultTime || '') + '').split(':'); - if (this.options.twelveHour && !(typeof value[1] === 'undefined')) { - if (value[1].toUpperCase().indexOf('AM') > 0) { - this.amOrPm = 'AM'; - } else { - this.amOrPm = 'PM'; - } - value[1] = value[1].replace('AM', '').replace('PM', ''); - } - if (value[0] === 'now') { - let now = new Date(+new Date() + this.options.fromNow); - value = [now.getHours(), now.getMinutes()]; - if (this.options.twelveHour) { - this.amOrPm = value[0] >= 12 && value[0] < 24 ? 'PM' : 'AM'; - } - } - this.hours = +value[0] || 0; - this.minutes = +value[1] || 0; - this.spanHours.innerHTML = this.hours; - this.spanMinutes.innerHTML = Timepicker._addLeadingZero(this.minutes); - - this._updateAmPmView(); - } - - showView(view, delay) { - if (view === 'minutes' && $(this.hoursView).css('visibility') === 'visible') { - // raiseCallback(this.options.beforeHourSelect); - } - let isHours = view === 'hours', - nextView = isHours ? this.hoursView : this.minutesView, - hideView = isHours ? this.minutesView : this.hoursView; - this.currentView = view; - - $(this.spanHours).toggleClass('text-primary', isHours); - $(this.spanMinutes).toggleClass('text-primary', !isHours); - - // Transition view - hideView.classList.add('timepicker-dial-out'); - $(nextView) - .css('visibility', 'visible') - .removeClass('timepicker-dial-out'); - - // Reset clock hand - this.resetClock(delay); - - // After transitions ended - clearTimeout(this.toggleViewTimer); - this.toggleViewTimer = setTimeout(() => { - $(hideView).css('visibility', 'hidden'); - }, this.options.duration); - } - - resetClock(delay) { - let view = this.currentView, - value = this[view], - isHours = view === 'hours', - unit = Math.PI / (isHours ? 6 : 30), - radian = value * unit, - radius = - isHours && value > 0 && value < 13 ? this.options.innerRadius : this.options.outerRadius, - x = Math.sin(radian) * radius, - y = -Math.cos(radian) * radius, - self = this; - - if (delay) { - $(this.canvas).addClass('timepicker-canvas-out'); - setTimeout(() => { - $(self.canvas).removeClass('timepicker-canvas-out'); - self.setHand(x, y); - }, delay); - } else { - this.setHand(x, y); - } - } - - setHand(x, y, roundBy5) { - let radian = Math.atan2(x, -y), - isHours = this.currentView === 'hours', - unit = Math.PI / (isHours || roundBy5 ? 6 : 30), - z = Math.sqrt(x * x + y * y), - inner = isHours && z < (this.options.outerRadius + this.options.innerRadius) / 2, - radius = inner ? this.options.innerRadius : this.options.outerRadius; - - if (this.options.twelveHour) { - radius = this.options.outerRadius; - } - - // Radian should in range [0, 2PI] - if (radian < 0) { - radian = Math.PI * 2 + radian; - } - - // Get the round value - let value = Math.round(radian / unit); - - // Get the round radian - radian = value * unit; - - // Correct the hours or minutes - if (this.options.twelveHour) { - if (isHours) { - if (value === 0) value = 12; - } else { - if (roundBy5) value *= 5; - if (value === 60) value = 0; - } - } else { - if (isHours) { - if (value === 12) { - value = 0; - } - value = inner ? (value === 0 ? 12 : value) : value === 0 ? 0 : value + 12; - } else { - if (roundBy5) { - value *= 5; - } - if (value === 60) { - value = 0; - } - } - } - - // Once hours or minutes changed, vibrate the device - if (this[this.currentView] !== value) { - if (this.vibrate && this.options.vibrate) { - // Do not vibrate too frequently - if (!this.vibrateTimer) { - navigator[this.vibrate](10); - this.vibrateTimer = setTimeout(() => { - this.vibrateTimer = null; - }, 100); - } - } - } - - this[this.currentView] = value; - if (isHours) { - this['spanHours'].innerHTML = value; - } else { - this['spanMinutes'].innerHTML = Timepicker._addLeadingZero(value); - } - - // Set clock hand and others' position - let cx1 = Math.sin(radian) * (radius - this.options.tickRadius), - cy1 = -Math.cos(radian) * (radius - this.options.tickRadius), - cx2 = Math.sin(radian) * radius, - cy2 = -Math.cos(radian) * radius; - this.hand.setAttribute('x2', cx1); - this.hand.setAttribute('y2', cy1); - this.bg.setAttribute('cx', cx2); - this.bg.setAttribute('cy', cy2); - } - - open() { - if (this.isOpen) { - return; - } - - this.isOpen = true; - this._updateTimeFromInput(); - this.showView('hours'); - - this.modal.open(); - } - - close() { - if (!this.isOpen) { - return; - } - - this.isOpen = false; - this.modal.close(); - } - - /** - * Finish timepicker selection. - */ - done(e, clearValue) { - // Set input value - let last = this.el.value; - let value = clearValue - ? '' - : Timepicker._addLeadingZero(this.hours) + ':' + Timepicker._addLeadingZero(this.minutes); - this.time = value; - if (!clearValue && this.options.twelveHour) { - value = `${value} ${this.amOrPm}`; - } - this.el.value = value; - - // Trigger change event - if (value !== last) { - this.$el.trigger('change'); - } - - this.close(); - this.el.focus(); - } - - clear() { - this.done(null, true); - } - } - - Timepicker._template = [ - '' - ].join(''); - - M.Timepicker = Timepicker; - - if (M.jQueryLoaded) { - M.initializeJqueryWrapper(Timepicker, 'timepicker', 'M_Timepicker'); - } -})(cash); diff --git a/static/js/toasts.js b/static/js/toasts.js deleted file mode 100644 index b0e4b83a..00000000 --- a/static/js/toasts.js +++ /dev/null @@ -1,310 +0,0 @@ -(function($, anim) { - 'use strict'; - - let _defaults = { - html: '', - displayLength: 4000, - inDuration: 300, - outDuration: 375, - classes: '', - completeCallback: null, - activationPercent: 0.8 - }; - - class Toast { - constructor(options) { - /** - * Options for the toast - * @member Toast#options - */ - this.options = $.extend({}, Toast.defaults, options); - this.message = this.options.html; - - /** - * Describes current pan state toast - * @type {Boolean} - */ - this.panning = false; - - /** - * Time remaining until toast is removed - */ - this.timeRemaining = this.options.displayLength; - - if (Toast._toasts.length === 0) { - Toast._createContainer(); - } - - // Create new toast - Toast._toasts.push(this); - let toastElement = this._createToast(); - toastElement.M_Toast = this; - this.el = toastElement; - this.$el = $(toastElement); - this._animateIn(); - this._setTimer(); - } - - static get defaults() { - return _defaults; - } - - /** - * Get Instance - */ - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_Toast; - } - - /** - * Append toast container and add event handlers - */ - static _createContainer() { - let container = document.createElement('div'); - container.setAttribute('id', 'toast-container'); - - // Add event handler - container.addEventListener('touchstart', Toast._onDragStart); - container.addEventListener('touchmove', Toast._onDragMove); - container.addEventListener('touchend', Toast._onDragEnd); - - container.addEventListener('mousedown', Toast._onDragStart); - document.addEventListener('mousemove', Toast._onDragMove); - document.addEventListener('mouseup', Toast._onDragEnd); - - document.body.appendChild(container); - Toast._container = container; - } - - /** - * Remove toast container and event handlers - */ - static _removeContainer() { - // Add event handler - document.removeEventListener('mousemove', Toast._onDragMove); - document.removeEventListener('mouseup', Toast._onDragEnd); - - $(Toast._container).remove(); - Toast._container = null; - } - - /** - * Begin drag handler - * @param {Event} e - */ - static _onDragStart(e) { - if (e.target && $(e.target).closest('.toast').length) { - let $toast = $(e.target).closest('.toast'); - let toast = $toast[0].M_Toast; - toast.panning = true; - Toast._draggedToast = toast; - toast.el.classList.add('panning'); - toast.el.style.transition = ''; - toast.startingXPos = Toast._xPos(e); - toast.time = Date.now(); - toast.xPos = Toast._xPos(e); - } - } - - /** - * Drag move handler - * @param {Event} e - */ - static _onDragMove(e) { - if (!!Toast._draggedToast) { - e.preventDefault(); - let toast = Toast._draggedToast; - toast.deltaX = Math.abs(toast.xPos - Toast._xPos(e)); - toast.xPos = Toast._xPos(e); - toast.velocityX = toast.deltaX / (Date.now() - toast.time); - toast.time = Date.now(); - - let totalDeltaX = toast.xPos - toast.startingXPos; - let activationDistance = toast.el.offsetWidth * toast.options.activationPercent; - toast.el.style.transform = `translateX(${totalDeltaX}px)`; - toast.el.style.opacity = 1 - Math.abs(totalDeltaX / activationDistance); - } - } - - /** - * End drag handler - */ - static _onDragEnd() { - if (!!Toast._draggedToast) { - let toast = Toast._draggedToast; - toast.panning = false; - toast.el.classList.remove('panning'); - - let totalDeltaX = toast.xPos - toast.startingXPos; - let activationDistance = toast.el.offsetWidth * toast.options.activationPercent; - let shouldBeDismissed = Math.abs(totalDeltaX) > activationDistance || toast.velocityX > 1; - - // Remove toast - if (shouldBeDismissed) { - toast.wasSwiped = true; - toast.dismiss(); - - // Animate toast back to original position - } else { - toast.el.style.transition = 'transform .2s, opacity .2s'; - toast.el.style.transform = ''; - toast.el.style.opacity = ''; - } - Toast._draggedToast = null; - } - } - - /** - * Get x position of mouse or touch event - * @param {Event} e - */ - static _xPos(e) { - if (e.targetTouches && e.targetTouches.length >= 1) { - return e.targetTouches[0].clientX; - } - // mouse event - return e.clientX; - } - - /** - * Remove all toasts - */ - static dismissAll() { - for (let toastIndex in Toast._toasts) { - Toast._toasts[toastIndex].dismiss(); - } - } - - /** - * Create toast and append it to toast container - */ - _createToast() { - let toast = document.createElement('div'); - toast.classList.add('toast'); - - // Add custom classes onto toast - if (!!this.options.classes.length) { - $(toast).addClass(this.options.classes); - } - - // Set content - if ( - typeof HTMLElement === 'object' - ? this.message instanceof HTMLElement - : this.message && - typeof this.message === 'object' && - this.message !== null && - this.message.nodeType === 1 && - typeof this.message.nodeName === 'string' - ) { - toast.appendChild(this.message); - - // Check if it is jQuery object - } else if (!!this.message.jquery) { - $(toast).append(this.message[0]); - - // Insert as html; - } else { - toast.innerHTML = this.message; - } - - // Append toasft - Toast._container.appendChild(toast); - return toast; - } - - /** - * Animate in toast - */ - _animateIn() { - // Animate toast in - anim({ - targets: this.el, - top: 0, - opacity: 1, - duration: this.options.inDuration, - easing: 'easeOutCubic' - }); - } - - /** - * Create setInterval which automatically removes toast when timeRemaining >= 0 - * has been reached - */ - _setTimer() { - if (this.timeRemaining !== Infinity) { - this.counterInterval = setInterval(() => { - // If toast is not being dragged, decrease its time remaining - if (!this.panning) { - this.timeRemaining -= 20; - } - - // Animate toast out - if (this.timeRemaining <= 0) { - this.dismiss(); - } - }, 20); - } - } - - /** - * Dismiss toast with animation - */ - dismiss() { - window.clearInterval(this.counterInterval); - let activationDistance = this.el.offsetWidth * this.options.activationPercent; - - if (this.wasSwiped) { - this.el.style.transition = 'transform .05s, opacity .05s'; - this.el.style.transform = `translateX(${activationDistance}px)`; - this.el.style.opacity = 0; - } - - anim({ - targets: this.el, - opacity: 0, - marginTop: -40, - duration: this.options.outDuration, - easing: 'easeOutExpo', - complete: () => { - // Call the optional callback - if (typeof this.options.completeCallback === 'function') { - this.options.completeCallback(); - } - // Remove toast from DOM - this.$el.remove(); - Toast._toasts.splice(Toast._toasts.indexOf(this), 1); - if (Toast._toasts.length === 0) { - Toast._removeContainer(); - } - } - }); - } - } - - /** - * @static - * @memberof Toast - * @type {Array.} - */ - Toast._toasts = []; - - /** - * @static - * @memberof Toast - */ - Toast._container = null; - - /** - * @static - * @memberof Toast - * @type {Toast} - */ - Toast._draggedToast = null; - - M.Toast = Toast; - M.toast = function(options) { - return new Toast(options); - }; -})(cash, M.anime); diff --git a/static/js/tooltip.js b/static/js/tooltip.js deleted file mode 100644 index b30dce2b..00000000 --- a/static/js/tooltip.js +++ /dev/null @@ -1,303 +0,0 @@ -(function($, anim) { - 'use strict'; - - let _defaults = { - exitDelay: 200, - enterDelay: 0, - html: null, - margin: 5, - inDuration: 250, - outDuration: 200, - position: 'bottom', - transitionMovement: 10 - }; - - /** - * @class - * - */ - class Tooltip extends Component { - /** - * Construct Tooltip instance - * @constructor - * @param {Element} el - * @param {Object} options - */ - constructor(el, options) { - super(Tooltip, el, options); - - this.el.M_Tooltip = this; - this.options = $.extend({}, Tooltip.defaults, options); - - this.isOpen = false; - this.isHovered = false; - this.isFocused = false; - this._appendTooltipEl(); - this._setupEventHandlers(); - } - - static get defaults() { - return _defaults; - } - - static init(els, options) { - return super.init(this, els, options); - } - - /** - * Get Instance - */ - static getInstance(el) { - let domElem = !!el.jquery ? el[0] : el; - return domElem.M_Tooltip; - } - - /** - * Teardown component - */ - destroy() { - $(this.tooltipEl).remove(); - this._removeEventHandlers(); - this.el.M_Tooltip = undefined; - } - - _appendTooltipEl() { - let tooltipEl = document.createElement('div'); - tooltipEl.classList.add('material-tooltip'); - this.tooltipEl = tooltipEl; - - let tooltipContentEl = document.createElement('div'); - tooltipContentEl.classList.add('tooltip-content'); - tooltipContentEl.innerHTML = this.options.html; - tooltipEl.appendChild(tooltipContentEl); - document.body.appendChild(tooltipEl); - } - - _updateTooltipContent() { - this.tooltipEl.querySelector('.tooltip-content').innerHTML = this.options.html; - } - - _setupEventHandlers() { - this._handleMouseEnterBound = this._handleMouseEnter.bind(this); - this._handleMouseLeaveBound = this._handleMouseLeave.bind(this); - this._handleFocusBound = this._handleFocus.bind(this); - this._handleBlurBound = this._handleBlur.bind(this); - this.el.addEventListener('mouseenter', this._handleMouseEnterBound); - this.el.addEventListener('mouseleave', this._handleMouseLeaveBound); - this.el.addEventListener('focus', this._handleFocusBound, true); - this.el.addEventListener('blur', this._handleBlurBound, true); - } - - _removeEventHandlers() { - this.el.removeEventListener('mouseenter', this._handleMouseEnterBound); - this.el.removeEventListener('mouseleave', this._handleMouseLeaveBound); - this.el.removeEventListener('focus', this._handleFocusBound, true); - this.el.removeEventListener('blur', this._handleBlurBound, true); - } - - open(isManual) { - if (this.isOpen) { - return; - } - isManual = isManual === undefined ? true : undefined; // Default value true - this.isOpen = true; - // Update tooltip content with HTML attribute options - this.options = $.extend({}, this.options, this._getAttributeOptions()); - this._updateTooltipContent(); - this._setEnterDelayTimeout(isManual); - } - - close() { - if (!this.isOpen) { - return; - } - - this.isHovered = false; - this.isFocused = false; - this.isOpen = false; - this._setExitDelayTimeout(); - } - - /** - * Create timeout which delays when the tooltip closes - */ - _setExitDelayTimeout() { - clearTimeout(this._exitDelayTimeout); - - this._exitDelayTimeout = setTimeout(() => { - if (this.isHovered || this.isFocused) { - return; - } - - this._animateOut(); - }, this.options.exitDelay); - } - - /** - * Create timeout which delays when the toast closes - */ - _setEnterDelayTimeout(isManual) { - clearTimeout(this._enterDelayTimeout); - - this._enterDelayTimeout = setTimeout(() => { - if (!this.isHovered && !this.isFocused && !isManual) { - return; - } - - this._animateIn(); - }, this.options.enterDelay); - } - - _positionTooltip() { - let origin = this.el, - tooltip = this.tooltipEl, - originHeight = origin.offsetHeight, - originWidth = origin.offsetWidth, - tooltipHeight = tooltip.offsetHeight, - tooltipWidth = tooltip.offsetWidth, - newCoordinates, - margin = this.options.margin, - targetTop, - targetLeft; - - (this.xMovement = 0), (this.yMovement = 0); - - targetTop = origin.getBoundingClientRect().top + M.getDocumentScrollTop(); - targetLeft = origin.getBoundingClientRect().left + M.getDocumentScrollLeft(); - - if (this.options.position === 'top') { - targetTop += -tooltipHeight - margin; - targetLeft += originWidth / 2 - tooltipWidth / 2; - this.yMovement = -this.options.transitionMovement; - } else if (this.options.position === 'right') { - targetTop += originHeight / 2 - tooltipHeight / 2; - targetLeft += originWidth + margin; - this.xMovement = this.options.transitionMovement; - } else if (this.options.position === 'left') { - targetTop += originHeight / 2 - tooltipHeight / 2; - targetLeft += -tooltipWidth - margin; - this.xMovement = -this.options.transitionMovement; - } else { - targetTop += originHeight + margin; - targetLeft += originWidth / 2 - tooltipWidth / 2; - this.yMovement = this.options.transitionMovement; - } - - newCoordinates = this._repositionWithinScreen( - targetLeft, - targetTop, - tooltipWidth, - tooltipHeight - ); - $(tooltip).css({ - top: newCoordinates.y + 'px', - left: newCoordinates.x + 'px' - }); - } - - _repositionWithinScreen(x, y, width, height) { - let scrollLeft = M.getDocumentScrollLeft(); - let scrollTop = M.getDocumentScrollTop(); - let newX = x - scrollLeft; - let newY = y - scrollTop; - - let bounding = { - left: newX, - top: newY, - width: width, - height: height - }; - - let offset = this.options.margin + this.options.transitionMovement; - let edges = M.checkWithinContainer(document.body, bounding, offset); - - if (edges.left) { - newX = offset; - } else if (edges.right) { - newX -= newX + width - window.innerWidth; - } - - if (edges.top) { - newY = offset; - } else if (edges.bottom) { - newY -= newY + height - window.innerHeight; - } - - return { - x: newX + scrollLeft, - y: newY + scrollTop - }; - } - - _animateIn() { - this._positionTooltip(); - this.tooltipEl.style.visibility = 'visible'; - anim.remove(this.tooltipEl); - anim({ - targets: this.tooltipEl, - opacity: 1, - translateX: this.xMovement, - translateY: this.yMovement, - duration: this.options.inDuration, - easing: 'easeOutCubic' - }); - } - - _animateOut() { - anim.remove(this.tooltipEl); - anim({ - targets: this.tooltipEl, - opacity: 0, - translateX: 0, - translateY: 0, - duration: this.options.outDuration, - easing: 'easeOutCubic' - }); - } - - _handleMouseEnter() { - this.isHovered = true; - this.isFocused = false; // Allows close of tooltip when opened by focus. - this.open(false); - } - - _handleMouseLeave() { - this.isHovered = false; - this.isFocused = false; // Allows close of tooltip when opened by focus. - this.close(); - } - - _handleFocus() { - if (M.tabPressed) { - this.isFocused = true; - this.open(false); - } - } - - _handleBlur() { - this.isFocused = false; - this.close(); - } - - _getAttributeOptions() { - let attributeOptions = {}; - let tooltipTextOption = this.el.getAttribute('data-tooltip'); - let positionOption = this.el.getAttribute('data-position'); - - if (tooltipTextOption) { - attributeOptions.html = tooltipTextOption; - } - - if (positionOption) { - attributeOptions.position = positionOption; - } - return attributeOptions; - } - } - - M.Tooltip = Tooltip; - - if (M.jQueryLoaded) { - M.initializeJqueryWrapper(Tooltip, 'tooltip', 'M_Tooltip'); - } -})(cash, M.anime); diff --git a/static/js/waves.js b/static/js/waves.js deleted file mode 100644 index b56cc2cb..00000000 --- a/static/js/waves.js +++ /dev/null @@ -1,335 +0,0 @@ -/*! - * Waves v0.6.4 - * http://fian.my.id/Waves - * - * Copyright 2014 Alfiana E. Sibuea and other contributors - * Released under the MIT license - * https://github.com/fians/Waves/blob/master/LICENSE - */ - -;(function(window) { - 'use strict'; - - var Waves = Waves || {}; - var $$ = document.querySelectorAll.bind(document); - - // Find exact position of element - function isWindow(obj) { - return obj !== null && obj === obj.window; - } - - function getWindow(elem) { - return isWindow(elem) ? elem : elem.nodeType === 9 && elem.defaultView; - } - - function offset(elem) { - var docElem, win, - box = {top: 0, left: 0}, - doc = elem && elem.ownerDocument; - - docElem = doc.documentElement; - - if (typeof elem.getBoundingClientRect !== typeof undefined) { - box = elem.getBoundingClientRect(); - } - win = getWindow(doc); - return { - top: box.top + win.pageYOffset - docElem.clientTop, - left: box.left + win.pageXOffset - docElem.clientLeft - }; - } - - function convertStyle(obj) { - var style = ''; - - for (var a in obj) { - if (obj.hasOwnProperty(a)) { - style += (a + ':' + obj[a] + ';'); - } - } - - return style; - } - - var Effect = { - - // Effect delay - duration: 750, - - show: function(e, element) { - - // Disable right click - if (e.button === 2) { - return false; - } - - var el = element || this; - - // Create ripple - var ripple = document.createElement('div'); - ripple.className = 'waves-ripple'; - el.appendChild(ripple); - - // Get click coordinate and element witdh - var pos = offset(el); - var relativeY = (e.pageY - pos.top); - var relativeX = (e.pageX - pos.left); - var scale = 'scale('+((el.clientWidth / 100) * 10)+')'; - - // Support for touch devices - if ('touches' in e) { - relativeY = (e.touches[0].pageY - pos.top); - relativeX = (e.touches[0].pageX - pos.left); - } - - // Attach data to element - ripple.setAttribute('data-hold', Date.now()); - ripple.setAttribute('data-scale', scale); - ripple.setAttribute('data-x', relativeX); - ripple.setAttribute('data-y', relativeY); - - // Set ripple position - var rippleStyle = { - 'top': relativeY+'px', - 'left': relativeX+'px' - }; - - ripple.className = ripple.className + ' waves-notransition'; - ripple.setAttribute('style', convertStyle(rippleStyle)); - ripple.className = ripple.className.replace('waves-notransition', ''); - - // Scale the ripple - rippleStyle['-webkit-transform'] = scale; - rippleStyle['-moz-transform'] = scale; - rippleStyle['-ms-transform'] = scale; - rippleStyle['-o-transform'] = scale; - rippleStyle.transform = scale; - rippleStyle.opacity = '1'; - - rippleStyle['-webkit-transition-duration'] = Effect.duration + 'ms'; - rippleStyle['-moz-transition-duration'] = Effect.duration + 'ms'; - rippleStyle['-o-transition-duration'] = Effect.duration + 'ms'; - rippleStyle['transition-duration'] = Effect.duration + 'ms'; - - rippleStyle['-webkit-transition-timing-function'] = 'cubic-bezier(0.250, 0.460, 0.450, 0.940)'; - rippleStyle['-moz-transition-timing-function'] = 'cubic-bezier(0.250, 0.460, 0.450, 0.940)'; - rippleStyle['-o-transition-timing-function'] = 'cubic-bezier(0.250, 0.460, 0.450, 0.940)'; - rippleStyle['transition-timing-function'] = 'cubic-bezier(0.250, 0.460, 0.450, 0.940)'; - - ripple.setAttribute('style', convertStyle(rippleStyle)); - }, - - hide: function(e) { - TouchHandler.touchup(e); - - var el = this; - var width = el.clientWidth * 1.4; - - // Get first ripple - var ripple = null; - var ripples = el.getElementsByClassName('waves-ripple'); - if (ripples.length > 0) { - ripple = ripples[ripples.length - 1]; - } else { - return false; - } - - var relativeX = ripple.getAttribute('data-x'); - var relativeY = ripple.getAttribute('data-y'); - var scale = ripple.getAttribute('data-scale'); - - // Get delay beetween mousedown and mouse leave - var diff = Date.now() - Number(ripple.getAttribute('data-hold')); - var delay = 350 - diff; - - if (delay < 0) { - delay = 0; - } - - // Fade out ripple after delay - setTimeout(function() { - var style = { - 'top': relativeY+'px', - 'left': relativeX+'px', - 'opacity': '0', - - // Duration - '-webkit-transition-duration': Effect.duration + 'ms', - '-moz-transition-duration': Effect.duration + 'ms', - '-o-transition-duration': Effect.duration + 'ms', - 'transition-duration': Effect.duration + 'ms', - '-webkit-transform': scale, - '-moz-transform': scale, - '-ms-transform': scale, - '-o-transform': scale, - 'transform': scale, - }; - - ripple.setAttribute('style', convertStyle(style)); - - setTimeout(function() { - try { - el.removeChild(ripple); - } catch(e) { - return false; - } - }, Effect.duration); - }, delay); - }, - - // Little hack to make can perform waves effect - wrapInput: function(elements) { - for (var a = 0; a < elements.length; a++) { - var el = elements[a]; - - if (el.tagName.toLowerCase() === 'input') { - var parent = el.parentNode; - - // If input already have parent just pass through - if (parent.tagName.toLowerCase() === 'i' && parent.className.indexOf('waves-effect') !== -1) { - continue; - } - - // Put element class and style to the specified parent - var wrapper = document.createElement('i'); - wrapper.className = el.className + ' waves-input-wrapper'; - - var elementStyle = el.getAttribute('style'); - - if (!elementStyle) { - elementStyle = ''; - } - - wrapper.setAttribute('style', elementStyle); - - el.className = 'waves-button-input'; - el.removeAttribute('style'); - - // Put element as child - parent.replaceChild(wrapper, el); - wrapper.appendChild(el); - } - } - } - }; - - - /** - * Disable mousedown event for 500ms during and after touch - */ - var TouchHandler = { - /* uses an integer rather than bool so there's no issues with - * needing to clear timeouts if another touch event occurred - * within the 500ms. Cannot mouseup between touchstart and - * touchend, nor in the 500ms after touchend. */ - touches: 0, - allowEvent: function(e) { - var allow = true; - - if (e.type === 'touchstart') { - TouchHandler.touches += 1; //push - } else if (e.type === 'touchend' || e.type === 'touchcancel') { - setTimeout(function() { - if (TouchHandler.touches > 0) { - TouchHandler.touches -= 1; //pop after 500ms - } - }, 500); - } else if (e.type === 'mousedown' && TouchHandler.touches > 0) { - allow = false; - } - - return allow; - }, - touchup: function(e) { - TouchHandler.allowEvent(e); - } - }; - - - /** - * Delegated click handler for .waves-effect element. - * returns null when .waves-effect element not in "click tree" - */ - function getWavesEffectElement(e) { - if (TouchHandler.allowEvent(e) === false) { - return null; - } - - var element = null; - var target = e.target || e.srcElement; - - while (target.parentNode !== null) { - if (!(target instanceof SVGElement) && target.className.indexOf('waves-effect') !== -1) { - element = target; - break; - } - target = target.parentNode; - } - return element; - } - - /** - * Bubble the click and show effect if .waves-effect elem was found - */ - function showEffect(e) { - var element = getWavesEffectElement(e); - - if (element !== null) { - Effect.show(e, element); - - if ('ontouchstart' in window) { - element.addEventListener('touchend', Effect.hide, false); - element.addEventListener('touchcancel', Effect.hide, false); - } - - element.addEventListener('mouseup', Effect.hide, false); - element.addEventListener('mouseleave', Effect.hide, false); - element.addEventListener('dragend', Effect.hide, false); - } - } - - Waves.displayEffect = function(options) { - options = options || {}; - - if ('duration' in options) { - Effect.duration = options.duration; - } - - //Wrap input inside tag - Effect.wrapInput($$('.waves-effect')); - - if ('ontouchstart' in window) { - document.body.addEventListener('touchstart', showEffect, false); - } - - document.body.addEventListener('mousedown', showEffect, false); - }; - - /** - * Attach Waves to an input element (or any element which doesn't - * bubble mouseup/mousedown events). - * Intended to be used with dynamically loaded forms/inputs, or - * where the user doesn't want a delegated click handler. - */ - Waves.attach = function(element) { - //FUTURE: automatically add waves classes and allow users - // to specify them with an options param? Eg. light/classic/button - if (element.tagName.toLowerCase() === 'input') { - Effect.wrapInput([element]); - element = element.parentNode; - } - - if ('ontouchstart' in window) { - element.addEventListener('touchstart', showEffect, false); - } - - element.addEventListener('mousedown', showEffect, false); - }; - - window.Waves = Waves; - - document.addEventListener('DOMContentLoaded', function() { - Waves.displayEffect(); - }, false); - -})(window); From c418678fe267aeaaa0e42847b9387802865a70e2 Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Sat, 5 Jan 2019 19:56:25 +0000 Subject: [PATCH 11/34] Updated asset model to remove collections --- models.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/models.py b/models.py index 1a771819..4b5c6c4b 100644 --- a/models.py +++ b/models.py @@ -31,28 +31,27 @@ class Supplier(models.Model): return self.name -class Collection(models.Model): - name = models.CharField(max_length=80) +class BaseAsset(models.Model): + class Meta: + abstract = True - def __str__(self): - return self.name - - -class Asset(models.Model): - asset_id = models.IntegerField(blank=True, null=True) + parent = models.ForeignKey(to='self', related_name='asset_parent', blank=True, null=True, on_delete=models.SET_NULL) + asset_id = models.CharField(max_length=10) description = models.CharField(max_length=120) category = models.ForeignKey(to=AssetCategory, on_delete=models.CASCADE) status = models.ForeignKey(to=AssetStatus, on_delete=models.CASCADE) - serial_number = models.CharField(max_length=150, blank=True, null=True) + serial_number = models.CharField(max_length=150, blank=True) purchased_from = models.ForeignKey(to=Supplier, on_delete=models.CASCADE, blank=True, null=True) date_acquired = models.DateField() date_sold = models.DateField(blank=True, null=True) - purchase_price = models.IntegerField(blank=True, null=True) - salvage_value = models.IntegerField(blank=True, null=True) - comments = models.TextField(blank=True, null=True) + purchase_price = models.DecimalField(blank=True, null=True, decimal_places=2, max_digits=10) + salvage_value = models.DecimalField(blank=True, null=True, decimal_places=2, max_digits=10) + comments = models.TextField(blank=True) next_sched_maint = models.DateField(blank=True, null=True) - collection = models.ForeignKey(to=Collection, on_delete=models.CASCADE, blank=True, null=True) + # Cable assets + is_cable = models.BooleanField(default=False) + length = models.DecimalField(decimal_places=1, max_digits=10, blank=True, null=True) def get_absolute_url(self): return reverse('asset_detail', kwargs={'pk': self.pk}) From 5ec0a179eb85e63d1a5c753f4b9e92b14fcbe963 Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Sat, 5 Jan 2019 19:56:43 +0000 Subject: [PATCH 12/34] Started on PAT models --- models.py | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/models.py b/models.py index 4b5c6c4b..c6053816 100644 --- a/models.py +++ b/models.py @@ -60,13 +60,31 @@ class BaseAsset(models.Model): return str(self.asset_id) + ' - ' + self.description -# Automatically updates Asset.asset_id to Asset.pk if none is given by the user -@receiver(post_save, sender=Asset) -def update_asset_id(sender, instance, **kwargs): - post_save.disconnect(update_asset_id, sender=sender) +class Asset(BaseAsset): + pass - if not instance.asset_id: - instance.asset_id = instance.pk - instance.save() - post_save.connect(update_asset_id, sender=sender) +class Connector(models.Model): + description = models.CharField(max_length=80) + current_rating = models.DecimalField(decimal_places=2, max_digits=10, help_text='Amps') + voltage_rating = models.IntegerField(help_text='Volts') + num_pins = models.IntegerField(blank=True, null=True) + + def __str__(self): + return self.description + + +class Cable(BaseAsset): + plug = models.ForeignKey(Connector, on_delete=models.SET_NULL, related_name='plug', null=True) + socket = models.ForeignKey(Connector, on_delete=models.SET_NULL, related_name='socket', null=True) + length = models.DecimalField(decimal_places=1, max_digits=10, blank=True, null=True, help_text='m') + csa = models.DecimalField(decimal_places=2, max_digits=10, blank=True, null=True, help_text='mm^2') + circuits = models.IntegerField(blank=True, null=True) + cores = models.IntegerField(blank=True, null=True) + + def cable_resistance(self): + rho = 0.0000000168 + return (rho * self.length) / (self.csa * 1000000) + + def __str__(self): + return '{} - {}m - {}'.format(self.plug, self.length, self.socket) From a4cfe17f74cce62322dfce3aa2a19a6c1a991091 Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Sat, 5 Jan 2019 19:57:31 +0000 Subject: [PATCH 13/34] Migrations for model updates --- ...8_1451_squashed_0021_auto_20190105_1156.py | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 migrations/0006_auto_20180728_1451_squashed_0021_auto_20190105_1156.py diff --git a/migrations/0006_auto_20180728_1451_squashed_0021_auto_20190105_1156.py b/migrations/0006_auto_20180728_1451_squashed_0021_auto_20190105_1156.py new file mode 100644 index 00000000..f15341e1 --- /dev/null +++ b/migrations/0006_auto_20180728_1451_squashed_0021_auto_20190105_1156.py @@ -0,0 +1,148 @@ +# Generated by Django 2.1.5 on 2019-01-05 19:54 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + replaces = [('assets', '0006_auto_20180728_1451'), ('assets', '0007_auto_20181215_1447'), ('assets', '0008_auto_20181215_1448'), ('assets', '0009_auto_20181215_1640'), ('assets', '0010_auto_20181215_1640'), ('assets', '0011_auto_20181215_1749'), ('assets', '0012_auto_20181215_1813'), ('assets', '0013_asset_parent'), ('assets', '0014_auto_20190103_1615'), ('assets', '0015_auto_20190103_1617'), ('assets', '0016_remove_asset_collection'), ('assets', '0017_delete_collection'), ('assets', '0018_auto_20190103_1708'), ('assets', '0019_auto_20190103_1723'), ('assets', '0020_auto_20190103_1729'), ('assets', '0021_auto_20190105_1156')] + + dependencies = [ + ('assets', '0005_auto_20180301_1725'), + ] + + operations = [ + migrations.AlterField( + model_name='asset', + name='asset_id', + field=models.IntegerField(blank=True, null=True, unique=True), + ), + migrations.AlterField( + model_name='asset', + name='purchase_price', + field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True), + ), + migrations.AlterField( + model_name='asset', + name='salvage_value', + field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True), + ), + migrations.AlterField( + model_name='asset', + name='asset_id', + field=models.IntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='asset', + name='is_cable', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='asset', + name='length', + field=models.DecimalField(blank=True, decimal_places=1, max_digits=10, null=True), + ), + migrations.AlterField( + model_name='asset', + name='asset_id', + field=models.CharField(blank=True, max_length=10, null=True), + ), + migrations.AlterField( + model_name='asset', + name='asset_id', + field=models.CharField(default='', max_length=10), + preserve_default=False, + ), + migrations.AddField( + model_name='asset', + name='parent', + field=models.ForeignKey(blank=True, null=True, on_delete=None, related_name='asset_parent', to='assets.Asset'), + ), + migrations.RemoveField( + model_name='asset', + name='collection', + ), + migrations.DeleteModel( + name='Collection', + ), + migrations.CreateModel( + name='Cable', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('asset_id', models.CharField(max_length=10)), + ('description', models.CharField(max_length=120)), + ('serial_number', models.CharField(blank=True, max_length=150, null=True)), + ('date_acquired', models.DateField()), + ('date_sold', models.DateField(blank=True, null=True)), + ('purchase_price', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), + ('salvage_value', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), + ('comments', models.TextField(blank=True, null=True)), + ('next_sched_maint', models.DateField(blank=True, null=True)), + ('is_cable', models.BooleanField(default=False)), + ('length', models.DecimalField(blank=True, decimal_places=1, help_text='m', max_digits=10, null=True)), + ('csa', models.DecimalField(blank=True, decimal_places=2, help_text='mm^2', max_digits=10, null=True)), + ('circuits', models.IntegerField(blank=True, null=True)), + ('cores', models.IntegerField(blank=True, null=True)), + ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.AssetCategory')), + ('parent', models.ForeignKey(blank=True, null=True, on_delete=None, related_name='asset_parent', to='assets.Cable')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Connector', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('description', models.CharField(max_length=80)), + ('current_rating', models.DecimalField(decimal_places=2, help_text='Amps', max_digits=10)), + ('voltage_rating', models.IntegerField(default=0, help_text='Volts')), + ('num_pins', models.IntegerField(blank=True, null=True)), + ], + ), + migrations.AddField( + model_name='cable', + name='plug', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='plug', to='assets.Connector'), + ), + migrations.AddField( + model_name='cable', + name='purchased_from', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.Supplier'), + ), + migrations.AddField( + model_name='cable', + name='socket', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='socket', to='assets.Connector'), + ), + migrations.AddField( + model_name='cable', + name='status', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.AssetStatus'), + ), + migrations.AlterField( + model_name='asset', + name='comments', + field=models.TextField(blank=True, default=''), + preserve_default=False, + ), + migrations.AlterField( + model_name='asset', + name='serial_number', + field=models.CharField(blank=True, default='', max_length=150), + preserve_default=False, + ), + migrations.AlterField( + model_name='cable', + name='comments', + field=models.TextField(blank=True, default=''), + preserve_default=False, + ), + migrations.AlterField( + model_name='cable', + name='serial_number', + field=models.CharField(blank=True, default='', max_length=150), + preserve_default=False, + ), + ] From 502a2c4e0ea91e07ab11a2638d08926c0d129517 Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Sat, 5 Jan 2019 19:58:17 +0000 Subject: [PATCH 14/34] Importer scripts for old asset DB --- management/commands/import_old_db.py | 229 ++++++++++++++++++++++ management/commands/update_old_db_file.py | 112 +++++++++++ 2 files changed, 341 insertions(+) create mode 100644 management/commands/import_old_db.py create mode 100644 management/commands/update_old_db_file.py diff --git a/management/commands/import_old_db.py b/management/commands/import_old_db.py new file mode 100644 index 00000000..4e2ef08a --- /dev/null +++ b/management/commands/import_old_db.py @@ -0,0 +1,229 @@ +import os +from django.core.management.base import BaseCommand, CommandError +from django.conf import settings +import datetime +import xml.etree.ElementTree as ET + +from assets import models + + +class Command(BaseCommand): + help = 'Imports old db from XML dump' + + epoch = datetime.date(1970, 1, 1) + + def handle(self, *args, **options): + self.import_categories() + self.import_statuses() + self.import_suppliers() + self.import_collections() + self.import_assets() + self.import_cables() + + @staticmethod + def xml_path(file): + return os.path.join(settings.BASE_DIR, 'data/DB_Dump/{}'.format(file)) + + @staticmethod + def parse_xml(file): + tree = ET.parse(file) + + return tree.getroot() + + def import_categories(self): + # 0: updated, 1: created + tally = [0, 0] + root = self.parse_xml(self.xml_path('TEC_Asset_Categories.xml')) + + for child in root: + obj, created = models.AssetCategory.objects.update_or_create( + pk=int(child.find('AssetCategoryID').text), + name=child.find('AssetCategory').text + ) + + if created: + tally[1] += 1 + else: + tally[0] += 1 + + print('Categories - Updated: {}, Created: {}'.format(tally[0], tally[1])) + + def import_statuses(self): + # 0: updated, 1: created + tally = [0, 0] + root = self.parse_xml(self.xml_path('TEC_Asset_Status_new.xml')) + + for child in root: + obj, created = models.AssetStatus.objects.update_or_create( + pk=int(child.find('StatusID').text), + name=child.find('Status').text + ) + + if created: + tally[1] += 1 + else: + tally[0] += 1 + + print('Statuses - Updated: {}, Created: {}'.format(tally[0], tally[1])) + + def import_suppliers(self): + # 0: updated, 1: created + tally = [0, 0] + root = self.parse_xml(self.xml_path('TEC_Asset_Suppliers_new.xml')) + + for child in root: + obj, created = models.Supplier.objects.update_or_create( + pk=int(child.find('Supplier_x0020_Id').text), + name=child.find('Supplier_x0020_Name').text + ) + + if created: + tally[1] += 1 + else: + tally[0] += 1 + + print('Suppliers - Updated: {}, Created: {}'.format(tally[0], tally[1])) + + def import_assets(self): + # 0: updated, 1: created + tally = [0, 0] + root = self.parse_xml(self.xml_path('TEC_Assets.xml')) + + for child in root: + defaults = dict() + + # defaults['pk'] = int(child.find('ID').text) + defaults['asset_id'] = child.find('AssetID').text + + try: + defaults['description'] = child.find('AssetDescription').text + except AttributeError: + defaults['description'] = 'None' + + defaults['category'] = models.AssetCategory.objects.get(pk=int(child.find('AssetCategoryID').text)) + defaults['status'] = models.AssetStatus.objects.get(pk=int(child.find('StatusID').text)) + + try: + defaults['serial_number'] = child.find('SerialNumber').text + except AttributeError: + pass + + try: + defaults['purchased_from'] = models.Supplier.objects.get(pk=int(child.find('Supplier_x0020_Id').text)) + except AttributeError: + pass + + try: + defaults['date_acquired'] = datetime.datetime.strptime(child.find('DateAcquired').text, '%d/%m/%Y').date() + except AttributeError: + defaults['date_acquired'] = self.epoch + + try: + defaults['date_sold'] = datetime.datetime.strptime(child.find('DateSold').text, '%d/%m/%Y').date() + except AttributeError: + pass + + try: + defaults['purchase_price'] = float(child.find('Replacement_x0020_Value').text) + except AttributeError: + pass + + try: + defaults['salvage_value'] = float(child.find('SalvageValue').text) + except AttributeError: + pass + + try: + defaults['comments'] = child.find('Comments').text + except AttributeError: + pass + + try: + date = child.find('NextSchedMaint').text.split('T')[0] + defaults['next_sched_maint'] = datetime.datetime.strptime(date, '%Y-%m-%d').date() + except AttributeError: + pass + + print(defaults) + + obj, created = models.Asset.objects.update_or_create(**defaults) + + if created: + tally[1] += 1 + else: + tally[0] += 1 + + print('Assets - Updated: {}, Created: {}'.format(tally[0], tally[1])) + + def import_collections(self): + tally = [0, 0] + root = self.parse_xml(self.xml_path('TEC_Cable_Collections.xml')) + + for child in root: + defaults = dict() + + defaults['pk'] = int(child.find('ID').text) + defaults['name'] = child.find('Cable_x0020_Trunk').text + + obj, created = models.Collection.objects.update_or_create(**defaults) + + if created: + tally[1] += 1 + else: + tally[0] += 1 + + print('Collections - Updated: {}, Created: {}'.format(tally[0], tally[1])) + + def import_cables(self): + tally = [0, 0] + root = self.parse_xml(self.xml_path('TEC_Cables.xml')) + + for child in root: + defaults = dict() + + defaults['asset_id'] = child.find('Asset_x0020_Number').text + + try: + defaults['description'] = child.find('Type_x0020_of_x0020_Cable').text + except AttributeError: + defaults['description'] = 'None' + + defaults['is_cable'] = True + defaults['category'] = models.AssetCategory.objects.get(pk=9) + + try: + defaults['length'] = child.find('Length_x0020__x0028_m_x0029_').text + except AttributeError: + pass + + defaults['status'] = models.AssetStatus.objects.get(pk=int(child.find('Status').text)) + + try: + defaults['comments'] = child.find('Comments').text + except AttributeError: + pass + + try: + collection_id = int(child.find('Collection').text) + if collection_id != 0: + defaults['collection'] = models.Collection.objects.get(pk=collection_id) + except AttributeError: + pass + + try: + defaults['purchase_price'] = float(child.find('Purchase_x0020_Price').text) + except AttributeError: + pass + + defaults['date_acquired'] = self.epoch + + print(defaults) + + obj, created = models.Asset.objects.update_or_create(**defaults) + + if created: + tally[1] += 1 + else: + tally[0] += 1 + + print('Collections - Updated: {}, Created: {}'.format(tally[0], tally[1])) diff --git a/management/commands/update_old_db_file.py b/management/commands/update_old_db_file.py new file mode 100644 index 00000000..f49593e1 --- /dev/null +++ b/management/commands/update_old_db_file.py @@ -0,0 +1,112 @@ +import os +from django.core.management.base import BaseCommand, CommandError +from django.conf import settings +import datetime +import xml.etree.ElementTree as ET + +from assets import models + + +class Command(BaseCommand): + help = 'Imports old db from XML dump' + + epoch = datetime.date(1970, 1, 1) + + def handle(self, *args, **options): + # self.update_statuses() + # self.update_suppliers() + self.update_cable_statuses() + + @staticmethod + def xml_path(file): + return os.path.join(settings.BASE_DIR, 'data/DB_Dump/{}'.format(file)) + + @staticmethod + def parse_xml(file): + tree = ET.parse(file) + + return tree.getroot() + + def update_statuses(self): + file = self.xml_path('TEC_Assets.xml') + tree = ET.parse(file) + root = tree.getroot() + + # map old status pk to new status pk + status_map = { + 2: 2, + 3: 4, + 4: 3, + 5: 5, + 6: 1 + } + + for child in root: + status = int(child.find('StatusID').text) + child.find('StatusID').text = str(status_map[status]) + + tree.write(file) + + def update_suppliers(self): + old_file = self.xml_path('TEC_Asset_Suppliers.xml') + old_tree = ET.parse(old_file) + old_root = old_tree.getroot() + + new_file = self.xml_path('TEC_Asset_Suppliers_new.xml') + new_tree = ET.parse(new_file) + new_root = new_tree.getroot() + + # map old supplier pk to new supplier pk + supplier_map = dict() + + def find_in_old(name, root): + for child in root: + found_id = child.find('Supplier_x0020_Id').text + found_name = child.find('Supplier_x0020_Name').text + + if found_name == name: + return found_id + + for new_child in new_root: + new_id = new_child.find('Supplier_x0020_Id').text + new_name = new_child.find('Supplier_x0020_Name').text + + old_id = find_in_old(new_name, old_root) + + supplier_map[int(old_id)] = int(new_id) + + file = self.xml_path('TEC_Assets.xml') + tree = ET.parse(file) + root = tree.getroot() + + for child in root: + try: + supplier = int(child.find('Supplier_x0020_Id').text) + child.find('Supplier_x0020_Id').text = str(supplier_map[supplier]) + except AttributeError: + pass + + tree.write(file) + + def update_cable_statuses(self): + file = self.xml_path('TEC_Cables.xml') + tree = ET.parse(file) + root = tree.getroot() + + # map old status pk to new status pk + status_map = { + 0: 7, + 1: 3, + 3: 2, + 4: 5, + 6: 6, + 7: 1, + 8: 4, + 9: 2, + } + + for child in root: + status = int(child.find('Status').text) + child.find('Status').text = str(status_map[status]) + + tree.write(file) From 9f3c1f7f3cdd0d6ac79d25ff27e57751a742d17d Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Sat, 5 Jan 2019 19:59:05 +0000 Subject: [PATCH 15/34] Updated asset list --- templates/asset_list.html | 43 +++++++++++++++++----------- templates/asset_list_table_body.html | 13 +++++++++ 2 files changed, 40 insertions(+), 16 deletions(-) create mode 100644 templates/asset_list_table_body.html diff --git a/templates/asset_list.html b/templates/asset_list.html index 95f48b43..055846b8 100644 --- a/templates/asset_list.html +++ b/templates/asset_list.html @@ -1,10 +1,21 @@ {% extends 'base.html' %} +{% block title %}List{% endblock %} {% block main %} -

        Asset List

        +

        Asset List

        - + + {% csrf_token %} +
        + + +
        + + + + +
        @@ -14,21 +25,21 @@ - - {% for item in object_list %} -{#
      • {{ item.asset_id }} - {{ item.description }}
      • #} - - - - - - - - {% endfor %} + + {% include 'asset_list_table_body.html' %}
        Asset IDQuick Links
        {{ item.asset_id }}{{ item.description }}{{ item.category }}{{ item.status }} - visibility - edit -
        + {% include 'helpers/paginator.html' %} + +{% endblock %} + +{% block script %} + {% endblock %} \ No newline at end of file diff --git a/templates/asset_list_table_body.html b/templates/asset_list_table_body.html new file mode 100644 index 00000000..289762e1 --- /dev/null +++ b/templates/asset_list_table_body.html @@ -0,0 +1,13 @@ +{% for item in object_list %} + {#
      • {{ item.asset_id }} - {{ item.description }}
      • #} + + {{ item.asset_id }} + {{ item.description }} + {{ item.category }} + {{ item.status }} + + visibility + edit + + +{% endfor %} \ No newline at end of file From fbfd8f272bafd1c57b11e279987d957878b553c3 Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Sat, 5 Jan 2019 20:00:00 +0000 Subject: [PATCH 16/34] Working asset edit/create template --- filters.py | 9 + forms.py | 9 + static/js/ajax_form.js | 73 +++++ static/js/csrf.js | 23 ++ templates/asset_form.html | 15 -- templates/asset_update.html | 298 +++++++++++++++++++++ templates/asset_update_search_results.html | 16 ++ 7 files changed, 428 insertions(+), 15 deletions(-) create mode 100644 filters.py create mode 100644 forms.py create mode 100644 static/js/ajax_form.js create mode 100644 static/js/csrf.js delete mode 100644 templates/asset_form.html create mode 100644 templates/asset_update.html create mode 100644 templates/asset_update_search_results.html diff --git a/filters.py b/filters.py new file mode 100644 index 00000000..c7efcd7f --- /dev/null +++ b/filters.py @@ -0,0 +1,9 @@ +import django_filters + +from assets import models + + +class AssetFilter(django_filters.FilterSet): + class Meta: + model = models.Asset + fields = ['asset_id', 'description', 'category', 'status'] diff --git a/forms.py b/forms.py new file mode 100644 index 00000000..90a02b9a --- /dev/null +++ b/forms.py @@ -0,0 +1,9 @@ +from django import forms + +from assets import models + + +class AssetForm(forms.ModelForm): + class Meta: + model = models.Asset + fields = '__all__' diff --git a/static/js/ajax_form.js b/static/js/ajax_form.js new file mode 100644 index 00000000..d90363a3 --- /dev/null +++ b/static/js/ajax_form.js @@ -0,0 +1,73 @@ +function filterAssetTable() { + $.ajax({ + url : "/asset/filter/", // the endpoint + type : "POST", // http method + data : { + form: $('#asset-filter-form').serialize() + }, + traditional: true, + + success : function(data) { + // console.log(data); + $('#asset_table_body').html(data) + }, + + error : function(xhr) {console.log(xhr.status + ": " + xhr.responseText)} + }); +} + +function updateAsset() { + $.ajax({ + url : "/asset/update/", // the endpoint + type : "POST", // http method + data : { + form: $('#asset_update_form').serialize() + }, + traditional: true, + + success : function(data) { + // console.log(data); + window.location.href = data['url']; + }, + + error : function(xhr) {console.log(xhr.status + ": " + xhr.responseText)} + }); +} + +function formAssetSearch() { + $.ajax({ + url : "/asset/filter/", // the endpoint + type : "POST", // http method + data : { + sender: 'asset_update', + form: "csrfmiddlewaretoken=" + $('input[name=csrfmiddlewaretoken]').val() + "&asset_id=" + $('#parent_search').val() + }, + traditional: true, + + success : function(data) { + // console.log(data); + $('#formAssetSearchResult').html(data); + // window.location.href = data['url']; + }, + + error : function(xhr) {console.log(xhr.status + ": " + xhr.responseText)} + }); +} + +function deleteAsset(asset_id) { + $.ajax({ + url : "/asset/delete/", // the endpoint + type : "POST", // http method + data : { + asset_id: asset_id + }, + traditional: true, + + success : function(data) { + // console.log(data); + window.location.href = data['url']; + }, + + error : function(xhr) {console.log(xhr.status + ": " + xhr.responseText)} + }); +} \ No newline at end of file diff --git a/static/js/csrf.js b/static/js/csrf.js new file mode 100644 index 00000000..895ce31e --- /dev/null +++ b/static/js/csrf.js @@ -0,0 +1,23 @@ +$.ajaxSetup({ + beforeSend: function(xhr, settings) { + function getCookie(name) { + var cookieValue = null; + if (document.cookie && document.cookie != '') { + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = jQuery.trim(cookies[i]); + // Does this cookie string begin with the name we want? + if (cookie.substring(0, name.length + 1) == (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; + } + if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) { + // Only send the token to relative URLs i.e. locally. + xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')); + } + } +}); \ No newline at end of file diff --git a/templates/asset_form.html b/templates/asset_form.html deleted file mode 100644 index 65c21816..00000000 --- a/templates/asset_form.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends 'base.html' %} - - -{% block main %} - -

        Asset {{ form_header|default:'Create' }}

        - -
        - {{ form }} - {% csrf_token %} - - -
        - -{% endblock %} diff --git a/templates/asset_update.html b/templates/asset_update.html new file mode 100644 index 00000000..fd0d6bf8 --- /dev/null +++ b/templates/asset_update.html @@ -0,0 +1,298 @@ +{% extends 'base.html' %} +{% load widget_tweaks %} +{% load asset_templatetags %} +{% block title %}Asset {{ object.asset_id }}{% endblock %} + + +{% block main %} + +

        + Asset + {% if object %} + | {{ object.asset_id }} + {% else %} + Create + {% endif %} +

        +
        + +
        + +
        + {% csrf_token %} + + +
        +
        +
        + {% if edit %} +{# #} + + {% else %} + Edit + {% endif %} + {% if object %} + Delete + {% endif %} +
        +
        +
        + +
        +
        +
        + Asset Details +
        +
        +
        + {% if edit %} +
        +
        + {% if object.asset_id %} + {% render_field form.asset_id|attr:'readonly'|add_class:'disabled_input' value=object.asset_id %} + {% else %} + {% render_field form.asset_id %} + {% endif %} + +
        + +
        + {% render_field form.description value=object.description %} + +
        + +
        + + +
        + +
        + + +
        + +
        + {% render_field form.serial_number value=object.serial_number %} + +
        + +
        + {% render_field form.comments|add_class:'materialize-textarea' %} + +
        + +
        + {% else %} +
        Asset ID
        +
        {{ object.asset_id }}
        + +
        Description
        +
        {{ object.description }}
        + +
        Category
        +
        {{ object.category }}
        + +
        Status
        +
        {{ object.status }}
        + +
        Serial Number
        +
        {{ object.serial_number|default:'-' }}
        + +
        Comments
        +
        {{ object.comments|default:'-'|linebreaksbr }}
        + {% endif %} +
        +
        +
        +
        + +
        +
        +
        + Purchase Details +
        +
        +
        + {% if edit %} +
        +
        + + +
        + +
        + {% render_field form.purchase_price value=object.purchase_price %} + +
        + +
        + {% render_field form.salvage_value value=object.salvage_value %} + +
        + +
        + {% render_field form.date_acquired|add_class:'datepicker' value=object.date_acquired|date %} + +
        + +
        + {% render_field form.date_sold|add_class:'datepicker' value=object.date_sold|date %} + +
        +
        + {% else %} +
        Purchased From
        +
        {{ object.purchased_from|default_if_none:'-' }}
        + +
        Purchase Price
        +
        £{{ object.purchase_price|default_if_none:'-' }}
        + +
        Salvage Value
        +
        £{{ object.salvage_value|default_if_none:'-' }}
        + +
        Date Acquired
        +
        {{ object.date_acquired|default_if_none:'-' }}
        + +
        Date Sold
        +
        {{ object.date_sold|default_if_none:'-' }}
        + {% endif %} +
        +
        +
        +
        + + {% if object.is_cable %} +
        +
        +
        + Cable Details +
        +
        +
        +
        Length
        +
        {{ object.length }}m
        + +
        Type
        +
        TODO
        + +
        Required CSA
        +
        TODO
        +
        +
        +
        +
        + {% endif %} + +
        +
        +
        + Collection Details +
        +
        +
        + {% if edit %} +
        + +
        + + + +
        +
        + +
        +
        + + +{# {% render_field form.parent value=object.parent %}#} +
        +
        + +
        +
        +
        + +
        +
        + {% else %} +
        Parent
        +
        + {% if object.parent %} + + {{ object.parent.asset_id }} - {{ object.parent.description }} + + {% else %} + - + {% endif %} +
        + +
        Children
        + {% if object.asset_parent.all %} + {% for child in object.asset_parent.all %} +
        + + {{ child.asset_id }} - {{ child.description }} + +
        + {% endfor %} + {% else %} +
        -
        + {% endif %} + {% endif %} +
        +
        +
        +
        +
        + + {% include 'confirm_delete.html' with object=object %} + +{% endblock %} + +{% block script %} + +{# #} + + + + + +{% endblock %} \ No newline at end of file diff --git a/templates/asset_update_search_results.html b/templates/asset_update_search_results.html new file mode 100644 index 00000000..edd156f1 --- /dev/null +++ b/templates/asset_update_search_results.html @@ -0,0 +1,16 @@ +{% for asset in object_list %} + + {{ asset.asset_id }} - {{ asset.description }} + +
        + {% empty %} + No assets match given ID +{% endfor %} + + \ No newline at end of file From b72bc6356078745f38c1bcf95a809c75fc359b18 Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Sat, 5 Jan 2019 20:01:02 +0000 Subject: [PATCH 17/34] Templatetags for better field rendering --- templatetags/__init__.py | 0 templatetags/asset_templatetags.py | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 templatetags/__init__.py create mode 100644 templatetags/asset_templatetags.py diff --git a/templatetags/__init__.py b/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/templatetags/asset_templatetags.py b/templatetags/asset_templatetags.py new file mode 100644 index 00000000..905f9ce2 --- /dev/null +++ b/templatetags/asset_templatetags.py @@ -0,0 +1,21 @@ +from django import template +from django.template.defaultfilters import stringfilter +from django.utils.safestring import SafeData, mark_safe +from django.utils.text import normalize_newlines +from django.utils.html import escape + +register = template.Library() + + +@register.filter(is_safe=True, needs_autoescape=True) +@stringfilter +def linebreaksn(value, autoescape=True): + """ + Convert all newlines in a piece of plain text to jQuery line breaks + (`\n`). + """ + autoescape = autoescape and not isinstance(value, SafeData) + value = normalize_newlines(value) + if autoescape: + value = escape(value) + return mark_safe(value.replace('\n', '\\n')) From e8fcfd3a504073d76ec1cf5d28d3160313f83b39 Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Sat, 5 Jan 2019 20:01:19 +0000 Subject: [PATCH 18/34] Sass updates --- static/sass/components/_mixins.scss | 16 +++++ static/sass/components/_variables.scss | 4 +- static/sass/main.scss | 42 ------------ static/sass/materialize.scss | 89 ++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 44 deletions(-) create mode 100644 static/sass/components/_mixins.scss delete mode 100644 static/sass/main.scss diff --git a/static/sass/components/_mixins.scss b/static/sass/components/_mixins.scss new file mode 100644 index 00000000..dc1a95d3 --- /dev/null +++ b/static/sass/components/_mixins.scss @@ -0,0 +1,16 @@ +@mixin clearfix() { + &:before, + &:after { + content: " "; // 1 + display: table; // 2 + } + &:after { + clear: both; + } +} + +@mixin text-overflow() { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/static/sass/components/_variables.scss b/static/sass/components/_variables.scss index 4c59c127..e40d9652 100644 --- a/static/sass/components/_variables.scss +++ b/static/sass/components/_variables.scss @@ -34,11 +34,11 @@ // 1. Colors // ========================================================================== -$primary-color: color("materialize-red", "lighten-2") !default; +$primary-color: color("grey", "darken-4") !default; $primary-color-light: lighten($primary-color, 15%) !default; $primary-color-dark: darken($primary-color, 15%) !default; -$secondary-color: color("teal", "lighten-1") !default; +$secondary-color: color("blue", "darken-4") !default; $success-color: color("green", "base") !default; $error-color: color("red", "base") !default; $link-color: color("light-blue", "darken-1") !default; diff --git a/static/sass/main.scss b/static/sass/main.scss deleted file mode 100644 index cc102c43..00000000 --- a/static/sass/main.scss +++ /dev/null @@ -1,42 +0,0 @@ -@charset "UTF-8"; -//@import "materialize"; - -// Mixins -@import "components/color-classes"; -@import "components/color-variables"; - -// Variables -@import "components/variables"; -@import "components/normalize"; - -// components -@import "components/global"; - -@import "components/badges"; -@import "components/icons-material-design"; -@import "components/grid"; -@import "components/navbar"; -//@import "materialize/components/roboto"; -//@import "materialize/components/typography"; -//@import "materialize/components/transitions"; -//@import "materialize/components/cards"; -//@import "materialize/components/toast"; -//@import "materialize/components/tabs"; -//@import "materialize/components/tooltip"; -//@import "materialize/components/buttons"; -//@import "materialize/components/dropdown"; -//@import "materialize/components/waves"; -//@import "materialize/components/modal"; -//@import "materialize/components/collapsible"; -//@import "materialize/components/chips"; -//@import "materialize/components/materialbox"; -//@import "materialize/components/forms/forms"; -//@import "materialize/components/table_of_contents"; -//@import "materialize/components/sideNav"; -//@import "materialize/components/preloader"; -//@import "materialize/components/slider"; -//@import "materialize/components/carousel"; -//@import "materialize/components/date_picker/default"; -//@import "materialize/components/date_picker/default.date"; -//@import "materialize/components/date_picker/default.time"; - diff --git a/static/sass/materialize.scss b/static/sass/materialize.scss index 37c64290..b7829557 100644 --- a/static/sass/materialize.scss +++ b/static/sass/materialize.scss @@ -11,6 +11,7 @@ @import "components/normalize"; // components +@import "components/mixins"; @import "components/global"; @import "components/badges"; @import "components/icons-material-design"; @@ -40,10 +41,98 @@ @import "components/datepicker"; @import "components/timepicker"; +#full-div { + //width: 100vw; + height: 90vh; +} + + .container { width: 80% !important; } .nav-wrapper { padding: 0 10%; +} + +nav { + >div>ul>li>a { + color: #aaa; + } +} + +.panel { + background-color: color('grey', 'lighten-5'); + border: 1px solid $primary-color; + border-radius: 4px; +} + +.panel-heading { + color: white; + background-color: $primary-color; + border-color: #bce8f1; + + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-right-radius: 3px; + border-top-left-radius: 3px; +} + +.panel-body { + padding: 15px; + + &.quick-actions { + >:last-child{ + margin-bottom: 0 !important; + } + >:first-child { + margin-top: 0 !important; + } + } +} + +dt { + font-weight: bold; +} + +$dl-horizontal-breakpoint: 768px; +$dl-horizontal-offset: 160px !default; + +.dl-horizontal { + dd { + @include clearfix; // Clear the floated `dt` if an empty `dd` is present + } + + @media (min-width: $dl-horizontal-breakpoint) { + dt { + float: left; + width: ($dl-horizontal-offset - 20); + clear: left; + text-align: right; + @include text-overflow; + } + dd { + margin-left: $dl-horizontal-offset; + } + } +} + +.button-group { + >button { + display: inline-block; + } +} + +.no-pad { + padding: 0 !important; +} + +.bold-text { + font-weight: bold; +} + +.disabled_input { + color: rgba(0, 0, 0, 0.42); + border-bottom: 1px dotted rgba(0, 0, 0, 0.42) !important; + cursor: default; } \ No newline at end of file From 779fc42f26b4ac6a42c20c41fb2669ceb2d922f6 Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Sat, 5 Jan 2019 20:04:58 +0000 Subject: [PATCH 19/34] Added views --- urls.py | 15 ++++--- views.py | 135 ++++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 122 insertions(+), 28 deletions(-) diff --git a/urls.py b/urls.py index bfc532c9..da61ab38 100644 --- a/urls.py +++ b/urls.py @@ -2,10 +2,13 @@ from django.urls import path from assets import views urlpatterns = [ - path('', views.Index.as_view(), name='index'), - path('assets/', views.AssetList.as_view(), name='asset_list'), - path('assets//', views.AssetDetail.as_view(), name='asset_detail'), - path('assets/create', views.AssetCreate.as_view(), name='asset_create'), - path('assets//update', views.AssetUpdate.as_view(), name='asset_update'), - path('assets//delete', views.AssetDelete.as_view(), name='asset_delete'), + # path('', views.Index.as_view(), name='index'), + path('', views.AssetList.as_view(), name='index'), + path('asset/list/', views.AssetList.as_view(), name='asset_list'), + path('asset//', views.AssetDetail.as_view(), name='asset_detail'), + path('asset/create/', views.AssetEdit.as_view(), name='asset_create'), + path('asset//edit/', views.AssetEdit.as_view(), name='asset_update'), + path('asset/delete/', views.asset_delete, name='ajax_asset_delete'), + path('asset/filter/', views.asset_filter, name='ajax_asset_filter'), + path('asset/update/', views.asset_update, name='ajax_asset_update'), ] diff --git a/views.py b/views.py index 79a45dc6..3bc31e4d 100644 --- a/views.py +++ b/views.py @@ -1,42 +1,133 @@ -from django.shortcuts import render +from django.shortcuts import render, get_object_or_404 +from django.contrib.auth.decorators import login_required +from django.contrib.auth.mixins import LoginRequiredMixin +from django.http import HttpResponse, QueryDict +from django.core import serializers from django.views import generic -from django.urls import reverse_lazy -from assets import models +from django.contrib.auth import views as auth_views +from django.urls import reverse_lazy, reverse +from django.db.models import Q +import datetime +from dateutil import parser +# import json +import simplejson as json +from assets import models, forms -class Index(generic.TemplateView): +class Login(auth_views.LoginView): + template_name = 'registration/login.html' + + +class Logout(auth_views.LogoutView): + template_name = 'registration/logout.html' + + +class PasswordChange(auth_views.PasswordChangeView): + template_name = 'registration/password_change.html' + + def get_success_url(self): + return reverse_lazy('profile_detail', kwargs={'pk': self.request.user.id}) + + +class Index(LoginRequiredMixin, generic.TemplateView): template_name = 'index.html' -class AssetList(generic.ListView): +class AssetList(LoginRequiredMixin, generic.ListView): model = models.Asset template_name = 'asset_list.html' + paginate_by = 40 + ordering = ['-pk'] -class AssetDetail(generic.DetailView): +class AssetDetail(LoginRequiredMixin, generic.DetailView): model = models.Asset - template_name = 'asset_detail.html' + template_name = 'asset_update.html' -class AssetCreate(generic.CreateView): - model = models.Asset - fields = '__all__' - template_name = 'asset_form.html' - # success_url = reverse_lazy('asset_list') +# class AssetCreate(LoginRequiredMixin, generic.TemplateView): +# fields = '__all__' +# template_name = 'asset_update.html' +# # success_url = reverse_lazy('asset_list') -class AssetUpdate(generic.UpdateView): - model = models.Asset - fields = '__all__' - template_name = 'asset_form.html' +class AssetEdit(LoginRequiredMixin, generic.TemplateView): + template_name = 'asset_update.html' def get_context_data(self, **kwargs): - context = super(AssetUpdate, self).get_context_data(**kwargs) - context['form_header'] = 'Update' + context = super(AssetEdit, self).get_context_data(**kwargs) + if self.kwargs: + context['object'] = get_object_or_404(models.Asset, pk=self.kwargs['pk']) + context['form'] = forms.AssetForm + # context['asset_names'] = models.Asset.objects.values_list('asset_id', 'description').order_by('-date_acquired')[] + context['edit'] = True + return context -class AssetDelete(generic.DeleteView): - model = models.Asset - template_name = 'confirm_delete.html' - success_url = reverse_lazy('asset_list') +@login_required() +def asset_update(request): + context = dict() + + if request.method == 'POST' and request.is_ajax(): + defaults = QueryDict(request.POST['form'].encode('ASCII')).dict() + defaults.pop('csrfmiddlewaretoken') + + asset_pk = int(defaults.pop('id')) + + if defaults['date_acquired']: + defaults['date_acquired'] = parser.parse(defaults.pop('date_acquired')) + else: + defaults['date_acquired'] = None + + if defaults['date_sold']: + defaults['date_sold'] = parser.parse(defaults.pop('date_sold')) + else: + defaults['date_sold'] = None + + # if defaults['parent']: + # defaults['parent'] = models.Asset.objects.get(asset_id=defaults.pop('parent')) + + form = forms.AssetForm(defaults) + context['valid'] = form.is_valid() + context['errors'] = form.errors.as_json() + + if asset_pk == 0: + asset = models.Asset.objects.create(**form.cleaned_data) + else: + asset, created = models.Asset.objects.update_or_create(pk=asset_pk, defaults=form.cleaned_data) + + context['url'] = reverse('asset_detail', args=[asset.pk]) + + return HttpResponse(json.dumps(context), content_type='application/json') + + +@login_required() +def asset_delete(request): + context = dict() + if request.method == 'POST' and request.is_ajax(): + asset = get_object_or_404(models.Asset, pk=request.POST.get('asset_id', None)) + asset.delete() + + context['url'] = reverse('asset_list') + + return HttpResponse(json.dumps(context), content_type='application/json') + + +@login_required() +def asset_filter(request): + context = dict() + + if request.method == 'POST' and request.is_ajax(): + defaults = QueryDict(request.POST['form'].encode('ASCII')).dict() + defaults.pop('csrfmiddlewaretoken') + + context['object_list'] = models.Asset.objects.filter( + Q(pk__icontains=defaults.get('asset_id')) | + Q(asset_id__icontains=defaults.get('asset_id')) + ) + + if request.POST.get('sender', None) == 'asset_update': + return render(request, template_name='asset_update_search_results.html', context=context) + else: + return render(request, template_name='asset_list_table_body.html', context=context) From ef8ddf3642849d6c05ad19cfcca4b4f22abd08f8 Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Sat, 5 Jan 2019 20:05:14 +0000 Subject: [PATCH 20/34] Removed old template --- templates/asset_detail.html | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 templates/asset_detail.html diff --git a/templates/asset_detail.html b/templates/asset_detail.html deleted file mode 100644 index 9bc7896f..00000000 --- a/templates/asset_detail.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends 'base.html' %} - - -{% block main %} - -

        Asset Detail

        - - {{ object.description }} -
        - {{ object.category.name }} -
        - {{ object.status.name }} -
        - {{ object.purchased_from.name }} -
        - Edit -
        - Delete - -{% endblock %} From d8440ad9e5501ec5ed6056b5f087e94e6ab467d7 Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Sat, 5 Jan 2019 20:05:41 +0000 Subject: [PATCH 21/34] Updated admin and settings --- admin.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/admin.py b/admin.py index d0f14bff..9e01045c 100644 --- a/admin.py +++ b/admin.py @@ -1,26 +1,37 @@ from django.contrib import admin from assets import models as assets + @admin.register(assets.AssetCategory) class AssetCategoryAdmin(admin.ModelAdmin): - pass + list_display = ['id', 'name'] + ordering = ['id'] @admin.register(assets.AssetStatus) class AssetStatusAdmin(admin.ModelAdmin): - pass + list_display = ['id', 'name'] + ordering = ['id'] @admin.register(assets.Supplier) class SupplierAdmin(admin.ModelAdmin): - pass - - -@admin.register(assets.Collection) -class CollectionAdmin(admin.ModelAdmin): - pass + list_display = ['id', 'name'] + ordering = ['id'] @admin.register(assets.Asset) class AssetAdmin(admin.ModelAdmin): + list_display = ['id', 'asset_id', 'description', 'category', 'status'] + list_filter = ['is_cable', 'category'] + search_fields = ['id', 'asset_id', 'description'] + + +@admin.register(assets.Connector) +class ConnectorAdmin(admin.ModelAdmin): + list_display = ['id', '__str__', 'current_rating', 'voltage_rating', 'num_pins'] + + +@admin.register(assets.Cable) +class CableAdmin(admin.ModelAdmin): pass From 51aec6f59701a3b817a7afb364e32a8255b96234 Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Sat, 5 Jan 2019 22:52:39 +0000 Subject: [PATCH 22/34] Added basic api --- api.py | 29 +++++++++++++++++++++++++++++ urls.py | 11 +++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 api.py diff --git a/api.py b/api.py new file mode 100644 index 00000000..3bd62db6 --- /dev/null +++ b/api.py @@ -0,0 +1,29 @@ +# endpoint method result +# +# api/assets/ get list all assets +# api/assets/ get get a specific asset + + +from rest_framework import serializers, viewsets, status +from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework.generics import get_object_or_404 +from django.db.models import Min +from django.core.mail import EmailMessage +from django.template.loader import get_template +from django.template import Context, RequestContext +import datetime + +from assets import models +from django.conf import settings + + +class AssetSerializer(serializers.ModelSerializer): + class Meta: + model = models.Asset + fields = '__all__' + + +class AssetViewSet(viewsets.ModelViewSet): + queryset = models.Asset.objects.all() + serializer_class = AssetSerializer diff --git a/urls.py b/urls.py index da61ab38..cbff5d47 100644 --- a/urls.py +++ b/urls.py @@ -1,5 +1,9 @@ -from django.urls import path -from assets import views +from django.urls import path, include +from rest_framework import routers +from assets import views, api + +router = routers.DefaultRouter() +router.register(r'api/assets', api.AssetViewSet) urlpatterns = [ # path('', views.Index.as_view(), name='index'), @@ -11,4 +15,7 @@ urlpatterns = [ path('asset/delete/', views.asset_delete, name='ajax_asset_delete'), path('asset/filter/', views.asset_filter, name='ajax_asset_filter'), path('asset/update/', views.asset_update, name='ajax_asset_update'), + + path('', include(router.urls)), ] + From f24a510a8ed8dc219024a09a53398a63477416ef Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Tue, 8 Jan 2019 00:21:58 +0000 Subject: [PATCH 23/34] Added crud for suppliers --- forms.py | 6 ++++++ models.py | 5 +++-- templates/supplier_detail.html | 6 ++++++ templates/supplier_list.html | 29 +++++++++++++++++++++++++++++ templates/supplier_update.html | 24 ++++++++++++++++++++++++ urls.py | 5 +++++ views.py | 24 ++++++++++++++++++++++++ 7 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 templates/supplier_detail.html create mode 100644 templates/supplier_list.html create mode 100644 templates/supplier_update.html diff --git a/forms.py b/forms.py index 90a02b9a..c727a9d1 100644 --- a/forms.py +++ b/forms.py @@ -7,3 +7,9 @@ class AssetForm(forms.ModelForm): class Meta: model = models.Asset fields = '__all__' + + +class SupplierForm(forms.ModelForm): + class Meta: + model = models.Supplier + fields = '__all__' diff --git a/models.py b/models.py index c6053816..efa32341 100644 --- a/models.py +++ b/models.py @@ -1,6 +1,4 @@ from django.db import models -from django.db.models.signals import post_save -from django.dispatch import receiver from django.urls import reverse @@ -27,6 +25,9 @@ class AssetStatus(models.Model): class Supplier(models.Model): name = models.CharField(max_length=80) + def get_absolute_url(self): + return reverse('supplier_list') + def __str__(self): return self.name diff --git a/templates/supplier_detail.html b/templates/supplier_detail.html new file mode 100644 index 00000000..135fdeba --- /dev/null +++ b/templates/supplier_detail.html @@ -0,0 +1,6 @@ +{% extends 'base.html' %} +{% block title %}Detail{% endblock %} + +{% block main %} +{{ object }} +{% endblock %} \ No newline at end of file diff --git a/templates/supplier_list.html b/templates/supplier_list.html new file mode 100644 index 00000000..bcfd8e09 --- /dev/null +++ b/templates/supplier_list.html @@ -0,0 +1,29 @@ +{% extends 'base.html' %} +{% block title %}List{% endblock %} + +{% block main %} + +

        Supplier List

        + + + + + + + + + + {% for item in object_list %} + + + + + {% endfor %} + +
        SupplierQuick Links
        {{ item.name }} + edit +
        + + {% include 'helpers/paginator.html' %} + +{% endblock %} \ No newline at end of file diff --git a/templates/supplier_update.html b/templates/supplier_update.html new file mode 100644 index 00000000..92ea3787 --- /dev/null +++ b/templates/supplier_update.html @@ -0,0 +1,24 @@ +{% extends 'base.html' %} +{% block title %}Edit{% endblock %} + +{% block main %} + +

        + Supplier + {% if object %} + Edit | {{ object.name }} + {% else %} + Create + {% endif %} +

        + +
        + +
        + {% csrf_token %} + + {{ form }} + + +
        +{% endblock %} \ No newline at end of file diff --git a/urls.py b/urls.py index cbff5d47..68727f9f 100644 --- a/urls.py +++ b/urls.py @@ -16,6 +16,11 @@ urlpatterns = [ path('asset/filter/', views.asset_filter, name='ajax_asset_filter'), path('asset/update/', views.asset_update, name='ajax_asset_update'), + path('supplier/list', views.SupplierList.as_view(), name='supplier_list'), + path('supplier/', views.SupplierDetail.as_view(), name='supplier_detail'), + path('supplier/create', views.SupplierCreate.as_view(), name='supplier_create'), + path('supplier//edit', views.SupplierUpdate.as_view(), name='supplier_update'), + path('', include(router.urls)), ] diff --git a/views.py b/views.py index 3bc31e4d..77f5e6c6 100644 --- a/views.py +++ b/views.py @@ -131,3 +131,27 @@ def asset_filter(request): return render(request, template_name='asset_update_search_results.html', context=context) else: return render(request, template_name='asset_list_table_body.html', context=context) + + +class SupplierList(generic.ListView): + model = models.Supplier + template_name = 'supplier_list.html' + paginate_by = 40 + ordering = ['name'] + + +class SupplierDetail(generic.DetailView): + model = models.Supplier + template_name = 'supplier_detail.html' + + +class SupplierCreate(generic.CreateView): + model = models.Supplier + form_class = forms.SupplierForm + template_name = 'supplier_update.html' + + +class SupplierUpdate(generic.UpdateView): + model = models.Supplier + form_class = forms.SupplierForm + template_name = 'supplier_update.html' From 905eea7792c421d4d0eff1df8c677a3d5741ac1f Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Tue, 8 Jan 2019 00:22:46 +0000 Subject: [PATCH 24/34] Updated sample data creation --- management/commands/createBaseUsers.py | 5 +++-- management/commands/createSampleData.py | 13 +++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/management/commands/createBaseUsers.py b/management/commands/createBaseUsers.py index 8bf0437a..ba24c508 100644 --- a/management/commands/createBaseUsers.py +++ b/management/commands/createBaseUsers.py @@ -15,7 +15,8 @@ class Command(BaseCommand): self.create_user_object('staff', True) self.create_user_object('basic') - def create_user_object(self, name, staff=False, superuser=False): + @staticmethod + def create_user_object(name, staff=False, superuser=False): user, created = User.objects.get_or_create( username=name, defaults={'email': '{}@{}.com'.format(name, name), 'first_name': name.title(), 'last_name': 'User', 'is_superuser': superuser, @@ -23,4 +24,4 @@ class Command(BaseCommand): if created: user.set_password(name) - user.save() \ No newline at end of file + user.save() diff --git a/management/commands/createSampleData.py b/management/commands/createSampleData.py index 9a5af2e2..e8d4c863 100644 --- a/management/commands/createSampleData.py +++ b/management/commands/createSampleData.py @@ -1,4 +1,5 @@ from django.core.management.base import BaseCommand, CommandError +from django.core.management import call_command from django.utils import timezone import random @@ -11,15 +12,16 @@ class Command(BaseCommand): def handle(self, *args, **kwargs): from django.conf import settings - if not (settings.DEBUG): + if not settings.DEBUG: raise CommandError('You cannot run this command in production') random.seed('Some object to see the random number generator') + call_command('createBaseUsers') + self.create_categories() self.create_statuses() self.create_suppliers() - self.create_collections() self.create_assets() def create_categories(self): @@ -40,12 +42,6 @@ class Command(BaseCommand): for supplier in suppliers: models.Supplier.objects.create(name=supplier) - def create_collections(self): - collections = ['An amp rack', 'Some video thing', 'Ampy patch boxes', 'The noise dept', 'Cable fun zone'] - - for collection in collections: - models.Collection.objects.create(name=collection) - def create_assets(self): assest_description = ['Large cable', 'Shiny thing', 'New lights', 'Really expensive microphone', 'Box of fuse flaps', 'Expensive tool we didn\'t agree to buy', 'Cable drums', 'Boring amount of tape', 'Video stuff no one knows how to use', 'More amplifiers', 'Heatshrink'] @@ -55,6 +51,7 @@ class Command(BaseCommand): for i in range(100): asset = models.Asset.objects.create( + asset_id='{}'.format(i), description=random.choice(assest_description), category=random.choice(categories), status=random.choice(statuses), From 393d3a1f4963e36a31f4e66e65437ff028742ddf Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Tue, 8 Jan 2019 00:23:29 +0000 Subject: [PATCH 25/34] Made asset list title less massive --- templates/asset_list.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/asset_list.html b/templates/asset_list.html index 055846b8..f135e52a 100644 --- a/templates/asset_list.html +++ b/templates/asset_list.html @@ -3,7 +3,7 @@ {% block main %} -

        Asset List

        +

        Asset List

        {% csrf_token %} From 5e15622cdfb92ccc2222cad3769c86338cc89584 Mon Sep 17 00:00:00 2001 From: Harry Bridge Date: Fri, 11 Jan 2019 22:52:35 +0000 Subject: [PATCH 26/34] Migration for some changes that happened in the past --- migrations/0007_auto_20190108_0202.py | 29 +++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 migrations/0007_auto_20190108_0202.py diff --git a/migrations/0007_auto_20190108_0202.py b/migrations/0007_auto_20190108_0202.py new file mode 100644 index 00000000..6164bd05 --- /dev/null +++ b/migrations/0007_auto_20190108_0202.py @@ -0,0 +1,29 @@ +# Generated by Django 2.1.5 on 2019-01-08 02:02 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0006_auto_20180728_1451_squashed_0021_auto_20190105_1156'), + ] + + operations = [ + migrations.AlterField( + model_name='asset', + name='parent', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='asset_parent', to='assets.Asset'), + ), + migrations.AlterField( + model_name='cable', + name='parent', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='asset_parent', to='assets.Cable'), + ), + migrations.AlterField( + model_name='connector', + name='voltage_rating', + field=models.IntegerField(help_text='Volts'), + ), + ] From 932739b505e0ec8939fbd7b48175ad4994ca08a3 Mon Sep 17 00:00:00 2001 From: Johnathan Graydon Date: Sat, 12 Jan 2019 15:22:03 +0000 Subject: [PATCH 27/34] Add default for acquired and status to base model Will close #3 --- models.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/models.py b/models.py index efa32341..a820baf7 100644 --- a/models.py +++ b/models.py @@ -1,6 +1,8 @@ from django.db import models from django.urls import reverse +import datetime + class AssetCategory(models.Model): class Meta: @@ -40,10 +42,10 @@ class BaseAsset(models.Model): asset_id = models.CharField(max_length=10) description = models.CharField(max_length=120) category = models.ForeignKey(to=AssetCategory, on_delete=models.CASCADE) - status = models.ForeignKey(to=AssetStatus, on_delete=models.CASCADE) + status = models.ForeignKey(to=AssetStatus, on_delete=models.CASCADE, default='Active') serial_number = models.CharField(max_length=150, blank=True) purchased_from = models.ForeignKey(to=Supplier, on_delete=models.CASCADE, blank=True, null=True) - date_acquired = models.DateField() + date_acquired = models.DateField(default=datetime.date.today().strftime('%d %b %Y')) date_sold = models.DateField(blank=True, null=True) purchase_price = models.DecimalField(blank=True, null=True, decimal_places=2, max_digits=10) salvage_value = models.DecimalField(blank=True, null=True, decimal_places=2, max_digits=10) From c03902d76b25a066e0f857bd778c7baee09a8bf2 Mon Sep 17 00:00:00 2001 From: Johnathan Graydon Date: Sat, 12 Jan 2019 15:46:48 +0000 Subject: [PATCH 28/34] Add minor customisation to backend --- admin.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/admin.py b/admin.py index 9e01045c..bbd7b2d0 100644 --- a/admin.py +++ b/admin.py @@ -35,3 +35,8 @@ class ConnectorAdmin(admin.ModelAdmin): @admin.register(assets.Cable) class CableAdmin(admin.ModelAdmin): pass + + +admin.AdminSite.site_header = 'PyAssets - TEC\'s Asset System' +admin.AdminSite.site_title = 'PyAssets Admin' +admin.AdminSite.index_title = 'System Administration' From 2717133d1cd108649f09c53728c0fc3e14340924 Mon Sep 17 00:00:00 2001 From: Johnathan Graydon Date: Sat, 12 Jan 2019 16:25:59 +0000 Subject: [PATCH 29/34] Style adjustments --- static/sass/components/_dropdown.scss | 2 +- templates/asset_list.html | 36 ++++++++++++++------------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/static/sass/components/_dropdown.scss b/static/sass/components/_dropdown.scss index 0caae65d..0632a8b0 100644 --- a/static/sass/components/_dropdown.scss +++ b/static/sass/components/_dropdown.scss @@ -13,7 +13,7 @@ opacity: 0; position: absolute; left: 0; - top: 0; + top: -64px; z-index: 9999; // TODO: Check if this doesn't break other things transform-origin: 0 0; diff --git a/templates/asset_list.html b/templates/asset_list.html index f135e52a..655cdac8 100644 --- a/templates/asset_list.html +++ b/templates/asset_list.html @@ -7,28 +7,30 @@ {% csrf_token %} -
        +
        - +
        + +
        - - - - - - - - - - - - {% include 'asset_list_table_body.html' %} - -
        Asset IDDescriptionCategoryStatusQuick Links
        + + + + + + + + + + + + {% include 'asset_list_table_body.html' %} + +
        Asset IDDescriptionCategoryStatusQuick Links
        {% include 'helpers/paginator.html' %} @@ -36,7 +38,7 @@ {% block script %} #} + {# #}