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 @@
]