Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8155d328a2 | ||
|
|
6118e706e0 | ||
|
|
eab07b9e9f |
@@ -0,0 +1,35 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/java
|
||||
{
|
||||
"name": "Java",
|
||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||
"image": "mcr.microsoft.com/devcontainers/java:1-17-bookworm",
|
||||
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/java:1": {
|
||||
"version": "none",
|
||||
"installMaven": "true",
|
||||
"installGradle": "false"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/github-cli:1": {},
|
||||
"ghcr.io/devcontainers/features/node:1": {}
|
||||
},
|
||||
|
||||
"hostRequirements": {
|
||||
"cpus": 8,
|
||||
"memory": "16gb",
|
||||
"storage": "64gb"
|
||||
}
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
// "postCreateCommand": "java -version",
|
||||
|
||||
// Configure tool-specific properties.
|
||||
// "customizations": {},
|
||||
|
||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||
// "remoteUser": "root"
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve CloudBeaver
|
||||
title: ''
|
||||
labels: bug, wait for review
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here, e.g. error log.
|
||||
@@ -1,61 +0,0 @@
|
||||
name: 🐛 Bug Report
|
||||
description: Create a report to help us improve our product
|
||||
labels: [wait for review]
|
||||
type: Bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for reporting a bug in CloudBeaver!
|
||||
To ensure we can address the issue as quickly and efficiently as possible, please provide as much detail as you can in the fields below.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: |
|
||||
A clear and concise description of the issue being addressed.
|
||||
- Describe the actual problematic behavior.
|
||||
- Ensure private information is redacted.
|
||||
- Include console output or log files if relevant.
|
||||
- If applicable, add screenshots or screen recordings to help explain your problem.
|
||||
placeholder: |
|
||||
Please enter a description of the issue. Here you can also attach log files, screenshots or a video
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduce
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Describe the steps to reproduce the bug
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected/Desired Behavior
|
||||
description: |
|
||||
A clear and concise description of what happens in the software **after** a fix is created and merged.
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: CloudBeaver Version
|
||||
description: What version of CloudBeaver are you running?
|
||||
placeholder: ex. CloudBeaver Community 25.0.0
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Any additional context about the environment
|
||||
placeholder: |
|
||||
Example:
|
||||
1. OS: [e.g. iOS, Ubuntu, Windows 11]
|
||||
2. Browser: [e.g. Chrome, Safari]
|
||||
validations:
|
||||
required: false
|
||||
@@ -1,14 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Read our documentations
|
||||
url: https://dbeaver.com/docs/cloudbeaver
|
||||
about: Many articles about product deployment and features.
|
||||
- name: Try advanced database management features in DBeaver PRO or CloudBeaver EE
|
||||
url: https://dbeaver.com/download/?utm_source=github&utm_medium=social&utm_campaign=issue
|
||||
about: Download a 14-day free trial on dbeaver.com. No credit card is needed.
|
||||
- name: Learn how to use CloudBeaver effectively with our tutorial videos
|
||||
url: https://www.youtube.com/channel/UC-yOjsQLSaJVEghg6UB3N7A
|
||||
about: Subscribe to our YouTube channel and watch new tutorials every week.
|
||||
- name: Follow us on X (ex. Twitter)
|
||||
url: https://x.com/dbeaver_news
|
||||
about: Get everyday tips, fresh news, and DBeaver/CloudBeaver feature overviews.
|
||||
@@ -1,9 +1,8 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Provide a description of your feature request for CloudBeaver. Include the problem you are trying to solve, why it is important, and any specific functionality you believe would improve the platform.
|
||||
name: Feature request
|
||||
about: Suggest an idea for CloudBeaver
|
||||
title: ''
|
||||
labels: wait for review
|
||||
type: Feature
|
||||
labels: feature request, wait for review
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
@@ -13,9 +12,3 @@ A clear and concise description of what the problem is. Ex. I'm always frustrate
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
---
|
||||
name: Question
|
||||
about: Is there something unclear? Ask your question.
|
||||
about: Is there something unclear?
|
||||
title: ''
|
||||
labels: wait for review
|
||||
type: Question
|
||||
labels: question, wait for review
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
name: Backend Build
|
||||
|
||||
on:
|
||||
# Allows you to reuse workflows by referencing their YAML files
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
build-backend:
|
||||
name: Build
|
||||
timeout-minutes: 10
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
path: cloudbeaver
|
||||
|
||||
- name: Clone Deps Repositories
|
||||
uses: dbeaver/github-actions/clone-repositories@devel
|
||||
with:
|
||||
project_deps_path: "./cloudbeaver/project.deps"
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "21"
|
||||
cache: maven
|
||||
|
||||
- uses: dbeaver/github-actions/install-maven@devel
|
||||
|
||||
- name: Run build script
|
||||
run: ./build-backend.sh
|
||||
shell: bash
|
||||
working-directory: ./cloudbeaver/deploy
|
||||
|
||||
# - name: Archive build artifacts
|
||||
# uses: actions/upload-artifact@v4
|
||||
# with:
|
||||
# name: backend-build-artifacts
|
||||
# path: cloudbeaver/deploy/cloudbeaver
|
||||
# if-no-files-found: error
|
||||
@@ -1,14 +0,0 @@
|
||||
name: Cleanup
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
push:
|
||||
branches:
|
||||
- devel
|
||||
|
||||
jobs:
|
||||
delete-caches:
|
||||
name: Cleanup
|
||||
uses: dbeaver/dbeaver-common/.github/workflows/cleanup-caches.yml@devel
|
||||
secrets: inherit
|
||||
@@ -1,93 +0,0 @@
|
||||
name: Build and Push Docker Image
|
||||
|
||||
on:
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# Allows you to reuse workflows by referencing their YAML files
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
build-and-push-docker:
|
||||
name: Build and Push Docker Image
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download backend artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: backend-build-artifacts
|
||||
path: deploy/cloudbeaver/
|
||||
|
||||
- name: Download frontend artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: frontend-build-artifacts
|
||||
path: deploy/cloudbeaver/web
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Run custom Docker build script
|
||||
run: ./make-docker-container.sh
|
||||
shell: bash
|
||||
working-directory: ./deploy/docker
|
||||
|
||||
- name: Tag Docker Image
|
||||
run: |
|
||||
REPO_NAME=$(basename ${{ github.repository }})
|
||||
IMAGE_NAME=ghcr.io/${{ github.repository_owner }}/$REPO_NAME
|
||||
BRANCH_NAME=${{ github.head_ref || github.ref_name }}
|
||||
TAG_NAME=$(echo $BRANCH_NAME | sed 's/[^a-zA-Z0-9._-]/-/g')
|
||||
docker tag dbeaver/cloudbeaver:dev $IMAGE_NAME:$TAG_NAME
|
||||
echo "image=$IMAGE_NAME:$TAG_NAME" >> $GITHUB_ENV
|
||||
|
||||
# - name: Install Docker Credential Helper
|
||||
# run: |
|
||||
# sudo apt-get update
|
||||
# sudo apt-get install -y gnupg2 pass
|
||||
# curl -fsSL https://github.com/docker/docker-credential-helpers/releases/download/v0.6.4/docker-credential-pass-v0.6.4-amd64.tar.gz -o docker-credential-pass.tar.gz
|
||||
# tar xzvf docker-credential-pass.tar.gz
|
||||
# sudo mv docker-credential-pass /usr/local/bin/docker-credential-pass
|
||||
# sudo chmod +x /usr/local/bin/docker-credential-pass
|
||||
|
||||
# - name: Configure Docker to use Credential Helper
|
||||
# run: |
|
||||
# mkdir -p ~/.docker
|
||||
# echo '{"credsStore":"pass"}' > ~/.docker/config.json
|
||||
|
||||
# - name: Initialize Password Store
|
||||
# run: |
|
||||
# gpg --batch --gen-key <<EOF
|
||||
# %no-protection
|
||||
# Key-Type: default
|
||||
# Subkey-Type: default
|
||||
# Name-Real: Docker Credential Helper
|
||||
# Expire-Date: 0
|
||||
# %commit
|
||||
# EOF
|
||||
# pass init "$(gpg --list-keys --with-colons | grep '^pub' | cut -d: -f5)"
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin
|
||||
|
||||
- name: Push Docker Image
|
||||
run: docker push ${{ env.image }}
|
||||
|
||||
# - name: Comment on Pull Request
|
||||
# if: github.event_name == 'pull_request'
|
||||
# uses: actions/github-script@v6
|
||||
# with:
|
||||
# github-token: ${{ secrets.PAT }}
|
||||
# script: |
|
||||
# const tag = process.env.TAG_NAME;
|
||||
# const commentBody = `The Docker image has been built and pushed to Docker Hub.\n\nYou can pull the image using the following command:\n\`\`\`\ndocker pull dbeaver/cloudbeaver:${tag}\n\`\`\``;
|
||||
# github.rest.issues.createComment({
|
||||
# owner: context.repo.owner,
|
||||
# repo: context.repo.repo,
|
||||
# issue_number: context.issue.number,
|
||||
# body: commentBody
|
||||
# });
|
||||
@@ -1,98 +0,0 @@
|
||||
name: Frontend Build
|
||||
|
||||
on:
|
||||
# Allows you to reuse workflows by referencing their YAML files
|
||||
workflow_call:
|
||||
outputs:
|
||||
build-status:
|
||||
description: "Build status"
|
||||
value: ${{ jobs.frontend-build.outputs.build-status }}
|
||||
test-status:
|
||||
description: "Test status"
|
||||
value: ${{ jobs.frontend-build.outputs.test-status }}
|
||||
inputs:
|
||||
clean-cache:
|
||||
required: false
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
frontend-build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
outputs:
|
||||
build-status: ${{ steps.build.outcome }}
|
||||
test-status: ${{ steps.test.outcome }}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./cloudbeaver/webapp
|
||||
|
||||
steps:
|
||||
- name: Checkout cloudbeaver
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: cloudbeaver
|
||||
|
||||
- name: Clone Deps Repositories
|
||||
uses: dbeaver/github-actions/clone-repositories@devel
|
||||
with:
|
||||
project_deps_path: "./cloudbeaver/project.deps"
|
||||
token: ${{ secrets.DEVOPS_ISSUE_RO_TOKEN }}
|
||||
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
cache: "yarn"
|
||||
cache-dependency-path: |
|
||||
cloudbeaver/common-*/yarn.lock
|
||||
cloudbeaver/webapp/yarn.lock
|
||||
|
||||
- name: restore cloudbeaver-common typescript cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "cloudbeaver/common-*/**/*/lib"
|
||||
key: ${{ runner.os }}-common-dist-${{ hashFiles('cloudbeaver/common-*/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-common-dist-
|
||||
|
||||
- name: restore typescript cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "cloudbeaver/webapp/**/packages/*/dist"
|
||||
key: ${{ runner.os }}-dist-${{ hashFiles('cloudbeaver/webapp/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-dist-
|
||||
|
||||
- if: inputs.clean-cache
|
||||
run: yarn clear
|
||||
|
||||
- run: yarn install --immutable
|
||||
working-directory: ./cloudbeaver/common-typescript
|
||||
|
||||
- run: yarn install --immutable
|
||||
working-directory: ./cloudbeaver/common-react
|
||||
|
||||
- run: yarn install --immutable
|
||||
|
||||
- run: yarn bundle
|
||||
working-directory: ./cloudbeaver/webapp/packages/product-default
|
||||
|
||||
- run: yarn test
|
||||
working-directory: ./cloudbeaver/common-typescript
|
||||
|
||||
# - run: yarn test
|
||||
# working-directory: ./cloudbeaver/common-react
|
||||
|
||||
- run: yarn test
|
||||
|
||||
# - name: Archive build artifacts
|
||||
# uses: actions/upload-artifact@v4
|
||||
# with:
|
||||
# name: frontend-build-artifacts
|
||||
# path: webapp/packages/product-default/lib
|
||||
# if-no-files-found: error
|
||||
@@ -0,0 +1,76 @@
|
||||
name: frontend-cache
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["devel"]
|
||||
paths:
|
||||
- "webapp/**"
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./webapp
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "18"
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: restore yarn cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: restore node_modules
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node_modules-
|
||||
|
||||
- name: restore typescript cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: "**/packages/*/dist"
|
||||
key: ${{ runner.os }}-dist-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-dist-
|
||||
|
||||
- name: yarn install
|
||||
uses: borales/actions-yarn@v4
|
||||
with:
|
||||
dir: webapp
|
||||
cmd: install
|
||||
|
||||
- name: yarn lerna bootstrap
|
||||
uses: borales/actions-yarn@v4
|
||||
with:
|
||||
dir: webapp
|
||||
cmd: lerna bootstrap
|
||||
|
||||
- name: build
|
||||
uses: borales/actions-yarn@v4
|
||||
with:
|
||||
dir: webapp/packages/product-default
|
||||
cmd: build # build tyescript source code
|
||||
|
||||
- name: test
|
||||
uses: borales/actions-yarn@v4
|
||||
with:
|
||||
dir: webapp
|
||||
cmd: lerna run test # build packages that have test script
|
||||
@@ -0,0 +1,29 @@
|
||||
name: cleanup caches by a branch
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Cleanup
|
||||
run: |
|
||||
gh extension install actions/gh-actions-cache
|
||||
|
||||
echo "Fetching list of cache key"
|
||||
cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH -L 100 | cut -f 1 )
|
||||
|
||||
## Setting this to not fail the workflow while deleting cache keys.
|
||||
set +e
|
||||
echo "Deleting caches..."
|
||||
for cacheKey in $cacheKeysForPR
|
||||
do
|
||||
gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm
|
||||
done
|
||||
echo "Done"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REPO: ${{ github.repository }}
|
||||
BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge
|
||||
@@ -1,46 +0,0 @@
|
||||
name: Frontend Lint
|
||||
|
||||
on:
|
||||
# Allows you to reuse workflows by referencing their YAML files
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./cloudbeaver/webapp
|
||||
|
||||
steps:
|
||||
- name: Checkout cloudbeaver
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: cloudbeaver
|
||||
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
cache: "yarn"
|
||||
cache-dependency-path: "./cloudbeaver/webapp/yarn.lock"
|
||||
|
||||
# - run: yarn install --immutable
|
||||
# working-directory: ./cloudbeaver/common-typescript
|
||||
|
||||
# - run: yarn install --immutable
|
||||
# working-directory: ./cloudbeaver/common-react
|
||||
|
||||
- run: |
|
||||
yarn install --immutable
|
||||
git fetch origin "${{ github.base_ref }}" --depth=1
|
||||
FILES=$(git diff --name-only 'origin/${{ github.base_ref }}' ${{ github.sha }} -- . | sed 's|^webapp/||')
|
||||
if [ -n "$FILES" ]; then
|
||||
yarn lint --pass-on-no-patterns --no-error-on-unmatched-pattern $FILES
|
||||
else
|
||||
echo "No files to lint"
|
||||
fi
|
||||
@@ -0,0 +1,80 @@
|
||||
name: frontend
|
||||
|
||||
on:
|
||||
# Triggers the workflow on push or pull request events but only for the "devel" branch
|
||||
pull_request:
|
||||
branches: ["devel"]
|
||||
paths:
|
||||
- "webapp/**" # monitor for the frontend changes
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# Allows you to reuse workflows by referencing their YAML files
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./webapp
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: restore yarn cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: restore node_modules
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node_modules-
|
||||
|
||||
- name: restore typescript cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: "**/packages/*/dist"
|
||||
key: ${{ runner.os }}-dist-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-dist-
|
||||
|
||||
- name: yarn install
|
||||
uses: borales/actions-yarn@v4
|
||||
with:
|
||||
dir: webapp
|
||||
cmd: install
|
||||
|
||||
- name: yarn lerna bootstrap
|
||||
uses: borales/actions-yarn@v4
|
||||
with:
|
||||
dir: webapp
|
||||
cmd: lerna bootstrap
|
||||
|
||||
- name: build
|
||||
uses: borales/actions-yarn@v4
|
||||
with:
|
||||
dir: webapp/packages/product-default
|
||||
cmd: bundle
|
||||
|
||||
- name: test
|
||||
uses: borales/actions-yarn@v4
|
||||
with:
|
||||
dir: webapp
|
||||
cmd: lerna run test
|
||||
@@ -1,64 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
push:
|
||||
branches: [devel]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-server:
|
||||
uses: dbeaver/dbeaver-common/.github/workflows/mvn-package.yml@devel
|
||||
name: Check
|
||||
secrets: inherit
|
||||
with:
|
||||
mvn-args: -Dplain-api-server -Dheadless-platform
|
||||
project-directory: ./cloudbeaver/server/product/aggregate
|
||||
project-deps: ./cloudbeaver/project.deps
|
||||
timeout-minutes: 5
|
||||
|
||||
# build-server:
|
||||
# name: Server
|
||||
# uses: ./.github/workflows/backend-build.yml
|
||||
# secrets: inherit
|
||||
|
||||
build-frontend:
|
||||
name: Frontend
|
||||
uses: ./.github/workflows/frontend-build.yml
|
||||
secrets: inherit
|
||||
|
||||
lint-server:
|
||||
name: Server
|
||||
uses: dbeaver/dbeaver-common/.github/workflows/java-checkstyle.yml@devel
|
||||
secrets: inherit
|
||||
|
||||
lint-frontend:
|
||||
name: Frontend
|
||||
uses: ./.github/workflows/frontend-lint.yml
|
||||
secrets: inherit
|
||||
|
||||
# call-frontend-tests:
|
||||
# name: Frontend Unit Tests
|
||||
# needs: call-frontend-build
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - name: Check if tests passed
|
||||
# if: ${{ needs.call-frontend-build.outputs.test-status != 'success' }}
|
||||
# run: |
|
||||
# echo "Tests failed"
|
||||
# exit 1
|
||||
# - name: Continue if tests passed
|
||||
# if: ${{ needs.call-frontend-build.outputs.test-status == 'success' }}
|
||||
# run: echo "Tests passed"
|
||||
|
||||
# call-docker-build-and-push:
|
||||
# name: Run
|
||||
# needs: [call-backend-build, call-frontend-build]
|
||||
# uses: ./.github/workflows/docker-build-and-push.yml
|
||||
@@ -0,0 +1,17 @@
|
||||
name: validation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- devel
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
- edited
|
||||
- ready_for_review
|
||||
- labeled
|
||||
|
||||
jobs:
|
||||
commit-message:
|
||||
uses: dbeaver/dbeaver/.github/workflows/reused-commit-msgs-validator.yml@devel
|
||||
@@ -26,7 +26,7 @@ dist/
|
||||
|
||||
*.iml
|
||||
/idea/
|
||||
|
||||
.idea/
|
||||
|
||||
## Mac OS
|
||||
.DS_Store
|
||||
@@ -40,11 +40,7 @@ server/test/io.cloudbeaver.test.platform/workspace/.data/
|
||||
.classpath
|
||||
.settings/
|
||||
|
||||
## Eclipse PDE
|
||||
*.product.launch
|
||||
|
||||
workspace-dev-ce/
|
||||
deploy/cloudbeaver
|
||||
server/**/target
|
||||
apps/**/target
|
||||
osgi-cache/
|
||||
apps/**/target
|
||||
@@ -15,11 +15,11 @@
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"streetsidesoftware.code-spell-checker-russian",
|
||||
"syler.sass-indented",
|
||||
"VisualStudioExptTeam.intellicode-api-usage-examples",
|
||||
"VisualStudioExptTeam.vscodeintellicode",
|
||||
"yzhang.markdown-all-in-one",
|
||||
"GraphQL.vscode-graphql-syntax",
|
||||
"mrmlnc.vscode-json5",
|
||||
"esbenp.prettier-vscode",
|
||||
"devnaumov.licensify"
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "CloudBeaver CE",
|
||||
"name": "Chrome",
|
||||
"url": "http://localhost:8080",
|
||||
"webRoot": "${workspaceFolder}/..",
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/../cloudbeaver/webapp/packages/**/dist/**/*.{js,jsx}"
|
||||
"${workspaceFolder}/../cloudbeaver/packages/**/dist/**/*.{js,jsx}"
|
||||
],
|
||||
"smartStep": true,
|
||||
"sourceMaps": true,
|
||||
@@ -17,60 +17,76 @@
|
||||
},
|
||||
{
|
||||
"type": "java",
|
||||
"name": "CloudBeaver CE Server",
|
||||
"cwd": "${workspaceFolder}/../dbeaver-workspace/app-workspace/cbce",
|
||||
"name": "CloudBeaver CE",
|
||||
"cwd": "${workspaceFolder}/workspace-dev-ce",
|
||||
"preLaunchTask": "Generate dev proprties for CBCE",
|
||||
"request": "launch",
|
||||
"mainClass": "org.jkiss.dbeaver.launcher.DBeaverLauncher",
|
||||
"args": [
|
||||
"-product",
|
||||
"io.cloudbeaver.product.ce.product",
|
||||
"-configuration",
|
||||
"file:${workspaceFolder}/../dbeaver-workspace/products/CloudbeaverServer.product/",
|
||||
"-dev",
|
||||
"file:${workspaceFolder}/../dbeaver-workspace/products/CloudbeaverServer.product/dev.properties",
|
||||
"-nl",
|
||||
"en",
|
||||
"-web-config",
|
||||
"conf/cloudbeaver.conf",
|
||||
"-registryMultiLanguage"
|
||||
],
|
||||
// "windows": {
|
||||
// "args": ["-os", "win32", "-ws", "win32", "-arch", "x86_64"]
|
||||
// },
|
||||
// "osx": {
|
||||
// "args": ["-os", "macosx", "-ws", "cocoa", "-arch", "aarch64"]
|
||||
// },
|
||||
|
||||
"vmArgs": [
|
||||
"-XX:+IgnoreUnrecognizedVMOptions",
|
||||
"-Xms64m",
|
||||
"-Xmx1024m",
|
||||
"-Declipse.pde.launch=true",
|
||||
"-XstartOnFirstThread",
|
||||
"-Dfile.encoding=UTF-8",
|
||||
"--add-modules=ALL-SYSTEM",
|
||||
"--add-opens=java.base/java.io=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.lang=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.lang.reflect=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.net=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.nio=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.nio.charset=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.text=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.time=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.util=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.util.concurrent=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED",
|
||||
"--add-opens=java.base/jdk.internal.vm=ALL-UNNAMED",
|
||||
"--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED",
|
||||
"--add-opens=java.base/sun.nio.ch=ALL-UNNAMED",
|
||||
"--add-opens=java.base/sun.security.ssl=ALL-UNNAMED",
|
||||
"--add-opens=java.base/sun.security.action=ALL-UNNAMED",
|
||||
"--add-opens=java.base/sun.security.util=ALL-UNNAMED",
|
||||
"--add-opens=java.security.jgss/sun.security.jgss=ALL-UNNAMED",
|
||||
"--add-opens=java.security.jgss/sun.security.krb5=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED",
|
||||
"--add-opens=java.sql/java.sql=ALL-UNNAMED"
|
||||
]
|
||||
"windows": {
|
||||
"type": "java",
|
||||
"name": "CloudBeaver CE",
|
||||
"request": "launch",
|
||||
"mainClass": "org.jkiss.dbeaver.launcher.DBeaverLauncher",
|
||||
"args": [
|
||||
"-product",
|
||||
"io.cloudbeaver.product.ce.product",
|
||||
"-configuration",
|
||||
"file:${workspaceFolder}/../eclipse/workspace/.metadata/.plugins/org.eclipse.pde.core/CloudbeaverServer.product/",
|
||||
"-dev",
|
||||
"file:${workspaceFolder}/../eclipse/workspace/.metadata/.plugins/org.eclipse.pde.core/CloudbeaverServer.product/dev.properties",
|
||||
"-os",
|
||||
"win32",
|
||||
"-ws",
|
||||
"win32",
|
||||
"-arch",
|
||||
"x86_64",
|
||||
"-nl",
|
||||
"en",
|
||||
"-showsplash",
|
||||
"-web-config",
|
||||
"conf/cloudbeaver.conf"
|
||||
],
|
||||
"vmArgs": [
|
||||
"-XX:+IgnoreUnrecognizedVMOptions",
|
||||
"--add-modules=ALL-SYSTEM",
|
||||
"-Xms64m",
|
||||
"-Xmx1024m",
|
||||
"-Declipse.pde.launch=true"
|
||||
]
|
||||
},
|
||||
"osx": {
|
||||
"type": "java",
|
||||
"name": "CloudBeaver CE",
|
||||
"request": "launch",
|
||||
"mainClass": "org.jkiss.dbeaver.launcher.DBeaverLauncher",
|
||||
"args": [
|
||||
"-product",
|
||||
"io.cloudbeaver.product.ce.product",
|
||||
"-configuration",
|
||||
"file:${workspaceFolder}/../eclipse/workspace/.metadata/.plugins/org.eclipse.pde.core/CloudbeaverServer.product/",
|
||||
"-dev",
|
||||
"file:${workspaceFolder}/../eclipse/workspace/.metadata/.plugins/org.eclipse.pde.core/CloudbeaverServer.product/dev.properties",
|
||||
"-os",
|
||||
"macosx",
|
||||
"-ws",
|
||||
"cocoa",
|
||||
"-arch",
|
||||
"aarch64",
|
||||
"-nl",
|
||||
"en",
|
||||
"-showsplash",
|
||||
"-web-config",
|
||||
"conf/cloudbeaver.conf"
|
||||
],
|
||||
"vmArgs": [
|
||||
"-XX:+IgnoreUnrecognizedVMOptions",
|
||||
"--add-modules=ALL-SYSTEM",
|
||||
"-Xms64m",
|
||||
"-Xmx1024m",
|
||||
"-Declipse.pde.launch=true",
|
||||
"-XstartOnFirstThread"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"license": {
|
||||
"scope": "typescript,typescriptreact,javascript,javascriptreact,css,scss",
|
||||
"prefix": "license",
|
||||
"body": [
|
||||
"/*",
|
||||
" * CloudBeaver - Cloud Database Manager",
|
||||
" * Copyright (C) 2020-2024 DBeaver Corp and others",
|
||||
" *",
|
||||
" * Licensed under the Apache License, Version 2.0.",
|
||||
" * you may not use this file except in compliance with the License.",
|
||||
" */",
|
||||
"",
|
||||
""
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,19 @@
|
||||
"editor.tabSize": 2,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.linkedEditing": true,
|
||||
|
||||
"task.autoDetect": "off",
|
||||
|
||||
"java.configuration.updateBuildConfiguration": "automatic",
|
||||
"java.jdt.ls.vmargs": "-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx16G -Xms100m -Xlog:disable",
|
||||
"java.compile.nullAnalysis.mode": "automatic",
|
||||
|
||||
"typescript.preferences.useAliasesForRenames": false,
|
||||
"typescript.preferences.importModuleSpecifier": "relative",
|
||||
"typescript.preferences.includePackageJsonAutoImports": "on",
|
||||
"typescript.preferences.quoteStyle": "single",
|
||||
"typescript.tsdk": "webapp/.yarn/sdks/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
"prettier.prettierPath": "webapp/.yarn/sdks/prettier/index.cjs",
|
||||
"eslint.nodePath": "webapp/.yarn/sdks",
|
||||
"typescript.tsdk": "webapp/node_modules/typescript/lib",
|
||||
|
||||
"eslint.enable": true,
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
@@ -21,9 +22,8 @@
|
||||
"typescript",
|
||||
"typescriptreact"
|
||||
],
|
||||
"eslint.workingDirectories": [
|
||||
"webapp"
|
||||
],
|
||||
"eslint.workingDirectories": ["webapp"],
|
||||
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
@@ -40,10 +40,12 @@
|
||||
"[java]": {
|
||||
"editor.defaultFormatter": "redhat.java"
|
||||
},
|
||||
|
||||
"files.eol": "\n",
|
||||
"files.associations": {
|
||||
"*.css": "postcss"
|
||||
},
|
||||
"java.checkstyle.configuration": "${workspaceFolder}/../dbeaver-common/.github/dbeaver-checkstyle-config.xml",
|
||||
|
||||
"java.checkstyle.configuration": "${workspaceFolder}/../dbeaver/dbeaver-checkstyle-config.xml",
|
||||
"java.checkstyle.version": "10.12.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,17 +17,7 @@
|
||||
"type": "shell",
|
||||
"command": "yarn",
|
||||
"group": "test",
|
||||
"args": ["test"]
|
||||
},
|
||||
{
|
||||
"label": "Run Tests Common Typescript",
|
||||
"type": "shell",
|
||||
"command": "yarn",
|
||||
"group": "test",
|
||||
"args": ["test"],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/common-typescript"
|
||||
}
|
||||
"args": ["lerna", "run", "test", "--no-bail", "--stream", "--"]
|
||||
},
|
||||
{
|
||||
"label": "Run DevServer CE",
|
||||
@@ -44,16 +34,27 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Generate Eclipse PDE CloudBeaver",
|
||||
"label": "Generate dev proprties for CBCE",
|
||||
"type": "shell",
|
||||
"command": "mvn",
|
||||
"osx": {
|
||||
"command": "./generate_workspace.sh"
|
||||
"args": [
|
||||
"package",
|
||||
"-q",
|
||||
"exec:java",
|
||||
"-Dexec.args=-config ${workspaceFolder}/../idea-workspace-dbeaver/rcp-gen.properties -productFile ${workspaceFolder}/server/product/web-server/CloudbeaverServer.product -projectsFolder ${workspaceFolder}/../ -eclipse ${workspaceFolder}/../eclipse/Eclipse.app/Contents/Eclipse -output ${workspaceFolder}/../eclipse/workspace/.metadata/.plugins/org.eclipse.pde.core/CloudbeaverServer.product"
|
||||
]
|
||||
},
|
||||
"windows": {
|
||||
"command": "./generate_workspace.cmd"
|
||||
"args": [
|
||||
"package",
|
||||
"-q",
|
||||
"exec:java",
|
||||
"-Dexec.args=-config ${workspaceFolder}/../idea-workspace-dbeaver/rcp-gen.properties -productFile ${workspaceFolder}/server/product/web-server/CloudbeaverServer.product -projectsFolder ${workspaceFolder}/../ -eclipse ${workspaceFolder}/../eclipse/eclipse -output ${workspaceFolder}/../eclipse/workspace/.metadata/.plugins/org.eclipse.pde.core/CloudbeaverServer.product"
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/../cloudbeaver"
|
||||
"cwd": "${workspaceFolder}/../idea-rcp-launch-config-generator"
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "silent",
|
||||
@@ -65,10 +66,10 @@
|
||||
"label": "Build CE",
|
||||
"type": "shell",
|
||||
"windows": {
|
||||
"command": "./build.bat"
|
||||
"command": "./build-sqlite.bat"
|
||||
},
|
||||
"osx": {
|
||||
"command": "./build.sh"
|
||||
"command": "./build-sqlite.sh"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/deploy"
|
||||
@@ -94,7 +95,7 @@
|
||||
{
|
||||
"label": "Yarn Install",
|
||||
"type": "shell",
|
||||
"command": "yarn install",
|
||||
"command": "yarn install; yarn lerna bootstrap",
|
||||
"args": [],
|
||||
"presentation": {
|
||||
"reveal": "silent",
|
||||
@@ -103,29 +104,12 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Clean CE",
|
||||
"label": "Update TS references CE",
|
||||
"type": "shell",
|
||||
"command": "yarn clear",
|
||||
"command": "yarn lerna run update-ts-references --stream",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/webapp"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Add Custom Plugin",
|
||||
"type": "shell",
|
||||
"command": "yarn run add-plugin",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/webapp/packages"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Run Ui Kit Preview",
|
||||
"type": "shell",
|
||||
"command": "yarn",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/common-react/@dbeaver/ui-kit"
|
||||
},
|
||||
"args": ["docs"]
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
|
||||
@@ -1,80 +1,62 @@
|
||||
<img src="https://github.com/dbeaver/cloudbeaver/wiki/images/cloudbeaver-logo.png" align="right" width="250"/>
|
||||
|
||||
# CloudBeaver Community
|
||||
|
||||
Cloud Database Manager - Community Edition.
|
||||
CloudBeaver is a web server that provides a rich web interface. The server itself is a Java application, and the web part is written in TypeScript and React.
|
||||
It is free to use and open-source (licensed under [Apache 2](https://github.com/dbeaver/cloudbeaver/blob/devel/LICENSE) license).
|
||||
See our [WIKI](https://github.com/dbeaver/cloudbeaver/wiki) for more details.
|
||||
<img src="https://github.com/dbeaver/cloudbeaver/wiki/images/cloudbeaver-logo.png" width="250"/>
|
||||
|
||||
<a><img src="https://github.com/dbeaver/cloudbeaver/wiki/images/connection-creation-demo.png" width="400"/></a>
|
||||
<img src="https://github.com/dbeaver/cloudbeaver/wiki/images/gis-demo.png" width="400"/>
|
||||
<img src="https://github.com/dbeaver/cloudbeaver/wiki/images/data-transfer-demo.png" width="400"/>
|
||||
<img src="https://github.com/dbeaver/cloudbeaver/wiki/images/sql-editor-demo.png" width="400"/>
|
||||
Cloud Database Manager - Community Edition.
|
||||
CloudBeaver is a web server which provides rich web interface. Server itself is a Java application, web part is written on TypeScript and React.
|
||||
It is free to use and open-source (licensed under [Apache 2](https://github.com/dbeaver/cloudbeaver/blob/devel/LICENSE) license).
|
||||
See out [WIKI](https://github.com/dbeaver/cloudbeaver/wiki) for more details.
|
||||
|
||||

|
||||
|
||||
## Run in Docker
|
||||
|
||||
- [Official Docker repository](https://hub.docker.com/r/dbeaver/cloudbeaver)
|
||||
- [Deployment instructions](https://github.com/dbeaver/cloudbeaver/wiki/CloudBeaver-Deployment)
|
||||
- [Running instructions](https://github.com/dbeaver/cloudbeaver/wiki/Run-Docker-Container)
|
||||
|
||||
## Demo server
|
||||
|
||||
You can see a live demo of CloudBeaver here: https://demo.cloudbeaver.io
|
||||
You can see live demo of CloudBeaver here: https://demo.cloudbeaver.io
|
||||
|
||||
[Database access instructions](https://github.com/dbeaver/cloudbeaver/wiki/Demo-Server)
|
||||
|
||||
## Changelog
|
||||
|
||||
### 25.1.3 2025-07-21
|
||||
- A password confirmation field has been added for administrators in the Easy Config section to help prevent accidental misconfigurations;
|
||||
- Added column descriptions in the Data Editor to provide more metadata context. You can disable this in Preferences > Data Viewer;
|
||||
- Added the option to display tabs across multiple rows, allowing you to see all tabs without scrolling. You can enable this in Preferences > Interface;
|
||||
- Changed the default engine used for autocompletion in the SQL Editor. This Semantic engine offers improved suggestions for database objects, keywords, and functions. You can switch back to the Legacy engine in Preferences -> SQL Editor;
|
||||
- Clickhouse: fixed the presentation of tuples and map data types in the data editor.
|
||||
### 24.0.1. 2024-03-25
|
||||
- Added support for two modes for committing changes to the database:
|
||||
- Auto-commit transfers all changes that you make immediately to the database;
|
||||
- Manual commit requires your confirmation before committing a change to the database or rolling it back.
|
||||
- Different bug fixes and enhancements have been made.
|
||||
|
||||
### 24.0.0. 2024-03-04
|
||||
- Changes since 23.3.0:
|
||||
|
||||
### 25.1.2 2025-07-07
|
||||
- The CloudBeaver default theme matches the device theme by default now. You can change this behavior in user preferences under the Theme section;
|
||||
- Added a button “Clear” to the Output panel;
|
||||
- Changed the data transfer mechanism to avoid intermediate file creation. The parameter dataExportFileSizeLimit was removed from the server configuration as deprecated;
|
||||
- PostgresSQL: fixed misplaced comment for table DDL generation.
|
||||
|
||||
### 25.1.1 2025-06-23
|
||||
### Changes since 25.1.0:
|
||||
- Added a search panel for SQL Editor and Value panel: press Ctrl+F to open a panel that allows searching and replacing by keyword or regular expression.
|
||||
- Added a new welcome screen for a freshly opened application. This screen contains shortcuts to create a new connection, open SQL editor or documentation.
|
||||
- Databend database support has been added.
|
||||
|
||||
### 25.1.0 2025-06-02
|
||||
### Changes since 25.0.0:
|
||||
- Important:
|
||||
- Connection templates were removed from the application. You can use the Connection Editor to create new connections.
|
||||
- Main updates:
|
||||
- Have enhanced security for unauthorised access;
|
||||
- Have added a password policy for the local authorization. Password parameters can be set in the configuration file;
|
||||
- Have added validation for mandatory fields in all forms to create and edit entities;
|
||||
- Have added an ability to edit the default preferences of the following parts: interface, tools and data viewer in the settings panel in the administrative part;
|
||||
- Performance:
|
||||
- Have improved the performance of the server.
|
||||
- Administration:
|
||||
- Log records now capture create, update, and delete actions for users, teams, and connections for improved tracking and transparency.
|
||||
- LDAP authorization:
|
||||
- Added support for secure [LDAP authentication over SSL](https://github.com/dbeaver/cloudbeaver/wiki/LDAP-Authentication);
|
||||
- Brute force protection is now supported for LDAP authentication. All authentication methods on the application level have this security mechanism.
|
||||
- SQL Editor:
|
||||
- A new setting to autosave scripts in the SQL editor appeared in global and users' preferences;
|
||||
- Auto-completion has been improved for aliases and camelCase entities.
|
||||
- Data Editor:
|
||||
- Improved table rendering to scale column width depending on the content;
|
||||
- Accessibility: improved keyboard navigation by sorting buttons in column headers.
|
||||
- General:
|
||||
- Added the ability to configure the default database or schema in the connection configuration;
|
||||
- Enhanced initial configuration stage security: a server needs to be restarted if the initial setup time exceeds 1 hour;
|
||||
- Improved Navigation tree performance: lost connection does not lead to the application freezing anymore;
|
||||
- Data transfer: Improved export/import functionality performance by optimizing disk memory consumption;
|
||||
- Vietnamese localization has been added (thanks to [0xhanh](https://github.com/0xhanh)).
|
||||
- Databases and drivers:
|
||||
- Clickhouse: driver has been updated to version 0.8.5;
|
||||
- DuckDB: expanded the list of system objects that can be hidden in the Navigation tree;
|
||||
- LibSQL: added the ability to connect using token authentication;
|
||||
- Oracle: users' DDL in Oracle is displayed in the metadata editor on the corresponding tab for users with Oracle administration permissions;
|
||||
- SQLite: added information about a table's Strict mode to the table metadata section (thanks to [eusebe-cda](https://github.com/eusebe-cda)).
|
||||
|
||||
## Contribution
|
||||
As a community-driven open-source project, we warmly welcome contributions through GitHub pull requests.
|
||||
|
||||
[We are happy to reward](https://dbeaver.com/help-dbeaver/) our most active contributors every major sprint.
|
||||
The most significant contribution to our code for the major release 25.1.0 was made by:
|
||||
1. [0xhanh](https://github.com/0xhanh) - for Vietnamese localization.
|
||||
- Have added the ability to configure reverse proxy header name and redirect URL at logout. Now the administrator can configure all settings. The delimiter symbol for the reverse proxy team headers is configurable;
|
||||
- Preferences:
|
||||
- Have added the ability in the admin panel to customise copying in the Tables and Values panel for all users except the administrator;
|
||||
- Have added the ability to disable alias autocomplete for all users.
|
||||
- Access Management:
|
||||
- A default user group was added to the product. This group includes all users which could not be deleted.
|
||||
- Connections:
|
||||
- The 'Keep alive' option has been added to the connection settings to keep the connection active even in case of inactivity.
|
||||
- Data Editor:
|
||||
- Have added the ability to view decoded binary-type data in the Value panel;
|
||||
- Text wrap is activated by default for texts and BLOBs in the Values panel for better visibility. The user can switch to the one-line mode using a button on the toolbar;
|
||||
- Have added the ability to display full text for a string data type in the Value panel;
|
||||
- Have added the ability to display the OpenStreetMap layer with the coordinate system ESPG 4326.
|
||||
- Resource management:
|
||||
- Read-only scripts now have a padlock icon.
|
||||
- Databases:
|
||||
- The DuckDB driver has been added;
|
||||
- Updated PostgreSQL driver to 42.5.2;
|
||||
- ClickHouse Legacy driver has been removed to prevent potential vulnerability issues;
|
||||
- Updated MariaDB driver to version 3.3.2.
|
||||
- Different bug fixes and enhancements have been made.
|
||||
|
||||
@@ -9,10 +9,9 @@ currently being supported with security updates.
|
||||
| ------- | --------- |
|
||||
| 22.x | yes |
|
||||
| 23.x | yes |
|
||||
| 24.x | yes |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please report (suspected) security vulnerabilities to devops@dbeaver.com.
|
||||
You will receive a response from us within 48 hours.
|
||||
If the issue is confirmed, we will release a patch as soon as possible, depending on complexity, but historically, within a few days.
|
||||
Please report (suspected) security vulnerabilities to devops@dbeaver.com.
|
||||
You will receive a response from us within 48 hours.
|
||||
If the issue is confirmed, we will release a patch as soon as possible depending on complexity but historically within a few days.
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>2.1.214</version>
|
||||
<version>2.2.220</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{js,json,yml}]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
@@ -1,4 +0,0 @@
|
||||
/.yarn/** linguist-vendored
|
||||
/.yarn/releases/* binary
|
||||
/.yarn/plugins/**/* binary
|
||||
/.pnp.* binary linguist-generated
|
||||
@@ -1,21 +0,0 @@
|
||||
# artifacts
|
||||
lib
|
||||
out
|
||||
dist
|
||||
node_modules
|
||||
|
||||
# Yarn
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
!.yarn/sdks/**
|
||||
|
||||
# Swap the comments on the following lines if you wish to use zero-installs
|
||||
# In that case, don't forget to run `yarn config set enableGlobalCache false`!
|
||||
# Documentation here: https://yarnpkg.com/features/caching#zero-installs
|
||||
|
||||
#!.yarn/cache
|
||||
.pnp.*
|
||||
@@ -1,10 +0,0 @@
|
||||
import defaultDbeaverConfig from '@dbeaver/prettier-config';
|
||||
|
||||
/**
|
||||
* @type {import("prettier").Config}
|
||||
*/
|
||||
const config = {
|
||||
...defaultDbeaverConfig,
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"arcanis.vscode-zipfs",
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"search.exclude": {
|
||||
"**/.yarn": true,
|
||||
"**/.pnp.*": true
|
||||
},
|
||||
"typescript.tsdk": ".yarn/sdks/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
"prettier.prettierPath": ".yarn/sdks/prettier/index.cjs"
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
# This file is automatically generated by @yarnpkg/sdks.
|
||||
# Manual changes might be lost!
|
||||
|
||||
integrations:
|
||||
- vscode
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, register} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
const {pathToFileURL} = require(`url`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
|
||||
const absRequire = createRequire(absPnpApiPath);
|
||||
|
||||
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
|
||||
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require prettier/bin/prettier.cjs
|
||||
require(absPnpApiPath).setup();
|
||||
if (isPnpLoaderEnabled && register) {
|
||||
register(pathToFileURL(absPnpLoaderPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
|
||||
? exports => absRequire(absUserWrapperPath)(exports)
|
||||
: exports => exports;
|
||||
|
||||
// Defer to the real prettier/bin/prettier.cjs your application uses
|
||||
module.exports = wrapWithUserWrapper(absRequire(`prettier/bin/prettier.cjs`));
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, register} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
const {pathToFileURL} = require(`url`);
|
||||
|
||||
const relPnpApiPath = "../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
|
||||
const absRequire = createRequire(absPnpApiPath);
|
||||
|
||||
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
|
||||
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require prettier
|
||||
require(absPnpApiPath).setup();
|
||||
if (isPnpLoaderEnabled && register) {
|
||||
register(pathToFileURL(absPnpLoaderPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
|
||||
? exports => absRequire(absUserWrapperPath)(exports)
|
||||
: exports => exports;
|
||||
|
||||
// Defer to the real prettier your application uses
|
||||
module.exports = wrapWithUserWrapper(absRequire(`prettier`));
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"name": "prettier",
|
||||
"version": "3.6.2-sdk",
|
||||
"main": "./index.cjs",
|
||||
"type": "commonjs",
|
||||
"bin": "./bin/prettier.cjs"
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
enableTelemetry: false
|
||||
preferReuse: true
|
||||
|
||||
# pnpIgnorePatterns:
|
||||
# - "**/cloudbeaver/common-typescript/**"
|
||||
|
||||
plugins:
|
||||
- checksum: 50414773926286d8c0fc32e9936a799ad23296281046f03674bc33517b597e1ff7856092c3fcf8d5460a29549ef8b3dca3f59a1732b650a02bf523b255d726e7
|
||||
path: .yarn/plugins/@yarnpkg/plugin-ts-project-linker.cjs
|
||||
spec: "https://raw.githubusercontent.com/Wroud/foundation/refs/heads/main/packages/yarn-plugin-ts-project-linker/bundles/%40yarnpkg/plugin-ts-project-linker.js"
|
||||
@@ -1 +0,0 @@
|
||||
# @dbeaver/react-data-grid
|
||||
@@ -1 +0,0 @@
|
||||
!lib
|
||||
@@ -1,537 +0,0 @@
|
||||
import * as react2 from "react";
|
||||
import * as react13 from "react";
|
||||
import * as react10 from "react";
|
||||
import { Key, ReactElement, ReactNode } from "react";
|
||||
import * as react_jsx_runtime1 from "react/jsx-runtime";
|
||||
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
||||
import * as react_jsx_runtime9 from "react/jsx-runtime";
|
||||
import * as react_jsx_runtime11 from "react/jsx-runtime";
|
||||
import * as react_jsx_runtime4 from "react/jsx-runtime";
|
||||
import * as react_jsx_runtime5 from "react/jsx-runtime";
|
||||
import * as react_jsx_runtime7 from "react/jsx-runtime";
|
||||
|
||||
//#region src/types.d.ts
|
||||
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
|
||||
type Maybe<T> = T | undefined | null;
|
||||
interface Column<TRow, TSummaryRow = unknown> {
|
||||
/** The name of the column. Displayed in the header cell by default */
|
||||
readonly name: string | ReactElement;
|
||||
/** A unique key to distinguish each column */
|
||||
readonly key: string;
|
||||
/**
|
||||
* Column width. If not specified, it will be determined automatically based on grid width and specified widths of other columns
|
||||
* @default 'auto'
|
||||
*/
|
||||
readonly width?: Maybe<number | string>;
|
||||
/**
|
||||
* Minimum column width in pixels
|
||||
* @default 50
|
||||
*/
|
||||
readonly minWidth?: Maybe<number>;
|
||||
/** Maximum column width in pixels */
|
||||
readonly maxWidth?: Maybe<number>;
|
||||
/** Class name(s) for the cell */
|
||||
readonly cellClass?: Maybe<string | ((row: TRow) => Maybe<string>)>;
|
||||
/** Class name(s) for the header cell */
|
||||
readonly headerCellClass?: Maybe<string>;
|
||||
/** Class name(s) for the summary cell */
|
||||
readonly summaryCellClass?: Maybe<string | ((row: TSummaryRow) => Maybe<string>)>;
|
||||
/** Render function to render the content of cells */
|
||||
readonly renderCell?: Maybe<(props: RenderCellProps<TRow, TSummaryRow>) => ReactNode>;
|
||||
/** Render function to render the content of the header cell */
|
||||
readonly renderHeaderCell?: Maybe<(props: RenderHeaderCellProps<TRow, TSummaryRow>) => ReactNode>;
|
||||
/** Render function to render the content of summary cells */
|
||||
readonly renderSummaryCell?: Maybe<(props: RenderSummaryCellProps<TSummaryRow, TRow>) => ReactNode>;
|
||||
/** Render function to render the content of group cells */
|
||||
readonly renderGroupCell?: Maybe<(props: RenderGroupCellProps<TRow, TSummaryRow>) => ReactNode>;
|
||||
/** Render function to render the content of edit cells. When set, the column is automatically set to be editable */
|
||||
readonly renderEditCell?: Maybe<(props: RenderEditCellProps<TRow, TSummaryRow>) => ReactNode>;
|
||||
/** Enables cell editing. If set and no editor property specified, then a textinput will be used as the cell editor */
|
||||
readonly editable?: Maybe<boolean | ((row: TRow) => boolean)>;
|
||||
readonly colSpan?: Maybe<(args: ColSpanArgs<TRow, TSummaryRow>) => Maybe<number>>;
|
||||
/** Determines whether column is frozen */
|
||||
readonly frozen?: Maybe<boolean>;
|
||||
/** Enable resizing of the column */
|
||||
readonly resizable?: Maybe<boolean>;
|
||||
/** Enable sorting of the column */
|
||||
readonly sortable?: Maybe<boolean>;
|
||||
/** Enable dragging of the column */
|
||||
readonly draggable?: Maybe<boolean>;
|
||||
/** Sets the column sort order to be descending instead of ascending the first time the column is sorted */
|
||||
readonly sortDescendingFirst?: Maybe<boolean>;
|
||||
/** Options for cell editing */
|
||||
readonly editorOptions?: Maybe<{
|
||||
/**
|
||||
* Render the cell content in addition to the edit cell.
|
||||
* Enable this option when the editor is rendered outside the grid, like a modal for example.
|
||||
* By default, the cell content is not rendered when the edit cell is open.
|
||||
* @default false
|
||||
*/
|
||||
readonly displayCellContent?: Maybe<boolean>;
|
||||
/**
|
||||
* Commit changes when clicking outside the cell
|
||||
* @default true
|
||||
*/
|
||||
readonly commitOnOutsideClick?: Maybe<boolean>;
|
||||
/**
|
||||
* Close the editor when the row changes externally
|
||||
* @default true
|
||||
*/
|
||||
readonly closeOnExternalRowChange?: Maybe<boolean>;
|
||||
}>;
|
||||
}
|
||||
interface CalculatedColumn<TRow, TSummaryRow = unknown> extends Column<TRow, TSummaryRow> {
|
||||
readonly parent: CalculatedColumnParent<TRow, TSummaryRow> | undefined;
|
||||
readonly idx: number;
|
||||
readonly level: number;
|
||||
readonly width: number | string;
|
||||
readonly minWidth: number;
|
||||
readonly maxWidth: number | undefined;
|
||||
readonly resizable: boolean;
|
||||
readonly sortable: boolean;
|
||||
readonly draggable: boolean;
|
||||
readonly frozen: boolean;
|
||||
readonly renderCell: (props: RenderCellProps<TRow, TSummaryRow>) => ReactNode;
|
||||
readonly renderHeaderCell: (props: RenderHeaderCellProps<TRow, TSummaryRow>) => ReactNode;
|
||||
}
|
||||
interface ColumnGroup<R, SR = unknown> {
|
||||
/** The name of the column group, it will be displayed in the header cell */
|
||||
readonly name: string | ReactElement;
|
||||
readonly headerCellClass?: Maybe<string>;
|
||||
readonly children: readonly ColumnOrColumnGroup<R, SR>[];
|
||||
}
|
||||
interface CalculatedColumnParent<R, SR> {
|
||||
readonly name: string | ReactElement;
|
||||
readonly parent: CalculatedColumnParent<R, SR> | undefined;
|
||||
readonly idx: number;
|
||||
readonly colSpan: number;
|
||||
readonly level: number;
|
||||
readonly headerCellClass?: Maybe<string>;
|
||||
}
|
||||
type ColumnOrColumnGroup<R, SR = unknown> = Column<R, SR> | ColumnGroup<R, SR>;
|
||||
type CalculatedColumnOrColumnGroup<R, SR> = CalculatedColumnParent<R, SR> | CalculatedColumn<R, SR>;
|
||||
interface Position {
|
||||
readonly idx: number;
|
||||
readonly rowIdx: number;
|
||||
}
|
||||
interface RenderCellProps<TRow, TSummaryRow = unknown> {
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
row: TRow;
|
||||
rowIdx: number;
|
||||
isCellEditable: boolean;
|
||||
tabIndex: number;
|
||||
onRowChange: (row: TRow) => void;
|
||||
}
|
||||
interface RenderSummaryCellProps<TSummaryRow, TRow = unknown> {
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
row: TSummaryRow;
|
||||
tabIndex: number;
|
||||
}
|
||||
interface RenderGroupCellProps<TRow, TSummaryRow = unknown> {
|
||||
groupKey: unknown;
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
row: GroupRow<TRow>;
|
||||
childRows: readonly TRow[];
|
||||
isExpanded: boolean;
|
||||
tabIndex: number;
|
||||
toggleGroup: () => void;
|
||||
}
|
||||
interface RenderEditCellProps<TRow, TSummaryRow = unknown> {
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
row: TRow;
|
||||
rowIdx: number;
|
||||
onRowChange: (row: TRow, commitChanges?: boolean) => void;
|
||||
onClose: (commitChanges?: boolean, shouldFocusCell?: boolean) => void;
|
||||
}
|
||||
interface RenderHeaderCellProps<TRow, TSummaryRow = unknown> {
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
sortDirection: SortDirection | undefined;
|
||||
priority: number | undefined;
|
||||
tabIndex: number;
|
||||
}
|
||||
interface BaseCellRendererProps<TRow, TSummaryRow = unknown> extends Omit<React.ComponentProps<'div'>, 'children'>, Pick<DataGridProps<TRow, TSummaryRow>, 'onCellMouseDown' | 'onCellClick' | 'onCellDoubleClick' | 'onCellContextMenu'> {
|
||||
rowIdx: number;
|
||||
selectCell: (position: Position, options?: SelectCellOptions) => void;
|
||||
}
|
||||
interface CellRendererProps<TRow, TSummaryRow> extends BaseCellRendererProps<TRow, TSummaryRow> {
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
row: TRow;
|
||||
colSpan: number | undefined;
|
||||
isDraggedOver: boolean;
|
||||
isCellSelected: boolean;
|
||||
onRowChange: (column: CalculatedColumn<TRow, TSummaryRow>, newRow: TRow) => void;
|
||||
}
|
||||
type CellEvent<E extends React.SyntheticEvent<HTMLDivElement>> = E & {
|
||||
preventGridDefault: () => void;
|
||||
isGridDefaultPrevented: () => boolean;
|
||||
};
|
||||
type CellMouseEvent = CellEvent<React.MouseEvent<HTMLDivElement>>;
|
||||
type CellKeyboardEvent = CellEvent<React.KeyboardEvent<HTMLDivElement>>;
|
||||
type CellClipboardEvent = React.ClipboardEvent<HTMLDivElement>;
|
||||
interface CellMouseArgs<TRow, TSummaryRow = unknown> {
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
row: TRow;
|
||||
rowIdx: number;
|
||||
selectCell: (enableEditor?: boolean) => void;
|
||||
}
|
||||
interface SelectCellKeyDownArgs<TRow, TSummaryRow = unknown> {
|
||||
mode: 'SELECT';
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
row: TRow;
|
||||
rowIdx: number;
|
||||
selectCell: (position: Position, options?: SelectCellOptions) => void;
|
||||
}
|
||||
interface EditCellKeyDownArgs<TRow, TSummaryRow = unknown> {
|
||||
mode: 'EDIT';
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
row: TRow;
|
||||
rowIdx: number;
|
||||
navigate: () => void;
|
||||
onClose: (commitChanges?: boolean, shouldFocusCell?: boolean) => void;
|
||||
}
|
||||
type CellKeyDownArgs<TRow, TSummaryRow = unknown> = SelectCellKeyDownArgs<TRow, TSummaryRow> | EditCellKeyDownArgs<TRow, TSummaryRow>;
|
||||
interface CellSelectArgs<TRow, TSummaryRow = unknown> {
|
||||
rowIdx: number;
|
||||
row: TRow | undefined;
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
}
|
||||
type CellMouseEventHandler<R, SR> = Maybe<(args: CellMouseArgs<NoInfer<R>, NoInfer<SR>>, event: CellMouseEvent) => void>;
|
||||
interface BaseRenderRowProps<TRow, TSummaryRow = unknown> extends BaseCellRendererProps<TRow, TSummaryRow> {
|
||||
viewportColumns: readonly CalculatedColumn<TRow, TSummaryRow>[];
|
||||
rowIdx: number;
|
||||
selectedCellIdx: number | undefined;
|
||||
isRowSelectionDisabled: boolean;
|
||||
isRowSelected: boolean;
|
||||
gridRowStart: number;
|
||||
}
|
||||
interface RenderRowProps<TRow, TSummaryRow = unknown> extends BaseRenderRowProps<TRow, TSummaryRow> {
|
||||
row: TRow;
|
||||
lastFrozenColumnIndex: number;
|
||||
draggedOverCellIdx: number | undefined;
|
||||
selectedCellEditor: ReactElement<RenderEditCellProps<TRow>> | undefined;
|
||||
onRowChange: (column: CalculatedColumn<TRow, TSummaryRow>, rowIdx: number, newRow: TRow) => void;
|
||||
rowClass: Maybe<(row: TRow, rowIdx: number) => Maybe<string>>;
|
||||
}
|
||||
interface RowsChangeData<R, SR = unknown> {
|
||||
indexes: number[];
|
||||
column: CalculatedColumn<R, SR>;
|
||||
}
|
||||
interface SelectRowEvent<TRow> {
|
||||
row: TRow;
|
||||
checked: boolean;
|
||||
isShiftClick: boolean;
|
||||
}
|
||||
interface SelectHeaderRowEvent {
|
||||
checked: boolean;
|
||||
}
|
||||
interface FillEvent<TRow> {
|
||||
columnKey: string;
|
||||
sourceRow: TRow;
|
||||
targetRow: TRow;
|
||||
}
|
||||
interface CellCopyPasteArgs<TRow, TSummaryRow = unknown> {
|
||||
column: CalculatedColumn<TRow, TSummaryRow>;
|
||||
row: TRow;
|
||||
}
|
||||
type CellCopyArgs<TRow, TSummaryRow = unknown> = CellCopyPasteArgs<TRow, TSummaryRow>;
|
||||
type CellPasteArgs<TRow, TSummaryRow = unknown> = CellCopyPasteArgs<TRow, TSummaryRow>;
|
||||
interface GroupRow<TRow> {
|
||||
readonly childRows: readonly TRow[];
|
||||
readonly id: string;
|
||||
readonly parentId: unknown;
|
||||
readonly groupKey: unknown;
|
||||
readonly isExpanded: boolean;
|
||||
readonly level: number;
|
||||
readonly posInSet: number;
|
||||
readonly setSize: number;
|
||||
readonly startRowIndex: number;
|
||||
}
|
||||
interface SortColumn {
|
||||
readonly columnKey: string;
|
||||
readonly direction: SortDirection;
|
||||
}
|
||||
type SortDirection = 'ASC' | 'DESC';
|
||||
type ColSpanArgs<TRow, TSummaryRow> = {
|
||||
type: 'HEADER';
|
||||
} | {
|
||||
type: 'ROW';
|
||||
row: TRow;
|
||||
} | {
|
||||
type: 'SUMMARY';
|
||||
row: TSummaryRow;
|
||||
};
|
||||
type RowHeightArgs<TRow> = {
|
||||
type: 'ROW';
|
||||
row: TRow;
|
||||
} | {
|
||||
type: 'GROUP';
|
||||
row: GroupRow<TRow>;
|
||||
};
|
||||
interface RenderSortIconProps {
|
||||
sortDirection: SortDirection | undefined;
|
||||
}
|
||||
interface RenderSortPriorityProps {
|
||||
priority: number | undefined;
|
||||
}
|
||||
interface RenderSortStatusProps extends RenderSortIconProps, RenderSortPriorityProps {}
|
||||
interface RenderCheckboxProps extends Pick<React.ComponentProps<'input'>, 'aria-label' | 'aria-labelledby' | 'checked' | 'tabIndex' | 'disabled'> {
|
||||
indeterminate?: boolean | undefined;
|
||||
onChange: (checked: boolean, shift: boolean) => void;
|
||||
}
|
||||
interface Renderers<TRow, TSummaryRow> {
|
||||
renderCell?: Maybe<(key: Key, props: CellRendererProps<TRow, TSummaryRow>) => ReactNode>;
|
||||
renderCheckbox?: Maybe<(props: RenderCheckboxProps) => ReactNode>;
|
||||
renderRow?: Maybe<(key: Key, props: RenderRowProps<TRow, TSummaryRow>) => ReactNode>;
|
||||
renderSortStatus?: Maybe<(props: RenderSortStatusProps) => ReactNode>;
|
||||
noRowsFallback?: Maybe<ReactNode>;
|
||||
}
|
||||
interface SelectCellOptions {
|
||||
enableEditor?: Maybe<boolean>;
|
||||
shouldFocusCell?: Maybe<boolean>;
|
||||
}
|
||||
interface ColumnWidth {
|
||||
readonly type: 'resized' | 'measured';
|
||||
readonly width: number;
|
||||
}
|
||||
type ColumnWidths = ReadonlyMap<string, ColumnWidth>;
|
||||
type Direction = 'ltr' | 'rtl';
|
||||
//#endregion
|
||||
//#region src/ScrollToCell.d.ts
|
||||
interface PartialPosition {
|
||||
readonly idx?: number | undefined;
|
||||
readonly rowIdx?: number | undefined;
|
||||
}
|
||||
//#endregion
|
||||
//#region src/DataGrid.d.ts
|
||||
type DefaultColumnOptions<R, SR> = Pick<Column<R, SR>, 'renderCell' | 'renderHeaderCell' | 'width' | 'minWidth' | 'maxWidth' | 'resizable' | 'sortable' | 'draggable'>;
|
||||
interface DataGridHandle {
|
||||
element: HTMLDivElement | null;
|
||||
scrollToCell: (position: PartialPosition) => void;
|
||||
selectCell: (position: Position, options?: SelectCellOptions) => void;
|
||||
}
|
||||
type SharedDivProps = Pick<React.ComponentProps<'div'>, 'role' | 'aria-label' | 'aria-labelledby' | 'aria-description' | 'aria-describedby' | 'aria-rowcount' | 'className' | 'style'>;
|
||||
interface DataGridProps<R, SR = unknown, K extends Key = Key> extends SharedDivProps {
|
||||
ref?: Maybe<React.Ref<DataGridHandle>>;
|
||||
/**
|
||||
* Grid and data Props
|
||||
*/
|
||||
/** An array of column definitions */
|
||||
columns: readonly ColumnOrColumnGroup<NoInfer<R>, NoInfer<SR>>[];
|
||||
/** A function called for each rendered row that should return a plain key/value pair object */
|
||||
rows: readonly R[];
|
||||
/** Rows pinned at the top of the grid for summary purposes */
|
||||
topSummaryRows?: Maybe<readonly SR[]>;
|
||||
/** Rows pinned at the bottom of the grid for summary purposes */
|
||||
bottomSummaryRows?: Maybe<readonly SR[]>;
|
||||
/** Function to return a unique key/identifier for each row */
|
||||
rowKeyGetter?: Maybe<(row: NoInfer<R>) => K>;
|
||||
/** Callback triggered when rows are changed */
|
||||
onRowsChange?: Maybe<(rows: NoInfer<R>[], data: RowsChangeData<NoInfer<R>, NoInfer<SR>>) => void>;
|
||||
/**
|
||||
* Dimensions props
|
||||
*/
|
||||
/**
|
||||
* Height of each row in pixels
|
||||
* @default 35
|
||||
*/
|
||||
rowHeight?: Maybe<number | ((row: NoInfer<R>) => number)>;
|
||||
/**
|
||||
* Height of the header row in pixels
|
||||
* @default 35
|
||||
*/
|
||||
headerRowHeight?: Maybe<number>;
|
||||
/**
|
||||
* Height of each summary row in pixels
|
||||
* @default 35
|
||||
*/
|
||||
summaryRowHeight?: Maybe<number>;
|
||||
/** A map of column widths */
|
||||
columnWidths?: Maybe<ColumnWidths>;
|
||||
/** Callback triggered when column widths change */
|
||||
onColumnWidthsChange?: Maybe<(columnWidths: ColumnWidths) => void>;
|
||||
/**
|
||||
* Feature props
|
||||
*/
|
||||
/** A set of selected row keys */
|
||||
selectedRows?: Maybe<ReadonlySet<K>>;
|
||||
/** Function to determine if row selection is disabled for a specific row */
|
||||
isRowSelectionDisabled?: Maybe<(row: NoInfer<R>) => boolean>;
|
||||
/** Callback triggered when the selection changes */
|
||||
onSelectedRowsChange?: Maybe<(selectedRows: Set<NoInfer<K>>) => void>;
|
||||
/** An array of sorted columns */
|
||||
sortColumns?: Maybe<readonly SortColumn[]>;
|
||||
/** Callback triggered when sorting changes */
|
||||
onSortColumnsChange?: Maybe<(sortColumns: SortColumn[]) => void>;
|
||||
/** Default options applied to all columns */
|
||||
defaultColumnOptions?: Maybe<DefaultColumnOptions<NoInfer<R>, NoInfer<SR>>>;
|
||||
onFill?: Maybe<(event: FillEvent<NoInfer<R>>) => NoInfer<R>>;
|
||||
/**
|
||||
* Event props
|
||||
*/
|
||||
/** Callback triggered when a pointer becomes active in a cell */
|
||||
onCellMouseDown?: CellMouseEventHandler<R, SR>;
|
||||
/** Callback triggered when a cell is clicked */
|
||||
onCellClick?: CellMouseEventHandler<R, SR>;
|
||||
/** Callback triggered when a cell is double-clicked */
|
||||
onCellDoubleClick?: CellMouseEventHandler<R, SR>;
|
||||
/** Callback triggered when a cell is right-clicked */
|
||||
onCellContextMenu?: CellMouseEventHandler<R, SR>;
|
||||
/** Callback triggered when a key is pressed in a cell */
|
||||
onCellKeyDown?: Maybe<(args: CellKeyDownArgs<NoInfer<R>, NoInfer<SR>>, event: CellKeyboardEvent) => void>;
|
||||
/** Callback triggered when a cell's content is copied */
|
||||
onCellCopy?: Maybe<(args: CellCopyArgs<NoInfer<R>, NoInfer<SR>>, event: CellClipboardEvent) => void>;
|
||||
/** Callback triggered when content is pasted into a cell */
|
||||
onCellPaste?: Maybe<(args: CellPasteArgs<NoInfer<R>, NoInfer<SR>>, event: CellClipboardEvent) => NoInfer<R>>;
|
||||
/** Function called whenever cell selection is changed */
|
||||
onSelectedCellChange?: Maybe<(args: CellSelectArgs<NoInfer<R>, NoInfer<SR>>) => void>;
|
||||
/** Callback triggered when the grid is scrolled */
|
||||
onScroll?: Maybe<(event: React.UIEvent<HTMLDivElement>) => void>;
|
||||
/** Callback triggered when column is resized */
|
||||
onColumnResize?: Maybe<(column: CalculatedColumn<R, SR>, width: number) => void>;
|
||||
/** Callback triggered when columns are reordered */
|
||||
onColumnsReorder?: Maybe<(sourceColumnKey: string, targetColumnKey: string) => void>;
|
||||
/**
|
||||
* Toggles and modes
|
||||
*/
|
||||
/** @default true */
|
||||
enableVirtualization?: Maybe<boolean>;
|
||||
/**
|
||||
* Miscellaneous
|
||||
*/
|
||||
/** Custom renderers for cells, rows, and other components */
|
||||
renderers?: Maybe<Renderers<NoInfer<R>, NoInfer<SR>>>;
|
||||
/** Function to apply custom class names to rows */
|
||||
rowClass?: Maybe<(row: NoInfer<R>, rowIdx: number) => Maybe<string>>;
|
||||
/** Custom class name for the header row */
|
||||
headerRowClass?: Maybe<string>;
|
||||
/**
|
||||
* Text direction of the grid ('ltr' or 'rtl')
|
||||
* @default 'ltr'
|
||||
* */
|
||||
direction?: Maybe<Direction>;
|
||||
'data-testid'?: Maybe<string>;
|
||||
'data-cy'?: Maybe<string>;
|
||||
}
|
||||
/**
|
||||
* Main API Component to render a data grid of rows and columns
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* <DataGrid columns={columns} rows={rows} />
|
||||
*/
|
||||
declare function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridProps<R, SR, K>): react_jsx_runtime1.JSX.Element;
|
||||
//#endregion
|
||||
//#region src/TreeDataGrid.d.ts
|
||||
interface TreeDataGridProps<R, SR = unknown, K extends Key = Key> extends Omit<DataGridProps<R, SR, K>, 'columns' | 'role' | 'aria-rowcount' | 'rowHeight' | 'onFill' | 'isRowSelectionDisabled'> {
|
||||
columns: readonly Column<NoInfer<R>, NoInfer<SR>>[];
|
||||
rowHeight?: Maybe<number | ((args: RowHeightArgs<NoInfer<R>>) => number)>;
|
||||
groupBy: readonly string[];
|
||||
rowGrouper: (rows: readonly NoInfer<R>[], columnKey: string) => Record<string, readonly NoInfer<R>[]>;
|
||||
expandedGroupIds: ReadonlySet<unknown>;
|
||||
onExpandedGroupIdsChange: (expandedGroupIds: Set<unknown>) => void;
|
||||
groupIdGetter?: Maybe<(groupKey: string, parentId?: string) => string>;
|
||||
}
|
||||
declare function TreeDataGrid<R, SR = unknown, K extends Key = Key>({
|
||||
columns: rawColumns,
|
||||
rows: rawRows,
|
||||
rowHeight: rawRowHeight,
|
||||
rowKeyGetter: rawRowKeyGetter,
|
||||
onCellKeyDown: rawOnCellKeyDown,
|
||||
onCellCopy: rawOnCellCopy,
|
||||
onCellPaste: rawOnCellPaste,
|
||||
onRowsChange,
|
||||
selectedRows: rawSelectedRows,
|
||||
onSelectedRowsChange: rawOnSelectedRowsChange,
|
||||
renderers,
|
||||
groupBy: rawGroupBy,
|
||||
rowGrouper,
|
||||
expandedGroupIds,
|
||||
onExpandedGroupIdsChange,
|
||||
groupIdGetter: rawGroupIdGetter,
|
||||
...props
|
||||
}: TreeDataGridProps<R, SR, K>): react_jsx_runtime0.JSX.Element;
|
||||
//#endregion
|
||||
//#region src/DataGridDefaultRenderersContext.d.ts
|
||||
declare const DataGridDefaultRenderersContext: react2.Context<Maybe<Renderers<any, any>>>;
|
||||
//#endregion
|
||||
//#region src/Row.d.ts
|
||||
declare const RowComponent: <R, SR>(props: RenderRowProps<R, SR>) => React.JSX.Element;
|
||||
//#endregion
|
||||
//#region src/Cell.d.ts
|
||||
declare const CellComponent: <R, SR>(props: CellRendererProps<R, SR>) => React.JSX.Element;
|
||||
//#endregion
|
||||
//#region src/Columns.d.ts
|
||||
declare const SELECT_COLUMN_KEY = "rdg-select-column";
|
||||
declare const SelectColumn: Column<any, any>;
|
||||
//#endregion
|
||||
//#region src/cellRenderers/renderCheckbox.d.ts
|
||||
declare function renderCheckbox({
|
||||
onChange,
|
||||
indeterminate,
|
||||
...props
|
||||
}: RenderCheckboxProps): react_jsx_runtime9.JSX.Element;
|
||||
//#endregion
|
||||
//#region src/cellRenderers/renderToggleGroup.d.ts
|
||||
declare function renderToggleGroup<R, SR>(props: RenderGroupCellProps<R, SR>): react_jsx_runtime11.JSX.Element;
|
||||
declare function ToggleGroup<R, SR>({
|
||||
groupKey,
|
||||
isExpanded,
|
||||
tabIndex,
|
||||
toggleGroup
|
||||
}: RenderGroupCellProps<R, SR>): react_jsx_runtime11.JSX.Element;
|
||||
//#endregion
|
||||
//#region src/cellRenderers/renderValue.d.ts
|
||||
declare function renderValue<R, SR>(props: RenderCellProps<R, SR>): react13.ReactNode;
|
||||
//#endregion
|
||||
//#region src/cellRenderers/SelectCellFormatter.d.ts
|
||||
type SharedInputProps = Pick<RenderCheckboxProps, 'disabled' | 'tabIndex' | 'aria-label' | 'aria-labelledby' | 'indeterminate' | 'onChange'>;
|
||||
interface SelectCellFormatterProps extends SharedInputProps {
|
||||
value: boolean;
|
||||
}
|
||||
declare function SelectCellFormatter({
|
||||
value,
|
||||
tabIndex,
|
||||
indeterminate,
|
||||
disabled,
|
||||
onChange,
|
||||
'aria-label': ariaLabel,
|
||||
'aria-labelledby': ariaLabelledBy
|
||||
}: SelectCellFormatterProps): react10.ReactNode;
|
||||
//#endregion
|
||||
//#region src/editors/textEditor.d.ts
|
||||
declare function textEditor<TRow, TSummaryRow>({
|
||||
row,
|
||||
column,
|
||||
onRowChange,
|
||||
onClose
|
||||
}: RenderEditCellProps<TRow, TSummaryRow>): react_jsx_runtime4.JSX.Element;
|
||||
//#endregion
|
||||
//#region src/renderHeaderCell.d.ts
|
||||
declare function renderHeaderCell<R, SR>({
|
||||
column,
|
||||
sortDirection,
|
||||
priority
|
||||
}: RenderHeaderCellProps<R, SR>): string | react_jsx_runtime5.JSX.Element;
|
||||
//#endregion
|
||||
//#region src/sortStatus.d.ts
|
||||
declare function renderSortIcon({
|
||||
sortDirection
|
||||
}: RenderSortIconProps): react_jsx_runtime7.JSX.Element | null;
|
||||
declare function renderSortPriority({
|
||||
priority
|
||||
}: RenderSortPriorityProps): number | undefined;
|
||||
//#endregion
|
||||
//#region src/hooks/useRowSelection.d.ts
|
||||
declare function useRowSelection(): {
|
||||
isRowSelectionDisabled: boolean;
|
||||
isRowSelected: boolean;
|
||||
onRowSelectionChange: (selectRowEvent: SelectRowEvent<any>) => void;
|
||||
};
|
||||
declare function useHeaderRowSelection(): {
|
||||
isIndeterminate: boolean;
|
||||
isRowSelected: boolean;
|
||||
onRowSelectionChange: (selectRowEvent: SelectHeaderRowEvent) => void;
|
||||
};
|
||||
//#endregion
|
||||
export { CalculatedColumn, CalculatedColumnOrColumnGroup, CalculatedColumnParent, CellComponent as Cell, CellCopyArgs, CellKeyDownArgs, CellKeyboardEvent, CellMouseArgs, CellMouseEvent, CellPasteArgs, CellRendererProps, CellSelectArgs, ColSpanArgs, Column, ColumnGroup, ColumnOrColumnGroup, ColumnWidth, ColumnWidths, DataGrid, DataGridDefaultRenderersContext, DataGridHandle, DataGridProps, DefaultColumnOptions, FillEvent, RenderCellProps, RenderCheckboxProps, RenderEditCellProps, RenderGroupCellProps, RenderHeaderCellProps, RenderRowProps, RenderSortIconProps, RenderSortPriorityProps, RenderSortStatusProps, RenderSummaryCellProps, Renderers, RowComponent as Row, RowHeightArgs, RowsChangeData, SELECT_COLUMN_KEY, SelectCellFormatter, SelectCellOptions, SelectColumn, SelectHeaderRowEvent, SelectRowEvent, SortColumn, SortDirection, ToggleGroup, TreeDataGrid, TreeDataGridProps, renderCheckbox, renderHeaderCell, renderSortIcon, renderSortPriority, renderToggleGroup, renderValue, textEditor, useHeaderRowSelection, useRowSelection };
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"name": "react-data-grid",
|
||||
"version": "7.0.0-beta.55",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./lib/styles.css": "./lib/styles.css",
|
||||
".": {
|
||||
"types": "./lib/index.d.ts",
|
||||
"default": "./lib/index.js"
|
||||
}
|
||||
},
|
||||
"browser": "./lib/index.js",
|
||||
"main": "./lib/index.js",
|
||||
"module": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"sideEffects": [
|
||||
"**/*.css"
|
||||
],
|
||||
"dependencies": {
|
||||
"clsx": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0",
|
||||
"react-dom": "^19.0"
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"name": "@dbeaver/react-data-grid",
|
||||
"type": "module",
|
||||
"sideEffects": [],
|
||||
"exports": {
|
||||
".": "./lib/index.js",
|
||||
"./*.css": "./src/*.css",
|
||||
"./*": "./lib/*.js"
|
||||
},
|
||||
"scripts": {
|
||||
"clear": "rimraf lib"
|
||||
},
|
||||
"files": [
|
||||
"package.json",
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"CHANGELOG.md",
|
||||
"lib",
|
||||
"!lib/**/*.d.ts.map",
|
||||
"!lib/**/*.test.js",
|
||||
"!lib/**/*.test.d.ts",
|
||||
"!lib/**/*.test.d.ts.map",
|
||||
"!lib/**/*.test.js.map",
|
||||
"!lib/tests",
|
||||
"!.tsbuildinfo"
|
||||
],
|
||||
"packageManager": "yarn@4.9.2",
|
||||
"devDependencies": {
|
||||
"@dbeaver/tsconfig": "workspace:^",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"rimraf": "^6",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dbeaver/react-dnd": "workspace:^",
|
||||
"@dbeaver/ui-kit": "workspace:^",
|
||||
"react": "^19",
|
||||
"react-data-grid": "file:./artifacts",
|
||||
"react-dom": "^19"
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
import { memo, use, useMemo } from 'react';
|
||||
import { Cell, type CellRendererProps } from 'react-data-grid';
|
||||
import { DataGridCellContext, type IDataGridCellRenderer } from './DataGridCellContext.js';
|
||||
import { createCellMouseEvent } from './eventUtils.js';
|
||||
import { DataGridCellInnerContext, type IDataGridCellInnerContext } from './DataGridCellInnerContext.js';
|
||||
import { useGridReactiveValue } from './useGridReactiveValue.js';
|
||||
import { HeaderDnDContext } from './useHeaderDnD.js';
|
||||
|
||||
export const BaseCell = memo(function BaseCell<TRow, TSummaryRow>(props: CellRendererProps<TRow, TSummaryRow>) {
|
||||
const cellContext = use(DataGridCellContext);
|
||||
const dndContext = use(HeaderDnDContext)!;
|
||||
const virtualColIdx = props.column.idx;
|
||||
const dataColIdx = dndContext.getDataColIdx(props.column.idx);
|
||||
const rowIdx = props.rowIdx;
|
||||
const tooltip = useGridReactiveValue(cellContext?.cellTooltip, rowIdx, dataColIdx);
|
||||
|
||||
function handleClick(event: React.MouseEvent<HTMLDivElement>) {
|
||||
props.onCellClick?.(
|
||||
{
|
||||
rowIdx,
|
||||
row: props.row,
|
||||
column: props.column,
|
||||
selectCell(enableEditor) {
|
||||
props.selectCell(
|
||||
{ rowIdx, idx: virtualColIdx },
|
||||
{
|
||||
enableEditor,
|
||||
},
|
||||
);
|
||||
},
|
||||
},
|
||||
createCellMouseEvent(event),
|
||||
);
|
||||
}
|
||||
|
||||
function handleDoubleClick(event: React.MouseEvent<HTMLDivElement>) {
|
||||
props.onCellDoubleClick?.(
|
||||
{
|
||||
rowIdx,
|
||||
row: props.row,
|
||||
column: props.column,
|
||||
selectCell(enableEditor) {
|
||||
props.selectCell({ rowIdx, idx: virtualColIdx }, { enableEditor });
|
||||
},
|
||||
},
|
||||
createCellMouseEvent(event),
|
||||
);
|
||||
}
|
||||
|
||||
function handleContextMenu(event: React.MouseEvent<HTMLDivElement>) {
|
||||
props.onCellContextMenu?.(
|
||||
{
|
||||
rowIdx,
|
||||
row: props.row,
|
||||
column: props.column,
|
||||
selectCell(enableEditor) {
|
||||
props.selectCell({ rowIdx, idx: virtualColIdx }, { enableEditor });
|
||||
},
|
||||
},
|
||||
createCellMouseEvent(event),
|
||||
);
|
||||
}
|
||||
|
||||
const mappedProps = useMemo(
|
||||
() => ({
|
||||
...props,
|
||||
onCellClick: undefined,
|
||||
onCellContextMenu: undefined,
|
||||
onCellDoubleClick: undefined,
|
||||
isFocused: props.isCellSelected,
|
||||
onClick: handleClick,
|
||||
onDoubleClick: handleDoubleClick,
|
||||
onContextMenu: handleContextMenu,
|
||||
}),
|
||||
Object.values(props),
|
||||
);
|
||||
|
||||
const renderDefaultCell = useMemo<IDataGridCellRenderer>(
|
||||
() =>
|
||||
({ onClick, onDoubleClick, onContextMenu, ...rest }) => {
|
||||
return (
|
||||
<Cell
|
||||
{...props}
|
||||
title={tooltip}
|
||||
onCellClick={(_, event) => onClick?.(event)}
|
||||
onCellDoubleClick={(_, event) => onDoubleClick?.(event)}
|
||||
onCellContextMenu={(_, event) => onContextMenu?.(event)}
|
||||
{...rest}
|
||||
isCellSelected={props.isCellSelected || rest.isFocused || false}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[...Object.values(props), tooltip],
|
||||
);
|
||||
|
||||
const cellElement = useGridReactiveValue(cellContext?.cellElement, rowIdx, dataColIdx, mappedProps, renderDefaultCell);
|
||||
|
||||
const innerCellContext = useMemo<IDataGridCellInnerContext>(() => ({ isFocused: props.isCellSelected }), [props.isCellSelected]);
|
||||
return <DataGridCellInnerContext value={innerCellContext}>{cellElement ?? <Cell title={tooltip} {...props} />}</DataGridCellInnerContext>;
|
||||
}) as <TRow, TSummaryRow>(props: CellRendererProps<TRow, TSummaryRow>) => React.ReactNode;
|
||||
@@ -1,16 +0,0 @@
|
||||
import { Row, type RenderRowProps } from 'react-data-grid';
|
||||
import { DataGridRowContext } from './DataGridRowContext.js';
|
||||
import { memo, use, useEffect } from 'react';
|
||||
import { useGridReactiveValue } from './useGridReactiveValue.js';
|
||||
|
||||
export const BaseRow = memo(function BaseRow<TRow, TSummaryRow>(props: RenderRowProps<TRow, TSummaryRow>) {
|
||||
const rowContext = use(DataGridRowContext);
|
||||
const rowsCount = useGridReactiveValue(rowContext?.onScrollToBottom ? rowContext?.rowCount : undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if (rowsCount === props.rowIdx + 1) {
|
||||
rowContext?.onScrollToBottom?.();
|
||||
}
|
||||
}, [rowsCount]);
|
||||
return <Row {...props} />;
|
||||
}) as <TRow, TSummaryRow>(props: RenderRowProps<TRow, TSummaryRow>) => React.ReactNode;
|
||||
@@ -1,33 +0,0 @@
|
||||
@import url('@dbeaver/ui-kit/index.css');
|
||||
@import url('react-data-grid/lib/styles.css') layer(components);
|
||||
|
||||
@layer components.rdg.MeasuringCell {
|
||||
[data-measuring-cell] {
|
||||
max-width: var(--rdg-max-auto-size, 350px);
|
||||
}
|
||||
}
|
||||
|
||||
@layer components.rdg.Cell {
|
||||
.rdg {
|
||||
--dbv-kit-icon-btn-outline-width: 1px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.rdg-cell-frozen {
|
||||
&:nth-last-child(1 of &) {
|
||||
box-shadow: none;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
background-color: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
import { forwardRef, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
||||
import { DataGrid as DataGridBase, type ColumnOrColumnGroup, type CellSelectArgs, type DataGridHandle, type ColumnWidth, type ColumnWidths } from 'react-data-grid';
|
||||
import { rowRenderer } from './renderers/rowRenderer.js';
|
||||
import { cellRenderer } from './renderers/cellRenderer.js';
|
||||
import { DataGridCellHeaderContext, type IDataGridHeaderCellContext } from './DataGridHeaderCellContext.js';
|
||||
import { DataGridCellContext, type IDataGridCellContext } from './DataGridCellContext.js';
|
||||
import type { IInnerRow } from './IInnerRow.js';
|
||||
import type { IGridReactiveValue } from './IGridReactiveValue.js';
|
||||
import { useGridReactiveValue } from './useGridReactiveValue.js';
|
||||
import { mapCellContentRenderer } from './mapCellContentRenderer.js';
|
||||
import { mapRenderHeaderCell } from './mapRenderHeaderCell.js';
|
||||
import { mapEditCellRenderer } from './mapEditCellRenderer.js';
|
||||
import { DataGridRowContext, type IDataGridRowContext } from './DataGridRowContext.js';
|
||||
import './DataGrid.css';
|
||||
import { HeaderDnDContext, useHeaderDnD } from './useHeaderDnD.js';
|
||||
|
||||
export interface ICellPosition {
|
||||
rowIdx: number;
|
||||
colIdx: number;
|
||||
}
|
||||
|
||||
export interface DataGridCellKeyboardEvent extends React.KeyboardEvent<HTMLDivElement> {
|
||||
preventGridDefault: () => void;
|
||||
isGridDefaultPrevented: () => boolean;
|
||||
}
|
||||
|
||||
export interface DataGridProps extends IDataGridCellContext, IDataGridRowContext, IDataGridHeaderCellContext, React.PropsWithChildren {
|
||||
getRowHeight?: (rowIdx: number) => number;
|
||||
getRowId?: (rowIdx: number) => React.Key;
|
||||
columnCount: IGridReactiveValue<number, []>;
|
||||
getColumnKey?: (colIdx: number) => string;
|
||||
onScroll?: (event: React.UIEvent<HTMLDivElement>) => void;
|
||||
onFocus?: (position: ICellPosition) => void;
|
||||
onEditorOpen?: (position: ICellPosition) => void;
|
||||
onCellKeyDown?: (position: ICellPosition, event: DataGridCellKeyboardEvent) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface DataGridRef {
|
||||
selectCell: (position: ICellPosition) => void;
|
||||
scrollToCell: (position: Partial<ICellPosition>) => void;
|
||||
openEditor: (position: ICellPosition) => void;
|
||||
}
|
||||
|
||||
export const DataGrid = forwardRef<DataGridRef, DataGridProps>(function DataGrid(
|
||||
{
|
||||
headerElement,
|
||||
getHeaderWidth,
|
||||
headerText,
|
||||
getHeaderOrder,
|
||||
getHeaderResizable,
|
||||
columnSortable,
|
||||
getHeaderHeight,
|
||||
getHeaderPinned,
|
||||
columnSortingState,
|
||||
getHeaderDnD,
|
||||
cell,
|
||||
cellText,
|
||||
cellElement,
|
||||
cellTooltip,
|
||||
getCellEditable,
|
||||
columnCount,
|
||||
getColumnKey,
|
||||
rowCount,
|
||||
getRowId,
|
||||
getRowHeight,
|
||||
onHeaderReorder,
|
||||
onScroll,
|
||||
onScrollToBottom,
|
||||
onFocus,
|
||||
onCellChange,
|
||||
onColumnSort,
|
||||
onHeaderKeyDown,
|
||||
children,
|
||||
className,
|
||||
onCellKeyDown,
|
||||
},
|
||||
ref,
|
||||
) {
|
||||
const [columnWidths, setColumnWidths] = useState<ColumnWidths>(() => new Map<string, ColumnWidth>());
|
||||
const rowsCount = useGridReactiveValue(rowCount);
|
||||
const columnsCount = useGridReactiveValue(columnCount);
|
||||
|
||||
const rowsCountRef = useRef(rowsCount);
|
||||
const innerGridRef = useRef<DataGridHandle>(null);
|
||||
const columns = new Array<ColumnOrColumnGroup<IInnerRow, unknown>>(columnsCount)
|
||||
.fill(null as any)
|
||||
.map((_, i): ColumnOrColumnGroup<IInnerRow, unknown> => {
|
||||
const width = getHeaderWidth?.(i) ?? 'max-content';
|
||||
return {
|
||||
key: getColumnKey?.(i) ?? String(i),
|
||||
name: '',
|
||||
resizable: getHeaderResizable?.(i) ?? true,
|
||||
width,
|
||||
minWidth: 26,
|
||||
editable: row => getCellEditable?.(row.idx, i) ?? false,
|
||||
frozen: getHeaderPinned?.(i),
|
||||
renderHeaderCell: mapRenderHeaderCell(i),
|
||||
renderCell: mapCellContentRenderer(i),
|
||||
renderEditCell: mapEditCellRenderer(i),
|
||||
};
|
||||
});
|
||||
|
||||
const dndHeaderContext = useHeaderDnD({ columns, onReorder: onHeaderReorder, getCanDrag: getHeaderDnD, getHeaderOrder });
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
selectCell: (position: ICellPosition) => {
|
||||
innerGridRef.current?.selectCell({ idx: dndHeaderContext.getDataColIdx(position.colIdx), rowIdx: position.rowIdx });
|
||||
},
|
||||
scrollToCell: (position: Partial<ICellPosition>) => {
|
||||
innerGridRef.current?.scrollToCell({ idx: position.colIdx && dndHeaderContext.getDataColIdx(position.colIdx), rowIdx: position.rowIdx });
|
||||
},
|
||||
openEditor: (position: ICellPosition) => {
|
||||
innerGridRef.current?.selectCell({ idx: dndHeaderContext.getDataColIdx(position.colIdx), rowIdx: position.rowIdx }, {
|
||||
enableEditor: true,
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
if (rowsCountRef.current !== rowsCount) {
|
||||
const previousRowCount = rowsCountRef.current;
|
||||
rowsCountRef.current = rowsCount;
|
||||
|
||||
if (previousRowCount === 0) {
|
||||
setColumnWidths(new Map<string, ColumnWidth>());
|
||||
}
|
||||
}
|
||||
|
||||
let rows = useMemo(
|
||||
() =>
|
||||
new Array<IInnerRow>(rowsCount).fill({ idx: 0 }).map((_, i) => ({
|
||||
idx: i,
|
||||
})),
|
||||
[rowsCount],
|
||||
);
|
||||
|
||||
function handleCellFocus(args: CellSelectArgs<IInnerRow, unknown>) {
|
||||
onFocus?.({ colIdx: dndHeaderContext.getDataColIdx(args.column.idx), rowIdx: args.rowIdx });
|
||||
}
|
||||
|
||||
function handleCellKeyDown(args: CellSelectArgs<IInnerRow, unknown>, event: DataGridCellKeyboardEvent) {
|
||||
onCellKeyDown?.({ colIdx: dndHeaderContext.getDataColIdx(args.column.idx), rowIdx: args.rowIdx }, event);
|
||||
}
|
||||
|
||||
const isMeasurementRender = columnWidths.size === 0 && columnCount.get() > 0;
|
||||
|
||||
if (isMeasurementRender) {
|
||||
rows = rows.slice(0, 100);
|
||||
}
|
||||
|
||||
return (
|
||||
<HeaderDnDContext value={dndHeaderContext}>
|
||||
<DataGridRowContext value={{ rowCount, onScrollToBottom }}>
|
||||
<DataGridCellContext value={{ cell, cellText, cellElement, cellTooltip, onCellChange }}>
|
||||
<DataGridCellHeaderContext value={{ headerElement, headerText, getHeaderDnD, columnSortable, onColumnSort, columnSortingState, onHeaderKeyDown }}>
|
||||
<DataGridBase
|
||||
ref={innerGridRef}
|
||||
columns={dndHeaderContext.columns}
|
||||
enableVirtualization={!isMeasurementRender}
|
||||
rows={rows}
|
||||
className={className}
|
||||
headerRowHeight={getHeaderHeight?.()}
|
||||
rowHeight={getRowHeight ? row => getRowHeight(row.idx) : undefined}
|
||||
rowKeyGetter={getRowId ? row => getRowId(row.idx) : undefined}
|
||||
columnWidths={columnWidths}
|
||||
renderers={{
|
||||
renderRow: rowRenderer,
|
||||
renderCell: cellRenderer,
|
||||
noRowsFallback: children,
|
||||
}}
|
||||
onScroll={onScroll}
|
||||
onSelectedCellChange={handleCellFocus}
|
||||
onCellKeyDown={handleCellKeyDown}
|
||||
onColumnWidthsChange={setColumnWidths}
|
||||
/>
|
||||
</DataGridCellHeaderContext>
|
||||
</DataGridCellContext>
|
||||
</DataGridRowContext>
|
||||
</HeaderDnDContext>
|
||||
);
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
import { createContext } from 'react';
|
||||
import type { IGridReactiveValue } from './IGridReactiveValue.js';
|
||||
|
||||
export interface IDataGridCellProps extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
|
||||
isFocused: boolean;
|
||||
}
|
||||
|
||||
export interface IDataGridCellRenderer {
|
||||
(propsOverride: Partial<IDataGridCellProps> & { [key: string]: any }): React.ReactNode;
|
||||
}
|
||||
|
||||
export interface IDataGridCellContext {
|
||||
cellElement?: IGridReactiveValue<
|
||||
React.ReactNode,
|
||||
[rowIdx: number, colIdx: number, props: IDataGridCellProps, renderDefaultCell: IDataGridCellRenderer]
|
||||
>;
|
||||
cell?: IGridReactiveValue<React.ReactNode, [rowIdx: number, colIdx: number]>;
|
||||
cellText?: IGridReactiveValue<string, [rowIdx: number, colIdx: number]>;
|
||||
cellTooltip?: IGridReactiveValue<string, [rowIdx: number, colIdx: number]>;
|
||||
getCellEditable?: (rowIdx: number, colIdx: number) => boolean;
|
||||
onCellChange?: (rowIdx: number, colIdx: number, value: any) => void;
|
||||
}
|
||||
|
||||
export const DataGridCellContext = createContext<IDataGridCellContext | null>(null);
|
||||
@@ -1,7 +0,0 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export interface IDataGridCellInnerContext {
|
||||
isFocused: boolean;
|
||||
}
|
||||
|
||||
export const DataGridCellInnerContext = createContext<IDataGridCellInnerContext | null>(null);
|
||||
@@ -1,20 +0,0 @@
|
||||
import { createContext } from 'react';
|
||||
import type { IGridReactiveValue } from './IGridReactiveValue.js';
|
||||
|
||||
export interface IDataGridHeaderCellContext {
|
||||
headerElement?: IGridReactiveValue<React.ReactNode, [colIdx: number]>;
|
||||
headerText?: IGridReactiveValue<string, [colIdx: number]>;
|
||||
getHeaderOrder?: () => number[];
|
||||
getHeaderWidth?: (colIdx: number) => number | string | null;
|
||||
getHeaderResizable?: (colIdx: number) => boolean;
|
||||
getHeaderHeight?: () => number;
|
||||
getHeaderPinned?: (colIdx: number) => boolean;
|
||||
getHeaderDnD?: (colIdx: number) => boolean;
|
||||
onHeaderReorder?: (from: number, to: number) => void;
|
||||
columnSortingState?: IGridReactiveValue<'asc' | 'desc' | undefined | null, [colIdx: number]>;
|
||||
columnSortable?: IGridReactiveValue<boolean, [colIdx: number]>;
|
||||
onColumnSort?: (colIdx: number, order: 'asc' | 'desc' | null, isMultiple: boolean) => void;
|
||||
onHeaderKeyDown?: (event: React.KeyboardEvent) => void;
|
||||
}
|
||||
|
||||
export const DataGridCellHeaderContext = createContext<IDataGridHeaderCellContext | null>(null);
|
||||
@@ -1,9 +0,0 @@
|
||||
import { createContext } from 'react';
|
||||
import type { IGridReactiveValue } from './IGridReactiveValue.js';
|
||||
|
||||
export interface IDataGridRowContext {
|
||||
rowCount: IGridReactiveValue<number, []>;
|
||||
onScrollToBottom?: () => void;
|
||||
}
|
||||
|
||||
export const DataGridRowContext = createContext<IDataGridRowContext | null>(null);
|
||||
@@ -1,6 +0,0 @@
|
||||
export type IGridReactiveValueSubscribe<TArgs extends any[]> = (onValueChange: () => void, ...args: TArgs) => () => void;
|
||||
|
||||
export interface IGridReactiveValue<T, TArgs extends any[]> {
|
||||
get(...args: TArgs): T;
|
||||
subscribe: IGridReactiveValueSubscribe<TArgs>;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export interface IInnerRow {
|
||||
idx: number;
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
:global(.rdg-cell) .editor {
|
||||
appearance: none;
|
||||
|
||||
box-sizing: border-box;
|
||||
min-height: initial;
|
||||
inline-size: 100%;
|
||||
block-size: 100%;
|
||||
padding-block: 0;
|
||||
padding-inline: 6px;
|
||||
border: 2px solid #ccc;
|
||||
vertical-align: top;
|
||||
color: var(--rdg-color);
|
||||
background-color: var(--rdg-background-color);
|
||||
|
||||
font: inherit;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
font-weight: inherit;
|
||||
letter-spacing: inherit;
|
||||
text-decoration: inherit;
|
||||
text-transform: inherit;
|
||||
|
||||
&:focus {
|
||||
border-color: var(--rdg-selection-color);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: #999;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { use } from 'react';
|
||||
import classes from './TextEditor.module.css';
|
||||
import { DataGridCellContext } from '../DataGridCellContext.js';
|
||||
import { useGridReactiveValue } from '../useGridReactiveValue.js';
|
||||
|
||||
function autoFocusAndSelect(input: HTMLInputElement | null) {
|
||||
input?.focus();
|
||||
input?.select();
|
||||
}
|
||||
|
||||
export interface IProps {
|
||||
rowIdx: number;
|
||||
colIdx: number;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function TextEditor({ rowIdx, colIdx, onClose }: IProps) {
|
||||
const cellContext = use(DataGridCellContext);
|
||||
|
||||
const value = useGridReactiveValue(cellContext?.cellText, rowIdx, colIdx) ?? '';
|
||||
|
||||
return (
|
||||
<input
|
||||
className={classes['editor']}
|
||||
ref={autoFocusAndSelect}
|
||||
value={value}
|
||||
onChange={event => cellContext?.onCellChange?.(rowIdx, colIdx, event.target.value)}
|
||||
onBlur={() => onClose()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import type { CellMouseEvent } from 'react-data-grid';
|
||||
|
||||
export function createCellMouseEvent<E extends React.MouseEvent<HTMLDivElement>>(event: E): CellMouseEvent {
|
||||
let defaultPrevented = false;
|
||||
const cellEvent = {
|
||||
...event,
|
||||
preventGridDefault() {
|
||||
defaultPrevented = true;
|
||||
},
|
||||
isGridDefaultPrevented() {
|
||||
return defaultPrevented;
|
||||
},
|
||||
};
|
||||
|
||||
Object.setPrototypeOf(cellEvent, Object.getPrototypeOf(event));
|
||||
|
||||
return cellEvent;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { clsx } from '@dbeaver/ui-kit';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||
}
|
||||
|
||||
export function BlobFormatter({ className, onClick }: Props) {
|
||||
return (
|
||||
<span className={clsx('tw:uppercase', className)} onClick={onClick}>
|
||||
[blob]
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { NullFormatter } from './NullFormatter.js';
|
||||
import { Checkbox, clsx, Focusable } from '@dbeaver/ui-kit';
|
||||
|
||||
interface Props {
|
||||
value: boolean | null;
|
||||
className?: string;
|
||||
focusable?: boolean;
|
||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||
onKeyDown?: React.KeyboardEventHandler<HTMLElement>;
|
||||
}
|
||||
|
||||
export function BooleanFormatter({ value, className, onClick, onKeyDown, focusable }: Props) {
|
||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
|
||||
if (event.code === 'Enter' || event.code === 'Space') {
|
||||
event.preventDefault();
|
||||
onKeyDown?.(event);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Focusable
|
||||
focusable={focusable}
|
||||
className={clsx('tw:flex tw:items-center tw:outline-none tw:hover:cursor-pointer', className)}
|
||||
onKeyDown={handleKeyDown}
|
||||
onClick={onClick}
|
||||
>
|
||||
{value === null ? <NullFormatter /> : <Checkbox className="tw:data-disabled:opacity-100!" disabled size="small" checked={value} />}
|
||||
</Focusable>
|
||||
);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { NullFormatter } from './NullFormatter.js';
|
||||
|
||||
interface Props {
|
||||
value: Date | null;
|
||||
type?: 'time' | 'date' | 'datetime';
|
||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||
}
|
||||
|
||||
export function DateFormatter({ value, type = 'datetime', onClick }: Props) {
|
||||
if (value === null) {
|
||||
return <NullFormatter />;
|
||||
}
|
||||
return (
|
||||
<div onClick={onClick}>
|
||||
{value.toLocaleString(undefined, {
|
||||
dateStyle: type === 'date' ? 'long' : undefined,
|
||||
timeStyle: type === 'time' ? 'long' : undefined,
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { clsx } from '@dbeaver/ui-kit';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||
}
|
||||
|
||||
export function NullFormatter({ className, onClick }: Props) {
|
||||
return (
|
||||
<span className={clsx('tw:uppercase', 'tw:opacity-65', className)} onClick={onClick}>
|
||||
[NULL]
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { NullFormatter } from './NullFormatter.js';
|
||||
|
||||
interface Props {
|
||||
value: number | null;
|
||||
className?: string;
|
||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||
}
|
||||
|
||||
export function NumberFormatter({ value, className, onClick }: Props) {
|
||||
if (value === null) {
|
||||
return <NullFormatter className={className} onClick={onClick} />;
|
||||
}
|
||||
return (
|
||||
<div style={{ textAlign: 'right' }} className={className} onClick={onClick}>
|
||||
{value.toLocaleString()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
export * from './DataGrid.js';
|
||||
export * from './DataGridCellContext.js';
|
||||
export * from './DataGridCellInnerContext.js';
|
||||
export * from './IGridReactiveValue.js';
|
||||
export * from './useCreateGridReactiveValue.js';
|
||||
export * from './useGridReactiveValue.js';
|
||||
export * from './reorderArray.js';
|
||||
|
||||
export * from './formatters/NullFormatter.js';
|
||||
export * from './formatters/BooleanFormatter.js';
|
||||
export * from './formatters/NumberFormatter.js';
|
||||
export * from './formatters/DateFormatter.js';
|
||||
export * from './formatters/BlobFormatter.js';
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { RenderCellProps } from 'react-data-grid';
|
||||
import type { IInnerRow } from './IInnerRow.js';
|
||||
import { CellContentRenderer } from './renderers/CellContentRenderer.js';
|
||||
|
||||
export function mapCellContentRenderer(colIdx: number) {
|
||||
return function RenderCell({ rowIdx }: RenderCellProps<IInnerRow, unknown>) {
|
||||
return <CellContentRenderer rowIdx={rowIdx} colIdx={colIdx} />;
|
||||
};
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { RenderEditCellProps } from 'react-data-grid';
|
||||
import type { IInnerRow } from './IInnerRow.js';
|
||||
import { TextEditor } from './editors/TextEditor.js';
|
||||
|
||||
export function mapEditCellRenderer(colIdx: number) {
|
||||
return function RenderEditCell({ rowIdx, onClose }: RenderEditCellProps<IInnerRow, unknown>) {
|
||||
return <TextEditor rowIdx={rowIdx} colIdx={colIdx} onClose={() => onClose()} />;
|
||||
};
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { RenderHeaderCellProps } from 'react-data-grid';
|
||||
import type { IInnerRow } from './IInnerRow.js';
|
||||
import { HeaderCellContentRenderer } from './renderers/HeaderCellContentRenderer.js';
|
||||
|
||||
export function mapRenderHeaderCell(colIdx: number) {
|
||||
return function RenderHeaderCell({ tabIndex }: RenderHeaderCellProps<IInnerRow, unknown>) {
|
||||
return <HeaderCellContentRenderer colIdx={colIdx} tabIndex={tabIndex} />;
|
||||
};
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { use } from 'react';
|
||||
import { DataGridCellContext } from '../DataGridCellContext.js';
|
||||
import { useGridReactiveValue } from '../useGridReactiveValue.js';
|
||||
|
||||
export interface Props {
|
||||
rowIdx: number;
|
||||
colIdx: number;
|
||||
}
|
||||
|
||||
export function CellContentRenderer({ rowIdx, colIdx }: Props) {
|
||||
const cellContext = use(DataGridCellContext);
|
||||
return useGridReactiveValue(cellContext?.cell, rowIdx, colIdx);
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
import { memo, use } from 'react';
|
||||
import { useDrag, useDrop, type DnDStoreProvider } from '@dbeaver/react-dnd';
|
||||
import { DataGridCellHeaderContext } from '../DataGridHeaderCellContext.js';
|
||||
import { useGridReactiveValue } from '../useGridReactiveValue.js';
|
||||
import { HeaderDnDContext } from '../useHeaderDnD.js';
|
||||
import { OrderButton } from './OrderButton.js';
|
||||
|
||||
interface Props {
|
||||
colIdx: number;
|
||||
tabIndex?: number;
|
||||
}
|
||||
|
||||
export const HeaderCellContentRenderer = memo(function HeaderCellContentRenderer({ colIdx, tabIndex }: Props) {
|
||||
const dndHeaderContext = use(HeaderDnDContext);
|
||||
const cellHeaderContext = use(DataGridCellHeaderContext);
|
||||
const headerElement = useGridReactiveValue(cellHeaderContext?.headerElement, colIdx);
|
||||
const getHeaderText = useGridReactiveValue(headerElement ? undefined : cellHeaderContext?.headerText, colIdx);
|
||||
const isColumnSortable = useGridReactiveValue(cellHeaderContext?.columnSortable, colIdx);
|
||||
const onColumnSort = cellHeaderContext?.onColumnSort;
|
||||
const onHeaderKeyDown = cellHeaderContext?.onHeaderKeyDown;
|
||||
const sortingState = useGridReactiveValue(cellHeaderContext?.columnSortingState, colIdx);
|
||||
|
||||
const draggable = dndHeaderContext?.getCanDrag?.(colIdx) ?? false;
|
||||
const drag = useDrag({
|
||||
draggable,
|
||||
onDragStart(event, store) {
|
||||
if (dndHeaderContext?.id) {
|
||||
store.setData('dbeaver-react-data-grid-id', dndHeaderContext?.id);
|
||||
}
|
||||
store.setData('dbeaver-react-data-grid-col-idx', colIdx);
|
||||
},
|
||||
onDragEnd() {
|
||||
dndHeaderContext?.onDragEnd(colIdx);
|
||||
},
|
||||
});
|
||||
|
||||
function canDrop(event: React.DragEvent<HTMLElement>, store: DnDStoreProvider | null) {
|
||||
const gridId = store?.getData('dbeaver-react-data-grid-id');
|
||||
return gridId === dndHeaderContext?.id;
|
||||
}
|
||||
const drop = useDrop({
|
||||
canDrop,
|
||||
onDragOver(event, store) {
|
||||
if (!canDrop(event, store)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dragColIdx = store?.getData('dbeaver-react-data-grid-col-idx');
|
||||
const position = event.clientX - event.currentTarget.getBoundingClientRect().left;
|
||||
const width = event.currentTarget.offsetWidth;
|
||||
const isAfter = position > width / 2;
|
||||
|
||||
dndHeaderContext?.onDragOver(dragColIdx, colIdx, isAfter);
|
||||
},
|
||||
});
|
||||
|
||||
function handleSort(e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) {
|
||||
if (!onColumnSort) return;
|
||||
|
||||
const nextSortState = sortingState === 'asc' ? 'desc' : sortingState === 'desc' ? null : 'asc';
|
||||
onColumnSort(colIdx, nextSortState, e.ctrlKey || e.metaKey);
|
||||
}
|
||||
|
||||
function onKeyDown(event: React.KeyboardEvent<HTMLElement>) {
|
||||
onHeaderKeyDown?.(event);
|
||||
|
||||
if ((event.key === 'Enter' || event.key === ' ') && isColumnSortable && onColumnSort) {
|
||||
event.preventDefault();
|
||||
handleSort(event);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
onKeyDown={onKeyDown}
|
||||
tabIndex={tabIndex}
|
||||
className="tw:w-full tw:h-full tw:content-center tw:flex tw:items-center tw:justify-between tw:gap-1 tw:outline-none tw:group"
|
||||
{...drag.props}
|
||||
{...drop.props}
|
||||
>
|
||||
<span className="tw:overflow-hidden tw:h-full tw:text-ellipsis">{headerElement ?? getHeaderText ?? ''}</span>
|
||||
{isColumnSortable && onColumnSort && <OrderButton sortState={sortingState} onClick={handleSort} />}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
import { clsx, IconButton, Icon } from '@dbeaver/ui-kit';
|
||||
|
||||
interface OrderButtonProps {
|
||||
sortState?: 'asc' | 'desc' | null;
|
||||
onClick: (e: React.MouseEvent<HTMLElement>) => void;
|
||||
tabIndex?: number;
|
||||
}
|
||||
|
||||
export function OrderButton({ sortState, onClick, tabIndex }: OrderButtonProps) {
|
||||
const svgSortAsc = 'sort-asc';
|
||||
const svgSortDesc = 'sort-desc';
|
||||
const svgSortUnknown = 'sort-unknown';
|
||||
|
||||
const iconSrc = sortState === 'asc' ? svgSortAsc : sortState === 'desc' ? svgSortDesc : svgSortUnknown;
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={onClick}
|
||||
tabIndex={tabIndex}
|
||||
title="Sort by column"
|
||||
aria-label="Sort by column"
|
||||
className={clsx(
|
||||
'tw:opacity-0 tw:group-focus:opacity-100 tw:focus:opacity-100 tw:group-hover:opacity-100 tw:hover:opacity-100 tw:outline-offset-0',
|
||||
sortState && 'tw:opacity-100',
|
||||
)}
|
||||
>
|
||||
<Icon name={iconSrc} />
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import type { CellRendererProps } from 'react-data-grid';
|
||||
import { BaseCell } from '../BaseCell.js';
|
||||
|
||||
export function cellRenderer<TRow, TSummaryRow>(key: React.Key, props: CellRendererProps<TRow, TSummaryRow>) {
|
||||
return <BaseCell key={key} {...props} />;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import type { RenderRowProps } from 'react-data-grid';
|
||||
import { BaseRow } from '../BaseRow.js';
|
||||
|
||||
export function rowRenderer<TRow, TSummaryRow>(key: React.Key, props: RenderRowProps<TRow, TSummaryRow>) {
|
||||
return <BaseRow key={key} {...props} />;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
export function reorderArray<T>(array: T[], from: number, to: number): T[] {
|
||||
const newArray = [...array];
|
||||
if (from < to) {
|
||||
newArray.splice(to, 0, array[from]!);
|
||||
newArray.splice(from, 1);
|
||||
} else {
|
||||
newArray.splice(from, 1);
|
||||
newArray.splice(to, 0, array[from]!);
|
||||
}
|
||||
return newArray;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
import type { IGridReactiveValue, IGridReactiveValueSubscribe } from './IGridReactiveValue.js';
|
||||
|
||||
type NoInfer<T> = intrinsic;
|
||||
export function useCreateGridReactiveValue<T, TArgs extends any[]>(
|
||||
get: (...args: TArgs) => T,
|
||||
subscribe: NoInfer<IGridReactiveValueSubscribe<TArgs>> | null,
|
||||
deps: React.DependencyList,
|
||||
): IGridReactiveValue<T, TArgs> {
|
||||
const value = useMemo<IGridReactiveValue<T, TArgs>>(
|
||||
() => ({
|
||||
get,
|
||||
subscribe: subscribe ?? (() => () => {}),
|
||||
}),
|
||||
deps,
|
||||
);
|
||||
|
||||
return value;
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import { useMemo, useSyncExternalStore } from 'react';
|
||||
import type { IGridReactiveValue } from './IGridReactiveValue.js';
|
||||
|
||||
export function useGridReactiveValue<P extends IGridReactiveValue<any, any[]> | undefined>(
|
||||
value: P,
|
||||
...args: P extends IGridReactiveValue<any, infer T> ? T : never[]
|
||||
): P extends IGridReactiveValue<infer T, any[]> ? T : undefined {
|
||||
const mappedValue = useMemo(
|
||||
() => ({
|
||||
cache: null as any,
|
||||
cacheInvalidated: true,
|
||||
subscribe: (onValueChange: () => void) => {
|
||||
if (value) {
|
||||
return value.subscribe(
|
||||
() => {
|
||||
mappedValue.cacheInvalidated = true;
|
||||
return onValueChange();
|
||||
},
|
||||
...args,
|
||||
);
|
||||
}
|
||||
|
||||
return () => {};
|
||||
},
|
||||
get: () => {
|
||||
if (mappedValue.cacheInvalidated) {
|
||||
mappedValue.cache = value?.get(...args);
|
||||
mappedValue.cacheInvalidated = false;
|
||||
}
|
||||
return mappedValue.cache;
|
||||
},
|
||||
}),
|
||||
[value, ...args],
|
||||
);
|
||||
return useSyncExternalStore(mappedValue.subscribe, mappedValue.get);
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
import type { ColumnOrColumnGroup } from 'react-data-grid';
|
||||
import type { IInnerRow } from './IInnerRow.js';
|
||||
import { createContext, useId, useMemo, useState } from 'react';
|
||||
import { reorderArray } from './reorderArray.js';
|
||||
|
||||
interface IHeaderDnDOptions {
|
||||
columns: Array<ColumnOrColumnGroup<IInnerRow, unknown>>;
|
||||
getCanDrag?: (colIdx: number) => boolean;
|
||||
onReorder?: (from: number, to: number) => void;
|
||||
getHeaderOrder?: () => number[];
|
||||
}
|
||||
|
||||
interface IHeaderDnD {
|
||||
id: string;
|
||||
columns: Array<ColumnOrColumnGroup<IInnerRow, unknown>>;
|
||||
getDataColIdx: (virtualColIdx: number) => number;
|
||||
getVirtualColIdx: (dataColIdx: number) => number;
|
||||
getCanDrag?: (colIdx: number) => boolean;
|
||||
onDragOver: (dragColIdx: number, dropColIdx: number, isAfter: boolean) => void;
|
||||
onDragEnd: (colIdx: number) => void;
|
||||
}
|
||||
|
||||
export const HeaderDnDContext = createContext<IHeaderDnD | null>(null);
|
||||
|
||||
export function useHeaderDnD({ columns, getCanDrag, onReorder, getHeaderOrder }: IHeaderDnDOptions): IHeaderDnD {
|
||||
const id = useId();
|
||||
const [activeDnDElement, setActiveDnDElement] = useState<number | null>(null);
|
||||
const [dragOverElement, setDragOverElement] = useState<[number, boolean] | null>(null);
|
||||
|
||||
function getDataColIdx(virtualColIdx: number) {
|
||||
// Given a visible (ordered) column index, return the original data column index
|
||||
const order = getHeaderOrder?.();
|
||||
if (order) {
|
||||
return order[virtualColIdx] ?? virtualColIdx;
|
||||
}
|
||||
return virtualColIdx;
|
||||
}
|
||||
|
||||
function getVirtualColIdx(dataColIdx: number) {
|
||||
// Given the original data column index, return its visible (ordered) index
|
||||
const order = getHeaderOrder?.();
|
||||
if (order) {
|
||||
return order.indexOf(dataColIdx);
|
||||
}
|
||||
return dataColIdx;
|
||||
}
|
||||
|
||||
function onDragEnd(dragColIdx: number) {
|
||||
setActiveDnDElement(null);
|
||||
setDragOverElement(null);
|
||||
|
||||
if (dragOverElement) {
|
||||
const isAfter = dragOverElement?.[1];
|
||||
let to = dragOverElement?.[0];
|
||||
|
||||
if (isAfter) {
|
||||
to = getDataColIdx(getVirtualColIdx(to) + 1);
|
||||
}
|
||||
|
||||
onReorder?.(dragColIdx, to);
|
||||
}
|
||||
}
|
||||
|
||||
function onDragOver(dragColIdx: number, dropColIdx: number, isAfter: boolean) {
|
||||
setActiveDnDElement(dragColIdx);
|
||||
if (dragColIdx !== dropColIdx) {
|
||||
setDragOverElement([dropColIdx, isAfter]);
|
||||
}
|
||||
}
|
||||
|
||||
columns = useMemo(() => columns.map((col, idx) => columns[getDataColIdx(idx)]!), [columns, getHeaderOrder]);
|
||||
|
||||
if (activeDnDElement !== null && dragOverElement !== null) {
|
||||
const from = getVirtualColIdx(activeDnDElement);
|
||||
let to = getVirtualColIdx(dragOverElement[0]);
|
||||
const isAfter = dragOverElement[1];
|
||||
|
||||
if (isAfter) {
|
||||
to++;
|
||||
}
|
||||
|
||||
columns = reorderArray(columns, from, to);
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
columns,
|
||||
getDataColIdx,
|
||||
getVirtualColIdx,
|
||||
getCanDrag,
|
||||
onDragOver,
|
||||
onDragEnd,
|
||||
};
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"extends": "@dbeaver/tsconfig/tsconfig.webview.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./lib/.tsbuildinfo",
|
||||
"rootDir": "src",
|
||||
"rootDirs": [
|
||||
"src"
|
||||
],
|
||||
"outDir": "lib",
|
||||
"incremental": true,
|
||||
"composite": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../react-dnd"
|
||||
},
|
||||
{
|
||||
"path": "../ui-kit"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
# @dbeaver/react-dnd
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"name": "@dbeaver/react-dnd",
|
||||
"type": "module",
|
||||
"sideEffects": [],
|
||||
"exports": {
|
||||
".": "./lib/index.js",
|
||||
"./*": "./lib/*.js"
|
||||
},
|
||||
"scripts": {
|
||||
"clear": "rimraf lib"
|
||||
},
|
||||
"files": [
|
||||
"package.json",
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"CHANGELOG.md",
|
||||
"lib",
|
||||
"!lib/**/*.d.ts.map",
|
||||
"!lib/**/*.test.js",
|
||||
"!lib/**/*.test.d.ts",
|
||||
"!lib/**/*.test.d.ts.map",
|
||||
"!lib/**/*.test.js.map",
|
||||
"!lib/tests",
|
||||
"!.tsbuildinfo"
|
||||
],
|
||||
"packageManager": "yarn@4.9.2",
|
||||
"devDependencies": {
|
||||
"@dbeaver/tsconfig": "workspace:^",
|
||||
"@types/react": "^19",
|
||||
"rimraf": "^6",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^19"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from './useDrag.js';
|
||||
export * from './useDrop.js';
|
||||
export { type DnDStoreProvider } from './store.js';
|
||||
@@ -1,46 +0,0 @@
|
||||
export const DND_STORE_PREFIX = 'application/dbeaver-react-dnd-';
|
||||
|
||||
export interface DnDStoreProvider {
|
||||
getData: (key: string) => any;
|
||||
setData: (key: string, value: any) => void;
|
||||
removeData: () => void;
|
||||
}
|
||||
|
||||
class DnDStore {
|
||||
private data: Map<string, Record<string, any>> = new Map();
|
||||
setData(id: string, key: string, value: any) {
|
||||
if (!this.data.has(id)) {
|
||||
this.data.set(id, {});
|
||||
}
|
||||
this.data.get(id)![key] = value;
|
||||
}
|
||||
getData(id: string, key: string) {
|
||||
return this.data.get(id)?.[key];
|
||||
}
|
||||
removeData(id: string) {
|
||||
this.data.delete(id);
|
||||
}
|
||||
|
||||
getProvider(id: string) {
|
||||
return {
|
||||
getData: (key: string) => this.getData(id, key),
|
||||
setData: (key: string, value: any) => {
|
||||
this.setData(id, key, value);
|
||||
},
|
||||
removeData: () => {
|
||||
this.removeData(id);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function getStoreProvider(event: React.DragEvent<HTMLElement>): DnDStoreProvider | null {
|
||||
const id = event.dataTransfer.types.find(type => type.startsWith(DND_STORE_PREFIX))?.slice(DND_STORE_PREFIX.length);
|
||||
|
||||
return id ? globalDnDStore.getProvider(id) : null;
|
||||
}
|
||||
|
||||
// TODO: do we want to use react context here?
|
||||
// the main idea was to use native DataTransfer, but it's available only for drag start and drop events
|
||||
// so we need to use a custom store
|
||||
export const globalDnDStore = new DnDStore();
|
||||
@@ -1,67 +0,0 @@
|
||||
import { useId, useMemo, useRef, useState } from 'react';
|
||||
import { DND_STORE_PREFIX, globalDnDStore, type DnDStoreProvider } from './store.js';
|
||||
|
||||
interface IDragOptions {
|
||||
draggable?: boolean;
|
||||
onDrag?: (event: React.DragEvent<HTMLElement>, provider: DnDStoreProvider) => void;
|
||||
onDragStart?: (event: React.DragEvent<HTMLElement>, provider: DnDStoreProvider) => void;
|
||||
onDragEnd?: (event: React.DragEvent<HTMLElement>, provider: DnDStoreProvider) => void;
|
||||
}
|
||||
interface IDrag {
|
||||
isDragging: boolean;
|
||||
props: {
|
||||
draggable?: boolean;
|
||||
onDrag: (event: React.DragEvent<HTMLElement>) => void;
|
||||
onDragStart: (event: React.DragEvent<HTMLElement>) => void;
|
||||
onDragEnd: (event: React.DragEvent<HTMLElement>) => void;
|
||||
};
|
||||
}
|
||||
|
||||
export function useDrag({ draggable, onDragStart, onDrag, onDragEnd }: IDragOptions = {}): IDrag {
|
||||
const id = useId();
|
||||
const [dragging, setDragging] = useState(false);
|
||||
const optionsRef = useRef<IDragOptions>({ draggable, onDragStart, onDrag, onDragEnd });
|
||||
optionsRef.current = { draggable, onDragStart, onDrag, onDragEnd };
|
||||
|
||||
function handleDragStart(event: React.DragEvent<HTMLElement>) {
|
||||
if (!optionsRef.current.draggable) {
|
||||
return;
|
||||
}
|
||||
event.stopPropagation();
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
event.dataTransfer.setData(`${DND_STORE_PREFIX}${id}`, id);
|
||||
setDragging(true);
|
||||
optionsRef.current.onDragStart?.(event, globalDnDStore.getProvider(id));
|
||||
}
|
||||
|
||||
function handleDrag(event: React.DragEvent<HTMLElement>) {
|
||||
if (!optionsRef.current.draggable) {
|
||||
return;
|
||||
}
|
||||
optionsRef.current.onDrag?.(event, globalDnDStore.getProvider(id));
|
||||
}
|
||||
|
||||
function handleDragEnd(event: React.DragEvent<HTMLElement>) {
|
||||
if (!optionsRef.current.draggable) {
|
||||
return;
|
||||
}
|
||||
setDragging(false);
|
||||
optionsRef.current.onDragEnd?.(event, globalDnDStore.getProvider(id));
|
||||
globalDnDStore.removeData(id);
|
||||
}
|
||||
|
||||
return {
|
||||
isDragging: dragging,
|
||||
props: {
|
||||
draggable,
|
||||
...useMemo(
|
||||
() => ({
|
||||
onDrag: handleDrag,
|
||||
onDragStart: handleDragStart,
|
||||
onDragEnd: handleDragEnd,
|
||||
}),
|
||||
[],
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { getStoreProvider, type DnDStoreProvider } from './store.js';
|
||||
|
||||
interface IDropOptions {
|
||||
canDrop?: (event: React.DragEvent<HTMLElement>, provider: DnDStoreProvider | null) => boolean;
|
||||
onDrop?: (event: React.DragEvent<HTMLElement>, provider: DnDStoreProvider | null) => void;
|
||||
onDragOver?: (event: React.DragEvent<HTMLElement>, provider: DnDStoreProvider | null) => void;
|
||||
onDragEnter?: (event: React.DragEvent<HTMLElement>, provider: DnDStoreProvider | null) => void;
|
||||
onDragLeave?: (event: React.DragEvent<HTMLElement>, provider: DnDStoreProvider | null) => void;
|
||||
}
|
||||
|
||||
interface IDrop {
|
||||
props: {
|
||||
onDrop: (event: React.DragEvent<HTMLElement>) => void;
|
||||
onDragOver: (event: React.DragEvent<HTMLElement>) => void;
|
||||
onDragEnter: (event: React.DragEvent<HTMLElement>) => void;
|
||||
onDragLeave: (event: React.DragEvent<HTMLElement>) => void;
|
||||
};
|
||||
}
|
||||
|
||||
export function useDrop({ canDrop, onDrop, onDragOver, onDragEnter, onDragLeave }: IDropOptions = {}): IDrop {
|
||||
const optionsRef = useRef<IDropOptions>({ canDrop, onDrop, onDragOver, onDragEnter, onDragLeave });
|
||||
optionsRef.current = { canDrop, onDrop, onDragOver, onDragEnter, onDragLeave };
|
||||
|
||||
function handleOver(event: React.DragEvent<HTMLElement>) {
|
||||
const provider = getStoreProvider(event);
|
||||
const isDroppable = optionsRef.current.canDrop?.(event, provider);
|
||||
if (isDroppable) {
|
||||
event.preventDefault();
|
||||
event.dataTransfer.dropEffect = 'move';
|
||||
} else {
|
||||
event.dataTransfer.dropEffect = 'none';
|
||||
}
|
||||
optionsRef.current.onDragOver?.(event, provider);
|
||||
}
|
||||
|
||||
function handleEnter(event: React.DragEvent<HTMLElement>) {
|
||||
const provider = getStoreProvider(event);
|
||||
optionsRef.current.onDragEnter?.(event, provider);
|
||||
}
|
||||
function handleLeave(event: React.DragEvent<HTMLElement>) {
|
||||
const provider = getStoreProvider(event);
|
||||
optionsRef.current.onDragLeave?.(event, provider);
|
||||
}
|
||||
function handleDrop(event: React.DragEvent<HTMLElement>) {
|
||||
const provider = getStoreProvider(event);
|
||||
optionsRef.current.onDrop?.(event, provider);
|
||||
}
|
||||
|
||||
return {
|
||||
props: useMemo(
|
||||
() => ({
|
||||
onDrop: handleDrop,
|
||||
onDragOver: handleOver,
|
||||
onDragEnter: handleEnter,
|
||||
onDragLeave: handleLeave,
|
||||
}),
|
||||
[],
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"extends": "@dbeaver/tsconfig/tsconfig.webview.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./lib/.tsbuildinfo",
|
||||
"rootDir": "src",
|
||||
"rootDirs": [
|
||||
"src"
|
||||
],
|
||||
"outDir": "lib",
|
||||
"incremental": true,
|
||||
"composite": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/lib
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env*
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
tsconfig.tsbuildinfo
|
||||
@@ -1,14 +0,0 @@
|
||||
import '../src/index.css';
|
||||
import './global.css';
|
||||
import Icons from '../assets/icons/icons.svg?react';
|
||||
|
||||
import type { GlobalProvider } from '@ladle/react';
|
||||
|
||||
export const Provider: GlobalProvider = ({ children }) => {
|
||||
return (
|
||||
<div>
|
||||
<Icons style={{ display: 'none' }} />
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
/** @type {import('@ladle/react').UserConfig} */
|
||||
export default {
|
||||
addons: {
|
||||
a11y: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,132 +0,0 @@
|
||||
@import 'tailwindcss' layer(base) prefix(tw);
|
||||
|
||||
@layer theme {
|
||||
main {
|
||||
color: var(--dbv-kit-color-foreground);
|
||||
background-color: var(--tw-color-gray-50);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--tw-color-blue-600);
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
color: var(--tw-color-blue-700);
|
||||
text-decoration-thickness: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
color: var(--tw-color-orange-700);
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h3,
|
||||
h5 {
|
||||
font-weight: var(--tw-font-weight-semibold);
|
||||
margin-block-start: 1em;
|
||||
margin-block-end: 0.5em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: var(--tw-text-2xl);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: var(--tw-text-xl);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: var(--tw-text-lg);
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: var(--tw-text-base);
|
||||
margin-block-start: 0.5em;
|
||||
margin-block-end: 0.5em;
|
||||
}
|
||||
|
||||
kbd {
|
||||
background-color: #eee;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #b4b4b4;
|
||||
box-shadow:
|
||||
0 1px 1px rgba(0, 0, 0, 0.2),
|
||||
0 2px 0 0 rgba(255, 255, 255, 0.7) inset;
|
||||
color: #333;
|
||||
display: inline-block;
|
||||
font-size: 0.85em;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
padding: 2px 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.codeblock {
|
||||
background-color: var(--tw-color-gray-100);
|
||||
padding: 1em;
|
||||
border-radius: 0.25em;
|
||||
margin-block-start: 1em;
|
||||
margin-block-end: 1em;
|
||||
}
|
||||
|
||||
.comment {
|
||||
color: var(--tw-color-gray-500);
|
||||
}
|
||||
|
||||
.color-sample {
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
border-radius: 0.25em;
|
||||
}
|
||||
}
|
||||
|
||||
:root[data-theme='dark'] {
|
||||
--dbv-kit-color-foreground: var(--tw-color-gray-200);
|
||||
--dbv-kit-color-background: var(--tw-color-gray-900);
|
||||
--dbv-kit-btn-loader-base-color: var(--tw-color-gray-900);
|
||||
|
||||
/* Primary Button */
|
||||
--dbv-kit-btn-primary-foreground: var(--tw-color-gray-100);
|
||||
--dbv-kit-btn-primary-background: var(--dbv-kit-color-primary-700);
|
||||
--dbv-kit-btn-primary-background-hover: var(--dbv-kit-color-primary-600);
|
||||
--dbv-kit-btn-primary-background-active: var(--dbv-kit-color-primary-500);
|
||||
|
||||
/* Secondary Button */
|
||||
--dbv-kit-btn-secondary-foreground: var(--tw-color-gray-100);
|
||||
--dbv-kit-btn-secondary-background: transparent;
|
||||
--dbv-kit-btn-secondary-background-hover: var(--tw-color-gray-800);
|
||||
--dbv-kit-btn-secondary-background-active: var(--tw-color-gray-700);
|
||||
--dbv-kit-btn-secondary-border-color: var(--tw-color-gray-200);
|
||||
|
||||
/* Danger Button */
|
||||
--dbv-kit-btn-danger-foreground: var(--tw-color-gray-100);
|
||||
--dbv-kit-btn-danger-background: var(--tw-color-red-700);
|
||||
--dbv-kit-btn-danger-background-hover: var(--tw-color-red-600);
|
||||
--dbv-kit-btn-danger-background-active: var(--tw-color-red-500);
|
||||
|
||||
/* Input */
|
||||
--dbv-kit-input-background: var(--tw-color-gray-800);
|
||||
--dbv-kit-input-border-color: var(--tw-color-gray-700);
|
||||
--dbv-kit-input-color: var(--tw-color-gray-100);
|
||||
--dbv-kit-input-placeholder-foreground: var(--tw-color-gray-400);
|
||||
|
||||
main {
|
||||
background-color: var(--tw-color-gray-900);
|
||||
}
|
||||
|
||||
code {
|
||||
color: var(--tw-color-orange-400);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--tw-color-blue-300);
|
||||
}
|
||||
|
||||
.codeblock {
|
||||
background-color: var(--tw-color-gray-800);
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
# DBeaver UI Kit
|
||||
|
||||
#### The DBeaver UI Kit is a collection of reusable React components designed to help developers build consistent and visually appealing user interfaces.
|
||||
|
||||
## Using Ladle for Component Development
|
||||
|
||||
[Ladle](https://ladle.dev/) is a tool that allows you to develop, test, and document your React components in isolation. We use it to create a sandbox environment where you can interact with your components and ensure they work as expected.
|
||||
|
||||
To get started with Ladle in the DBeaver UI Kit, write in terminal:
|
||||
|
||||
```bash
|
||||
yarn docs
|
||||
```
|
||||
|
||||
Then open the provided URL to see components.
|
||||
|
||||
## How to Customize
|
||||
|
||||
DBeaver UI Kit components have basic styles and use some defaults from Tailwind CSS. To change styling, you can use different approaches.
|
||||
|
||||
### Global CSS Tokens
|
||||
|
||||
The UI kit uses several global CSS tokens described on the Global Tokens page. These tokens help maintain consistency across the UI components. All DBeaver UI tokens are prefixed with `--dbv-kit-`. You can use these tokens to customize the UI kit to fit your application's design. For example, changing the `--dbv-kit-font-size-base` token will change the base font size for text in the UI kit. Changing the `--dbv-kit-control-height-base` token will change all control heights. If you don't want to change all controls, you can change `--dbv-kit-control-height-small`, `--dbv-kit-control-height-medium`, or other tokens separately. They will affect only specific components.
|
||||
|
||||
### Component CSS Tokens
|
||||
|
||||
Some components have their own tokens. For example, the Button component has tokens for button sizes, padding, and colors. You can change these tokens to customize the Button component. To see the list of tokens, go to sources or start Ladle.
|
||||
|
||||
### Component CSS Classes
|
||||
|
||||
Each component in the UI kit has a default CSS class name following the `dbv-kit-[component name(in lowercase)]` naming convention that you can use to customize the component. You can find the default class name in the component's documentation.
|
||||
|
||||
For button:
|
||||
|
||||
```css
|
||||
.dbv-kit-button {
|
||||
/* Your custom styles */
|
||||
}
|
||||
```
|
||||
|
||||
For input:
|
||||
|
||||
```css
|
||||
.dbv-kit-input {
|
||||
/* Your custom styles */
|
||||
}
|
||||
```
|
||||
|
||||
Some components have additional classes for different states or sizes. For example, the Button component has classes for different sizes: `.dbv-kit-button--small`, `.dbv-kit-button--large`. We use the BEM convention for class names. If a component has a modifier, it will be separated by two dashes. If a component has a child element, it will be separated by two underscores, for example:
|
||||
|
||||
- `.dbv-kit-button__icon` - icon is a child element of the button
|
||||
- `.dbv-kit-button--large` - large is a size modifier
|
||||
- `.dbv-kit-button__icon--start` - icon is a child element of the button and has a start placement
|
||||
|
||||
Each component accepts the standard `className` and `style` props which enable using vanilla CSS, utility classes (e.g., Tailwind), CSS-in-JS (e.g., Styled Components), etc. A custom `className` can be specified and will be appended to a class list of a component:
|
||||
|
||||
```jsx
|
||||
<Button className="my-custom-button">Button</Button>
|
||||
```
|
||||
|
||||
```jsx
|
||||
<Button style={{ background: 'red' }}>Button</Button>
|
||||
```
|
||||
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: currentColor;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-1" x="15" y="2" width="2" height="26" />
|
||||
<rect class="cls-1" x="13.21" y="23.28" width="14.14" height="2.02"
|
||||
transform="translate(-11.23 21.46) rotate(-45)" />
|
||||
<rect class="cls-1" x="10.7" y="17.21" width="2.02" height="14.14"
|
||||
transform="translate(-13.74 15.4) rotate(-45)" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 540 B |
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: currentColor;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-1" x="11.17" y="2" width="1.67" height="18.33" />
|
||||
<rect class="cls-1" x="9.68" y="16.4" width="11.78" height="1.68"
|
||||
transform="translate(-7.63 16.06) rotate(-45)" />
|
||||
<rect class="cls-1" x="7.59" y="11.35" width="1.68" height="11.79"
|
||||
transform="translate(-9.72 11.01) rotate(-45)" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 546 B |
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: currentColor;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-1" x="7.5" y="1" width="1" height="13" />
|
||||
<rect class="cls-1" x="6.61" y="11.35" width="7.07" height="1.01"
|
||||
transform="translate(-5.41 10.64) rotate(-45)" />
|
||||
<rect class="cls-1" x="5.35" y="8.32" width="1.01" height="7.07"
|
||||
transform="translate(-6.67 7.61) rotate(-45)" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 535 B |
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: currentColor;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-1" x="15" y="4" width="2" height="26" />
|
||||
<rect class="cls-1" x="4.64" y="6.7" width="14.14" height="2.02"
|
||||
transform="translate(-2.02 10.54) rotate(-45)" />
|
||||
<rect class="cls-1" x="19.28" y=".64" width="2.02" height="14.14"
|
||||
transform="translate(.49 16.6) rotate(-45)" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 532 B |
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: currentColor;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-1" x="11.17" y="3.67" width="1.67" height="18.33" />
|
||||
<rect class="cls-1" x="2.54" y="5.92" width="11.78" height="1.68"
|
||||
transform="translate(-2.31 7.94) rotate(-45)" />
|
||||
<rect class="cls-1" x="14.73" y=".87" width="1.68" height="11.79"
|
||||
transform="translate(-.22 12.99) rotate(-45)" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 546 B |
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: currentColor;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-1" x="7.5" y="2" width="1" height="13" />
|
||||
<rect class="cls-1" x="2.32" y="3.64" width="7.07" height="1.01"
|
||||
transform="translate(-1.21 5.36) rotate(-45)" />
|
||||
<rect class="cls-1" x="9.64" y=".61" width="1.01" height="7.07"
|
||||
transform="translate(.04 8.39) rotate(-45)" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 530 B |
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: currentColor;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="cls-1"
|
||||
d="M5.76,18.69l-2.07,6.28H1.02L7.84,5h3.08l6.82,19.97h-2.76l-2.13-6.28h-7.08ZM12.34,16.68l-1.99-5.75c-.44-1.3-.74-2.49-1.04-3.65h-.06c-.3,1.16-.59,2.4-1.01,3.62l-1.96,5.78h6.05Z" />
|
||||
<path class="cls-1"
|
||||
d="M30.77,21.31c0,1.22.06,2.41.21,3.37h-2.34l-.21-1.77h-.09c-.8,1.1-2.34,2.09-4.39,2.09-2.9,0-4.39-2-4.39-4.04,0-3.4,3.08-5.26,8.62-5.23v-.29c0-1.13-.33-3.25-3.26-3.22-1.36,0-2.76.38-3.76,1.05l-.59-1.71c1.19-.73,2.93-1.22,4.74-1.22,4.39,0,5.45,2.93,5.45,5.72v5.26ZM28.25,17.51c-2.84-.06-6.08.43-6.08,3.17,0,1.68,1.13,2.44,2.43,2.44,1.9,0,3.11-1.16,3.53-2.35.09-.29.12-.58.12-.81v-2.44Z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 856 B |
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: currentColor;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="cls-1"
|
||||
d="M4.48,14.37l-1.52,4.61h-1.96L6.01,4.31h2.26l5.01,14.67h-2.02l-1.57-4.61h-5.2ZM9.31,12.89l-1.46-4.22c-.33-.96-.54-1.83-.76-2.68h-.04c-.22.85-.44,1.76-.74,2.65l-1.44,4.24h4.44Z" />
|
||||
<path class="cls-1"
|
||||
d="M22.85,16.29c0,.9.04,1.77.15,2.47h-1.72l-.15-1.3h-.07c-.59.81-1.72,1.53-3.22,1.53-2.13,0-3.22-1.47-3.22-2.96,0-2.5,2.26-3.86,6.33-3.84v-.21c0-.83-.24-2.39-2.39-2.37-1,0-2.02.28-2.76.77l-.44-1.26c.87-.53,2.15-.9,3.48-.9,3.22,0,4,2.15,4,4.2v3.86ZM21,13.5c-2.09-.04-4.46.32-4.46,2.32,0,1.24.83,1.79,1.78,1.79,1.39,0,2.29-.85,2.59-1.73.07-.21.09-.43.09-.6v-1.79Z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 832 B |
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: currentColor;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="cls-1"
|
||||
d="M2.53,9.63l-1.11,3.36H0L3.64,2.32h1.65l3.64,10.67h-1.47l-1.14-3.36h-3.78ZM6.05,8.55l-1.06-3.07c-.24-.7-.4-1.33-.55-1.95h-.03c-.16.62-.32,1.28-.54,1.93l-1.04,3.09h3.23Z" />
|
||||
<path class="cls-1"
|
||||
d="M15.89,11.03c0,.65.03,1.29.11,1.8h-1.25l-.11-.95h-.05c-.43.59-1.25,1.12-2.34,1.12-1.55,0-2.34-1.07-2.34-2.16,0-1.81,1.65-2.81,4.61-2.79v-.15c0-.6-.17-1.74-1.74-1.72-.73,0-1.47.2-2.01.56l-.32-.91c.63-.39,1.57-.65,2.53-.65,2.34,0,2.91,1.57,2.91,3.06v2.81ZM14.54,9c-1.52-.03-3.24.23-3.24,1.69,0,.9.6,1.3,1.3,1.3,1.01,0,1.66-.62,1.88-1.26.05-.15.06-.31.06-.43v-1.3Z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 828 B |
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="currentColor">
|
||||
<polygon points="2 10 12 10 7 4 2 10" />
|
||||
<rect x="6" y="6" width="2" height="22" />
|
||||
<rect x="14" y="6" width="16" height="2" />
|
||||
<rect x="14" y="15" width="12" height="2" />
|
||||
<rect x="14" y="24" width="8" height="2" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 353 B |
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<polygon points="2 8.14 9.14 8.14 5.57 3.86 2 8.14" />
|
||||
<rect x="4.86" y="5.29" width="1.43" height="15.71" />
|
||||
<rect x="10.57" y="5.29" width="11.43" height="1.43" />
|
||||
<rect x="10.57" y="11.71" width="8.57" height="1.43" />
|
||||
<rect x="10.57" y="18.14" width="5.71" height="1.43" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 414 B |