Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to using a context object for shared state #48

Merged
merged 3 commits into from
Mar 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
54 changes: 54 additions & 0 deletions src/context.ts
@@ -0,0 +1,54 @@
import { type App, type TAbstractFile, TFile, TFolder } from 'obsidian';
import { derived, get, writable } from 'svelte/store';
import type MealPlugin from './main';
import { type Recipe, get_recipes } from './recipe/recipe';
import { MealSettings } from './settings';

export class Context {
app: App;
plugin: MealPlugin;

recipes = writable(new Array<Recipe>());

ingredients = derived(this.recipes, ($recipes) => {
const ingredients = new Set<string>();

for (const recipe of $recipes) {
for (const ingredient of recipe.ingredients) {
if (ingredient === undefined) {
console.error('Recipe ingredient is broken', recipe, ingredient);
continue;
}

ingredients.add(ingredient.description.toLowerCase());
}
}

return ingredients;
});

settings = writable(new MealSettings());

constructor(plugin: MealPlugin) {
this.plugin = plugin;
this.app = plugin.app;
}

async load_recipes(file: TAbstractFile | undefined) {
const recipe_folder_path = get(this.settings).recipe_directory;
const recipe_folder = this.app.vault.getFolderByPath(recipe_folder_path);
if (recipe_folder == null) {
console.error(`Failed to load recipes, can't access directory: ${recipe_folder_path}`);
return;
}

if (file !== undefined) {
if (file instanceof TFolder && file !== recipe_folder) return;
if (file instanceof TFile && file.parent !== recipe_folder) return;
}

get_recipes(this, recipe_folder!).then((r) => {
this.recipes.set(r);
});
}
}
61 changes: 32 additions & 29 deletions src/main.ts
@@ -1,27 +1,29 @@
import { App, Modal, Plugin, PluginSettingTab, Setting } from 'obsidian';
import { get } from 'svelte/store';
import { Context } from './context';
import { open_meal_plan_note } from './meal_plan/plan';
import { clear_checked_ingredients, generate_shopping_list } from './meal_plan/shopping_list';
import SearchRecipe from './recipe/SearchRecipe.svelte';
import { MealSettings, RecipeFormat, settings } from './settings';
import { load_recipes } from './store';
import { MealSettings, RecipeFormat } from './settings';
import 'virtual:uno.css';

export default class MealPlugin extends Plugin {
ctx = new Context(this);

async onload() {
await this.loadSettings();

load_recipes(this.app, undefined);
this.ctx.load_recipes(undefined);

this.registerEvent(
this.app.vault.on('create', (file) => {
load_recipes(this.app, file);
this.ctx.load_recipes(file);
}),
);

this.registerEvent(
this.app.vault.on('modify', (file) => {
load_recipes(this.app, file);
this.ctx.load_recipes(file);
}),
);

Expand All @@ -31,62 +33,61 @@ export default class MealPlugin extends Plugin {
id: 'open-recipe-search',
name: 'Find a recipe',
callback: () => {
new RecipeSearch(this.app, get(settings)).open();
new RecipeSearch(this.ctx).open();
},
});

this.addCommand({
id: 'open-meal-plan',
name: 'Open meal plan note',
callback: async () => {
await open_meal_plan_note(get(settings).meal_plan_note);
await open_meal_plan_note(this.app, get(this.ctx.settings).meal_plan_note);
},
});

this.addCommand({
id: 'create-shopping-list',
name: "Add week's shopping list",
callback: async () => {
generate_shopping_list(this.app);
generate_shopping_list(this.ctx);
},
});

this.addCommand({
id: 'clear-shopping-list',
name: 'Clear checked shopping list items',
callback: async () => {
clear_checked_ingredients(this.app);
clear_checked_ingredients(this.ctx);
},
});

console.log('tmayoff-meals loaded');
}

onunload() {}

async loadSettings() {
settings.set(Object.assign({}, new MealSettings(), await this.loadData()));
this.ctx.settings.set(Object.assign({}, new MealSettings(), await this.loadData()));
}

async saveSettings() {
await this.saveData(get(settings));
await this.saveData(get(this.ctx.settings));
}
}

