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

Register a custom view #27

Closed
Quorafind opened this issue Sep 15, 2022 · 27 comments · Fixed by #79
Closed

Register a custom view #27

Quorafind opened this issue Sep 15, 2022 · 27 comments · Fixed by #79
Assignees
Labels
area/view/custom Issues related to the Custom View API kind/feature New feature request lifecycle/backlog Issues that are ready to be worked on. triage/confirmed Issue is well-defined and understood. Needs to have a priority/* label.

Comments

@Quorafind
Copy link

Saw your idea in discord, so I decide to file a issue to record this.

Have been playing with something even simpler, though perhaps too simple. Plugins could subscribe to an event with a context that lets you register an onload function that gives you the data and an HTMLElement where you can add your view.

And can't wait to see this happen. I wanted to build a view and use data from projects.

@marcusolsson
Copy link
Owner

Thanks for creating the issue! For anyone who's interested, there's some context in #13 as well!

I really want this to be as effortless as possible for other plugin developers, rather than have to maintain a separate "plugin". Ideally, it should just have to be a translation layer from one data structure to another.

I've been passively experimenting with this, but now that the plugin seems to be relatively stable, I'll focus on this more.

@marcusolsson marcusolsson added the kind/feature New feature request label Sep 15, 2022
@RafaelGB
Copy link

Let me know if I need to share some info or implement internally something inside dbfolder to help you with this =)

@marcusolsson
Copy link
Owner

I have a working proof-of-concept right now. The Projects plugin scans all enabled plugins and looks for a onRegisterProjectView method.

import { Plugin } from "obsidian";

export default class MyPlugin extends Plugin {
	onRegisterProjectView(data: DataFrame, contentEl: HTMLElement) {
		contentEl.createEl("h1", { text: "Debug" });

		const ul = contentEl.createEl("ul");

		for (let field of data.fields) {
			ul.createEl("li", {
				text: field.name,
			});
		}
	}
}

Most likely we'll need to change the signature to provide a builder-type pattern like for menus so we can extend it with more configuration options in the future.

onRegisterProjectView(ctx: ViewContext) {
  ctx.setTitle("Sample view").setNoPadding().setOnLoad((data, contentEl) => { ... });
}

WDYT?

Screenshot 2022-09-16 at 18 02 14@2x

Screenshot 2022-09-16 at 18 01 57@2x

@marcusolsson
Copy link
Owner

I'd love to ship it pretty soon so that you can start testing it out and we can start iterating on the API.

@RafaelGB
Copy link

I will test it as soon as is released for sure

@RafaelGB
Copy link

maybe I have to expose a selector modal with all the current db notes the user have inside that registerproyect

@marcusolsson
Copy link
Owner

Ok. I think I finally have something you can start testing.

Screenshot 2022-09-21 at 23 59 37@2x

Here's an example of a plugin that does nothing but registers itself as a Projects view.

import { Plugin } from "obsidian";
import { ProjectView } from "obsidian-projects-types";

export default class MyPlugin extends Plugin {
  onRegisterProjectView(view: ProjectView) {
    view.setTitle("Sample view")
      .setIcon("apple")
      .setOnOpen((data, contentEl) => {
        contentEl.createEl("h1", { text: "Debug" });

        const ul = contentEl.createEl("ul");

        for (let field of data.fields) {
          ul.createEl("li", {
            text: field.name,
          });
        }
      });
  }
}

Install the types by running:

yarn add --dev obsidian-projects-types@0.2.0

The API is only intended to give you something to experiment for now. Please don't add this to any releases :)

I'm super excited to see if anyone actually gets it to work 😅

@RafaelGB
Copy link

with v0.14.7 I try it with dbfolder plugin and an empty one and I am receiving an undefined on view: ProjectView

The function its called when I try to create a new view but breaks the modal

@marcusolsson
Copy link
Owner

Support was introduced in 0.14.8, so make sure upgrade and try again 🙏

@RafaelGB
Copy link

Please, update your manifest.json. BRAT (and also official plugins) reads your main branch > manifest.json to download the version

https://github.com/marcusolsson/obsidian-projects/blob/main/manifest.json

@marcusolsson
Copy link
Owner

What should I update? 0.14.8 is the latest version and that's what's in the manifest? I don't know BRAT that well tbh :)

@RafaelGB
Copy link

now BRAT upload it, thanks!

I am afraid I need a lot of work for integrate the table.
Just like Kanban plugin we use the workspaceLeaf element as input

I will try to mock it

@RafaelGB
Copy link

The context of the plugin is lost inside the api function.

https://stackoverflow.com/questions/4011793/this-is-undefined-in-javascript-class-methods. We need to bind it on call

Screenshot 2022-09-22 at 10 31 01

@marcusolsson
Copy link
Owner

Good find! I'll fix it with the next release!

@marcusolsson
Copy link
Owner

@RafaelGB Should hopefully be fixed in 0.14.10. Let me know if not.

@RafaelGB
Copy link

Looks like now the context is available, thanks!

I will let you know if I found another issue

@RafaelGB
Copy link

I could mock the table inside! but there is a little problem...

Your API is rendering on loop the view, I don't know if it happens after an exception or that loops provokes de exception

Screen.Recording.2022-09-22.at.16.10.08.mov

@marcusolsson
Copy link
Owner

Wow!! 🤩

I'm not sure I understand what you mean by "rendering on loop the view"? The onOpen function gets called any time there's new data, for example if you add a file to the workspace.

@RafaelGB
Copy link

In the case of dbfolder, the refresh of the information is managed internally with Zustand and I think that this refresh trigger jumps in cases where it shouldn't.

Would there be the possibility of disabling refresh as an API option (just like addIcon but toggleRefresh)? If the plugins already have their own way of managing it, you may incur a conflict. I think that is the case here

@RafaelGB
Copy link

RafaelGB commented Sep 22, 2022

Also, I just need a path to the ddbb file, but with the view the user will need to select it every time, maybe some kind of config map per view inside the API?

@marcusolsson
Copy link
Owner

Would you rather have a separate callbacks for receiving new data and when the user opens the view?

For example:

.setOnData((data: DataFrame) => {
  /...
})
.setOnOpen((contentEl: HTMLElement) => {
  // ...
})

Each view has a config object stored in the data.json. Haven't figured out a good way to expose it yet, but it's coming.

@RafaelGB
Copy link

Would you rather have a separate callbacks for receiving new data and when the user opens the view?

For example:

.setOnData((data: DataFrame) => {
  /...
})
.setOnOpen((contentEl: HTMLElement) => {
  // ...
})

Each view has a config object stored in the data.json. Haven't figured out a good way to expose it yet, but it's coming.

Since the plugin rewrite all the config every time it Marshall/unmarshall the yaml info I think it provokes the eternal loop of the callback.

I like the idea of separating them 👌

@marcusolsson
Copy link
Owner

I've published 0.14.11 of the Projects plugin, and 0.3.3 of obsidian-projects-types package, which exposes a reworked version of the API.

Instead of the builder pattern, in V2, you implement an abstract class:

import { Plugin } from "obsidian";
import { DataFrame, ProjectViewV2 } from "obsidian-projects-types";

class MySampleView extends ProjectViewV2 {
  dataEl?: HTMLElement;

  getViewType(): string {
    return "my-sample-view";
  }

  getDisplayName(): string {
    return "Sample view";
  }

  getIcon(): string {
    return "apple";
  }

  async onData(frame: DataFrame) {
    if (this.dataEl) {
      this.dataEl.empty();

      this.dataEl.createDiv({ text: JSON.stringify(frame.fields) });
      this.dataEl.createDiv({ text: JSON.stringify(frame.records) });
    }
  }

  async onOpen() {
    console.log("Opening ", this.getDisplayName());

    const contentEl = this.containerEl;

    contentEl.createEl("h1", { text: "My Sample View" });

    this.dataEl = contentEl.createEl("div");
  }

  async onClose() {
    console.log("Closing ", this.getDisplayName());
  }
}

export default class MyPlugin extends Plugin {
  onRegisterProjectViewV2 = () => new MySampleView();
}
  • onOpen is called when the user switches to your view
  • onClose is called when the user leaves your view
  • onData is called whenever the data has changed.

Compared to V1, you can now set up more expensive computations in onOpen and only update the relevant parts of the view. You can also use onClose to clean up any resources you created in onOpen.

@RafaelGB
Copy link

Really nice job. I will test it this weekend to give you feedback :)

@RafaelGB
Copy link

class MySampleView extends ProjectViewV2 {
	private plugin: DBFolderPlugin;
	constructor(plugin: DBFolderPlugin) {
		super();
		this.plugin = plugin;
	}
	dataEl?: HTMLElement;

	getViewType(): string {
		return "my-sample-view";
	}

	getDisplayName(): string {
		return "Sample view";
	}

	getIcon(): string {
		return DB_ICONS.NAME;
	}

	async onData(frame: DataFrame) {
		// Do nothing here
	}

	async onOpen() {
		const [firstKey] = this.plugin.viewMap.keys();
		const db = this.plugin.viewMap.get(firstKey)

		db.initDatabase().then(() => {
			this.containerEl.createDiv().appendChild(db.containerEl)
		})
	}

	async onClose() {
		console.log("Closing ", this.getDisplayName());
	}
}
// ***
onRegisterProjectViewV2 = () => new MySampleView(this);
Screen.Recording.2022-09-25.at.17.00.15.mov

With the correct configuration the mock is working =)
For some reason the view had a limited height but it is just a visual detail, the core of the API fits with dbfolder now

This was referenced Oct 1, 2022
@marcusolsson marcusolsson changed the title [DEV]Register a view in Obsidian Projects Register a custom view Oct 27, 2022
@marcusolsson marcusolsson added triage/confirmed Issue is well-defined and understood. Needs to have a priority/* label. lifecycle/backlog Issues that are ready to be worked on. labels Oct 27, 2022
@marcusolsson marcusolsson self-assigned this Oct 30, 2022
@marcusolsson
Copy link
Owner

0.21.0 contains an updated API for custom views. This release has several breaking changes, but updating should be relatively straight-forward.

Notably, with this release, all the built-in views are now using the custom view API. Hopefully, that means that I'll be able to catch issues early. Feel free to have a look the table view, for example.

For documentation, refer to Custom View API. It's a bit bare, but I'll keep improving it as we go.

With this release, I'll consider this issue closed. Feel free to open new issues for any issues you find. I'll label any issues for custom views with area/view/custom in case you want to keep updated.

Exciting times ahead!

@marcusolsson marcusolsson added the area/view/custom Issues related to the Custom View API label Nov 1, 2022
@RafaelGB
Copy link

RafaelGB commented Nov 1, 2022

I can't wait to play with it 😄😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/view/custom Issues related to the Custom View API kind/feature New feature request lifecycle/backlog Issues that are ready to be worked on. triage/confirmed Issue is well-defined and understood. Needs to have a priority/* label.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants