Skip to content

Commit

Permalink
Merge branch 'develop' into wip/sb/save-settings-sections
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] authored Nov 20, 2024
2 parents 3b688c3 + 04075c5 commit 2c2f32f
Show file tree
Hide file tree
Showing 265 changed files with 5,343 additions and 3,274 deletions.
1 change: 1 addition & 0 deletions .github/workflows/gui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,7 @@ jobs:
ENSO_TEST_USER: ${{ secrets.ENSO_CLOUD_TEST_ACCOUNT_USERNAME }}
ENSO_TEST_USER_PASSWORD: ${{ secrets.ENSO_CLOUD_TEST_ACCOUNT_PASSWORD }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
continue-on-error: true
- run: rm $HOME/.enso/credentials
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@
component.][11452]
- [New documentation editor provides improved Markdown editing experience, and
paves the way for new documentation features.][11469]
- [You can now add images to documentation panel][11547] by pasting them from
clipboard or by drag'n'dropping image files.
- ["Write" button in component menu allows to evaluate it separately from the
rest of the workflow][11523].
- [The documentation editor can now display tables][11564]

[11151]: https://github.com/enso-org/enso/pull/11151
[11271]: https://github.com/enso-org/enso/pull/11271
Expand All @@ -42,7 +45,9 @@
[11448]: https://github.com/enso-org/enso/pull/11448
[11452]: https://github.com/enso-org/enso/pull/11452
[11469]: https://github.com/enso-org/enso/pull/11469
[11547]: https://github.com/enso-org/enso/pull/11547
[11523]: https://github.com/enso-org/enso/pull/11523
[11564]: https://github.com/enso-org/enso/pull/11564

#### Enso Standard Library

Expand Down
7 changes: 7 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,10 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

---

This project includes components that are licensed under the MIT license. The
full text of the MIT license and its copyright notice can be found in the
`app/licenses/` directory.

4 changes: 2 additions & 2 deletions app/common/src/text/english.json
Original file line number Diff line number Diff line change
Expand Up @@ -805,8 +805,8 @@
"arbitraryFieldInvalid": "This field is invalid",
"arbitraryFieldTooShort": "This field is too short",
"arbitraryFieldTooLong": "This field is too long",
"arbitraryFieldTooSmall": "The value is too small, the minimum is $0",
"arbitraryFieldTooLarge": "The value is too large, the maximum is $0",
"arbitraryFieldTooSmall": "The value must be greater than $0",
"arbitraryFieldTooLarge": "The value must be less than $0",
"arbitraryFieldNotEqual": "This field is not equal to another field",
"arbitraryFieldNotMatch": "This field does not match the pattern",
"arbitraryFieldNotMatchAny": "This field does not match any of the patterns",
Expand Down
1 change: 1 addition & 0 deletions app/gui/e2e/project-view/locate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export const componentBrowser = componentLocator('.ComponentBrowser')
export const nodeOutputPort = componentLocator('.outputPortHoverArea')
export const smallPlusButton = componentLocator('.SmallPlusButton')
export const editorRoot = componentLocator('.EditorRoot')
export const nodeComment = componentLocator('.GraphNodeComment div[contentEditable]')

