Repository navigation
feat(plugins): Add sidebar actions in plugin.config.ts and openSettings #253
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
Changes from all commits
512088d
4d83767
3c65049
53c3d1a
e065f62
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import { toast } from 'frappe-ui' | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 53c3d1a: both callers now use one |
||
| import { appLocation, openPluginSettings } from '../ia/plugins' | ||
|
|
||
| // A sidebar action is an item in the plugin's commera/plugin.config.ts. The build ships that file as one module; | ||
| // a click imports it, finds the item by name and calls its run() with what a row can do. | ||
| export async function runSidebarAction(entry) { | ||
| const { app, place, name, module_url, label } = entry | ||
| try { | ||
| if (entry.error || !module_url) throw new Error(entry.error || 'This plugin has not been built yet.') | ||
| const url = import.meta.env.DEV ? `/@commera-plugin/${app}/${place}/${name}` : module_url | ||
| const config = (await import(/* @vite-ignore */ url)).default | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The README says Also, dev mode builds the URL from
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Documented in 53c3d1a: the README now says the build runs |
||
| const action = config?.[place]?.find((item) => item.name === name) | ||
| if (!action) throw new Error(`${name} is not in ${app}'s plugin.config`) | ||
| await action.run({ | ||
| openSettings: (tab) => openPluginSettings(app, tab), | ||
| // Imported on use: router.js imports ia/plugins, which imports this file. | ||
| navigate: async (to) => (await import('../router')).router.push(appLocation(app, to)), | ||
| openUrl: (target) => window.open(target, '_blank', 'noopener'), | ||
| toast, | ||
| }) | ||
| } catch (error) { | ||
| console.error(`[commera plugin ${entry.key}]`, error) | ||
| toast.error(`${label} couldn't run`) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| export interface SidebarActionContext { | ||
| /** Opens the plugin's own Settings tab, or a Commera tab such as 'payments'. */ | ||
| openSettings(tab?: string): void; | ||
| /** Goes to a dashboard route; a relative path resolves against /commera/plugins/<app>/. */ | ||
| navigate(to: string): Promise<unknown>; | ||
| /** Opens a URL in a new tab. */ | ||
| openUrl(url: string): void; | ||
| toast: { | ||
| success(message: string): void; | ||
| error(message: string): void; | ||
| warning(message: string): void; | ||
| info(message: string): void; | ||
| }; | ||
| } | ||
|
|
||
| export interface SidebarAction { | ||
| /** 1 to 40 lowercase letters, digits and hyphens; the entry's stable key. */ | ||
| name: string; | ||
| label: string; | ||
| /** A name from Commera's icon list, such as 'settings'. */ | ||
| icon?: string; | ||
| order?: number; | ||
| /** A DocType the user must be able to read to see the row. */ | ||
| requires?: string; | ||
| /** A dotted path to a function in the plugin that returns a bool. */ | ||
| condition?: string; | ||
| /** Runs when the row is clicked. */ | ||
| run(context: SidebarActionContext): void | Promise<void>; | ||
| } | ||
|
|
||
| export interface PluginConfig { | ||
| /** Rows after the plugin's pages in its sidebar group. */ | ||
| sidebar?: SidebarAction[]; | ||
| } | ||
|
|
||
| export function definePlugin(config: PluginConfig): PluginConfig; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| // Contributions that draw nothing; types are in index.d.ts. It runs in the dashboard, so it must not import Node. | ||
| export function definePlugin(config) { | ||
| return config; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A single sidebar action becomes a top-level row with no
toand the app icon.NavSection.vueonly handles a child withouttoin the children loop, not the top-level branch. Please confirm a top-level item withouttorenders and firesonClick, and show it in a screenshot. Also check an app with one action and no pages: there's no/plugins/${app}route, butappLocationandopenSettingsrely on the app's settings tab.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The top-level branch of
NavSection.vuealready handles an item withoutto::active="Boolean(item.to) && …"and@click="item.onClick?.()", the same path the Search row uses. No route is needed for an app with only actions: nothing links to/plugins/<app>there. If the plugin has no Settings tab,openSettings()falls back to General, and the README now says so. Two related bugs are fixed in 53c3d1a: a group holding an action used to open itself on any page outside the nav (such as Settings), and the phone's More sheet lost an action's logo icon.