Add spice field handling

This commit is contained in:
Evan Fiordeliso 2025-06-30 14:20:25 -04:00
parent 20ffc78e8e
commit 113ddefac7
6 changed files with 119 additions and 27 deletions

View File

@ -302,7 +302,10 @@ export default class BookTrackerPlugin extends Plugin {
return;
}
const rating = await RatingModal.createAndOpen(this.app);
const rating = await RatingModal.createAndOpen(
this.app,
this.settings.spiceProperty !== ""
);
await this.readingLog.addEntry(
activeFile.basename,
@ -317,6 +320,9 @@ export default class BookTrackerPlugin extends Plugin {
frontMatter[this.settings.statusProperty] = READ_STATE;
frontMatter[this.settings.endDateProperty] = endDate;
frontMatter[this.settings.ratingProperty] = rating;
if (this.settings.spiceProperty !== "") {
frontMatter[this.settings.spiceProperty] = rating;
}
});
new Notice("Reading finished for " + activeFile.name);

View File

@ -1,14 +1,26 @@
<script lang="ts">
import { Star, StarHalf } from "lucide-svelte";
import type { Snippet } from "svelte";
interface Props {
value?: number;
name?: string;
max?: number;
half?: boolean;
inactive?: Snippet;
active?: Snippet;
partial?: Snippet;
}
const maxV = 5;
let { value = $bindable(), name }: Props = $props();
let {
value = $bindable(),
name = "",
max = 5,
half = false,
inactive,
active,
partial,
}: Props = $props();
let ctrl: HTMLElement | null = $state(null);
let w = $state(0);
@ -27,7 +39,7 @@
let displayVal = $derived(hovering ? valueHover : (value ?? 0));
let items = $derived.by(() => {
const full = Number.isInteger(displayVal);
return Array.from({ length: maxV }, (_, i) => i + 1).map((index) => ({
return Array.from({ length: max }, (_, i) => i + 1).map((index) => ({
index,
state:
index <= Math.ceil(displayVal)
@ -39,7 +51,7 @@
});
function calcSliderPos(e: MouseEvent) {
return (e.offsetX / (e.target as HTMLElement).clientWidth) * maxV;
return (e.offsetX / (e.target as HTMLElement).clientWidth) * max;
}
function onclick() {
@ -53,7 +65,14 @@
function onmousemove(e: MouseEvent) {
hovering = true;
valueHover = Math.ceil(calcSliderPos(e) * 2) / 2;
const newValue = calcSliderPos(e);
if (half) {
valueHover = Math.ceil(newValue * 2) / 2;
} else {
valueHover = Math.ceil(newValue);
}
}
</script>
@ -62,18 +81,37 @@
<!-- svelte-ignore a11y_no_static_element_interactions -->
<!-- svelte-ignore a11y_mouse_events_have_key_events -->
<div class="rating-input" {onclick} {onmouseout}>
<input type="number" {name} bind:value />
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="ctrl" {onmousemove} bind:this={ctrl}></div>
<div class="cont m-1" bind:clientWidth={w} bind:clientHeight={h}>
{#each items as item (item.index)}
<div class="rating-item">
{#if item.state === "inactive"}
<Star fill="var(--interactive-normal)" />
{#if inactive}
{@render inactive()}
{:else}
<Star color="var(--background-modifier-border)" />
{/if}
{:else if item.state === "active"}
<Star fill="var(--interactive-accent)" />
{#if active}
{@render active()}
{:else}
<Star
color="var(--color-yellow)"
fill="rgba(var(--color-yellow-rgb), 0.2)"
/>
{/if}
{:else if item.state === "partial"}
<Star fill="var(--interactive-normal)" />
<StarHalf fill="var(--interactive-accent)" />
{#if partial}
{@render partial()}
{:else}
<Star color="var(--background-modifier-border)" />
<StarHalf
color="var(--color-yellow)"
fill="rgba(var(--color-yellow-rgb), 0.2)"
/>
{/if}
{/if}
</div>
{/each}
@ -84,6 +122,10 @@
.rating-input {
cursor: pointer;
input {
display: none;
}
.ctrl {
position: absolute;
z-index: 2;

View File

@ -3,16 +3,27 @@ import { App } from "obsidian";
import { SvelteModal } from "./SvelteModal";
export class RatingModal extends SvelteModal<typeof RatingModalView> {
constructor(app: App, onSubmit: (rating: number) => void = () => {}) {
super(app, RatingModalView, { props: { onSubmit } });
constructor(
app: App,
spiceConfigured: boolean,
onSubmit: (rating: number, spice: number) => void = () => {}
) {
super(app, RatingModalView, { props: { spiceConfigured, onSubmit } });
}
static createAndOpen(app: App): Promise<number> {
static createAndOpen(
app: App,
spiceConfigured: boolean
): Promise<{ rating: number; spice: number }> {
return new Promise((resolve) => {
const modal = new RatingModal(app, (rating) => {
const modal = new RatingModal(
app,
spiceConfigured,
(rating, spice) => {
modal.close();
resolve(rating);
});
resolve({ rating, spice });
}
);
modal.open();
});
}

View File

@ -1,32 +1,53 @@
<script lang="ts">
import RatingInput from "@ui/components/RatingInput.svelte";
import { Flame } from "lucide-svelte";
interface Props {
onSubmit: (value: number) => void;
spiceConfigured?: boolean;
onSubmit: (rating: number, spice: number) => void;
}
let { onSubmit }: Props = $props();
let { spiceConfigured = false, onSubmit }: Props = $props();
let value = $state(0);
let rating = $state(0);
let spice = $state(0);
function onsubmit(ev: SubmitEvent) {
ev.preventDefault();
onSubmit(value);
onSubmit(rating, spice);
}
</script>
<div class="obt-rating">
<h2>Rating</h2>
<form {onsubmit}>
<div class="value-field">
<label for="value">Rating</label>
<RatingInput bind:value />
<div class="field">
<label for="rating">Rating</label>
<RatingInput name="rating" half bind:value={rating} />
</div>
{#if spiceConfigured}
<div class="field">
<label for="spice">Spice</label>
<RatingInput name="spice" bind:value={spice}>
{#snippet inactive()}
<Flame color="var(--background-modifier-border)" />
{/snippet}
{#snippet active()}
<Flame
color="var(--color-red)"
fill="rgba(var(--color-red-rgb), 0.2)"
/>
{/snippet}
</RatingInput>
</div>
{/if}
<button type="submit">Submit</button>
</form>
</div>
<style lang="scss">
@use "../styles/utils";
.obt-rating {
padding-bottom: var(--size-4-4);
@ -39,13 +60,13 @@
flex-direction: column;
gap: var(--size-4-4);
.value-field {
.field {
display: flex;
flex-direction: column;
align-items: center;
label {
display: none;
@include utils.visually-hidden;
}
}

View File

@ -8,6 +8,7 @@
import type BookTrackerPlugin from "@src/main";
import { createSettingsStore } from "./store";
import { onMount } from "svelte";
import FieldSuggest from "@ui/components/suggesters/FieldSuggest.svelte";
type Props = {
plugin: BookTrackerPlugin;
@ -121,6 +122,15 @@
bind:value={$settings.ratingProperty}
accepts={["number"]}
/>
<FieldSuggestItem
{app}
id="spice-field"
name="Spice Field"
description={`Select the field to use for spice rating.
Set to empty to disable.`}
bind:value={$settings.spiceProperty}
accepts={["number"]}
/>
<FieldSuggestItem
{app}
id="page-count-field"

View File

@ -12,6 +12,7 @@ export interface BookTrackerSettings {
startDateProperty: string;
endDateProperty: string;
ratingProperty: string;
spiceProperty: string;
pageCountProperty: string;
}
@ -29,5 +30,6 @@ export const DEFAULT_SETTINGS: BookTrackerSettings = {
startDateProperty: "startDate",
endDateProperty: "endDate",
ratingProperty: "rating",
spiceProperty: "",
pageCountProperty: "pageCount",
};