Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/src/api/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export const ledgerDataValidator = object({
payees: array(string),
precisions: record(number),
sidebar_links: array(tuple(string, string)),
filter_presets: array(tuple(string, string, string)),
tags: array(string),
upcoming_events_count: number,
user_queries: array(object({ name: string, query_string: string })),
Expand Down
51 changes: 51 additions & 0 deletions frontend/src/sidebar/AdvancedFilterPresetLink.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<script lang="ts">
import { filter_params, fql_filter } from "../stores/filters";

interface Props {
value: string;
label: string;
}

let { value, label }: Props = $props();

// Track if the filter is active (present in the current filter as a substring)
let isActive = $derived($filter_params.filter.includes(value));

function setAdvancedFilter() {
const currentFilter = $filter_params.filter;

// Check if the value is already a substring of the current filter
if (currentFilter.includes(value)) {
// If it's already in the filter, remove it
const newFilter = currentFilter.replace(value, "").trim();

fql_filter.set(newFilter);
} else {
// If it's not in the filter, append it
// If the current filter is empty, just set the value
if (!currentFilter) {
fql_filter.set(value);
} else {
// Otherwise, append space-separated value
fql_filter.set(`${currentFilter} ${value}`);
}
}
}
</script>

<a href="#adv" onclick={setAdvancedFilter} class:active={isActive}>{label}</a>

<style>
a:link {
color: #66c4ff;
}

a:link:hover {
color: #737373;
}

a.active {
font-weight: bold;
text-decoration: underline;
}
</style>
57 changes: 57 additions & 0 deletions frontend/src/sidebar/FilterPresets.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<script lang="ts">
import { ledgerData } from "../stores";
import AdvancedFilterPresetLink from "./AdvancedFilterPresetLink.svelte";
import TimeFilterPresetLink from "./TimeFilterPresetLink.svelte";

let filter_presets = $derived($ledgerData.filter_presets);
let time_presets = $derived(
filter_presets.filter(([type]) => type === "time"),
);
let advanced_presets = $derived(
filter_presets.filter(([type]) => type === "advanced"),
);
</script>

<div class="filter-presets">
<div class="filter-group">
<span class="filter-presets-label">🕒</span>
<!-- eslint-disable-next-line @typescript-eslint/no-unused-vars -->
{#each time_presets as [_, value, label], index}
{#if index > 0}
<span class="separator">·</span>
{/if}
<TimeFilterPresetLink {value} {label} />
{/each}
</div>

<div class="filter-group">
<span class="filter-presets-label">🎚</span>
<!-- eslint-disable-next-line @typescript-eslint/no-unused-vars -->
{#each advanced_presets as [_, value, label], index}
{#if index > 0}
<span class="separator">·</span>
{/if}
<AdvancedFilterPresetLink {value} {label} />
{/each}
</div>
</div>

<style>
.filter-presets {
display: flex;
justify-content: space-between;
width: 100%;
padding-top: 0.5rem;
padding-right: 0.5rem;
padding-left: 0.5rem;

/* background-color: var(--header-placeholder-background); */
border-top: 1px dotted var(--header-placeholder-background);
}

.filter-group {
display: flex;
gap: 0.5rem;
align-items: center;
}
</style>
5 changes: 5 additions & 0 deletions frontend/src/sidebar/Header.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import router from "../router";
import { ledger_title, ledgerData } from "../stores";
import FilterForm from "./FilterForm.svelte";
import FilterPresets from "./FilterPresets.svelte";
import HeaderIcon from "./HeaderIcon.svelte";
import { has_changes } from "./page-title";
import PageTitle from "./PageTitle.svelte";

let other_ledgers = $derived($ledgerData.other_ledgers);
let has_dropdown = $derived(other_ledgers.length);
let has_filter_presets = $derived($ledgerData.filter_presets.length > 0);
</script>

<header>
Expand Down Expand Up @@ -38,6 +40,9 @@
</button>
<span class="spacer"></span>
<FilterForm />
{#if has_filter_presets}
<FilterPresets />
{/if}
</header>

<style>
Expand Down
37 changes: 37 additions & 0 deletions frontend/src/sidebar/TimeFilterPresetLink.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<script lang="ts">
import { filter_params, time_filter } from "../stores/filters";

interface Props {
value: string;
label: string;
}

let { value, label }: Props = $props();

let isActive = $derived($filter_params.time === value);

function setTimeFilter() {
if (isActive) {
time_filter.set("");
} else {
time_filter.set(value);
}
}
</script>

<a href="#time" onclick={setTimeFilter} class:active={isActive}>{label}</a>

<style>
a:link {
color: #66c4ff;
}

a:link:hover {
color: #737373;
}

a.active {
font-weight: bold;
text-decoration: underline;
}
</style>
22 changes: 22 additions & 0 deletions src/fava/core/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,13 @@ def __init__(self, ledger: FavaLedger) -> None:
self.sidebar_links: SidebarLinks = []
#: Upcoming events in the next few days.
self.upcoming_events: Sequence[Event] = []
#: User-defined filter presets.
self.filter_presets: Sequence[tuple[str, str, str]] = []

def load_file(self) -> None: # noqa: D102
custom_entries = self.ledger.all_entries_by_type.Custom
self.sidebar_links = sidebar_links(custom_entries)
self.filter_presets = filter_presets(custom_entries)

self.upcoming_events = upcoming_events(
self.ledger.all_entries_by_type.Event,
Expand Down Expand Up @@ -76,6 +79,25 @@ def sidebar_links(custom_entries: Sequence[Custom]) -> SidebarLinks:
]


def filter_presets(
custom_entries: Sequence[Custom],
) -> Sequence[tuple[str, str, str]]:
"""Parse custom entries for filter presets.

They have the following format:

2016-04-01 custom "fava-filter-preset" "time" "month" "Current Month"
2016-04-01 custom "fava-filter-preset" "advanced" "-#tag" "Exclude #tag"
"""
filter_preset_entries = [
entry for entry in custom_entries if entry.type == "fava-filter-preset"
]
return [
(entry.values[0].value, entry.values[1].value, entry.values[2].value)
for entry in filter_preset_entries
]


def upcoming_events(
events: Sequence[Event], max_delta: int
) -> Sequence[Event]:
Expand Down
2 changes: 2 additions & 0 deletions src/fava/internal_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class LedgerData:
extensions: Sequence[ExtensionDetails]
sidebar_links: Sequence[tuple[str, str]]
other_ledgers: Sequence[tuple[str, str]]
filter_presets: Sequence[tuple[str, str, str]]


def get_errors() -> list[SerialisedError]:
Expand Down Expand Up @@ -128,6 +129,7 @@ def get_ledger_data() -> LedgerData:
for (file_slug, ledger) in current_app.config["LEDGERS"].items()
if file_slug != g.beancount_file_slug
],
ledger.misc.filter_presets,
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,7 @@
"uptodate_indicator_grey_lookback_days": 60,
"use_external_editor": false
},
"filter_presets": [],
"have_excel": true,
"incognito": false,
"links": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,7 @@
"uptodate_indicator_grey_lookback_days": 60,
"use_external_editor": false
},
"filter_presets": [],
"have_excel": true,
"incognito": false,
"links": [
Expand Down