/**
* A not-selected variant of Component Browser Entry.
Expand Down
12 changes: 6 additions & 6 deletions app/gui/e2e/project-view/nodeClipboard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ test('Copy node with comment', async ({ page }) => {

// Check state before operation.
const originalNodes = await locate.graphNode(page).count()
await expect(page.locator('.GraphNodeComment')).toExist()
const originalNodeComments = await page.locator('.GraphNodeComment').count()
await expect(locate.nodeComment(page)).toExist()
const originalNodeComments = await locate.nodeComment(page).count()

// Select a node.
const nodeToCopy = locate.graphNodeByBinding(page, 'final')
Expand All @@ -48,16 +48,16 @@ test('Copy node with comment', async ({ page }) => {

// Node and comment have been copied.
await expect(locate.graphNode(page)).toHaveCount(originalNodes + 1)
await expect(page.locator('.GraphNodeComment')).toHaveCount(originalNodeComments + 1)
await expect(locate.nodeComment(page)).toHaveCount(originalNodeComments + 1)
})

test('Copy multiple nodes', async ({ page }) => {
await actions.goToGraph(page)

// Check state before operation.
const originalNodes = await locate.graphNode(page).count()
await expect(page.locator('.GraphNodeComment')).toExist()
const originalNodeComments = await page.locator('.GraphNodeComment').count()
await expect(locate.nodeComment(page)).toExist()
const originalNodeComments = await locate.nodeComment(page).count()

// Select some nodes.
const node1 = locate.graphNodeByBinding(page, 'final')
Expand All @@ -76,7 +76,7 @@ test('Copy multiple nodes', async ({ page }) => {
// Nodes and comment have been copied.
await expect(locate.graphNode(page)).toHaveCount(originalNodes + 2)
// `final` node has a comment.
await expect(page.locator('.GraphNodeComment')).toHaveCount(originalNodeComments + 1)
await expect(locate.nodeComment(page)).toHaveCount(originalNodeComments + 1)
// Check that two copied nodes are isolated, i.e. connected to each other, not original nodes.
await expect(locate.graphNodeByBinding(page, 'prod1')).toBeVisible()
await expect(locate.graphNodeByBinding(page, 'final1')).toBeVisible()
Expand Down
75 changes: 75 additions & 0 deletions app/gui/e2e/project-view/nodeComments.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import test from 'playwright/test'
import * as actions from './actions'
import { expect } from './customExpect'
import { CONTROL_KEY } from './keyboard'
import * as locate from './locate'

test('Edit comment by click', async ({ page }) => {
await actions.goToGraph(page)
const nodeComment = locate.nodeComment(locate.graphNodeByBinding(page, 'final'))
await expect(nodeComment).toHaveText('This node can be entered')

await nodeComment.click()
await page.keyboard.press(`${CONTROL_KEY}+A`)
const NEW_COMMENT = 'New comment text'
await nodeComment.fill(NEW_COMMENT)
await page.keyboard.press(`Enter`)
await expect(nodeComment).not.toBeFocused()
await expect(nodeComment).toHaveText(NEW_COMMENT)
})

test('Start editing comment via menu', async ({ page }) => {
await actions.goToGraph(page)
const node = locate.graphNodeByBinding(page, 'final')
await node.click()
await locate.circularMenu(node).getByRole('button', { name: 'More' }).click()
await locate.circularMenu(node).getByRole('button', { name: 'Comment' }).click()
await expect(locate.nodeComment(node)).toBeFocused()
})

test('Add new comment via menu', async ({ page }) => {
await actions.goToGraph(page)
const INITIAL_NODE_COMMENTS = 1
await expect(locate.nodeComment(page)).toHaveCount(INITIAL_NODE_COMMENTS)
const node = locate.graphNodeByBinding(page, 'data')
const nodeComment = locate.nodeComment(node)

await node.click()
await locate.circularMenu(node).getByRole('button', { name: 'More' }).click()
await locate.circularMenu(node).getByRole('button', { name: 'Comment' }).click()
await expect(locate.nodeComment(node)).toBeFocused()
const NEW_COMMENT = 'New comment text'
await nodeComment.fill(NEW_COMMENT)
await page.keyboard.press(`Enter`)
await expect(nodeComment).not.toBeFocused()
await expect(nodeComment).toHaveText(NEW_COMMENT)
await expect(locate.nodeComment(page)).toHaveCount(INITIAL_NODE_COMMENTS + 1)
})

test('Delete comment by clearing text', async ({ page }) => {
await actions.goToGraph(page)
const nodeComment = locate.nodeComment(locate.graphNodeByBinding(page, 'final'))
await expect(nodeComment).toHaveText('This node can be entered')

await nodeComment.click()
await page.keyboard.press(`${CONTROL_KEY}+A`)
await page.keyboard.press(`Delete`)
await page.keyboard.press(`Enter`)
await expect(nodeComment).not.toExist()
})

test('URL added to comment is rendered as link', async ({ page }) => {
await actions.goToGraph(page)
const nodeComment = locate.nodeComment(locate.graphNodeByBinding(page, 'final'))
await expect(nodeComment).toHaveText('This node can be entered')
await expect(nodeComment.locator('a')).not.toExist()

await nodeComment.click()
await page.keyboard.press(`${CONTROL_KEY}+A`)
const NEW_COMMENT = "Here's a URL: https://example.com"
await nodeComment.fill(NEW_COMMENT)
await page.keyboard.press(`Enter`)
await expect(nodeComment).not.toBeFocused()
await expect(nodeComment).toHaveText(NEW_COMMENT)
await expect(nodeComment.locator('a')).toHaveCount(1)
})
11 changes: 8 additions & 3 deletions app/gui/e2e/project-view/rightPanel.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ import * as locate from './locate'
test('Main method documentation', async ({ page }) => {
await actions.goToGraph(page)

const rightDock = locate.rightDock(page)
// Documentation panel hotkey opens right-dock.
await expect(locate.rightDock(page)).toBeHidden()
await expect(rightDock).toBeHidden()
await page.keyboard.press(`${CONTROL_KEY}+D`)
await expect(locate.rightDock(page)).toBeVisible()
await expect(rightDock).toBeVisible()

// Right-dock displays main method documentation.
await expect(locate.editorRoot(locate.rightDock(page))).toHaveText('The main method')
await expect(locate.editorRoot(rightDock)).toContainText('The main method')
// All three images are loaded properly
await expect(rightDock.getByAltText('Image')).toHaveCount(3)
for (const img of await rightDock.getByAltText('Image').all())
await expect(img).toHaveJSProperty('naturalWidth', 3)

// Documentation hotkey closes right-dock.p
await page.keyboard.press(`${CONTROL_KEY}+D`)
Expand Down
3 changes: 2 additions & 1 deletion app/gui/e2e/project-view/undoRedo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ test('Removing node', async ({ page }) => {
await page.keyboard.press(`${CONTROL_KEY}+Z`)
await expect(locate.graphNode(page)).toHaveCount(nodesCount)
await expect(deletedNode.locator('.WidgetToken')).toHaveText(['Main', '.', 'func1', 'prod'])
await expect(deletedNode.locator('.GraphNodeComment')).toHaveText('This node can be entered')
await expect(locate.nodeComment(deletedNode)).toHaveText('This node can be entered')

const restoredBBox = await deletedNode.boundingBox()
expect(restoredBBox).toEqual(deletedNodeBBox)

Expand Down
4 changes: 1 addition & 3 deletions app/gui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"build-cloud": "cross-env CLOUD_BUILD=true corepack pnpm run build",
"preview": "vite preview",
"//": "max-warnings set to 41 to match the amount of warnings introduced by the new react compiler. Eventual goal is to remove all the warnings.",
"lint": "eslint . --max-warnings=41",
"lint": "eslint . --max-warnings=39",
"format": "prettier --version && prettier --write src/ && eslint . --fix",
"dev:vite": "vite",
"test": "corepack pnpm run /^^^^test:.*/",
Expand Down Expand Up @@ -94,7 +94,6 @@
"@lexical/plain-text": "^0.16.0",
"@lexical/utils": "^0.16.0",
"@lezer/common": "^1.1.0",
"@lezer/markdown": "^1.3.1",
"@lezer/highlight": "^1.1.6",
"@noble/hashes": "^1.4.0",
"@vueuse/core": "^10.4.1",
Expand All @@ -118,7 +117,6 @@
"veaury": "^2.3.18",
"vue": "^3.5.2",
"vue-component-type-helpers": "^2.0.29",
"y-codemirror.next": "^0.3.2",
"y-protocols": "^1.0.5",
"y-textarea": "^1.0.0",
"y-websocket": "^1.5.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export function useForm<Schema extends types.TSchema, SubmitResult = void>(
errorMap: (issue) => {
switch (issue.code) {
case 'too_small':
if (issue.minimum === 0) {
if (issue.minimum === 1 && issue.type === 'string') {
return {
message: getText('arbitraryFieldRequired'),
}
Expand Down
17 changes: 16 additions & 1 deletion app/gui/src/dashboard/components/AriaComponents/Text/Text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ export interface TextProps
readonly elementType?: keyof HTMLElementTagNameMap
readonly lineClamp?: number
readonly tooltip?: React.ReactElement | string | false | null
readonly tooltipTriggerRef?: React.RefObject<HTMLElement>
readonly tooltipDisplay?: visualTooltip.VisualTooltipProps['display']
readonly tooltipPlacement?: aria.Placement
readonly tooltipOffset?: number
readonly tooltipCrossOffset?: number
}

export const TEXT_STYLE = twv.tv({
Expand Down Expand Up @@ -134,8 +137,11 @@ export const Text = forwardRef(function Text(props: TextProps, ref: React.Ref<HT
balance,
elementType: ElementType = 'span',
tooltip: tooltipElement = children,
tooltipTriggerRef,
tooltipDisplay = 'whenOverflowing',
tooltipPlacement,
tooltipOffset,
tooltipCrossOffset,
textSelection,
disableLineHeightCompensation = false,
...ariaProps
Expand Down Expand Up @@ -176,9 +182,18 @@ export const Text = forwardRef(function Text(props: TextProps, ref: React.Ref<HT
const { tooltip, targetProps } = visualTooltip.useVisualTooltip({
isDisabled: isTooltipDisabled(),
targetRef: textElementRef,
triggerRef: tooltipTriggerRef,
display: tooltipDisplay,
children: tooltipElement,
...(tooltipPlacement ? { overlayPositionProps: { placement: tooltipPlacement } } : {}),
...(tooltipPlacement || tooltipOffset != null ?
{
overlayPositionProps: {
...(tooltipPlacement && { placement: tooltipPlacement }),
...(tooltipOffset != null && { offset: tooltipOffset }),
...(tooltipCrossOffset != null && { crossOffset: tooltipCrossOffset }),
},
}
: {}),
})

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ export interface VisualTooltipProps
readonly children: React.ReactNode
readonly className?: string
readonly targetRef: React.RefObject<HTMLElement>
readonly triggerRef?: React.RefObject<HTMLElement> | undefined
readonly isDisabled?: boolean
readonly overlayPositionProps?: Pick<
aria.AriaPositionProps,
'containerPadding' | 'offset' | 'placement'
'containerPadding' | 'crossOffset' | 'offset' | 'placement'
>
/**
* Determines when the tooltip should be displayed.
Expand Down Expand Up @@ -56,6 +57,7 @@ export function useVisualTooltip(props: VisualTooltipProps): VisualTooltipReturn
const {
children,
targetRef,
triggerRef = targetRef,
className,
isDisabled = false,
overlayPositionProps = {},
Expand All @@ -70,6 +72,7 @@ export function useVisualTooltip(props: VisualTooltipProps): VisualTooltipReturn
const {
containerPadding = 0,
offset = DEFAULT_OFFSET,
crossOffset = 0,
placement = 'bottom',
} = overlayPositionProps

Expand Down Expand Up @@ -115,8 +118,9 @@ export function useVisualTooltip(props: VisualTooltipProps): VisualTooltipReturn
const { overlayProps, updatePosition } = aria.useOverlayPosition({
isOpen: state.isOpen,
overlayRef: popoverRef,
targetRef,
targetRef: triggerRef,
offset,
crossOffset,
placement,
containerPadding,
})
Expand Down
20 changes: 9 additions & 11 deletions app/gui/src/dashboard/components/Autocomplete.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/** @file A select menu with a dropdown. */
import {
useEffect,
useMemo,
useRef,
useState,
Expand Down Expand Up @@ -92,22 +91,15 @@ export default function Autocomplete<T>(props: AutocompleteProps<T>) {
const [selectedIndex, setSelectedIndex] = useState<number | null>(null)
const valuesSet = useMemo(() => new Set(values), [values])
const canEditText = setText != null && values.length === 0
// We are only interested in the initial value of `canEditText` in effects.
const canEditTextRef = useRef(canEditText)
const isMultipleAndCustomValue = multiple === true && text != null
const matchingItems = useMemo(
() => (text == null ? items : items.filter((item) => matches(item, text))),
[items, matches, text],
)

useEffect(() => {
if (!canEditTextRef.current) {
setIsDropdownVisible(true)
}
}, [])

const fallbackInputRef = useRef<HTMLFieldSetElement>(null)
const inputRef = rawInputRef ?? fallbackInputRef
const containerRef = useRef<HTMLDivElement>(null)

// This type is a little too wide but it is unavoidable.
/** Set values, while also changing the input text. */
Expand Down Expand Up @@ -184,6 +176,7 @@ export default function Autocomplete<T>(props: AutocompleteProps<T>) {
return (
<div className={twJoin('relative isolate h-6 w-full', isDropdownVisible && 'z-1')}>
<div
ref={containerRef}
onKeyDown={onKeyDown}
className={twMerge(
'absolute w-full grow transition-colors',
Expand Down Expand Up @@ -259,7 +252,7 @@ export default function Autocomplete<T>(props: AutocompleteProps<T>) {
<div
key={itemToKey(item)}
className={twMerge(
'text relative cursor-pointer whitespace-nowrap px-input-x last:rounded-b-xl hover:bg-hover-bg',
'text relative min-w-max cursor-pointer whitespace-nowrap rounded-full px-input-x last:rounded-b-xl hover:bg-hover-bg',
valuesSet.has(item) && 'bg-hover-bg',
index === selectedIndex && 'bg-black/5',
)}
Expand All @@ -271,7 +264,12 @@ export default function Autocomplete<T>(props: AutocompleteProps<T>) {
toggleValue(item)
}}
>
<Text truncate="1" className="w-full" tooltipPlacement="left">
<Text
truncate="1"
className="w-full"
tooltipPlacement="top"
tooltipTriggerRef={containerRef}
>
{children(item)}
</Text>
</div>
Expand Down
Loading

0 comments on commit 2c2f32f

Please sign in to comment.