diff --git a/.gitignore b/.gitignore index d68f717..1a77a3e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .idea __pycache__/ -app.db \ No newline at end of file +app.db +static/.webassets-cache* +static/*.css diff --git a/Pipfile b/Pipfile index d1f2669..1397719 100644 --- a/Pipfile +++ b/Pipfile @@ -12,12 +12,14 @@ gevent = "*" flask-wtf = "*" sqlalchemy = "*" flask-sqlalchemy = "*" +flask-assets = "*" flask-table = "*" flask-migrate = "*" flask-marshmallow = "*" ma = "*" marshmallow-sqlalchemy = "*" wtforms-alchemy = "*" +pyscss = "*" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index 815f353..60849bd 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "96c16527bfb9e0a4363b92b0fff94997f533b831fedb0c5b06c84fcad31231d8" + "sha256": "d08ffb3e7cd1b35e9acf7581587157d4f9577b937b3f4a0289ac7ab7a0b2134e" }, "pipfile-spec": 6, "requires": { @@ -90,6 +90,14 @@ "index": "pypi", "version": "==1.1.1" }, + "flask-assets": { + "hashes": [ + "sha256:1dfdea35e40744d46aada72831f7613d67bf38e8b20ccaaa9e91fdc37aa3b8c2", + "sha256:2845bd3b479be9db8556801e7ebc2746ce2d9edb4e7b64a1c786ecbfc1e5867b" + ], + "index": "pypi", + "version": "==2.0" + }, "flask-babel": { "hashes": [ "sha256:247f4ec34cf605d03781f480bccb1a5acb719df1d1a2a743c091ab3db5d5fde2", @@ -195,10 +203,10 @@ }, "idna": { "hashes": [ - "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", - "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", + "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" ], - "version": "==2.8" + "version": "==2.9" }, "importlib-metadata": { "hashes": [ @@ -306,10 +314,10 @@ }, "marshmallow": { "hashes": [ - "sha256:7669b944d6233b81f68739d5826f1176c3841cc31cf6b856841083b5a72f5ca9", - "sha256:c9d277f6092f32300395fb83d343be9f61b5e99d66d22bae1e5e7cd82608fee6" + "sha256:3a94945a7461f2ab4df9576e51c97d66bee2c86155d3d3933fab752b31effab8", + "sha256:4b95c7735f93eb781dfdc4dded028108998cad759dda8dd9d4b5b4ac574cbf13" ], - "version": "==3.4.0" + "version": "==3.5.0" }, "marshmallow-sqlalchemy": { "hashes": [ @@ -333,6 +341,13 @@ ], "version": "==0.15.7" }, + "pyscss": { + "hashes": [ + "sha256:14a25c33c221a66bb1f000a6a067f376528d3df2f9333cee9c95709a9280cdb0" + ], + "index": "pypi", + "version": "==1.3.5" + }, "python-dateutil": { "hashes": [ "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", @@ -373,10 +388,10 @@ }, "requests": { "hashes": [ - "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", - "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" + "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", + "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" ], - "version": "==2.22.0" + "version": "==2.23.0" }, "six": { "hashes": [ @@ -419,6 +434,13 @@ ], "version": "==0.14.2" }, + "webassets": { + "hashes": [ + "sha256:167132337677c8cedc9705090f6d48da3fb262c8e0b2773b29f3352f050181cd", + "sha256:a31a55147752ba1b3dc07dee0ad8c8efff274464e08bbdb88c1fd59ffd552724" + ], + "version": "==2.0" + }, "werkzeug": { "hashes": [ "sha256:169ba8a33788476292d04186ab33b01d6add475033dfc07215e6d219cc077096", diff --git a/app.py b/app.py index f631c8d..8d26e84 100644 --- a/app.py +++ b/app.py @@ -4,6 +4,7 @@ from datetime import date import connexion import yaml from flask import url_for, json, redirect, render_template, request, flash +from flask_assets import Environment, Bundle from flask_sqlalchemy import SQLAlchemy from flask_marshmallow import Marshmallow from flask_migrate import Migrate @@ -47,6 +48,10 @@ app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db' db = SQLAlchemy(app) migrate = Migrate(app, db) ma = Marshmallow(app) +assets = Environment(app) +assets.url = app.static_url_path +scss = Bundle('scss/app.scss', filters='pyscss', output='app.css') +assets.register('app_scss', scss) # Loads all the descriptions from the API so we can display them in the editor. description_map = {} @@ -213,4 +218,4 @@ def study_details(study_id): if __name__ == '__main__': # run our standalone gevent server - app.run(port=4200) \ No newline at end of file + app.run(port=4200) diff --git a/forms.py b/forms.py index b8fac6c..c1e61e3 100644 --- a/forms.py +++ b/forms.py @@ -32,16 +32,36 @@ class RequirementsTable(Table): class InvestigatorsTable(Table): NETBADGEID = Col('UVA Id') INVESTIGATORTYPE = Col('Type') - delete = LinkCol('Delete', 'del_investigator', url_kwargs=dict(inv_id='id')) + delete = LinkCol( + 'delete', 'del_investigator', url_kwargs=dict(inv_id='id'), + anchor_attrs={'class': 'btn btn-icon btn-warn', 'title': 'Delete Investigator'}, + th_html_attrs={'class': 'mat-icon text-center', 'title': 'Delete Investigator'} + ) class StudyTable(Table): def sort_url(self, col_id, reverse=False): pass - edit = LinkCol('Edit', 'edit_study', url_kwargs=dict(study_id='STUDYID')) - delete = LinkCol('Delete', 'del_study', url_kwargs=dict(study_id='STUDYID')) - details = LinkCol('Details', 'study_details', url_kwargs=dict(study_id='STUDYID')) - add_inv = LinkCol('Add Person', 'new_investigator', url_kwargs=dict(study_id='STUDYID')) + edit = LinkCol( + 'edit', 'edit_study', url_kwargs=dict(study_id='STUDYID'), + anchor_attrs={'class': 'btn btn-icon btn-primary', 'title': 'Edit Study'}, + th_html_attrs={'class': 'mat-icon text-center', 'title': 'Edit Study'} + ) + delete = LinkCol( + 'delete', 'del_study', url_kwargs=dict(study_id='STUDYID'), + anchor_attrs={'class': 'btn btn-icon btn-warn', 'title': 'Delete Study'}, + th_html_attrs={'class': 'mat-icon text-center', 'title': 'Delete Study'} + ) + details = LinkCol( + 'ballot', 'study_details', url_kwargs=dict(study_id='STUDYID'), + anchor_attrs={'class': 'btn btn-icon btn-default', 'title': 'Edit Questions'}, + th_html_attrs={'class': 'mat-icon text-center', 'title': 'Edit Questions'} + ) + add_inv = LinkCol( + 'person_add', 'new_investigator', url_kwargs=dict(study_id='STUDYID'), + anchor_attrs={'class': 'btn btn-icon btn-accent', 'title': 'Add Investigator'}, + th_html_attrs={'class': 'mat-icon text-center', 'title': 'Add Investigator'} + ) STUDYID = Col('Study Id') TITLE = Col('Title') NETBADGEID = Col('User') diff --git a/static/scss/app.scss b/static/scss/app.scss new file mode 100644 index 0000000..5a04bb1 --- /dev/null +++ b/static/scss/app.scss @@ -0,0 +1,244 @@ +// COLOR PALETTE + +// gray +$color-gray: #4e4e4e; +$color-gray-light-2: scale-color($color-gray, $lightness: +90%); +$color-gray-light-1: scale-color($color-gray, $lightness: +70%); +$color-gray-light: $color-gray-light-1; +$color-gray-dark: scale-color($color-gray, $lightness: -30%); + +// primary (UVA "Jefferson Blue") +$color-primary: #232D4B; +$color-primary-light: scale-color($color-primary, $lightness: +30%); +$color-primary-dark: scale-color($color-primary, $lightness: -30%); + +// accent (UVA "Rotunda Orange") +$color-accent: #E57200; +$color-accent-light: scale-color($color-accent, $lightness: +30%); +$color-accent-dark: scale-color($color-accent, $lightness: -30%); + +// warn (UVA "Emergency Red") +$color-warn: #DF1E43; +$color-warn-light: scale-color($color-warn, $lightness: +30%); +$color-warn-dark: scale-color($color-warn, $lightness: -30%); + +$font-size-default: 16px; +$font-size-lg: 24px; +$font-size-md: 16px; +$font-size-sm: 14px; + +@mixin mat-icon { + font-family: 'Material Icons', sans-serif; + font-size: $font-size-lg; +} + +.mat-icon { + @include mat-icon; +} + +.text-center { + text-align: center; +} + +html, body { + padding: 1em; + margin: 0; + font-family: Arial, sans-serif; + font-size: $font-size-default; +} + +table { + border: 1px solid $color-gray-light; + background-color: white; + width: 100%; + text-align: left; + border-collapse: collapse; + + th, td { + padding: 0.5em; + } + + td, &.blueTable th { + border: 1px solid $color-gray-light; + } + + tbody td { + font-size: $font-size-sm; + } + + tr:nth-child(even) { + background: $color-gray-light-2; + } + + thead { + background-color: $color-primary-light; + + th { + font-size: $font-size-default; + font-weight: bold; + color: white; + border-left: 1px solid $color-gray-light; + } + } + + thead th:first-child { + border-left: none; + } + + tfoot { + font-size: $font-size-default; + font-weight: bold; + color: white; + background-color: $color-gray-light; + + td { + font-size: $font-size-default; + } + + .links { + text-align: right; + + a { + display: inline-block; + background: $color-primary-light; + color: white; + padding: 2px 8px; + border-radius: 5px; + } + } + } +} + +.btn { + font-size: $font-size-default; + padding: 0.5em 1em; + border-radius: 5px; + text-decoration: none; + color: white; + white-space: nowrap; + border: none; + + &:hover { + text-decoration: none; + } + + &.btn-icon { + @include mat-icon; + border: none; + + &.btn-default { + color: $color-gray; + background-color: transparent; + border: none; + + &:hover { + color: $color-gray-dark; + background-color: transparent; + } + } + + &.btn-primary { + color: $color-primary; + background-color: transparent; + + &:hover { + color: $color-primary-dark; + background-color: transparent; + } + } + + &.btn-accent { + color: $color-accent; + background-color: transparent; + + &:hover { + color: $color-accent-dark; + background-color: transparent; + } + } + + &.btn-warn { + color: $color-warn; + background-color: transparent; + + &:hover { + color: $color-warn-dark; + background-color: transparent; + } + } + } + + &.btn-default { + color: $color-gray-dark; + background-color: white; + border: 1px solid $color-gray-light; + + &:hover { + background-color: $color-gray-light-2; + } + } + + &.btn-primary { + background-color: $color-primary; + + &:hover { + background-color: $color-primary-dark; + } + } + + &.btn-warn { + background-color: $color-warn; + + &:hover { + background-color: $color-warn-dark; + } + } + + &.btn-accent { + background-color: $color-accent; + + &:hover { + background-color: $color-accent-dark; + } + } +} + +select.multi { + height: 600px; +} + +.form-field { + display: flex; + width: 100%; + margin-bottom: 40px; + padding: 2em; + + &:nth-child(even) { + background-color: $color-gray-light-2; + } + + .form-field-label, + .form-field-help, + .form-field-input { + width: 30%; + text-align: left; + margin-right: 40px; + } + + .form-field-label { + font-weight: bold; + } + + .form-field-input input { + width: 100%; + } + + .form-field-help { + font-style: italic; + } + + .form-field-error { + color: $color-warn; + } +} + diff --git a/templates/form.html b/templates/form.html index 17b51ee..d1dff9a 100644 --- a/templates/form.html +++ b/templates/form.html @@ -3,38 +3,31 @@ Protocol Builder Mock Configuration + + + {% assets 'app_scss' %} + + {% endassets %} - -

{{title}}

-

{{details}}

-
+

{{ title }}

+

{{ details }}

+ {{ form.csrf_token() }} {% for field in form if field.name != "csrf_token" %} -

- {{ field.label() }}: - {{ field }} - {{ description_map[field.name] }} +

+
{{ field.label() }}:
+
{{ field }}
+
{{ description_map[field.name] }}
{% for error in field.errors %} - {{ error }} +
{{ error }}
{% endfor %} -

+
{% endfor %} - + + Cancel
diff --git a/templates/index.html b/templates/index.html index 254dc0d..9ad0eb3 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,93 +1,33 @@ - - + + + Protocol Builder Mock - - - -

Protocol Builder Mock

- -

-

- New Study - - {% with messages = get_flashed_messages() %} - {% if messages %} -

+{% with messages = get_flashed_messages() %} + {% if messages %} + {% endif %} - {% endwith %} +{% endwith %} -

Current Studies

- {{ table }} +

Current Studies

+{{ table }} + -
\ No newline at end of file + +