class RecipeSearch extends Modal {
recipeView: SearchRecipe | undefined;
settings: MealSettings;
app: App;

constructor(app: App, settings: MealSettings) {
super(app);
ctx: Context;

this.app = app;
this.settings = settings;
constructor(ctx: Context) {
super(ctx.app);
this.ctx = ctx;
}
async onOpen() {
this.recipeView = new SearchRecipe({
target: this.containerEl.children[1].children[2],
props: {
app: this.app,
ctx: this.ctx,
},
});

Expand All @@ -103,10 +104,12 @@ class RecipeSearch extends Modal {

class MealPluginSettingsTab extends PluginSettingTab {
plugin: MealPlugin;
ctx: Context;

constructor(app: App, plugin: MealPlugin) {
super(app, plugin);
this.plugin = plugin;
this.ctx = plugin.ctx;
}

display(): void {
Expand All @@ -118,8 +121,8 @@ class MealPluginSettingsTab extends PluginSettingTab {
.setName('Recipe directory')
.setDesc('Parent folder where recipes are stored')
.addText(async (text) => {
text.setValue(get(settings).recipe_directory).onChange(async (value) => {
settings.update((s) => {
text.setValue(get(this.ctx.settings).recipe_directory).onChange(async (value) => {
this.ctx.settings.update((s) => {
s.recipe_directory = value;
return s;
});
Expand All @@ -132,9 +135,9 @@ class MealPluginSettingsTab extends PluginSettingTab {
.addText((text) =>
text
.setPlaceholder('Meal Plan')
.setValue(get(settings).meal_plan_note)
.setValue(get(this.ctx.settings).meal_plan_note)
.onChange(async (value) => {
settings.update((s) => {
this.ctx.settings.update((s) => {
s.meal_plan_note = value;
return s;
});
Expand All @@ -148,9 +151,9 @@ class MealPluginSettingsTab extends PluginSettingTab {
.addText((text) =>
text
.setPlaceholder('Shopping List')
.setValue(get(settings).shopping_list_note)
.setValue(get(this.ctx.settings).shopping_list_note)
.onChange(async (value) => {
settings.update((s) => {
this.ctx.settings.update((s) => {
s.shopping_list_note = value;
return s;
});
Expand All @@ -166,9 +169,9 @@ class MealPluginSettingsTab extends PluginSettingTab {
dropdown
.addOption('RecipeMD', RecipeFormat.RecipeMD)
.addOption('Meal Planner', RecipeFormat.Meal_Plan)
.setValue(get(settings).recipe_format)
.setValue(get(this.ctx.settings).recipe_format)
.onChange(async (value) => {
settings.update((s) => {
this.ctx.settings.update((s) => {
s.recipe_format = <RecipeFormat>value;
return s;
});
Expand All @@ -181,9 +184,9 @@ class MealPluginSettingsTab extends PluginSettingTab {
.setDesc('CSV list of ingredients to not add to the shopping list automatically')
.addText((text) => {
text.setPlaceholder('salt,pepper')
.setValue(get(settings).shopping_list_ignore.join(','))
.setValue(get(this.ctx.settings).shopping_list_ignore.join(','))
.onChange(async (value) => {
settings.update((s) => {
this.ctx.settings.update((s) => {
s.shopping_list_ignore = value.split(',');
return s;
});
Expand Down
10 changes: 5 additions & 5 deletions src/meal_plan/plan.ts
@@ -1,19 +1,19 @@
import { App, TFile } from 'obsidian';
import { get } from 'svelte/store';
import { DAYS_OF_WEEK } from '../constants';
import type { Context } from '../context';
import type { Recipe } from '../recipe/recipe';
import { settings } from '../settings';
import { get_current_week } from './utils';

export async function add_recipe_to_meal_plan(app: App, recipe: Recipe, day: string) {
let file_path = get(settings).meal_plan_note;
export async function add_recipe_to_meal_plan(ctx: Context, recipe: Recipe, day: string) {
let file_path = get(ctx.settings).meal_plan_note;
if (!file_path.endsWith('.md')) {
file_path += '.md';
}

await fill_meal_plan_note(app, file_path);
await fill_meal_plan_note(ctx.app, file_path);

const file = app.vault.getAbstractFileByPath(file_path);
const file = ctx.app.vault.getAbstractFileByPath(file_path);
if (file instanceof TFile) {
file.vault.process(file, (content) => {
const header = `Week of ${get_current_week()}`;
Expand Down
45 changes: 22 additions & 23 deletions src/meal_plan/shopping_list.ts
@@ -1,23 +1,22 @@
import { App, TFile } from 'obsidian';
import { TFile } from 'obsidian';
import type { Ingredient } from 'parse-ingredient';
import { get } from 'svelte/store';
import { settings } from '../settings';
import { recipes } from '../store';
import type { Context } from '../context';
import { get_current_week } from './utils';

export async function clear_checked_ingredients(app: App) {
let file_path = get(settings).shopping_list_note;
export async function clear_checked_ingredients(ctx: Context) {
let file_path = get(ctx.settings).shopping_list_note;
if (!file_path.endsWith('.md')) {
file_path += '.md';
}

const file = app.vault.getAbstractFileByPath(file_path);
const file = ctx.app.vault.getAbstractFileByPath(file_path);
if (file instanceof TFile) {
const list_items = app.metadataCache.getFileCache(file)?.listItems;
const list_items = ctx.app.metadataCache.getFileCache(file)?.listItems;
if (list_items === undefined) return;

// Get current files content
let content = await app.vault.read(file);
let content = await ctx.app.vault.read(file);

// Since we're modifying the content but keeping the original content's metadata we need to keep track of
// how much we remove and offset all removals by that amount
Expand All @@ -31,35 +30,35 @@ export async function clear_checked_ingredients(app: App) {
}

// Save the new content
app.vault.modify(file, content);
ctx.app.vault.modify(file, content);
}
}

export async function generate_shopping_list(app: App) {
let file_path = get(settings).meal_plan_note;
export async function generate_shopping_list(ctx: Context) {
let file_path = get(ctx.settings).meal_plan_note;
if (!file_path.endsWith('.md')) {
file_path += '.md';
}

let ingredients: Array<Ingredient> = [];
const meal_plan_file = app.vault.getAbstractFileByPath(file_path);
const meal_plan_file = ctx.app.vault.getAbstractFileByPath(file_path);
if (meal_plan_file instanceof TFile) {
ingredients = get_ingredients(app, meal_plan_file);
ingredients = get_ingredients(ctx, meal_plan_file);
}

file_path = get(settings).shopping_list_note;
file_path = get(ctx.settings).shopping_list_note;
if (!file_path.endsWith('.md')) {
file_path += '.md';
}

let file = app.vault.getAbstractFileByPath(file_path);
let file = ctx.app.vault.getAbstractFileByPath(file_path);
if (file == null) {
app.vault.create(file_path, '');
file = app.vault.getAbstractFileByPath(file_path);
ctx.app.vault.create(file_path, '');
file = ctx.app.vault.getAbstractFileByPath(file_path);
}

if (file instanceof TFile) {
app.vault.process(file, (data) => {
ctx.app.vault.process(file, (data) => {
for (const i of ingredients) {
let line = '';
if (i.quantity != null) line += `${i.quantity} `;
Expand All @@ -74,9 +73,9 @@ export async function generate_shopping_list(app: App) {
}
}

function get_ingredients(app: App, file: TFile) {
function get_ingredients(ctx: Context, file: TFile) {
const this_week = get_current_week();
const fileCache = app.metadataCache.getFileCache(file)!;
const fileCache = ctx.app.metadataCache.getFileCache(file)!;

const topLevel = fileCache.headings!.filter((h) => {
return h.level === 1;
Expand All @@ -96,7 +95,7 @@ function get_ingredients(app: App, file: TFile) {
const endPos = end !== -1 ? topLevel[end]?.position! : null;

const links = fileCache.links!;
const ignore_list = get(settings).shopping_list_ignore;
const ignore_list = get(ctx.settings).shopping_list_ignore;
const ingredients: Array<Ingredient> = [];
for (const i of links) {
// Skip links outside the bounds of the date range
Expand All @@ -108,9 +107,9 @@ function get_ingredients(app: App, file: TFile) {
continue;
}

const recipeFile = app.metadataCache.getFirstLinkpathDest(i.link, file.path);
const recipeFile = ctx.app.metadataCache.getFirstLinkpathDest(i.link, file.path);
if (recipeFile != null) {
const r = get(recipes).find((r) => {
const r = get(ctx.recipes).find((r) => {
return r.path.path === recipeFile.path;
});
if (r !== undefined) {
Expand Down
6 changes: 3 additions & 3 deletions src/recipe/RecipeButton.svelte
@@ -1,12 +1,12 @@
<script lang="ts">
import type { App } from "obsidian";
import { DAYS_OF_WEEK } from "../constants";
import type { Context } from "../context";
import { add_recipe_to_meal_plan } from "../meal_plan/plan";
import { open_note_file } from "../utils/filesystem";
import type { Recipe } from "./recipe";
import { createEventDispatcher } from "svelte";

export let app: App;
export let ctx: Context;
export let recipe: Recipe;

let open = false;
Expand Down Expand Up @@ -58,7 +58,7 @@
<button
class="rounded-none"
on:click={async () => {
await add_recipe_to_meal_plan(app, recipe, day);
await add_recipe_to_meal_plan(ctx, recipe, day);
open = false;
}}>{day}</button
>
Expand Down