Skip to content

Commit

Permalink
implement journal filters as svelte component
Browse files Browse the repository at this point in the history
  • Loading branch information
yagebu committed Feb 6, 2022
1 parent 9a503e4 commit 16cdcd4
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 106 deletions.
4 changes: 0 additions & 4 deletions frontend/css/journal-table.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
/* stylelint-disable no-descending-specificity */

.entry-filters {
justify-content: flex-end;
}

.journal {
margin-top: 0.25rem;
}
Expand Down
82 changes: 82 additions & 0 deletions frontend/src/journal/JournalFilters.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<script lang="ts" context="module">
import type { Writable } from "svelte/store";
import { _, format } from "../i18n";
import { keyboardShortcut } from "../keyboard-shortcuts";
const toggleText = _("Toggle %(type)s entries");
/**
* This is the list of all toggle buttons to show.
* For some entry types we have subtypes (like 'cleared' for 'transaction'),
* which get special-cased in the toggle logic below.
*/
const buttons: [
type: string,
button_text: string,
title: string | null,
shortcut: string,
supertype?: string
][] = [
["open", "Open", null, "s o"],
["close", "Close", null, "s c"],
["transaction", "Transaction", null, "s t"],
["cleared", "*", _("Cleared transactions"), "t c", "transaction"],
["pending", "!", _("Pending transactions"), "t p", "transaction"],
["other", "x", _("Other transactions"), "t o", "transaction"],
["balance", "Balance", null, "s b"],
["note", "Note", null, "s n"],
["document", "Document", null, "s d"],
[
"discovered",
"D",
_("Documents with a #discovered tag"),
"d d",
"document",
],
["linked", "L", _("Documents with a #linked tag"), "d l", "document"],
["pad", "Pad", null, "s p"],
["query", "Query", null, "s q"],
["custom", "Custom", null, "s C"],
["budget", "B", _("Budget entries"), "s B", "custom"],
["metadata", _("Metadata"), "Toggle metadata", "m"],
["postings", _("Postings"), "Toggle postings", "p"],
];
</script>

<script lang="ts">
export let show: Writable<Set<string>>;
function toggle(type: string): void {
const toggle_func = $show.has(type)
? $show.delete.bind($show)
: $show.add.bind($show);
toggle_func(type);
// Also toggle all entries that have `type` as their supertype.
buttons.filter((b) => b[4] === type).forEach((b) => toggle_func(b[0]));
$show = $show;
}
$: active = (type: string, supertype?: string): boolean =>
supertype ? $show.has(supertype) && $show.has(type) : $show.has(type);
</script>

