Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions src/renderer/application/useCases/widget/getWidgetApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,38 @@ import { ProcessProvider } from '@/application/interfaces/processProvider';
import { ShellProvider } from '@/application/interfaces/shellProvider';
import { DataStorageRenderer } from '@/application/interfaces/dataStorage';
import { EntityId } from '@/base/entity';
import { WidgetApiModuleName, WidgetApiSetContextMenuFactoryHandler, WidgetApiUpdateActionBarHandler, createWidgetApiFactory } from '@/base/widgetApi';
import { WidgetApiExposeApiHandler, WidgetApiModuleName, WidgetApiSetContextMenuFactoryHandler, WidgetApiUpdateActionBarHandler, createWidgetApiFactory } from '@/base/widgetApi';
import { ObjectManager } from '@common/base/objectManager';
import { TerminalProvider } from '@/application/interfaces/terminalProvider';
import { GetWidgetsInCurrentWorkflowUseCase } from '@/application/useCases/widget/widgetApiWidgets/getWidgetsInCurrentWorkflow';

interface Deps {
clipboardProvider: ClipboardProvider;
widgetDataStorageManager: ObjectManager<DataStorageRenderer>;
processProvider: ProcessProvider;
shellProvider: ShellProvider;
terminalProvider: TerminalProvider;
getWidgetsInCurrentWorkflowUseCase: GetWidgetsInCurrentWorkflowUseCase;
}
function _createWidgetApiFactory({
clipboardProvider,
processProvider,
shellProvider,
widgetDataStorageManager,
terminalProvider,
getWidgetsInCurrentWorkflowUseCase,
}: Deps, forPreview: boolean) {
return createWidgetApiFactory(
(_widgetId, updateActionBarHandler, setWidgetContextMenuFactoryHandler) => ({
(_widgetId, updateActionBarHandler, setWidgetContextMenuFactoryHandler, exposeApiHandler) => ({
updateActionBar: !forPreview ? (actionBarItems) => {
updateActionBarHandler(actionBarItems);
} : () => undefined,
setContextMenuFactory: !forPreview ? (factory) => {
setWidgetContextMenuFactoryHandler(factory);
} : () => undefined
} : () => undefined,
exposeApi: !forPreview ? (api) => {
exposeApiHandler(api)
} : () => undefined,
}),
{
clipboard: () => ({
Expand Down Expand Up @@ -63,6 +69,9 @@ function _createWidgetApiFactory({
terminal: () => ({
execCmdLines: (cmdLines, cwd) => terminalProvider.execCmdLines(cmdLines, cwd)
}),
widgets: () => ({
getWidgetsInCurrentWorkflow: (widgetTypeId) => getWidgetsInCurrentWorkflowUseCase(widgetTypeId)
})
}
)
}
Expand All @@ -76,11 +85,12 @@ export function createGetWidgetApiUseCase(deps: Deps) {
forPreview: boolean,
updateActionBarHandler: WidgetApiUpdateActionBarHandler,
setContextMenuFactoryHandler: WidgetApiSetContextMenuFactoryHandler,
exposeApiHandler: WidgetApiExposeApiHandler,
requiredModules: WidgetApiModuleName[]
) {
return forPreview
? widgetApiPreviewFactory(widgetId, updateActionBarHandler, setContextMenuFactoryHandler, requiredModules)
: widgetApiFactory(widgetId, updateActionBarHandler, setContextMenuFactoryHandler, requiredModules);
? widgetApiPreviewFactory(widgetId, updateActionBarHandler, setContextMenuFactoryHandler, exposeApiHandler, requiredModules)
: widgetApiFactory(widgetId, updateActionBarHandler, setContextMenuFactoryHandler, exposeApiHandler, requiredModules);
}

return getWidgetApiUseCase;
Expand Down
40 changes: 40 additions & 0 deletions src/renderer/application/useCases/widget/setExposedApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright: (c) 2024, Alex Kaul
* GNU General Public License v3.0 or later (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
*/

import { AppStore } from '@/application/interfaces/store';
import { EntityId } from '@/base/entity';
import { updateOneInEntityCollection } from '@/base/entityCollection';

interface Deps {
appStore: AppStore,
}

export function createSetExposedApiUseCase({
appStore,
}: Deps) {
return function setExposedApiUseCase(
widgetId: EntityId,
api: object
) {
const state = appStore.get();
const newWidgets = updateOneInEntityCollection(state.entities.widgets, {
changes: {
exposedApi: api
},
id: widgetId
})
if (newWidgets !== state.entities.widgets) {
appStore.set({
...state,
entities: {
...state.entities,
widgets: newWidgets
}
})
}
}
}

export type SetExposedApiUseCase = ReturnType<typeof createSetExposedApiUseCase>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright: (c) 2024, Alex Kaul
* GNU General Public License v3.0 or later (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
*/

import { AppStore } from '@/application/interfaces/store';
import { getOneFromEntityCollection } from '@/base/entityCollection';
import { mapIdListToEntityList } from '@/base/entityList';
import { WidgetApiWidget } from '@/base/widgetApi';

interface Deps {
appStore: AppStore,
}

export function createGetWidgetsInCurrentWorkflowUseCase({
appStore,
}: Deps) {
return function getWidgetsInCurrentWorkflowUseCase<T extends object>(
widgetTypeId: string,
): WidgetApiWidget<T>[] {
const state = appStore.get();
const curPrjId = state.ui.projectSwitcher.currentProjectId;
const curWflId = state.entities.projects[curPrjId]?.currentWorkflowId;
if (!curWflId) {
return [];
}
const curWfl = getOneFromEntityCollection(state.entities.workflows, curWflId);
if (!curWfl) {
return [];
}

const wgtType = getOneFromEntityCollection(state.entities.widgetTypes, widgetTypeId);
if (!wgtType) {
return [];
}

return mapIdListToEntityList(state.entities.widgets, curWfl.layout.map(item => item.widgetId))
.filter(({ type }) => widgetTypeId === type)
.map(({ id, coreSettings, exposedApi }) => {
const { name } = coreSettings;
const api = exposedApi || {};
return {
id,
name,
api: api as T
}
})
}
}

export type GetWidgetsInCurrentWorkflowUseCase = ReturnType<typeof createGetWidgetsInCurrentWorkflowUseCase>;
17 changes: 8 additions & 9 deletions src/renderer/base/state/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,15 @@ export function initAppStateWidgets(appState: AppState): AppState {

export function createPersistentAppState(appState: AppState) {
const { copy, dragDrop, editMode, palette, memSaver, modalScreens, worktable, ...persistentUi } = appState.ui
const { widgetTypes, /* widgets, */...persistentEntities } = appState.entities;
const { widgetTypes, widgets, ...persistentEntities } = appState.entities;
return {
// entities: {
// ...persistentEntities,
// widgets: mapEntityCollection(widgets, widget => {
// const { ...persistentWidget } = widget;
// return persistentWidget
// })
// },
entities: persistentEntities,
entities: {
...persistentEntities,
widgets: mapEntityCollection(widgets, widget => {
const { exposedApi, ...persistentWidget } = widget;
return persistentWidget
})
},
ui: persistentUi
}
}
Expand Down
1 change: 1 addition & 0 deletions src/renderer/base/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface Widget<TSettings = WidgetSettings> extends Entity {
readonly type: string;
readonly coreSettings: WidgetCoreSettings;
readonly settings: TSettings;
readonly exposedApi?: object;
}

interface WidgetEnvCommon {
Expand Down
16 changes: 15 additions & 1 deletion src/renderer/base/widgetApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,15 @@ import { OpenDialogResult, OpenDirDialogConfig, OpenFileDialogConfig } from '@co
interface WidgetApiCommon {
readonly updateActionBar: (actionBarItems: ActionBarItems) => void;
readonly setContextMenuFactory: (factory: WidgetContextMenuFactory) => void;
readonly exposeApi: <T extends object>(api: T) => void; // exposes api for consumption by other widgets via WidgetAPI.widgets
}

// Widget things available for use by other widgets via WidgetAPI.widgets
export interface WidgetApiWidget<T extends object = object> {
id: EntityId;
name: string;
api: Partial<T>; // api exposed by widget
}
interface WidgetApiModules {
readonly clipboard: {
writeBookmark: (title: string, url: string) => Promise<void>;
Expand All @@ -39,6 +46,9 @@ interface WidgetApiModules {
readonly terminal: {
execCmdLines: (cmdLines: ReadonlyArray<string>, cwd?: string) => void;
}
readonly widgets: {
getWidgetsInCurrentWorkflow<T extends object>(widgetTypeId: string): ReadonlyArray<WidgetApiWidget<T>>;
}
}

export type WidgetApiModuleName = keyof WidgetApiModules;
Expand All @@ -47,10 +57,12 @@ export interface WidgetApi extends WidgetApiCommon, WidgetApiModules { }

export type WidgetApiUpdateActionBarHandler = (actionBarItems: ActionBarItems) => void;
export type WidgetApiSetContextMenuFactoryHandler = (factory: WidgetContextMenuFactory) => void;
export type WidgetApiExposeApiHandler = (api: object) => void;
export type WidgetApiCommonFactory = (
widgetId: EntityId,
updateActionBarHandler: WidgetApiUpdateActionBarHandler,
setContextMenuFactoryHandler: WidgetApiSetContextMenuFactoryHandler,
exposeApiHandler: WidgetApiExposeApiHandler
) => WidgetApiCommon;
type WidgetApiModuleFactory<N extends WidgetApiModuleName> = (widgetId: EntityId) => WidgetApiModules[N];
export type WidgetApiModuleFactories = {
Expand All @@ -61,6 +73,7 @@ export type WidgetApiFactory = (
widgetId: EntityId,
updateActionBarHandler: WidgetApiUpdateActionBarHandler,
setContextMenuFactoryHandler: WidgetApiSetContextMenuFactoryHandler,
exposeApiHandler: WidgetApiExposeApiHandler,
availableModules: WidgetApiModuleName[]
) => WidgetApi;

Expand All @@ -69,9 +82,10 @@ export function createWidgetApiFactory(commonFactory: WidgetApiCommonFactory, mo
widgetId: EntityId,
updateActionBarHandler: WidgetApiUpdateActionBarHandler,
setContextMenuFactoryHandler: WidgetApiSetContextMenuFactoryHandler,
exposeApiHandler: WidgetApiExposeApiHandler,
availableModules: WidgetApiModuleName[]
) => ({
...commonFactory(widgetId, updateActionBarHandler, setContextMenuFactoryHandler),
...commonFactory(widgetId, updateActionBarHandler, setContextMenuFactoryHandler, exposeApiHandler),
...Object.fromEntries(availableModules.map(featName => ([featName, moduleFactories[featName](widgetId)])))
} as WidgetApi);
}
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/base/widgetType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ export interface WidgetType<TSettings = WidgetSettings> extends Entity {
readonly widgetComp: WidgetTypeComponent;
readonly settingsEditorComp: WidgetTypeComponent;
readonly createSettingsState: CreateSettingsState<TSettings>;
readonly requiresApi?: WidgetApiModuleName[];
readonly requiresApi?: WidgetApiModuleName[]; // specifies WidgetAPI modules it requires access to
readonly requiresState?: SharedStateSliceName[];
}
9 changes: 9 additions & 0 deletions src/renderer/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ import { createDeactivateWorkflowUseCase } from '@/application/useCases/memSaver
import { createToggleTopBarUseCase } from '@/application/useCases/toggleTopBar';
import { createSetProjectSwitcherPositionUseCase } from '@/application/useCases/projectSwitcher/setProjectSwitcherPosition';
import { createSetEditTogglePositionUseCase } from '@/application/useCases/setEditTogglePosition';
import { createGetWidgetsInCurrentWorkflowUseCase } from '@/application/useCases/widget/widgetApiWidgets/getWidgetsInCurrentWorkflow';
import { createSetExposedApiUseCase } from '@/application/useCases/widget/setExposedApi';

function prepareDataStorageForRenderer(dataStorage: DataStorage): DataStorageRenderer {
return setTextOnlyIfChanged(withJson(dataStorage));
Expand Down Expand Up @@ -246,6 +248,8 @@ async function createUseCases(store: ReturnType<typeof createStore>) {
dialog: osDialogProvider
})

const getWidgetsInCurrentWorkflowUseCase = createGetWidgetsInCurrentWorkflowUseCase(deps);

const clipboardProvider = createClipboardProvider();
const shellProvider = createShellProvider();
const processProvider = await createProcessProvider();
Expand All @@ -260,6 +264,7 @@ async function createUseCases(store: ReturnType<typeof createStore>) {
shellProvider,
widgetDataStorageManager,
terminalProvider,
getWidgetsInCurrentWorkflowUseCase,
})
const deleteWidgetUseCase = createDeleteWidgetUseCase({
...deps,
Expand Down Expand Up @@ -427,6 +432,8 @@ async function createUseCases(store: ReturnType<typeof createStore>) {
const dragWorkflowFromWorkflowSwitcherUseCase = createDragWorkflowFromWorkflowSwitcherUseCase(deps);
const dropOnWorkflowSwitcherUseCase = createDropOnWorkflowSwitcherUseCase(deps);

const setExposedApiUseCase = createSetExposedApiUseCase(deps);

return {
dragWidgetFromWorktableLayoutUseCase,
dragOverWorktableLayoutUseCase,
Expand Down Expand Up @@ -519,6 +526,8 @@ async function createUseCases(store: ReturnType<typeof createStore>) {

deactivateWorkflowUseCase,
initMemSaverUseCase,

setExposedApiUseCase
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/renderer/ui/components/widget/widgetViewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { EntityId } from '@/base/entity';
import { CopyWidgetUseCase } from '@/application/useCases/widget/copyWidget';
import { ShowContextMenuUseCase } from '@/application/useCases/contextMenu/showContextMenu';
import { createSharedState } from '@/base/state/shared';
import { SetExposedApiUseCase } from '@/application/useCases/widget/setExposedApi';

type Deps = {
useAppState: UseAppState;
Expand All @@ -28,6 +29,7 @@ type Deps = {
openWidgetSettingsUseCase: OpenWidgetSettingsUseCase;
deleteWidgetUseCase: DeleteWidgetUseCase;
copyWidgetUseCase: CopyWidgetUseCase;
setExposedApiUseCase: SetExposedApiUseCase;
}

export interface WidgetProps {
Expand Down Expand Up @@ -56,6 +58,7 @@ export function createWidgetViewModelHook({
openWidgetSettingsUseCase,
deleteWidgetUseCase,
copyWidgetUseCase,
setExposedApiUseCase,
}: Deps) {
function showMoreActions(
id: EntityId,
Expand Down Expand Up @@ -156,6 +159,7 @@ export function createWidgetViewModelHook({
!!env.isPreview,
(items) => setActionBarItemsViewMode([...items, ...createActionBarCommonItemsViewMode(widgetType?.maximizable || false, maximizeAction)]),
(factory: WidgetContextMenuFactory | undefined) => setContextMenuFactoryViewMode(() => factory),
(api) => setExposedApiUseCase(widget.id, api),
widgetType?.requiresApi || []
), [env.isPreview, maximizeAction, widget.id, widgetType?.maximizable, widgetType?.requiresApi])

Expand Down
2 changes: 1 addition & 1 deletion src/renderer/widgets/appModules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

export type { OpenDialogResult } from '@common/base/dialog';
export type { WidgetApi, WidgetSettingsApi } from '@/base/widgetApi';
export type { WidgetApi, WidgetSettingsApi, WidgetApiWidget } from '@/base/widgetApi';
export type { WidgetType, CreateSettingsState } from '@/base/widgetType';
export type { WidgetMenuItem, WidgetMenuItemRole, WidgetMenuItems, WidgetContextMenuFactory, WidgetEnv, WidgetSettings } from '@/base/widget';
export type { ActionBarItem, ActionBarItems } from '@/base/actionBar';
Expand Down
9 changes: 9 additions & 0 deletions src/renderer/widgets/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright: (c) 2024, Alex Kaul
* GNU General Public License v3.0 or later (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
*/

export interface WebpageExposedApi {
openUrl: (url: string) => void;
getUrl: () => string;
}
2 changes: 1 addition & 1 deletion src/renderer/widgets/web-query/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const widgetType: WidgetType<Settings> = {
createSettingsState,
settingsEditorComp,
widgetComp,
requiresApi: ['shell']
requiresApi: ['shell', 'widgets']
}

export default widgetType;
Loading