edit settings dynamically
This commit is contained in:
parent
ced259d7e8
commit
d07503dd6f
|
@ -0,0 +1,111 @@
|
|||
(function(){
|
||||
if(!('fetch' in window)){
|
||||
return;
|
||||
}
|
||||
|
||||
let status_timeout = null;
|
||||
|
||||
let form = document.forms.settings;
|
||||
let backoff_level = 0
|
||||
|
||||
function hide_status(){
|
||||
status_display.classList.remove('error', 'success', 'saving');
|
||||
status_display.classList.add('hidden');
|
||||
status_display.innerHTML='';
|
||||
}
|
||||
function show_error(e){
|
||||
hide_status();
|
||||
status_display.textContent='Could not save. Retrying...';
|
||||
status_display.classList.add('error');
|
||||
status_display.classList.remove('hidden');
|
||||
}
|
||||
function show_success(){
|
||||
hide_status();
|
||||
status_display.textContent='Saved!';
|
||||
status_display.classList.add('success');
|
||||
status_display.classList.remove('hidden');
|
||||
}
|
||||
function show_saving(){
|
||||
hide_status();
|
||||
status_display.textContent='Saving...';
|
||||
status_display.classList.add('saving');
|
||||
status_display.classList.remove('hidden');
|
||||
status_timeout = setTimeout(show_still_saving, 5000);
|
||||
}
|
||||
function show_still_saving(){
|
||||
status_display.textContent='Still saving...';
|
||||
}
|
||||
|
||||
function on_change(e){
|
||||
hide_status();
|
||||
clearTimeout(status_timeout);
|
||||
status_timeout = setTimeout(show_saving, 70);
|
||||
|
||||
send_settings(get_all_inputs())
|
||||
.then(data => {
|
||||
show_success();
|
||||
clearTimeout(status_timeout);
|
||||
status_timeout = setTimeout(hide_status, 3000);
|
||||
backoff_level = 0;
|
||||
})
|
||||
.catch(e => {
|
||||
console.error('Fetch rejected:', e);
|
||||
show_error();
|
||||
clearTimeout(status_timeout);
|
||||
status_timeout = setTimeout(save, Math.pow(2, backoff_level)*1000);
|
||||
backoff_level += 1;
|
||||
backoff_level = Math.min(backoff_level, 5);
|
||||
});
|
||||
|
||||
// remove server-rendered banner
|
||||
let settings_section = document.querySelector('#settings-section');
|
||||
let banner = settings_section.querySelector('.banner');
|
||||
if(banner){
|
||||
settings_section.removeChild(banner);
|
||||
}
|
||||
}
|
||||
|
||||
function get_all_inputs(){
|
||||
let o = Object()
|
||||
for(input of form.elements){
|
||||
if(input.type != 'radio' || input.checked){
|
||||
o[input.name] = input.value;
|
||||
}
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
function send_settings(body){
|
||||
return fetch('/api/settings', {
|
||||
method:'PUT',
|
||||
credentials:'same-origin',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
.then(resp => { if(!resp.ok){ return Promise.reject(resp) }; return resp; })
|
||||
.then(resp => resp.json())
|
||||
.then(data => {
|
||||
if(data.status == 'error'){ return Promise.reject(data) }
|
||||
return data
|
||||
});
|
||||
}
|
||||
|
||||
for(input of form.elements){
|
||||
input.addEventListener('change', on_change);
|
||||
input.addEventListener('change', e=>console.log(e.target));
|
||||
}
|
||||
|
||||
// remove submit button since we're doing live updates
|
||||
let submit = form.querySelector('input[type=submit]');
|
||||
form.removeChild(submit);
|
||||
|
||||
let status_display = document.createElement('span');
|
||||
status_display.classList.add('status-display', 'hidden');
|
||||
let settings_title = document.querySelector('#settings-title');
|
||||
settings_title.appendChild(status_display);
|
||||
|
||||
// silently send_settings in case the user changed settings while the page was loading
|
||||
send_settings(get_all_inputs());
|
||||
})();
|
|
@ -82,17 +82,17 @@ body > section > .banner {
|
|||
background: #eee;
|
||||
}
|
||||
|
||||
.banner.success {
|
||||
.success {
|
||||
border-color: #4a2;
|
||||
background: #dec;
|
||||
border-left-color: #4a2;
|
||||
}
|
||||
|
||||
.banner.error {
|
||||
.error {
|
||||
border-color: #a24;
|
||||
background: #ecd;
|
||||
}
|
||||
|
||||
.banner.warning {
|
||||
.warning {
|
||||
border-color: #dc4;
|
||||
background: #eec;
|
||||
}
|
||||
|
@ -126,3 +126,57 @@ footer a {
|
|||
color: inherit;
|
||||
}
|
||||
|
||||
.status-display {
|
||||
font-size: 0.8rem;
|
||||
display: inline-block;
|
||||
margin-left: 1ch;
|
||||
padding: 0 0.4em;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.status-display.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.status-display.error {
|
||||
background: #ecd;
|
||||
}
|
||||
|
||||
.status-display.success {
|
||||
display: inline-block;
|
||||
background: #dec;
|
||||
animation: fade-background 2s forwards;
|
||||
}
|
||||
|
||||
@keyframes fade-background {
|
||||
to {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.status-display::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
width: 0.8em;
|
||||
height: 0.8em;
|
||||
border-color: inherit;
|
||||
border-style: solid;
|
||||
border-width: 0.4em;
|
||||
margin-right: 0.3em;
|
||||
|
||||
}
|
||||
.status-display.saving::before {
|
||||
border-color: #666;
|
||||
border-width: 2px;
|
||||
animation: blink infinite 0.6s steps(1) normal;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
2
dodo.py
2
dodo.py
|
@ -23,7 +23,7 @@ def task_gen_logo():
|
|||
|
||||
def task_copy_asset():
|
||||
import shutil
|
||||
assets = ('icon.png', 'logotype.png', 'version.js')
|
||||
assets = ('icon.png', 'logotype.png', 'version.js', 'settings_form.js')
|
||||
for asset in assets:
|
||||
yield dict(
|
||||
name=asset,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from .auth import require_auth
|
||||
from . import auth
|
||||
from .interval import decompose_interval
|
||||
from .interval import SCALES as interval_scales
|
||||
from .cachebust import cachebust
|
||||
from .session import set_session_cookie, get_viewer_session, get_viewer
|
||||
from . import brotli
|
||||
from . import settings
|
||||
|
|
20
lib/auth.py
20
lib/auth.py
|
@ -1,18 +1,20 @@
|
|||
from flask import g, redirect
|
||||
from flask import g, redirect, jsonify, make_response
|
||||
from functools import wraps
|
||||
|
||||
def require_auth(fun, redir=True):
|
||||
|
||||
from functools import update_wrapper
|
||||
def require_auth(fun):
|
||||
@wraps(fun)
|
||||
def wrapper(*args, **kwargs):
|
||||
if not g.viewer:
|
||||
if redir:
|
||||
return redirect('/')
|
||||
else:
|
||||
return 403
|
||||
else:
|
||||
return fun(*args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
update_wrapper(wrapper, fun)
|
||||
def require_auth_api(fun):
|
||||
@wraps(fun)
|
||||
def wrapper(*args, **kwargs):
|
||||
if not g.viewer:
|
||||
return make_response((jsonify(status='error', error='not logged in'), 403))
|
||||
return fun(*args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
attrs = (
|
||||
'policy_keep_favourites',
|
||||
'policy_keep_latest',
|
||||
'policy_delete_every_scale',
|
||||
'policy_delete_every_significand',
|
||||
'policy_keep_younger_scale',
|
||||
'policy_keep_younger_significand',
|
||||
'policy_keep_media',
|
||||
)
|
28
routes.py
28
routes.py
|
@ -2,9 +2,9 @@ from flask import render_template, url_for, redirect, request, g, Response, json
|
|||
from datetime import datetime, timedelta
|
||||
import lib.twitter
|
||||
import lib
|
||||
from lib import require_auth
|
||||
from lib.auth import require_auth, require_auth_api
|
||||
from lib import set_session_cookie
|
||||
from lib import get_viewer_session
|
||||
from lib import get_viewer_session, get_viewer
|
||||
from model import Account, Session, Post, TwitterArchive
|
||||
from app import app, db, sentry, limiter
|
||||
import tasks
|
||||
|
@ -13,6 +13,7 @@ from twitter import TwitterError
|
|||
from urllib.error import URLError
|
||||
import version
|
||||
import lib.brotli
|
||||
import lib.settings
|
||||
|
||||
@app.before_request
|
||||
def load_viewer():
|
||||
|
@ -111,15 +112,7 @@ def upload_tweet_archive():
|
|||
@app.route('/settings', methods=('POST',))
|
||||
@require_auth
|
||||
def settings():
|
||||
for attr in (
|
||||
'policy_keep_favourites',
|
||||
'policy_keep_latest',
|
||||
'policy_delete_every_scale',
|
||||
'policy_delete_every_significand',
|
||||
'policy_keep_younger_scale',
|
||||
'policy_keep_younger_significand',
|
||||
'policy_keep_media',
|
||||
):
|
||||
for attr in lib.settings.attrs:
|
||||
try:
|
||||
if attr in request.form:
|
||||
setattr(g.viewer.account, attr, request.form[attr])
|
||||
|
@ -174,3 +167,16 @@ def logout():
|
|||
@app.route('/api/about')
|
||||
def api_about():
|
||||
return jsonify(service='Forget', version=version.version)
|
||||
|
||||
@app.route('/api/settings', methods=('PUT',))
|
||||
@require_auth_api
|
||||
def api_settings_put():
|
||||
viewer = get_viewer()
|
||||
data = request.json
|
||||
updated = dict()
|
||||
for key in lib.settings.attrs:
|
||||
if key in data:
|
||||
setattr(viewer, key, data[key])
|
||||
updated[key] = data[key]
|
||||
db.session.commit()
|
||||
return jsonify(status='success', updated=updated)
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
onload="Raven.config('{{sentry_dsn}}').install()"></script>
|
||||
{% endif -%}
|
||||
<script defer src="{{ st('version.js') }}"></script>
|
||||
{% block scripts %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
{% from 'lib/interval.html' import interval_input -%}
|
||||
{% extends 'lib/layout.html' %}
|
||||
{% block scripts %}
|
||||
<script async src="{{st('settings_form.js')}}"></script>
|
||||
{% endblock %}
|
||||
{% block body -%}
|
||||
|
||||
<section class=viewer>
|
||||
|
@ -30,8 +33,8 @@
|
|||
{% endif %}
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Your expiration rules</h2>
|
||||
<section id='settings-section'>
|
||||
<h2 id="settings-title">Your expiration rules</h2>
|
||||
|
||||
{% if request.args.get('settings_saved') != None %}
|
||||
<div class='banner success'>Rules saved successfully</div>
|
||||
|
@ -41,7 +44,7 @@
|
|||
<div class='banner error'>Something went wrong trying to save your settings. Please try again later.</div>
|
||||
{% endif %}
|
||||
|
||||
<form action='{{url_for("settings")}}' method='post' enctype='multipart/form-data'>
|
||||
<form name='settings' action='{{url_for("settings")}}' method='post' enctype='multipart/form-data'>
|
||||
<p>Posts that are over
|
||||
{{interval_input(g.viewer.account, 'policy_keep_younger', scales)}}
|
||||
old and are not one of your
|
||||
|
|
Loading…
Reference in New Issue