<form class="flex-row">
{#each buttons as [type, button_text, title, shortcut, supertype]}
<button
type="button"
title={title ?? format(toggleText, { type: button_text })}
use:keyboardShortcut={shortcut}
class:inactive={!active(type, supertype)}
on:click={() => toggle(type)}
>
{button_text}
</button>
{/each}
</form>

<style>
form {
justify-content: flex-end;
}
</style>
95 changes: 47 additions & 48 deletions frontend/src/journal.ts → frontend/src/journal/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { delegate } from "./lib/events";
import { log_error } from "./log";
import router from "./router";
import { sortableJournal } from "./sort";
import { fql_filter } from "./stores/filters";
import type { SvelteComponent } from "svelte";
import { get, writable } from "svelte/store";

import { shallow_equal } from "../lib/equals";
import { delegate } from "../lib/events";
import router from "../router";
import { sortableJournal } from "../sort";
import { favaOptions } from "../stores";
import { fql_filter } from "../stores/filters";

import JournalFilters from "./JournalFilters.svelte";

/**
* Escape the value to produce a valid regex.
Expand Down Expand Up @@ -68,57 +74,50 @@ function handleClick({ target }: MouseEvent): void {
}

export class FavaJournal extends HTMLElement {
constructor() {
super();
component?: SvelteComponent;

unsubscribe?: () => void;

connectedCallback() {
const opts = get(favaOptions);
const defaults = [
...opts.journal_show,
...opts.journal_show_transaction,
...opts.journal_show_document,
].sort();

const ol = this.querySelector("ol");
const form = this.querySelector("form");
if (!ol || !form) {
if (!ol) {
throw new Error("fava-journal is missing its <ol> or <form>");
}
sortableJournal(ol);
delegate(this, "click", "li", handleClick);

const entryButtons = form.querySelectorAll("button");
// Toggle entries with buttons.
entryButtons.forEach((button) => {
button.addEventListener("click", () => {
const type = button.getAttribute("data-type");
if (!type) {
log_error("Button is missing type: ", button);
return;
}
const shouldShow = button.classList.contains("inactive");

button.classList.toggle("inactive", !shouldShow);
if (
type === "transaction" ||
type === "custom" ||
type === "document"
) {
form.querySelectorAll(`.${type}-toggle`).forEach((el) => {
el.classList.toggle("inactive", !shouldShow);
});
}

ol.classList.toggle(`show-${type}`, shouldShow);
const url_show = new URL(window.location.href).searchParams.getAll("show");
const show = writable(new Set(url_show.length ? url_show : defaults));
this.unsubscribe = show.subscribe((show_value) => {
const classes = [...show_value].map((s) => `show-${s}`).join(" ");
ol.className = `flex-table journal ${classes}`;

// Modify get params
const filterShow: string[] = [];
entryButtons.forEach((el) => {
const datatype = el.getAttribute("data-type");
if (datatype && !el.classList.contains("inactive")) {
filterShow.push(datatype);
}
});

const url = new URL(window.location.href);
url.searchParams.delete("show");
filterShow.forEach((filter) => {
const url = new URL(window.location.href);
url.searchParams.delete("show");
if (!shallow_equal([...show_value].sort(), defaults)) {
show_value.forEach((filter) => {
url.searchParams.append("show", filter);
});
router.navigate(url.toString(), false);
});
}
router.navigate(url.toString(), false);
});
this.component = new JournalFilters({
target: this,
props: { show },
anchor: ol,
});

sortableJournal(ol);
delegate(this, "click", "li", handleClick);
}

disconnectedCallback(): void {
this.unsubscribe?.();
this.component?.$destroy();
}
}
3 changes: 3 additions & 0 deletions frontend/src/stores/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export const ledgerDataValidator = object({
insert_entry: array(
object({ date: string, filename: string, lineno: number, re: string })
),
journal_show: array(string),
journal_show_document: array(string),
journal_show_transaction: array(string),
}),
have_excel: boolean,
incognito: boolean,
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/svelte-custom-elements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const components = new Map([
/**
* A custom element that represents a Svelte component.
*
* The tag should have a `data-component` attribute with one
* The tag should have a `type` attribute with one
* of the valid values in the Map above.
*/
export class SvelteCustomElement extends HTMLElement {
Expand Down
54 changes: 1 addition & 53 deletions src/fava/templates/_journal_table.html
Original file line number Diff line number Diff line change
@@ -1,52 +1,12 @@
{% import 'macros/_commodity_macros.html' as commodity_macros %}


{% set entry_types = ['open', 'close', 'transaction', 'balance', 'note', 'document', 'pad', 'query', 'custom'] %}
{% set sub_types = {
'custom': (
('budget', 'B', _('Budget entries')),
),
'document': (
('discovered', 'D', _('Documents with a #discovered tag')),
('linked', 'L', _('Documents with a #linked tag')),
),
'transaction': (
('cleared', '*', _('Cleared transactions')),
('pending', '!', _('Pending transactions')),
('other', 'x', _('Other transactions')),
),
} %}
{% set keyboard_shortcuts = {
'open': 's o',
'close': 's c',
'balance': 's b',
'note': 's n',
'pad': 's p',
'query': 's q',
'custom': 's C',
'budget': 's B',

'transaction': 's t',
'cleared': 't c',
'pending': 't p',
'other': 't o',

'document': 's d',
'discovered': 'd d',
'linked': 'd l',
} %}
{% set short_type = {
'balance': 'Bal',
'close': 'Close',
'document': 'Doc',
'note': 'Note',
'open': 'Open',
} %}
{% if not request.args.get('show') %}
{% set journal_show = g.ledger.fava_options.journal_show + g.ledger.fava_options.journal_show_transaction + g.ledger.fava_options.journal_show_document %}
{% else %}
{% set journal_show = request.args.getlist('show') %}
{% endif %}

{% macro account_link(name) %}<a href="{{ url_for('account', name=name) }}">{{ name }}</a>{% endmacro %}
{% macro render_metadata_indicators(metadata) -%}
Expand Down Expand Up @@ -78,19 +38,7 @@
{% macro journal_table(entries, show_change_and_balance=False) %}
{% autoescape false %}
<fava-journal>
<form class="flex-row entry-filters">
{% for type in entry_types %}
<button type="button" title="Toggle {{ type|capitalize }} entries" data-type="{{ type }}" data-keyboard-shortcut="{{ keyboard_shortcuts[type] }}"{% if type not in journal_show %} class="inactive"{% endif %}>{{ type|capitalize }}</button>
{% if type in sub_types.keys() %}
{% for sub_type in sub_types[type] %}
<button type="button" title="{{ sub_type.2 }}" data-type="{{ sub_type.0 }}" data-keyboard-shortcut="{{ keyboard_shortcuts[sub_type.0] }}" class="small {{ type }}-toggle{% if sub_type.0 not in journal_show or type not in journal_show %} inactive{% endif %}">{{ sub_type.1 }}</button>
{% endfor %}
{% endif %}
{% endfor %}
<button type="button" title="Toggle metadata" data-type="metadata" data-keyboard-shortcut="m"{% if 'metadata' not in journal_show %} class="inactive"{% endif %}>{{ _('Metadata') }}</button>
<button type="button" title="Toggle postings" data-type="postings" data-keyboard-shortcut="p"{% if 'postings' not in journal_show %} class="inactive"{% endif %}>{{ _('Postings') }}</button>
</form>
<ol class="flex-table journal{% for type in journal_show %} show-{{ type }}{% endfor %}">
<ol class="flex-table journal">
<li class="head">
<p>
<span class="datecell" data-sort="num" data-order="desc">{{ _('Date') }}</span>
Expand Down

0 comments on commit 16cdcd4

Please sign in to comment.