Skip to content

Commit

Permalink
Issue 141: make shortcuts customizable.
Browse files Browse the repository at this point in the history
  • Loading branch information
jzohrab committed Oct 19, 2024
1 parent 7f14906 commit b901191
Show file tree
Hide file tree
Showing 13 changed files with 327 additions and 162 deletions.
4 changes: 0 additions & 4 deletions lute/app_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,6 @@ def show_version():
is_docker=ac.is_docker,
)

@app.route("/hotkeys")
def show_hotkeys():
return render_template("hotkeys.html")

@app.route("/info")
def show_info():
"""
Expand Down
4 changes: 3 additions & 1 deletion lute/dev_api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ def load_demo_languages():
lute.db.demo.load_demo_languages()
langs = db.session.query(Language).all()
for lang in langs:
lang.dictionaries[0].dicturi = f"/dev_api/dummy_dict/{lang.name}/###"
d = lang.dictionaries[0]
d.dicturi = f"/dev_api/dummy_dict/{lang.name}/###"
d.dicttype = "embeddedhtml" # Ensure not pop-up
db.session.add(lang)
db.session.commit()
return redirect("/", 302)
Expand Down
21 changes: 21 additions & 0 deletions lute/models/setting.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,27 @@ def load():
"open_popup_in_new_tab": False,
"stop_audio_on_term_form_open": True,
"stats_calc_sample_size": 5,
# Keyboard shortcuts:
"hotkey_StartHover": "escape",
"hotkey_PrevWord": "arrowleft",
"hotkey_NextWord": "arrowright",
"hotkey_StatusUp": "arrowup",
"hotkey_StatusDown": "arrowdown",
"hotkey_Bookmark": "b",
"hotkey_CopySentence": "c",
"hotkey_CopyPara": "shift+c",
"hotkey_TranslateSentence": "t",
"hotkey_TranslatePara": "shift+t",
"hotkey_NextTheme": "m",
"hotkey_ToggleHighlight": "h",
"hotkey_ToggleFocus": "f",
"hotkey_Status1": "1",
"hotkey_Status2": "2",
"hotkey_Status3": "3",
"hotkey_Status4": "4",
"hotkey_Status5": "5",
"hotkey_StatusIgnore": "i",
"hotkey_StatusWellKnown": "w",
}
for k, v in keys_and_defaults.items():
if not UserSetting.key_exists(k):
Expand Down
98 changes: 98 additions & 0 deletions lute/settings/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,101 @@ def set_key_value(key, value):
result = {"result": "failure", "message": message}
db.session.commit()
return jsonify(result)


class UserShortcutsForm(FlaskForm):
"""
Shortcuts form.
The route manages getting and storing the settings
from the db, as there's a variable number of settings,
and it's easier to just work with the data directly
rather than trying to create a variable number of fields.
I'm only using this form to get the validate_on_submit()!
There's likely a better way to do this.
"""


def _get_categorized_hotkeys():
"""
Return hotkey UserSetting keys and values,
grouped by category.
"""

categorized_settings = {
"Navigation": ["hotkey_StartHover", "hotkey_PrevWord", "hotkey_NextWord"],
"Update status": [
"hotkey_Status1",
"hotkey_Status2",
"hotkey_Status3",
"hotkey_Status4",
"hotkey_Status5",
"hotkey_StatusIgnore",
"hotkey_StatusWellKnown",
"hotkey_StatusUp",
"hotkey_StatusDown",
],
"Misc": [
"hotkey_Bookmark",
"hotkey_CopySentence",
"hotkey_CopyPara",
"hotkey_TranslateSentence",
"hotkey_TranslatePara",
"hotkey_NextTheme",
"hotkey_ToggleHighlight",
"hotkey_ToggleFocus",
],
}

settings = {h.key: h.value for h in db.session.query(UserSetting).all()}
return {
category: {k: settings[k] for k in keylist}
for category, keylist in categorized_settings.items()
}


@bp.route("/shortcuts", methods=["GET", "POST"])
def edit_shortcuts():
"Edit shortcuts."
form = UserShortcutsForm()
if form.validate_on_submit():
# print(request.form, flush=True)
# Update the settings in the database
for k, v in request.form.items():
# print(f"{k} = {v}", flush=True)
UserSetting.set_value(k, v)
db.session.commit()
flash("Shortcuts updated", "success")
return redirect("/")

categorized_settings = _get_categorized_hotkeys()

setting_descs = {
"hotkey_StartHover": "Deselect all words",
"hotkey_PrevWord": "Move to previous word",
"hotkey_NextWord": "Move to next word",
"hotkey_StatusUp": "Bump the status up by 1",
"hotkey_StatusDown": "Bump that status down by 1",
"hotkey_Bookmark": "Bookmark the current page",
"hotkey_CopySentence": "Copy the sentence of the current word",
"hotkey_CopyPara": "Copy the paragraph of the current word",
"hotkey_TranslateSentence": "Translate the sentence of the current word",
"hotkey_TranslatePara": "Translate the paragraph of the current word",
"hotkey_NextTheme": "Change to the next theme",
"hotkey_ToggleHighlight": "Toggle highlights",
"hotkey_ToggleFocus": "Toggle focus mode",
"hotkey_Status1": "Set status to 1",
"hotkey_Status2": "Set status to 2",
"hotkey_Status3": "Set status to 3",
"hotkey_Status4": "Set status to 4",
"hotkey_Status5": "Set status to 5",
"hotkey_StatusIgnore": "Set status to Ignore",
"hotkey_StatusWellKnown": "Set status to Well Known",
}

return render_template(
"settings/shortcuts.html",
setting_descs=setting_descs,
categorized_settings=categorized_settings,
)
17 changes: 17 additions & 0 deletions lute/static/css/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -1209,6 +1209,23 @@ table input:not([type="checkbox"]) {
width: 20rem;
}

#shortcutstable th {
text-align: left;
}

#shortcutstable .settingcategory {
font-size: 1.2em;
font-weight: bold;
}

#shortcutstable input[type=text] {
max-width: 8rem;
}

.dupShortcut {
background-color: #FFCCCB;
}

#book td:first-child,
#language td:first-child {
width: 15rem;
Expand Down
34 changes: 34 additions & 0 deletions lute/static/js/lute-hotkey-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Get the pressed keys as a string, eg 'meta-c', 'shift-a'.
*
* Note that there _must_ be a "regular" key pressed as well.
* If only meta/alt/ctl/shift are pressed, returns null.
*/
function get_pressed_keys_as_string(event) {
const keys = [];

// Check for modifier keys
if (event.ctrlKey) keys.push('ctrl');
if (event.shiftKey) keys.push('shift');
if (event.altKey) keys.push('alt');
if (event.metaKey) keys.push('meta');

// Map special keys to names if needed
const keyMap = {
' ': 'space'
};

if (event.key == null) {
// window.alert("no key for event?");
return null;
}

const actual_key = keyMap[event.key] || event.key.toLowerCase();
if (['shift', 'ctrl', 'alt', 'meta'].includes(actual_key))
return null

keys.push(actual_key);
const ret = keys.join('+');
// window.alert(`got hotkey = ${ret}`);
return ret;
}
99 changes: 26 additions & 73 deletions lute/static/js/lute.js
Original file line number Diff line number Diff line change
Expand Up @@ -775,33 +775,6 @@ function add_page_after() {
}


function getKeyString(event) {
const keys = [];

// Check for modifier keys
if (event.ctrlKey) keys.push('ctrl');
if (event.shiftKey) keys.push('shift');
if (event.altKey) keys.push('alt');
if (event.metaKey) keys.push('meta');

// Map special keys to names if needed
const keyMap = {
' ': 'space'
};

const key = keyMap[event.key] || event.key.toLowerCase();

// If it's a normal key (not a modifier), add it to the keys array
if (!['shift', 'ctrl', 'alt', 'meta'].includes(key)) {
keys.push(key);
}

const ret = keys.join('+');
console.log(`Got keydown = ${ret}`);
return ret;
}


function get_right_increment() {
// read/index.js has some data rendered at the top of the page.
const lang_is_rtl = $('#lang_is_rtl');
Expand All @@ -819,55 +792,35 @@ function handle_keydown (e) {
return; // Nothing to do.
}

// User hotkeys, to be read from UserSettings.
const user_keys = {
'HotkeyStartHover': 'escape',
'HotkeyPrevWord': 'arrowleft',
'HotkeyNextWord': 'arrowright',
'HotkeyStatusUp': 'arrowup',
'HotkeyStatusDown': 'arrowdown',
'HotkeyBookmark': 'b',
'HotkeyCopySentence': 'c',
'HotkeyCopyPara': 'shift+c',
'HotkeyTranslateSentence': 't',
'HotkeyTranslatePara': 'shift+t',
'HotkeyNextTheme': 'm',
'HotkeyToggleHighlight': 'h',
'HotkeyToggleFocus': 'f',
'HotkeyStatus1': '1',
'HotkeyStatus2': '2',
'HotkeyStatus3': '3',
'HotkeyStatus4': '4',
'HotkeyStatus5': '5',
'HotkeyStatusIgnore': 'i',
'HotkeyStatusWellKnown': 'w',
};
// User hotkeys are stored in LUTE_USER_SETTINGS
// hash in global space.
const k = LUTE_USER_SETTINGS; // shorthand varname.

// Map of shortcuts to lambdas:
let map = {
[user_keys['HotkeyStartHover']]: () => start_hover_mode(),
[user_keys['HotkeyPrevWord']]: () => move_cursor(-1 * get_right_increment()),
[user_keys['HotkeyNextWord']]: () => move_cursor(get_right_increment()),
[user_keys['HotkeyStatusUp']]: () => increment_status_for_selected_elements(+1),
[user_keys['HotkeyStatusDown']]: () => increment_status_for_selected_elements(-1),
[user_keys['HotkeyBookmark']]: () => handle_bookmark(),
[user_keys['HotkeyCopySentence']]: () => handle_copy('sentence-id'),
[user_keys['HotkeyCopyPara']]: () => handle_copy('paragraph-id'),
[user_keys['HotkeyTranslateSentence']]: () => handle_translate('sentence-id'),
[user_keys['HotkeyTranslatePara']]: () => handle_translate('paragraph-id'),
[user_keys['HotkeyNextTheme']]: () => next_theme(),
[user_keys['HotkeyToggleHighlight']]: () => toggle_highlight(),
[user_keys['HotkeyToggleFocus']]: () => toggleFocus(),
[user_keys['HotkeyStatus1']]: () => update_status_for_marked_elements(1),
[user_keys['HotkeyStatus2']]: () => update_status_for_marked_elements(2),
[user_keys['HotkeyStatus3']]: () => update_status_for_marked_elements(3),
[user_keys['HotkeyStatus4']]: () => update_status_for_marked_elements(4),
[user_keys['HotkeyStatus5']]: () => update_status_for_marked_elements(5),
[user_keys['HotkeyStatusIgnore']]: () => update_status_for_marked_elements(98),
[user_keys['HotkeyStatusWellKnown']]: () => update_status_for_marked_elements(99),
}

const ks = getKeyString(e);
[k.hotkey_StartHover]: () => start_hover_mode(),
[k.hotkey_PrevWord]: () => move_cursor(-1 * get_right_increment()),
[k.hotkey_NextWord]: () => move_cursor(get_right_increment()),
[k.hotkey_StatusUp]: () => increment_status_for_selected_elements(+1),
[k.hotkey_StatusDown]: () => increment_status_for_selected_elements(-1),
[k.hotkey_Bookmark]: () => handle_bookmark(),
[k.hotkey_CopySentence]: () => handle_copy('sentence-id'),
[k.hotkey_CopyPara]: () => handle_copy('paragraph-id'),
[k.hotkey_TranslateSentence]: () => handle_translate('sentence-id'),
[k.hotkey_TranslatePara]: () => handle_translate('paragraph-id'),
[k.hotkey_NextTheme]: () => next_theme(),
[k.hotkey_ToggleHighlight]: () => toggle_highlight(),
[k.hotkey_ToggleFocus]: () => toggleFocus(),
[k.hotkey_Status1]: () => update_status_for_marked_elements(1),
[k.hotkey_Status2]: () => update_status_for_marked_elements(2),
[k.hotkey_Status3]: () => update_status_for_marked_elements(3),
[k.hotkey_Status4]: () => update_status_for_marked_elements(4),
[k.hotkey_Status5]: () => update_status_for_marked_elements(5),
[k.hotkey_StatusIgnore]: () => update_status_for_marked_elements(98),
[k.hotkey_StatusWellKnown]: () => update_status_for_marked_elements(99),
}

const ks = get_pressed_keys_as_string(e);
if (ks in map) {
// Override any existing event - e.g., if "up" arrow is in the map,
// don't scroll screen.
Expand Down
4 changes: 3 additions & 1 deletion lute/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
<script type="text/javascript" src="{{ url_for('static', filename='vendor/datatables/datatables.button.download.js') }}" charset="utf-8"></script>
<script type="text/javascript" src="{{ url_for('static', filename='vendor/jquery/jquery.hoverIntent.js') }}" charset="utf-8"></script>

<!-- this calls a route defined in app_factory to never cache this file -->
<!-- the "never_cache" calls a route defined in app_factory to never cache that particular .js file -->
<script type="text/javascript" src="/static/js/never_cache/lute-hotkey-utils.js" charset="utf-8"></script>
<script type="text/javascript" src="/static/js/never_cache/lute.js" charset="utf-8"></script>

<script type="text/javascript">
Expand Down Expand Up @@ -98,6 +99,7 @@ <h1 id="luteTitle" title="Learning Using Texts">
<ul class="sub-menu">
<li><a id="lang_index" href="/language/index">Languages</a></li>
<li><a href="/settings/index">Settings</a></li>
<li><a href="/settings/shortcuts">Keyboard shortcuts</a></li>
</ul>
</div>
{% if backup_enabled and backup_directory != '' %}
Expand Down
41 changes: 0 additions & 41 deletions lute/templates/hotkeys.html

This file was deleted.

Loading

0 comments on commit b901191

Please sign in to comment.