Compare commits
47 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
3a3b97cc6c | ||
|
7b2ac0ff1e | ||
|
965fcc39fc | ||
|
772eb17aec | ||
|
7159fed969 | ||
|
e4e66b8371 | ||
|
fc58c53acb | ||
2663507b71 | |||
|
658432d05c | ||
|
9ba1b957d6 | ||
|
cd45dedf62 | ||
|
b6b7df7a82 | ||
|
e2317c86ef | ||
|
7dc8e42fe3 | ||
|
bd8c28365d | ||
|
7c282947e6 | ||
|
c72a8b114d | ||
|
35c27b859e | ||
|
547bb44aa6 | ||
|
29f2f340bc | ||
|
e19415a97e | ||
|
83900a12dd | ||
|
2709aecd89 | ||
|
b1553d1766 | ||
|
e2e4466117 | ||
|
14fb7788d7 | ||
|
2aee4aed9a | ||
|
2350b12c4c | ||
|
ab2e37a2e0 | ||
|
8dcaea83a8 | ||
|
afdb04d90c | ||
|
739a7be1ad | ||
|
a5d07659eb | ||
|
a56ec131de | ||
|
a2e54e40a3 | ||
|
db725447b9 | ||
|
366a658e70 | ||
|
4b3932586a | ||
|
042607fc0c | ||
|
f73005ac1a | ||
|
a151516fd6 | ||
|
1e353136a0 | ||
|
fd82ede36d | ||
|
eb3adc740d | ||
|
7d8b2ebf03 | ||
|
5cab7f9740 | ||
|
a5e9e16b49 |
3
.babelrc
Normal file
3
.babelrc
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"presets": ["@babel/env", "@babel/preset-react"]
|
||||||
|
}
|
@ -12,6 +12,7 @@
|
|||||||
# below:
|
# below:
|
||||||
.git
|
.git
|
||||||
.gitignore
|
.gitignore
|
||||||
|
.idea
|
||||||
|
|
||||||
# Python pycache:
|
# Python pycache:
|
||||||
__pycache__/
|
__pycache__/
|
||||||
@ -19,3 +20,5 @@ __pycache__/
|
|||||||
/setup.cfg
|
/setup.cfg
|
||||||
|
|
||||||
env
|
env
|
||||||
|
venv
|
||||||
|
node_modules/
|
||||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,8 +1,14 @@
|
|||||||
|
service.json
|
||||||
|
node_modules/
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
*/__pycache__/*
|
*/__pycache__/*
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
*$py.class
|
*$py.class
|
||||||
|
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
|
||||||
# C extensions
|
# C extensions
|
||||||
*.so
|
*.so
|
||||||
|
|
||||||
|
8
README.md
Normal file
8
README.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
sarsoo.xyz
|
||||||
|
===============
|
||||||
|
|
||||||
|
### back end
|
||||||
|
python flask web server
|
||||||
|
|
||||||
|
### front end
|
||||||
|
couple of react elements, currently the dev page
|
72
admin.py
Normal file
72
admin.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import shutil
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
from cmd import Cmd
|
||||||
|
|
||||||
|
stage_dir = '_sarsoo.xyz'
|
||||||
|
scss_rel_path = Path('src', 'scss', 'style.scss')
|
||||||
|
css_rel_path = Path('build', 'style.css')
|
||||||
|
|
||||||
|
folders_to_ignore = ['venv', 'docs', '.git', '.idea', 'node_modules']
|
||||||
|
|
||||||
|
|
||||||
|
class Admin(Cmd):
|
||||||
|
intro = 'Sarsoo.xyz Admin... ? for help'
|
||||||
|
prompt = '> '
|
||||||
|
|
||||||
|
def prepare_stage(self):
|
||||||
|
print('>> backing up a directory')
|
||||||
|
os.chdir(Path(__file__).absolute().parent.parent)
|
||||||
|
|
||||||
|
print('>> deleting old deployment stage')
|
||||||
|
shutil.rmtree(stage_dir, ignore_errors=True)
|
||||||
|
|
||||||
|
print('>> copying main source')
|
||||||
|
shutil.copytree('sarsoo.xyz',
|
||||||
|
stage_dir,
|
||||||
|
ignore=lambda path, contents:
|
||||||
|
contents if any(i in Path(path).parts for i in folders_to_ignore) else []
|
||||||
|
)
|
||||||
|
|
||||||
|
for dependency in ['fmframework']:
|
||||||
|
print(f'>> injecting {dependency}')
|
||||||
|
shutil.copytree(
|
||||||
|
Path(dependency, dependency),
|
||||||
|
Path(stage_dir, dependency)
|
||||||
|
)
|
||||||
|
|
||||||
|
os.chdir(stage_dir)
|
||||||
|
os.system('gcloud config set project sarsooxyz')
|
||||||
|
|
||||||
|
def prepare_frontend(self):
|
||||||
|
print('>> building css')
|
||||||
|
os.system(f'sass --style=compressed {scss_rel_path} {css_rel_path}')
|
||||||
|
|
||||||
|
print('>> building javascript')
|
||||||
|
os.system('npm run build')
|
||||||
|
|
||||||
|
def do_api(self, args):
|
||||||
|
self.prepare_frontend()
|
||||||
|
self.prepare_stage()
|
||||||
|
|
||||||
|
print('>> deploying')
|
||||||
|
os.system('gcloud app deploy')
|
||||||
|
|
||||||
|
def do_exit(self, args):
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
def do_sass(self, args):
|
||||||
|
os.system(f'sass --style=compressed {scss_rel_path} {css_rel_path}')
|
||||||
|
|
||||||
|
def do_watchsass(self, args):
|
||||||
|
os.system(f'sass --style=compressed --watch {scss_rel_path} {css_rel_path}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
console = Admin()
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
console.onecmd(' '.join(sys.argv[1:]))
|
||||||
|
else:
|
||||||
|
console.cmdloop()
|
5
app.yaml
5
app.yaml
@ -2,9 +2,12 @@ runtime: python37
|
|||||||
|
|
||||||
handlers:
|
handlers:
|
||||||
- url: /static
|
- url: /static
|
||||||
static_dir: static
|
static_dir: build
|
||||||
|
|
||||||
- url: /.*
|
- url: /.*
|
||||||
script: auto
|
script: auto
|
||||||
secure: always
|
secure: always
|
||||||
|
|
||||||
|
env_variables:
|
||||||
|
DEPLOY_DESTINATION: 'PROD'
|
||||||
|
|
||||||
|
14
cron.yaml
Normal file
14
cron.yaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
cron:
|
||||||
|
- description: "playlist executions job"
|
||||||
|
url: /api/playlist/run/users/cron
|
||||||
|
schedule: every 8 hours
|
||||||
|
target: spotify
|
||||||
|
- description: "playlist stats refresh"
|
||||||
|
url: /api/spotfm/playlist/refresh/users/cron
|
||||||
|
schedule: every 72 hours
|
||||||
|
target: spotify
|
||||||
|
- description: "tag refresh"
|
||||||
|
url: /api/tag/update/users/cron
|
||||||
|
schedule: every 24 hours
|
||||||
|
target: spotify
|
BIN
footer.xcf
Normal file
BIN
footer.xcf
Normal file
Binary file not shown.
2
main.py
2
main.py
@ -3,4 +3,4 @@ from sarsoo import app
|
|||||||
app = app
|
app = app
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run()
|
app.run(host='127.0.0.1', port=8080, debug=True)
|
||||||
|
5687
package-lock.json
generated
Normal file
5687
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
39
package.json
Normal file
39
package.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "sarsoo.xyz",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "sarsoo web",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"build": "webpack --config webpack.prod.js",
|
||||||
|
"devbuild": "webpack --config webpack.dev.js"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/Sarsoo/sarsoo.xyz.git"
|
||||||
|
},
|
||||||
|
"author": "sarsoo",
|
||||||
|
"license": "ISC",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/Sarsoo/sarsoo.xyz/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/Sarsoo/sarsoo.xyz#readme",
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/cli": "^7.10.5",
|
||||||
|
"@babel/core": "^7.11.0",
|
||||||
|
"@babel/preset-env": "^7.11.0",
|
||||||
|
"@babel/preset-react": "^7.10.4",
|
||||||
|
"babel-loader": "^8.1.0",
|
||||||
|
"clean-webpack-plugin": "^3.0.0",
|
||||||
|
"css-loader": "^2.1.1",
|
||||||
|
"style-loader": "^0.23.1",
|
||||||
|
"webpack": "^4.44.1",
|
||||||
|
"webpack-cli": "^3.3.12",
|
||||||
|
"webpack-merge": "^4.2.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^0.21.1",
|
||||||
|
"react": "^16.13.0",
|
||||||
|
"react-dom": "^16.13.0"
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,25 @@
|
|||||||
Flask==1.0.2
|
cachetools==4.1.1
|
||||||
requests==2.20
|
certifi==2020.6.20
|
||||||
google.cloud.firestore==0.31.0
|
chardet==3.0.4
|
||||||
|
click==7.1.2
|
||||||
|
Flask==1.1.2
|
||||||
|
google-api-core==1.22.0
|
||||||
|
google-auth==1.20.0
|
||||||
|
google-cloud-core==1.3.0
|
||||||
|
google-cloud-firestore==1.8.1
|
||||||
|
google-cloud-logging==1.15.0
|
||||||
|
googleapis-common-protos==1.52.0
|
||||||
|
grpcio==1.30.0
|
||||||
|
idna==2.10
|
||||||
|
itsdangerous==1.1.0
|
||||||
|
Jinja2==2.11.3
|
||||||
|
MarkupSafe==1.1.1
|
||||||
|
protobuf==3.12.4
|
||||||
|
pyasn1==0.4.8
|
||||||
|
pyasn1-modules==0.2.8
|
||||||
|
pytz==2020.1
|
||||||
|
requests==2.24.0
|
||||||
|
rsa==4.7
|
||||||
|
six==1.15.0
|
||||||
|
urllib3==1.25.10
|
||||||
|
Werkzeug==1.0.1
|
||||||
|
@ -1 +1,34 @@
|
|||||||
from .sarsoo import app
|
from .sarsoo import app
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logger.setLevel('DEBUG')
|
||||||
|
|
||||||
|
fmframework_logger = logging.getLogger('fmframework')
|
||||||
|
|
||||||
|
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
|
||||||
|
import google.cloud.logging
|
||||||
|
from google.cloud.logging.handlers import CloudLoggingHandler, setup_logging
|
||||||
|
|
||||||
|
log_format = '%(funcName)s - %(message)s'
|
||||||
|
formatter = logging.Formatter(log_format)
|
||||||
|
|
||||||
|
client = google.cloud.logging.Client()
|
||||||
|
handler = CloudLoggingHandler(client, name="sarsooxyz")
|
||||||
|
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
logger.addHandler(handler)
|
||||||
|
fmframework_logger.addHandler(handler)
|
||||||
|
|
||||||
|
else:
|
||||||
|
log_format = '%(levelname)s %(name)s:%(funcName)s - %(message)s'
|
||||||
|
formatter = logging.Formatter(log_format)
|
||||||
|
|
||||||
|
stream_handler = logging.StreamHandler()
|
||||||
|
stream_handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
logger.addHandler(stream_handler)
|
||||||
|
fmframework_logger.addHandler(stream_handler)
|
||||||
|
3
sarsoo/api/__init__.py
Normal file
3
sarsoo/api/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from .art_api import art_api_print
|
||||||
|
from .dev_api import dev_api_print
|
||||||
|
from .eng_api import eng_api_print
|
57
sarsoo/api/art_api.py
Normal file
57
sarsoo/api/art_api.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
from flask import Blueprint, jsonify, abort
|
||||||
|
from sarsoo.art import db
|
||||||
|
from sarsoo.art.art import get_asset_url
|
||||||
|
|
||||||
|
art_api_print = Blueprint('artapi', __name__)
|
||||||
|
|
||||||
|
|
||||||
|
@art_api_print.route('/', methods=['GET'])
|
||||||
|
def get_all_collections():
|
||||||
|
|
||||||
|
tagdicts = db.pull_all_tags()
|
||||||
|
|
||||||
|
art_collections = []
|
||||||
|
|
||||||
|
for tag_dict in tagdicts:
|
||||||
|
art_collections.append({
|
||||||
|
'name': tag_dict['name'],
|
||||||
|
'id': tag_dict['doc_name'],
|
||||||
|
'description': tag_dict['description'],
|
||||||
|
'index': tag_dict['index'],
|
||||||
|
'splash_url': get_asset_url(tag_dict['splash']['file_name'])
|
||||||
|
})
|
||||||
|
|
||||||
|
response = {'collections': art_collections}
|
||||||
|
|
||||||
|
return jsonify(response)
|
||||||
|
|
||||||
|
|
||||||
|
@art_api_print.route('/<id>', methods=['GET'])
|
||||||
|
def get_named_collection(art_id):
|
||||||
|
|
||||||
|
try:
|
||||||
|
tagdict = db.pull_named_tag(art_id)
|
||||||
|
except TypeError as e:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
artlist = []
|
||||||
|
|
||||||
|
for image in tagdict['images']:
|
||||||
|
artlist.append({
|
||||||
|
'file_url': get_asset_url(image['file_name']),
|
||||||
|
'date': image['date'].strftime('%d %B %y')
|
||||||
|
})
|
||||||
|
|
||||||
|
response = {'name': tagdict['name'],
|
||||||
|
'description': tagdict['description'],
|
||||||
|
'art': artlist}
|
||||||
|
|
||||||
|
return jsonify(response)
|
||||||
|
|
||||||
|
|
||||||
|
@art_api_print.errorhandler(404)
|
||||||
|
def error400(error):
|
||||||
|
|
||||||
|
errorresponse = {'error': 'collection not found'}
|
||||||
|
|
||||||
|
return jsonify(errorresponse), 404
|
26
sarsoo/api/dev_api.py
Normal file
26
sarsoo/api/dev_api.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from flask import Blueprint, jsonify, abort
|
||||||
|
from google.cloud import firestore, exceptions
|
||||||
|
|
||||||
|
fs = firestore.Client()
|
||||||
|
|
||||||
|
dev_api_print = Blueprint('devapi', __name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dev_api_print.route('/', methods=['GET'])
|
||||||
|
def get_all_collections():
|
||||||
|
|
||||||
|
dev_collection = fs.collection(u'dev')
|
||||||
|
|
||||||
|
try:
|
||||||
|
tags = dev_collection.get()
|
||||||
|
response = {'dev': sorted([i.to_dict() for i in tags], key=lambda k: k['index'])}
|
||||||
|
return jsonify(response)
|
||||||
|
|
||||||
|
except exceptions.NotFound:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
|
||||||
|
@dev_api_print.errorhandler(404)
|
||||||
|
def error400(error):
|
||||||
|
errorresponse = {'error': 'collection not found'}
|
||||||
|
return jsonify(errorresponse), 404
|
26
sarsoo/api/eng_api.py
Normal file
26
sarsoo/api/eng_api.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from flask import Blueprint, jsonify, abort
|
||||||
|
from google.cloud import firestore, exceptions
|
||||||
|
|
||||||
|
fs = firestore.Client()
|
||||||
|
|
||||||
|
eng_api_print = Blueprint('engapi', __name__)
|
||||||
|
|
||||||
|
|
||||||
|
@eng_api_print.route('/', methods=['GET'])
|
||||||
|
def get_all_collections():
|
||||||
|
|
||||||
|
eng_collection = fs.collection(u'eng')
|
||||||
|
|
||||||
|
try:
|
||||||
|
tags = eng_collection.get()
|
||||||
|
response = {'eng': sorted([i.to_dict() for i in tags], key=lambda k: k['index'])}
|
||||||
|
return jsonify(response)
|
||||||
|
|
||||||
|
except exceptions.NotFound:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
|
||||||
|
@eng_api_print.errorhandler(404)
|
||||||
|
def error400(error):
|
||||||
|
errorresponse = {'error': 'collection not found'}
|
||||||
|
return jsonify(errorresponse), 404
|
@ -1,30 +1,30 @@
|
|||||||
from flask import Blueprint, render_template, abort
|
from flask import Blueprint, render_template
|
||||||
from jinja2 import TemplateNotFound
|
|
||||||
|
|
||||||
from google.cloud import firestore
|
from .db import pull_all_tags, pull_named_tag, get_populated_tags
|
||||||
|
|
||||||
from .db import getTagDicts, getPopulatedTagDict, getPopulatedTagDicts
|
|
||||||
|
|
||||||
staticbucketurl = 'https://storage.googleapis.com/sarsooxyzstatic/'
|
staticbucketurl = 'https://storage.googleapis.com/sarsooxyzstatic/'
|
||||||
urlprefix = 'art'
|
urlprefix = 'art'
|
||||||
|
|
||||||
art_print = Blueprint('art', __name__, template_folder='templates')
|
art_print = Blueprint('art', __name__, template_folder='templates')
|
||||||
#db = firestore.Client()
|
|
||||||
|
|
||||||
@art_print.route('/')
|
@art_print.route('/')
|
||||||
def root():
|
def view_root():
|
||||||
tags = getTagDicts()
|
tags = pull_all_tags()
|
||||||
print(tags)
|
return render_template('art/index.html', tags=tags, staticroot=staticbucketurl, urlprefix=urlprefix)
|
||||||
return render_template('art/index.html', tags = tags, urlprefix = urlprefix)
|
|
||||||
|
|
||||||
@art_print.route('/<tag>')
|
@art_print.route('/<tag>')
|
||||||
def tag_view(tag):
|
def view_named_tag(tag):
|
||||||
tags = getPopulatedTagDict(tag)
|
tags = pull_named_tag(tag)
|
||||||
return render_template('art/all.html', staticroot = staticbucketurl, tags = [tags])
|
return render_template('art/all.html', staticroot=staticbucketurl, tags=[tags])
|
||||||
|
|
||||||
|
|
||||||
@art_print.route('/all')
|
@art_print.route('/all')
|
||||||
def all():
|
def view_all_tags():
|
||||||
|
sections = get_populated_tags()
|
||||||
|
return render_template('art/all.html', staticroot=staticbucketurl, tags=sections)
|
||||||
|
|
||||||
sections = getPopulatedTagDicts()
|
|
||||||
|
|
||||||
return render_template('art/all.html', staticroot = staticbucketurl, tags=sections)
|
def get_asset_url(name):
|
||||||
|
return staticbucketurl + 'art/' + name + '.jpg'
|
||||||
|
@ -1,25 +1,29 @@
|
|||||||
from google.cloud import firestore
|
from google.cloud import firestore
|
||||||
|
from google.cloud import exceptions
|
||||||
|
|
||||||
staticbucketurl = 'https://storage.googleapis.com/sarsooxyzstatic/'
|
staticbucketurl = 'https://storage.googleapis.com/sarsooxyzstatic/'
|
||||||
|
|
||||||
fs = firestore.Client()
|
fs = firestore.Client()
|
||||||
|
|
||||||
def getTagDicts():
|
|
||||||
|
def pull_all_tags():
|
||||||
|
|
||||||
art_tags_collection = fs.collection(u'art_tags')
|
art_tags_collection = fs.collection(u'art_tags')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tags = art_tags_collection.get()
|
tags = art_tags_collection.get()
|
||||||
except google.cloud.exceptions.NotFound:
|
except exceptions.NotFound:
|
||||||
return 'no such document'
|
return 'no such document'
|
||||||
|
|
||||||
dicts = []
|
dicts = list(map(lambda x: x.to_dict(), tags))
|
||||||
for tag in tags:
|
|
||||||
dicts.append(tag.to_dict())
|
|
||||||
|
|
||||||
return dicts
|
for artdict in dicts:
|
||||||
|
artdict['splash'] = artdict['splash'].get().to_dict()
|
||||||
|
|
||||||
def getPopulatedTagDict(name):
|
return sorted(dicts, key=lambda k: k['index'])
|
||||||
|
|
||||||
|
|
||||||
|
def pull_named_tag(name):
|
||||||
|
|
||||||
name_doc = fs.document(u'art_tags/{}'.format(name))
|
name_doc = fs.document(u'art_tags/{}'.format(name))
|
||||||
|
|
||||||
@ -28,13 +32,15 @@ def getPopulatedTagDict(name):
|
|||||||
image_list = []
|
image_list = []
|
||||||
for image in tag_dicts['art']:
|
for image in tag_dicts['art']:
|
||||||
image_list.append(image.get().to_dict())
|
image_list.append(image.get().to_dict())
|
||||||
tag_dicts['images'] = image_list
|
|
||||||
|
tag_dicts['images'] = sorted(image_list, key=lambda k: k['date'], reverse=True)
|
||||||
|
|
||||||
return tag_dicts
|
return tag_dicts
|
||||||
|
|
||||||
def getPopulatedTagDicts():
|
|
||||||
|
def get_populated_tags():
|
||||||
|
|
||||||
tag_dicts = getTagDicts()
|
tag_dicts = pull_all_tags()
|
||||||
|
|
||||||
for tag in tag_dicts:
|
for tag in tag_dicts:
|
||||||
image_list = []
|
image_list = []
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
|
||||||
{% block title %}art{% endblock %}
|
{% block title %}/art{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% for tag in tags %}
|
{% for tag in tags %}
|
||||||
<div class="row">
|
<div class="row text-no-select">
|
||||||
<h1 class="sectiontitle">{{ tag.name }}</h1>
|
<h1 class="sectiontitle">{{ tag.name }}</h1>
|
||||||
{% if tag.description %}<p>{{ tag.description }}</p>{% endif %}
|
{% if tag.description %}<p>{{ tag.description }}</p>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% for row in tag.images|batch(3) %}
|
{% for row in tag.images|batch(3) %}
|
||||||
<div class="row">
|
<div class="row text-no-select">
|
||||||
{% for image in row %}
|
{% for image in row %}
|
||||||
{#{% if loop.index0 % 3 == 0 %}<div class="row">{% endif %}#}
|
{#{% if loop.index0 % 3 == 0 %}<div class="row">{% endif %}#}
|
||||||
<div class="card col-pad-4">
|
<div class="card pad-4">
|
||||||
<img src="{{ staticroot }}art/{{ image.file_name }}.jpg" alt="{{ image.file_name }}" class="col-pad-4">
|
<img src="{{ staticroot }}art/{{ image.file_name }}.jpg" alt="{{ image.file_name }}" class="col-pad-4">
|
||||||
<p><b>{{ image.description }}</b></p>
|
<p><b>{{ image.description }}</b></p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block title %}dev{% endblock %}
|
{% block title %}/art{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% for row in tags|batch(2) %}
|
{% for row in tags|batch(4) %}
|
||||||
<div class="row">
|
<div class="row text-no-select">
|
||||||
{% for tag in row %}
|
{% for tag in row %}
|
||||||
<div class="col-pad-6 card">
|
<div class="pad-3 card">
|
||||||
<h1>{{ tag.name }}</h1>
|
<h1>{{ tag.name }}</h1>
|
||||||
<p>{{ tag.description }}</p>
|
<img src="{{ staticroot }}art/{{ tag.splash.file_name }}.jpg" alt="{{ tag.splash.file_name }}">
|
||||||
<a href="/{{ urlprefix }}/{{ tag.doc_name }}" style="width:100%;" class="button">view</a>
|
{% if tag.description %}<p>{{ tag.description }}{{ tag.image }}</p>{% endif %}
|
||||||
|
<a href="/{{ urlprefix }}/{{ tag.doc_name }}" style="width:100%;" class="button">View</a>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
1
sarsoo/music/__init__.py
Normal file
1
sarsoo/music/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .music import music_print
|
19
sarsoo/music/music.py
Normal file
19
sarsoo/music/music.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
from flask import Blueprint, render_template
|
||||||
|
from fmframework.net.network import Network
|
||||||
|
from google.cloud import firestore
|
||||||
|
|
||||||
|
fs = firestore.Client()
|
||||||
|
|
||||||
|
urlprefix = 'music'
|
||||||
|
fm_url = 'https://ws.audioscrobbler.com/2.0/'
|
||||||
|
|
||||||
|
music_print = Blueprint('music', __name__, template_folder='templates')
|
||||||
|
|
||||||
|
|
||||||
|
@music_print.route('/')
|
||||||
|
def root():
|
||||||
|
fmkey = fs.document('config/music-tools').get().to_dict()['last_fm_client_id']
|
||||||
|
fmnet = Network(username='sarsoo', api_key=fmkey)
|
||||||
|
albums = fmnet.get_top_albums(Network.Range.MONTH, limit=6)
|
||||||
|
|
||||||
|
return render_template('music/index.html', albums=albums)
|
18
sarsoo/music/templates/music/index.html
Normal file
18
sarsoo/music/templates/music/index.html
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}/music{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row text-no-select">
|
||||||
|
<h1 class = "col-12" style = "text-align: center; color: white;">Last Month</h1>
|
||||||
|
</div>
|
||||||
|
<div class="row text-no-select">
|
||||||
|
{% for album in albums %}
|
||||||
|
<div class = "pad-2 card">
|
||||||
|
<img src="{{album.images[-1].link}}">
|
||||||
|
<p style="text-align:center">{{ album.name }}<br>{{ album.artist.name -}}</p>
|
||||||
|
<p style="text-align:center">{{- album.user_scrobbles }} plays</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -3,41 +3,40 @@ from google.cloud import firestore
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from .art import art_print
|
from .art import art_print
|
||||||
|
from .music import music_print
|
||||||
|
from .api import art_api_print, dev_api_print, eng_api_print
|
||||||
|
|
||||||
# Project ID is determined by the GCLOUD_PROJECT environment variable
|
# Project ID is determined by the GCLOUD_PROJECT environment variable
|
||||||
db = firestore.Client()
|
db = firestore.Client()
|
||||||
|
|
||||||
app = Flask(__name__, static_folder=os.path.join(os.path.dirname(__file__), '..', 'static'), template_folder = "templates")
|
app = Flask(__name__, static_folder=os.path.join(os.path.dirname(__file__), '..', 'build'), template_folder="templates")
|
||||||
|
|
||||||
app.register_blueprint(art_print, url_prefix='/art')
|
app.register_blueprint(art_print, url_prefix='/art')
|
||||||
|
app.register_blueprint(music_print, url_prefix='/music')
|
||||||
|
app.register_blueprint(art_api_print, url_prefix='/api/art')
|
||||||
|
app.register_blueprint(dev_api_print, url_prefix='/api/dev')
|
||||||
|
app.register_blueprint(eng_api_print, url_prefix='/api/eng')
|
||||||
|
|
||||||
staticbucketurl = 'https://storage.googleapis.com/sarsooxyzstatic/'
|
staticbucketurl = 'https://storage.googleapis.com/sarsooxyzstatic/'
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def main():
|
def main():
|
||||||
|
|
||||||
index_doc = db.collection(u'pages').document(u'index')
|
index_query = db.collection(u'pages').document(u'index')
|
||||||
doc = index_doc.get()
|
index_dict = index_query.get().to_dict()
|
||||||
index_dict = doc.to_dict()
|
|
||||||
|
|
||||||
splashtext = index_dict['splash_text']
|
main_text = index_dict['main_text']
|
||||||
|
|
||||||
art = []
|
art = []
|
||||||
for image in index_dict['art']:
|
for image in index_dict['art']:
|
||||||
art.append(image.get().to_dict())
|
art.append(image.get().to_dict())
|
||||||
|
|
||||||
print(index_dict['art'][0].get().to_dict())
|
return render_template('index.html', staticroot=staticbucketurl, art=art, main_text=main_text)
|
||||||
|
|
||||||
return render_template('index.html', staticroot = staticbucketurl, splash = splashtext, art=art)
|
|
||||||
|
|
||||||
@app.route('/music')
|
|
||||||
def music():
|
|
||||||
return render_template('music.html')
|
|
||||||
|
|
||||||
@app.route('/dev')
|
@app.route('/dev')
|
||||||
def dev():
|
def dev():
|
||||||
return render_template('dev.html')
|
return render_template('dev.html')
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
app.run(host='127.0.0.1', port=8080, debug=True)
|
|
||||||
# [END gae_python37_app]
|
# [END gae_python37_app]
|
||||||
|
@ -1,29 +1,45 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>sarsoo/{% block title %}{% endblock %}</title>
|
<title>sarsoo{% block title %}{% endblock %}</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||||
<link href="https://fonts.googleapis.com/css?family=Megrim|Roboto" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Pirata+One|Roboto" rel="stylesheet">
|
||||||
|
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="https://storage.googleapis.com/sarsooxyzstatic/apple-touch-icon.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="https://storage.googleapis.com/sarsooxyzstatic/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="https://storage.googleapis.com/sarsooxyzstatic/favicon-16x16.png">
|
||||||
|
<link rel="manifest" href="https://storage.googleapis.com/sarsooxyzstatic/site.webmanifest">
|
||||||
|
<link rel="mask-icon" href="https://storage.googleapis.com/sarsooxyzstatic/safari-pinned-tab.svg" color="#5bbad5">
|
||||||
|
<link rel="shortcut icon" href="https://storage.googleapis.com/sarsooxyzstatic/favicon.ico">
|
||||||
|
|
||||||
|
{% block scripts %}{% endblock %}
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="title">andy</h1>
|
<h1 class="title">sarsoo</h1>
|
||||||
</div>
|
</div>
|
||||||
<br><br>
|
<br><br>
|
||||||
<ul class="navbar">
|
<ul class="navbar">
|
||||||
<li><a href="/">home</a></li>
|
<li><a href="/">Home</a></li>
|
||||||
<li><a href="/dev">dev</a></li>
|
<li><a href="/dev">Dev & Engineering</a></li>
|
||||||
<li><a href="/art">art</a></li>
|
<li><a href="/art">Art</a></li>
|
||||||
<li><a href="/music">music</a></li>
|
<li><a href="/music">Music</a></li>
|
||||||
<li class="right"><a href="https://www.instagram.com/pack_it_in_/">art ig</a></li>
|
<li class="right"><a href="https://www.instagram.com/pack_it_in_/">Art IG</a></li>
|
||||||
<li class="right"><a href="https://www.last.fm/user/sarsoo">last.fm</a></li>
|
<li class="right"><a href="https://www.last.fm/user/sarsoo">Last.fm</a></li>
|
||||||
<li class="right"><a href="https://github.com/sarsoo">github</a></li>
|
<li class="right"><a href="https://github.com/sarsoo">Github</a></li>
|
||||||
|
<li class="right"><a href="https://music.sarsoo.xyz">Music Tools</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
<footer>
|
<img src="https://storage.googleapis.com/sarsooxyzstatic/andy.png"
|
||||||
<a href="https://github.com/Sarsoo/sarsoo.xyz">view source code</a>
|
alt="AP"
|
||||||
</footer>
|
width="120px"
|
||||||
|
style="align:center;
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
padding: 8px">
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,14 +1,8 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block title %}dev{% endblock %}
|
{% block title %}/dev{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div id="react"></div>
|
||||||
<div class="col-pad-6 card">
|
<script src="{{ url_for('static', filename='js/dev.bundle.js') }}"></script>
|
||||||
<h1>fm framework</h1>
|
|
||||||
<p>a client for last.fm music tracking written in java</p>
|
|
||||||
<p>this project was important for learning how to interact with REST APIs and implementing knowledge from my java classes at uni</p>
|
|
||||||
<a href="https://github.com/Sarsoo/fmframework" style="width:100%;" class="button">view source</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,25 +1,24 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block title %}home{% endblock %}
|
{% block title %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="row">
|
<div class="row text-no-select">
|
||||||
<div class="col-9" style="text-align:center">
|
<div class="col-9" style="text-align:center">
|
||||||
<p>electronic engineering student / disney intern</p>
|
{% for text_entry in main_text %}
|
||||||
|
<p>{{ text_entry }}</p>
|
||||||
<p>proficient in C, Java, Python</p>
|
{% endfor %}
|
||||||
{% if splash %}<p>{{ splash }}</p>{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3 profile">
|
<div class="col-3 profile">
|
||||||
<img src="{{ staticroot }}me.jpg" height=150>
|
<img src="{{ staticroot }}me.jpg" height=150>
|
||||||
<p style="text-align: center">sarsoo</p>
|
<p style="text-align: center">Sarsoo</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="gallerystrip row">
|
<div class="gallerystrip row text-no-select">
|
||||||
{% for image in art %}
|
{% for image in art %}
|
||||||
<img src="{{ staticroot }}art/{{ image.file_name }}.jpg" alt="{{ image.file_name }}" class="col-pad-4">
|
<img src="{{ staticroot }}art/{{ image.file_name }}.jpg" alt="{{ image.file_name }}" class="pad-4">
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block title %}music{% endblock %}
|
{% block title %}/music{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
73
src/js/dev/DevGallery.js
Normal file
73
src/js/dev/DevGallery.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import React, { Component } from "react";
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
class DevGallery extends Component {
|
||||||
|
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
entries: [],
|
||||||
|
isLoading: true
|
||||||
|
};
|
||||||
|
this.getEntries();
|
||||||
|
}
|
||||||
|
|
||||||
|
getEntries(){
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
axios.get('/api/dev')
|
||||||
|
.then(function (response){
|
||||||
|
self.setState({
|
||||||
|
entries: response.data.dev,
|
||||||
|
isLoading: false
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(function (error){
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
|
||||||
|
var arrays = [], size = 3;
|
||||||
|
|
||||||
|
var a = this.state.entries.slice()
|
||||||
|
while (a.length > 0)
|
||||||
|
arrays.push(a.splice(0, size));
|
||||||
|
|
||||||
|
const gallery = <div>{arrays.map((entry) => <Row entries={entry}/>)}</div>;
|
||||||
|
|
||||||
|
const loadingMessage = <p className="center-text text-no-select" >Loading...</p>;
|
||||||
|
|
||||||
|
return this.state.isLoading ? loadingMessage : gallery;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function Row(props){
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="row text-no-select">
|
||||||
|
{props.entries.map((entry) => <DevEntry entry={entry} key={entry.index} />)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function DevEntry(props){
|
||||||
|
return (
|
||||||
|
|
||||||
|
<div className="pad-4 card">
|
||||||
|
<h1>{props.entry.name}</h1>
|
||||||
|
|
||||||
|
{props.entry.description.map((entry) => <p key={entry} >{entry}</p>)}
|
||||||
|
|
||||||
|
<a href={props.entry.url} className="button full-width">View</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DevGallery;
|
6
src/js/dev/dev.js
Normal file
6
src/js/dev/dev.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom";
|
||||||
|
|
||||||
|
import DevGallery from "./DevGallery.js";
|
||||||
|
|
||||||
|
ReactDOM.render(<DevGallery />, document.getElementById('react'));
|
426
src/scss/style.scss
Normal file
426
src/scss/style.scss
Normal file
@ -0,0 +1,426 @@
|
|||||||
|
$font-stack: 'Roboto', arial;
|
||||||
|
$background-colour: #202124;
|
||||||
|
$ui-colour: #131313;
|
||||||
|
$light-ui: #575757;
|
||||||
|
$text-colour: white;
|
||||||
|
$ui-element: #505050;
|
||||||
|
$ui-shadow: #404040;
|
||||||
|
|
||||||
|
$pad-px: 20px;
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-family: $font-stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: $background-colour;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $text-colour;
|
||||||
|
font-size: 20px;
|
||||||
|
outline: 0;
|
||||||
|
/*text-shadow: 1px 1px 1px #aaa;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: $text-colour;
|
||||||
|
font-size: 20px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-text {
|
||||||
|
color: $text-colour;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-text {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.half-width {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
background-color: $ui-element;
|
||||||
|
color: $text-colour;
|
||||||
|
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
border-radius: 3px;
|
||||||
|
border: none;
|
||||||
|
margin: 4px auto;
|
||||||
|
padding: 15px;
|
||||||
|
|
||||||
|
font-size: 15px;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
box-shadow: 0 9px #383838;
|
||||||
|
|
||||||
|
text: {
|
||||||
|
shadow: 0.5px 0.5px 0.5px rgba(0,0,0,0.14);
|
||||||
|
align: center;
|
||||||
|
decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
box-shadow: 0 5px #383838;
|
||||||
|
transform: translateY(4px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=text], input[type=password], input[type=number], select {
|
||||||
|
padding: 15px;
|
||||||
|
background-color: $ui-element;
|
||||||
|
border: black;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
margin: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallerystrip{
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
margin: auto;
|
||||||
|
margin-top: 15px;
|
||||||
|
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: auto;
|
||||||
|
border: 10px solid #313439;
|
||||||
|
margin: $pad-px / 2;
|
||||||
|
box-shadow: 4px 4px 7px #070707;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile {
|
||||||
|
background-color: $ui-colour;
|
||||||
|
height: auto;
|
||||||
|
margin-top: 10px;
|
||||||
|
/*border: 8px solid #313439;*/
|
||||||
|
border-radius: 0px;
|
||||||
|
box-shadow: 7px 7px 8px black;
|
||||||
|
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-radius: 50%;
|
||||||
|
display: block;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border: 8px solid #212121;
|
||||||
|
box-shadow: 4px 4px 3px #0c0c0c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h1.title {
|
||||||
|
color: black;
|
||||||
|
font-size: 6em;
|
||||||
|
font-family: 'Pirata One', arial;
|
||||||
|
text-shadow: 3px 3px 3px #aaa;
|
||||||
|
|
||||||
|
cursor: default;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
|
||||||
|
font-weight: bold;
|
||||||
|
display: block;
|
||||||
|
text-decoration: none;
|
||||||
|
background-color: white;
|
||||||
|
padding: 0px;
|
||||||
|
/*font-size: 16em;*/
|
||||||
|
width: 3.5em;
|
||||||
|
height: 1.3em;
|
||||||
|
text-align: center;
|
||||||
|
margin: auto;
|
||||||
|
box-shadow: 5px 5px 8px #000000;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
/*background-color: grey;*/
|
||||||
|
background-color: $ui-colour;
|
||||||
|
box-shadow: 4px 4px 8px black;
|
||||||
|
padding: 10px;
|
||||||
|
margin: $pad-px / 2;
|
||||||
|
/*border-radius: 3px;*/
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
color: $text-colour;
|
||||||
|
text-shadow: 1px 1px 2px #4f4f4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
box-shadow: 2px 2px 2px black;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h1.sectiontitle {
|
||||||
|
text-align:center;
|
||||||
|
color: $text-colour;
|
||||||
|
font-family: 'Megrim', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.navbar {
|
||||||
|
list-style-type: none;
|
||||||
|
background-color: $light-ui;
|
||||||
|
margin: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
/*box-shadow: 3px 3px 1px grey;*/
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
-khtml-user-drag: none;
|
||||||
|
-moz-user-drag: none;
|
||||||
|
-o-user-drag: none;
|
||||||
|
|
||||||
|
box-shadow: 0 9px 12px 0 black, 0 9px 12px 0 black;
|
||||||
|
|
||||||
|
li {
|
||||||
|
float: left;
|
||||||
|
position: -webkit-sticky;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: block;
|
||||||
|
color: $text-colour;
|
||||||
|
text-align: center;
|
||||||
|
padding: 14px 16px;
|
||||||
|
// padding: 10px;
|
||||||
|
margin: 2px;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
text-decoration: none;
|
||||||
|
text-shadow: 1px 1px 1px rgba(0,0,0, 0.5);
|
||||||
|
-webkit-transition: background-color 0.4s;
|
||||||
|
transition: background-color 0.4s;
|
||||||
|
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $ui-shadow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
li.right {float: right;}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
|
||||||
|
list-style-type: none;
|
||||||
|
background-color: $light-ui;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
// padding-top: 10px;
|
||||||
|
// padding-bottom: 10px;
|
||||||
|
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
-khtml-user-drag: none;
|
||||||
|
-moz-user-drag: none;
|
||||||
|
-o-user-drag: none;
|
||||||
|
|
||||||
|
box-shadow: 0 9px 12px 0 black, 0 9px 12px 0 black;
|
||||||
|
|
||||||
|
tr {
|
||||||
|
top: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
td {
|
||||||
|
|
||||||
|
// height: 50px;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: center;
|
||||||
|
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $text-colour;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
text-decoration: none;
|
||||||
|
text-shadow: 1px 1px 1px rgba(0,0,0, 0.5);
|
||||||
|
-webkit-transition: background-color 0.4s;
|
||||||
|
transition: background-color 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $ui-shadow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-selected {
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-table {
|
||||||
|
width: 100%;
|
||||||
|
margin: auto;
|
||||||
|
td {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.max-width {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-no-select {
|
||||||
|
cursor: default;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
p {
|
||||||
|
text-align: right;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
text-align: right;
|
||||||
|
display: block;
|
||||||
|
color: grey;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#snackbar {
|
||||||
|
visibility: hidden;
|
||||||
|
min-width: 250px;
|
||||||
|
margin-left: -125px;
|
||||||
|
background-color: white;
|
||||||
|
color: black;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 16px;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 30px;
|
||||||
|
font-size: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#snackbar.show {
|
||||||
|
visibility: visible;
|
||||||
|
-webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s;
|
||||||
|
animation: fadein 0.5s, fadeout 0.5s 2.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes fadein {
|
||||||
|
from {bottom: 0; opacity: 0;}
|
||||||
|
to {bottom: 30px; opacity: 1;}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadein {
|
||||||
|
from {bottom: 0; opacity: 0;}
|
||||||
|
to {bottom: 30px; opacity: 1;}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes fadeout {
|
||||||
|
from {bottom: 30px; opacity: 1;}
|
||||||
|
to {bottom: 0; opacity: 0;}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeout {
|
||||||
|
from {bottom: 30px; opacity: 1;}
|
||||||
|
to {bottom: 0; opacity: 0;}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 950px) {
|
||||||
|
ul.navbar li.right,
|
||||||
|
ul.navbar li {float: none;}
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*="col-"] {
|
||||||
|
float: left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*="pad-"] {
|
||||||
|
float: left;
|
||||||
|
width: calc(100% - #{$pad-px});
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-width: 768px) {
|
||||||
|
/* For desktop: */
|
||||||
|
.col-1 {width: 8.33%;}
|
||||||
|
.col-2 {width: 16.66%;}
|
||||||
|
.col-3 {width: 25%;}
|
||||||
|
.col-4 {width: 33.33%;}
|
||||||
|
.col-5 {width: 41.66%;}
|
||||||
|
.col-6 {width: 50%;}
|
||||||
|
.col-7 {width: 58.33%;}
|
||||||
|
.col-8 {width: 66.66%;}
|
||||||
|
.col-9 {width: 75%;}
|
||||||
|
.col-10 {width: 83.33%;}
|
||||||
|
.col-11 {width: 91.66%;}
|
||||||
|
.col-12 {width: 100%;}
|
||||||
|
|
||||||
|
/* For desktop: */
|
||||||
|
.pad-2 {width: calc(16.66% - #{$pad-px});}
|
||||||
|
.pad-3 {width: calc(25% - #{$pad-px});}
|
||||||
|
.pad-4 {width: calc(33.33% - #{$pad-px});}
|
||||||
|
.pad-5 {width: calc(41.66% - #{$pad-px});}
|
||||||
|
.pad-6 {width: calc(50% - #{$pad-px});}
|
||||||
|
.pad-7 {width: calc(58.33% - #{$pad-px});}
|
||||||
|
.pad-8 {width: calc(66.66% - #{$pad-px});}
|
||||||
|
.pad-9 {width: calc(75% - #{$pad-px});}
|
||||||
|
.pad-10 {width: calc(83.33% - #{$pad-px});}
|
||||||
|
.pad-11 {width: calc(91.66% - #{$pad-px});}
|
||||||
|
.pad-12 {width: calc(100% - #{$pad-px});}
|
||||||
|
}
|
||||||
|
|
||||||
|
.row::after {
|
||||||
|
content: "";
|
||||||
|
clear: both;
|
||||||
|
display: table;
|
||||||
|
}
|
218
static/style.css
218
static/style.css
@ -1,218 +0,0 @@
|
|||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-family: 'Roboto', arial;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: #111725;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: white;
|
|
||||||
font-size: 20px;
|
|
||||||
/*text-shadow: 1px 1px 1px #aaa;*/
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
color: white;
|
|
||||||
font-size: 20px;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
background-color: white;
|
|
||||||
color: black;
|
|
||||||
border-radius: 10px;
|
|
||||||
display: inline-block;
|
|
||||||
margin: 4px auto;
|
|
||||||
cursor: pointer;
|
|
||||||
text-align: center;
|
|
||||||
padding: 15px;
|
|
||||||
text-decoration: none;
|
|
||||||
box-shadow: 2px 2px 4px black;
|
|
||||||
-webkit-transition-duration: 0.4s;
|
|
||||||
transition-duration: 0.4s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:hover {
|
|
||||||
box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24),0 17px 50px 0 rgba(0,0,0,0.19);
|
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
|
||||||
margin:auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallerystrip img {
|
|
||||||
height: auto;
|
|
||||||
border: 6px solid #484c55;
|
|
||||||
margin: 5px;
|
|
||||||
box-shadow: 7px 7px 8px #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.gallerystrip {
|
|
||||||
width: 100%;
|
|
||||||
margin: auto;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.profile {
|
|
||||||
background-color: #041617;
|
|
||||||
height: auto;
|
|
||||||
margin-top: 10px;
|
|
||||||
border: 8px solid #1a2324;
|
|
||||||
border-radius: 6px;
|
|
||||||
box-shadow: 7px 7px 8px #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.profile img {
|
|
||||||
border-radius: 50%;
|
|
||||||
display: block;
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
border: 8px solid #404c4d;
|
|
||||||
box-shadow: 2px 6px 2px #888888;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1.title {
|
|
||||||
color: white;
|
|
||||||
font-size: 14vw;
|
|
||||||
font-family: 'Megrim', arial;
|
|
||||||
text-shadow: 2px 2px 2px #aaa;
|
|
||||||
|
|
||||||
background-color: #070c1a;
|
|
||||||
padding: 0px;
|
|
||||||
width: 45vw;
|
|
||||||
height: 18vw;
|
|
||||||
text-align: center;
|
|
||||||
margin: auto;
|
|
||||||
box-shadow: 4px 4px 8px #000000;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
div.card {
|
|
||||||
background-color: grey;
|
|
||||||
box-shadow: 4px 4px 8px black;
|
|
||||||
padding: 10px;
|
|
||||||
margin: 5px;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.card h1 {
|
|
||||||
text-align: center;
|
|
||||||
color: white;
|
|
||||||
text-shadow: 1px 1px 2px #4f4f4f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card img {
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 3px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
box-shadow: 2px 2px 2px black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card p {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1.sectiontitle {
|
|
||||||
text-align:center;
|
|
||||||
color: white;
|
|
||||||
font-family: 'Megrim', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.navbar {
|
|
||||||
list-style-type: none;
|
|
||||||
border-radius: 5px 15px;
|
|
||||||
box-shadow: 5px 5px grey;
|
|
||||||
margin: 10px;
|
|
||||||
padding: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
background-color: #1e2028;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.navbar li {
|
|
||||||
float: left;
|
|
||||||
position: -webkit-sticky;
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.navbar li a {
|
|
||||||
display: block;
|
|
||||||
color: white;
|
|
||||||
text-align: center;
|
|
||||||
padding: 14px 16px;
|
|
||||||
text-decoration: none;
|
|
||||||
-webkit-transition: background-color 0.4s;
|
|
||||||
transition: background-color 0.4s;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.navbar a:hover {
|
|
||||||
background-color: #00030a;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.navbar li.right {float: right;}
|
|
||||||
|
|
||||||
footer p {
|
|
||||||
text-align: right;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer a {
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
text-align: right;
|
|
||||||
display: block;
|
|
||||||
color: grey;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: 600px) {
|
|
||||||
ul.navbar li.right,
|
|
||||||
ul.navbar li {float: none;}
|
|
||||||
}
|
|
||||||
|
|
||||||
[class*="col-"] {
|
|
||||||
float: left;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (min-width: 768px) {
|
|
||||||
/* For desktop: */
|
|
||||||
.col-1 {width: 8.33%;}
|
|
||||||
.col-2 {width: 16.66%;}
|
|
||||||
.col-3 {width: 25%;}
|
|
||||||
.col-4 {width: 33.33%;}
|
|
||||||
.col-5 {width: 41.66%;}
|
|
||||||
.col-6 {width: 50%;}
|
|
||||||
.col-7 {width: 58.33%;}
|
|
||||||
.col-8 {width: 66.66%;}
|
|
||||||
.col-9 {width: 75%;}
|
|
||||||
.col-10 {width: 83.33%;}
|
|
||||||
.col-11 {width: 91.66%;}
|
|
||||||
.col-12 {width: 100%;}
|
|
||||||
|
|
||||||
/* For desktop: */
|
|
||||||
.col-pad-3 {width: calc(25% - 10px);}
|
|
||||||
.col-pad-4 {width: calc(33.33% - 10px);}
|
|
||||||
.col-pad-5 {width: 41.66%;}
|
|
||||||
.col-pad-6 {width: calc(50% - 10px);}
|
|
||||||
.col-pad-7 {width: 58.33%;}
|
|
||||||
.col-pad-8 {width: 66.66%;}
|
|
||||||
.col-pad-9 {width: 75%;}
|
|
||||||
.col-pad-10 {width: 83.33%;}
|
|
||||||
.col-pad-11 {width: 91.66%;}
|
|
||||||
.col-pad-12 {width: calc(100% - 10px);}
|
|
||||||
}
|
|
||||||
|
|
||||||
.row::after {
|
|
||||||
content: "";
|
|
||||||
clear: both;
|
|
||||||
display: table;
|
|
||||||
}
|
|
31
webpack.common.js
Normal file
31
webpack.common.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: {
|
||||||
|
dev: './src/js/dev/dev.js'
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.(js|jsx)$/,
|
||||||
|
exclude: /(node_modules|bower_components)/,
|
||||||
|
loader: "babel-loader",
|
||||||
|
options: { presets: ["@babel/env"] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
use: ["style-loader", "css-loader"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new CleanWebpackPlugin()
|
||||||
|
],
|
||||||
|
resolve: { extensions: ["*", ".js", ".jsx"] },
|
||||||
|
output: {
|
||||||
|
filename: '[name].bundle.js',
|
||||||
|
path: path.resolve(__dirname, 'build/js')
|
||||||
|
}
|
||||||
|
};
|
8
webpack.dev.js
Normal file
8
webpack.dev.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
const merge = require('webpack-merge');
|
||||||
|
const common = require('./webpack.common.js');
|
||||||
|
|
||||||
|
module.exports = merge(common, {
|
||||||
|
mode: 'development',
|
||||||
|
devtool: 'inline-source-map',
|
||||||
|
watch: true
|
||||||
|
});
|
7
webpack.prod.js
Normal file
7
webpack.prod.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
const merge = require('webpack-merge');
|
||||||
|
const common = require('./webpack.common.js');
|
||||||
|
|
||||||
|
module.exports = merge(common, {
|
||||||
|
mode: 'production',
|
||||||
|
devtool: 'source-map'
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user