mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 22:01:42 +03:00
Hide placeholders (#9246)
* New menuVisible logic * Hide placeholders unless sole selected node, required argument, or requested by dynamic config
This commit is contained in:
parent
f02213ae2b
commit
b0842feea2
@ -60,6 +60,10 @@ test('Selection widgets in Data.read node', async ({ page }) => {
|
|||||||
// Check initially visible arguments
|
// Check initially visible arguments
|
||||||
const node = locate.graphNodeByBinding(page, 'data')
|
const node = locate.graphNodeByBinding(page, 'data')
|
||||||
const argumentNames = node.locator('.WidgetArgumentName')
|
const argumentNames = node.locator('.WidgetArgumentName')
|
||||||
|
await expect(argumentNames).toHaveCount(1)
|
||||||
|
|
||||||
|
// Check arguments after selecting node
|
||||||
|
await node.click()
|
||||||
await expect(argumentNames).toHaveCount(3)
|
await expect(argumentNames).toHaveCount(3)
|
||||||
|
|
||||||
// Set value on `on_problems` (static drop-down)
|
// Set value on `on_problems` (static drop-down)
|
||||||
@ -129,6 +133,10 @@ test('Managing aggregates in `aggregate` node', async ({ page }) => {
|
|||||||
// Check initially visible arguments
|
// Check initially visible arguments
|
||||||
const node = locate.graphNodeByBinding(page, 'aggregated')
|
const node = locate.graphNodeByBinding(page, 'aggregated')
|
||||||
const argumentNames = node.locator('.WidgetArgumentName')
|
const argumentNames = node.locator('.WidgetArgumentName')
|
||||||
|
await expect(argumentNames).toHaveCount(1)
|
||||||
|
|
||||||
|
// Check arguments after selecting node
|
||||||
|
await node.click()
|
||||||
await expect(argumentNames).toHaveCount(3)
|
await expect(argumentNames).toHaveCount(3)
|
||||||
|
|
||||||
// Add first aggregate
|
// Add first aggregate
|
||||||
|
@ -30,6 +30,7 @@ export const graphSelection: GraphSelection = {
|
|||||||
hoveredNode: undefined,
|
hoveredNode: undefined,
|
||||||
hoveredPort: undefined,
|
hoveredPort: undefined,
|
||||||
isSelected: () => false,
|
isSelected: () => false,
|
||||||
|
isChanging: false,
|
||||||
mouseHandler: () => false,
|
mouseHandler: () => false,
|
||||||
selectAll: () => {},
|
selectAll: () => {},
|
||||||
selected: new Set(),
|
selected: new Set(),
|
||||||
|
@ -88,14 +88,6 @@ const contentNode = ref<HTMLElement>()
|
|||||||
const nodeSize = useResizeObserver(rootNode)
|
const nodeSize = useResizeObserver(rootNode)
|
||||||
const baseNodeSize = computed(() => new Vec2(contentNode.value?.scrollWidth ?? 0, nodeSize.value.y))
|
const baseNodeSize = computed(() => new Vec2(contentNode.value?.scrollWidth ?? 0, nodeSize.value.y))
|
||||||
|
|
||||||
/// Menu can be full, partial or off
|
|
||||||
enum MenuState {
|
|
||||||
Full,
|
|
||||||
Partial,
|
|
||||||
Off,
|
|
||||||
}
|
|
||||||
const menuVisible = ref(MenuState.Off)
|
|
||||||
|
|
||||||
const error = computed(() => {
|
const error = computed(() => {
|
||||||
const externalId = graph.db.idToExternal(nodeId.value)
|
const externalId = graph.db.idToExternal(nodeId.value)
|
||||||
if (!externalId) return
|
if (!externalId) return
|
||||||
@ -122,12 +114,19 @@ const warning = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const isSelected = computed(() => nodeSelection?.isSelected(nodeId.value) ?? false)
|
const isSelected = computed(() => nodeSelection?.isSelected(nodeId.value) ?? false)
|
||||||
const isOnlyOneSelected = computed(() => isSelected.value && nodeSelection?.selected.size === 1)
|
const isOnlyOneSelected = computed(
|
||||||
watch(isSelected, (selected) => {
|
() => isSelected.value && nodeSelection?.selected.size === 1 && !nodeSelection.isChanging,
|
||||||
if (!selected) {
|
)
|
||||||
menuVisible.value = MenuState.Off
|
|
||||||
}
|
const menuVisible = isOnlyOneSelected
|
||||||
|
const menuFull = ref(false)
|
||||||
|
watch(menuVisible, (visible) => {
|
||||||
|
if (!visible) menuFull.value = false
|
||||||
})
|
})
|
||||||
|
function openFullMenu() {
|
||||||
|
menuFull.value = true
|
||||||
|
nodeSelection?.setSelection(new Set([nodeId.value]))
|
||||||
|
}
|
||||||
|
|
||||||
const isDocsVisible = ref(false)
|
const isDocsVisible = ref(false)
|
||||||
const visualizationWidth = computed(() => props.node.vis?.width ?? null)
|
const visualizationWidth = computed(() => props.node.vis?.width ?? null)
|
||||||
@ -181,7 +180,6 @@ const dragPointer = usePointer((pos, event, type) => {
|
|||||||
) {
|
) {
|
||||||
nodeSelection?.handleSelectionOf(event, new Set([nodeId.value]))
|
nodeSelection?.handleSelectionOf(event, new Set([nodeId.value]))
|
||||||
handleNodeClick(event)
|
handleNodeClick(event)
|
||||||
menuVisible.value = MenuState.Partial
|
|
||||||
}
|
}
|
||||||
startEvent = null
|
startEvent = null
|
||||||
startEpochMs.value = 0
|
startEpochMs.value = 0
|
||||||
@ -363,13 +361,6 @@ function portGroupStyle(port: PortData) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openFullMenu() {
|
|
||||||
if (!nodeSelection?.isSelected(nodeId.value)) {
|
|
||||||
nodeSelection?.setSelection(new Set([nodeId.value]))
|
|
||||||
}
|
|
||||||
menuVisible.value = MenuState.Full
|
|
||||||
}
|
|
||||||
|
|
||||||
const editingComment = ref(false)
|
const editingComment = ref(false)
|
||||||
|
|
||||||
const documentation = computed<string | undefined>({
|
const documentation = computed<string | undefined>({
|
||||||
@ -419,12 +410,12 @@ const documentation = computed<string | undefined>({
|
|||||||
{{ node.pattern?.code() ?? '' }}
|
{{ node.pattern?.code() ?? '' }}
|
||||||
</div>
|
</div>
|
||||||
<CircularMenu
|
<CircularMenu
|
||||||
v-if="menuVisible === MenuState.Full || menuVisible === MenuState.Partial"
|
v-if="menuVisible"
|
||||||
v-model:isOutputContextOverridden="isOutputContextOverridden"
|
v-model:isOutputContextOverridden="isOutputContextOverridden"
|
||||||
v-model:isDocsVisible="isDocsVisible"
|
v-model:isDocsVisible="isDocsVisible"
|
||||||
:isOutputContextEnabledGlobally="projectStore.isOutputContextEnabled"
|
:isOutputContextEnabledGlobally="projectStore.isOutputContextEnabled"
|
||||||
:isVisualizationVisible="isVisualizationVisible"
|
:isVisualizationVisible="isVisualizationVisible"
|
||||||
:isFullMenuVisible="menuVisible === MenuState.Full"
|
:isFullMenuVisible="menuVisible && menuFull"
|
||||||
@update:isVisualizationVisible="emit('update:visualizationVisible', $event)"
|
@update:isVisualizationVisible="emit('update:visualizationVisible', $event)"
|
||||||
@startEditing="startEditingNode"
|
@startEditing="startEditingNode"
|
||||||
@startEditingComment="editingComment = true"
|
@startEditingComment="editingComment = true"
|
||||||
@ -436,7 +427,7 @@ const documentation = computed<string | undefined>({
|
|||||||
:nodeSize="baseNodeSize"
|
:nodeSize="baseNodeSize"
|
||||||
:scale="navigator?.scale ?? 1"
|
:scale="navigator?.scale ?? 1"
|
||||||
:nodePosition="props.node.position"
|
:nodePosition="props.node.position"
|
||||||
:isCircularMenuVisible="menuVisible === MenuState.Full || menuVisible === MenuState.Partial"
|
:isCircularMenuVisible="menuVisible"
|
||||||
:currentType="node.vis?.identifier"
|
:currentType="node.vis?.identifier"
|
||||||
:isFullscreen="isVisualizationFullscreen"
|
:isFullscreen="isVisualizationFullscreen"
|
||||||
:dataSource="{ type: 'node', nodeId: externalId }"
|
:dataSource="{ type: 'node', nodeId: externalId }"
|
||||||
@ -474,6 +465,7 @@ const documentation = computed<string | undefined>({
|
|||||||
:icon="icon"
|
:icon="icon"
|
||||||
:connectedSelfArgumentId="connectedSelfArgumentId"
|
:connectedSelfArgumentId="connectedSelfArgumentId"
|
||||||
:potentialSelfArgumentId="potentialSelfArgumentId"
|
:potentialSelfArgumentId="potentialSelfArgumentId"
|
||||||
|
:extended="isOnlyOneSelected"
|
||||||
@openFullMenu="openFullMenu"
|
@openFullMenu="openFullMenu"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -484,7 +476,7 @@ const documentation = computed<string | undefined>({
|
|||||||
<GraphNodeError
|
<GraphNodeError
|
||||||
v-if="warning && (nodeHovered || isSelected)"
|
v-if="warning && (nodeHovered || isSelected)"
|
||||||
class="afterNode warning"
|
class="afterNode warning"
|
||||||
:class="{ messageWithMenu: menuVisible !== MenuState.Off }"
|
:class="{ messageWithMenu: menuVisible }"
|
||||||
:message="warning"
|
:message="warning"
|
||||||
icon="warning"
|
icon="warning"
|
||||||
type="warning"
|
type="warning"
|
||||||
|
@ -15,6 +15,7 @@ const props = defineProps<{
|
|||||||
icon: Icon
|
icon: Icon
|
||||||
connectedSelfArgumentId: Ast.AstId | undefined
|
connectedSelfArgumentId: Ast.AstId | undefined
|
||||||
potentialSelfArgumentId: Ast.AstId | undefined
|
potentialSelfArgumentId: Ast.AstId | undefined
|
||||||
|
extended: boolean
|
||||||
}>()
|
}>()
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
openFullMenu: []
|
openFullMenu: []
|
||||||
@ -69,6 +70,7 @@ provideWidgetTree(
|
|||||||
toRef(props, 'icon'),
|
toRef(props, 'icon'),
|
||||||
toRef(props, 'connectedSelfArgumentId'),
|
toRef(props, 'connectedSelfArgumentId'),
|
||||||
toRef(props, 'potentialSelfArgumentId'),
|
toRef(props, 'potentialSelfArgumentId'),
|
||||||
|
toRef(props, 'extended'),
|
||||||
layoutTransitions.active,
|
layoutTransitions.active,
|
||||||
() => {
|
() => {
|
||||||
emit('openFullMenu')
|
emit('openFullMenu')
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import NodeWidget from '@/components/GraphEditor/NodeWidget.vue'
|
import NodeWidget from '@/components/GraphEditor/NodeWidget.vue'
|
||||||
import { WidgetInput, defineWidget, widgetProps } from '@/providers/widgetRegistry'
|
import { WidgetInput, defineWidget, widgetProps } from '@/providers/widgetRegistry'
|
||||||
|
import { injectWidgetTree } from '@/providers/widgetTree'
|
||||||
import { Ast } from '@/util/ast'
|
import { Ast } from '@/util/ast'
|
||||||
import { ArgumentApplication, ArgumentApplicationKey } from '@/util/callTree'
|
import { ArgumentApplication, ArgumentApplicationKey } from '@/util/callTree'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
const props = defineProps(widgetProps(widgetDefinition))
|
const props = defineProps(widgetProps(widgetDefinition))
|
||||||
|
const tree = injectWidgetTree()
|
||||||
const application = computed(() => props.input[ArgumentApplicationKey])
|
const application = computed(() => props.input[ArgumentApplicationKey])
|
||||||
const targetMaybePort = computed(() => {
|
const targetMaybePort = computed(() => {
|
||||||
const target = application.value.target
|
const target = application.value.target
|
||||||
@ -45,7 +47,13 @@ export const widgetDefinition = defineWidget(ArgumentApplicationKey, {
|
|||||||
<div v-if="application.infixOperator" class="infixOp" :style="operatorStyle">
|
<div v-if="application.infixOperator" class="infixOp" :style="operatorStyle">
|
||||||
<NodeWidget :input="WidgetInput.FromAst(application.infixOperator)" />
|
<NodeWidget :input="WidgetInput.FromAst(application.infixOperator)" />
|
||||||
</div>
|
</div>
|
||||||
<NodeWidget :input="application.argument.toWidgetInput()" nest />
|
<div
|
||||||
|
v-if="tree.extended || !application.argument.hideByDefault"
|
||||||
|
class="argument"
|
||||||
|
:class="{ animateWhenShown: application.argument.hideByDefault }"
|
||||||
|
>
|
||||||
|
<NodeWidget :input="application.argument.toWidgetInput()" nest />
|
||||||
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -75,4 +83,22 @@ export const widgetDefinition = defineWidget(ArgumentApplicationKey, {
|
|||||||
white-space: pre;
|
white-space: pre;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.argument {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animateWhenShown {
|
||||||
|
animation: show 4800ms 100ms cubic-bezier(0.38, 0.97, 0.56, 0.76) forwards;
|
||||||
|
max-width: 0;
|
||||||
|
overflow-x: clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes show {
|
||||||
|
100% {
|
||||||
|
max-width: 2000px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -148,6 +148,7 @@ export function useSelection<T>(
|
|||||||
},
|
},
|
||||||
deselectAll: () => selected.clear(),
|
deselectAll: () => selected.clear(),
|
||||||
isSelected: (element: T) => selected.has(element),
|
isSelected: (element: T) => selected.has(element),
|
||||||
|
isChanging: computed(() => anchor.value != null),
|
||||||
setSelection,
|
setSelection,
|
||||||
handleSelectionOf,
|
handleSelectionOf,
|
||||||
hoveredNode,
|
hoveredNode,
|
||||||
|
@ -14,6 +14,7 @@ const { provideFn, injectFn } = createContextStore(
|
|||||||
icon: Ref<Icon>,
|
icon: Ref<Icon>,
|
||||||
connectedSelfArgumentId: Ref<Ast.AstId | undefined>,
|
connectedSelfArgumentId: Ref<Ast.AstId | undefined>,
|
||||||
potentialSelfArgumentId: Ref<Ast.AstId | undefined>,
|
potentialSelfArgumentId: Ref<Ast.AstId | undefined>,
|
||||||
|
extended: Ref<boolean>,
|
||||||
hasActiveAnimations: Ref<boolean>,
|
hasActiveAnimations: Ref<boolean>,
|
||||||
emitOpenFullMenu: () => void,
|
emitOpenFullMenu: () => void,
|
||||||
) => {
|
) => {
|
||||||
@ -25,6 +26,7 @@ const { provideFn, injectFn } = createContextStore(
|
|||||||
icon,
|
icon,
|
||||||
connectedSelfArgumentId,
|
connectedSelfArgumentId,
|
||||||
potentialSelfArgumentId,
|
potentialSelfArgumentId,
|
||||||
|
extended,
|
||||||
nodeSpanStart,
|
nodeSpanStart,
|
||||||
hasActiveAnimations,
|
hasActiveAnimations,
|
||||||
emitOpenFullMenu,
|
emitOpenFullMenu,
|
||||||
|
@ -2,6 +2,7 @@ import type { PortId } from '@/providers/portInfo'
|
|||||||
import { WidgetInput } from '@/providers/widgetRegistry'
|
import { WidgetInput } from '@/providers/widgetRegistry'
|
||||||
import type { WidgetConfiguration } from '@/providers/widgetRegistry/configuration'
|
import type { WidgetConfiguration } from '@/providers/widgetRegistry/configuration'
|
||||||
import * as widgetCfg from '@/providers/widgetRegistry/configuration'
|
import * as widgetCfg from '@/providers/widgetRegistry/configuration'
|
||||||
|
import { DisplayMode } from '@/providers/widgetRegistry/configuration'
|
||||||
import type { SuggestionEntry, SuggestionEntryArgument } from '@/stores/suggestionDatabase/entry'
|
import type { SuggestionEntry, SuggestionEntryArgument } from '@/stores/suggestionDatabase/entry'
|
||||||
import { Ast } from '@/util/ast'
|
import { Ast } from '@/util/ast'
|
||||||
import { findLastIndex, tryGetIndex } from '@/util/data/array'
|
import { findLastIndex, tryGetIndex } from '@/util/data/array'
|
||||||
@ -23,7 +24,7 @@ export class ArgumentPlaceholder {
|
|||||||
public argInfo: SuggestionEntryArgument,
|
public argInfo: SuggestionEntryArgument,
|
||||||
public kind: ApplicationKind,
|
public kind: ApplicationKind,
|
||||||
public insertAsNamed: boolean,
|
public insertAsNamed: boolean,
|
||||||
public dynamicConfig?: WidgetConfiguration | undefined,
|
public dynamicConfig?: (WidgetConfiguration & { display?: DisplayMode }) | undefined,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
static WithRetrievedConfig(
|
static WithRetrievedConfig(
|
||||||
@ -52,6 +53,10 @@ export class ArgumentPlaceholder {
|
|||||||
get portId(): PortId {
|
get portId(): PortId {
|
||||||
return `${this.callId}[${this.index}]` as PortId
|
return `${this.callId}[${this.index}]` as PortId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get hideByDefault(): boolean {
|
||||||
|
return this.argInfo.hasDefault && this.dynamicConfig?.display !== DisplayMode.Always
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ArgumentAst {
|
export class ArgumentAst {
|
||||||
@ -88,6 +93,10 @@ export class ArgumentAst {
|
|||||||
get portId(): PortId {
|
get portId(): PortId {
|
||||||
return this.ast.id
|
return this.ast.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get hideByDefault(): boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type InterpretedCall = InterpretedInfix | InterpretedPrefix
|
type InterpretedCall = InterpretedInfix | InterpretedPrefix
|
||||||
|
Loading…
Reference in New Issue
Block a user