mirror of
https://github.com/enso-org/enso.git
synced 2024-11-22 03:32:23 +03:00
Merge branch 'develop' into wip/sb/async-execution
This commit is contained in:
commit
b7e5b812ca
196
.github/workflows/gui-checks.yml
vendored
Normal file
196
.github/workflows/gui-checks.yml
vendored
Normal file
@ -0,0 +1,196 @@
|
||||
name: GUI Checks
|
||||
on: workflow_call
|
||||
|
||||
# Cancel in-progress workflows if a new one is started
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-gui-checks
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read # Read-only access to repository contents
|
||||
issues: write # Write access to issues
|
||||
pull-requests: write # Write access to pull requests
|
||||
statuses: write # Write access to commit statuses
|
||||
checks: write
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: 👮 Lint GUI
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
|
||||
- name: 📦 Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
name: ⎔ Setup Node
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: "pnpm"
|
||||
|
||||
- uses: actions/cache/restore@v4
|
||||
name: Download cache
|
||||
id: cache
|
||||
with:
|
||||
path: |
|
||||
**/.eslintcache
|
||||
node_modules/.cache/prettier
|
||||
key: ${{ runner.os }}-gui-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gui
|
||||
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Installing wasm-pack
|
||||
uses: jetli/wasm-pack-action@v0.4.0
|
||||
with:
|
||||
version: v0.12.1
|
||||
|
||||
- name: 📦 Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: 📝 Prettier
|
||||
id: prettier
|
||||
continue-on-error: true
|
||||
run: pnpm run ci:prettier
|
||||
|
||||
# Next Tasks are depend on Typecheck, because we build libraries at this stage
|
||||
- name: 🧠 Typecheck
|
||||
id: typecheck
|
||||
continue-on-error: true
|
||||
run: pnpm run ci:typecheck
|
||||
|
||||
- name: 🧹 Lint
|
||||
id: lint
|
||||
continue-on-error: true
|
||||
run: pnpm run ci:lint
|
||||
|
||||
- name: 🧪 Unit Tests
|
||||
id: unit-tests
|
||||
continue-on-error: true
|
||||
run: pnpm run ci:test
|
||||
|
||||
- name: 💾 Save cache
|
||||
uses: actions/cache/save@v4
|
||||
if: always() && steps.cache.outputs.cache-hit != 'true'
|
||||
id: save-cache
|
||||
with:
|
||||
key: ${{ steps.cache.outputs.cache-primary-key }}
|
||||
path: |
|
||||
**/.eslintcache
|
||||
node_modules/.cache/prettier
|
||||
|
||||
- name: 📝 Annotate Code Linting Results
|
||||
uses: ataylorme/eslint-annotate-action@v3
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
report-json: "./**/eslint_report.json"
|
||||
markdown-report-on-step-summary: true
|
||||
check-name: 🧹 GUI Lint Results
|
||||
|
||||
- name: ❌ Fail if any check failed
|
||||
if: always() && (steps.prettier.outcome == 'failure' || steps.lint.outcome == 'failure' || steps.typecheck.outcome == 'failure' || steps.unit-tests.outcome == 'failure')
|
||||
run: |
|
||||
echo "Prettier outcome: ${{ steps.prettier.outcome }}"
|
||||
echo "Lint outcome: ${{ steps.lint.outcome }}"
|
||||
echo "Typecheck outcome: ${{ steps.typecheck.outcome }}"
|
||||
echo "Unit tests outcome: ${{ steps.unit-tests.outcome }}"
|
||||
exit 1
|
||||
|
||||
playwright:
|
||||
name: 🎭 Playwright Tests
|
||||
env:
|
||||
NODE_OPTIONS: --disable-warning=ExperimentalWarning
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- Linux
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 24
|
||||
matrix:
|
||||
shardIndex: [1, 2, 3, 4, 5, 6]
|
||||
shardTotal: [6]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: 📦 Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
name: ⎔ Setup Node
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: "pnpm"
|
||||
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Installing wasm-pack
|
||||
uses: jetli/wasm-pack-action@v0.4.0
|
||||
with:
|
||||
version: v0.12.1
|
||||
|
||||
- name: 📦 Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: 📺 Install Playwright Browsers
|
||||
working-directory: app/gui
|
||||
run: pnpm run playwright:install
|
||||
|
||||
- name: 🎭 Playwright Tests
|
||||
working-directory: app/gui
|
||||
run: pnpm run e2e --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
||||
|
||||
- name: ⬆️ Upload blob report to GitHub Actions Artifacts
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: blob-report-${{ matrix.shardIndex }}
|
||||
path: app/gui/blob-report
|
||||
retention-days: 7
|
||||
|
||||
merge-reports:
|
||||
name: 🔗 Merge Playwright Reports
|
||||
if: ${{ !cancelled() }}
|
||||
needs: [playwright]
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: 📦 Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: "pnpm"
|
||||
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Installing wasm-pack
|
||||
uses: jetli/wasm-pack-action@v0.4.0
|
||||
with:
|
||||
version: v0.12.1
|
||||
|
||||
- name: 📦 Install dependencies
|
||||
run: pnpm install --frozen-lockfile --ignore-scripts
|
||||
|
||||
- name: 📥 Download blob reports from GitHub Actions Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: app/gui/blob-report/
|
||||
pattern: blob-report-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: 🔗 Merge into HTML Report
|
||||
working-directory: app/gui
|
||||
run: pnpm playwright merge-reports --reporter html ./blob-report
|
||||
|
||||
- name: ⬆️ Upload HTML report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-report--attempt-${{ github.run_attempt }}
|
||||
path: app/gui/playwright-report/
|
||||
retention-days: 14
|
83
.github/workflows/gui-pull-request.yml
vendored
Normal file
83
.github/workflows/gui-pull-request.yml
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
# This file is not auto-generated. Feel free to edit it.
|
||||
|
||||
name: ✨ GUI Pull Request
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read # Read-only access to repository contents
|
||||
issues: write # Write access to issues
|
||||
pull-requests: write # Write access to pull requests
|
||||
statuses: write # Write access to commit statuses
|
||||
checks: write
|
||||
|
||||
jobs:
|
||||
changed-files:
|
||||
runs-on: ubuntu-latest
|
||||
name: 🔍 Detect changed files in GUI
|
||||
outputs:
|
||||
all_changed_files: ${{ steps.changed-files.outputs.all_changed_files }}
|
||||
any_changed: ${{ steps.changed-files.outputs.any_changed }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v44
|
||||
with:
|
||||
files: |
|
||||
app/**
|
||||
package.json
|
||||
pnpm-lock.yaml
|
||||
pnpm-workspace.yaml
|
||||
eslint.config.js
|
||||
.prettierrc.js
|
||||
.prettierignore
|
||||
vitest.workspace.ts
|
||||
files_ignore: |
|
||||
app/ide-desktop/**
|
||||
app/gui/scripts/**
|
||||
app/gui/.gitignore
|
||||
.git-*
|
||||
|
||||
- name: List all changed files
|
||||
env:
|
||||
ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
||||
run: |
|
||||
for file in ${ALL_CHANGED_FILES}; do
|
||||
echo "$file was changed"
|
||||
done
|
||||
|
||||
checks:
|
||||
name: 🧰 Checks
|
||||
uses: ./.github/workflows/gui-checks.yml
|
||||
needs: [changed-files]
|
||||
if: ${{ needs.changed-files.outputs.any_changed == 'true' }}
|
||||
secrets: inherit
|
||||
|
||||
storybook:
|
||||
name: 📚 Deploy Storybook
|
||||
uses: ./.github/workflows/storybook.yml
|
||||
needs: [changed-files]
|
||||
if: ${{ needs.changed-files.outputs.any_changed == 'true' }}
|
||||
secrets: inherit
|
||||
|
||||
# This job is used to report success if the needed jobs were successful.
|
||||
# This is a workaround to make optional jobs required if they run
|
||||
report-success:
|
||||
name: ✅ Success or skipped due to no changes
|
||||
runs-on: ubuntu-latest
|
||||
needs: [checks, storybook]
|
||||
if: needs.checks.result == 'skipped' && needs.storybook.result == 'skipped' || needs.checks.result == 'success' && needs.storybook.result == 'success'
|
||||
steps:
|
||||
- name: Report success
|
||||
run: echo "Success!"
|
43
.github/workflows/gui-tests.yml
vendored
43
.github/workflows/gui-tests.yml
vendored
@ -27,49 +27,6 @@ jobs:
|
||||
access_token: ${{ github.token }}
|
||||
permissions:
|
||||
actions: write
|
||||
enso-build-ci-gen-job-gui-check-linux-amd64:
|
||||
name: GUI tests (linux, amd64)
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- Linux
|
||||
steps:
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Installing wasm-pack
|
||||
uses: jetli/wasm-pack-action@v0.4.0
|
||||
with:
|
||||
version: v0.12.1
|
||||
- name: Expose Artifact API and context information.
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: "\n core.exportVariable(\"ACTIONS_RUNTIME_TOKEN\", process.env[\"ACTIONS_RUNTIME_TOKEN\"])\n core.exportVariable(\"ACTIONS_RUNTIME_URL\", process.env[\"ACTIONS_RUNTIME_URL\"])\n core.exportVariable(\"GITHUB_RETENTION_DAYS\", process.env[\"GITHUB_RETENTION_DAYS\"])\n console.log(context)\n "
|
||||
- name: Checking out the repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
submodules: recursive
|
||||
- name: Build Script Setup
|
||||
run: ./run --help || (git clean -ffdx && ./run --help)
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- if: "(contains(github.event.pull_request.labels.*.name, 'CI: Clean build required') || inputs.clean_build_required)"
|
||||
name: Clean before
|
||||
run: ./run git-clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- run: ./run gui check
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- if: failure() && runner.os == 'Windows'
|
||||
name: List files if failed (Windows)
|
||||
run: Get-ChildItem -Force -Recurse
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
run: ls -lAR
|
||||
- if: "(always()) && (contains(github.event.pull_request.labels.*.name, 'CI: Clean build required') || inputs.clean_build_required)"
|
||||
name: Clean after
|
||||
run: ./run git-clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
enso-build-ci-gen-job-lint-linux-amd64:
|
||||
name: Lint (linux, amd64)
|
||||
runs-on:
|
||||
|
104
.github/workflows/storybook.yml
vendored
Normal file
104
.github/workflows/storybook.yml
vendored
Normal file
@ -0,0 +1,104 @@
|
||||
# This file is not auto-generated. Feel free to edit it.
|
||||
|
||||
name: Storybook Chromatic Deployment
|
||||
|
||||
on: workflow_call
|
||||
|
||||
# Cancel in-progress workflows if a new one is started
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-chromatic
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read # Read-only access to repository contents
|
||||
issues: write # Write access to issues
|
||||
pull-requests: write # Write access to pull requests
|
||||
statuses: write # Write access to commit statuses
|
||||
|
||||
env:
|
||||
ENSO_BUILD_SKIP_VERSION_CHECK: "true"
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
|
||||
jobs:
|
||||
deploy-chromatic-react:
|
||||
name: 🚀 Deploy React to Chromatic
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
dashboardUrl: ${{ steps.publish_chromatic.outputs.url }}
|
||||
dashboardStorybookUrl: ${{ steps.publish_chromatic.outputs.storybookUrl }}
|
||||
env:
|
||||
CHROMATIC_RETRIES: 3
|
||||
CHROMATIC_PROJECT_TOKEN: ${{ secrets.DASHBOARD_CHROMATIC_PROJECT_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
name: Checkout
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
fetch-tags: false
|
||||
|
||||
- name: 📦 Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
name: ⎔ Setup Node
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: "pnpm"
|
||||
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Installing wasm-pack
|
||||
uses: jetli/wasm-pack-action@v0.4.0
|
||||
with:
|
||||
version: v0.12.1
|
||||
|
||||
- name: 📦 Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: 📥 Download storybook cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: ${{ runner.os }}-gui-${{ github.run_id }}
|
||||
path: app/gui/node_modules/.cache/
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gui
|
||||
|
||||
- name: 🚀 Deploy to Chromatic
|
||||
id: publish_chromatic
|
||||
uses: chromaui/action@v11
|
||||
with:
|
||||
workingDir: app/gui
|
||||
autoAcceptChanges: develop
|
||||
exitZeroOnChanges: true
|
||||
exitOnceUploaded: true
|
||||
configFile: "chromatic.config.json"
|
||||
|
||||
comment-on-pr:
|
||||
name: 💬 Comment on PR
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy-chromatic-react
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
- name: 💬 Comment on PR
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const { setMessage } = await import('${{ github.workspace }}/app/gui/scripts/ci/set-message.js')
|
||||
|
||||
await setMessage({
|
||||
header: "## 🧪 Storybook is successfully deployed!",
|
||||
body: `
|
||||
### 📊 Dashboard:
|
||||
- 👀 Review changes: ${{ needs.deploy-chromatic-react.outputs.dashboardUrl }}
|
||||
- 👨🎨 Preview storybook: ${{ needs.deploy-chromatic-react.outputs.dashboardStorybookUrl }}
|
||||
`,
|
||||
github,
|
||||
repo: context.repo,
|
||||
prNumber: context.payload.pull_request.number
|
||||
})
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -37,6 +37,8 @@ generated/
|
||||
############
|
||||
|
||||
node_modules/
|
||||
eslint_report.json
|
||||
.eslintcache
|
||||
|
||||
############
|
||||
## System ##
|
||||
|
@ -29,7 +29,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
"lint": "eslint . --max-warnings=0"
|
||||
"lint": "eslint . --cache --max-warnings=0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tanstack/query-core": "5.54.1",
|
||||
|
@ -46,24 +46,9 @@ export async function readEnvironmentFromFile() {
|
||||
}
|
||||
process.env.ENSO_CLOUD_DASHBOARD_VERSION ??= buildInfo.version ?? '0.0.0-dev'
|
||||
process.env.ENSO_CLOUD_DASHBOARD_COMMIT_HASH ??= buildInfo.commit
|
||||
} catch (error) {
|
||||
} catch {
|
||||
process.env.ENSO_CLOUD_DASHBOARD_VERSION ??= buildInfo.version
|
||||
process.env.ENSO_CLOUD_DASHBOARD_COMMIT_HASH ??= buildInfo.commit
|
||||
const expectedKeys = Object.keys(DUMMY_DEFINES)
|
||||
.map(key => key.replace(/^process[.]env[.]/, ''))
|
||||
.filter(key => key !== 'NODE_ENV')
|
||||
/** @type {string[]} */
|
||||
const missingKeys = []
|
||||
for (const key of expectedKeys) {
|
||||
if (!(key in process.env)) {
|
||||
missingKeys.push(key)
|
||||
}
|
||||
}
|
||||
if (missingKeys.length !== 0) {
|
||||
console.warn('Could not load `.env` file; disabling cloud backend.')
|
||||
console.warn(`Missing keys: ${missingKeys.map(key => `'${key}'`).join(', ')}`)
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
4
app/gui/.gitignore
vendored
4
app/gui/.gitignore
vendored
@ -25,7 +25,11 @@ mockDist
|
||||
|
||||
test-results/
|
||||
playwright-report/
|
||||
blob-report/
|
||||
playwright/
|
||||
|
||||
src/project-view/util/iconList.json
|
||||
src/project-view/util/iconName.ts
|
||||
|
||||
*storybook.log
|
||||
storybook-static
|
||||
|
9
app/gui/.storybook/env.d.ts
vendored
Normal file
9
app/gui/.storybook/env.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
ENV: {
|
||||
FRAMEWORK: 'vue' | 'react'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {}
|
63
app/gui/.storybook/main.ts
Normal file
63
app/gui/.storybook/main.ts
Normal file
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* Main file for Storybook configuration.
|
||||
*/
|
||||
import type { StorybookConfig as ReactStorybookConfig } from '@storybook/react-vite'
|
||||
import type { StorybookConfig as VueStorybookConfig } from '@storybook/vue3-vite'
|
||||
import z from 'zod'
|
||||
|
||||
const framework = z.enum(['vue', 'react']).parse(process.env.FRAMEWORK)
|
||||
|
||||
const sharedConfig: Partial<ReactStorybookConfig> = {
|
||||
addons: [
|
||||
'@storybook/addon-onboarding',
|
||||
'@storybook/addon-essentials',
|
||||
'@chromatic-com/storybook',
|
||||
'@storybook/addon-interactions',
|
||||
],
|
||||
features: {},
|
||||
core: { disableTelemetry: true },
|
||||
env: { FRAMEWORK: framework },
|
||||
|
||||
previewHead: (head) => {
|
||||
return `
|
||||
<script>
|
||||
window.global = window;
|
||||
window.ENV = {
|
||||
FRAMEWORK: '${framework}',
|
||||
}
|
||||
</script>
|
||||
${head}
|
||||
`
|
||||
},
|
||||
}
|
||||
|
||||
const vueConfig: VueStorybookConfig = {
|
||||
...sharedConfig,
|
||||
stories: [
|
||||
'../src/project-view/**/*.mdx',
|
||||
'../src/project-view/**/*.stories.@(js|jsx|mjs|ts|tsx)',
|
||||
],
|
||||
framework: {
|
||||
name: '@storybook/vue3-vite',
|
||||
options: {},
|
||||
},
|
||||
refs: {
|
||||
Dashboard: {
|
||||
title: 'Dashboard',
|
||||
url: 'http://localhost:6007',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const reactConfig: ReactStorybookConfig = {
|
||||
...sharedConfig,
|
||||
stories: ['../src/dashboard/**/*.mdx', '../src/dashboard/**/*.stories.tsx'],
|
||||
framework: {
|
||||
name: '@storybook/react-vite',
|
||||
options: { strictMode: true },
|
||||
},
|
||||
}
|
||||
|
||||
export default framework === 'vue' ? vueConfig : reactConfig
|
69
app/gui/.storybook/preview.tsx
Normal file
69
app/gui/.storybook/preview.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* @file Storybook preview
|
||||
*/
|
||||
import type { Preview as ReactPreview } from '@storybook/react'
|
||||
import type { Preview as VuePreview } from '@storybook/vue3'
|
||||
import React, { useLayoutEffect, useState } from 'react'
|
||||
import invariant from 'tiny-invariant'
|
||||
import UIProviders from '../src/dashboard/components/UIProviders'
|
||||
|
||||
import z from 'zod'
|
||||
import '../src/dashboard/tailwind.css'
|
||||
|
||||
const framework = z.enum(['vue', 'react']).parse(window.ENV.FRAMEWORK)
|
||||
|
||||
const vuePreview: VuePreview = {
|
||||
parameters: {
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/i,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const reactPreview: ReactPreview = {
|
||||
parameters: {
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/i,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Decorators for all stories
|
||||
// Decorators are applied in the reverse order they are defined
|
||||
decorators: [
|
||||
(Story, context) => {
|
||||
const [portalRoot, setPortalRoot] = useState<Element | null>(null)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const portalRoot = document.querySelector('#enso-portal-root')
|
||||
invariant(portalRoot, 'PortalRoot element not found')
|
||||
|
||||
setPortalRoot(portalRoot)
|
||||
}, [])
|
||||
|
||||
if (!portalRoot) return <></>
|
||||
|
||||
return (
|
||||
<UIProviders locale="en-US" portalRoot={portalRoot}>
|
||||
{Story(context)}
|
||||
</UIProviders>
|
||||
)
|
||||
},
|
||||
|
||||
(Story, context) => (
|
||||
<>
|
||||
<div className="enso-dashboard">{Story(context)}</div>
|
||||
<div id="enso-portal-root" className="enso-portal-root" />
|
||||
</>
|
||||
),
|
||||
],
|
||||
}
|
||||
|
||||
const preview = framework === 'vue' ? vuePreview : reactPreview
|
||||
|
||||
export default preview
|
8
app/gui/chromatic.config.json
Normal file
8
app/gui/chromatic.config.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://www.chromatic.com/config-file.schema.json",
|
||||
"projectId": "Enso Dashboard",
|
||||
"autoAcceptChanges": "main",
|
||||
"exitOnceUploaded": true,
|
||||
"skip": "dependabot/**",
|
||||
"buildScriptName": "build-storybook:react"
|
||||
}
|
@ -22,18 +22,22 @@
|
||||
"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=39",
|
||||
"lint": "eslint . --cache --max-warnings=39",
|
||||
"format": "prettier --version && prettier --write src/ && eslint . --fix",
|
||||
"dev:vite": "vite",
|
||||
"test": "corepack pnpm run /^^^^test:.*/",
|
||||
"test:unit": "vitest run",
|
||||
"test-dev:unit": "vitest",
|
||||
"test:e2e": "cross-env NODE_ENV=production playwright test",
|
||||
"test-dev:e2e": "cross-env NODE_ENV=production playwright test --ui",
|
||||
"test-dev-dashboard:e2e": "cross-env NODE_ENV=production playwright test ./e2e/dashboard/ --ui",
|
||||
"preinstall": "corepack pnpm run generate-metadata",
|
||||
"postinstall": "playwright install",
|
||||
"generate-metadata": "node scripts/generateIconMetadata.js"
|
||||
"generate-metadata": "node scripts/generateIconMetadata.js",
|
||||
"build-storybook:react": "cross-env FRAMEWORK=react storybook build",
|
||||
"build-storybook:vue": "cross-env FRAMEWORK=vue storybook build",
|
||||
"chromatic:react": "cross-env FRAMEWORK=react chromatic deploy",
|
||||
"chromatic:vue": "cross-env FRAMEWORK=vue chromatic deploy",
|
||||
"e2e": "cross-env NODE_ENV=production playwright test",
|
||||
"playwright:install": "playwright install chromium"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-amplify/auth": "5.6.5",
|
||||
@ -59,7 +63,7 @@
|
||||
"react": "^18.3.1",
|
||||
"react-aria": "^3.34.3",
|
||||
"react-aria-components": "^1.3.3",
|
||||
"react-compiler-runtime": "19.0.0-beta-6fc168f-20241025",
|
||||
"react-compiler-runtime": "19.0.0-beta-a7bf2bd-20241110",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-error-boundary": "4.0.13",
|
||||
"react-hook-form": "^7.51.4",
|
||||
@ -80,7 +84,7 @@
|
||||
"@ag-grid-enterprise/core": "^31.1.1",
|
||||
"@ag-grid-enterprise/range-selection": "^31.1.1",
|
||||
"@babel/parser": "^7.24.7",
|
||||
"babel-plugin-react-compiler": "19.0.0-beta-6fc168f-20241025",
|
||||
"babel-plugin-react-compiler": "19.0.0-beta-a7bf2bd-20241110",
|
||||
"@codemirror/commands": "^6.6.0",
|
||||
"@codemirror/language": "^6.10.2",
|
||||
"@codemirror/lang-markdown": "^v6.3.0",
|
||||
@ -125,10 +129,20 @@
|
||||
"marked": "14.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chromatic-com/storybook": "^3.2.2",
|
||||
"@fast-check/vitest": "^0.0.8",
|
||||
"@modyfi/vite-plugin-yaml": "^1.0.4",
|
||||
"@playwright/test": "^1.40.0",
|
||||
"@react-types/shared": "^3.22.1",
|
||||
"@storybook/addon-essentials": "^8.4.2",
|
||||
"@storybook/addon-interactions": "^8.4.2",
|
||||
"@storybook/addon-onboarding": "^8.4.2",
|
||||
"@storybook/blocks": "^8.4.2",
|
||||
"@storybook/react": "^8.4.2",
|
||||
"@storybook/react-vite": "^8.4.2",
|
||||
"@storybook/test": "^8.4.2",
|
||||
"@storybook/vue3": "^8.4.2",
|
||||
"@storybook/vue3-vite": "^8.4.2",
|
||||
"@tanstack/react-query-devtools": "5.45.1",
|
||||
"@types/node": "^22.9.0",
|
||||
"@types/react": "^18.0.27",
|
||||
@ -181,6 +195,8 @@
|
||||
"react-dom": "^18.3.1",
|
||||
"shuffle-seed": "^1.1.6",
|
||||
"sql-formatter": "^13.0.0",
|
||||
"storybook": "^8.4.2",
|
||||
"chromatic": "11.18.1",
|
||||
"tar": "^6.2.1",
|
||||
"tsx": "^4.7.1",
|
||||
"vite-plugin-vue-devtools": "7.6.3",
|
||||
|
@ -19,7 +19,8 @@ const TIMEOUT_MS =
|
||||
: 15_000
|
||||
|
||||
// We tend to use less CPU on CI to reduce the number of failures due to timeouts.
|
||||
const WORKERS = isCI ? '25%' : '35%'
|
||||
// Instead of using workers on CI, we use shards to run tests in parallel.
|
||||
const WORKERS = isCI ? 2 : '35%'
|
||||
|
||||
async function findFreePortInRange(min: number, max: number) {
|
||||
for (let i = 0; i < 50; i++) {
|
||||
@ -62,12 +63,13 @@ process.env.PLAYWRIGHT_PORT_PV = `${ports.projectView}`
|
||||
export default defineConfig({
|
||||
fullyParallel: true,
|
||||
...(WORKERS ? { workers: WORKERS } : {}),
|
||||
forbidOnly: !!process.env.CI,
|
||||
repeatEach: process.env.CI ? 3 : 1,
|
||||
reporter: 'html',
|
||||
forbidOnly: isCI,
|
||||
reporter: isCI ? ([['list'], ['blob']] as const) : ([['list']] as const),
|
||||
retries: isCI ? 3 : 0,
|
||||
use: {
|
||||
headless: !DEBUG,
|
||||
actionTimeout: 5000,
|
||||
|
||||
trace: 'retain-on-failure',
|
||||
...(DEBUG ?
|
||||
{}
|
||||
|
64
app/gui/scripts/ci/dedent.js
Normal file
64
app/gui/scripts/ci/dedent.js
Normal file
@ -0,0 +1,64 @@
|
||||
/**
|
||||
* @param {string | TemplateStringsArray} strings
|
||||
* @param {...unknown[]} values
|
||||
* @returns {string}
|
||||
*/
|
||||
export function dedent(strings, ...values) {
|
||||
const raw = typeof strings === 'string' ? [strings] : strings.raw
|
||||
const escapeSpecialCharacters = Array.isArray(strings)
|
||||
|
||||
// first, perform interpolation
|
||||
let result = ''
|
||||
for (let i = 0; i < raw.length; i++) {
|
||||
let next = raw[i]
|
||||
|
||||
if (next === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (escapeSpecialCharacters) {
|
||||
// handle escaped newlines, backticks, and interpolation characters
|
||||
next = next
|
||||
.replace(/\\\n[ \t]*/g, '')
|
||||
.replace(/\\`/g, '`')
|
||||
.replace(/\\\$/g, '$')
|
||||
.replace(/\\\{/g, '{')
|
||||
}
|
||||
|
||||
result += next
|
||||
|
||||
if (i < values.length) {
|
||||
result += values[i]
|
||||
}
|
||||
}
|
||||
|
||||
// now strip indentation
|
||||
const lines = result.split('\n')
|
||||
let mindent = null
|
||||
for (const l of lines) {
|
||||
const m = l.match(/^(\s+)\S+/)
|
||||
if (m && m[1]) {
|
||||
const indent = m[1].length
|
||||
if (!mindent) {
|
||||
// this is the first indented line
|
||||
mindent = indent
|
||||
} else {
|
||||
mindent = Math.min(mindent, indent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mindent !== null) {
|
||||
const m = mindent
|
||||
result = lines.map((l) => (l[0] === ' ' || l[0] === '\t' ? l.slice(m) : l)).join('\n')
|
||||
}
|
||||
|
||||
// dedent eats leading and trailing whitespace too
|
||||
result = result.trim()
|
||||
if (escapeSpecialCharacters) {
|
||||
// handle escaped newlines at the end to ensure they don't get stripped too
|
||||
result = result.replace(/\\n/g, '\n')
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
35
app/gui/scripts/ci/set-message.js
Normal file
35
app/gui/scripts/ci/set-message.js
Normal file
@ -0,0 +1,35 @@
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-nocheck
|
||||
import { dedent } from './dedent.js'
|
||||
|
||||
export async function setMessage({ header, body, prNumber, repo, github }) {
|
||||
const commentList = await github.paginate(
|
||||
'GET /repos/:owner/:repo/issues/:issue_number/comments',
|
||||
// eslint-disable-next-line camelcase
|
||||
{ ...repo, issue_number: prNumber },
|
||||
)
|
||||
|
||||
const commentBody = dedent`
|
||||
${header}
|
||||
|
||||
${body}
|
||||
`
|
||||
|
||||
const comment = commentList.find((comment) => comment.body.startsWith(header))
|
||||
|
||||
if (!comment) {
|
||||
await github.rest.issues.createComment({
|
||||
...repo,
|
||||
// eslint-disable-next-line camelcase
|
||||
issue_number: prNumber,
|
||||
body: commentBody,
|
||||
})
|
||||
} else {
|
||||
await github.rest.issues.updateComment({
|
||||
...repo,
|
||||
// eslint-disable-next-line camelcase
|
||||
comment_id: comment.id,
|
||||
body: commentBody,
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
import Enso from '#/assets/enso_logo.svg'
|
||||
import type * as aria from '#/components/aria'
|
||||
import { Text } from '#/components/AriaComponents'
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import { expect, userEvent, within } from '@storybook/test'
|
||||
import type { BaseButtonProps } from './Button'
|
||||
import { Button } from './Button'
|
||||
|
||||
type Story = StoryObj<BaseButtonProps<aria.ButtonRenderProps>>
|
||||
|
||||
export default {
|
||||
title: 'Components/AriaComponents/Button',
|
||||
component: Button,
|
||||
render: (props) => <Button {...props} />,
|
||||
} as Meta<BaseButtonProps<aria.ButtonRenderProps>>
|
||||
|
||||
export const Variants: Story = {
|
||||
render: () => (
|
||||
<div className="flex flex-col gap-4">
|
||||
<Text.Heading>Variants</Text.Heading>
|
||||
<div className="grid grid-cols-4 place-content-start place-items-start gap-3">
|
||||
<Button>Default</Button>
|
||||
<Button variant="primary">Primary</Button>
|
||||
<Button variant="accent">Accent</Button>
|
||||
<Button variant="delete">Delete</Button>
|
||||
<Button variant="ghost-fading">Ghost Fading</Button>
|
||||
<Button variant="ghost">Ghost</Button>
|
||||
<Button variant="link">Link</Button>
|
||||
<Button variant="submit">Submit</Button>
|
||||
<Button variant="outline">Outline</Button>
|
||||
</div>
|
||||
|
||||
<Text.Heading>Sizes</Text.Heading>
|
||||
<div className="grid grid-cols-4 place-content-center place-items-start gap-3">
|
||||
<Button size="hero">Hero</Button>
|
||||
<Button size="large">Large</Button>
|
||||
<Button size="medium">Medium</Button>
|
||||
<Button size="small">Small</Button>
|
||||
<Button size="xsmall">XSmall</Button>
|
||||
<Button size="xxsmall">XXSmall</Button>
|
||||
</div>
|
||||
|
||||
<Text.Heading>Icons</Text.Heading>
|
||||
<div className="grid grid-cols-4 place-content-center place-items-start gap-3">
|
||||
<Button icon={Enso}>Icon start</Button>
|
||||
<Button icon={Enso} iconPosition="end">
|
||||
Icon end
|
||||
</Button>
|
||||
<Button icon={Enso} aria-label="Only icon" />
|
||||
</div>
|
||||
|
||||
<Text.Heading>States</Text.Heading>
|
||||
<div className="grid grid-cols-4 place-content-center place-items-start gap-3">
|
||||
<Button isDisabled>Disabled</Button>
|
||||
<Button loading>Loading</Button>
|
||||
<Button loaderPosition="icon" loading>
|
||||
Loading
|
||||
</Button>
|
||||
<Button isActive>Active</Button>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
export const Tooltips: Story = {
|
||||
render: () => (
|
||||
<div className="flex flex-col gap-4">
|
||||
<Text.Heading>Tooltip</Text.Heading>
|
||||
<div className="grid grid-cols-4 place-content-center place-items-start gap-3">
|
||||
<Button tooltip="This is a tooltip">Tooltip</Button>
|
||||
<Button
|
||||
aria-label="Tooltip uses aria-label for icon buttons"
|
||||
icon={Enso}
|
||||
testId="icon-button"
|
||||
/>
|
||||
<Button icon={Enso} tooltip={false} testId="icon-button-no-tooltip" />
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
export const LoadingOnPress: Story = {
|
||||
render: () => {
|
||||
return (
|
||||
<Button
|
||||
onPress={() => {
|
||||
return new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
}}
|
||||
>
|
||||
Click me to trigger loading
|
||||
</Button>
|
||||
)
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const { getByRole, findByTestId } = within(canvasElement)
|
||||
|
||||
const button = getByRole('button', { name: 'Click me to trigger loading' })
|
||||
await userEvent.click(button)
|
||||
await expect(button).toHaveAttribute('disabled')
|
||||
// then the spinner appears after some delay
|
||||
await expect(await findByTestId('spinner')).toBeInTheDocument()
|
||||
},
|
||||
}
|
@ -4,14 +4,14 @@ import * as React from 'react'
|
||||
import * as focusHooks from '#/hooks/focusHooks'
|
||||
|
||||
import * as aria from '#/components/aria'
|
||||
import * as ariaComponents from '#/components/AriaComponents'
|
||||
import { StatelessSpinner } from '#/components/StatelessSpinner'
|
||||
import SvgMask from '#/components/SvgMask'
|
||||
|
||||
import { forwardRef } from '#/utilities/react'
|
||||
import type { ExtractFunction, VariantProps } from '#/utilities/tailwindVariants'
|
||||
import { tv } from '#/utilities/tailwindVariants'
|
||||
import { TEXT_STYLE } from '../Text'
|
||||
import { TEXT_STYLE, useVisualTooltip } from '../Text'
|
||||
import { Tooltip, TooltipTrigger } from '../Tooltip'
|
||||
|
||||
// ==============
|
||||
// === Button ===
|
||||
@ -82,7 +82,7 @@ export const BUTTON_STYLES = tv({
|
||||
],
|
||||
variants: {
|
||||
isDisabled: {
|
||||
true: 'disabled:opacity-50 disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:cursor-not-allowed',
|
||||
true: 'opacity-50 cursor-not-allowed',
|
||||
},
|
||||
isFocused: {
|
||||
true: 'focus:outline-none focus-visible:outline-2 focus-visible:outline-black focus-visible:outline-offset-[-2px]',
|
||||
@ -256,6 +256,7 @@ export const BUTTON_STYLES = tv({
|
||||
variant: 'primary',
|
||||
iconPosition: 'start',
|
||||
showIconOnHover: false,
|
||||
isDisabled: false,
|
||||
},
|
||||
compoundVariants: [
|
||||
{ isFocused: true, iconOnly: true, class: 'focus-visible:outline-offset-[3px]' },
|
||||
@ -393,14 +394,14 @@ export const Button = forwardRef(function Button(
|
||||
render: aria.ButtonRenderProps | aria.LinkRenderProps,
|
||||
): React.ReactNode => {
|
||||
const iconComponent = (() => {
|
||||
if (icon == null) {
|
||||
return null
|
||||
} else if (isLoading && loaderPosition === 'icon') {
|
||||
if (isLoading && loaderPosition === 'icon') {
|
||||
return (
|
||||
<span className={styles.icon()}>
|
||||
<StatelessSpinner state="loading-medium" size={16} />
|
||||
</span>
|
||||
)
|
||||
} else if (icon == null) {
|
||||
return null
|
||||
} else {
|
||||
/* @ts-expect-error any here is safe because we transparently pass it to the children, and ts infer the type outside correctly */
|
||||
const actualIcon = typeof icon === 'function' ? icon(render) : icon
|
||||
@ -429,7 +430,7 @@ export const Button = forwardRef(function Button(
|
||||
}
|
||||
}
|
||||
|
||||
const { tooltip: visualTooltip, targetProps } = ariaComponents.useVisualTooltip({
|
||||
const { tooltip: visualTooltip, targetProps } = useVisualTooltip({
|
||||
targetRef: contentRef,
|
||||
children: tooltipElement,
|
||||
isDisabled: !shouldUseVisualTooltip,
|
||||
@ -483,14 +484,12 @@ export const Button = forwardRef(function Button(
|
||||
{button}
|
||||
{visualTooltip}
|
||||
</>
|
||||
: <ariaComponents.TooltipTrigger delay={0} closeDelay={0}>
|
||||
: <TooltipTrigger delay={0} closeDelay={0}>
|
||||
{button}
|
||||
|
||||
<ariaComponents.Tooltip
|
||||
{...(tooltipPlacement != null ? { placement: tooltipPlacement } : {})}
|
||||
>
|
||||
<Tooltip {...(tooltipPlacement != null ? { placement: tooltipPlacement } : {})}>
|
||||
{tooltipElement}
|
||||
</ariaComponents.Tooltip>
|
||||
</ariaComponents.TooltipTrigger>
|
||||
</Tooltip>
|
||||
</TooltipTrigger>
|
||||
)
|
||||
})
|
||||
|
@ -4,10 +4,10 @@ import * as React from 'react'
|
||||
import Success from '#/assets/check_mark.svg'
|
||||
import Error from '#/assets/cross.svg'
|
||||
|
||||
import * as ariaComponents from '#/components/AriaComponents'
|
||||
import * as loader from '#/components/Loader'
|
||||
import SvgMask from '#/components/SvgMask'
|
||||
import { tv, type VariantProps } from '#/utilities/tailwindVariants'
|
||||
import { Text } from './AriaComponents/Text'
|
||||
import * as loader from './Loader'
|
||||
import SvgMask from './SvgMask'
|
||||
|
||||
// =================
|
||||
// === Constants ===
|
||||
@ -15,9 +15,9 @@ import { tv, type VariantProps } from '#/utilities/tailwindVariants'
|
||||
|
||||
const INFO_ICON = (
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
<ariaComponents.Text variant="custom" className="pb-0.5 text-xl leading-[0]" aria-hidden>
|
||||
<Text variant="custom" className="pb-0.5 text-xl leading-[0]" aria-hidden>
|
||||
!
|
||||
</ariaComponents.Text>
|
||||
</Text>
|
||||
)
|
||||
|
||||
const STATUS_ICON_MAP: Readonly<Record<Status, StatusIcon>> = {
|
||||
@ -142,15 +142,15 @@ export function Result(props: ResultProps) {
|
||||
: null}
|
||||
|
||||
{typeof title === 'string' ?
|
||||
<ariaComponents.Text.Heading level={2} className={classes.title()} variant="subtitle">
|
||||
<Text.Heading level={2} className={classes.title()} variant="subtitle">
|
||||
{title}
|
||||
</ariaComponents.Text.Heading>
|
||||
</Text.Heading>
|
||||
: title}
|
||||
|
||||
{typeof subtitle === 'string' ?
|
||||
<ariaComponents.Text elementType="p" className={classes.subtitle()} balance variant="body">
|
||||
<Text elementType="p" className={classes.subtitle()} balance variant="body">
|
||||
{subtitle}
|
||||
</ariaComponents.Text>
|
||||
</Text>
|
||||
: subtitle}
|
||||
|
||||
{children != null && <div className={classes.content()}>{children}</div>}
|
||||
|
@ -40,6 +40,7 @@ export function Spinner(props: SpinnerProps) {
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
data-testid="spinner"
|
||||
>
|
||||
<rect
|
||||
x={1.5}
|
||||
|
32
app/gui/src/dashboard/styles.css
Normal file
32
app/gui/src/dashboard/styles.css
Normal file
@ -0,0 +1,32 @@
|
||||
:where(body) {
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
background-blend-mode: lighten;
|
||||
}
|
||||
|
||||
:where(body:not(.vibrancy)) {
|
||||
&::before {
|
||||
content: '';
|
||||
inset: 0 -16vw -16vh 0;
|
||||
z-index: -1;
|
||||
|
||||
background-color: #b09778ff;
|
||||
|
||||
@apply pointer-events-none fixed bg-cover;
|
||||
}
|
||||
|
||||
& > * {
|
||||
@apply bg-white/80;
|
||||
}
|
||||
}
|
||||
|
||||
:where(.enso-dashboard) {
|
||||
@apply absolute inset-0 flex flex-col overflow-hidden;
|
||||
}
|
||||
|
||||
/* Disable all animations. */
|
||||
html.disable-animations * {
|
||||
transition: none !important;
|
||||
animation: none !important;
|
||||
animation-play-state: paused !important;
|
||||
}
|
@ -398,43 +398,10 @@
|
||||
--sidebar-section-heading-margin-b: 0.375rem;
|
||||
}
|
||||
|
||||
/* Disable all animations. */
|
||||
html.disable-animations * {
|
||||
transition: none !important;
|
||||
animation: none !important;
|
||||
animation-play-state: paused !important;
|
||||
}
|
||||
|
||||
:where(body) {
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
background-blend-mode: lighten;
|
||||
}
|
||||
|
||||
:where(body:not(.vibrancy)) {
|
||||
&::before {
|
||||
content: '';
|
||||
inset: 0 -16vw -16vh 0;
|
||||
z-index: -1;
|
||||
|
||||
background-color: #b09778ff;
|
||||
|
||||
@apply pointer-events-none fixed bg-cover;
|
||||
}
|
||||
|
||||
& > * {
|
||||
@apply bg-white/80;
|
||||
}
|
||||
}
|
||||
|
||||
.Toastify--animate {
|
||||
animation-duration: 200ms;
|
||||
}
|
||||
|
||||
:where(.enso-dashboard) {
|
||||
@apply absolute inset-0 flex flex-col overflow-hidden;
|
||||
}
|
||||
|
||||
/* These styles MUST still be copied
|
||||
* as `.enso-dashboard body` and `.enso-dashboard html` make no sense. */
|
||||
:where(.enso-dashboard, .enso-chat, .enso-portal-root) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as dashboard from '#/index'
|
||||
import '#/styles.css'
|
||||
import '#/tailwind.css'
|
||||
import { AsyncApp } from '@/asyncApp'
|
||||
import { baseConfig, configValue, mergeConfig } from '@/util/config'
|
||||
|
@ -49,7 +49,7 @@ const activity = shallowRef<VNode>()
|
||||
|
||||
// How much wider a dropdown can be than a port it is attached to, when a long text is present.
|
||||
// Any text beyond that limit will receive an ellipsis and sliding animation on hover.
|
||||
const MAX_DROPDOWN_OVERSIZE_PX = 150
|
||||
const MAX_DROPDOWN_OVERSIZE_PX = 390
|
||||
|
||||
const floatReference = computed(
|
||||
() => enclosingTopLevelArgument(widgetRoot.value, tree) ?? widgetRoot.value,
|
||||
|
@ -148,7 +148,7 @@ export interface DropdownEntry {
|
||||
|
||||
&:hover {
|
||||
background-color: color-mix(in oklab, var(--dropdown-bg) 50%, white 50%);
|
||||
span {
|
||||
.itemContent {
|
||||
--text-scroll-max: calc(var(--dropdown-max-width) - 28px);
|
||||
will-change: transform;
|
||||
animation: 6s 1s infinite text-scroll;
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": [
|
||||
".storybook/**/*",
|
||||
"env.d.ts",
|
||||
"lib0-ext.d.ts",
|
||||
"src/**/*.vue",
|
||||
|
10
app/gui/tsconfig.storybook.json
Normal file
10
app/gui/tsconfig.storybook.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./tsconfig.app.json",
|
||||
"include": [
|
||||
".storybook/**/*",
|
||||
".storybook/env.d.ts",
|
||||
"./src/**/*.stories.tsx",
|
||||
"./src/**/*.stories.vue",
|
||||
"./src/**/*.stories.ts"
|
||||
]
|
||||
}
|
@ -30,12 +30,15 @@ const entrypoint =
|
||||
// and because Vite's HTML env replacements only work with import.meta.env variables, not defines.
|
||||
process.env.ENSO_IDE_VERSION = process.env.ENSO_CLOUD_DASHBOARD_VERSION
|
||||
|
||||
const isCI = process.env.CI === 'true'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
root: fileURLToPath(new URL('.', import.meta.url)),
|
||||
cacheDir: fileURLToPath(new URL('../../node_modules/.cache/vite', import.meta.url)),
|
||||
publicDir: fileURLToPath(new URL('./public', import.meta.url)),
|
||||
envDir: fileURLToPath(new URL('.', import.meta.url)),
|
||||
logLevel: isCI ? 'error' : 'info',
|
||||
plugins: [
|
||||
wasm(),
|
||||
...(process.env.NODE_ENV === 'development' ? [await VueDevTools()] : []),
|
||||
|
@ -63,7 +63,7 @@
|
||||
"typecheck": "tsc --build",
|
||||
"build": "tsx bundle.ts",
|
||||
"dist": "tsx dist.ts",
|
||||
"lint": "eslint . --max-warnings=0",
|
||||
"lint": "eslint . --cache --max-warnings=0",
|
||||
"watch:windows": "cross-env ENSO_BUILD_IDE=%LOCALAPPDATA%\\Temp\\enso\\dist\\ide ENSO_BUILD_PROJECT_MANAGER=%CD%\\..\\..\\..\\dist\\backend ENSO_BUILD_PROJECT_MANAGER_IN_BUNDLE_PATH=bin\\project-manager.exe ENSO_BUILD_IDE_BUNDLED_ENGINE_VERSION=0 ENSO_POLYGLOT_YDOC_SERVER=wss://localhost:8080 tsx watch.ts",
|
||||
"watch:linux": "ENSO_BUILD_IDE=\"${ENSO_BUILD_IDE:-/tmp/enso/dist/ide}\" ENSO_BUILD_PROJECT_MANAGER=\"${ENSO_BUILD_PROJECT_MANAGER:-\"$(pwd)/../../../dist/backend\"}\" ENSO_BUILD_PROJECT_MANAGER_IN_BUNDLE_PATH=\"${ENSO_BUILD_PROJECT_MANAGER_IN_BUNDLE_PATH:-bin/project-manager}\" ENSO_BUILD_IDE_BUNDLED_ENGINE_VERSION=\"${ENSO_BUILD_IDE_BUNDLED_ENGINE_VERSION:-0}\" ENSO_POLYGLOT_YDOC_SERVER=\"${ENSO_POLYGLOT_YDOC_SERVER:-wss://localhost:8080}\" tsx watch.ts \"$@\"",
|
||||
"watch:macos": "ENSO_BUILD_IDE=\"${ENSO_BUILD_IDE:-/tmp/enso/dist/ide}\" ENSO_BUILD_PROJECT_MANAGER=\"${ENSO_BUILD_PROJECT_MANAGER:-\"$(pwd)/../../../dist/backend\"}\" ENSO_BUILD_PROJECT_MANAGER_IN_BUNDLE_PATH=\"${ENSO_BUILD_PROJECT_MANAGER_IN_BUNDLE_PATH:-bin/project-manager}\" ENSO_BUILD_IDE_BUNDLED_ENGINE_VERSION=\"${ENSO_BUILD_IDE_BUNDLED_ENGINE_VERSION:-0}\" ENSO_POLYGLOT_YDOC_SERVER=\"${ENSO_POLYGLOT_YDOC_SERVER:-wss://localhost:8080}\" tsx watch.ts \"$@\""
|
||||
|
@ -16,7 +16,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node src/index.js",
|
||||
"lint": "eslint . --max-warnings=0"
|
||||
"lint": "eslint . --cache --max-warnings=0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"sharp": "^0.31.2",
|
||||
|
@ -12,7 +12,7 @@
|
||||
"compile": "node ./build.mjs build",
|
||||
"start": "node ./dist/main.mjs",
|
||||
"dev:watch": "node ./build.mjs watch",
|
||||
"lint": "eslint . --max-warnings=0",
|
||||
"lint": "eslint . --cache --max-warnings=0",
|
||||
"format": "prettier --version && prettier --write src/ && eslint . --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -12,7 +12,7 @@
|
||||
"compile": "node ./build.mjs build",
|
||||
"start": "node ./dist/main.cjs",
|
||||
"dev:watch": "node ./build.mjs watch",
|
||||
"lint": "eslint . --max-warnings=0"
|
||||
"lint": "eslint . --cache --max-warnings=0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ydoc-server": "workspace:*",
|
||||
|
@ -12,7 +12,7 @@
|
||||
"test:watch": "vitest",
|
||||
"typecheck": "tsc",
|
||||
"compile": "tsc",
|
||||
"lint": "eslint . --max-warnings=0"
|
||||
"lint": "eslint . --cache --max-warnings=0"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
|
@ -17,7 +17,7 @@
|
||||
"generate:ast-schema": "cargo run -p enso-parser-schema > src/ast/generated/ast-schema.json",
|
||||
"generate:ast-types": "vite-node ./parser-codegen/index.ts src/ast/generated/ast-schema.json src/ast/generated/ast.ts",
|
||||
"generate:ast-types-lazy": "vite-node ./parser-codegen/index.ts src/ast/generated/ast-schema.json src/ast/generated/ast.ts --if-changed",
|
||||
"lint": "eslint . --max-warnings=0",
|
||||
"lint": "eslint . --cache --max-warnings=0",
|
||||
"format": "prettier --version && prettier --write src/ && eslint . --fix",
|
||||
"postinstall": "corepack pnpm run generate:ast-schema && corepack pnpm run generate:ast-types-lazy"
|
||||
},
|
||||
|
@ -661,7 +661,6 @@ pub fn gui_tests() -> Result<Workflow> {
|
||||
workflow.add(PRIMARY_TARGET, job::Lint);
|
||||
workflow.add(PRIMARY_TARGET, job::WasmTest);
|
||||
workflow.add(PRIMARY_TARGET, job::NativeTest);
|
||||
workflow.add(PRIMARY_TARGET, job::GuiCheck);
|
||||
Ok(workflow)
|
||||
}
|
||||
|
||||
|
@ -396,16 +396,6 @@ impl JobArchetype for NativeTest {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct GuiCheck;
|
||||
|
||||
impl JobArchetype for GuiCheck {
|
||||
fn job(&self, target: Target) -> Job {
|
||||
plain_job(target, "GUI tests", "gui check")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct GuiBuild;
|
||||
|
||||
|
@ -556,4 +556,15 @@ export default [
|
||||
'no-undef': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['app/gui/src/dashboard/**/*.stories.tsx'],
|
||||
rules: {
|
||||
'no-restricted-syntax': 'off',
|
||||
'jsdoc/require-jsdoc': 'off',
|
||||
'jsdoc/require-param-type': 'off',
|
||||
'jsdoc/require-file-overview': 'off',
|
||||
'@typescript-eslint/no-magic-numbers': 'off',
|
||||
'@typescript-eslint/unbound-method': 'off',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^8.10.0",
|
||||
"@typescript-eslint/parser": "^8.10.0",
|
||||
@ -30,10 +31,12 @@
|
||||
"format": "prettier --write .",
|
||||
"format:workflows": "prettier --write .github/workflows",
|
||||
"ci-check": "corepack pnpm run --aggregate-output /^ci:/",
|
||||
"ci:prettier": "prettier --check .",
|
||||
"ci:lint": "corepack pnpm run -r lint",
|
||||
"ci:prettier": "prettier --check --cache .",
|
||||
"ci:lint": "corepack pnpm run -r --parallel lint --output-file eslint_report.json --format json",
|
||||
"ci:test": "corepack pnpm run -r --parallel test",
|
||||
"ci:typecheck": "corepack pnpm run -r typecheck"
|
||||
"ci:typecheck": "corepack pnpm run -r typecheck",
|
||||
"ci:chromatic:react": "corepack pnpm run -r --filter enso-gui chromatic:react",
|
||||
"ci:chromatic:vue": "corepack pnpm run -r --filter enso-gui chromatic:vue"
|
||||
},
|
||||
"pnpm": {
|
||||
"//": "To completely ignore deep dependencies, see .pnpmfile.cjs",
|
||||
|
1462
pnpm-lock.yaml
1462
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user