Compare commits
No commits in common. "main" and "compat" have entirely different histories.
@ -440,31 +440,19 @@ class Poobrain(flask.Flask):
|
|||||||
peewee.DoesNotExist: 404
|
peewee.DoesNotExist: 404
|
||||||
}
|
}
|
||||||
|
|
||||||
setup_funcs = None
|
|
||||||
cronjobs = None
|
cronjobs = None
|
||||||
|
|
||||||
_setup_done = False
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
if not 'root_path' in kwargs:
|
if not 'root_path' in kwargs:
|
||||||
kwargs['root_path'] = str(pathlib.Path('.').absolute()) #TODO: pathlib probably isn't really needed here
|
kwargs['root_path'] = str(pathlib.Path('.').absolute()) #TODO: pathlib probably isn't really needed here
|
||||||
|
|
||||||
if 'DEBUG' in dir(config) and config.DEBUG:
|
|
||||||
# There's some shitty overriding going on based on FLASK_ENV.
|
|
||||||
# Set the env var to override the override and enforce what
|
|
||||||
# the config says.
|
|
||||||
os.environ['FLASK_ENV'] = 'development'
|
|
||||||
else:
|
|
||||||
os.environ['FLASK_ENV'] = 'production'
|
|
||||||
|
|
||||||
super(Poobrain, self).__init__(*args, **kwargs)
|
super(Poobrain, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.setup_funcs = []
|
|
||||||
self.cronjobs = []
|
self.cronjobs = []
|
||||||
|
|
||||||
@click.group(cls=flask.cli.FlaskGroup, create_app=lambda x=None: self)
|
@click.group(cls=flask.cli.FlaskGroup, create_app=lambda x: self)
|
||||||
@click.option('--database', default=f"sqlite:///{project_name}.db")
|
@click.option('--database', default=f"sqlite:///{project_name}.db")
|
||||||
def cli(database):
|
def cli(database):
|
||||||
self.db = db_url.connect(database)
|
self.db = db_url.connect(database)
|
||||||
@ -548,15 +536,7 @@ class Poobrain(flask.Flask):
|
|||||||
self.site = Pooprint('site', 'site')
|
self.site = Pooprint('site', 'site')
|
||||||
self.admin = Pooprint('admin', 'admin')
|
self.admin = Pooprint('admin', 'admin')
|
||||||
|
|
||||||
|
|
||||||
def full_dispatch_request(self):
|
|
||||||
|
|
||||||
if not self._setup_done:
|
|
||||||
self.run_setup()
|
|
||||||
|
|
||||||
return super().full_dispatch_request()
|
|
||||||
|
|
||||||
|
|
||||||
def main(self):
|
def main(self):
|
||||||
#self.cli(obj={})
|
#self.cli(obj={})
|
||||||
self.cli()
|
self.cli()
|
||||||
@ -566,28 +546,18 @@ class Poobrain(flask.Flask):
|
|||||||
return True # autoescape everything
|
return True # autoescape everything
|
||||||
|
|
||||||
|
|
||||||
def setup(self, f):
|
def try_trigger_before_first_request_functions(self):
|
||||||
|
|
||||||
self.setup_funcs.append(f)
|
if not self.setup in self.before_first_request_funcs:
|
||||||
|
self.before_first_request_funcs.append(self.setup)
|
||||||
|
super(Poobrain, self).try_trigger_before_first_request_functions()
|
||||||
|
|
||||||
return f
|
|
||||||
|
def setup(self):
|
||||||
def run_setup(self):
|
|
||||||
|
|
||||||
"""
|
|
||||||
Global runtime setup. Calls all @app.setup decorated functions
|
|
||||||
and registers site and admin blueprints.
|
|
||||||
Called by request_setup on first request.
|
|
||||||
"""
|
|
||||||
|
|
||||||
for f in self.setup_funcs:
|
|
||||||
f()
|
|
||||||
|
|
||||||
self.register_blueprint(self.site)
|
self.register_blueprint(self.site)
|
||||||
self.register_blueprint(self.admin, url_prefix='/admin/')
|
self.register_blueprint(self.admin, url_prefix='/admin/')
|
||||||
|
|
||||||
self._setup_done = True
|
|
||||||
|
|
||||||
|
|
||||||
@locked_cached_property
|
@locked_cached_property
|
||||||
def theme_paths(self):
|
def theme_paths(self):
|
||||||
@ -603,7 +573,7 @@ class Poobrain(flask.Flask):
|
|||||||
|
|
||||||
return paths
|
return paths
|
||||||
|
|
||||||
|
|
||||||
def serve_theme_resources(self, resource):
|
def serve_theme_resources(self, resource):
|
||||||
|
|
||||||
r = False
|
r = False
|
||||||
@ -642,6 +612,7 @@ class Poobrain(flask.Flask):
|
|||||||
except jinja2.exceptions.TemplateNotFound:
|
except jinja2.exceptions.TemplateNotFound:
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
paths = [os.path.join(path, resource) for path in app.theme_paths]
|
paths = [os.path.join(path, resource) for path in app.theme_paths]
|
||||||
@ -658,12 +629,12 @@ class Poobrain(flask.Flask):
|
|||||||
r.cache_control.public = True
|
r.cache_control.public = True
|
||||||
r.cache_control.max_age = app.config['CACHE_LONG']
|
r.cache_control.max_age = app.config['CACHE_LONG']
|
||||||
return r
|
return r
|
||||||
|
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
|
|
||||||
def request_setup(self):
|
def request_setup(self):
|
||||||
|
|
||||||
flask.g.boxes = {}
|
flask.g.boxes = {}
|
||||||
flask.g.forms = {}
|
flask.g.forms = {}
|
||||||
#self.db.close() # fails first request and thus always on sqlite
|
#self.db.close() # fails first request and thus always on sqlite
|
||||||
@ -794,7 +765,7 @@ class Poobrain(flask.Flask):
|
|||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
raise LookupError(f"Failed generating URL for {cls.__name__}[{url_params.get('handle', None)}]-{mode}. No matching route found.")
|
raise LookupError("Failed generating URL for {cls.__name__}[{url_params.get('handle', None)}]-{mode}. No matching route found.")
|
||||||
|
|
||||||
def get_related_view_url(self, cls, handle, related_field, add=None):
|
def get_related_view_url(self, cls, handle, related_field, add=None):
|
||||||
|
|
||||||
|
@ -576,8 +576,6 @@ class Dataset(EphemeralDataset, poobrains.commenting.Commentable):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
database = app.db
|
|
||||||
|
|
||||||
modes = collections.OrderedDict([
|
modes = collections.OrderedDict([
|
||||||
('add', 'create'),
|
('add', 'create'),
|
||||||
('teaser', 'read'),
|
('teaser', 'read'),
|
||||||
|
@ -77,15 +77,12 @@ class FileFieldset(poobrains.formapp.MultistateFieldset):
|
|||||||
|
|
||||||
self['file'] = poobrains.form.fields.File()
|
self['file'] = poobrains.form.fields.File()
|
||||||
|
|
||||||
self['consumer'] = poobrains.form.fields.Select(choices=(
|
self['consumer'] = poobrains.form.fields.Select(choices=(
|
||||||
('csv', "CSV"),
|
('csv', "CSV"),
|
||||||
('geojson', "geojson"),
|
('geojson', "geojson"),
|
||||||
))
|
))
|
||||||
|
|
||||||
if self.state == 'csv':
|
elif self.state == 'csv':
|
||||||
|
|
||||||
self['consumer'].value = 'csv'
|
|
||||||
self['consumer'].readonly = True
|
|
||||||
|
|
||||||
csv_options = self.session['csv_options']
|
csv_options = self.session['csv_options']
|
||||||
|
|
||||||
@ -105,14 +102,13 @@ class FileFieldset(poobrains.formapp.MultistateFieldset):
|
|||||||
flash(f"Full error message: {e}", 'error')
|
flash(f"Full error message: {e}", 'error')
|
||||||
else:
|
else:
|
||||||
|
|
||||||
self.session['csv_options'] = csv_options
|
|
||||||
table = poobrains.rendering.Table(title='CSV preview')
|
table = poobrains.rendering.Table(title='CSV preview')
|
||||||
for idx, row in enumerate(reader):
|
for idx, row in enumerate(reader):
|
||||||
if idx == 5:
|
if idx == 5:
|
||||||
break
|
break
|
||||||
table.append(*row)
|
table.append(*row)
|
||||||
|
|
||||||
self['table'] = poobrains.form.fields.RenderableWrapper(table)
|
self['table'] = poobrains.form.fields.RenderableWrapper(value=table)
|
||||||
self.approve = poobrains.form.Button('submit', label='Approve')
|
self.approve = poobrains.form.Button('submit', label='Approve')
|
||||||
|
|
||||||
escaped = {}
|
escaped = {}
|
||||||
@ -138,46 +134,21 @@ class FileFieldset(poobrains.formapp.MultistateFieldset):
|
|||||||
|
|
||||||
def process(self, submit):
|
def process(self, submit):
|
||||||
|
|
||||||
if self.state == 'csv':
|
if submit == 'change_csv_options':
|
||||||
if submit == 'change_csv_options':
|
unescape = lambda x: bytes(x, 'utf-8').decode('unicode_escape')
|
||||||
unescape = lambda x: bytes(x, 'utf-8').decode('unicode_escape')
|
self.session['csv_options'] = {
|
||||||
self.session['csv_options'] = {
|
'delimiter': unescape(self['delimiter'].value),
|
||||||
'delimiter': unescape(self['delimiter'].value or ''),
|
'doublequote': self['doublequote'].value,
|
||||||
'doublequote': self['doublequote'].value,
|
'escapechar': unescape(self['escapechar'].value),
|
||||||
'escapechar': unescape(self['escapechar'].value or ''),
|
'lineterminator': unescape(self['lineterminator'].value),
|
||||||
'lineterminator': unescape(self['lineterminator'].value),
|
'quotechar': unescape(self['quotechar'].value),
|
||||||
'quotechar': unescape(self['quotechar'].value or ''),
|
'quoting': self['quoting'].value,
|
||||||
'quoting': self['quoting'].value,
|
'skipinitialspace': self['skipinitialspace'].value,
|
||||||
'skipinitialspace': self['skipinitialspace'].value,
|
}
|
||||||
}
|
|
||||||
|
|
||||||
flash("Changed CSV dialect options.")
|
flash("Changed CSV dialect options.")
|
||||||
else:
|
|
||||||
dialect = dialect_from_dict(self.session['csv_options'])
|
|
||||||
|
|
||||||
try:
|
else: # means parent form wasn't submitting via cancel button – i.e. the "load" button was clicked
|
||||||
reader = csv.reader(io.StringIO(self.session['raw']), dialect)
|
|
||||||
except csv.Error as e:
|
|
||||||
flash("Can't read file as CSV with supplied options.", 'error')
|
|
||||||
if app.debug:
|
|
||||||
flash(f"Full error message: {e}", 'error')
|
|
||||||
|
|
||||||
ds = self.owner()
|
|
||||||
ds['csv_col_fixme'] = {
|
|
||||||
'title': "CSV Column",
|
|
||||||
'description': 'FIXME: CSV has no type recognition, everything is float64',
|
|
||||||
'dtype': 'float64',
|
|
||||||
'color': None,
|
|
||||||
'observations': {}
|
|
||||||
}
|
|
||||||
|
|
||||||
for idx, row in enumerate(reader):
|
|
||||||
ds['csv_col_fixme']['observations'][idx] = util.float64.string_numpy(row[0]);
|
|
||||||
|
|
||||||
self.parent.session['other'] = ds.to_dict(whole=True)
|
|
||||||
self.parent.state = 'merge'
|
|
||||||
|
|
||||||
else: # submitted via load button with file upload
|
|
||||||
|
|
||||||
consumer = self['consumer'].value
|
consumer = self['consumer'].value
|
||||||
raw = self['file'].value.read()
|
raw = self['file'].value.read()
|
||||||
@ -213,7 +184,7 @@ class FileFieldset(poobrains.formapp.MultistateFieldset):
|
|||||||
|
|
||||||
elif consumer == 'csv':
|
elif consumer == 'csv':
|
||||||
self.state = 'csv'
|
self.state = 'csv'
|
||||||
self.session['csv_options'] = None
|
self.session['data_action_info']['csv_options'] = None
|
||||||
|
|
||||||
else:
|
else:
|
||||||
flash("Unhandled", 'error')
|
flash("Unhandled", 'error')
|
||||||
|
@ -39,7 +39,7 @@ def enforce_tls():
|
|||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
@app.setup
|
@app.before_first_request
|
||||||
def admin_setup():
|
def admin_setup():
|
||||||
|
|
||||||
if not app._got_first_request:
|
if not app._got_first_request:
|
||||||
|
@ -331,13 +331,18 @@ def add(storable):
|
|||||||
|
|
||||||
default = None
|
default = None
|
||||||
|
|
||||||
if callable(field.default):
|
if field.default:
|
||||||
default = field.default()
|
|
||||||
else:
|
|
||||||
default = field.default
|
|
||||||
|
|
||||||
if field.null and default is None:
|
if callable(field.default):
|
||||||
default = '' # None interpreted as no default, '' treated as no value
|
default = field.default()
|
||||||
|
else:
|
||||||
|
default = field.default
|
||||||
|
|
||||||
|
elif field.type == types.DATETIME:
|
||||||
|
default = datetime.datetime.now()
|
||||||
|
|
||||||
|
elif field.type == types.BOOL:
|
||||||
|
default = False
|
||||||
|
|
||||||
value = click.prompt(field.name, type=field.type, default=default)
|
value = click.prompt(field.name, type=field.type, default=default)
|
||||||
|
|
||||||
|
@ -321,7 +321,7 @@ class BaseField(object, metaclass=poobrains.helpers.MetaCompatibility):
|
|||||||
return 'true' if value == True else 'false'
|
return 'true' if value == True else 'false'
|
||||||
|
|
||||||
elif isinstance(value, datetime.datetime):
|
elif isinstance(value, datetime.datetime):
|
||||||
return value.strftime('%Y-%m-%dT%H:%M:%S')
|
return value.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
elif inspect.isclass(value):
|
elif inspect.isclass(value):
|
||||||
return value.__name__
|
return value.__name__
|
||||||
@ -545,8 +545,3 @@ class File(Field):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
def bind(self, value):
|
|
||||||
# simple passthrough because this is filled with object(s) from
|
|
||||||
# request.files and shouldn't be converted to a string.
|
|
||||||
self.value = value
|
|
||||||
|
@ -49,7 +49,7 @@ class DateTimeParamType(ParamType):
|
|||||||
return value # apparently we need this function to be idempotent.
|
return value # apparently we need this function to be idempotent.
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S')
|
return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ class DateTimeParamType(ParamType):
|
|||||||
else:
|
else:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%f')
|
return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S.%f')
|
||||||
|
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
|
||||||
@ -71,7 +71,7 @@ class DateTimeParamType(ParamType):
|
|||||||
self.fail("We dun goof'd, this field isn't working.")
|
self.fail("We dun goof'd, this field isn't working.")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.fail(f"'{value}' is not a valid datetime. Expected format '%Y-%m-%dT%H:%M:%S' or '%Y-%m-%dT%H:%M:%S.%f'")
|
self.fail(f"'{value}' is not a valid datetime. Expected format '%Y-%m-%d %H:%M:%S' or '%Y-%m-%d %H:%M:%S.%f'")
|
||||||
|
|
||||||
DATETIME = DateTimeParamType()
|
DATETIME = DateTimeParamType()
|
||||||
|
|
||||||
|
@ -263,7 +263,7 @@ def pretty_bytes(bytecount):
|
|||||||
|
|
||||||
value /= 1024.0
|
value /= 1024.0
|
||||||
|
|
||||||
return f"{value:.2f} {unit}"
|
return "{value:.2f} {unit}"
|
||||||
|
|
||||||
|
|
||||||
def levenshtein_distance(s1, s2):
|
def levenshtein_distance(s1, s2):
|
||||||
@ -329,7 +329,6 @@ class FakeMetaOptions(object):
|
|||||||
modes = None
|
modes = None
|
||||||
permission_class = None
|
permission_class = None
|
||||||
schema = None # needed by peewee.ModelBase.__new__
|
schema = None # needed by peewee.ModelBase.__new__
|
||||||
database = None # needed py peewee.ModelBase.__new__ at least from 3.15.0 on
|
|
||||||
_additional_keys = None # needed by peewee.ModelBase.__new__
|
_additional_keys = None # needed by peewee.ModelBase.__new__
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -14,7 +14,7 @@ class SVG(poobrains.auth.Protected):
|
|||||||
|
|
||||||
handle = None # needed so that app.expose registers a route with extra param, this is kinda hacky…
|
handle = None # needed so that app.expose registers a route with extra param, this is kinda hacky…
|
||||||
_css_cache = None
|
_css_cache = None
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
modes = collections.OrderedDict([
|
modes = collections.OrderedDict([
|
||||||
@ -23,7 +23,7 @@ class SVG(poobrains.auth.Protected):
|
|||||||
('raw', 'read'),
|
('raw', 'read'),
|
||||||
('inline', 'read')
|
('inline', 'read')
|
||||||
])
|
])
|
||||||
|
|
||||||
style = None
|
style = None
|
||||||
|
|
||||||
def __init__(self, handle=None, mode=None, **kwargs):
|
def __init__(self, handle=None, mode=None, **kwargs):
|
||||||
@ -36,16 +36,16 @@ class SVG(poobrains.auth.Protected):
|
|||||||
else:
|
else:
|
||||||
self.style = Markup(app.scss_compiler.compile_string("@import 'svg';"))
|
self.style = Markup(app.scss_compiler.compile_string("@import 'svg';"))
|
||||||
self.__class__._css_cache = self.style
|
self.__class__._css_cache = self.style
|
||||||
|
|
||||||
|
|
||||||
def templates(self, mode=None):
|
def templates(self, mode=None):
|
||||||
|
|
||||||
r = super(SVG, self).templates(mode=mode)
|
r = super(SVG, self).templates(mode=mode)
|
||||||
return [f"svg/{template}" for template in r]
|
return [f"svg/{template}" for template in r]
|
||||||
|
|
||||||
|
|
||||||
def instance_url(self, mode='full', quiet=False, **url_params):
|
def instance_url(self, mode='full', quiet=False, **url_params):
|
||||||
|
|
||||||
url_params['handle'] = self.handle
|
url_params['handle'] = self.handle
|
||||||
|
|
||||||
return super(SVG, self).instance_url(mode=mode, quiet=quiet, **url_params)
|
return super(SVG, self).instance_url(mode=mode, quiet=quiet, **url_params)
|
||||||
@ -55,23 +55,23 @@ class SVG(poobrains.auth.Protected):
|
|||||||
def view(self, mode=None, handle=None):
|
def view(self, mode=None, handle=None):
|
||||||
|
|
||||||
if mode == 'raw':
|
if mode == 'raw':
|
||||||
|
|
||||||
response = Response(self.render('raw'))
|
response = Response(self.render('raw'))
|
||||||
response.headers['Content-Type'] = 'image/svg+xml'
|
response.headers['Content-Type'] = 'image/svg+xml'
|
||||||
response.headers['Content-Disposition'] = f'filename="{self.__class__.__name__}.svg"'
|
response.headers['Content-Disposition'] = f'filename="{self.__class__.__name__}.svg"'
|
||||||
|
|
||||||
# Disable "public" mode caching downstream (nginx, varnish) in order to hopefully not leak restricted content
|
# Disable "public" mode caching downstream (nginx, varnish) in order to hopefully not leak restricted content
|
||||||
response.cache_control.public = False
|
response.cache_control.public = False
|
||||||
response.cache_control.private = True
|
response.cache_control.private = True
|
||||||
response.cache_control.max_age = app.config['CACHE_LONG']
|
response.cache_control.max_age = app.config['CACHE_LONG']
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return poobrains.helpers.ThemedPassthrough(super(SVG, self).view(mode=mode, handle=handle))
|
return poobrains.helpers.ThemedPassthrough(super(SVG, self).view(mode=mode, handle=handle))
|
||||||
|
|
||||||
|
|
||||||
@app.setup
|
@app.before_first_request
|
||||||
def register_svg_raw():
|
def register_svg_raw():
|
||||||
for cls in set(SVG.class_children()):
|
for cls in set(SVG.class_children()):
|
||||||
rule = os.path.join("/svg/", cls.__name__.lower(), '<handle>', 'raw')
|
rule = os.path.join("/svg/", cls.__name__.lower(), '<handle>', 'raw')
|
||||||
|
@ -207,7 +207,7 @@ y
|
|||||||
if name.isupper():
|
if name.isupper():
|
||||||
poobrains.app.config[name] = getattr(config, name)
|
poobrains.app.config[name] = getattr(config, name)
|
||||||
|
|
||||||
client.get('/') # first request that triggers app.run_setup to finish booting poobrains
|
client.get('/') # first request that triggers before_first_request to finish booting poobrains
|
||||||
|
|
||||||
|
|
||||||
def test_cli_minica(client):
|
def test_cli_minica(client):
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
$color_background_dark: rgba(8,8,8, 0.85);
|
$color_background_dark: rgba(8,8,8, 0.8);
|
||||||
$color_background_light: rgba(255,255,255, 0.85);
|
$color_background_light: rgba(255,255,255, 0.8);
|
||||||
$color_highlight: rgba(0,128,255, 0.85);
|
$color_highlight: rgba(0,128,255, 0.8);
|
||||||
/*$color_highlight: transparentize(rebeccapurple, 20%);*/
|
/*$color_highlight: transparentize(rebeccapurple, 20%);*/
|
||||||
$color_danger: rgba(255, 0, 128, 0.85);
|
$color_danger: rgba(255, 0, 128, 0.8);
|
||||||
$color_font_dark: opacify($color_background_dark, 10%);
|
$color_font_dark: opacify($color_background_dark, 10%);
|
||||||
$color_font_light: opacify($color_background_light, 10%);
|
$color_font_light: opacify($color_background_light, 10%);
|
||||||
$color_background_form: opacify($color_background_light, -0.3);
|
$color_background_form: opacify($color_background_light, -0.3);
|
||||||
|
@ -104,8 +104,7 @@
|
|||||||
@media (min-width: 56rem) {
|
@media (min-width: 56rem) {
|
||||||
|
|
||||||
#logo-link {
|
#logo-link {
|
||||||
display: block !important;
|
display: flex !important;
|
||||||
padding: 4rem 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body > header {
|
body > header {
|
||||||
@ -114,6 +113,7 @@
|
|||||||
.sticky {
|
.sticky {
|
||||||
width: 13rem;
|
width: 13rem;
|
||||||
top: 11.5rem !important; /* 7.5rem logo height + 2rem space at top and bottom */
|
top: 11.5rem !important; /* 7.5rem logo height + 2rem space at top and bottom */
|
||||||
|
margin-bottom: 4rem; /* needed at least in firefox, to avoid vertical cutoff. should be the same as vertical padding for logo */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,10 +133,10 @@ html {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
/*overflow-y: scroll;*/ /* always show vertical scrollbar, viewport dependant widths stay the same */
|
/*overflow-y: scroll;*/ /* always show vertical scrollbar, viewport dependant widths stay the same */
|
||||||
/*background-image: url('/theme/bg.svg'), linear-gradient(45deg, lighten(opacify($color_background_dark, 100%), 15%), mix($color_highlight, $color_background_dark, 25%));*/
|
background-image: url('/theme/bg.svg'), linear-gradient(45deg, lighten(opacify($color_background_dark, 100%), 15%), mix($color_highlight, $color_background_dark, 25%));
|
||||||
background-image: url('/theme/bg.svg');
|
|
||||||
/*background-image: url('/theme/pooscape-probablyold.svg'), linear-gradient(to bottom, opacify($color_background_dark, 0.2), darken(opacify($color_highlight, 0.2), 40%) 100%);*/
|
/*background-image: url('/theme/pooscape-probablyold.svg'), linear-gradient(to bottom, opacify($color_background_dark, 0.2), darken(opacify($color_highlight, 0.2), 40%) 100%);*/
|
||||||
background-color: opacify($color_background_dark, 100%);
|
/*background-image: url('/theme/test.png');*/
|
||||||
|
background-color: #333;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-attachment: fixed;
|
background-attachment: fixed;
|
||||||
@ -173,6 +173,7 @@ body {
|
|||||||
line-height: 3rem;
|
line-height: 3rem;
|
||||||
display: inherit;
|
display: inherit;
|
||||||
background: $color_background_light;
|
background: $color_background_light;
|
||||||
|
backdrop-filter: blur($backdrop_blur);
|
||||||
color: $color_font_dark;
|
color: $color_font_dark;
|
||||||
font-family: 'Orbitron', sans;
|
font-family: 'Orbitron', sans;
|
||||||
font-weight: 100;
|
font-weight: 100;
|
||||||
@ -225,18 +226,25 @@ body {
|
|||||||
flex-basis: 15rem;
|
flex-basis: 15rem;
|
||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
flex-grow: 15;
|
flex-grow: 15;
|
||||||
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
max-height: 100vh;
|
max-height: 100vh;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
z-index: 200;
|
z-index: 200;
|
||||||
background-color: $color_background_dark;
|
background-color: $color_background_dark;
|
||||||
|
backdrop-filter: blur($backdrop_blur);
|
||||||
|
/*border-left: 0.25rem solid;
|
||||||
|
border-right: 0.25rem solid;
|
||||||
|
border-bottom: 0.25rem solid;
|
||||||
|
border-color: $color_border;*/
|
||||||
|
|
||||||
/*-webkit-clip-path: polygon(0 0, 0 calc(100% - 3rem), 50% 100%, 100% calc(100% - 3rem), 100% 0);*/
|
/*-webkit-clip-path: polygon(0 0, 0 calc(100% - 3rem), 50% 100%, 100% calc(100% - 3rem), 100% 0);*/
|
||||||
|
|
||||||
overflow: auto;
|
overflow: hidden;
|
||||||
|
|
||||||
&:nth-child(2) { /* means dashbar is rendered */
|
&:nth-child(2) { /* means dashbar is rendered */
|
||||||
margin-top: 3rem;
|
margin-top: 3rem;
|
||||||
@ -312,8 +320,6 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#logo {
|
#logo {
|
||||||
display: block;
|
|
||||||
margin: 0 auto;
|
|
||||||
height: 7.5rem;
|
height: 7.5rem;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
@ -344,6 +350,7 @@ main {
|
|||||||
top: 0;
|
top: 0;
|
||||||
padding: 2rem 1rem;
|
padding: 2rem 1rem;
|
||||||
background: $color_background_light;
|
background: $color_background_light;
|
||||||
|
backdrop-filter: blur($backdrop_blur);
|
||||||
color: $color_font_dark;
|
color: $color_font_dark;
|
||||||
z-index: 110;
|
z-index: 110;
|
||||||
|
|
||||||
@ -503,6 +510,7 @@ main {
|
|||||||
& article.content-type-tag.mode-full > .content > .description {
|
& article.content-type-tag.mode-full > .content > .description {
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
background: $color_background_dark;
|
background: $color_background_dark;
|
||||||
|
backdrop-filter: blur($backdrop_blur);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -525,6 +533,7 @@ main {
|
|||||||
margin: 0.25rem 0;
|
margin: 0.25rem 0;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
background: $color_background_dark;
|
background: $color_background_dark;
|
||||||
|
backdrop-filter: blur($backdrop_blur);
|
||||||
|
|
||||||
&, a {
|
&, a {
|
||||||
color: opacify($color_font_light, -0.3);
|
color: opacify($color_font_light, -0.3);
|
||||||
@ -597,6 +606,7 @@ nav.menu {
|
|||||||
a {
|
a {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background: $color_background_light;
|
background: $color_background_light;
|
||||||
|
backdrop-filter: blur($backdrop_blur);
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
line-height: 2.3rem; /* +0.3rem based on fiddling around in ff inspector */
|
line-height: 2.3rem; /* +0.3rem based on fiddling around in ff inspector */
|
||||||
@ -632,6 +642,7 @@ nav.menu {
|
|||||||
background-color: $color_background_dark;
|
background-color: $color_background_dark;
|
||||||
background-size: 32px 32px;
|
background-size: 32px 32px;
|
||||||
background-position: 4px 4px;
|
background-position: 4px 4px;
|
||||||
|
backdrop-filter: blur($backdrop_blur);
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
|
||||||
transition: background 0.3s linear, color 0.3s linear;
|
transition: background 0.3s linear, color 0.3s linear;
|
||||||
@ -666,6 +677,7 @@ ul {
|
|||||||
& > div.comment {
|
& > div.comment {
|
||||||
/* comments have transparent bg by default, avoid breaking the design when showing them outside of an article */
|
/* comments have transparent bg by default, avoid breaking the design when showing them outside of an article */
|
||||||
background: $color_background_dark;
|
background: $color_background_dark;
|
||||||
|
backdrop-filter: blur($backdrop_blur);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -815,6 +827,7 @@ form {
|
|||||||
& > header {
|
& > header {
|
||||||
|
|
||||||
background: $color_background_light;
|
background: $color_background_light;
|
||||||
|
backdrop-filter: blur($backdrop_blur);
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
@ -843,6 +856,7 @@ form {
|
|||||||
|
|
||||||
& > div.content {
|
& > div.content {
|
||||||
background: $color_background_dark;
|
background: $color_background_dark;
|
||||||
|
backdrop-filter: blur($backdrop_blur);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -892,6 +906,7 @@ article {
|
|||||||
|
|
||||||
padding: 0.25rem 1rem;
|
padding: 0.25rem 1rem;
|
||||||
background: $color_background_light;
|
background: $color_background_light;
|
||||||
|
backdrop-filter: blur($backdrop_blur);
|
||||||
color: $color_font_dark;
|
color: $color_font_dark;
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
|
@ -69,14 +69,14 @@ svg#hexascroll {
|
|||||||
animation: decorotion 5s linear infinite;
|
animation: decorotion 5s linear infinite;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
animation: scroll 10s linear infinite; /* apply the animation to every hexagon instead of the g so we don't get perspective fuckups */
|
/*animation: scroll 10s linear infinite; /* apply the animation to every hexagon instead of the g so we don't get perspective fuckups */*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use {
|
use {
|
||||||
fill: opacify($color_background_dark, -0.2);
|
fill: opacify($color_background_dark, -0.2);
|
||||||
stroke: darken($color_highlight, 30%);
|
stroke: $color_background_light;
|
||||||
stroke-width: 2;
|
stroke-width: 2;
|
||||||
|
|
||||||
&.hexagon {
|
&.hexagon {
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
width="100%"
|
width="100%"
|
||||||
height="100%"
|
height="100%"
|
||||||
|
|
||||||
viewBox="0 0 100% 100%"
|
viewBox="0 0 100 100"
|
||||||
preserveAspectRatio="xMinYMin meet"
|
preserveAspectRatio="xMinYMin meet"
|
||||||
id="{{ content.dataset.ref_id }}"
|
id="{{ content.dataset.ref_id }}"
|
||||||
class="plot {{ content.__class__.__name__.lower()}}"
|
class="plot {{ content.__class__.__name__.lower()}}"
|
||||||
|
@ -57,7 +57,7 @@ class UploadForm(poobrains.auth.AutoForm):
|
|||||||
upload_file = self.fields['upload'].value
|
upload_file = self.fields['upload'].value
|
||||||
filename = self.fields['filename'].value
|
filename = self.fields['filename'].value
|
||||||
|
|
||||||
if filename != '':
|
if filename is not '':
|
||||||
|
|
||||||
file_path = os.path.join(self.instance.path, filename)
|
file_path = os.path.join(self.instance.path, filename)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user