diff --git a/app/gui/src/components/AppContainer/RightPanel.vue b/app/gui/src/components/AppContainer/RightPanel.vue index 290f1d2075af..45608114135a 100644 --- a/app/gui/src/components/AppContainer/RightPanel.vue +++ b/app/gui/src/components/AppContainer/RightPanel.vue @@ -6,11 +6,10 @@ import { ProjectSessions, } from '$/components/AppContainer/reactTabs' import SelectableTab from '$/components/AppContainer/SelectableTab.vue' -import WithCurrentProject from '$/components/WithCurrentProject.vue' import { useRightPanelData, type RightPanelTabId } from '$/providers/rightPanel' import ComponentHelpPanel from '@/components/ComponentHelpPanel.vue' import DescriptionEditor from '@/components/DescriptionEditor.vue' -import DocumentationEditor from '@/components/DocumentationEditor.vue' +import DocumentationEditor from '@/components/DocumentationEditor' import ResizeHandles from '@/components/ResizeHandles.vue' import SizeTransition from '@/components/SizeTransition.vue' import WithFullscreenMode from '@/components/WithFullscreenMode.vue' @@ -70,11 +69,9 @@ const style = computed(() => (data.width == null ? {} : { '--panel-width': `${da
- -
- -
-
+
+ +
diff --git a/app/gui/src/components/WithCurrentProject.vue b/app/gui/src/components/WithCurrentProject.vue index 58665eaf561c..d39c820c6d6d 100644 --- a/app/gui/src/components/WithCurrentProject.vue +++ b/app/gui/src/components/WithCurrentProject.vue @@ -4,62 +4,82 @@ import { isLocalProjectId } from '#/services/LocalBackend' import { injectOpenedProjects, type OpenedProject } from '$/providers/openedProjects' import { groupColorVar } from '@/composables/nodeColors' import { createContextStore } from '@/providers' +import { assert } from '@/util/assert' import { colorFromString } from '@/util/colors' import type { Opt } from '@/util/data/opt' import type { ToValue } from '@/util/reactivity' -import { computed, toValue, watch, type ToRefs } from 'vue' +import { computed, type Ref, shallowRef, type ToRefs, toValue, watch } from 'vue' +export type CurrentProjectStore = ReturnType +const [provideCurrentProject, useCurrentProjectRaw] = createContextStore( + 'currentProject', + (project: Ref) => { + const ref = computed(() => { + assert(project.value != null) + return project.value + }) + return { + maybeRef: project, + /* Current project as a single ref */ + store: computed(() => ref.value.store), + projectNames: computed(() => ref.value.projectNames), + suggestionDb: computed(() => ref.value.suggestionDb), + module: computed(() => ref.value.module), + graph: computed(() => ref.value.graph), + widgetRegistry: computed(() => ref.value.widgetRegistry), + } satisfies ToRefs<{ [K in keyof OpenedProject]: OpenedProject[K] }> & { + maybeRef: Ref + } + }, +) + +export function useCurrentProject(allowMissing: true): CurrentProjectStore | undefined +export function useCurrentProject(allowMissing?: false): CurrentProjectStore +export function useCurrentProject(allowMissing?: boolean): CurrentProjectStore | undefined /** * A context of a single opened project. * * Use `WithCurrentProject` component to provide which project is the current for entire component - * tree (it's injects context and also sets proper css properties). Inside, inject will bring all - * project-related stores. If the project is closed, all stores becomes undefined. + * tree (it injects context, makes sure the project is available, and sets proper css properties). + * + * The refs inside aren't proxied, so this store may be deconstructed. */ -const [provideCurrentProject, useCurrentProject] = createContextStore( - 'currentProject', - (projectId: ToValue>) => { - const openedProjects = injectOpenedProjects() - - const hybridResolvedProjectId = computed(() => { - const id = toValue(projectId) - // When we have a hybrid project opened, we have to translate cloud project ID to corresponding hybrid project. - if (id && openedProjects.get(id) == null && !isLocalProjectId(id)) { - for (const openedId of openedProjects.listIds()) { - if (openedId.includes('/cloud-' + id) && isLocalProjectId(openedId)) return openedId - } - } - return id - }) +export function useCurrentProject(allowMissing?: boolean) { + const currentProjectStore = useCurrentProjectRaw(allowMissing) + if (currentProjectStore == null) return undefined + // If the store is defined, but there is no project in it, it has to be fallback component. + if (currentProjectStore.maybeRef.value == null) { + if (allowMissing) return undefined + else throw new Error(`Trying to inject currentProject in WithProject's fallback component`) + } + return currentProjectStore +} - const ref = computed((): OpenedProject | undefined => { - const id = hybridResolvedProjectId.value - return id != null ? openedProjects.get(id) : undefined - }) +function useOpenedProject(projectId: ToValue>) { + const openedProjects = injectOpenedProjects() - return { - id: hybridResolvedProjectId, - /* Current project as a single ref */ - ref, - /* Current project's stores decomposed to separate refs. */ - storesRefs: { - store: computed(() => ref.value?.store), - names: computed(() => ref.value?.names), - suggestionDb: computed(() => ref.value?.suggestionDb), - graph: computed(() => ref.value?.graph), - widgetRegistry: computed(() => ref.value?.widgetRegistry), - } satisfies ToRefs<{ [K in keyof OpenedProject]: OpenedProject[K] | undefined }>, + const hybridResolvedProjectId = computed(() => { + const id = toValue(projectId) + // When we have a hybrid project opened, we have to translate cloud project ID to corresponding hybrid project. + if (id && openedProjects.get(id) == null && !isLocalProjectId(id)) { + for (const openedId of openedProjects.listIds()) { + if (openedId.includes('/cloud-' + id) && isLocalProjectId(openedId)) return openedId + } } - }, -) + return id + }) -export { useCurrentProject } + return computed((): OpenedProject | undefined => { + const id = hybridResolvedProjectId.value + return id != null ? openedProjects.get(id) : undefined + }) +} function useStoreTemplate( storeKey: K, ): () => NonNullable { return () => { - const currentProject = useCurrentProject().ref + const currentProject = useCurrentProject().maybeRef const store: Opt = currentProject.value?.[storeKey] if (store == null) { throw new Error('Current Project missing, probably closed.') @@ -77,7 +97,7 @@ function useStoreTemplate( export const useProjectStore = useStoreTemplate('store') /** @deprecated it expects the current project will not change. Use {@link useCurrentProject} instead. */ -export const useProjectNames = useStoreTemplate('names') +export const useProjectNames = useStoreTemplate('projectNames') /** @deprecated it expects the current project will not change. Use {@link useCurrentProject} instead. */ export const useSuggestionDbStore = useStoreTemplate('suggestionDb') @@ -92,11 +112,32 @@ export const useWidgetRegistry = useStoreTemplate('widgetRegistry') diff --git a/app/gui/src/project-view/components/DocumentationEditor/ClosedProjectDocumentationEditor.vue b/app/gui/src/project-view/components/DocumentationEditor/ClosedProjectDocumentationEditor.vue new file mode 100644 index 000000000000..46ac74bfa1d7 --- /dev/null +++ b/app/gui/src/project-view/components/DocumentationEditor/ClosedProjectDocumentationEditor.vue @@ -0,0 +1,91 @@ + + + diff --git a/app/gui/src/project-view/components/DocumentationEditor/DocumentationEditor.vue b/app/gui/src/project-view/components/DocumentationEditor/DocumentationEditor.vue new file mode 100644 index 000000000000..6850c6172698 --- /dev/null +++ b/app/gui/src/project-view/components/DocumentationEditor/DocumentationEditor.vue @@ -0,0 +1,36 @@ + + + + + diff --git a/app/gui/src/project-view/components/DocumentationEditor/OpenedProjectDocumentationEditor.vue b/app/gui/src/project-view/components/DocumentationEditor/OpenedProjectDocumentationEditor.vue new file mode 100644 index 000000000000..bf99c58c170a --- /dev/null +++ b/app/gui/src/project-view/components/DocumentationEditor/OpenedProjectDocumentationEditor.vue @@ -0,0 +1,66 @@ + + + diff --git a/app/gui/src/project-view/components/DocumentationEditor/index.ts b/app/gui/src/project-view/components/DocumentationEditor/index.ts new file mode 100644 index 000000000000..3fc5e803584a --- /dev/null +++ b/app/gui/src/project-view/components/DocumentationEditor/index.ts @@ -0,0 +1,4 @@ +import DocumentationEditor from './DocumentationEditor.vue' + +export * from './DocumentationEditor.vue' +export default DocumentationEditor diff --git a/app/gui/src/project-view/components/FunctionSignatureEditor.vue b/app/gui/src/project-view/components/FunctionSignatureEditor.vue index e3eb6e122a1b..d7af78976918 100644 --- a/app/gui/src/project-view/components/FunctionSignatureEditor.vue +++ b/app/gui/src/project-view/components/FunctionSignatureEditor.vue @@ -1,11 +1,15 @@ diff --git a/app/gui/src/project-view/components/GraphEditor/GraphNodeOutputPorts.vue b/app/gui/src/project-view/components/GraphEditor/GraphNodeOutputPorts.vue index 92a806894e11..c1c9cb60cbbc 100644 --- a/app/gui/src/project-view/components/GraphEditor/GraphNodeOutputPorts.vue +++ b/app/gui/src/project-view/components/GraphEditor/GraphNodeOutputPorts.vue @@ -1,12 +1,12 @@ diff --git a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetCheckbox.vue b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetCheckbox.vue index 934c6284bbae..ffb6d331acab 100644 --- a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetCheckbox.vue +++ b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetCheckbox.vue @@ -1,9 +1,14 @@ diff --git a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetNumber.vue b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetNumber.vue index fb0c8d00ed2c..09c11eeae841 100644 --- a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetNumber.vue +++ b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetNumber.vue @@ -1,7 +1,12 @@ @@ -262,7 +262,7 @@ export const widgetDefinition = defineWidget( ) export { CustomDropdownItemsKey } -declare module '@/providers/widgetRegistry' { +declare module '$/providers/openedProjects/widgetRegistry' { export interface WidgetInput { [CustomDropdownItemsKey]?: readonly DropdownItem[] } diff --git a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetSelection/tags.ts b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetSelection/tags.ts index 97a89981f215..afd5f77d7c08 100644 --- a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetSelection/tags.ts +++ b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetSelection/tags.ts @@ -1,15 +1,22 @@ -import type { SubmenuEntry } from '@/components/GraphEditor/widgets/WidgetSelection/submenuEntry' -import type { Choice, WidgetConfiguration } from '@/providers/widgetRegistry/configuration' -import type { GraphStore } from '@/stores/graph' -import { printRequiredImport, requiredImports, type RequiredImport } from '@/stores/graph/imports' -import type { ProjectNameStore } from '@/stores/projectNames' -import { SuggestionDb } from '@/stores/suggestionDatabase' +import { type ModuleStore } from '$/providers/openedProjects/module' +import { + printRequiredImport, + requiredImports, + type RequiredImport, +} from '$/providers/openedProjects/module/imports' +import { type ProjectNameStore } from '$/providers/openedProjects/projectNames' +import { SuggestionDb } from '$/providers/openedProjects/suggestionDatabase' import { entryDisplayPath, entryIsStatic, SuggestionKind, type SuggestionEntry, -} from '@/stores/suggestionDatabase/entry' +} from '$/providers/openedProjects/suggestionDatabase/entry' +import type { + Choice, + WidgetConfiguration, +} from '$/providers/openedProjects/widgetRegistry/configuration' +import type { SubmenuEntry } from '@/components/GraphEditor/widgets/WidgetSelection/submenuEntry' import { Ast } from '@/util/ast' import type { Opt } from '@/util/data/opt' import { isIconName, type Icon } from '@/util/iconMetadata/iconName' @@ -135,9 +142,9 @@ export class ExpressionTag { * Add any needed imports to the provided module, and return the expression with any necessary * qualification. */ - resolveExpression(edit: Ast.MutableModule, graph: GraphStore) { + resolveExpression(edit: Ast.MutableModule, module: ModuleStore) { if (this.requiredImports) { - const conflicts = graph.addMissingImports(edit, this.requiredImports) + const conflicts = module.addMissingImports(edit, this.requiredImports) if (conflicts != null && conflicts.length > 0) { // TODO: Substitution does not work, because we interpret imports wrongly. To be fixed in // https://github.com/enso-org/enso/issues/9356 @@ -244,8 +251,8 @@ export interface Entry extends SubmenuEntry { export interface DynamicConfigTagsOptions { dynamicConfig: ToValue> staticTags: ToValue> - suggestionDb: ToValue> - projectNames: Readonly>> + suggestionDb: ToValue + projectNames: Readonly> } /** @returns The dropdown tags applicable to an expression. */ export function useExpressionTags({ @@ -255,19 +262,13 @@ export function useExpressionTags({ projectNames, }: DynamicConfigTagsOptions): ComputedRef<(ExpressionTag | NestedChoiceTag)[]> { const staticExpressionTags = computed((): ExpressionTag[] | null => { - const suggestionDbValue = toValue(suggestionDb) - const projectNamesValue = projectNames.value - if (!suggestionDbValue || !projectNamesValue) return null - const tags = toValue(staticTags) if (tags == null) return null - return tags.map((t) => ExpressionTag.FromExpression(suggestionDbValue, projectNamesValue, t)) + return tags.map((t) => + ExpressionTag.FromExpression(toValue(suggestionDb), projectNames.value, t), + ) }) const dynamicTags = computed((): (ExpressionTag | NestedChoiceTag)[] | null => { - const suggestionDbValue = toValue(suggestionDb) - const projectNamesValue = projectNames.value - if (!suggestionDbValue || !projectNamesValue) return null - const config = toValue(dynamicConfig) if (config?.kind !== 'Single_Choice' && config?.kind !== 'Multiple_Choice') return null @@ -275,8 +276,8 @@ export function useExpressionTags({ Array.isArray(choice.value) ? new NestedChoiceTag(choice.label ?? '…', choice.value.map(choiceToTag)) : ExpressionTag.FromExpression( - suggestionDbValue, - projectNamesValue, + toValue(suggestionDb), + projectNames.value, choice.value, choice.label, choice.icon, diff --git a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetSelectionArrow.vue b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetSelectionArrow.vue index 8340dee0d993..26d51c576710 100644 --- a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetSelectionArrow.vue +++ b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetSelectionArrow.vue @@ -1,9 +1,14 @@