Browse Source

feat: Improved UI (#6222)

* feat: Improved ui (#6156)

* refactor: revert

Signed-off-by: Pranav C <pranavxc@gmail.com>

feat: shared base

Signed-off-by: Pranav C <pranavxc@gmail.com>

fix: remove duplicate import statement

Signed-off-by: Pranav C <pranavxc@gmail.com>

fix: disable starred & license menu

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

test: fix airtable wait issue

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

test: enable mysql in ci

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

test: fix checkbox order for sqlite

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

test: disable quick tests

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

test: fix dbType env variable for CI

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

test: workspace API access error fix

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

test: enable SQLite CI CD

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

test: use DB_TYPE env variable

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

test: enable SQLite UT

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

test: isHub cleanup

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

test: add check for EE Timezone spec

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

chore: cleanup

Signed-off-by: Pranav C <pranavxc@gmail.com>

chore: cleanup

Signed-off-by: Pranav C <pranavxc@gmail.com>

test: EE check fix

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

chore: test correction

Signed-off-by: Pranav C <pranavxc@gmail.com>

chore: sync latest changes

Signed-off-by: Pranav C <pranavxc@gmail.com>

test: set EE=false

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

test: set NC Edition to community in workflow file

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

chore: update sdk build command

Signed-off-by: Pranav C <pranavxc@gmail.com>

refactor: i18n and other changes

Signed-off-by: Pranav C <pranavxc@gmail.com>

feat: new ui

Signed-off-by: Pranav C <pranavxc@gmail.com>

* chore: sync tests

Signed-off-by: Pranav C <pranavxc@gmail.com>

* chore: lint

Signed-off-by: Pranav C <pranavxc@gmail.com>

* fix: shared view/base related bugs

Signed-off-by: Pranav C <pranavxc@gmail.com>

* test: checkbox verification sort order fix

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

* test: fix sqlite reset

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

* test: enable selfhosted runners

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

* docs: table ops (draft)

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

* Docs: screenshots for table-operations.md

* refactor: introduce missing buttons

Signed-off-by: Pranav C <pranavxc@gmail.com>

* fix: get all fields

Signed-off-by: Pranav C <pranavxc@gmail.com>

* test: UT fix- new data API response

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

* test: EE is false

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

* test: webhook lookup as string in CE

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

* fix: include created_at and updated_at

Signed-off-by: Pranav C <pranavxc@gmail.com>

* test: fix UT newDataAPI response for PG

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

* fix: separate api for webhook related plugins

Signed-off-by: Pranav C <pranavxc@gmail.com>

* test: msyql filter corrections

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

* test: mysql group by test corrections

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

* test: fix datatype for rating field in groupby spec for pg

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

* test: kanban datatype correction

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

* test: column edit for mysql- rating field

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

* test: misc fixes

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

* test: enable 4 workers

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

* test: enable 2 workers per shard only

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

* docs: table CRUD

* Rename table-operations.md to table-crud.md

* Create column-crud.md

* docs: row CRUD

* Rename row.md to row-crud.md

* docs: project crud

* docs: toolbar (skeleton)

* refactor: single page UI and bug fixes

Signed-off-by: Pranav C <pranavxc@gmail.com>

* chore: sync tests playwright

Signed-off-by: Pranav C <pranavxc@gmail.com>

* chore: add missing dependency

Signed-off-by: Pranav C <pranavxc@gmail.com>

* feat: single page ui, test corrections

Signed-off-by: Pranav C <pranavxc@gmail.com>

* chore: tests

Signed-off-by: Pranav C <pranavxc@gmail.com>

* test: project rename test correction

Signed-off-by: Pranav C <pranavxc@gmail.com>

* chore: remove only

Signed-off-by: Pranav C <pranavxc@gmail.com>

* test: remove wrong import statement

Signed-off-by: Pranav C <pranavxc@gmail.com>

* fix: delete option not visible in project context menu

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

* test: move ws access within isEE()

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

* test: fix groupby

* test: groupby fix

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

* docs: signup & landing page

* docs: project crud

* docs: project-crud misc

* docs: toolbar fields

* docs: toolbar / filters

* docs: toolbar / group by

* docs: toolbar / sort

* docs: toolbar / row height

* docs: filters additional options

* docs: file re-order

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

* docs: add links to column types

* docs: code snippets

* docs: links

* docs: lookup

* docs: rollup

* docs: formula

* docs: primary key

* docs: display value

* docs: development setup

* docs: swagger

* fix(nc-gui): encodeURIComponent for row id

- closes: #6202

* docs: language

* docs: expanded record

* docs: import airtable

* docs: airtable

* docs: webhook

* docs: revert file rename

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

* docs: account settings

* docs: audit

* docs: meta management

* docs: project settings

* docs: shared base

* docs: shared view

* docs: meta sync

* docs: team-auth

* docs: views

* docs: fix URL

* docs: URL corrections

* fix:  shared base, view related bugs

Signed-off-by: Pranav C <pranavxc@gmail.com>

* test: EE check for WSaccess

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

* test: exclude EE tests

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

* fix: missing project delete

closes #6215

Signed-off-by: Pranav C <pranavxc@gmail.com>

* fix: merge existing project meta  if found

closes #6216

Signed-off-by: Pranav C <pranavxc@gmail.com>

* fix: merge existing project meta  if found

closes #6216

Signed-off-by: Pranav C <pranavxc@gmail.com>

---------

Signed-off-by: Pranav C <pranavxc@gmail.com>
Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>
Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com>
Co-authored-by: DarkPhoenix2704 <anbarasun123@gmail.com>
Co-authored-by: Wing-Kam Wong <wingkwong.code@gmail.com>

* refactor: docs and other bug fixes

Signed-off-by: Pranav C <pranavxc@gmail.com>

* feat: populate default project on super admin signup

Signed-off-by: Pranav C <pranavxc@gmail.com>

* fix: include created project details in signup response if avail, missing Dockerfile

Signed-off-by: Pranav C <pranavxc@gmail.com>

* chore: use custom function for resolving ts path aliases

Signed-off-by: Pranav C <pranavxc@gmail.com>

* chore: add missing generate script

Signed-off-by: Pranav C <pranavxc@gmail.com>

* chore: webpack build correction - ts path resolve

Signed-off-by: Pranav C <pranavxc@gmail.com>

---------

Signed-off-by: Pranav C <pranavxc@gmail.com>
Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>
Co-authored-by: mertmit <mertmit99@gmail.com>
Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com>
Co-authored-by: DarkPhoenix2704 <anbarasun123@gmail.com>
Co-authored-by: Wing-Kam Wong <wingkwong.code@gmail.com>
pull/6225/head
Pranav C 1 year ago committed by GitHub
parent
commit
33ee9bfa62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      .github/workflows/ci-cd.yml
  2. 144
      .github/workflows/playwright-test-workflow.yml
  3. 5
      .gitignore
  4. 9
      packages/nc-gui/app.vue
  5. 4
      packages/nc-gui/assets/css/global.css
  6. 31
      packages/nc-gui/assets/dashboardLayout.scss
  7. 529
      packages/nc-gui/assets/docsPage.scss
  8. BIN
      packages/nc-gui/assets/icons/FileIconImageBox.png
  9. 18
      packages/nc-gui/assets/img/404.svg
  10. 3
      packages/nc-gui/assets/img/add-page.svg
  11. BIN
      packages/nc-gui/assets/img/brand/nocodb-full.png
  12. 26
      packages/nc-gui/assets/img/clickup-icon.svg
  13. 16
      packages/nc-gui/assets/img/dashboards/layout-visualizer/gap.svg
  14. 20
      packages/nc-gui/assets/img/dashboards/layout-visualizer/horizontal.svg
  15. 28
      packages/nc-gui/assets/img/dashboards/layout-visualizer/none.svg
  16. 20
      packages/nc-gui/assets/img/dashboards/layout-visualizer/vertical.svg
  17. 10
      packages/nc-gui/assets/img/dashboards/widget-types/bar-chart.svg
  18. 26
      packages/nc-gui/assets/img/dashboards/widget-types/button.svg
  19. 10
      packages/nc-gui/assets/img/dashboards/widget-types/divider.svg
  20. 15
      packages/nc-gui/assets/img/dashboards/widget-types/form.svg
  21. 15
      packages/nc-gui/assets/img/dashboards/widget-types/gallery.svg
  22. 15
      packages/nc-gui/assets/img/dashboards/widget-types/grid.svg
  23. 15
      packages/nc-gui/assets/img/dashboards/widget-types/image.svg
  24. 15
      packages/nc-gui/assets/img/dashboards/widget-types/kanban.svg
  25. 15
      packages/nc-gui/assets/img/dashboards/widget-types/line-chart.svg
  26. 9
      packages/nc-gui/assets/img/dashboards/widget-types/number.svg
  27. 10
      packages/nc-gui/assets/img/dashboards/widget-types/pie-chart.svg
  28. 15
      packages/nc-gui/assets/img/dashboards/widget-types/scatter-chart.svg
  29. 9
      packages/nc-gui/assets/img/dashboards/widget-types/text.svg
  30. 89
      packages/nc-gui/assets/img/google-doc.svg
  31. 89
      packages/nc-gui/assets/img/google-sheet.svg
  32. 97
      packages/nc-gui/assets/img/google-slide.svg
  33. BIN
      packages/nc-gui/assets/img/icons/256x256-trans.png
  34. BIN
      packages/nc-gui/assets/img/icons/256x256.png
  35. BIN
      packages/nc-gui/assets/img/icons/64x64.png
  36. BIN
      packages/nc-gui/assets/img/miro-icon.png
  37. 10
      packages/nc-gui/assets/nc-icons/add-box.svg
  38. 1
      packages/nc-gui/assets/nc-icons/article.svg
  39. 5
      packages/nc-gui/assets/nc-icons/check.svg
  40. 4
      packages/nc-gui/assets/nc-icons/credit-card.svg
  41. 3
      packages/nc-gui/assets/nc-icons/dashboard.svg
  42. 3
      packages/nc-gui/assets/nc-icons/database.svg
  43. 9
      packages/nc-gui/assets/nc-icons/db.svg
  44. 9
      packages/nc-gui/assets/nc-icons/doc.svg
  45. 3
      packages/nc-gui/assets/nc-icons/docs.svg
  46. 10
      packages/nc-gui/assets/nc-icons/download.svg
  47. 4
      packages/nc-gui/assets/nc-icons/eye.svg
  48. 3
      packages/nc-gui/assets/nc-icons/filter.svg
  49. 8
      packages/nc-gui/assets/nc-icons/inbox.svg
  50. 12
      packages/nc-gui/assets/nc-icons/layers.svg
  51. 5
      packages/nc-gui/assets/nc-icons/layout.svg
  52. 8
      packages/nc-gui/assets/nc-icons/list.svg
  53. 1
      packages/nc-gui/assets/nc-icons/postgresql.svg
  54. 4
      packages/nc-gui/assets/nc-icons/search.svg
  55. 5
      packages/nc-gui/assets/nc-icons/share.svg
  56. 4
      packages/nc-gui/assets/nc-icons/sidebar.svg
  57. 6
      packages/nc-gui/assets/nc-icons/users.svg
  58. 205
      packages/nc-gui/assets/style.scss
  59. 37
      packages/nc-gui/assets/style/fonts.css
  60. BIN
      packages/nc-gui/assets/style/material.woff2
  61. 60
      packages/nc-gui/components.d.ts
  62. 24
      packages/nc-gui/components/account/License.vue
  63. 6
      packages/nc-gui/components/account/SignupSettings.vue
  64. 88
      packages/nc-gui/components/account/Token.vue
  65. 65
      packages/nc-gui/components/account/UserList.vue
  66. 38
      packages/nc-gui/components/account/UsersModal.vue
  67. 68
      packages/nc-gui/components/api-client/Headers.vue
  68. 64
      packages/nc-gui/components/api-client/Params.vue
  69. 6
      packages/nc-gui/components/cell/Checkbox.vue
  70. 2
      packages/nc-gui/components/cell/ClampedText.vue
  71. 2
      packages/nc-gui/components/cell/Currency.vue
  72. 24
      packages/nc-gui/components/cell/DatePicker.vue
  73. 24
      packages/nc-gui/components/cell/DateTimePicker.vue
  74. 69
      packages/nc-gui/components/cell/Decimal.vue
  75. 2
      packages/nc-gui/components/cell/Duration.vue
  76. 6
      packages/nc-gui/components/cell/Email.vue
  77. 18
      packages/nc-gui/components/cell/GeoData.vue
  78. 51
      packages/nc-gui/components/cell/Integer.vue
  79. 47
      packages/nc-gui/components/cell/Json.vue
  80. 34
      packages/nc-gui/components/cell/MultiSelect.vue
  81. 9
      packages/nc-gui/components/cell/Rating.vue
  82. 31
      packages/nc-gui/components/cell/SingleSelect.vue
  83. 130
      packages/nc-gui/components/cell/TextArea.vue
  84. 8
      packages/nc-gui/components/cell/TimePicker.vue
  85. 8
      packages/nc-gui/components/cell/Url.vue
  86. 8
      packages/nc-gui/components/cell/YearPicker.vue
  87. 22
      packages/nc-gui/components/cell/attachment/Carousel.vue
  88. 61
      packages/nc-gui/components/cell/attachment/Modal.vue
  89. 38
      packages/nc-gui/components/cell/attachment/RenameFile.vue
  90. 13
      packages/nc-gui/components/cell/attachment/index.vue
  91. 8
      packages/nc-gui/components/cell/attachment/sort.ts
  92. 7
      packages/nc-gui/components/cell/attachment/utils.ts
  93. 3
      packages/nc-gui/components/cmd-k/index.vue
  94. 148
      packages/nc-gui/components/dashboard/Sidebar.vue
  95. 235
      packages/nc-gui/components/dashboard/TreeView.vue
  96. 262
      packages/nc-gui/components/dashboard/TreeViewNew/AddNewTableNode.vue
  97. 179
      packages/nc-gui/components/dashboard/TreeViewNew/BaseOptions.vue
  98. 742
      packages/nc-gui/components/dashboard/TreeViewNew/ProjectNode.vue
  99. 19
      packages/nc-gui/components/dashboard/TreeViewNew/ProjectWrapper.vue
  100. 172
      packages/nc-gui/components/dashboard/TreeViewNew/TableList.vue
  101. Some files were not shown because too many files have changed in this diff Show More

7
.github/workflows/ci-cd.yml

@ -18,7 +18,10 @@ on:
- "packages/nc-gui/**" - "packages/nc-gui/**"
- "packages/nocodb/**" - "packages/nocodb/**"
- ".github/workflows/ci-cd.yml" - ".github/workflows/ci-cd.yml"
- ".github/workflows/playwright-test-workflow.yml"
- "tests/playwright/**" - "tests/playwright/**"
# Triggered manually
workflow_dispatch:
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
@ -89,6 +92,10 @@ jobs:
${{ runner.os }}-build-${{ env.cache-name }}- ${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build- ${{ runner.os }}-build-
${{ runner.os }}- ${{ runner.os }}-
- name: Set CI env
run: export CI=true
- name: Set NC Edition
run: export EE=true
- name: setup pg - name: setup pg
working-directory: ./ working-directory: ./
run: docker-compose -f ./tests/playwright/scripts/docker-compose-playwright-pg.yml up -d & run: docker-compose -f ./tests/playwright/scripts/docker-compose-playwright-pg.yml up -d &

144
.github/workflows/playwright-test-workflow.yml

@ -13,21 +13,9 @@ on:
jobs: jobs:
playwright: playwright:
runs-on: ubuntu-20.04 runs-on: [self-hosted, v2]
timeout-minutes: 40 timeout-minutes: 100
steps: steps:
# Reference: https://github.com/pierotofy/set-swap-space/blob/master/action.yml
- name: Set 5gb swap
shell: bash
# Delete the swap file, allocate a new one, and activate it
run: |
export SWAP_FILE=$(swapon --show=NAME | tail -n 1)
sudo swapoff $SWAP_FILE
sudo rm $SWAP_FILE
sudo fallocate -l 5G $SWAP_FILE
sudo chmod 600 $SWAP_FILE
sudo mkswap $SWAP_FILE
sudo swapon $SWAP_FILE
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
@ -42,57 +30,87 @@ jobs:
with: with:
# npm cache files are stored in `~/.npm` on Linux/macOS # npm cache files are stored in `~/.npm` on Linux/macOS
path: ~/.npm path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} key: ${{ runner.os }}-v2-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: | restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}- ${{ runner.os }}-v2-build-${{ env.cache-name }}-
${{ runner.os }}-build- ${{ runner.os }}-v2-build-
${{ runner.os }}- ${{ runner.os }}-v2
- name: setup pg
if: ${{ inputs.db == 'pg' || ( inputs.db == 'sqlite' && inputs.shard == '1' ) }}
working-directory: ./
run: |
service postgresql start
cd /var/lib/postgresql/ && sudo -u postgres psql -c "SELECT 'dropdb '||datname||'' FROM pg_database WHERE datistemplate = false AND datallowconn = true And datname NOT IN ('postgres')" |grep ' dropdb ' | sudo -u postgres /bin/bash ; cd
sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD 'password';"
sudo -u postgres psql -c "ALTER USER postgres WITH SUPERUSER;"
service postgresql restart
- name: Set CI env
run: export CI=true
- name: Kill stale servers
run: |
# export NODE_OPTIONS=\"--max_old_space_size=16384\";
kill -9 $(lsof -t -i:8080) || echo "no process running on 8080"
kill -9 $(lsof -t -i:3000) || echo "no process running on 3000"
- name: Set CI env
run: export CI=true
- name: Set NC Edition
run: export EE=true
- name: install dependencies nocodb-sdk - name: install dependencies nocodb-sdk
working-directory: ./packages/nocodb-sdk working-directory: ./packages/nocodb-sdk
run: npm install run: npm install
- name: build nocodb-sdk - name: Build nocodb-sdk
working-directory: ./packages/nocodb-sdk working-directory: ./packages/nocodb-sdk
run: npm run build run: npm run build
- name: setup mysql - name: Setup mysql
if: ${{ inputs.db == 'mysql' }} if: ${{ inputs.db == 'mysql' }}
working-directory: ./ working-directory: ./packages/nocodb/tests/mysql-sakila-db
run: docker-compose -f ./tests/playwright/scripts/docker-compose-mysql-playwright.yml up -d & run: |
- name: setup pg # Get a list of non-system databases and construct the DROP DATABASE statement for each
if: ${{ inputs.db == 'pg' }} service mysql start
working-directory: ./ mysql -u'root' -p'password' -e "SHOW DATABASES" --skip-column-names | grep -Ev "(information_schema|mysql|performance_schema|sys)" | while read db; do
run: docker-compose -f ./tests/playwright/scripts/docker-compose-playwright-pg.yml up -d & mysql -u'root' -p'password' -e "DROP DATABASE IF EXISTS \`$db\`";
- name: setup pg for quick tests done
# keep sql_mode default except remove "STRICT_TRANS_TABLES"
mysql -u'root' -p'password' -e "SET GLOBAL sql_mode = 'ONLY_FULL_GROUP_BY,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';"
# this is only needed for connecting to sakila db as its refeferred in multiple places in test code
mysql -u'root' -p'password' < 01-mysql-sakila-schema.sql
mysql -u'root' -p'password' < 02-mysql-sakila-insert-data.sql
- name: Setup pg for quick tests
if: ${{ inputs.db == 'sqlite' && inputs.shard == '1' }} if: ${{ inputs.db == 'sqlite' && inputs.shard == '1' }}
working-directory: ./ working-directory: ./packages/nocodb/tests/pg-cy-quick/
run: docker-compose -f ./tests/playwright/scripts/docker-compose-pg-pw-quick.yml up -d & run: |
sudo -u postgres psql -U postgres -f 01-cy-quick.sql
- name: run frontend - name: run frontend
working-directory: ./packages/nc-gui working-directory: ./packages/nc-gui
run: npm run ci:run run: npm run ci:run
timeout-minutes: 20
- name: Run backend - name: Run backend
if: ${{ inputs.db == 'sqlite' }} if: ${{ inputs.db == 'sqlite' }}
working-directory: ./packages/nocodb working-directory: ./packages/nocodb
run: | run: |
npm install npm install
npm run watch:run:playwright > ${{ inputs.db }}_${{ inputs.shard }}_test_backend.log & npm run watch:run:playwright &> ${{ inputs.db }}_${{ inputs.shard }}_test_backend.log &
- name: Run backend:mysql - name: Run backend:mysql
if: ${{ inputs.db == 'mysql' }} if: ${{ inputs.db == 'mysql' }}
working-directory: ./packages/nocodb working-directory: ./packages/nocodb
run: | run: |
npm install npm install
npm run watch:run:playwright:mysql > ${{ inputs.db }}_${{ inputs.shard }}_test_backend.log & npm run watch:run:playwright:mysql &> ${{ inputs.db }}_${{ inputs.shard }}_test_backend.log &
- name: Run backend:pg - name: Run backend:pg
if: ${{ inputs.db == 'pg' }} if: ${{ inputs.db == 'pg' }}
working-directory: ./packages/nocodb working-directory: ./packages/nocodb
run: | run: |
npm install npm install
npm run watch:run:playwright:pg > ${{ inputs.db }}_${{ inputs.shard }}_test_backend.log & npm run watch:run:playwright:pg &> ${{ inputs.db }}_${{ inputs.shard }}_test_backend.log &
- name: Cache playwright npm modules - name: Cache playwright npm modules
uses: actions/cache@v3 uses: actions/cache@v3
id: playwright-cache id: playwright-cache
with: with:
path: | path: |
**/tests/playwright/node_modules **/tests/playwright/node_modules
key: cache-nc-playwright-${{ hashFiles('**/tests/playwright/package-lock.json') }} key: cache-v2-nc-playwright-${{ hashFiles('**/tests/playwright/package-lock.json') }}
restore-keys: |
cache-v2-nc-playwright-
- name: Install dependencies - name: Install dependencies
if: steps.playwright-cache.outputs.cache-hit != 'true' if: steps.playwright-cache.outputs.cache-hit != 'true'
working-directory: ./tests/playwright working-directory: ./tests/playwright
@ -106,11 +124,11 @@ jobs:
printf '.' printf '.'
sleep 2 sleep 2
done done
timeout-minutes: 2
- name: Run Playwright tests - name: Run Playwright Tests
working-directory: ./tests/playwright working-directory: ./tests/playwright
run: E2E_DB_TYPE=${{ inputs.db }} npm run ci:test:shard:${{ inputs.shard }} run: E2E_DB_TYPE=${{ inputs.db }} npm run ci:test:shard:${{ inputs.shard }}
timeout-minutes: 60
# Stress test added/modified tests # Stress test added/modified tests
- name: Fetch develop branch - name: Fetch develop branch
working-directory: ./tests/playwright working-directory: ./tests/playwright
@ -119,28 +137,32 @@ jobs:
working-directory: ./tests/playwright working-directory: ./tests/playwright
run: E2E_DB_TYPE=${{ inputs.db }} node ./scripts/stressTestNewlyAddedTest.js run: E2E_DB_TYPE=${{ inputs.db }} node ./scripts/stressTestNewlyAddedTest.js
# Quick tests (pg on sqlite shard 0 and sqlite on sqlite shard 1) # # Quick tests (pg on sqlite shard 0 and sqlite on sqlite shard 1)
- name: Run quick server and tests (pg) # - name: Run quick server and tests (pg)
if: ${{ inputs.db == 'sqlite' && inputs.shard == '1' }} # if: ${{ inputs.db == 'sqlite' && inputs.shard == '1' }}
working-directory: ./packages/nocodb # working-directory: ./packages/nocodb
run: | # run: |
kill -9 $(lsof -t -i:8080) # kill -9 $(lsof -t -i:8080)
npm run watch:run:playwright:pg:cyquick & # npm run watch:run:playwright:pg:cyquick > quick_${{ inputs.shard }}_test_backend.log &
- name: Run quick server and tests (sqlite) # - name: Run quick server and tests (sqlite)
if: ${{ inputs.db == 'sqlite' && inputs.shard == '2' }} # if: ${{ inputs.db == 'sqlite' && inputs.shard == '2' }}
working-directory: ./packages/nocodb # working-directory: ./packages/nocodb
run: | # run: |
kill -9 $(lsof -t -i:8080) # kill -9 $(lsof -t -i:8080)
npm run watch:run:playwright:quick > quick_${{ inputs.shard }}_test_backend.log & # npm run watch:run:playwright:quick > quick_${{ inputs.shard }}_test_backend.log &
- name: Wait for backend & run quick tests # - name: Wait for backend for sqlite-tests
if: ${{ inputs.db == 'sqlite' }} # if: ${{ inputs.db == 'sqlite' }}
working-directory: ./tests/playwright # working-directory: ./tests/playwright
run: | # run: |
while ! curl --output /dev/null --silent --head --fail http://localhost:8080; do # while ! curl --output /dev/null --silent --head --fail http://localhost:8080; do
printf '.' # printf '.'
sleep 2 # sleep 2
done # done
PLAYWRIGHT_HTML_REPORT=playwright-report-quick npm run test:quick # timeout-minutes: 1
# - name: Run quick tests
# if: ${{ inputs.db == 'sqlite' }}
# working-directory: ./tests/playwright
# run: PLAYWRIGHT_HTML_REPORT=playwright-report-quick npm run test:quick
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
if: ${{ inputs.db == 'sqlite' }} if: ${{ inputs.db == 'sqlite' }}
with: with:
@ -172,3 +194,9 @@ jobs:
name: backend-logs-${{ inputs.db }}-${{ inputs.shard }} name: backend-logs-${{ inputs.db }}-${{ inputs.shard }}
path: ./packages/nocodb/${{ inputs.db }}_${{ inputs.shard }}_test_backend.log path: ./packages/nocodb/${{ inputs.db }}_${{ inputs.shard }}_test_backend.log
retention-days: 2 retention-days: 2
- name: stop database servers
if: always()
working-directory: ./packages/nocodb
run: |
service postgresql stop
service mysql stop

5
.gitignore vendored

@ -2,8 +2,10 @@
# =========== # ===========
.DS_Store .DS_Store
ehthumbs.db ehthumbs.db
Icon?
Thumbs.db Thumbs.db
Icon
# Node and related ecosystem # Node and related ecosystem
# ========================== # ==========================
@ -92,3 +94,4 @@ test_noco.db
# ngrok config # ngrok config
httpbin httpbin
.run/test-debug.run.xml

9
packages/nc-gui/app.vue

@ -1,12 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, useRoute, useTheme } from '#imports' import { applyNonSelectable, computed, useRouter, useTheme } from '#imports'
const route = useRoute() const router = useRouter()
const disableBaseLayout = computed(() => route.path.startsWith('/nc/view') || route.path.startsWith('/nc/form')) const route = router.currentRoute
const disableBaseLayout = computed(() => route.value.path.startsWith('/nc/view') || route.value.path.startsWith('/nc/form'))
useTheme() useTheme()
applyNonSelectable()
useEventListener(document, 'keydown', async (e: KeyboardEvent) => { useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
const cmdOrCtrl = isMac() ? e.metaKey : e.ctrlKey const cmdOrCtrl = isMac() ? e.metaKey : e.ctrlKey
if (cmdOrCtrl) { if (cmdOrCtrl) {

4
packages/nc-gui/assets/css/global.css

@ -31,3 +31,7 @@ For Drag and Drop
.grabbing * { .grabbing * {
cursor: grabbing; cursor: grabbing;
} }
.flex.hidden {
display: none;
}

31
packages/nc-gui/assets/dashboardLayout.scss

@ -0,0 +1,31 @@
.nc-dashboard-layouts-propspanel-value-input {
@apply flex-grow py-1 px-3 border-grey-light border border-solid rounded-lg text-sm w-full my-2;
}
.nc-dashboard-layouts-propspanel-description-input {
@apply flex-grow py-1 px-3 !border-gray-200 border border-solid rounded-lg text-sm w-full my-2;
}
.nc-dashboard-layouts-propspanel-selectable-config-section {
@apply bg-gray-100 rouwded-lg p-2;
h3 {
@apply text-black;
}
h4 {
@apply text-gray-500
}
}
.nc-dashboard-layouts-propspanel-collapse {
background-color: transparent;
.nc-dashboard-layouts-propspanel-collapse-panel {
@apply border-1 border-grey-light rounded-lg my-2 min-w-full;
.ant-collapse-header {
@apply font-semibold;
@apply !text-sm !2xl:text-base;
}
h3 {
@apply text-gray-500;
}
}
}

529
packages/nc-gui/assets/docsPage.scss

@ -0,0 +1,529 @@
.nc-docs-page {
overflow-y: overlay;
height: calc(100vh - var(--topbar-height));
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: #f6f6f600 !important;
}
&::-webkit-scrollbar-thumb {
background: #f6f6f600;
}
&::-webkit-scrollbar-thumb:hover {
background: #f6f6f600;
}
}
.nc-docs-page:hover {
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: #f6f6f600 !important;
}
&::-webkit-scrollbar-thumb {
background: rgb(215, 215, 215);
}
&::-webkit-scrollbar-thumb:hover {
background: rgb(203, 203, 203);
}
}
.nc-docs-page-title {
font-weight: 600;
font-size: 3rem;
line-height: 1.25;
word-break: break-all;
outline: none;
padding: 0;
color: black;
}
.nc-docs-page-content {
@apply min-h-full;
.ProseMirror {
@apply min-h-full;
}
.ProseMirror-focused {
// remove all border
outline: none;
}
[data-diff-node='ins'] {
@apply !bg-green-200 rounded-sm p-0.5 m-0.5;
}
[data-diff-node='del'] {
@apply !bg-red-200 rounded-sm p-0.5 m-0.5;
}
del {
@apply !bg-red-200 rounded-sm my-0.5;
text-decoration: none;
}
ins {
@apply !bg-green-200 rounded-sm my-0.5 mx-0.5;
text-decoration: none;
}
ins[isempty='true'] {
display: block;
color: transparent;
user-select: none;
@apply !w-full;
}
del[isempty='true'] {
display: block;
color: transparent;
user-select: none;
@apply !w-full;
}
td {
ins {
@apply !p-0 !m-0;
}
}
.draggable-block-wrapper{
overflow: visible;
}
.draggable-block-wrapper.focused {
.attachment-wrapper .attachment {
@apply !bg-primary-selected;
}
}
.draggable-block-wrapper.selected {
table {
@apply !bg-primary-selected;
tr:first-child td {
@apply !bg-primary-selected;
}
}
.attachment-wrapper .attachment {
@apply !bg-primary-selected;
}
p,
h1,
h2,
h3,
h4,
h5,
h6,
li,
blockquote,
pre,
code,
img,
.link-to-page-wrapper {
@apply !bg-primary-selected;
}
.node-view-drag-content > ul {
@apply !bg-primary-selected;
}
}
div[contenteditable='false'].ProseMirror {
user-select: text !important;
}
p.is-empty::after,
h1.is-empty::after,
h2.is-empty::after,
h3.is-empty::after {
content: attr(data-placeholder);
float: left;
color: #afafaf;
pointer-events: none;
margin-top: -1.55rem;
margin-left: 0.01rem;
}
[data-one-content='true'] [data-type='collapsable_content'] {
p.is-empty::after {
content: 'Empty collapsable. Press / to open the command menu or start writing';
}
}
p.is-empty::after {
margin-top: -1.55rem;
}
h1.is-empty::after {
margin-top: -2.85rem;
}
h2.is-empty::after {
margin-top: -2.25rem;
}
h3.is-empty::after {
margin-top: -1.8rem;
}
.collapsable-wrapper {
h1,
h2,
h3 {
margin-top: 0;
margin-bottom: 0;
}
}
.editable {
.focused {
div[data-is-empty='true'] {
p::after {
content: 'Press / to open the command menu or start writing' !important;
float: left;
color: #afafaf;
pointer-events: none;
margin-top: -1.55rem;
margin-left: 0.01rem;
}
}
}
div.is-empty.focused {
p::after {
content: 'Press / to open the command menu or start writing' !important;
float: left;
color: #afafaf;
pointer-events: none;
margin-top: -1.55rem;
margin-left: 0.01rem;
}
}
}
h1.is-empty::before,
h2.is-empty::before,
h3.is-empty::before {
color: #d6d6d6;
}
.nc-docs-list-item > p {
margin-top: 0.25rem !important;
margin-bottom: 0.25rem !important;
}
p {
font-weight: 400;
color: #000000;
font-size: 1rem;
margin-top: 0.25rem;
margin-bottom: 0.25rem;
}
h1 {
font-weight: 600;
font-size: 1.85rem;
margin-bottom: 0.6em;
}
h2 {
font-weight: 600;
font-size: 1.45rem;
margin-bottom: 0.5em;
}
h3 {
font-weight: 600;
font-size: 1.15rem;
margin-bottom: 0.3em;
}
h4 {
font-size: 1.2rem;
}
h5 {
font-size: 1rem;
}
h6 {
font-size: 1rem;
}
// Pre tag is the parent wrapper for Code block
pre {
background: #f2f4f7;
border-color: #d0d5dd;
border: 1px;
color: black;
font-family: 'JetBrainsMono', monospace;
padding: 1rem;
border-radius: 0.5rem;
@apply overflow-auto mt-3;
code {
@apply !px-0;
}
}
code {
background: #f2f4f7;
@apply rounded-md px-2 py-1;
color: inherit;
font-size: 0.8rem;
}
[data-group-type='list-item'] {
@apply flex items-start;
.tiptap-list-item-start {
@apply flex mt-1.125 pr-2 !select-none;
input {
@apply mt-0.75 flex rounded-sm;
}
// Unchecked
input:not(:checked) {
// Add border to checkbox
border-width: 1.5px;
@apply border-gray-700;
}
}
}
table {
[data-group-type='list-item'] {
.tiptap-list-item-start {
@apply mt-0.125;
}
}
}
[data-type='ordered'] {
@apply flex flex-row items-start gap-x-1;
.tiptap-list-item-start > span::before {
margin-top: 6px;
content: attr(data-number) '. ';
display: inline-block;
white-space: nowrap;
}
.tiptap-list-item-content {
@apply flex flex-grow;
}
}
[data-type='image'] {
@apply mb-3
}
hr {
border: 0;
border-top: 1px solid #ccc;
margin: 1.5em 0;
}
hr.ProseMirror-selectednode {
// outline with rounded corners
outline: 4px solid #e8eafd;
border-radius: 4px;
}
.focused {
hr {
// outline with rounded corners
outline: 4px solid #e8eafd;
border-radius: 4px;
}
}
.selected {
hr {
// outline with rounded corners
outline: 4px solid #e8eafd;
border-radius: 4px;
}
}
.selected {
.external-content-wrapper {
// outline with rounded corners
outline: 2px solid #e8eafd;
border-radius: 1px;
}
}
.external-content-wrapper.ProseMirror-selectednode {
// outline with rounded corners
outline: 2px solid #e8eafd;
border-radius: 1px;
}
blockquote {
border-left: 3px solid #d0d5dd;
padding: 0 1em;
color: #666;
margin: 1em 0;
font-style: italic;
}
.column-resize-handle {
background-color: #e3e5ff !important;
width: 6px;
cursor: col-resize;
z-index: 1;
}
.resize-cursor {
cursor: ew-resize;
cursor: col-resize;
}
.external-content-wrapper {
@apply bg-gray-100 my-2;
}
div[data-type='column'] {
@apply flex flex-row gap-x-12 justify-between;
}
/**
* Table styles
*/
.tiptap-table-wrapper {
@apply !pb-4 !pt-4;
}
table {
border-collapse: collapse;
table-layout: fixed;
width: 100%;
padding-top: 2rem;
padding-bottom: 2rem;
overflow: visible;
tbody {
overflow: visible;
}
td {
.tiptap-list-item-start {
@apply -mt-1;
}
position: relative;
min-width: 1em;
border: 1px solid #e5e5e5;
overflow: visible !important;
height: 20px;
border-top: 0;
padding-left: 1rem;
padding-right: 1rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
// First row's td
tr:first-child {
td {
border-top: 1px solid #e5e5e5 !important;
background-color: #fafbfb;
p {
font-weight: 500;
}
}
}
th {
@apply font-semibold;
background-color: #fafbfb;
}
.column-resize-handle {
position: absolute;
right: -2px;
top: 0;
bottom: 0px;
margin-top: 1px;
margin-bottom: 1px;
width: 8px;
outline: 1px solid #e3e5ff;
}
p {
margin: 0;
}
.column-resize-handle {
background-color: #e3e5ff !important;
width: 3px;
cursor: col-resize;
z-index: 1;
}
}
// First cell
tr:hover > td:first-child .row-drag-handle {
display: block;
}
tr:first-child > td:hover .tiptap-column-options {
display: flex;
}
tr:first-child > td:only-child:hover .tiptap-column-options {
display: none;
}
.selectedCell {
@apply !bg-primary-selected;
// transition for white to blue background with delay as row/column creation causes cell selection for a moment which causes flicker
transition: background-color 1ms ease-out 1ms;
.tiptap-column-options {
display: none !important;
}
.row-drag-handle {
display: none !important;
}
}
.tiptap-table-cell {
@apply w-full;
}
.selected {
.callout {
@apply bg-primary-selected;
}
}
.callout {
@apply my-2.5 px-2 py-2 rounded-md;
[data-type='bullet'] {
margin-left: 0.7rem;
}
.nc-callout-emoji {
@apply text-base;
}
}
[data-bg-color='gray'] {
@apply bg-gray-100 bg-opacity-30;
}
[data-bg-color='brown'] {
@apply bg-amber-600 bg-opacity-20;
}
[data-bg-color='orange'] {
@apply bg-orange-100 bg-opacity-50;
}
[data-bg-color='yellow'] {
@apply bg-yellow-100 bg-opacity-50;
}
[data-bg-color='green'] {
@apply bg-green-100 bg-opacity-50;
}
[data-bg-color='blue'] {
@apply bg-blue-100 bg-opacity-50;
}
[data-bg-color='purple'] {
@apply bg-purple-100 bg-opacity-50;
}
[data-bg-color='pink'] {
@apply bg-pink-100 bg-opacity-50;
}
[data-bg-color='red'] {
@apply bg-red-100 bg-opacity-50;
}
}

BIN
packages/nc-gui/assets/icons/FileIconImageBox.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

18
packages/nc-gui/assets/img/404.svg

@ -0,0 +1,18 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="100" r="100" fill="#F0F1FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M100 200C155.228 200 200 155.228 200 100C200 44.7715 155.228 0 100 0C44.7715 0 0 44.7715 0 100C0 155.228 44.7715 200 100 200ZM100 185.714C147.339 185.714 185.714 147.339 185.714 100C185.714 52.6613 147.339 14.2857 100 14.2857C52.6613 14.2857 14.2857 52.6613 14.2857 100C14.2857 147.339 52.6613 185.714 100 185.714Z" fill="#D9D9D9"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M100 200C155.228 200 200 155.228 200 100C200 44.7715 155.228 0 100 0C44.7715 0 0 44.7715 0 100C0 155.228 44.7715 200 100 200ZM100 185.714C147.339 185.714 185.714 147.339 185.714 100C185.714 52.6613 147.339 14.2857 100 14.2857C52.6613 14.2857 14.2857 52.6613 14.2857 100C14.2857 147.339 52.6613 185.714 100 185.714Z" fill="url(#paint0_linear_537_994273)"/>
<path d="M75.1413 60.6389C74.2954 58.4994 77.1381 56.8582 78.568 58.6605L106.773 94.211C107.532 95.1677 107.264 96.5755 106.207 97.1861L94.6861 103.837C93.6285 104.448 92.2752 103.977 91.8262 102.841L75.1413 60.6389Z" fill="#F04438"/>
<path d="M122.146 142.054C123.576 143.856 126.419 142.215 125.573 140.075L108.888 97.8735C108.439 96.7378 107.086 96.2661 106.028 96.8768L94.5078 103.528C93.4502 104.139 93.182 105.547 93.9411 106.503L122.146 142.054Z" fill="#D0D5DD"/>
<circle cx="100.357" cy="100.357" r="9.64286" fill="#344054"/>
<path d="M91.416 39C91.3093 39 91.2187 38.968 91.144 38.904C91.08 38.8293 91.048 38.7387 91.048 38.632V36.328H85.864C85.7573 36.328 85.6667 36.296 85.592 36.232C85.528 36.1573 85.496 36.0667 85.496 35.96V35.32C85.496 35.2773 85.5067 35.2133 85.528 35.128C85.56 35.032 85.608 34.936 85.672 34.84L90.472 28.072C90.6 27.8907 90.7973 27.8 91.064 27.8H92.216C92.3227 27.8 92.408 27.8373 92.472 27.912C92.5467 27.976 92.584 28.0613 92.584 28.168V34.936H94.024C94.1413 34.936 94.232 34.9733 94.296 35.048C94.3707 35.112 94.408 35.1973 94.408 35.304V35.96C94.408 36.0667 94.3707 36.1573 94.296 36.232C94.232 36.296 94.1467 36.328 94.04 36.328H92.584V38.632C92.584 38.7387 92.5467 38.8293 92.472 38.904C92.408 38.968 92.3227 39 92.216 39H91.416ZM87.208 34.968H91.064V29.48L87.208 34.968ZM100.01 39.16C99.2735 39.16 98.6495 39.048 98.1375 38.824C97.6362 38.5893 97.2255 38.2693 96.9055 37.864C96.5855 37.4587 96.3508 36.9947 96.2015 36.472C96.0628 35.9493 95.9828 35.3947 95.9615 34.808C95.9508 34.52 95.9402 34.216 95.9295 33.896C95.9295 33.576 95.9295 33.256 95.9295 32.936C95.9402 32.6053 95.9508 32.2907 95.9615 31.992C95.9722 31.4053 96.0522 30.8507 96.2015 30.328C96.3615 29.7947 96.5962 29.3307 96.9055 28.936C97.2255 28.5307 97.6415 28.216 98.1535 27.992C98.6655 27.7573 99.2842 27.64 100.01 27.64C100.746 27.64 101.364 27.7573 101.866 27.992C102.378 28.216 102.794 28.5307 103.114 28.936C103.434 29.3307 103.668 29.7947 103.818 30.328C103.978 30.8507 104.063 31.4053 104.074 31.992C104.084 32.2907 104.09 32.6053 104.09 32.936C104.1 33.256 104.1 33.576 104.09 33.896C104.09 34.216 104.084 34.52 104.074 34.808C104.063 35.3947 103.978 35.9493 103.818 36.472C103.668 36.9947 103.434 37.4587 103.114 37.864C102.804 38.2693 102.394 38.5893 101.882 38.824C101.38 39.048 100.756 39.16 100.01 39.16ZM100.01 37.8C100.842 37.8 101.455 37.528 101.85 36.984C102.255 36.4293 102.463 35.6773 102.474 34.728C102.495 34.4187 102.506 34.12 102.506 33.832C102.506 33.5333 102.506 33.24 102.506 32.952C102.506 32.6533 102.495 32.36 102.474 32.072C102.463 31.144 102.255 30.4027 101.85 29.848C101.455 29.2827 100.842 29 100.01 29C99.1882 29 98.5748 29.2827 98.1695 29.848C97.7748 30.4027 97.5668 31.144 97.5455 32.072C97.5455 32.36 97.5402 32.6533 97.5295 32.952C97.5295 33.24 97.5295 33.5333 97.5295 33.832C97.5402 34.12 97.5455 34.4187 97.5455 34.728C97.5668 35.6773 97.7802 36.4293 98.1855 36.984C98.5908 37.528 99.1988 37.8 100.01 37.8ZM111.51 39C111.403 39 111.312 38.968 111.238 38.904C111.174 38.8293 111.142 38.7387 111.142 38.632V36.328H105.958C105.851 36.328 105.76 36.296 105.686 36.232C105.622 36.1573 105.59 36.0667 105.59 35.96V35.32C105.59 35.2773 105.6 35.2133 105.622 35.128C105.654 35.032 105.702 34.936 105.766 34.84L110.566 28.072C110.694 27.8907 110.891 27.8 111.158 27.8H112.31C112.416 27.8 112.502 27.8373 112.566 27.912C112.64 27.976 112.678 28.0613 112.678 28.168V34.936H114.118C114.235 34.936 114.326 34.9733 114.39 35.048C114.464 35.112 114.502 35.1973 114.502 35.304V35.96C114.502 36.0667 114.464 36.1573 114.39 36.232C114.326 36.296 114.24 36.328 114.134 36.328H112.678V38.632C112.678 38.7387 112.64 38.8293 112.566 38.904C112.502 38.968 112.416 39 112.31 39H111.51ZM107.302 34.968H111.158V29.48L107.302 34.968Z" fill="#344054"/>
<path d="M27.8447 106.429C27.738 106.429 27.6474 106.397 27.5727 106.333C27.5087 106.258 27.4767 106.167 27.4767 106.061V103.757H22.2927C22.186 103.757 22.0954 103.725 22.0207 103.661C21.9567 103.586 21.9247 103.495 21.9247 103.389V102.749C21.9247 102.706 21.9354 102.642 21.9567 102.557C21.9887 102.461 22.0367 102.365 22.1007 102.269L26.9007 95.5006C27.0287 95.3193 27.226 95.2286 27.4927 95.2286H28.6447C28.7514 95.2286 28.8367 95.2659 28.9007 95.3406C28.9754 95.4046 29.0127 95.4899 29.0127 95.5966V102.365H30.4527C30.57 102.365 30.6607 102.402 30.7247 102.477C30.7994 102.541 30.8367 102.626 30.8367 102.733V103.389C30.8367 103.495 30.7994 103.586 30.7247 103.661C30.6607 103.725 30.5754 103.757 30.4687 103.757H29.0127V106.061C29.0127 106.167 28.9754 106.258 28.9007 106.333C28.8367 106.397 28.7514 106.429 28.6447 106.429H27.8447ZM23.6367 102.397H27.4927V96.9086L23.6367 102.397ZM36.4382 106.589C35.7022 106.589 35.0782 106.477 34.5662 106.253C34.0649 106.018 33.6542 105.698 33.3342 105.293C33.0142 104.887 32.7795 104.423 32.6302 103.901C32.4915 103.378 32.4115 102.823 32.3902 102.237C32.3795 101.949 32.3689 101.645 32.3582 101.325C32.3582 101.005 32.3582 100.685 32.3582 100.365C32.3689 100.034 32.3795 99.7193 32.3902 99.4206C32.4009 98.8339 32.4809 98.2793 32.6302 97.7566C32.7902 97.2233 33.0249 96.7593 33.3342 96.3646C33.6542 95.9593 34.0702 95.6446 34.5822 95.4206C35.0942 95.1859 35.7129 95.0686 36.4382 95.0686C37.1742 95.0686 37.7929 95.1859 38.2942 95.4206C38.8062 95.6446 39.2222 95.9593 39.5422 96.3646C39.8622 96.7593 40.0969 97.2233 40.2462 97.7566C40.4062 98.2793 40.4915 98.8339 40.5022 99.4206C40.5129 99.7193 40.5182 100.034 40.5182 100.365C40.5289 100.685 40.5289 101.005 40.5182 101.325C40.5182 101.645 40.5129 101.949 40.5022 102.237C40.4915 102.823 40.4062 103.378 40.2462 103.901C40.0969 104.423 39.8622 104.887 39.5422 105.293C39.2329 105.698 38.8222 106.018 38.3102 106.253C37.8089 106.477 37.1849 106.589 36.4382 106.589ZM36.4382 105.229C37.2702 105.229 37.8835 104.957 38.2782 104.413C38.6835 103.858 38.8915 103.106 38.9022 102.157C38.9235 101.847 38.9342 101.549 38.9342 101.261C38.9342 100.962 38.9342 100.669 38.9342 100.381C38.9342 100.082 38.9235 99.7886 38.9022 99.5006C38.8915 98.5726 38.6835 97.8313 38.2782 97.2766C37.8835 96.7113 37.2702 96.4286 36.4382 96.4286C35.6169 96.4286 35.0035 96.7113 34.5982 97.2766C34.2035 97.8313 33.9955 98.5726 33.9742 99.5006C33.9742 99.7886 33.9689 100.082 33.9582 100.381C33.9582 100.669 33.9582 100.962 33.9582 101.261C33.9689 101.549 33.9742 101.847 33.9742 102.157C33.9955 103.106 34.2089 103.858 34.6142 104.413C35.0195 104.957 35.6275 105.229 36.4382 105.229ZM47.9385 106.429C47.8318 106.429 47.7411 106.397 47.6665 106.333C47.6025 106.258 47.5705 106.167 47.5705 106.061V103.757H42.3865C42.2798 103.757 42.1891 103.725 42.1145 103.661C42.0505 103.586 42.0185 103.495 42.0185 103.389V102.749C42.0185 102.706 42.0291 102.642 42.0505 102.557C42.0825 102.461 42.1305 102.365 42.1945 102.269L46.9945 95.5006C47.1225 95.3193 47.3198 95.2286 47.5865 95.2286H48.7385C48.8451 95.2286 48.9305 95.2659 48.9945 95.3406C49.0691 95.4046 49.1065 95.4899 49.1065 95.5966V102.365H50.5465C50.6638 102.365 50.7545 102.402 50.8185 102.477C50.8931 102.541 50.9305 102.626 50.9305 102.733V103.389C50.9305 103.495 50.8931 103.586 50.8185 103.661C50.7545 103.725 50.6691 103.757 50.5625 103.757H49.1065V106.061C49.1065 106.167 49.0691 106.258 48.9945 106.333C48.9305 106.397 48.8451 106.429 48.7385 106.429H47.9385ZM43.7305 102.397H47.5865V96.9086L43.7305 102.397Z" fill="#344054"/>
<path d="M91.416 176C91.3093 176 91.2187 175.968 91.144 175.904C91.08 175.829 91.048 175.739 91.048 175.632V173.328H85.864C85.7573 173.328 85.6667 173.296 85.592 173.232C85.528 173.157 85.496 173.067 85.496 172.96V172.32C85.496 172.277 85.5067 172.213 85.528 172.128C85.56 172.032 85.608 171.936 85.672 171.84L90.472 165.072C90.6 164.891 90.7973 164.8 91.064 164.8H92.216C92.3227 164.8 92.408 164.837 92.472 164.912C92.5467 164.976 92.584 165.061 92.584 165.168V171.936H94.024C94.1413 171.936 94.232 171.973 94.296 172.048C94.3707 172.112 94.408 172.197 94.408 172.304V172.96C94.408 173.067 94.3707 173.157 94.296 173.232C94.232 173.296 94.1467 173.328 94.04 173.328H92.584V175.632C92.584 175.739 92.5467 175.829 92.472 175.904C92.408 175.968 92.3227 176 92.216 176H91.416ZM87.208 171.968H91.064V166.48L87.208 171.968ZM100.01 176.16C99.2735 176.16 98.6495 176.048 98.1375 175.824C97.6362 175.589 97.2255 175.269 96.9055 174.864C96.5855 174.459 96.3508 173.995 96.2015 173.472C96.0628 172.949 95.9828 172.395 95.9615 171.808C95.9508 171.52 95.9402 171.216 95.9295 170.896C95.9295 170.576 95.9295 170.256 95.9295 169.936C95.9402 169.605 95.9508 169.291 95.9615 168.992C95.9722 168.405 96.0522 167.851 96.2015 167.328C96.3615 166.795 96.5962 166.331 96.9055 165.936C97.2255 165.531 97.6415 165.216 98.1535 164.992C98.6655 164.757 99.2842 164.64 100.01 164.64C100.746 164.64 101.364 164.757 101.866 164.992C102.378 165.216 102.794 165.531 103.114 165.936C103.434 166.331 103.668 166.795 103.818 167.328C103.978 167.851 104.063 168.405 104.074 168.992C104.084 169.291 104.09 169.605 104.09 169.936C104.1 170.256 104.1 170.576 104.09 170.896C104.09 171.216 104.084 171.52 104.074 171.808C104.063 172.395 103.978 172.949 103.818 173.472C103.668 173.995 103.434 174.459 103.114 174.864C102.804 175.269 102.394 175.589 101.882 175.824C101.38 176.048 100.756 176.16 100.01 176.16ZM100.01 174.8C100.842 174.8 101.455 174.528 101.85 173.984C102.255 173.429 102.463 172.677 102.474 171.728C102.495 171.419 102.506 171.12 102.506 170.832C102.506 170.533 102.506 170.24 102.506 169.952C102.506 169.653 102.495 169.36 102.474 169.072C102.463 168.144 102.255 167.403 101.85 166.848C101.455 166.283 100.842 166 100.01 166C99.1882 166 98.5748 166.283 98.1695 166.848C97.7748 167.403 97.5668 168.144 97.5455 169.072C97.5455 169.36 97.5402 169.653 97.5295 169.952C97.5295 170.24 97.5295 170.533 97.5295 170.832C97.5402 171.12 97.5455 171.419 97.5455 171.728C97.5668 172.677 97.7802 173.429 98.1855 173.984C98.5908 174.528 99.1988 174.8 100.01 174.8ZM111.51 176C111.403 176 111.312 175.968 111.238 175.904C111.174 175.829 111.142 175.739 111.142 175.632V173.328H105.958C105.851 173.328 105.76 173.296 105.686 173.232C105.622 173.157 105.59 173.067 105.59 172.96V172.32C105.59 172.277 105.6 172.213 105.622 172.128C105.654 172.032 105.702 171.936 105.766 171.84L110.566 165.072C110.694 164.891 110.891 164.8 111.158 164.8H112.31C112.416 164.8 112.502 164.837 112.566 164.912C112.64 164.976 112.678 165.061 112.678 165.168V171.936H114.118C114.235 171.936 114.326 171.973 114.39 172.048C114.464 172.112 114.502 172.197 114.502 172.304V172.96C114.502 173.067 114.464 173.157 114.39 173.232C114.326 173.296 114.24 173.328 114.134 173.328H112.678V175.632C112.678 175.739 112.64 175.829 112.566 175.904C112.502 175.968 112.416 176 112.31 176H111.51ZM107.302 171.968H111.158V166.48L107.302 171.968Z" fill="#344054"/>
<path d="M154.416 106C154.309 106 154.219 105.968 154.144 105.904C154.08 105.829 154.048 105.739 154.048 105.632V103.328H148.864C148.757 103.328 148.667 103.296 148.592 103.232C148.528 103.157 148.496 103.067 148.496 102.96V102.32C148.496 102.277 148.507 102.213 148.528 102.128C148.56 102.032 148.608 101.936 148.672 101.84L153.472 95.072C153.6 94.8907 153.797 94.8 154.064 94.8H155.216C155.323 94.8 155.408 94.8373 155.472 94.912C155.547 94.976 155.584 95.0613 155.584 95.168V101.936H157.024C157.141 101.936 157.232 101.973 157.296 102.048C157.371 102.112 157.408 102.197 157.408 102.304V102.96C157.408 103.067 157.371 103.157 157.296 103.232C157.232 103.296 157.147 103.328 157.04 103.328H155.584V105.632C155.584 105.739 155.547 105.829 155.472 105.904C155.408 105.968 155.323 106 155.216 106H154.416ZM150.208 101.968H154.064V96.48L150.208 101.968ZM163.01 106.16C162.274 106.16 161.65 106.048 161.138 105.824C160.636 105.589 160.226 105.269 159.906 104.864C159.586 104.459 159.351 103.995 159.202 103.472C159.063 102.949 158.983 102.395 158.962 101.808C158.951 101.52 158.94 101.216 158.93 100.896C158.93 100.576 158.93 100.256 158.93 99.936C158.94 99.6053 158.951 99.2907 158.962 98.992C158.972 98.4053 159.052 97.8507 159.202 97.328C159.362 96.7947 159.596 96.3307 159.906 95.936C160.226 95.5307 160.642 95.216 161.154 94.992C161.666 94.7573 162.284 94.64 163.01 94.64C163.746 94.64 164.364 94.7573 164.866 94.992C165.378 95.216 165.794 95.5307 166.114 95.936C166.434 96.3307 166.668 96.7947 166.818 97.328C166.978 97.8507 167.063 98.4053 167.074 98.992C167.084 99.2907 167.09 99.6053 167.09 99.936C167.1 100.256 167.1 100.576 167.09 100.896C167.09 101.216 167.084 101.52 167.074 101.808C167.063 102.395 166.978 102.949 166.818 103.472C166.668 103.995 166.434 104.459 166.114 104.864C165.804 105.269 165.394 105.589 164.882 105.824C164.38 106.048 163.756 106.16 163.01 106.16ZM163.01 104.8C163.842 104.8 164.455 104.528 164.85 103.984C165.255 103.429 165.463 102.677 165.474 101.728C165.495 101.419 165.506 101.12 165.506 100.832C165.506 100.533 165.506 100.24 165.506 99.952C165.506 99.6533 165.495 99.36 165.474 99.072C165.463 98.144 165.255 97.4027 164.85 96.848C164.455 96.2827 163.842 96 163.01 96C162.188 96 161.575 96.2827 161.17 96.848C160.775 97.4027 160.567 98.144 160.546 99.072C160.546 99.36 160.54 99.6533 160.53 99.952C160.53 100.24 160.53 100.533 160.53 100.832C160.54 101.12 160.546 101.419 160.546 101.728C160.567 102.677 160.78 103.429 161.186 103.984C161.591 104.528 162.199 104.8 163.01 104.8ZM174.51 106C174.403 106 174.312 105.968 174.238 105.904C174.174 105.829 174.142 105.739 174.142 105.632V103.328H168.958C168.851 103.328 168.76 103.296 168.686 103.232C168.622 103.157 168.59 103.067 168.59 102.96V102.32C168.59 102.277 168.6 102.213 168.622 102.128C168.654 102.032 168.702 101.936 168.766 101.84L173.566 95.072C173.694 94.8907 173.891 94.8 174.158 94.8H175.31C175.416 94.8 175.502 94.8373 175.566 94.912C175.64 94.976 175.678 95.0613 175.678 95.168V101.936H177.118C177.235 101.936 177.326 101.973 177.39 102.048C177.464 102.112 177.502 102.197 177.502 102.304V102.96C177.502 103.067 177.464 103.157 177.39 103.232C177.326 103.296 177.24 103.328 177.134 103.328H175.678V105.632C175.678 105.739 175.64 105.829 175.566 105.904C175.502 105.968 175.416 106 175.31 106H174.51ZM170.302 101.968H174.158V96.48L170.302 101.968Z" fill="#344054"/>
<defs>
<linearGradient id="paint0_linear_537_994273" x1="40.189" y1="159.526" x2="222.529" y2="-21.9425" gradientUnits="userSpaceOnUse">
<stop stop-color="#4351E8"/>
<stop offset="1" stop-color="#2A1EA5"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

3
packages/nc-gui/assets/img/add-page.svg

@ -0,0 +1,3 @@
<svg width="41" height="40" viewBox="0 0 41 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 36C2.16667 36 1.45833 35.7083 0.875 35.125C0.291667 34.5417 0 33.8333 0 33V3C0 2.16667 0.291667 1.45833 0.875 0.875C1.45833 0.291667 2.16667 0 3 0H33C33.8333 0 34.5417 0.291667 35.125 0.875C35.7083 1.45833 36 2.16667 36 3V19.45C35.5333 19.1833 35.0417 18.9667 34.525 18.8C34.0083 18.6333 33.5 18.5 33 18.4V3H3V33H18.45C18.5833 33.5333 18.7333 34.05 18.9 34.55C19.0667 35.05 19.2667 35.5333 19.5 36H3ZM3 33V3V18.4V18.2V33ZM8 26.5C8 26.9333 8.14167 27.2917 8.425 27.575C8.70833 27.8583 9.06667 28 9.5 28H18.55C18.6833 27.4667 18.8333 26.95 19 26.45C19.1667 25.95 19.3833 25.4667 19.65 25H9.5C9.06667 25 8.70833 25.1417 8.425 25.425C8.14167 25.7083 8 26.0667 8 26.5ZM8 18C8 18.4333 8.14167 18.7917 8.425 19.075C8.70833 19.3583 9.06667 19.5 9.5 19.5H25.2C25.6667 19.2667 26.1167 19.075 26.55 18.925C26.9833 18.775 27.4667 18.6333 28 18.5V18C28 17.6 27.85 17.25 27.55 16.95C27.25 16.65 26.9 16.5 26.5 16.5H9.5C9.06667 16.5 8.70833 16.6417 8.425 16.925C8.14167 17.2083 8 17.5667 8 18ZM8 9.5C8 9.93333 8.14167 10.2917 8.425 10.575C8.70833 10.8583 9.06667 11 9.5 11H26.5C26.9333 11 27.2917 10.8583 27.575 10.575C27.8583 10.2917 28 9.93333 28 9.5C28 9.06667 27.8583 8.70833 27.575 8.425C27.2917 8.14167 26.9333 8 26.5 8H9.5C9.06667 8 8.70833 8.14167 8.425 8.425C8.14167 8.70833 8 9.06667 8 9.5ZM30.65 39.95C28.05 39.95 25.8333 39.025 24 37.175C22.1667 35.325 21.25 33.1333 21.25 30.6C21.25 28 22.1667 25.775 24 23.925C25.8333 22.075 28.05 21.15 30.65 21.15C33.2167 21.15 35.425 22.075 37.275 23.925C39.125 25.775 40.05 28 40.05 30.6C40.05 33.1333 39.125 35.325 37.275 37.175C35.425 39.025 33.2167 39.95 30.65 39.95ZM29.9 31.45V36.15C29.9 36.3833 29.9833 36.575 30.15 36.725C30.3167 36.875 30.5167 36.95 30.75 36.95C30.9833 36.95 31.175 36.8667 31.325 36.7C31.475 36.5333 31.55 36.3333 31.55 36.1V31.45H36.25C36.4833 31.45 36.675 31.3667 36.825 31.2C36.975 31.0333 37.05 30.8333 37.05 30.6C37.05 30.3667 36.975 30.175 36.825 30.025C36.675 29.875 36.4833 29.8 36.25 29.8H31.55V25.1C31.55 24.8667 31.475 24.675 31.325 24.525C31.175 24.375 30.9833 24.3 30.75 24.3C30.5167 24.3 30.3167 24.375 30.15 24.525C29.9833 24.675 29.9 24.8667 29.9 25.1V29.8H25.2C24.9667 29.8 24.775 29.875 24.625 30.025C24.475 30.175 24.4 30.3667 24.4 30.6C24.4 30.8333 24.4833 31.0333 24.65 31.2C24.8167 31.3667 25.0167 31.45 25.25 31.45H29.9Z" fill="#1C26B8"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
packages/nc-gui/assets/img/brand/nocodb-full.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

26
packages/nc-gui/assets/img/clickup-icon.svg

@ -0,0 +1,26 @@
<svg width="185" height="185" viewBox="0 0 185 185" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d)">
<rect x="30" y="20" width="125" height="125" rx="30" fill="white"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M55.8789 105.714L69.3974 95.3593C76.5762 104.732 84.1998 109.051 92.6948 109.051C101.143 109.051 108.557 104.781 115.414 95.4832L129.119 105.59C119.232 118.996 106.932 126.079 92.6948 126.079C78.5049 126.079 66.0907 119.046 55.8789 105.714Z" fill="url(#paint0_linear)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M92.6491 60.7078L68.5883 81.4406L57.4727 68.5407L92.6969 38.1885L127.647 68.5644L116.477 81.417L92.6491 60.7078Z" fill="url(#paint1_linear)"/>
<defs>
<filter id="filter0_d" x="0" y="0" width="185" height="185" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dy="10"/>
<feGaussianBlur stdDeviation="15"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.0627451 0 0 0 0 0.117647 0 0 0 0 0.211765 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
<linearGradient id="paint0_linear" x1="55.8789" y1="116.251" x2="129.119" y2="116.251" gradientUnits="userSpaceOnUse">
<stop stop-color="#8930FD"/>
<stop offset="1" stop-color="#49CCF9"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="57.4727" y1="67.6025" x2="127.647" y2="67.6025" gradientUnits="userSpaceOnUse">
<stop stop-color="#FF02F0"/>
<stop offset="1" stop-color="#FFC800"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

16
packages/nc-gui/assets/img/dashboards/layout-visualizer/gap.svg

@ -0,0 +1,16 @@
<svg width="356" height="94" viewBox="0 0 356 94" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_826_23423)">
<rect x="13" y="4" width="330" height="86" rx="8" fill="white"/>
<path d="M158.83 31H156.614L163.023 13.5455H165.205L171.614 31H169.398L164.182 16.3068H164.045L158.83 31ZM159.648 24.1818H168.58V26.0568H159.648V24.1818ZM173.852 31V13.5455H175.864V19.9886H176.034C176.182 19.7614 176.386 19.4716 176.648 19.1193C176.915 18.7614 177.296 18.4432 177.79 18.1648C178.29 17.8807 178.966 17.7386 179.818 17.7386C180.921 17.7386 181.892 18.0142 182.733 18.5653C183.574 19.1165 184.23 19.8977 184.702 20.9091C185.173 21.9205 185.409 23.1136 185.409 24.4886C185.409 25.875 185.173 27.0767 184.702 28.0938C184.23 29.1051 183.577 29.8892 182.742 30.446C181.906 30.9972 180.943 31.2727 179.852 31.2727C179.011 31.2727 178.338 31.1335 177.833 30.8551C177.327 30.571 176.938 30.25 176.665 29.892C176.392 29.5284 176.182 29.2273 176.034 28.9886H175.796V31H173.852ZM175.83 24.4545C175.83 25.4432 175.975 26.3153 176.264 27.071C176.554 27.821 176.977 28.4091 177.534 28.8352C178.091 29.2557 178.773 29.4659 179.58 29.4659C180.421 29.4659 181.122 29.2443 181.685 28.8011C182.253 28.3523 182.679 27.75 182.963 26.9943C183.253 26.233 183.398 25.3864 183.398 24.4545C183.398 23.5341 183.256 22.7045 182.972 21.9659C182.693 21.2216 182.27 20.6335 181.702 20.2017C181.139 19.7642 180.432 19.5455 179.58 19.5455C178.761 19.5455 178.074 19.7528 177.517 20.1676C176.96 20.5767 176.54 21.1506 176.256 21.8892C175.972 22.6222 175.83 23.4773 175.83 24.4545ZM193.324 31.2727C192.097 31.2727 191.04 30.983 190.154 30.4034C189.267 29.8239 188.585 29.0256 188.108 28.0085C187.631 26.9915 187.392 25.8295 187.392 24.5227C187.392 23.1932 187.637 22.0199 188.125 21.0028C188.62 19.9801 189.307 19.1818 190.188 18.608C191.074 18.0284 192.108 17.7386 193.29 17.7386C194.21 17.7386 195.04 17.9091 195.779 18.25C196.517 18.5909 197.122 19.0682 197.594 19.6818C198.066 20.2955 198.358 21.0114 198.472 21.8295H196.46C196.307 21.233 195.966 20.7045 195.438 20.2443C194.915 19.7784 194.21 19.5455 193.324 19.5455C192.54 19.5455 191.853 19.75 191.262 20.1591C190.676 20.5625 190.219 21.1335 189.889 21.8722C189.566 22.6051 189.404 23.4659 189.404 24.4545C189.404 25.4659 189.563 26.3466 189.881 27.0966C190.205 27.8466 190.659 28.429 191.245 28.8438C191.835 29.2585 192.529 29.4659 193.324 29.4659C193.847 29.4659 194.321 29.375 194.747 29.1932C195.174 29.0114 195.534 28.75 195.83 28.4091C196.125 28.0682 196.335 27.6591 196.46 27.1818H198.472C198.358 27.9545 198.077 28.6506 197.628 29.2699C197.185 29.8835 196.597 30.3722 195.864 30.7358C195.137 31.0938 194.29 31.2727 193.324 31.2727Z" fill="black"/>
<rect width="370" height="1" transform="translate(-7 40)" fill="#CA2E9E"/>
<rect width="370" height="12" transform="translate(-7 41)" fill="#FEB0E8"/>
<rect width="370" height="1" transform="translate(-7 53)" fill="#CA2E9E"/>
<path d="M158.83 81H156.614L163.023 63.5455H165.205L171.614 81H169.398L164.182 66.3068H164.045L158.83 81ZM159.648 74.1818H168.58V76.0568H159.648V74.1818ZM173.852 81V63.5455H175.864V69.9886H176.034C176.182 69.7614 176.386 69.4716 176.648 69.1193C176.915 68.7614 177.296 68.4432 177.79 68.1648C178.29 67.8807 178.966 67.7386 179.818 67.7386C180.921 67.7386 181.892 68.0142 182.733 68.5653C183.574 69.1165 184.23 69.8977 184.702 70.9091C185.173 71.9205 185.409 73.1136 185.409 74.4886C185.409 75.875 185.173 77.0767 184.702 78.0938C184.23 79.1051 183.577 79.8892 182.742 80.446C181.906 80.9972 180.943 81.2727 179.852 81.2727C179.011 81.2727 178.338 81.1335 177.833 80.8551C177.327 80.571 176.938 80.25 176.665 79.892C176.392 79.5284 176.182 79.2273 176.034 78.9886H175.796V81H173.852ZM175.83 74.4545C175.83 75.4432 175.975 76.3153 176.264 77.071C176.554 77.821 176.977 78.4091 177.534 78.8352C178.091 79.2557 178.773 79.4659 179.58 79.4659C180.421 79.4659 181.122 79.2443 181.685 78.8011C182.253 78.3523 182.679 77.75 182.963 76.9943C183.253 76.233 183.398 75.3864 183.398 74.4545C183.398 73.5341 183.256 72.7045 182.972 71.9659C182.693 71.2216 182.27 70.6335 181.702 70.2017C181.139 69.7642 180.432 69.5455 179.58 69.5455C178.761 69.5455 178.074 69.7528 177.517 70.1676C176.96 70.5767 176.54 71.1506 176.256 71.8892C175.972 72.6222 175.83 73.4773 175.83 74.4545ZM193.324 81.2727C192.097 81.2727 191.04 80.983 190.154 80.4034C189.267 79.8239 188.585 79.0256 188.108 78.0085C187.631 76.9915 187.392 75.8295 187.392 74.5227C187.392 73.1932 187.637 72.0199 188.125 71.0028C188.62 69.9801 189.307 69.1818 190.188 68.608C191.074 68.0284 192.108 67.7386 193.29 67.7386C194.21 67.7386 195.04 67.9091 195.779 68.25C196.517 68.5909 197.122 69.0682 197.594 69.6818C198.066 70.2955 198.358 71.0114 198.472 71.8295H196.46C196.307 71.233 195.966 70.7045 195.438 70.2443C194.915 69.7784 194.21 69.5455 193.324 69.5455C192.54 69.5455 191.853 69.75 191.262 70.1591C190.676 70.5625 190.219 71.1335 189.889 71.8722C189.566 72.6051 189.404 73.4659 189.404 74.4545C189.404 75.4659 189.563 76.3466 189.881 77.0966C190.205 77.8466 190.659 78.429 191.245 78.8438C191.835 79.2585 192.529 79.4659 193.324 79.4659C193.847 79.4659 194.321 79.375 194.747 79.1932C195.174 79.0114 195.534 78.75 195.83 78.4091C196.125 78.0682 196.335 77.6591 196.46 77.1818H198.472C198.358 77.9545 198.077 78.6506 197.628 79.2699C197.185 79.8835 196.597 80.3722 195.864 80.7358C195.137 81.0938 194.29 81.2727 193.324 81.2727Z" fill="black"/>
</g>
<rect x="0.5" y="0.5" width="355" height="93" rx="3.5" stroke="#C4C7CC"/>
<defs>
<clipPath id="clip0_826_23423">
<rect width="356" height="94" rx="4" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.5 KiB

20
packages/nc-gui/assets/img/dashboards/layout-visualizer/horizontal.svg

@ -0,0 +1,20 @@
<svg width="356" height="94" viewBox="0 0 356 94" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_826_23379)">
<g opacity="0.5">
<rect width="12" height="94" fill="#FEB0E8"/>
</g>
<rect width="1" height="94" transform="translate(12)" fill="#CA2E9E"/>
<rect x="13" width="330" height="94" rx="8" fill="white"/>
<path d="M158.83 56H156.614L163.023 38.5455H165.205L171.614 56H169.398L164.182 41.3068H164.045L158.83 56ZM159.648 49.1818H168.58V51.0568H159.648V49.1818ZM173.852 56V38.5455H175.864V44.9886H176.034C176.182 44.7614 176.386 44.4716 176.648 44.1193C176.915 43.7614 177.296 43.4432 177.79 43.1648C178.29 42.8807 178.966 42.7386 179.818 42.7386C180.921 42.7386 181.892 43.0142 182.733 43.5653C183.574 44.1165 184.23 44.8977 184.702 45.9091C185.173 46.9205 185.409 48.1136 185.409 49.4886C185.409 50.875 185.173 52.0767 184.702 53.0938C184.23 54.1051 183.577 54.8892 182.742 55.446C181.906 55.9972 180.943 56.2727 179.852 56.2727C179.011 56.2727 178.338 56.1335 177.833 55.8551C177.327 55.571 176.938 55.25 176.665 54.892C176.392 54.5284 176.182 54.2273 176.034 53.9886H175.796V56H173.852ZM175.83 49.4545C175.83 50.4432 175.975 51.3153 176.264 52.071C176.554 52.821 176.977 53.4091 177.534 53.8352C178.091 54.2557 178.773 54.4659 179.58 54.4659C180.421 54.4659 181.122 54.2443 181.685 53.8011C182.253 53.3523 182.679 52.75 182.963 51.9943C183.253 51.233 183.398 50.3864 183.398 49.4545C183.398 48.5341 183.256 47.7045 182.972 46.9659C182.693 46.2216 182.27 45.6335 181.702 45.2017C181.139 44.7642 180.432 44.5455 179.58 44.5455C178.761 44.5455 178.074 44.7528 177.517 45.1676C176.96 45.5767 176.54 46.1506 176.256 46.8892C175.972 47.6222 175.83 48.4773 175.83 49.4545ZM193.324 56.2727C192.097 56.2727 191.04 55.983 190.154 55.4034C189.267 54.8239 188.585 54.0256 188.108 53.0085C187.631 51.9915 187.392 50.8295 187.392 49.5227C187.392 48.1932 187.637 47.0199 188.125 46.0028C188.62 44.9801 189.307 44.1818 190.188 43.608C191.074 43.0284 192.108 42.7386 193.29 42.7386C194.21 42.7386 195.04 42.9091 195.779 43.25C196.517 43.5909 197.122 44.0682 197.594 44.6818C198.066 45.2955 198.358 46.0114 198.472 46.8295H196.46C196.307 46.233 195.966 45.7045 195.438 45.2443C194.915 44.7784 194.21 44.5455 193.324 44.5455C192.54 44.5455 191.853 44.75 191.262 45.1591C190.676 45.5625 190.219 46.1335 189.889 46.8722C189.566 47.6051 189.404 48.4659 189.404 49.4545C189.404 50.4659 189.563 51.3466 189.881 52.0966C190.205 52.8466 190.659 53.429 191.245 53.8438C191.835 54.2585 192.529 54.4659 193.324 54.4659C193.847 54.4659 194.321 54.375 194.747 54.1932C195.174 54.0114 195.534 53.75 195.83 53.4091C196.125 53.0682 196.335 52.6591 196.46 52.1818H198.472C198.358 52.9545 198.077 53.6506 197.628 54.2699C197.185 54.8835 196.597 55.3722 195.864 55.7358C195.137 56.0938 194.29 56.2727 193.324 56.2727Z" fill="black"/>
<rect width="1" height="94" transform="translate(343)" fill="#CA2E9E"/>
<g opacity="0.5">
<rect width="12" height="94" transform="translate(344)" fill="#FEB0E8"/>
</g>
</g>
<rect x="0.5" y="0.5" width="355" height="93" rx="3.5" stroke="#C4C7CC"/>
<defs>
<clipPath id="clip0_826_23379">
<rect width="356" height="94" rx="4" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

28
packages/nc-gui/assets/img/dashboards/layout-visualizer/none.svg

@ -0,0 +1,28 @@
<svg width="356" height="94" viewBox="0 0 356 94" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_415_4198)">
<g opacity="0.5">
<rect width="12" height="94" fill="#FEB0E8"/>
</g>
<rect width="1" height="94" transform="translate(12)" fill="#CA2E9E"/>
<rect x="13" width="330" height="94" rx="8" fill="white"/>
<g opacity="0.5">
<rect width="370" height="12" transform="translate(-7)" fill="#FEB0E8"/>
</g>
<rect width="370" height="1" transform="translate(-7 12)" fill="#CA2E9E"/>
<path d="M158.83 56H156.614L163.023 38.5455H165.205L171.614 56H169.398L164.182 41.3068H164.045L158.83 56ZM159.648 49.1818H168.58V51.0568H159.648V49.1818ZM173.852 56V38.5455H175.864V44.9886H176.034C176.182 44.7614 176.386 44.4716 176.648 44.1193C176.915 43.7614 177.296 43.4432 177.79 43.1648C178.29 42.8807 178.966 42.7386 179.818 42.7386C180.921 42.7386 181.892 43.0142 182.733 43.5653C183.574 44.1165 184.23 44.8977 184.702 45.9091C185.173 46.9205 185.409 48.1136 185.409 49.4886C185.409 50.875 185.173 52.0767 184.702 53.0938C184.23 54.1051 183.577 54.8892 182.742 55.446C181.906 55.9972 180.943 56.2727 179.852 56.2727C179.011 56.2727 178.338 56.1335 177.833 55.8551C177.327 55.571 176.938 55.25 176.665 54.892C176.392 54.5284 176.182 54.2273 176.034 53.9886H175.796V56H173.852ZM175.83 49.4545C175.83 50.4432 175.975 51.3153 176.264 52.071C176.554 52.821 176.977 53.4091 177.534 53.8352C178.091 54.2557 178.773 54.4659 179.58 54.4659C180.421 54.4659 181.122 54.2443 181.685 53.8011C182.253 53.3523 182.679 52.75 182.963 51.9943C183.253 51.233 183.398 50.3864 183.398 49.4545C183.398 48.5341 183.256 47.7045 182.972 46.9659C182.693 46.2216 182.27 45.6335 181.702 45.2017C181.139 44.7642 180.432 44.5455 179.58 44.5455C178.761 44.5455 178.074 44.7528 177.517 45.1676C176.96 45.5767 176.54 46.1506 176.256 46.8892C175.972 47.6222 175.83 48.4773 175.83 49.4545ZM193.324 56.2727C192.097 56.2727 191.04 55.983 190.154 55.4034C189.267 54.8239 188.585 54.0256 188.108 53.0085C187.631 51.9915 187.392 50.8295 187.392 49.5227C187.392 48.1932 187.637 47.0199 188.125 46.0028C188.62 44.9801 189.307 44.1818 190.188 43.608C191.074 43.0284 192.108 42.7386 193.29 42.7386C194.21 42.7386 195.04 42.9091 195.779 43.25C196.517 43.5909 197.122 44.0682 197.594 44.6818C198.066 45.2955 198.358 46.0114 198.472 46.8295H196.46C196.307 46.233 195.966 45.7045 195.438 45.2443C194.915 44.7784 194.21 44.5455 193.324 44.5455C192.54 44.5455 191.853 44.75 191.262 45.1591C190.676 45.5625 190.219 46.1335 189.889 46.8722C189.566 47.6051 189.404 48.4659 189.404 49.4545C189.404 50.4659 189.563 51.3466 189.881 52.0966C190.205 52.8466 190.659 53.429 191.245 53.8438C191.835 54.2585 192.529 54.4659 193.324 54.4659C193.847 54.4659 194.321 54.375 194.747 54.1932C195.174 54.0114 195.534 53.75 195.83 53.4091C196.125 53.0682 196.335 52.6591 196.46 52.1818H198.472C198.358 52.9545 198.077 53.6506 197.628 54.2699C197.185 54.8835 196.597 55.3722 195.864 55.7358C195.137 56.0938 194.29 56.2727 193.324 56.2727Z" fill="black"/>
<rect width="370" height="1" transform="translate(-7 81)" fill="#CA2E9E"/>
<g opacity="0.5">
<rect width="370" height="12" transform="translate(-7 82)" fill="#FEB0E8"/>
</g>
<rect width="1" height="94" transform="translate(343)" fill="#CA2E9E"/>
<g opacity="0.5">
<rect width="12" height="94" transform="translate(344)" fill="#FEB0E8"/>
</g>
</g>
<rect x="0.5" y="0.5" width="355" height="93" rx="3.5" stroke="#C4C7CC"/>
<defs>
<clipPath id="clip0_415_4198">
<rect width="356" height="94" rx="4" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

20
packages/nc-gui/assets/img/dashboards/layout-visualizer/vertical.svg

@ -0,0 +1,20 @@
<svg width="356" height="94" viewBox="0 0 356 94" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_826_23321)">
<rect x="13" width="330" height="94" rx="8" fill="white"/>
<g opacity="0.5">
<rect width="370" height="12" transform="translate(-7)" fill="#FEB0E8"/>
</g>
<rect width="370" height="1" transform="translate(-7 12)" fill="#CA2E9E"/>
<path d="M158.83 56H156.614L163.023 38.5455H165.205L171.614 56H169.398L164.182 41.3068H164.045L158.83 56ZM159.648 49.1818H168.58V51.0568H159.648V49.1818ZM173.852 56V38.5455H175.864V44.9886H176.034C176.182 44.7614 176.386 44.4716 176.648 44.1193C176.915 43.7614 177.296 43.4432 177.79 43.1648C178.29 42.8807 178.966 42.7386 179.818 42.7386C180.921 42.7386 181.892 43.0142 182.733 43.5653C183.574 44.1165 184.23 44.8977 184.702 45.9091C185.173 46.9205 185.409 48.1136 185.409 49.4886C185.409 50.875 185.173 52.0767 184.702 53.0938C184.23 54.1051 183.577 54.8892 182.742 55.446C181.906 55.9972 180.943 56.2727 179.852 56.2727C179.011 56.2727 178.338 56.1335 177.833 55.8551C177.327 55.571 176.938 55.25 176.665 54.892C176.392 54.5284 176.182 54.2273 176.034 53.9886H175.796V56H173.852ZM175.83 49.4545C175.83 50.4432 175.975 51.3153 176.264 52.071C176.554 52.821 176.977 53.4091 177.534 53.8352C178.091 54.2557 178.773 54.4659 179.58 54.4659C180.421 54.4659 181.122 54.2443 181.685 53.8011C182.253 53.3523 182.679 52.75 182.963 51.9943C183.253 51.233 183.398 50.3864 183.398 49.4545C183.398 48.5341 183.256 47.7045 182.972 46.9659C182.693 46.2216 182.27 45.6335 181.702 45.2017C181.139 44.7642 180.432 44.5455 179.58 44.5455C178.761 44.5455 178.074 44.7528 177.517 45.1676C176.96 45.5767 176.54 46.1506 176.256 46.8892C175.972 47.6222 175.83 48.4773 175.83 49.4545ZM193.324 56.2727C192.097 56.2727 191.04 55.983 190.154 55.4034C189.267 54.8239 188.585 54.0256 188.108 53.0085C187.631 51.9915 187.392 50.8295 187.392 49.5227C187.392 48.1932 187.637 47.0199 188.125 46.0028C188.62 44.9801 189.307 44.1818 190.188 43.608C191.074 43.0284 192.108 42.7386 193.29 42.7386C194.21 42.7386 195.04 42.9091 195.779 43.25C196.517 43.5909 197.122 44.0682 197.594 44.6818C198.066 45.2955 198.358 46.0114 198.472 46.8295H196.46C196.307 46.233 195.966 45.7045 195.438 45.2443C194.915 44.7784 194.21 44.5455 193.324 44.5455C192.54 44.5455 191.853 44.75 191.262 45.1591C190.676 45.5625 190.219 46.1335 189.889 46.8722C189.566 47.6051 189.404 48.4659 189.404 49.4545C189.404 50.4659 189.563 51.3466 189.881 52.0966C190.205 52.8466 190.659 53.429 191.245 53.8438C191.835 54.2585 192.529 54.4659 193.324 54.4659C193.847 54.4659 194.321 54.375 194.747 54.1932C195.174 54.0114 195.534 53.75 195.83 53.4091C196.125 53.0682 196.335 52.6591 196.46 52.1818H198.472C198.358 52.9545 198.077 53.6506 197.628 54.2699C197.185 54.8835 196.597 55.3722 195.864 55.7358C195.137 56.0938 194.29 56.2727 193.324 56.2727Z" fill="black"/>
<rect width="370" height="1" transform="translate(-7 81)" fill="#CA2E9E"/>
<g opacity="0.5">
<rect width="370" height="12" transform="translate(-7 82)" fill="#FEB0E8"/>
</g>
</g>
<rect x="0.5" y="0.5" width="355" height="93" rx="3.5" stroke="#C4C7CC"/>
<defs>
<clipPath id="clip0_826_23321">
<rect width="356" height="94" rx="4" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

10
packages/nc-gui/assets/img/dashboards/widget-types/bar-chart.svg

@ -0,0 +1,10 @@
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="13" y="13" width="94" height="94" rx="9" stroke="#15171A" stroke-width="6"/>
<mask id="mask0_120_12698" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="18" y="18" width="84" height="84">
<rect x="18" y="18" width="84" height="84" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_120_12698)">
<path d="M27.5048 88.9949C28.0079 89.4983 28.6314 89.7499 29.3751 89.7499H90.625C91.3687 89.7499 91.9921 89.4986 92.4953 88.996C92.9984 88.4935 93.25 87.8702 93.25 87.1262C93.25 86.3821 92.9984 85.7584 92.4953 85.2551C91.9921 84.7517 91.3687 84.5 90.625 84.5H29.3751C28.6314 84.5 28.0079 84.7513 27.5048 85.2538C27.0017 85.7564 26.7501 86.3797 26.7501 87.1238C26.7501 87.8678 27.0017 88.4915 27.5048 88.9949Z" fill="#15171A"/>
<path d="M34.6291 79.6538C33.4149 79.6538 32.3816 79.2284 31.529 78.3777C30.6764 77.5271 30.2501 76.4941 30.2501 75.2788V62.6251C30.2501 61.4098 30.6751 60.3769 31.525 59.5262C32.3749 58.6755 33.4069 58.2501 34.6211 58.2501C35.8353 58.2501 36.8687 58.6755 37.7212 59.5262C38.5738 60.3769 39 61.4098 39 62.6251V75.2788C39 76.4941 38.5751 77.5271 37.7252 78.3777C36.8752 79.2284 35.8432 79.6538 34.6291 79.6538ZM51.5232 79.6538C50.3091 79.6538 49.2757 79.2284 48.4232 78.3777C47.5706 77.5271 47.1444 76.4941 47.1444 75.2788V45.1251C47.1444 43.9099 47.5693 42.8769 48.4192 42.0262C49.2692 41.1755 50.3012 40.7501 51.5153 40.7501C52.7295 40.7501 53.7628 41.1755 54.6154 42.0262C55.4679 42.8769 55.8942 43.9099 55.8942 45.1251V75.2788C55.8942 76.4941 55.4693 77.5271 54.6194 78.3777C53.7695 79.2284 52.7374 79.6538 51.5232 79.6538ZM68.451 79.6538C67.2369 79.6538 66.2036 79.2284 65.351 78.3777C64.4985 77.5271 64.0722 76.4941 64.0722 75.2788V55.6251C64.0722 54.4098 64.4972 53.3769 65.3471 52.5262C66.197 51.6755 67.229 51.2501 68.4432 51.2501C69.6573 51.2501 70.6907 51.6755 71.5432 52.5262C72.3958 53.3769 72.822 54.4098 72.822 55.6251V75.2788C72.822 76.4941 72.3971 77.5271 71.5472 78.3777C70.6973 79.2284 69.6653 79.6538 68.451 79.6538ZM85.3789 79.6538C84.1647 79.6538 83.1314 79.2284 82.2789 78.3777C81.4263 77.5271 81 76.4941 81 75.2788V34.6251C81 33.4098 81.425 32.3769 82.2749 31.5262C83.1248 30.6755 84.1569 30.2501 85.371 30.2501C86.5852 30.2501 87.6185 30.6755 88.4711 31.5262C89.3237 32.3769 89.75 33.4098 89.75 34.6251V75.2788C89.75 76.4941 89.325 77.5271 88.4751 78.3777C87.6252 79.2284 86.5931 79.6538 85.3789 79.6538Z" fill="#3366FF"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

26
packages/nc-gui/assets/img/dashboards/widget-types/button.svg

@ -0,0 +1,26 @@
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_dd_120_12681)">
<rect x="10" y="35" width="100" height="50" rx="8" fill="#3069FE"/>
<path d="M22.017 69V51.5455H29.0057C30.2898 51.5455 31.3608 51.7358 32.2188 52.1165C33.0767 52.4972 33.7216 53.0256 34.1534 53.7017C34.5852 54.3722 34.8011 55.1449 34.8011 56.0199C34.8011 56.7017 34.6648 57.3011 34.392 57.8182C34.1193 58.3295 33.7443 58.75 33.267 59.0795C32.7955 59.4034 32.2557 59.6335 31.6477 59.7699V59.9403C32.3125 59.9687 32.9347 60.1562 33.5142 60.5028C34.0994 60.8494 34.5739 61.3352 34.9375 61.9602C35.3011 62.5795 35.483 63.3182 35.483 64.1761C35.483 65.1023 35.2528 65.929 34.7926 66.6562C34.3381 67.3778 33.6648 67.9489 32.7727 68.3693C31.8807 68.7898 30.7813 69 29.4744 69H22.017ZM25.7074 65.983H28.7159C29.7443 65.983 30.4943 65.7869 30.9659 65.3949C31.4375 64.9972 31.6733 64.4687 31.6733 63.8097C31.6733 63.3267 31.5568 62.9006 31.3239 62.5312C31.0909 62.1619 30.7585 61.8722 30.3267 61.6619C29.9006 61.4517 29.392 61.3466 28.8011 61.3466H25.7074V65.983ZM25.7074 58.8494H28.4432C28.9489 58.8494 29.3977 58.7614 29.7898 58.5852C30.1875 58.4034 30.5 58.1477 30.7273 57.8182C30.9602 57.4886 31.0767 57.0938 31.0767 56.6335C31.0767 56.0028 30.8523 55.4943 30.4034 55.108C29.9602 54.7216 29.3295 54.5284 28.5114 54.5284H25.7074V58.8494ZM46.2109 63.4261V55.9091H49.8416V69H46.3558V66.6222H46.2195C45.924 67.3892 45.4325 68.0057 44.745 68.4716C44.0632 68.9375 43.2308 69.1705 42.2479 69.1705C41.3729 69.1705 40.603 68.9716 39.9382 68.5739C39.2734 68.1761 38.7536 67.6108 38.3786 66.8778C38.0092 66.1449 37.8217 65.267 37.8161 64.2443V55.9091H41.4467V63.5966C41.4524 64.3693 41.6598 64.9801 42.0689 65.429C42.478 65.8778 43.0263 66.1023 43.7138 66.1023C44.1513 66.1023 44.5604 66.0028 44.9411 65.804C45.3217 65.5994 45.6286 65.2983 45.8615 64.9006C46.1001 64.5028 46.2166 64.0114 46.2109 63.4261ZM59.7088 55.9091V58.6364H51.8253V55.9091H59.7088ZM53.6151 52.7727H57.2457V64.9773C57.2457 65.3125 57.2969 65.5739 57.3991 65.7614C57.5014 65.9432 57.6435 66.071 57.8253 66.1449C58.0128 66.2187 58.2287 66.2557 58.473 66.2557C58.6435 66.2557 58.8139 66.2415 58.9844 66.2131C59.1548 66.179 59.2855 66.1534 59.3764 66.1364L59.9474 68.8381C59.7656 68.8949 59.5099 68.9602 59.1804 69.0341C58.8509 69.1136 58.4503 69.1619 57.9787 69.179C57.1037 69.2131 56.3366 69.0966 55.6776 68.8295C55.0241 68.5625 54.5156 68.1477 54.152 67.5852C53.7884 67.0227 53.6094 66.3125 53.6151 65.4545V52.7727ZM69.0369 55.9091V58.6364H61.1534V55.9091H69.0369ZM62.9432 52.7727H66.5739V64.9773C66.5739 65.3125 66.625 65.5739 66.7273 65.7614C66.8295 65.9432 66.9716 66.071 67.1534 66.1449C67.3409 66.2187 67.5568 66.2557 67.8011 66.2557C67.9716 66.2557 68.142 66.2415 68.3125 66.2131C68.483 66.179 68.6136 66.1534 68.7045 66.1364L69.2756 68.8381C69.0938 68.8949 68.8381 68.9602 68.5085 69.0341C68.179 69.1136 67.7784 69.1619 67.3068 69.179C66.4318 69.2131 65.6648 69.0966 65.0057 68.8295C64.3523 68.5625 63.8438 68.1477 63.4801 67.5852C63.1165 67.0227 62.9375 66.3125 62.9432 65.4545V52.7727ZM77.1761 69.2557C75.8523 69.2557 74.7074 68.9744 73.7415 68.4119C72.7813 67.8437 72.0398 67.054 71.517 66.0426C70.9943 65.0256 70.733 63.8466 70.733 62.5057C70.733 61.1534 70.9943 59.9716 71.517 58.9602C72.0398 57.9432 72.7813 57.1534 73.7415 56.5909C74.7074 56.0227 75.8523 55.7386 77.1761 55.7386C78.5 55.7386 79.642 56.0227 80.6023 56.5909C81.5682 57.1534 82.3125 57.9432 82.8352 58.9602C83.358 59.9716 83.6193 61.1534 83.6193 62.5057C83.6193 63.8466 83.358 65.0256 82.8352 66.0426C82.3125 67.054 81.5682 67.8437 80.6023 68.4119C79.642 68.9744 78.5 69.2557 77.1761 69.2557ZM77.1932 66.4432C77.7955 66.4432 78.2983 66.2727 78.7017 65.9318C79.1051 65.5852 79.4091 65.1136 79.6136 64.517C79.8239 63.9205 79.929 63.2415 79.929 62.4801C79.929 61.7188 79.8239 61.0398 79.6136 60.4432C79.4091 59.8466 79.1051 59.375 78.7017 59.0284C78.2983 58.6818 77.7955 58.5085 77.1932 58.5085C76.5852 58.5085 76.0739 58.6818 75.6591 59.0284C75.25 59.375 74.9403 59.8466 74.7301 60.4432C74.5256 61.0398 74.4233 61.7188 74.4233 62.4801C74.4233 63.2415 74.5256 63.9205 74.7301 64.517C74.9403 65.1136 75.25 65.5852 75.6591 65.9318C76.0739 66.2727 76.5852 66.4432 77.1932 66.4432ZM89.6108 61.4318V69H85.9801V55.9091H89.4403V58.2188H89.5938C89.8835 57.4574 90.3693 56.8551 91.0511 56.4119C91.733 55.9631 92.5597 55.7386 93.5312 55.7386C94.4403 55.7386 95.233 55.9375 95.9091 56.3352C96.5852 56.733 97.1108 57.3011 97.4858 58.0398C97.8608 58.7727 98.0483 59.6477 98.0483 60.6648V69H94.4176V61.3125C94.4233 60.5114 94.2188 59.8864 93.804 59.4375C93.3892 58.983 92.8182 58.7557 92.0909 58.7557C91.6023 58.7557 91.1705 58.8608 90.7955 59.071C90.4261 59.2812 90.1364 59.5881 89.9261 59.9915C89.7216 60.3892 89.6165 60.8693 89.6108 61.4318Z" fill="white"/>
</g>
<defs>
<filter id="filter0_dd_120_12681" x="9" y="35" width="102" height="56" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feMorphology radius="2" operator="erode" in="SourceAlpha" result="effect1_dropShadow_120_12681"/>
<feOffset dy="5"/>
<feGaussianBlur stdDeviation="1.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_120_12681"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feMorphology radius="2" operator="erode" in="SourceAlpha" result="effect2_dropShadow_120_12681"/>
<feOffset dy="3"/>
<feGaussianBlur stdDeviation="0.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_120_12681" result="effect2_dropShadow_120_12681"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_120_12681" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

10
packages/nc-gui/assets/img/dashboards/widget-types/divider.svg

@ -0,0 +1,10 @@
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_120_12677" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="12" y="12" width="96" height="96">
<rect x="12" y="12" width="96" height="96" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_120_12677)">
<path d="M22.0002 59.9987C22.0002 59.1483 22.2877 58.4359 22.8627 57.8616C23.4377 57.2872 24.1502 57.0001 25.0002 57.0001L95 57.0001C95.85 57.0001 96.5625 57.2877 97.1375 57.8629C97.7125 58.4381 98 59.1509 98 60.0013C98 60.8517 97.7125 61.564 97.1375 62.1384C96.5625 62.7127 95.85 62.9999 95 62.9999L25.0002 62.9999C24.1502 62.9999 23.4377 62.7123 22.8627 62.1371C22.2877 61.5618 22.0002 60.849 22.0002 59.9987Z" fill="#3366FF"/>
</g>
<path d="M42.0396 40.0395C41.3467 40.7324 41.0002 41.5911 41.0002 42.6154L41.0002 45.3845C41.0002 46.4089 41.3467 47.2676 42.0396 47.9605C42.7325 48.6534 43.5912 48.9999 44.6155 48.9999L75.3847 48.9999C76.409 48.9999 77.2677 48.6534 77.9606 47.9605C78.6535 47.2676 79 46.4089 79 45.3845L79 42.6154C79 41.5911 78.6535 40.7324 77.9606 40.0395C77.2677 39.3465 76.409 39 75.3847 39L44.6155 39C43.5912 39 42.7325 39.3465 42.0396 40.0395Z" fill="#15171A"/>
<path d="M42.0396 72.0395C41.3467 72.7324 41.0002 73.5911 41.0002 74.6154L41.0002 77.3845C41.0002 78.4089 41.3467 79.2676 42.0396 79.9605C42.7325 80.6534 43.5912 80.9999 44.6155 80.9999L75.3847 80.9999C76.409 80.9999 77.2677 80.6534 77.9606 79.9605C78.6535 79.2676 79 78.4089 79 77.3845L79 74.6154C79 73.5911 78.6535 72.7324 77.9606 72.0395C77.2677 71.3465 76.409 71 75.3847 71L44.6155 71C43.5912 71 42.7325 71.3465 42.0396 72.0395Z" fill="#15171A"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

15
packages/nc-gui/assets/img/dashboards/widget-types/form.svg

@ -0,0 +1,15 @@
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_219_16692)">
<mask id="mask0_219_16692" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="120" height="120">
<rect width="120" height="120" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_219_16692)">
<path d="M17.5 82.4762V36.8508C17.5 34.3415 18.385 32.2038 20.1551 30.4377C21.9251 28.6717 24.0529 27.7887 26.5385 27.7887H93.4613C95.9468 27.7887 98.0746 28.6717 99.8446 30.4377C101.615 32.2038 102.5 34.3415 102.5 36.8508V82.4762C102.5 84.9855 101.615 87.1232 99.8446 88.8893C98.0746 90.6553 95.9468 91.5383 93.4613 91.5383H26.5385C24.0529 91.5383 21.9251 90.6553 20.1551 88.8893C18.385 87.1232 17.5 84.9855 17.5 82.4762ZM24.9999 46.5387H36.25V35.2886H26.5385C26.0898 35.2886 25.7211 35.4328 25.4326 35.7213C25.1441 36.0097 24.9999 36.3783 24.9999 36.8271V46.5387ZM43.7498 46.5387H94.9999V36.8271C94.9999 36.3783 94.8556 36.0097 94.5671 35.7213C94.2786 35.4328 93.91 35.2886 93.4613 35.2886H43.7498V46.5387ZM43.7498 65.2886H94.9999V54.0384H43.7498V65.2886ZM43.7498 84.0384H93.4613C93.91 84.0384 94.2786 83.8942 94.5671 83.6057C94.8556 83.3173 94.9999 82.9487 94.9999 82.5V72.7883H43.7498V84.0384ZM26.5385 84.0384H36.25V72.7883H24.9999V82.5C24.9999 82.9487 25.1441 83.3173 25.4326 83.6057C25.7211 83.8942 26.0898 84.0384 26.5385 84.0384ZM24.9999 65.2886H36.25V54.0384H24.9999V65.2886Z" fill="#1C1B1F"/>
</g>
</g>
<defs>
<clipPath id="clip0_219_16692">
<rect width="120" height="120" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

15
packages/nc-gui/assets/img/dashboards/widget-types/gallery.svg

@ -0,0 +1,15 @@
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_219_16678)">
<mask id="mask0_219_16678" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="120" height="120">
<rect width="120" height="120" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_219_16678)">
<path d="M50.0769 69.2307H87.4229C88.4421 69.2307 89.1569 68.8045 89.5671 67.9519C89.9774 67.0993 89.8812 66.2884 89.2786 65.5192L78.4037 51.2309C77.9358 50.6283 77.3252 50.327 76.572 50.327C75.8188 50.327 75.2082 50.6283 74.7402 51.2309L64.7114 64.0386L58.721 56.5866C58.2531 55.9841 57.6425 55.6909 56.8892 55.7069C56.1361 55.7229 55.5255 56.0322 55.0576 56.6347L48.2692 65.5192C47.6667 66.2884 47.5705 67.0993 47.9807 67.9519C48.391 68.8045 49.0897 69.2307 50.0769 69.2307ZM40.2885 87.4999C37.7628 87.4999 35.625 86.6249 33.8751 84.8749C32.1251 83.1249 31.2501 80.987 31.2501 78.4614V21.5386C31.2501 19.013 32.1251 16.8751 33.8751 15.1251C35.625 13.3751 37.7628 12.5001 40.2885 12.5001H97.2114C99.7369 12.5001 101.875 13.3751 103.625 15.1251C105.375 16.8751 106.25 19.013 106.25 21.5386V78.4614C106.25 80.987 105.375 83.1249 103.625 84.8749C101.875 86.6249 99.7369 87.4999 97.2114 87.4999H40.2885ZM40.2885 80H97.2114C97.6601 80 98.0287 79.8557 98.3171 79.5672C98.6056 79.2787 98.7499 78.9101 98.7499 78.4614V21.5386C98.7499 21.0899 98.6056 20.7212 98.3171 20.4327C98.0287 20.1442 97.6601 20 97.2114 20H40.2885C39.8397 20 39.4711 20.1442 39.1826 20.4327C38.8941 20.7212 38.7499 21.0899 38.7499 21.5386V78.4614C38.7499 78.9101 38.8941 79.2787 39.1826 79.5672C39.4711 79.8557 39.8397 80 40.2885 80ZM22.7886 105C20.263 105 18.1252 104.125 16.3752 102.375C14.6252 100.625 13.7502 98.4868 13.7502 95.9612V35.2885C13.7502 34.2244 14.1092 33.3334 14.8271 32.6155C15.5451 31.8976 16.4361 31.5386 17.5001 31.5386C18.5642 31.5386 19.4552 31.8976 20.1732 32.6155C20.8912 33.3334 21.2501 34.2244 21.2501 35.2885V95.9612C21.2501 96.41 21.3944 96.7786 21.6829 97.0671C21.9713 97.3556 22.3399 97.4999 22.7886 97.4999H83.4614C84.5254 97.4999 85.4165 97.8588 86.1345 98.5767C86.8524 99.2947 87.2114 100.186 87.2114 101.25C87.2114 102.314 86.8524 103.205 86.1345 103.923C85.4165 104.641 84.5254 105 83.4614 105H22.7886Z" fill="#1C1B1F"/>
</g>
</g>
<defs>
<clipPath id="clip0_219_16678">
<rect width="120" height="120" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

15
packages/nc-gui/assets/img/dashboards/widget-types/grid.svg

@ -0,0 +1,15 @@
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_219_16672)">
<mask id="mask0_219_16672" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="123" height="123">
<rect width="123" height="123" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_219_16672)">
<path d="M25.625 56.375C23.5291 56.375 21.7239 55.6178 20.2094 54.1034C18.6949 52.5889 17.9377 50.7836 17.9377 48.6877V25.625C17.9377 23.5291 18.6949 21.7239 20.2094 20.2094C21.7239 18.6949 23.5291 17.9377 25.625 17.9377H48.6877C50.7836 17.9377 52.5888 18.6949 54.1034 20.2094C55.6178 21.7239 56.375 23.5291 56.375 25.625V48.6877C56.375 50.7836 55.6178 52.5889 54.1034 54.1034C52.5888 55.6178 50.7836 56.375 48.6877 56.375H25.625ZM25.625 105.062C23.5291 105.062 21.7239 104.305 20.2094 102.791C18.6949 101.276 17.9377 99.471 17.9377 97.375V74.3124C17.9377 72.2164 18.6949 70.4112 20.2094 68.8967C21.7239 67.3822 23.5291 66.625 25.625 66.625H48.6877C50.7836 66.625 52.5888 67.3822 54.1034 68.8967C55.6178 70.4112 56.375 72.2164 56.375 74.3124V97.375C56.375 99.471 55.6178 101.276 54.1034 102.791C52.5888 104.305 50.7836 105.062 48.6877 105.062H25.625ZM74.3124 56.375C72.2164 56.375 70.4112 55.6178 68.8967 54.1034C67.3822 52.5889 66.625 50.7836 66.625 48.6877V25.625C66.625 23.5291 67.3822 21.7239 68.8967 20.2094C70.4112 18.6949 72.2164 17.9377 74.3124 17.9377H97.375C99.471 17.9377 101.276 18.6949 102.791 20.2094C104.305 21.7239 105.062 23.5291 105.062 25.625V48.6877C105.062 50.7836 104.305 52.5889 102.791 54.1034C101.276 55.6178 99.471 56.375 97.375 56.375H74.3124ZM74.3124 105.062C72.2164 105.062 70.4112 104.305 68.8967 102.791C67.3822 101.276 66.625 99.471 66.625 97.375V74.3124C66.625 72.2164 67.3822 70.4112 68.8967 68.8967C70.4112 67.3822 72.2164 66.625 74.3124 66.625H97.375C99.471 66.625 101.276 67.3822 102.791 68.8967C104.305 70.4112 105.062 72.2164 105.062 74.3124V97.375C105.062 99.471 104.305 101.276 102.791 102.791C101.276 104.305 99.471 105.062 97.375 105.062H74.3124ZM25.625 48.6877H48.6877V25.625H25.625V48.6877ZM74.3124 48.6877H97.375V25.625H74.3124V48.6877ZM74.3124 97.375H97.375V74.3124H74.3124V97.375ZM25.625 97.375H48.6877V74.3124H25.625V97.375Z" fill="#1C1B1F"/>
</g>
</g>
<defs>
<clipPath id="clip0_219_16672">
<rect width="120" height="120" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

15
packages/nc-gui/assets/img/dashboards/widget-types/image.svg

@ -0,0 +1,15 @@
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_219_16802)">
<mask id="mask0_219_16802" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="120" height="120">
<rect width="120" height="120" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_219_16802)">
<path d="M26.5385 102.5C24.0128 102.5 21.875 101.625 20.125 99.8749C18.375 98.1249 17.5 95.9871 17.5 93.4614V26.5387C17.5 24.013 18.375 21.8752 20.125 20.1252C21.875 18.3752 24.0128 17.5002 26.5385 17.5002H93.4613C95.9869 17.5002 98.1248 18.3752 99.8748 20.1252C101.625 21.8752 102.5 24.013 102.5 26.5387V93.4614C102.5 95.9871 101.625 98.1249 99.8748 99.8749C98.1248 101.625 95.9869 102.5 93.4613 102.5H26.5385ZM29.9999 64.654L46.8365 47.8174C47.3044 47.3495 47.798 47.0194 48.3173 46.827C48.8364 46.6348 49.3973 46.5387 49.9999 46.5387C50.6025 46.5387 51.1633 46.6348 51.6825 46.827C52.2018 47.0194 52.6953 47.3495 53.1633 47.8174L69.9999 64.654L86.8365 47.8174C87.3044 47.3495 87.798 47.0194 88.3173 46.827C88.8364 46.6348 89.3973 46.5387 89.9999 46.5387C90.6025 46.5387 91.1633 46.6348 91.6825 46.827C92.2018 47.0194 92.6953 47.3495 93.1633 47.8174L94.9999 49.654V26.5387C94.9999 26.0899 94.8556 25.7213 94.5671 25.4328C94.2786 25.1443 93.91 25 93.4613 25H26.5385C26.0898 25 25.7211 25.1443 25.4326 25.4328C25.1441 25.7213 24.9999 26.0899 24.9999 26.5387V59.654L29.9999 64.654ZM24.9999 93.4614C24.9999 93.9102 25.1441 94.2788 25.4326 94.5673C25.7211 94.8558 26.0898 95 26.5385 95H93.4613C93.91 95 94.2786 94.8558 94.5671 94.5673C94.8556 94.2788 94.9999 93.9102 94.9999 93.4614V60.2692L89.9999 55.2692L73.1633 72.1057C72.7466 72.5223 72.2658 72.8397 71.721 73.0577C71.1762 73.2756 70.6025 73.3845 69.9999 73.3845C69.3973 73.3845 68.8364 73.2756 68.3173 73.0577C67.798 72.8397 67.3044 72.5223 66.8365 72.1057L49.9999 55.2692L33.1633 72.1057C32.6953 72.5737 32.2017 72.9038 31.6825 73.096C31.1633 73.2884 30.6025 73.3845 29.9999 73.3845C29.3973 73.3845 28.8364 73.2884 28.3173 73.096C27.798 72.9038 27.3044 72.5737 26.8365 72.1057L24.9999 70.2692V93.4614ZM24.9999 93.4614V95V60.2692V67.7689V25V93.4614Z" fill="#1C1B1F"/>
</g>
</g>
<defs>
<clipPath id="clip0_219_16802">
<rect width="120" height="120" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

15
packages/nc-gui/assets/img/dashboards/widget-types/kanban.svg

@ -0,0 +1,15 @@
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_219_16686)">
<mask id="mask0_219_16686" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="120" height="120">
<rect width="120" height="120" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_219_16686)">
<path d="M39.9999 83.7499C41.064 83.7499 41.955 83.3909 42.6729 82.673C43.3908 81.9551 43.7498 81.0641 43.7498 80V40C43.7498 38.9359 43.3908 38.0449 42.6729 37.327C41.955 36.6091 41.064 36.2502 39.9999 36.2502C38.9358 36.2502 38.0448 36.6091 37.3269 37.327C36.609 38.0449 36.25 38.9359 36.25 40V80C36.25 81.0641 36.609 81.9551 37.3269 82.673C38.0448 83.3909 38.9358 83.7499 39.9999 83.7499ZM59.9999 58.7499C61.064 58.7499 61.955 58.3909 62.6729 57.673C63.3908 56.9551 63.7498 56.0641 63.7498 55V40C63.7498 38.9359 63.3908 38.0449 62.6729 37.327C61.955 36.6091 61.064 36.2502 59.9999 36.2502C58.9358 36.2502 58.0448 36.6091 57.3269 37.327C56.609 38.0449 56.25 38.9359 56.25 40V55C56.25 56.0641 56.609 56.9551 57.3269 57.673C58.0448 58.3909 58.9358 58.7499 59.9999 58.7499ZM79.9999 73.7499C81.064 73.7499 81.955 73.3909 82.6729 72.673C83.3908 71.9551 83.7498 71.0641 83.7498 70V40C83.7498 38.9359 83.3908 38.0449 82.6729 37.327C81.955 36.6091 81.064 36.2502 79.9999 36.2502C78.9358 36.2502 78.0448 36.6091 77.3269 37.327C76.609 38.0449 76.25 38.9359 76.25 40V70C76.25 71.0641 76.609 71.9551 77.3269 72.673C78.0448 73.3909 78.9358 73.7499 79.9999 73.7499ZM26.5385 102.5C24.0128 102.5 21.875 101.625 20.125 99.8749C18.375 98.1249 17.5 95.9871 17.5 93.4614V26.5387C17.5 24.013 18.375 21.8752 20.125 20.1252C21.875 18.3752 24.0128 17.5002 26.5385 17.5002H93.4613C95.9869 17.5002 98.1248 18.3752 99.8748 20.1252C101.625 21.8752 102.5 24.013 102.5 26.5387V93.4614C102.5 95.9871 101.625 98.1249 99.8748 99.8749C98.1248 101.625 95.9869 102.5 93.4613 102.5H26.5385ZM26.5385 95H93.4613C93.8459 95 94.1985 94.8398 94.5191 94.5193C94.8396 94.1987 94.9999 93.8461 94.9999 93.4614V26.5387C94.9999 26.154 94.8396 25.8014 94.5191 25.4808C94.1985 25.1603 93.8459 25 93.4613 25H26.5385C26.1538 25 25.8012 25.1603 25.4806 25.4808C25.1601 25.8014 24.9999 26.154 24.9999 26.5387V93.4614C24.9999 93.8461 25.1601 94.1987 25.4806 94.5193C25.8012 94.8398 26.1538 95 26.5385 95Z" fill="#1C1B1F"/>
</g>
</g>
<defs>
<clipPath id="clip0_219_16686">
<rect width="120" height="120" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

15
packages/nc-gui/assets/img/dashboards/widget-types/line-chart.svg

@ -0,0 +1,15 @@
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="13" y="13" width="94" height="94" rx="9" stroke="#15171A" stroke-width="6"/>
<mask id="mask0_120_12702" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="18" y="18" width="84" height="84">
<rect x="18" y="18" width="84" height="84" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_120_12702)">
<path d="M27.5048 88.9949C28.0079 89.4983 28.6314 89.7499 29.3751 89.7499H90.625C91.3687 89.7499 91.9921 89.4986 92.4953 88.996C92.9984 88.4935 93.25 87.8702 93.25 87.1262C93.25 86.3821 92.9984 85.7584 92.4953 85.2551C91.9921 84.7517 91.3687 84.5 90.625 84.5H29.3751C28.6314 84.5 28.0079 84.7513 27.5048 85.2538C27.0017 85.7564 26.7501 86.3797 26.7501 87.1238C26.7501 87.8678 27.0017 88.4915 27.5048 88.9949Z" fill="#15171A"/>
</g>
<mask id="mask1_120_12702" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="24" y="17" width="73" height="73">
<rect x="24" y="17" width="73" height="73" fill="#3366FF"/>
</mask>
<g mask="url(#mask1_120_12702)">
<path d="M54.6009 62.4152L40.5062 77.3374C40.0974 77.7702 39.6235 77.991 39.0844 77.9998C38.5454 78.0085 38.0633 77.7877 37.638 77.3374C37.2127 76.8871 37 76.381 37 75.819C37 75.2571 37.2127 74.751 37.638 74.3007L52.9738 58.0643C53.4387 57.5721 53.9811 57.326 54.6009 57.326C55.2207 57.326 55.7631 57.5721 56.2279 58.0643L64.8875 67.2323L81.6675 47.0113C82.0236 46.5611 82.4736 46.3359 83.0176 46.3359C83.5616 46.3359 84.033 46.5384 84.432 46.9433C84.788 47.3202 84.9767 47.7687 84.9982 48.2888C85.0196 48.8089 84.8523 49.2801 84.4963 49.7025L66.564 71.3632C66.142 71.9008 65.5889 72.1809 64.9048 72.2036C64.2207 72.2263 63.6462 71.9916 63.1813 71.4994L54.6009 62.4152ZM54.6009 46.0792L40.5062 61.0015C40.0974 61.4343 39.6235 61.6551 39.0844 61.6638C38.5454 61.6726 38.0633 61.4518 37.638 61.0015C37.2127 60.5512 37 60.0451 37 59.4831C37 58.9212 37.2127 58.4151 37.638 57.9648L52.9738 41.7284C53.4387 41.2362 53.9811 40.9901 54.6009 40.9901C55.2207 40.9901 55.7631 41.2362 56.2279 41.7284L64.8875 50.8964L81.6675 30.6754C82.0236 30.2251 82.4736 30 83.0176 30C83.5616 30 84.033 30.2025 84.432 30.6074C84.788 30.9843 84.9767 31.4328 84.9982 31.9529C85.0196 32.473 84.8523 32.9442 84.4963 33.3666L66.564 55.0273C66.142 55.5649 65.5889 55.845 64.9048 55.8677C64.2207 55.8904 63.6462 55.6556 63.1813 55.1634L54.6009 46.0792Z" fill="#3366FF"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

9
packages/nc-gui/assets/img/dashboards/widget-types/number.svg

@ -0,0 +1,9 @@
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_120_12664" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="10" y="10" width="100" height="100">
<rect x="10" y="10" width="100" height="100" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_120_12664)">
<path d="M36.544 71.859C35.827 71.859 35.2335 71.6244 34.7634 71.1551C34.2933 70.6859 34.0583 70.0926 34.0583 69.375V53.109H30.2922C29.5746 53.109 28.9812 52.8745 28.512 52.4056C28.0429 51.9368 27.8083 51.3439 27.8083 50.6269C27.8083 49.9099 28.0429 49.3163 28.512 48.8462C28.9812 48.3761 29.5746 48.1411 30.2922 48.1411H35.2602C36.3272 48.1411 37.2216 48.502 37.9434 49.2238C38.6652 49.9456 39.0261 50.8401 39.0261 51.9071V69.375C39.0261 70.0926 38.7917 70.6859 38.3229 71.1551C37.8541 71.6244 37.2611 71.859 36.544 71.859ZM51.6864 71.859C50.6193 71.859 49.7249 71.4981 49.0031 70.7763C48.2813 70.0544 47.9204 69.1599 47.9204 68.0929V61.8429C47.9204 60.7986 48.2736 59.9232 48.98 59.2168C49.6865 58.5103 50.5619 58.1571 51.6062 58.1571H60.5005V53.109H50.4043C49.6867 53.109 49.0933 52.8745 48.6241 52.4056C48.1549 51.9368 47.9204 51.3439 47.9204 50.6269C47.9204 49.9099 48.1549 49.3163 48.6241 48.8462C49.0933 48.3761 49.6867 48.1411 50.4043 48.1411H61.7824C62.8268 48.1411 63.7022 48.4943 64.4087 49.2007C65.1151 49.9072 65.4684 50.7826 65.4684 51.827V58.1571C65.4684 59.2014 65.1151 60.0768 64.4087 60.7832C63.7022 61.4897 62.8268 61.8429 61.7824 61.8429H52.8882V66.8911H62.9844C63.702 66.8911 64.2954 67.1255 64.7645 67.5944C65.2338 68.0632 65.4684 68.6561 65.4684 69.3731C65.4684 70.0901 65.2338 70.6837 64.7645 71.1539C64.2954 71.6239 63.702 71.859 62.9844 71.859H51.6864ZM75.244 71.859C74.5264 71.859 73.9331 71.6245 73.4639 71.1556C72.9947 70.6868 72.76 70.0939 72.76 69.3769C72.76 68.6599 72.9947 68.0663 73.4639 67.5962C73.9331 67.1261 74.5264 66.8911 75.244 66.8911H85.3403V61.8429H78.7697C78.2783 61.8429 77.8482 61.6587 77.4796 61.2901C77.111 60.9215 76.9267 60.4915 76.9267 60C76.9267 59.5086 77.111 59.0785 77.4796 58.7099C77.8482 58.3414 78.2783 58.1571 78.7697 58.1571H85.3403V53.109H75.244C74.5264 53.109 73.9331 52.8745 73.4639 52.4056C72.9947 51.9368 72.76 51.3439 72.76 50.6269C72.76 49.9099 72.9947 49.3163 73.4639 48.8462C73.9331 48.3761 74.5264 48.1411 75.244 48.1411H86.6222C87.6665 48.1411 88.5419 48.4943 89.2484 49.2007C89.9548 49.9072 90.3081 50.7826 90.3081 51.827V68.173C90.3081 69.2174 89.9548 70.0928 89.2484 70.7993C88.5419 71.5057 87.6665 71.859 86.6222 71.859H75.244Z" fill="#3366FF"/>
</g>
<rect x="13" y="32" width="94" height="56" rx="9" stroke="#15171A" stroke-width="6"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

10
packages/nc-gui/assets/img/dashboards/widget-types/pie-chart.svg

@ -0,0 +1,10 @@
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="13" y="13" width="94" height="94" rx="9" stroke="#15171A" stroke-width="6"/>
<path d="M60 60V29C78 29 91 46 91 60H60Z" fill="#3366FF"/>
<mask id="mask0_120_12706" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="18" y="18" width="84" height="84">
<rect x="18" y="18" width="84" height="84" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_120_12706)">
<path d="M62.6248 57.3751H87.8922C87.2192 50.6219 84.528 44.885 79.8187 40.1645C75.1094 35.4439 69.3781 32.7583 62.6248 32.1077V57.3751ZM57.375 87.8923V32.1077C50.1596 32.7583 44.1265 35.7457 39.2759 41.0697C34.4252 46.3938 31.9999 52.7039 31.9999 60C31.9999 67.2962 34.4252 73.6063 39.2759 78.9303C44.1265 84.2544 50.1596 87.2417 57.375 87.8923ZM62.6248 87.8923C69.3781 87.2776 75.1183 84.601 79.8456 79.8625C84.5728 75.1241 87.255 69.3782 87.8922 62.6249H62.6248V87.8923ZM60.0058 93.2499C55.407 93.2499 51.0844 92.3773 47.0379 90.6319C42.9914 88.8866 39.4715 86.518 36.4783 83.5261C33.4849 80.5341 31.1152 77.0157 29.3691 72.971C27.623 68.9264 26.75 64.6047 26.75 60.0059C26.75 55.4071 27.6227 51.0845 29.368 47.038C31.1133 42.9915 33.482 39.4716 36.4739 36.4784C39.4659 33.485 42.9842 31.1153 47.0289 29.3692C51.0736 27.6232 55.3953 26.7501 59.9941 26.7501C64.5928 26.7501 68.9058 27.624 72.933 29.3718C76.9603 31.1195 80.4793 33.4988 83.4903 36.5097C86.5011 39.5206 88.8804 43.0383 90.6282 47.0628C92.3759 51.0873 93.2498 55.4033 93.2498 60.0107C93.2498 64.5626 92.3772 68.8595 90.6318 72.9015C88.8865 76.9434 86.5179 80.4733 83.526 83.491C80.534 86.5086 77.0156 88.8895 72.9709 90.6337C68.9263 92.3779 64.6045 93.2499 60.0058 93.2499Z" fill="#15171A"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

15
packages/nc-gui/assets/img/dashboards/widget-types/scatter-chart.svg

@ -0,0 +1,15 @@
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="13" y="13" width="94" height="94" rx="9" stroke="#15171A" stroke-width="6"/>
<mask id="mask0_120_12707" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="18" y="18" width="84" height="84">
<rect x="18" y="18" width="84" height="84" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_120_12707)">
<path d="M27.5048 88.9949C28.0079 89.4983 28.6314 89.7499 29.3751 89.7499H90.625C91.3687 89.7499 91.9921 89.4986 92.4953 88.996C92.9984 88.4935 93.25 87.8702 93.25 87.1262C93.25 86.3821 92.9984 85.7584 92.4953 85.2551C91.9921 84.7517 91.3687 84.5 90.625 84.5H29.3751C28.6314 84.5 28.0079 84.7513 27.5048 85.2538C27.0017 85.7564 26.7501 86.3797 26.7501 87.1238C26.7501 87.8678 27.0017 88.4915 27.5048 88.9949Z" fill="#15171A"/>
<mask id="mask1_120_12707" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="31" y="28" width="59" height="59">
<rect x="31" y="28" width="59" height="59" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask1_120_12707)">
<path d="M72.508 78.1121C70.1419 78.1121 68.1163 77.2696 66.4314 75.5846C64.7464 73.8997 63.9039 71.8741 63.9039 69.508C63.9039 67.1419 64.7464 65.1163 66.4314 63.4313C68.1163 61.7464 70.1419 60.9039 72.508 60.9039C74.8741 60.9039 76.8997 61.7464 78.5846 63.4313C80.2696 65.1163 81.1121 67.1419 81.1121 69.508C81.1121 71.8741 80.2696 73.8997 78.5846 75.5846C76.8997 77.2696 74.8741 78.1121 72.508 78.1121ZM72.508 74.4246C73.8601 74.4246 75.0175 73.9432 75.9804 72.9804C76.9432 72.0175 77.4247 70.8601 77.4247 69.508C77.4247 68.1559 76.9432 66.9984 75.9804 66.0356C75.0175 65.0727 73.8601 64.5913 72.508 64.5913C71.1559 64.5913 69.9984 65.0727 69.0356 66.0356C68.0727 66.9984 67.5913 68.1559 67.5913 69.508C67.5913 70.8601 68.0727 72.0175 69.0356 72.9804C69.9984 73.9432 71.1559 74.4246 72.508 74.4246ZM48.492 70.7371C46.1259 70.7371 44.1004 69.8946 42.4154 68.2096C40.7304 66.5247 39.8879 64.4991 39.8879 62.133C39.8879 59.7669 40.7304 57.7413 42.4154 56.0563C44.1004 54.3714 46.1259 53.5289 48.492 53.5289C50.8581 53.5289 52.8837 54.3714 54.5687 56.0563C56.2536 57.7413 57.0961 59.7669 57.0961 62.133C57.0961 64.4991 56.2536 66.5247 54.5687 68.2096C52.8837 69.8946 50.8581 70.7371 48.492 70.7371ZM48.492 67.0496C49.8441 67.0496 51.0016 66.5682 51.9644 65.6054C52.9273 64.6425 53.4087 63.4851 53.4087 62.133C53.4087 60.7809 52.9273 59.6234 51.9644 58.6606C51.0016 57.6977 49.8441 57.2163 48.492 57.2163C47.14 57.2163 45.9825 57.6977 45.0196 58.6606C44.0568 59.6234 43.5754 60.7809 43.5754 62.133C43.5754 63.4851 44.0568 64.6425 45.0196 65.6054C45.9825 66.5682 47.14 67.0496 48.492 67.0496ZM58.0417 51.8268C55.6756 51.8268 53.65 50.9844 51.9651 49.2994C50.2801 47.6144 49.4376 45.5889 49.4376 43.2228C49.4376 40.8567 50.2801 38.8311 51.9651 37.1462C53.65 35.4612 55.6756 34.6187 58.0417 34.6187C60.4078 34.6187 62.4333 35.4612 64.1183 37.1462C65.8033 38.8311 66.6458 40.8567 66.6458 43.2228C66.6458 45.5889 65.8033 47.6144 64.1183 49.2994C62.4333 50.9844 60.4078 51.8268 58.0417 51.8268ZM58.0417 48.1395C59.3938 48.1395 60.5512 47.658 61.5141 46.6952C62.4769 45.7324 62.9584 44.5749 62.9584 43.2228C62.9584 41.8707 62.4769 40.7133 61.5141 39.7504C60.5512 38.7876 59.3938 38.3061 58.0417 38.3061C56.6896 38.3061 55.5321 38.7876 54.5693 39.7504C53.6064 40.7133 53.125 41.8707 53.125 43.2228C53.125 44.5749 53.6064 45.7324 54.5693 46.6952C55.5321 47.658 56.6896 48.1395 58.0417 48.1395Z" fill="#3366FF"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

9
packages/nc-gui/assets/img/dashboards/widget-types/text.svg

@ -0,0 +1,9 @@
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_120_12656" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="12" y="12" width="96" height="96">
<rect x="12" y="12" width="96" height="96" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_120_12656)">
<path d="M19 101V81H26V39H19V19H39V26H81V19H101V39H94V81H101V101H81V94H39V101H19ZM39 88H81V81H88V39H81V32H39V39H32V81H39V88ZM25 33H33V25H25V33ZM87 33H95V25H87V33ZM87 95H95V87H87V95ZM25 95H33V87H25V95Z" fill="#15171A"/>
<path d="M57.0001 49.9999V76H62.9999V49.9999H74.9999V44H45.0001V49.9999H57.0001Z" fill="#3366FF"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 647 B

89
packages/nc-gui/assets/img/google-doc.svg

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="47px" height="65px" viewBox="0 0 47 65" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 54.1 (76490) - https://sketchapp.com -->
<title>Docs-icon</title>
<desc>Created with Sketch.</desc>
<defs>
<path d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z" id="path-1"></path>
<path d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z" id="path-3"></path>
<linearGradient x1="50.0053945%" y1="8.58610612%" x2="50.0053945%" y2="100.013939%" id="linearGradient-5">
<stop stop-color="#1A237E" stop-opacity="0.2" offset="0%"></stop>
<stop stop-color="#1A237E" stop-opacity="0.02" offset="100%"></stop>
</linearGradient>
<path d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z" id="path-6"></path>
<path d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z" id="path-8"></path>
<path d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z" id="path-10"></path>
<path d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z" id="path-12"></path>
<path d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z" id="path-14"></path>
<radialGradient cx="3.16804688%" cy="2.71744318%" fx="3.16804688%" fy="2.71744318%" r="161.248516%" gradientTransform="translate(0.031680,0.027174),scale(1.000000,0.723077),translate(-0.031680,-0.027174)" id="radialGradient-16">
<stop stop-color="#FFFFFF" stop-opacity="0.1" offset="0%"></stop>
<stop stop-color="#FFFFFF" stop-opacity="0" offset="100%"></stop>
</radialGradient>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Consumer-Apps-Docs-Large-VD-R8" transform="translate(-451.000000, -463.000000)">
<g id="Hero" transform="translate(0.000000, 63.000000)">
<g id="Personal" transform="translate(277.000000, 309.000000)">
<g id="Docs-icon" transform="translate(174.000000, 91.000000)">
<g id="Group">
<g id="Clipped">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="SVGID_1_"></g>
<path d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L36.71875,10.3409091 L29.375,0 Z" id="Path" fill="#4285F4" fill-rule="nonzero" mask="url(#mask-2)"></path>
</g>
<g id="Clipped">
<mask id="mask-4" fill="white">
<use xlink:href="#path-3"></use>
</mask>
<g id="SVGID_1_"></g>
<polygon id="Path" fill="url(#linearGradient-5)" fill-rule="nonzero" mask="url(#mask-4)" points="30.6638281 16.4309659 47 32.8582386 47 17.7272727"></polygon>
</g>
<g id="Clipped">
<mask id="mask-7" fill="white">
<use xlink:href="#path-6"></use>
</mask>
<g id="SVGID_1_"></g>
<path d="M11.75,47.2727273 L35.25,47.2727273 L35.25,44.3181818 L11.75,44.3181818 L11.75,47.2727273 Z M11.75,53.1818182 L29.375,53.1818182 L29.375,50.2272727 L11.75,50.2272727 L11.75,53.1818182 Z M11.75,32.5 L11.75,35.4545455 L35.25,35.4545455 L35.25,32.5 L11.75,32.5 Z M11.75,41.3636364 L35.25,41.3636364 L35.25,38.4090909 L11.75,38.4090909 L11.75,41.3636364 Z" id="Shape" fill="#F1F1F1" fill-rule="nonzero" mask="url(#mask-7)"></path>
</g>
<g id="Clipped">
<mask id="mask-9" fill="white">
<use xlink:href="#path-8"></use>
</mask>
<g id="SVGID_1_"></g>
<g id="Group" mask="url(#mask-9)">
<g transform="translate(26.437500, -2.954545)">
<path d="M2.9375,2.95454545 L2.9375,16.25 C2.9375,18.6985795 4.90929688,20.6818182 7.34375,20.6818182 L20.5625,20.6818182 L2.9375,2.95454545 Z" id="Path" fill="#A1C2FA" fill-rule="nonzero"></path>
</g>
</g>
</g>
<g id="Clipped">
<mask id="mask-11" fill="white">
<use xlink:href="#path-10"></use>
</mask>
<g id="SVGID_1_"></g>
<path d="M4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,4.80113636 C0,2.36363636 1.9828125,0.369318182 4.40625,0.369318182 L29.375,0.369318182 L29.375,0 L4.40625,0 Z" id="Path" fill-opacity="0.2" fill="#FFFFFF" fill-rule="nonzero" mask="url(#mask-11)"></path>
</g>
<g id="Clipped">
<mask id="mask-13" fill="white">
<use xlink:href="#path-12"></use>
</mask>
<g id="SVGID_1_"></g>
<path d="M42.59375,64.6306818 L4.40625,64.6306818 C1.9828125,64.6306818 0,62.6363636 0,60.1988636 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,60.1988636 C47,62.6363636 45.0171875,64.6306818 42.59375,64.6306818 Z" id="Path" fill-opacity="0.2" fill="#1A237E" fill-rule="nonzero" mask="url(#mask-13)"></path>
</g>
<g id="Clipped">
<mask id="mask-15" fill="white">
<use xlink:href="#path-14"></use>
</mask>
<g id="SVGID_1_"></g>
<path d="M33.78125,17.7272727 C31.3467969,17.7272727 29.375,15.7440341 29.375,13.2954545 L29.375,13.6647727 C29.375,16.1133523 31.3467969,18.0965909 33.78125,18.0965909 L47,18.0965909 L47,17.7272727 L33.78125,17.7272727 Z" id="Path" fill-opacity="0.1" fill="#1A237E" fill-rule="nonzero" mask="url(#mask-15)"></path>
</g>
</g>
<path d="M29.375,0 L4.40625,0 C1.9828125,0 0,1.99431818 0,4.43181818 L0,60.5681818 C0,63.0056818 1.9828125,65 4.40625,65 L42.59375,65 C45.0171875,65 47,63.0056818 47,60.5681818 L47,17.7272727 L29.375,0 Z" id="Path" fill="url(#radialGradient-16)" fill-rule="nonzero"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.0 KiB

89
packages/nc-gui/assets/img/google-sheet.svg

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="49px" height="67px" viewBox="0 0 49 67" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 54.1 (76490) - https://sketchapp.com -->
<title>Sheets-icon</title>
<desc>Created with Sketch.</desc>
<defs>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-1"></path>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-3"></path>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-5"></path>
<linearGradient x1="50.0053945%" y1="8.58610612%" x2="50.0053945%" y2="100.013939%" id="linearGradient-7">
<stop stop-color="#263238" stop-opacity="0.2" offset="0%"></stop>
<stop stop-color="#263238" stop-opacity="0.02" offset="100%"></stop>
</linearGradient>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-8"></path>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-10"></path>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-12"></path>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-14"></path>
<radialGradient cx="3.16804688%" cy="2.71744318%" fx="3.16804688%" fy="2.71744318%" r="161.248516%" gradientTransform="translate(0.031680,0.027174),scale(1.000000,0.727273),translate(-0.031680,-0.027174)" id="radialGradient-16">
<stop stop-color="#FFFFFF" stop-opacity="0.1" offset="0%"></stop>
<stop stop-color="#FFFFFF" stop-opacity="0" offset="100%"></stop>
</radialGradient>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Consumer-Apps-Sheets-Large-VD-R8-" transform="translate(-451.000000, -451.000000)">
<g id="Hero" transform="translate(0.000000, 63.000000)">
<g id="Personal" transform="translate(277.000000, 299.000000)">
<g id="Sheets-icon" transform="translate(174.833333, 89.958333)">
<g id="Group">
<g id="Clipped">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="SVGID_1_"></g>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L36.9791667,10.3541667 L29.5833333,0 Z" id="Path" fill="#0F9D58" fill-rule="nonzero" mask="url(#mask-2)"></path>
</g>
<g id="Clipped">
<mask id="mask-4" fill="white">
<use xlink:href="#path-3"></use>
</mask>
<g id="SVGID_1_"></g>
<path d="M11.8333333,31.8020833 L11.8333333,53.25 L35.5,53.25 L35.5,31.8020833 L11.8333333,31.8020833 Z M22.1875,50.2916667 L14.7916667,50.2916667 L14.7916667,46.59375 L22.1875,46.59375 L22.1875,50.2916667 Z M22.1875,44.375 L14.7916667,44.375 L14.7916667,40.6770833 L22.1875,40.6770833 L22.1875,44.375 Z M22.1875,38.4583333 L14.7916667,38.4583333 L14.7916667,34.7604167 L22.1875,34.7604167 L22.1875,38.4583333 Z M32.5416667,50.2916667 L25.1458333,50.2916667 L25.1458333,46.59375 L32.5416667,46.59375 L32.5416667,50.2916667 Z M32.5416667,44.375 L25.1458333,44.375 L25.1458333,40.6770833 L32.5416667,40.6770833 L32.5416667,44.375 Z M32.5416667,38.4583333 L25.1458333,38.4583333 L25.1458333,34.7604167 L32.5416667,34.7604167 L32.5416667,38.4583333 Z" id="Shape" fill="#F1F1F1" fill-rule="nonzero" mask="url(#mask-4)"></path>
</g>
<g id="Clipped">
<mask id="mask-6" fill="white">
<use xlink:href="#path-5"></use>
</mask>
<g id="SVGID_1_"></g>
<polygon id="Path" fill="url(#linearGradient-7)" fill-rule="nonzero" mask="url(#mask-6)" points="30.8813021 16.4520313 47.3333333 32.9003646 47.3333333 17.75"></polygon>
</g>
<g id="Clipped">
<mask id="mask-9" fill="white">
<use xlink:href="#path-8"></use>
</mask>
<g id="SVGID_1_"></g>
<g id="Group" mask="url(#mask-9)">
<g transform="translate(26.625000, -2.958333)">
<path d="M2.95833333,2.95833333 L2.95833333,16.2708333 C2.95833333,18.7225521 4.94411458,20.7083333 7.39583333,20.7083333 L20.7083333,20.7083333 L2.95833333,2.95833333 Z" id="Path" fill="#87CEAC" fill-rule="nonzero"></path>
</g>
</g>
</g>
<g id="Clipped">
<mask id="mask-11" fill="white">
<use xlink:href="#path-10"></use>
</mask>
<g id="SVGID_1_"></g>
<path d="M4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,4.80729167 C0,2.36666667 1.996875,0.369791667 4.4375,0.369791667 L29.5833333,0.369791667 L29.5833333,0 L4.4375,0 Z" id="Path" fill-opacity="0.2" fill="#FFFFFF" fill-rule="nonzero" mask="url(#mask-11)"></path>
</g>
<g id="Clipped">
<mask id="mask-13" fill="white">
<use xlink:href="#path-12"></use>
</mask>
<g id="SVGID_1_"></g>
<path d="M42.8958333,64.7135417 L4.4375,64.7135417 C1.996875,64.7135417 0,62.7166667 0,60.2760417 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,60.2760417 C47.3333333,62.7166667 45.3364583,64.7135417 42.8958333,64.7135417 Z" id="Path" fill-opacity="0.2" fill="#263238" fill-rule="nonzero" mask="url(#mask-13)"></path>
</g>
<g id="Clipped">
<mask id="mask-15" fill="white">
<use xlink:href="#path-14"></use>
</mask>
<g id="SVGID_1_"></g>
<path d="M34.0208333,17.75 C31.5691146,17.75 29.5833333,15.7642188 29.5833333,13.3125 L29.5833333,13.6822917 C29.5833333,16.1340104 31.5691146,18.1197917 34.0208333,18.1197917 L47.3333333,18.1197917 L47.3333333,17.75 L34.0208333,17.75 Z" id="Path" fill-opacity="0.1" fill="#263238" fill-rule="nonzero" mask="url(#mask-15)"></path>
</g>
</g>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="Path" fill="url(#radialGradient-16)" fill-rule="nonzero"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.0 KiB

97
packages/nc-gui/assets/img/google-slide.svg

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="48px" height="66px" viewBox="0 0 48 66" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 54.1 (76490) - https://sketchapp.com -->
<title>Slides-icon</title>
<desc>Created with Sketch.</desc>
<defs>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-1"></path>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-3"></path>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-5"></path>
<linearGradient x1="50.0053945%" y1="8.58610612%" x2="50.0053945%" y2="100.013939%" id="linearGradient-7">
<stop stop-color="#BF360C" stop-opacity="0.2" offset="0%"></stop>
<stop stop-color="#BF360C" stop-opacity="0.02" offset="100%"></stop>
</linearGradient>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-8"></path>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-10"></path>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-12"></path>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-14"></path>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-16"></path>
<radialGradient cx="3.16804688%" cy="2.71744318%" fx="3.16804688%" fy="2.71744318%" r="161.248516%" gradientTransform="translate(0.031680,0.027174),scale(1.000000,0.727273),translate(-0.031680,-0.027174)" id="radialGradient-18">
<stop stop-color="#FFFFFF" stop-opacity="0.1" offset="0%"></stop>
<stop stop-color="#FFFFFF" stop-opacity="0" offset="100%"></stop>
</radialGradient>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Consumer-Apps-Slides-Large-VD-R8" transform="translate(-449.000000, -452.000000)">
<g id="Hero" transform="translate(0.000000, 63.000000)">
<g id="Personal" transform="translate(277.000000, 299.000000)">
<g id="Slides-icon" transform="translate(172.000000, 90.000000)">
<g id="Group">
<g id="Clipped">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="SVGID_1_"></g>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L36.9791667,10.3541667 L29.5833333,0 Z" id="Path" fill="#F4B400" fill-rule="nonzero" mask="url(#mask-2)"></path>
</g>
<g id="Clipped">
<mask id="mask-4" fill="white">
<use xlink:href="#path-3"></use>
</mask>
<g id="SVGID_1_"></g>
<path d="M33.28125,29.5833333 L14.0520833,29.5833333 C12.8317708,29.5833333 11.8333333,30.5817708 11.8333333,31.8020833 L11.8333333,51.03125 C11.8333333,52.2515625 12.8317708,53.25 14.0520833,53.25 L33.28125,53.25 C34.5015625,53.25 35.5,52.2515625 35.5,51.03125 L35.5,31.8020833 C35.5,30.5817708 34.5015625,29.5833333 33.28125,29.5833333 Z M32.5416667,46.59375 L14.7916667,46.59375 L14.7916667,36.2395833 L32.5416667,36.2395833 L32.5416667,46.59375 Z" id="Shape" fill="#F1F1F1" fill-rule="nonzero" mask="url(#mask-4)"></path>
</g>
<g id="Clipped">
<mask id="mask-6" fill="white">
<use xlink:href="#path-5"></use>
</mask>
<g id="SVGID_1_"></g>
<polygon id="Path" fill="url(#linearGradient-7)" fill-rule="nonzero" mask="url(#mask-6)" points="30.8813021 16.4520313 47.3333333 32.9003646 47.3333333 17.75"></polygon>
</g>
<g id="Clipped">
<mask id="mask-9" fill="white">
<use xlink:href="#path-8"></use>
</mask>
<g id="SVGID_1_"></g>
<g id="Group" mask="url(#mask-9)">
<g transform="translate(26.625000, -2.958333)">
<path d="M2.95833333,2.95833333 L2.95833333,16.2708333 C2.95833333,18.7225521 4.94411458,20.7083333 7.39583333,20.7083333 L20.7083333,20.7083333 L2.95833333,2.95833333 Z" id="Path" fill="#FADA80" fill-rule="nonzero"></path>
</g>
</g>
</g>
<g id="Clipped">
<mask id="mask-11" fill="white">
<use xlink:href="#path-10"></use>
</mask>
<g id="SVGID_1_"></g>
<polygon id="Path" fill-opacity="0.1" fill="#FFFFFF" fill-rule="nonzero" mask="url(#mask-11)" points="29.5833333 0 29.5833333 0.369791667 46.9635417 17.75 47.3333333 17.75"></polygon>
</g>
<g id="Clipped">
<mask id="mask-13" fill="white">
<use xlink:href="#path-12"></use>
</mask>
<g id="SVGID_1_"></g>
<path d="M4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,4.80729167 C0,2.36666667 1.996875,0.369791667 4.4375,0.369791667 L29.5833333,0.369791667 L29.5833333,0 L4.4375,0 Z" id="Path" fill-opacity="0.2" fill="#FFFFFF" fill-rule="nonzero" mask="url(#mask-13)"></path>
</g>
<g id="Clipped">
<mask id="mask-15" fill="white">
<use xlink:href="#path-14"></use>
</mask>
<g id="SVGID_1_"></g>
<path d="M42.8958333,64.7135417 L4.4375,64.7135417 C1.996875,64.7135417 0,62.7166667 0,60.2760417 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,60.2760417 C47.3333333,62.7166667 45.3364583,64.7135417 42.8958333,64.7135417 Z" id="Path" fill-opacity="0.2" fill="#BF360C" fill-rule="nonzero" mask="url(#mask-15)"></path>
</g>
<g id="Clipped">
<mask id="mask-17" fill="white">
<use xlink:href="#path-16"></use>
</mask>
<g id="SVGID_1_"></g>
<path d="M34.0208333,17.75 C31.5691146,17.75 29.5833333,15.7642188 29.5833333,13.3125 L29.5833333,13.6822917 C29.5833333,16.1340104 31.5691146,18.1197917 34.0208333,18.1197917 L47.3333333,18.1197917 L47.3333333,17.75 L34.0208333,17.75 Z" id="Path" fill-opacity="0.1" fill="#BF360C" fill-rule="nonzero" mask="url(#mask-17)"></path>
</g>
</g>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="Path" fill="url(#radialGradient-18)" fill-rule="nonzero"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.5 KiB

BIN
packages/nc-gui/assets/img/icons/256x256-trans.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

BIN
packages/nc-gui/assets/img/icons/256x256.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
packages/nc-gui/assets/img/icons/64x64.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

BIN
packages/nc-gui/assets/img/miro-icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

10
packages/nc-gui/assets/nc-icons/add-box.svg

@ -0,0 +1,10 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="add_box">
<mask id="mask0_101_20727" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="40" height="40">
<rect id="Bounding box" width="40" height="40" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_101_20727)">
<path id="add_box_2" d="M20.0047 27.9166C20.3033 27.9166 20.5516 27.8164 20.7496 27.616C20.9476 27.4156 21.0466 27.1668 21.0466 26.8697V21.047H26.8693C27.1665 21.047 27.4153 20.9463 27.6157 20.7449C27.8161 20.5436 27.9163 20.2936 27.9163 19.995C27.9163 19.6963 27.8161 19.448 27.6157 19.25C27.4153 19.052 27.1665 18.953 26.8693 18.953H21.0466V13.1303C21.0466 12.8332 20.9459 12.5844 20.7445 12.384C20.5432 12.1836 20.2932 12.0834 19.9946 12.0834C19.696 12.0834 19.4477 12.1836 19.2497 12.384C19.0517 12.5844 18.9527 12.8332 18.9527 13.1303V18.953H13.13C12.8328 18.953 12.584 19.0537 12.3836 19.2551C12.1832 19.4564 12.083 19.7064 12.083 20.005C12.083 20.3037 12.1832 20.552 12.3836 20.75C12.584 20.948 12.8328 21.047 13.13 21.047H18.9527V26.8697C18.9527 27.1668 19.0534 27.4156 19.2547 27.616C19.4561 27.8164 19.7061 27.9166 20.0047 27.9166ZM8.43984 34.1666C7.72351 34.1666 7.10995 33.9112 6.59917 33.4005C6.0884 32.8897 5.83301 32.2761 5.83301 31.5598V8.44021C5.83301 7.72387 6.0884 7.11032 6.59917 6.59954C7.10995 6.08876 7.72351 5.83337 8.43984 5.83337H31.5594C32.2758 5.83337 32.8893 6.08876 33.4001 6.59954C33.9109 7.11032 34.1663 7.72387 34.1663 8.44021V31.5598C34.1663 32.2761 33.9109 32.8897 33.4001 33.4005C32.8893 33.9112 32.2758 34.1666 31.5594 34.1666H8.43984ZM8.43984 32.0727H31.5594C31.709 32.0727 31.8319 32.0246 31.9281 31.9284C32.0242 31.8323 32.0723 31.7094 32.0723 31.5598V8.44021C32.0723 8.29062 32.0242 8.16775 31.9281 8.07158C31.8319 7.97541 31.709 7.92733 31.5594 7.92733H8.43984C8.29026 7.92733 8.16738 7.97541 8.07122 8.07158C7.97505 8.16775 7.92697 8.29062 7.92697 8.44021V31.5598C7.92697 31.7094 7.97505 31.8323 8.07122 31.9284C8.16738 32.0246 8.29026 32.0727 8.43984 32.0727Z" fill="#40444D"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

1
packages/nc-gui/assets/nc-icons/article.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path fill="currentColor" d="M298.001-610.001h363.998v-51.998H298.001v51.998Zm0 312h267.998v-51.998H298.001v51.998Zm0-156h363.998v-51.998H298.001v51.998Zm-69.692 290q-27.008 0-45.658-18.65-18.65-18.65-18.65-45.658v-503.382q0-27.008 18.65-45.658 18.65-18.65 45.658-18.65h503.382q27.008 0 45.658 18.65 18.65 18.65 18.65 45.658v503.382q0 27.008-18.65 45.658-18.65 18.65-45.658 18.65H228.309Zm0-51.999h503.382q4.616 0 8.463-3.846 3.846-3.847 3.846-8.463v-503.382q0-4.616-3.846-8.463-3.847-3.846-8.463-3.846H228.309q-4.616 0-8.463 3.846-3.846 3.847-3.846 8.463v503.382q0 4.616 3.846 8.463 3.847 3.846 8.463 3.846ZM216-744v528-528Z"/></svg>

After

Width:  |  Height:  |  Size: 722 B

5
packages/nc-gui/assets/nc-icons/check.svg

@ -0,0 +1,5 @@
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="check">
<path id="Vector" d="M13.3333 4.5L5.99996 11.8333L2.66663 8.5" stroke="#40444D" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 275 B

4
packages/nc-gui/assets/nc-icons/credit-card.svg

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 2.66602H1.99999C1.26361 2.66602 0.666656 3.26297 0.666656 3.99935V11.9993C0.666656 12.7357 1.26361 13.3327 1.99999 13.3327H14C14.7364 13.3327 15.3333 12.7357 15.3333 11.9993V3.99935C15.3333 3.26297 14.7364 2.66602 14 2.66602Z" stroke="#565B66" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M0.666656 6.66602H15.3333" stroke="#565B66" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 557 B

3
packages/nc-gui/assets/nc-icons/dashboard.svg

@ -0,0 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 5.33333C0.895432 5.33333 0 4.4379 0 3.33333V2C0 0.895432 0.895431 0 2 0H3.33333C4.4379 0 5.33333 0.895431 5.33333 2V3.33333C5.33333 4.4379 4.4379 5.33333 3.33333 5.33333H2ZM2 12C0.895432 12 0 11.1046 0 10V8.66667C0 7.5621 0.895431 6.66667 2 6.66667H3.33333C4.4379 6.66667 5.33333 7.5621 5.33333 8.66667V10C5.33333 11.1046 4.4379 12 3.33333 12H2ZM8.66667 5.33333C7.5621 5.33333 6.66667 4.4379 6.66667 3.33333V2C6.66667 0.895432 7.5621 0 8.66667 0H10C11.1046 0 12 0.895431 12 2V3.33333C12 4.4379 11.1046 5.33333 10 5.33333H8.66667ZM8.66667 12C7.5621 12 6.66667 11.1046 6.66667 10V8.66667C6.66667 7.5621 7.5621 6.66667 8.66667 6.66667H10C11.1046 6.66667 12 7.5621 12 8.66667V10C12 11.1046 11.1046 12 10 12H8.66667Z" fill="#27D665"/>
</svg>

After

Width:  |  Height:  |  Size: 845 B

3
packages/nc-gui/assets/nc-icons/database.svg

@ -0,0 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 5.14286C4.25029 5.14286 2.81286 4.90171 1.68771 4.41943C0.562571 3.93714 0 3.32114 0 2.57143C0 1.86914 0.589143 1.26486 1.76743 0.758571C2.94629 0.252857 4.35714 0 6 0C7.64286 0 9.05371 0.252857 10.2326 0.758571C11.4109 1.26486 12 1.86914 12 2.57143C12 3.32114 11.4374 3.93714 10.3123 4.41943C9.18714 4.90171 7.74971 5.14286 6 5.14286ZM6 8.57143C4.29771 8.57143 2.872 8.32429 1.72286 7.83C0.574286 7.33629 0 6.72629 0 6V4.32171C0 4.66686 0.160571 4.99114 0.481714 5.29457C0.803429 5.598 1.238 5.866 1.78543 6.09857C2.33343 6.33057 2.97029 6.51486 3.696 6.65143C4.42229 6.78857 5.19029 6.85714 6 6.85714C6.80971 6.85714 7.57771 6.78857 8.304 6.65143C9.02971 6.51486 9.66657 6.33057 10.2146 6.09857C10.762 5.866 11.1966 5.598 11.5183 5.29457C11.8394 4.99114 12 4.66686 12 4.32171V6C12 6.72629 11.4257 7.33629 10.2771 7.83C9.128 8.32429 7.70229 8.57143 6 8.57143ZM6 12C4.33314 12 2.91657 11.7411 1.75029 11.2234C0.583428 10.7057 0 10.0777 0 9.33943V7.66114C0 8.00629 0.163714 8.33657 0.491143 8.652C0.818571 8.96743 1.25914 9.24714 1.81286 9.49114C2.366 9.73514 3.00571 9.92857 3.732 10.0714C4.45829 10.2143 5.21429 10.2857 6 10.2857C6.78571 10.2857 7.54171 10.2143 8.268 10.0714C8.99429 9.92857 9.634 9.73514 10.1871 9.49114C10.7409 9.24714 11.1814 8.96743 11.5089 8.652C11.8363 8.33657 12 8.00629 12 7.66114V9.33943C12 10.0777 11.4166 10.7057 10.2497 11.2234C9.08343 11.7411 7.66686 12 6 12Z" fill="#3366FF"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

9
packages/nc-gui/assets/nc-icons/db.svg

@ -0,0 +1,9 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="16" height="16" rx="4" fill="currentColor"/>
<mask id="mask0_81_9662" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16">
<rect width="16" height="16" fill="#fff"/>
</mask>
<g mask="url(#mask0_81_9662)">
<path d="M7.9999 7.1999C6.36684 7.1999 5.02524 6.97484 3.9751 6.5247C2.92497 6.07457 2.3999 5.49964 2.3999 4.7999C2.3999 4.14444 2.94977 3.58044 4.0495 3.1079C5.14977 2.6359 6.46657 2.3999 7.9999 2.3999C9.53324 2.3999 10.85 2.6359 11.9503 3.1079C13.05 3.58044 13.5999 4.14444 13.5999 4.7999C13.5999 5.49964 13.0748 6.07457 12.0247 6.5247C10.9746 6.97484 9.63297 7.1999 7.9999 7.1999ZM7.9999 10.3999C6.4111 10.3999 5.08044 10.1692 4.0079 9.7079C2.9359 9.2471 2.3999 8.67777 2.3999 7.9999V6.4335C2.3999 6.75564 2.54977 7.0583 2.8495 7.3415C3.14977 7.6247 3.55537 7.87484 4.0663 8.0919C4.57777 8.30844 5.17217 8.48044 5.8495 8.6079C6.52737 8.7359 7.24417 8.7999 7.9999 8.7999C8.75564 8.7999 9.47244 8.7359 10.1503 8.6079C10.8276 8.48044 11.422 8.30844 11.9335 8.0919C12.4444 7.87484 12.85 7.6247 13.1503 7.3415C13.45 7.0583 13.5999 6.75564 13.5999 6.4335V7.9999C13.5999 8.67777 13.0639 9.2471 11.9919 9.7079C10.9194 10.1692 9.5887 10.3999 7.9999 10.3999ZM7.9999 13.5999C6.44417 13.5999 5.12204 13.3583 4.0335 12.8751C2.94444 12.3919 2.3999 11.8058 2.3999 11.1167V9.5503C2.3999 9.87244 2.5527 10.1807 2.8583 10.4751C3.1639 10.7695 3.5751 11.0306 4.0919 11.2583C4.60817 11.486 5.20524 11.6666 5.8831 11.7999C6.56097 11.9332 7.26657 11.9999 7.9999 11.9999C8.73324 11.9999 9.43884 11.9332 10.1167 11.7999C10.7946 11.6666 11.3916 11.486 11.9079 11.2583C12.4247 11.0306 12.8359 10.7695 13.1415 10.4751C13.4471 10.1807 13.5999 9.87244 13.5999 9.5503V11.1167C13.5999 11.8058 13.0554 12.3919 11.9663 12.8751C10.8778 13.3583 9.55564 13.5999 7.9999 13.5999Z" fill="white"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

9
packages/nc-gui/assets/nc-icons/doc.svg

@ -0,0 +1,9 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="16" height="16" rx="4" fill="currentColor"/>
<mask id="mask0_81_9671" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16">
<rect width="16" height="16" fill="#fff"/>
</mask>
<g mask="url(#mask0_81_9671)">
<path d="M5.3663 11.1663H8.6663C8.81083 11.1663 8.9359 11.1135 9.0415 11.0079C9.1471 10.9023 9.1999 10.7775 9.1999 10.6335C9.1999 10.489 9.1471 10.3639 9.0415 10.2583C8.9359 10.1527 8.81083 10.0999 8.6663 10.0999H5.3663C5.2223 10.0999 5.0975 10.1527 4.9919 10.2583C4.8863 10.3639 4.8335 10.489 4.8335 10.6335C4.8335 10.7775 4.8863 10.9023 4.9919 11.0079C5.0975 11.1135 5.2223 11.1663 5.3663 11.1663ZM5.3663 8.5335H10.6335C10.7775 8.5335 10.9023 8.4807 11.0079 8.3751C11.1135 8.2695 11.1663 8.14443 11.1663 7.9999C11.1663 7.85536 11.1135 7.7303 11.0079 7.6247C10.9023 7.5191 10.7775 7.4663 10.6335 7.4663H5.3663C5.2223 7.4663 5.0975 7.5191 4.9919 7.6247C4.8863 7.7303 4.8335 7.85536 4.8335 7.9999C4.8335 8.14443 4.8863 8.2695 4.9919 8.3751C5.0975 8.4807 5.2223 8.5335 5.3663 8.5335ZM5.3663 5.8999H10.6335C10.7775 5.8999 10.9023 5.8471 11.0079 5.7415C11.1135 5.6359 11.1663 5.51083 11.1663 5.3663C11.1663 5.2223 11.1135 5.0975 11.0079 4.9919C10.9023 4.8863 10.7775 4.8335 10.6335 4.8335H5.3663C5.2223 4.8335 5.0975 4.8863 4.9919 4.9919C4.8863 5.0975 4.8335 5.2223 4.8335 5.3663C4.8335 5.51083 4.8863 5.6359 4.9919 5.7415C5.0975 5.8471 5.2223 5.8999 5.3663 5.8999ZM3.5999 13.6663C3.24416 13.6663 2.94416 13.5442 2.6999 13.2999C2.45563 13.0556 2.3335 12.7556 2.3335 12.3999V3.5999C2.3335 3.24416 2.45563 2.94416 2.6999 2.6999C2.94416 2.45563 3.24416 2.3335 3.5999 2.3335H12.3999C12.7556 2.3335 13.0556 2.45563 13.2999 2.6999C13.5442 2.94416 13.6663 3.24416 13.6663 3.5999V12.3999C13.6663 12.7556 13.5442 13.0556 13.2999 13.2999C13.0556 13.5442 12.7556 13.6663 12.3999 13.6663H3.5999ZM3.5999 12.5999H12.3999C12.4442 12.5999 12.4887 12.5778 12.5335 12.5335C12.5778 12.4887 12.5999 12.4442 12.5999 12.3999V3.5999C12.5999 3.55563 12.5778 3.5111 12.5335 3.4663C12.4887 3.42203 12.4442 3.3999 12.3999 3.3999H3.5999C3.55563 3.3999 3.5111 3.42203 3.4663 3.4663C3.42203 3.5111 3.3999 3.55563 3.3999 3.5999V12.3999C3.3999 12.4442 3.42203 12.4887 3.4663 12.5335C3.5111 12.5778 3.55563 12.5999 3.5999 12.5999Z" fill="white"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

3
packages/nc-gui/assets/nc-icons/docs.svg

@ -0,0 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.66667 9.33333H7.33333V8H2.66667V9.33333ZM2.66667 6.66667H9.33333V5.33333H2.66667V6.66667ZM2.66667 4H9.33333V2.66667H2.66667V4ZM1.33333 12C0.966667 12 0.652778 11.8694 0.391667 11.6083C0.130556 11.3472 0 11.0333 0 10.6667V1.33333C0 0.966667 0.130556 0.652778 0.391667 0.391667C0.652778 0.130556 0.966667 0 1.33333 0H10.6667C11.0333 0 11.3472 0.130556 11.6083 0.391667C11.8694 0.652778 12 0.966667 12 1.33333V10.6667C12 11.0333 11.8694 11.3472 11.6083 11.6083C11.3472 11.8694 11.0333 12 10.6667 12H1.33333Z" fill="#FCBE3A"/>
</svg>

After

Width:  |  Height:  |  Size: 638 B

10
packages/nc-gui/assets/nc-icons/download.svg

@ -0,0 +1,10 @@
<svg width="20" height="20" viewBox="0 0 41 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="download">
<mask id="mask0_101_20734" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="41" height="40">
<rect id="Bounding box" x="0.5" width="40" height="40" fill="currentColor"/>
</mask>
<g mask="url(#mask0_101_20734)">
<path id="download_2" d="M20.4948 25.6453C20.3202 25.6453 20.1595 25.6143 20.0128 25.5523C19.8661 25.4903 19.7247 25.3913 19.5886 25.2553L14.2628 19.9295C14.062 19.7287 13.9618 19.4828 13.9621 19.1918C13.9625 18.9009 14.0684 18.6496 14.28 18.4381C14.4915 18.2266 14.7415 18.1194 15.0299 18.1165C15.3184 18.1137 15.5684 18.218 15.7799 18.4295L19.453 22.1198V8.25854C19.453 7.96137 19.5537 7.71258 19.755 7.51217C19.9564 7.31178 20.2064 7.21158 20.505 7.21158C20.8036 7.21158 21.0519 7.31178 21.2499 7.51217C21.4479 7.71258 21.5469 7.96137 21.5469 8.25854V22.1198L25.2371 18.4295C25.438 18.2287 25.6853 18.127 25.9791 18.1245C26.2729 18.122 26.5256 18.2266 26.7371 18.4381C26.9486 18.6496 27.0544 18.8982 27.0544 19.1838C27.0544 19.4694 26.9486 19.718 26.7371 19.9295L21.4113 25.2553C21.2752 25.3913 21.1321 25.4903 20.982 25.5523C20.8319 25.6143 20.6695 25.6453 20.4948 25.6453ZM10.6068 32.5C9.8905 32.5 9.27694 32.2446 8.76617 31.7338C8.25539 31.223 8 30.6095 8 29.8931V25.985C8 25.6878 8.10069 25.439 8.30208 25.2386C8.50344 25.0382 8.75343 24.938 9.05204 24.938C9.35065 24.938 9.59896 25.0382 9.79696 25.2386C9.99496 25.439 10.094 25.6878 10.094 25.985V29.8931C10.094 30.0213 10.1474 30.1389 10.2542 30.2457C10.3611 30.3526 10.4786 30.406 10.6068 30.406H30.3931C30.5213 30.406 30.6388 30.3526 30.7457 30.2457C30.8525 30.1389 30.906 30.0213 30.906 29.8931V25.985C30.906 25.6878 31.0066 25.439 31.208 25.2386C31.4094 25.0382 31.6594 24.938 31.958 24.938C32.2566 24.938 32.5049 25.0382 32.7029 25.2386C32.9009 25.439 32.9999 25.6878 32.9999 25.985V29.8931C32.9999 30.6095 32.7445 31.223 32.2337 31.7338C31.723 32.2446 31.1094 32.5 30.3931 32.5H10.6068Z" fill="currentColor"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

4
packages/nc-gui/assets/nc-icons/eye.svg

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.666504 8.00033C0.666504 8.00033 3.33317 2.66699 7.99984 2.66699C12.6665 2.66699 15.3332 8.00033 15.3332 8.00033C15.3332 8.00033 12.6665 13.3337 7.99984 13.3337C3.33317 13.3337 0.666504 8.00033 0.666504 8.00033Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 10C9.10457 10 10 9.10457 10 8C10 6.89543 9.10457 6 8 6C6.89543 6 6 6.89543 6 8C6 9.10457 6.89543 10 8 10Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 634 B

3
packages/nc-gui/assets/nc-icons/filter.svg

@ -0,0 +1,3 @@
<svg width="16" height="14" viewBox="0 0 16 14" stroke="currentColor" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.6668 1H1.3335L6.66683 7.30667V11.6667L9.3335 13V7.30667L14.6668 1Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 300 B

8
packages/nc-gui/assets/nc-icons/inbox.svg

@ -0,0 +1,8 @@
<svg width="49" height="48" viewBox="0 0 49 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_81_24829" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="49" height="48">
<rect x="0.5" width="48" height="48" fill="currentColor"/>
</mask>
<g mask="url(#mask0_81_24829)">
<path d="M11.25 40C10.4833 40 9.83333 39.7333 9.3 39.2C8.76667 38.6667 8.5 38.0167 8.5 37.25V10.75C8.5 9.98333 8.76667 9.33333 9.3 8.8C9.83333 8.26667 10.4833 8 11.25 8H37.75C38.5167 8 39.1667 8.26667 39.7 8.8C40.2333 9.33333 40.5 9.98333 40.5 10.75V37.25C40.5 38.0167 40.2333 38.6667 39.7 39.2C39.1667 39.7333 38.5167 40 37.75 40H11.25ZM11.25 38.45H37.75C38.0833 38.45 38.3667 38.3333 38.6 38.1C38.8333 37.8667 38.95 37.5833 38.95 37.25V31.3H31.2C30.5333 32.6333 29.5917 33.65 28.375 34.35C27.1583 35.05 25.8667 35.4 24.5 35.4C23.1333 35.4 21.8417 35.05 20.625 34.35C19.4083 33.65 18.4667 32.6333 17.8 31.3H10.05V37.25C10.05 37.5833 10.1667 37.8667 10.4 38.1C10.6333 38.3333 10.9167 38.45 11.25 38.45ZM24.5 33.85C25.6 33.85 26.6333 33.575 27.6 33.025C28.5667 32.475 29.3 31.6833 29.8 30.65C29.9667 30.3167 30.2333 30.0917 30.6 29.975C30.9667 29.8583 31.3333 29.8 31.7 29.8H38.95V10.75C38.95 10.4167 38.8333 10.1333 38.6 9.9C38.3667 9.66667 38.0833 9.55 37.75 9.55H11.25C10.9167 9.55 10.6333 9.66667 10.4 9.9C10.1667 10.1333 10.05 10.4167 10.05 10.75V29.8H17.3C17.6667 29.8 18.0333 29.8583 18.4 29.975C18.7667 30.0917 19.0333 30.3167 19.2 30.65C19.7 31.6833 20.4333 32.475 21.4 33.025C22.3667 33.575 23.4 33.85 24.5 33.85ZM11.25 38.45H10.05H38.95H37.75H11.25Z" fill="#1C1B1F"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

12
packages/nc-gui/assets/nc-icons/layers.svg

@ -0,0 +1,12 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_656_42199)">
<path d="M1.33325 11.334L7.99992 14.6673L14.6666 11.334" stroke="#565B66" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1.33325 8L7.99992 11.3333L14.6666 8" stroke="#565B66" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.99992 1.33398L1.33325 4.66732L7.99992 8.00065L14.6666 4.66732L7.99992 1.33398Z" stroke="#565B66" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_656_42199">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 712 B

5
packages/nc-gui/assets/nc-icons/layout.svg

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M12.6667 2H3.33333C2.59695 2 2 2.59695 2 3.33333V12.6667C2 13.403 2.59695 14 3.33333 14H12.6667C13.403 14 14 13.403 14 12.6667V3.33333C14 2.59695 13.403 2 12.6667 2Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 14V6" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2 6H14" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 602 B

8
packages/nc-gui/assets/nc-icons/list.svg

@ -0,0 +1,8 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.3335 12H14.0002" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2 12H2.00667" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.3335 8H14.0002" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2 8H2.00667" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.3335 4H14.0002" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2 4H2.00667" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 822 B

1
packages/nc-gui/assets/nc-icons/postgresql.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

4
packages/nc-gui/assets/nc-icons/search.svg

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.33333 12.6667C10.2789 12.6667 12.6667 10.2789 12.6667 7.33333C12.6667 4.38781 10.2789 2 7.33333 2C4.38781 2 2 4.38781 2 7.33333C2 10.2789 4.38781 12.6667 7.33333 12.6667Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14.0001 13.9996L11.1001 11.0996" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 518 B

5
packages/nc-gui/assets/nc-icons/share.svg

@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.6665 8V13.3333C2.6665 13.687 2.80698 14.0261 3.05703 14.2761C3.30708 14.5262 3.64622 14.6667 3.99984 14.6667H11.9998C12.3535 14.6667 12.6926 14.5262 12.9426 14.2761C13.1927 14.0261 13.3332 13.687 13.3332 13.3333V8" stroke="white" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.6668 3.99967L8.00016 1.33301L5.3335 3.99967" stroke="white" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 1.33301V9.99967" stroke="white" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 678 B

4
packages/nc-gui/assets/nc-icons/sidebar.svg

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.33333 2H12.6667C13.403 2 14 2.59695 14 3.33333V12.6667C14 13.403 13.403 14 12.6667 14H3.33333C2.59695 14 2 13.403 2 12.6667V3.33333C2 2.59695 2.59695 2 3.33333 2Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 2V14" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 486 B

6
packages/nc-gui/assets/nc-icons/users.svg

@ -0,0 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.3335 14.0002V12.6669C15.3331 12.0761 15.1364 11.5021 14.7744 11.0351C14.4124 10.5682 13.9056 10.2346 13.3335 10.0869" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.3332 14V12.6667C11.3332 11.9594 11.0522 11.2811 10.5521 10.781C10.052 10.281 9.37375 10 8.6665 10H3.33317C2.62593 10 1.94765 10.281 1.44755 10.781C0.947456 11.2811 0.666504 11.9594 0.666504 12.6667V14" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.6665 2.08691C11.2401 2.23378 11.7485 2.56738 12.1116 3.03512C12.4747 3.50286 12.6717 4.07813 12.6717 4.67025C12.6717 5.26236 12.4747 5.83763 12.1116 6.30537C11.7485 6.77311 11.2401 7.10671 10.6665 7.25358" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.00016 7.33333C7.47292 7.33333 8.66683 6.13943 8.66683 4.66667C8.66683 3.19391 7.47292 2 6.00016 2C4.5274 2 3.3335 3.19391 3.3335 4.66667C3.3335 6.13943 4.5274 7.33333 6.00016 7.33333Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

205
packages/nc-gui/assets/style.scss

@ -3,15 +3,33 @@
@import '@vue-flow/core/dist/theme-default.css'; @import '@vue-flow/core/dist/theme-default.css';
:root { :root {
--header-height: 42px; --sidebar-top-height: 9.75rem;
--toolbar-height: 48px; --topbar-height: 3.1rem;
--new-header-height: 3.5rem;
--tw-text-opacity: 1; --tw-text-opacity: 1;
--navbar-bg: #FAFAFA; --navbar-bg: #FAFAFA;
--navbar-border: #e0e0e0; --navbar-border: #e0e0e0;
--nc-grid-bg: #fdfdfd;
}
::-moz-selection {
/* Code for Firefox */
color: inherit;
background-color: #3366ff20;
}
::selection {
color: inherit;
background-color: #3366ff20;
}
.ant-layout-content {
overflow-x: visible !important;
overflow-y: visible !important;
} }
.ant-layout-header { .ant-layout-header {
height: var(--header-height) !important; height: var(--topbar-height) !important;
} }
html, html,
@ -22,6 +40,79 @@ main {
@apply m-0 h-full w-full bg-white; @apply m-0 h-full w-full bg-white;
} }
.nc-input-md {
@apply !rounded-lg !py-2 !px-3 mb-1;
}
.nc-scrollbar-md {
overflow-y: scroll;
overflow-x: hidden;
&::-webkit-scrollbar {
width: 4px;
height: 4px;
}
&::-webkit-scrollbar-track-piece {
width: 0px;
}
&::-webkit-scrollbar {
@apply bg-transparent;
}
&::-webkit-scrollbar-thumb {
width: 4px;
@apply bg-gray-300 ;
}
&::-webkit-scrollbar-thumb:hover {
@apply bg-gray-400;
}
}
.nc-scrollbar-dark-md {
overflow-y: scroll;
overflow-x: hidden;
&::-webkit-scrollbar {
width: 4px;
height: 4px;
}
&::-webkit-scrollbar-track-piece {
width: 0px;
}
&::-webkit-scrollbar {
@apply bg-transparent;
}
&::-webkit-scrollbar-thumb {
width: 4px;
@apply bg-gray-200 ;
}
&::-webkit-scrollbar-thumb:hover {
@apply bg-gray-300;
}
}
.nc-scrollbar-x-md {
overflow-x: scroll;
&::-webkit-scrollbar {
width: 4px;
height: 4px;
}
&::-webkit-scrollbar-track-piece {
width: 0px;
}
&::-webkit-scrollbar {
@apply bg-transparent;
}
&::-webkit-scrollbar-thumb {
width: 4px;
@apply bg-gray-200 ;
}
&::-webkit-scrollbar-thumb:hover {
@apply bg-gray-300;
}
}
html { html {
overflow-y: auto !important; overflow-y: auto !important;
} }
@ -39,6 +130,13 @@ a {
@apply color-transition inline; @apply color-transition inline;
} }
.ant-select {
@apply !rounded-md;
.ant-select-selector {
@apply !rounded-md;
}
}
// menu item styling // menu item styling
.nc-menu-item { .nc-menu-item {
@apply cursor-pointer text-xs flex items-center gap-2 px-4 py-3 relative after:(content-[''] absolute top-0 left-0 bottom-0 w-full h-full right-0 bg-current opacity-0 transition transition-opactity duration-100) hover:(after:(opacity-5)); @apply cursor-pointer text-xs flex items-center gap-2 px-4 py-3 relative after:(content-[''] absolute top-0 left-0 bottom-0 w-full h-full right-0 bg-current opacity-0 transition transition-opactity duration-100) hover:(after:(opacity-5));
@ -49,6 +147,16 @@ a {
} }
.nc-project-menu-item { .nc-project-menu-item {
@apply cursor-pointer flex items-center gap-2 py-2 after:(content-[''] absolute top-0 left-0 bottom-0 right-0 w-full h-full bg-current opacity-0 transition transition-opacity duration-100) hover:(after:(opacity-5));
// &:hover {
// .nc-icon {
// @apply text-accent;
// }
// }
}
.nc-workspace-menu-item {
@apply cursor-pointer flex items-center gap-2 py-2 hover:text-primary after:(content-[''] absolute top-0 left-0 bottom-0 right-0 w-full h-full bg-current opacity-0 transition transition-opactity duration-100) hover:(after:(opacity-5)); @apply cursor-pointer flex items-center gap-2 py-2 hover:text-primary after:(content-[''] absolute top-0 left-0 bottom-0 right-0 w-full h-full bg-current opacity-0 transition transition-opactity duration-100) hover:(after:(opacity-5));
&:hover { &:hover {
@ -90,19 +198,16 @@ a {
// badge with count // badge with count
.nc-count-badge { .nc-count-badge {
@apply absolute flex items-center top-[-6px] right-[-6px] px-1 min-w-[14px] h-[14px] rounded-full bg-primary bg-opacity-100 text-white !text-[9px] !z-21; @apply absolute flex items-center top-[-6px] right-[-6px] px-1 min-w-[14px] h-[14px] rounded-full bg-gray-500 !text-[9px] text-white !z-21;
} }
// for highlighting toolbar menu item // for highlighting toolbar menu item
.nc-active-btn > .ant-btn { .nc-active-btn > .ant-btn {
@apply bg-primary bg-opacity-20 hover:(bg-primary bg-opacity-20); @apply bg-gray-100;
} }
// for highlighing toolbar kanban menu item // for highlighing toolbar kanban menu item
.nc-kanban-btn > .ant-btn {
@apply bg-green-400 bg-opacity-20 hover:(bg-primary bg-opacity-20);
}
.nc-locked-overlay { .nc-locked-overlay {
@apply absolute h-full w-full z-2 top-0 left-0; @apply absolute h-full w-full z-2 top-0 left-0;
@ -284,7 +389,7 @@ a {
} }
.nc-toolbar-btn { .nc-toolbar-btn {
@apply !shadow-none rounded hover:(ring-1 ring-primary ring-opacity-100) focus:(ring-1 ring-accent ring-opacity-100); @apply !shadow-none rounded hover:(ring-1 ring-gray-200 ring-opacity-100 bg-gray-100 !text-gray-800) focus:(ring-1 ring-gray-300 ring-opacity-100 !text-gray-800 bg-gray-100) text-gray-600 text-xs font-medium px-2 border-0;
} }
.nc-warning-info { .nc-warning-info {
@ -292,7 +397,17 @@ a {
} }
.ant-modal { .ant-modal {
@apply !top-[50px]; @apply !top-[50px] ;
}
.ant-modal-content {
@apply !p-6;
border-radius: 1rem;
}
.ant-modal-body {
@apply !p-0;
}
.ant-modal-confirm-body-wrapper{
@apply p-4;
} }
.ant-select-item-option-active:not(.ant-select-item-option-disabled) { .ant-select-item-option-active:not(.ant-select-item-option-disabled) {
@ -331,5 +446,73 @@ a {
.nc-icon-transition { .nc-icon-transition {
@apply transform transition-transform !hover:(scale-115) !active:(scale-100) @apply transform transition-transform !hover:(scale-115 text-shadow-sm) !active:(scale-100)
}
.nc-animation-pulse {
animation: pulse 2s linear infinite;
}
@keyframes pulse {
0% {
opacity: 0.2;
}
50% {
opacity: 1;
}
100% {
opacity: 0.2;
}
}
.nc-menu-item-wrapper {
@apply flex flex-row items-center py-3 gap-2;
}
.nc-click-transition {
@apply transform transition-transform transition-color !text-gray-400 !hover:(scale-130 !text-gray-500) !active:(scale-100 !text-gray-500)
}
.nc-click-transition-1 {
@apply transform transition-transform !hover:(scale-120) !active:(scale-100)
}
@keyframes pop-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.pop-in-animation {
-webkit-animation: pop-in 0.075s cubic-bezier(0, 0, 0.6, -0.13);
animation: pop-in 0.075s cubic-bezier(0, 0, 0.6, -0.13);
}
.pop-in-animation-med-delay {
-webkit-animation: pop-in 0.15s cubic-bezier(0, 0, 0.6, -0.13);
animation: pop-in 0.15s cubic-bezier(0, 0, 0.6, -0.13);
}
// button
.nc-btn{
@apply rounded py-2 px-4 border-gray-200 h-auto
}
.ant-popover-inner {
padding: 0 !important;
}
.ant-popover-inner-content {
@apply !px-1.5 !py-1 text-xs text-white bg-black;
}
.ant-tooltip-inner {
@apply !px-1.5 !py-1 text-xs text-white bg-black;
min-height: 0 !important;
}
.ant-skeleton-input {
@apply !h-full;
} }

37
packages/nc-gui/assets/style/fonts.css

@ -151,32 +151,17 @@
} }
/* material-symbols-outlined-200 - latin */ /* // href: 'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20,200,0,-25',
@font-face {
font-family: 'Material Symbols Outlined';
font-style: normal;
font-weight: 200;
src: url('./material-symbols/material-symbols-outlined-v92-latin-200.eot'); /* IE9 Compat Modes */
src: url('./material-symbols/material-symbols-outlined-v92-latin-200.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('./material-symbols/material-symbols-outlined-v92-latin-200.woff2') format('woff2'), /* Super Modern Browsers */
url('./material-symbols/material-symbols-outlined-v92-latin-200.woff') format('woff'), /* Modern Browsers */
url('./material-symbols/material-symbols-outlined-v92-latin-200.ttf') format('truetype'), /* Safari, Android, iOS */
url('./material-symbols/material-symbols-outlined-v92-latin-200.svg#MaterialSymbolsOutlined') format('svg'); /* Legacy iOS */
}
/* // href: 'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20,200,0,-25',
*/ */
/* fallback */
@font-face { @font-face {
font-family: 'Material Symbols Outlined'; font-family: 'Material Symbols';
font-style: normal; font-style: normal;
font-weight: 200; font-weight: 300;
src: url(./material.woff2) format('woff2'); src: url(./material.woff2) format('woff2');
} }
.material-symbols-outlined { .material-symbols {
font-family: 'Material Symbols Outlined'; font-family: 'Material Symbols';
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
line-height: 1; line-height: 1;
@ -186,8 +171,16 @@
white-space: nowrap; white-space: nowrap;
word-wrap: normal; word-wrap: normal;
direction: ltr; direction: ltr;
font-weight: 300;
-webkit-font-feature-settings: 'liga'; -webkit-font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
font-size: 22px !important; font-size: 1rem;
user-select: none; user-select: none;
}
.nc-fonts-not-loaded {
.material-symbols {
opacity: 0;
width: 0 !important;
}
} }

BIN
packages/nc-gui/assets/style/material.woff2

Binary file not shown.

60
packages/nc-gui/components.d.ts vendored

@ -9,6 +9,8 @@ declare module '@vue/runtime-core' {
export interface GlobalComponents { export interface GlobalComponents {
AAlert: typeof import('ant-design-vue/es')['Alert'] AAlert: typeof import('ant-design-vue/es')['Alert']
AAutoComplete: typeof import('ant-design-vue/es')['AutoComplete'] AAutoComplete: typeof import('ant-design-vue/es')['AutoComplete']
AAvatar: typeof import('ant-design-vue/es')['Avatar']
ABadge: typeof import('ant-design-vue/es')['Badge']
ABadgeRibbon: typeof import('ant-design-vue/es')['BadgeRibbon'] ABadgeRibbon: typeof import('ant-design-vue/es')['BadgeRibbon']
ABreadcrumb: typeof import('ant-design-vue/es')['Breadcrumb'] ABreadcrumb: typeof import('ant-design-vue/es')['Breadcrumb']
ABreadcrumbItem: typeof import('ant-design-vue/es')['BreadcrumbItem'] ABreadcrumbItem: typeof import('ant-design-vue/es')['BreadcrumbItem']
@ -47,8 +49,6 @@ declare module '@vue/runtime-core' {
AMenuItem: typeof import('ant-design-vue/es')['MenuItem'] AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
AMenuItemGroup: typeof import('ant-design-vue/es')['MenuItemGroup'] AMenuItemGroup: typeof import('ant-design-vue/es')['MenuItemGroup']
AModal: typeof import('ant-design-vue/es')['Modal'] AModal: typeof import('ant-design-vue/es')['Modal']
AntDesignMenuFoldOutlined: typeof import('~icons/ant-design/menu-fold-outlined')['default']
AntDesignMenuUnfoldOutlined: typeof import('~icons/ant-design/menu-unfold-outlined')['default']
APagination: typeof import('ant-design-vue/es')['Pagination'] APagination: typeof import('ant-design-vue/es')['Pagination']
ARadio: typeof import('ant-design-vue/es')['Radio'] ARadio: typeof import('ant-design-vue/es')['Radio']
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup'] ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
@ -58,6 +58,7 @@ declare module '@vue/runtime-core' {
ASelectOption: typeof import('ant-design-vue/es')['SelectOption'] ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
ASkeleton: typeof import('ant-design-vue/es')['Skeleton'] ASkeleton: typeof import('ant-design-vue/es')['Skeleton']
ASkeletonImage: typeof import('ant-design-vue/es')['SkeletonImage'] ASkeletonImage: typeof import('ant-design-vue/es')['SkeletonImage']
ASkeletonInput: typeof import('ant-design-vue/es')['SkeletonInput']
ASpin: typeof import('ant-design-vue/es')['Spin'] ASpin: typeof import('ant-design-vue/es')['Spin']
ASubMenu: typeof import('ant-design-vue/es')['SubMenu'] ASubMenu: typeof import('ant-design-vue/es')['SubMenu']
ASwitch: typeof import('ant-design-vue/es')['Switch'] ASwitch: typeof import('ant-design-vue/es')['Switch']
@ -67,6 +68,8 @@ declare module '@vue/runtime-core' {
ATabs: typeof import('ant-design-vue/es')['Tabs'] ATabs: typeof import('ant-design-vue/es')['Tabs']
ATag: typeof import('ant-design-vue/es')['Tag'] ATag: typeof import('ant-design-vue/es')['Tag']
ATextarea: typeof import('ant-design-vue/es')['Textarea'] ATextarea: typeof import('ant-design-vue/es')['Textarea']
ATimeline: typeof import('ant-design-vue/es')['Timeline']
ATimelineItem: typeof import('ant-design-vue/es')['TimelineItem']
ATimePicker: typeof import('ant-design-vue/es')['TimePicker'] ATimePicker: typeof import('ant-design-vue/es')['TimePicker']
ATooltip: typeof import('ant-design-vue/es')['Tooltip'] ATooltip: typeof import('ant-design-vue/es')['Tooltip']
ATree: typeof import('ant-design-vue/es')['Tree'] ATree: typeof import('ant-design-vue/es')['Tree']
@ -79,10 +82,12 @@ declare module '@vue/runtime-core' {
ClarityColorPickerSolid: typeof import('~icons/clarity/color-picker-solid')['default'] ClarityColorPickerSolid: typeof import('~icons/clarity/color-picker-solid')['default']
ClaritySuccessLine: typeof import('~icons/clarity/success-line')['default'] ClaritySuccessLine: typeof import('~icons/clarity/success-line')['default']
IcBaselineMoreVert: typeof import('~icons/ic/baseline-more-vert')['default'] IcBaselineMoreVert: typeof import('~icons/ic/baseline-more-vert')['default']
IcOutlineAccessTime: typeof import('~icons/ic/outline-access-time')['default']
IcOutlineInsertDriveFile: typeof import('~icons/ic/outline-insert-drive-file')['default'] IcOutlineInsertDriveFile: typeof import('~icons/ic/outline-insert-drive-file')['default']
IcRoundEdit: typeof import('~icons/ic/round-edit')['default'] IcRoundEdit: typeof import('~icons/ic/round-edit')['default']
IcRoundKeyboardArrowDown: typeof import('~icons/ic/round-keyboard-arrow-down')['default'] IcRoundKeyboardArrowDown: typeof import('~icons/ic/round-keyboard-arrow-down')['default']
IcRoundSearch: typeof import('~icons/ic/round-search')['default'] IcRoundSearch: typeof import('~icons/ic/round-search')['default']
IcRoundStarBorder: typeof import('~icons/ic/round-star-border')['default']
IcTwotoneWidthFull: typeof import('~icons/ic/twotone-width-full')['default'] IcTwotoneWidthFull: typeof import('~icons/ic/twotone-width-full')['default']
IcTwotoneWidthNormal: typeof import('~icons/ic/twotone-width-normal')['default'] IcTwotoneWidthNormal: typeof import('~icons/ic/twotone-width-normal')['default']
LogosGoogleGmail: typeof import('~icons/logos/google-gmail')['default'] LogosGoogleGmail: typeof import('~icons/logos/google-gmail')['default']
@ -95,53 +100,94 @@ declare module '@vue/runtime-core' {
MaterialSymbolsAttachFile: typeof import('~icons/material-symbols/attach-file')['default'] MaterialSymbolsAttachFile: typeof import('~icons/material-symbols/attach-file')['default']
MaterialSymbolsChevronRightRounded: typeof import('~icons/material-symbols/chevron-right-rounded')['default'] MaterialSymbolsChevronRightRounded: typeof import('~icons/material-symbols/chevron-right-rounded')['default']
MaterialSymbolsCloseRounded: typeof import('~icons/material-symbols/close-rounded')['default'] MaterialSymbolsCloseRounded: typeof import('~icons/material-symbols/close-rounded')['default']
MaterialSymbolsCreditCardOutline: typeof import('~icons/material-symbols/credit-card-outline')['default']
MaterialSymbolsDarkModeOutline: typeof import('~icons/material-symbols/dark-mode-outline')['default'] MaterialSymbolsDarkModeOutline: typeof import('~icons/material-symbols/dark-mode-outline')['default']
MaterialSymbolsDeleteOutlineRounded: typeof import('~icons/material-symbols/delete-outline-rounded')['default']
MaterialSymbolsFileCopyOutline: typeof import('~icons/material-symbols/file-copy-outline')['default'] MaterialSymbolsFileCopyOutline: typeof import('~icons/material-symbols/file-copy-outline')['default']
MaterialSymbolsGroupOutlineRounded: typeof import('~icons/material-symbols/group-outline-rounded')['default']
MaterialSymbolsInboxOutlineRounded: typeof import('~icons/material-symbols/inbox-outline-rounded')['default']
MaterialSymbolsKeyboardArrowDownRounded: typeof import('~icons/material-symbols/keyboard-arrow-down-rounded')['default']
MaterialSymbolsKeyboardReturn: typeof import('~icons/material-symbols/keyboard-return')['default'] MaterialSymbolsKeyboardReturn: typeof import('~icons/material-symbols/keyboard-return')['default']
MaterialSymbolsLightModeOutline: typeof import('~icons/material-symbols/light-mode-outline')['default'] MaterialSymbolsLightModeOutline: typeof import('~icons/material-symbols/light-mode-outline')['default']
MaterialSymbolsLockOutline: typeof import('~icons/material-symbols/lock-outline')['default']
MaterialSymbolsMobileFriendly: typeof import('~icons/material-symbols/mobile-friendly')['default'] MaterialSymbolsMobileFriendly: typeof import('~icons/material-symbols/mobile-friendly')['default']
MaterialSymbolsPublic: typeof import('~icons/material-symbols/public')['default']
MaterialSymbolsRocketLaunchOutline: typeof import('~icons/material-symbols/rocket-launch-outline')['default'] MaterialSymbolsRocketLaunchOutline: typeof import('~icons/material-symbols/rocket-launch-outline')['default']
MaterialSymbolsSendOutline: typeof import('~icons/material-symbols/send-outline')['default'] MaterialSymbolsSendOutline: typeof import('~icons/material-symbols/send-outline')['default']
MaterialSymbolsTranslate: typeof import('~icons/material-symbols/translate')['default'] MaterialSymbolsTranslate: typeof import('~icons/material-symbols/translate')['default']
MaterialSymbolsVisibility: typeof import('~icons/material-symbols/visibility')['default'] MaterialSymbolsVisibility: typeof import('~icons/material-symbols/visibility')['default']
MaterialSymbolsVisibilityOff: typeof import('~icons/material-symbols/visibility-off')['default'] MaterialSymbolsVisibilityOff: typeof import('~icons/material-symbols/visibility-off')['default']
MaterialSymbolsWarning: typeof import('~icons/material-symbols/warning')['default'] MaterialSymbolsWarning: typeof import('~icons/material-symbols/warning')['default']
MdiAccount: typeof import('~icons/mdi/account')['default']
MdiAccountCircleOutline: typeof import('~icons/mdi/account-circle-outline')['default']
MdiAccountPlusOutline: typeof import('~icons/mdi/account-plus-outline')['default']
MdiAccountSupervisorOutline: typeof import('~icons/mdi/account-supervisor-outline')['default'] MdiAccountSupervisorOutline: typeof import('~icons/mdi/account-supervisor-outline')['default']
MdiAlpha: typeof import('~icons/mdi/alpha')['default'] MdiAlpha: typeof import('~icons/mdi/alpha')['default']
MdiAppleKeyboardShift: typeof import('~icons/mdi/apple-keyboard-shift')['default'] MdiAppleKeyboardShift: typeof import('~icons/mdi/apple-keyboard-shift')['default']
MdiArrowDownDropCircle: typeof import('~icons/mdi/arrow-down-drop-circle')['default'] MdiArrowDownDropCircle: typeof import('~icons/mdi/arrow-down-drop-circle')['default']
MdiArrowDownDropCircleOutline: typeof import('~icons/mdi/arrow-down-drop-circle-outline')['default'] MdiArrowDownDropCircleOutline: typeof import('~icons/mdi/arrow-down-drop-circle-outline')['default']
MdiArrowLeftBold: typeof import('~icons/mdi/arrow-left-bold')['default'] MdiArrowLeft: typeof import('~icons/mdi/arrow-left')['default']
MdiArrowULeftBottom: typeof import('~icons/mdi/arrow-u-left-bottom')['default'] MdiArrowULeftBottom: typeof import('~icons/mdi/arrow-u-left-bottom')['default']
MdiBugOutline: typeof import('~icons/mdi/bug-outline')['default'] MdiBookOpenBlankVariant: typeof import('~icons/mdi/book-open-blank-variant')['default']
MdiCalculatorVariant: typeof import('~icons/mdi/calculator-variant')['default']
MdiCardsHeart: typeof import('~icons/mdi/cards-heart')['default'] MdiCardsHeart: typeof import('~icons/mdi/cards-heart')['default']
MdiCellphoneMessage: typeof import('~icons/mdi/cellphone-message')['default'] MdiCellphoneMessage: typeof import('~icons/mdi/cellphone-message')['default']
MdiChat: typeof import('~icons/mdi/chat')['default'] MdiChat: typeof import('~icons/mdi/chat')['default']
MdiChatProcessingOutline: typeof import('~icons/mdi/chat-processing-outline')['default'] MdiChatProcessingOutline: typeof import('~icons/mdi/chat-processing-outline')['default']
MdiCheck: typeof import('~icons/mdi/check')['default']
MdiChevronDown: typeof import('~icons/mdi/chevron-down')['default']
MdiChevronLeft: typeof import('~icons/mdi/chevron-left')['default']
MdiChevronRight: typeof import('~icons/mdi/chevron-right')['default']
MdiCircleMedium: typeof import('~icons/mdi/circle-medium')['default']
MdiClose: typeof import('~icons/mdi/close')['default'] MdiClose: typeof import('~icons/mdi/close')['default']
MdiCloseCircleOutline: typeof import('~icons/mdi/close-circle-outline')['default']
MdiCodeTags: typeof import('~icons/mdi/code-tags')['default']
MdiContentCopy: typeof import('~icons/mdi/content-copy')['default']
MdiCurrencyUsd: typeof import('~icons/mdi/currency-usd')['default'] MdiCurrencyUsd: typeof import('~icons/mdi/currency-usd')['default']
MdiDatabaseOutline: typeof import('~icons/mdi/database-outline')['default']
MdiDeleteOutline: typeof import('~icons/mdi/delete-outline')['default']
MdiDiscord: typeof import('~icons/mdi/discord')['default'] MdiDiscord: typeof import('~icons/mdi/discord')['default']
MdiDotsHorizontal: typeof import('~icons/mdi/dots-horizontal')['default']
MdiDotsVertical: typeof import('~icons/mdi/dots-vertical')['default']
MdiEarth: typeof import('~icons/mdi/earth')['default']
MdiEditOutline: typeof import('~icons/mdi/edit-outline')['default'] MdiEditOutline: typeof import('~icons/mdi/edit-outline')['default']
MdiFlag: typeof import('~icons/mdi/flag')['default'] MdiFlag: typeof import('~icons/mdi/flag')['default']
MdiGestureDoubleTap: typeof import('~icons/mdi/gesture-double-tap')['default'] MdiFolder: typeof import('~icons/mdi/folder')['default']
MdiHeart: typeof import('~icons/mdi/heart')['default'] MdiHeart: typeof import('~icons/mdi/heart')['default']
MdiHistory: typeof import('~icons/mdi/history')['default']
MdiKeyStar: typeof import('~icons/mdi/key-star')['default'] MdiKeyStar: typeof import('~icons/mdi/key-star')['default']
MdiLinkVariant: typeof import('~icons/mdi/link-variant')['default']
MdiLogin: typeof import('~icons/mdi/login')['default']
MdiLogout: typeof import('~icons/mdi/logout')['default']
MdiMapMarkerOutline: typeof import('~icons/mdi/map-marker-outline')['default']
MdiMenuDown: typeof import('~icons/mdi/menu-down')['default'] MdiMenuDown: typeof import('~icons/mdi/menu-down')['default']
MdiMicrosoftTeams: typeof import('~icons/mdi/microsoft-teams')['default'] MdiMicrosoftTeams: typeof import('~icons/mdi/microsoft-teams')['default']
MdiMoonFull: typeof import('~icons/mdi/moon-full')['default'] MdiMoonFull: typeof import('~icons/mdi/moon-full')['default']
MdiPlus: typeof import('~icons/mdi/plus')['default']
MdiPlusOutline: typeof import('~icons/mdi/plus-outline')['default']
MdiRefresh: typeof import('~icons/mdi/refresh')['default']
MdiReload: typeof import('~icons/mdi/reload')['default']
MdiRocketLaunchOutline: typeof import('~icons/mdi/rocket-launch-outline')['default'] MdiRocketLaunchOutline: typeof import('~icons/mdi/rocket-launch-outline')['default']
MdiScriptTextOutline: typeof import('~icons/mdi/script-text-outline')['default'] MdiScriptTextOutline: typeof import('~icons/mdi/script-text-outline')['default']
MdiShieldKeyOutline: typeof import('~icons/mdi/shield-key-outline')['default'] MdiShieldKeyOutline: typeof import('~icons/mdi/shield-key-outline')['default']
MdiSlack: typeof import('~icons/mdi/slack')['default'] MdiSlack: typeof import('~icons/mdi/slack')['default']
MdiStar: typeof import('~icons/mdi/star')['default'] MdiStar: typeof import('~icons/mdi/star')['default']
MdiStarOutline: typeof import('~icons/mdi/star-outline')['default'] MdiStarOutline: typeof import('~icons/mdi/star-outline')['default']
MdiTestTube: typeof import('~icons/mdi/test-tube')['default'] MdiStickerCheckOutline: typeof import('~icons/mdi/sticker-check-outline')['default']
MdiTable: typeof import('~icons/mdi/table')['default']
MdiTableColumnPlusAfter: typeof import('~icons/mdi/table-column-plus-after')['default']
MdiThumbUp: typeof import('~icons/mdi/thumb-up')['default'] MdiThumbUp: typeof import('~icons/mdi/thumb-up')['default']
MdiTick: typeof import('~icons/mdi/tick')['default']
MdiWhatsapp: typeof import('~icons/mdi/whatsapp')['default'] MdiWhatsapp: typeof import('~icons/mdi/whatsapp')['default']
MiCircleWarning: typeof import('~icons/mi/circle-warning')['default'] MiCircleWarning: typeof import('~icons/mi/circle-warning')['default']
NcIconsInbox: typeof import('~icons/nc-icons/inbox')['default']
PhBook: typeof import('~icons/ph/book')['default']
PhDownloadSimpleFill: typeof import('~icons/ph/download-simple-fill')['default']
PhFileCsv: typeof import('~icons/ph/file-csv')['default'] PhFileCsv: typeof import('~icons/ph/file-csv')['default']
PhMagnifyingGlassBold: typeof import('~icons/ph/magnifying-glass-bold')['default']
PhTriangleFill: typeof import('~icons/ph/triangle-fill')['default']
PhUsersBold: typeof import('~icons/ph/users-bold')['default']
PhXCircleLight: typeof import('~icons/ph/x-circle-light')['default'] PhXCircleLight: typeof import('~icons/ph/x-circle-light')['default']
RiExternalLinkLine: typeof import('~icons/ri/external-link-line')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
SimpleIconsMicrosoftsqlserver: typeof import('~icons/simple-icons/microsoftsqlserver')['default'] SimpleIconsMicrosoftsqlserver: typeof import('~icons/simple-icons/microsoftsqlserver')['default']

24
packages/nc-gui/components/account/License.vue

@ -9,19 +9,19 @@ const { $e } = useNuxtApp()
const { loadAppInfo } = useGlobal() const { loadAppInfo } = useGlobal()
let key = $ref('') let key = ref('')
const loadLicense = async () => { const loadLicense = async () => {
try { try {
const response = await api.orgLicense.get() const response = await api.orgLicense.get()
key = response.key! key.value = response.key!
} catch (e: any) { } catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e)) message.error(await extractSdkResponseErrorMsg(e))
} }
} }
const setLicense = async () => { const setLicense = async () => {
try { try {
await api.orgLicense.set({ key: key }) await api.orgLicense.set({ key: key.value })
message.success('License key updated') message.success('License key updated')
await loadAppInfo() await loadAppInfo()
} catch (e: any) { } catch (e: any) {
@ -35,14 +35,14 @@ loadLicense()
<template> <template>
<div class="h-full overflow-y-scroll scrollbar-thin-dull"> <div class="h-full overflow-y-scroll scrollbar-thin-dull">
<div class="text-xl mt-4 mb-8 text-center font-weight-bold">License</div> <!-- <div class="text-xl mt-4 mb-8 text-center font-weight-bold">License</div>-->
<div class="mx-auto w-150"> <!-- <div class="mx-auto w-150">-->
<div> <!-- <div>-->
<a-textarea v-model:value="key" placeholder="License key" class="!mt-2 !max-w-[600px]"></a-textarea> <!-- <a-textarea v-model:value="key" placeholder="License key" class="!mt-2 !max-w-[600px]"></a-textarea>-->
</div> <!-- </div>-->
<div class="text-center"> <!-- <div class="text-center">-->
<a-button class="mt-4 !h-[2.2rem] !rounded-md" @click="setLicense" type="primary">Save license key</a-button> <!-- <a-button class="mt-4 !h-[2.2rem] !rounded-md" @click="setLicense" type="primary">Save license key</a-button>-->
</div> <!-- </div>-->
</div> <!-- </div>-->
</div> </div>
</template> </template>

6
packages/nc-gui/components/account/SignupSettings.vue

@ -6,12 +6,12 @@ const { api } = useApi()
const { t } = useI18n() const { t } = useI18n()
let settings = $ref<{ invite_only_signup?: boolean }>({ invite_only_signup: false }) const settings = ref<{ invite_only_signup?: boolean }>({ invite_only_signup: false })
const loadSettings = async () => { const loadSettings = async () => {
try { try {
const response = await api.orgAppSettings.get() const response = await api.orgAppSettings.get()
settings = response settings.value = response
} catch (e: any) { } catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e)) message.error(await extractSdkResponseErrorMsg(e))
} }
@ -19,7 +19,7 @@ const loadSettings = async () => {
const saveSettings = async () => { const saveSettings = async () => {
try { try {
await api.orgAppSettings.set(settings) await api.orgAppSettings.set(settings.value)
message.success(t('msg.success.settingsSaved')) message.success(t('msg.success.settingsSaved'))
} catch (e: any) { } catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e)) message.error(await extractSdkResponseErrorMsg(e))

88
packages/nc-gui/components/account/Token.vue

@ -1,8 +1,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { VNodeRef } from '@vue/runtime-core' import type { VNodeRef } from '@vue/runtime-core'
import { Empty, Modal, message } from 'ant-design-vue' import { Empty, message } from 'ant-design-vue'
import type { ApiTokenType, RequestParams, UserType } from 'nocodb-sdk' import type { ApiTokenType, RequestParams, UserType } from 'nocodb-sdk'
import { extractSdkResponseErrorMsg, iconMap, useApi, useCopy, useNuxtApp } from '#imports' import { extractSdkResponseErrorMsg, iconMap, ref, useApi, useCopy, useNuxtApp } from '#imports'
const { api, isLoading } = useApi() const { api, isLoading } = useApi()
@ -12,15 +12,15 @@ const { copy } = useCopy()
const { t } = useI18n() const { t } = useI18n()
let tokens = $ref<UserType[]>([]) const tokens = ref<UserType[]>([])
let currentPage = $ref(1) const currentPage = ref(1)
let showNewTokenModal = $ref(false) const showNewTokenModal = ref(false)
const currentLimit = $ref(10) const currentLimit = ref(10)
let selectedTokenData = $ref<ApiTokenType>({}) const selectedTokenData = ref<ApiTokenType>({})
const searchText = ref<string>('') const searchText = ref<string>('')
@ -28,8 +28,8 @@ const pagination = reactive({
total: 0, total: 0,
pageSize: 10, pageSize: 10,
}) })
const loadTokens = async (page = currentPage, limit = currentLimit) => { const loadTokens = async (page = currentPage.value, limit = currentLimit.value) => {
currentPage = page currentPage.value = page
try { try {
const response: any = await api.orgTokens.list({ const response: any = await api.orgTokens.list({
query: { query: {
@ -42,7 +42,7 @@ const loadTokens = async (page = currentPage, limit = currentLimit) => {
pagination.total = response.pageInfo.totalRows ?? 0 pagination.total = response.pageInfo.totalRows ?? 0
pagination.pageSize = 10 pagination.pageSize = 10
tokens = response.list as UserType[] tokens.value = response.list as UserType[]
} catch (e: any) { } catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e)) message.error(await extractSdkResponseErrorMsg(e))
} }
@ -50,30 +50,37 @@ const loadTokens = async (page = currentPage, limit = currentLimit) => {
loadTokens() loadTokens()
const deleteToken = async (token: string) => { const isModalOpen = ref(false)
Modal.confirm({ const tokenDesc = ref('')
title: t('msg.info.deleteTokenConfirmation'), const tokenToCopy = ref('')
type: 'warn',
onOk: async () => { const openModal = (tk: string, desc: string) => {
try { isModalOpen.value = true
await api.orgTokens.delete(token) tokenToCopy.value = tk
message.success(t('msg.success.tokenDeleted')) tokenDesc.value = desc
await loadTokens() }
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e)) const deleteToken = async (token: string): Promise<void> => {
} try {
$e('a:account:token:delete') await api.orgTokens.delete(token)
}, // message.success(t('msg.success.tokenDeleted'))
}) await loadTokens()
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
$e('a:account:token:delete')
isModalOpen.value = false
tokenToCopy.value = ''
tokenDesc.value = ''
} }
const generateToken = async () => { const generateToken = async () => {
try { try {
await api.orgTokens.create(selectedTokenData) await api.orgTokens.create(selectedTokenData.value)
showNewTokenModal = false showNewTokenModal.value = false
// Token generated successfully // Token generated successfully
message.success(t('msg.success.tokenGenerated')) // message.success(t('msg.success.tokenGenerated'))
selectedTokenData = {} selectedTokenData.value = {}
await loadTokens() await loadTokens()
} catch (e: any) { } catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e)) message.error(await extractSdkResponseErrorMsg(e))
@ -104,7 +111,7 @@ const descriptionInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
<div class="text-xl my-4 text-left font-weight-bold">{{ $t('title.tokenManagement') }}</div> <div class="text-xl my-4 text-left font-weight-bold">{{ $t('title.tokenManagement') }}</div>
<div class="py-2 flex gap-4 items-center"> <div class="py-2 flex gap-4 items-center">
<div class="flex-grow"></div> <div class="flex-grow"></div>
<component :is="iconMap.reload" class="cursor-pointer" @click="loadTokens" /> <component :is="iconMap.reload" class="cursor-pointer" @click="() => loadTokens()" />
<a-button <a-button
class="!rounded-md" class="!rounded-md"
data-testid="nc-token-create" data-testid="nc-token-create"
@ -203,7 +210,10 @@ const descriptionInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
<template #overlay> <template #overlay>
<a-menu data-testid="nc-token-row-action-icon"> <a-menu data-testid="nc-token-row-action-icon">
<a-menu-item> <a-menu-item>
<div class="flex flex-row items-center py-3 h-[2rem] nc-delete-token" @click="deleteToken(record.token)"> <div
class="flex flex-row items-center py-3 h-[2rem] nc-delete-token"
@click="openModal(record.token, record.description)"
>
<component :is="iconMap.delete" class="flex" /> <component :is="iconMap.delete" class="flex" />
<div class="text-sm pl-2">{{ $t('general.remove') }}</div> <div class="text-sm pl-2">{{ $t('general.remove') }}</div>
</div> </div>
@ -217,6 +227,22 @@ const descriptionInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
</a-table> </a-table>
</div> </div>
<GeneralDeleteModal v-model:visible="isModalOpen" entity-name="Token" :on-delete="() => deleteToken(tokenToCopy)">
<template #entity-preview>
<span>
<div class="flex flex-row items-center py-2.25 px-2.5 bg-gray-50 rounded-lg text-gray-700 mb-4">
<GeneralIcon icon="key" class="nc-view-icon"></GeneralIcon>
<div
class="capitalize text-ellipsis overflow-hidden select-none w-full pl-1.75"
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }"
>
{{ tokenDesc }}
</div>
</div>
</span>
</template>
</GeneralDeleteModal>
<a-modal <a-modal
v-model:visible="showNewTokenModal" v-model:visible="showNewTokenModal"
:class="{ active: showNewTokenModal }" :class="{ active: showNewTokenModal }"

65
packages/nc-gui/components/account/UserList.vue

@ -1,8 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Modal, message } from 'ant-design-vue'
import type { OrgUserReqType, RequestParams, UserType } from 'nocodb-sdk' import type { OrgUserReqType, RequestParams, UserType } from 'nocodb-sdk'
import type { User } from '#imports'
import { Role, extractSdkResponseErrorMsg, iconMap, useApi, useCopy, useDashboard, useNuxtApp } from '#imports' import { Role, extractSdkResponseErrorMsg, iconMap, useApi, useCopy, useDashboard, useNuxtApp } from '#imports'
import type { User } from '~/lib'
const { api, isLoading } = useApi() const { api, isLoading } = useApi()
@ -10,20 +9,22 @@ const { $e } = useNuxtApp()
const { t } = useI18n() const { t } = useI18n()
const { dashboardUrl } = $(useDashboard()) const { dashboardUrl } = useDashboard()
const { copy } = useCopy() const { copy } = useCopy()
let users = $ref<UserType[]>([]) const users = ref<UserType[]>([])
let currentPage = $ref(1) const currentPage = ref(1)
const currentLimit = $ref(10) const currentLimit = ref(10)
const showUserModal = ref(false) const showUserModal = ref(false)
const userMadalKey = ref(0) const userMadalKey = ref(0)
const isOpen = ref(false)
const searchText = ref<string>('') const searchText = ref<string>('')
const pagination = reactive({ const pagination = reactive({
@ -32,8 +33,8 @@ const pagination = reactive({
position: ['bottomCenter'], position: ['bottomCenter'],
}) })
const loadUsers = async (page = currentPage, limit = currentLimit) => { const loadUsers = async (page = currentPage.value, limit = currentLimit.value) => {
currentPage = page currentPage.value = page
try { try {
const response: any = await api.orgUsers.list({ const response: any = await api.orgUsers.list({
query: { query: {
@ -49,7 +50,7 @@ const loadUsers = async (page = currentPage, limit = currentLimit) => {
pagination.pageSize = 10 pagination.pageSize = 10
users = response.list as UserType[] users.value = response.list as UserType[]
} catch (e: any) { } catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e)) message.error(await extractSdkResponseErrorMsg(e))
} }
@ -69,22 +70,18 @@ const updateRole = async (userId: string, roles: Role) => {
message.error(await extractSdkResponseErrorMsg(e)) message.error(await extractSdkResponseErrorMsg(e))
} }
} }
const deleteUser = async (userId: string) => { const deleteUser = async (userId: string) => {
Modal.confirm({ try {
title: 'Are you sure you want to delete this user?', await api.orgUsers.delete(userId)
type: 'warn', message.success(t('msg.success.userDeleted'))
content: 'Upon deletion, the user will be removed from the installation.', await loadUsers()
onOk: async () => { $e('a:org-user:user-deleted')
try { } catch (e: any) {
await api.orgUsers.delete(userId) message.error(await extractSdkResponseErrorMsg(e))
message.success(t('msg.success.userDeleted')) }
await loadUsers() // closing the modal
$e('a:org-user:user-deleted') isOpen.value = false
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
},
})
} }
const resendInvite = async (user: User) => { const resendInvite = async (user: User) => {
@ -104,7 +101,7 @@ const resendInvite = async (user: User) => {
const copyInviteUrl = async (user: User) => { const copyInviteUrl = async (user: User) => {
if (!user.invite_token) return if (!user.invite_token) return
try { try {
await copy(`${dashboardUrl}#/signup/${user.invite_token}`) await copy(`${dashboardUrl.value}#/signup/${user.invite_token}`)
// Invite URL copied to clipboard // Invite URL copied to clipboard
message.success(t('msg.success.inviteURLCopied')) message.success(t('msg.success.inviteURLCopied'))
@ -269,7 +266,23 @@ const copyPasswordResetUrl = async (user: User) => {
</div> </div>
</a-menu-item> </a-menu-item>
<a-menu-item> <a-menu-item>
<div class="flex flex-row items-center py-3" @click="deleteUser(text)"> <div class="flex flex-row items-center py-3" @click="isOpen = true">
<!-- Delete user modal -->
<GeneralDeleteModal v-model:visible="isOpen" entity-name="User" :on-delete="() => deleteUser(text)">
<template #entity-preview>
<span>
<div class="flex flex-row items-center py-2.25 px-2.5 bg-gray-50 rounded-lg text-gray-700 mb-4">
<GeneralIcon icon="account" class="nc-view-icon"></GeneralIcon>
<div
class="capitalize text-ellipsis overflow-hidden select-none w-full pl-1.75"
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }"
>
{{ record.email }}
</div>
</div>
</span>
</template>
</GeneralDeleteModal>
<component :is="iconMap.delete" data-testid="nc-super-user-delete" class="flex h-[1rem] text-gray-500" /> <component :is="iconMap.delete" data-testid="nc-super-user-delete" class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">{{ $t('general.delete') }}</div> <div class="text-xs pl-2">{{ $t('general.delete') }}</div>
</div> </div>

38
packages/nc-gui/components/account/UsersModal.vue

@ -1,8 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import type { VNodeRef } from '@vue/runtime-core' import type { VNodeRef } from '@vue/runtime-core'
import type { OrgUserReqType } from 'nocodb-sdk' import type { OrgUserReqType } from 'nocodb-sdk'
import type { User, Users } from '#imports'
import { import {
Form, Form,
Role,
computed, computed,
emailValidator, emailValidator,
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
@ -14,20 +16,12 @@ import {
useI18n, useI18n,
useNuxtApp, useNuxtApp,
} from '#imports' } from '#imports'
import type { User } from '~/lib'
import { Role } from '~/lib'
interface Props { interface Props {
show: boolean show: boolean
selectedUser?: User selectedUser?: User
} }
interface Users {
emails: string
role: Role.OrgLevelCreator | Role.OrgLevelViewer
invitationToken?: string
}
const { show } = defineProps<Props>() const { show } = defineProps<Props>()
const emit = defineEmits(['closed', 'reload']) const emit = defineEmits(['closed', 'reload'])
@ -38,9 +32,9 @@ const { $api, $e } = useNuxtApp()
const { copy } = useCopy() const { copy } = useCopy()
const { dashboardUrl } = $(useDashboard()) const { dashboardUrl } = useDashboard()
const usersData = $ref<Users>({ emails: '', role: Role.OrgLevelViewer, invitationToken: undefined }) const usersData = ref<Users>({ emails: '', role: Role.OrgLevelViewer, invitationToken: undefined })
const formRef = ref() const formRef = ref()
@ -52,20 +46,20 @@ const validators = computed(() => {
} }
}) })
const { validateInfos } = useForm(usersData, validators) const { validateInfos } = useForm(usersData.value, validators)
const saveUser = async () => { const saveUser = async () => {
$e('a:org-user:invite', { role: usersData.role }) $e('a:org-user:invite', { role: usersData.value.role })
await formRef.value?.validateFields() await formRef.value?.validateFields()
try { try {
const res = await $api.orgUsers.add({ const res = await $api.orgUsers.add({
roles: usersData.role, roles: usersData.value.role,
email: usersData.emails, email: usersData.value.emails,
} as unknown as OrgUserReqType) } as unknown as OrgUserReqType)
usersData.invitationToken = res.invite_token usersData.value.invitationToken = res.invite_token
emit('reload') emit('reload')
// Successfully updated the user details // Successfully updated the user details
@ -76,12 +70,14 @@ const saveUser = async () => {
} }
} }
const inviteUrl = $computed(() => (usersData.invitationToken ? `${dashboardUrl}#/signup/${usersData.invitationToken}` : null)) const inviteUrl = computed(() =>
usersData.value.invitationToken ? `${dashboardUrl.value}#/signup/${usersData.value.invitationToken}` : null,
)
const copyUrl = async () => { const copyUrl = async () => {
if (!inviteUrl) return if (!inviteUrl.value) return
try { try {
await copy(inviteUrl) await copy(inviteUrl.value)
// Copied shareable base url to clipboard! // Copied shareable base url to clipboard!
message.success(t('msg.success.shareableURLCopied')) message.success(t('msg.success.shareableURLCopied'))
@ -93,9 +89,9 @@ const copyUrl = async () => {
const clickInviteMore = () => { const clickInviteMore = () => {
$e('c:user:invite-more') $e('c:user:invite-more')
usersData.invitationToken = undefined usersData.value.invitationToken = undefined
usersData.role = Role.OrgLevelViewer usersData.value.role = Role.OrgLevelViewer
usersData.emails = '' usersData.value.emails = ''
} }
const emailInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus() const emailInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()

68
packages/nc-gui/components/api-client/Headers.vue

@ -12,7 +12,6 @@ interface Option {
} }
const vModel = useVModel(props, 'modelValue', emits) const vModel = useVModel(props, 'modelValue', emits)
const headerList = ref<Option[]>([ const headerList = ref<Option[]>([
{ value: 'A-IM' }, { value: 'A-IM' },
{ value: 'Accept' }, { value: 'Accept' },
@ -56,50 +55,38 @@ const headerList = ref<Option[]>([
]) ])
const addHeaderRow = () => vModel.value.push({}) const addHeaderRow = () => vModel.value.push({})
const deleteHeaderRow = (i: number) => vModel.value.splice(i, 1) const deleteHeaderRow = (i: number) => vModel.value.splice(i, 1)
const filterOption = (input: string, option: Option) => option.value.toUpperCase().includes(input.toUpperCase())
const filterOption = (input: string, option: Option) => {
return option.value.toUpperCase().includes(input.toUpperCase())
}
</script> </script>
<template> <template>
<div class="flex flex-row justify-center"> <div class="flex flex-row justify-between w-full">
<table> <table class="w-full nc-webhooks-params">
<thead> <thead class="h-8">
<tr> <tr>
<th> <th></th>
<!-- Intended to be empty - For checkbox -->
</th>
<th> <th>
<div class="text-left font-normal ml-2">Header Name</div> <div class="text-left font-normal ml-2">Header Name</div>
</th> </th>
<th> <th>
<div class="text-left font-normal ml-2">Value</div> <div class="text-left font-normal ml-2">Value</div>
</th> </th>
<th class="w-8"></th>
<th>
<!-- Intended to be empty - For delete button -->
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="(headerRow, idx) in vModel" :key="idx"> <tr v-for="(headerRow, idx) in vModel" :key="idx" class="!h-2 overflow-hidden">
<td class="px-2 nc-hook-header-tab-checkbox"> <td class="px-2 nc-hook-header-tab-checkbox">
<a-form-item> <a-form-item class="form-item">
<a-checkbox v-model:checked="headerRow.enabled" /> <a-checkbox v-model:checked="headerRow.enabled" />
</a-form-item> </a-form-item>
</td> </td>
<td class="px-2 w-min-[400px]"> <td class="px-2">
<a-form-item> <a-form-item class="form-item">
<a-auto-complete <a-auto-complete
v-model:value="headerRow.name" v-model:value="headerRow.name"
size="large"
placeholder="Key" placeholder="Key"
class="nc-input-hook-header-key" class="nc-input-hook-header-key"
:options="headerList" :options="headerList"
@ -108,29 +95,44 @@ const filterOption = (input: string, option: Option) => {
</a-form-item> </a-form-item>
</td> </td>
<td class="px-2 w-min-[400px]"> <td class="px-2">
<a-form-item> <a-form-item class="form-item">
<a-input v-model:value="headerRow.value" size="large" placeholder="Value" class="nc-input-hook-header-value" /> <a-input v-model:value="headerRow.value" placeholder="Value" class="!rounded-md nc-input-hook-header-value" />
</a-form-item> </a-form-item>
</td> </td>
<td class="relative"> <td class="relative">
<div v-if="idx !== 0" class="absolute flex flex-col justify-start mt-2 -right-6 top-0"> <div
<component :is="iconMap.delete" class="cursor-pointer" @click="deleteHeaderRow(idx)" /> v-if="idx !== 0"
class="absolute left-0 top-0.25 py-1 px-1.5 rounded-md border-1 border-gray-100"
:class="{
'text-gray-400 cursor-not-allowed bg-gray-50': vModel.length === 1,
'text-gray-600 cursor-pointer hover:bg-gray-50 hover:text-black': vModel.length !== 1,
}"
@click="deleteHeaderRow(idx)"
>
<component :is="iconMap.delete" />
</div> </div>
</td> </td>
</tr> </tr>
<tr> <tr>
<td :colspan="12" class="text-center"> <td :colspan="12" class="">
<a-button type="default" class="!bg-gray-100 rounded-md border-none mr-1 mb-3" @click="addHeaderRow"> <NcButton size="small" type="secondary" @click="addHeaderRow">
<template #icon> <div class="flex flex-row items-center gap-x-1">
<div>Add Header</div>
<component :is="iconMap.plus" class="flex mx-auto" /> <component :is="iconMap.plus" class="flex mx-auto" />
</template> </div>
</a-button> </NcButton>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
</template> </template>
<style lang="scss" scoped>
.form-item {
@apply !mb-3;
}
</style>

64
packages/nc-gui/components/api-client/Params.vue

@ -11,69 +11,77 @@ const vModel = useVModel(props, 'modelValue', emits)
const addParamRow = () => vModel.value.push({}) const addParamRow = () => vModel.value.push({})
const deleteParamRow = (i: number) => vModel.value.splice(i, 1) const deleteParamRow = (i: number) => {
if (vModel.value.length === 1) return
vModel.value.splice(i, 1)
}
</script> </script>
<template> <template>
<div class="flex flex-row justify-start"> <div class="flex flex-row justify-between w-full">
<table> <table class="w-full nc-webhooks-params">
<thead> <thead class="h-8">
<tr> <tr>
<th> <th>
<!-- Intended to be empty - For checkbox --> <div class="text-left font-normal ml-2">Parameter Name</div>
</th>
<th>
<div class="text-left font-normal ml-2">Param Name</div>
</th> </th>
<th> <th>
<div class="text-left font-normal ml-2">Value</div> <div class="text-left font-normal ml-2">Value</div>
</th> </th>
<th> <th class="w-8">
<!-- Intended to be empty - For delete button --> <!-- Intended to be empty - For delete button -->
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="(paramRow, idx) in vModel" :key="idx"> <tr v-for="(paramRow, idx) in vModel" :key="idx" class="!h-2 overflow-hidden">
<td class="px-2"> <td class="px-2">
<a-form-item> <a-form-item class="form-item">
<a-checkbox v-model:checked="paramRow.enabled" /> <a-input v-model:value="paramRow.name" placeholder="Key" class="!rounded-lg" />
</a-form-item> </a-form-item>
</td> </td>
<td class="px-2"> <td class="px-2">
<a-form-item> <a-form-item class="form-item">
<a-input v-model:value="paramRow.name" size="large" placeholder="Key" /> <a-input v-model:value="paramRow.value" placeholder="Value" class="!rounded-lg" />
</a-form-item>
</td>
<td class="px-2">
<a-form-item>
<a-input v-model:value="paramRow.value" size="large" placeholder="Value" />
</a-form-item> </a-form-item>
</td> </td>
<td class="relative"> <td class="relative">
<div v-if="idx !== 0" class="absolute flex flex-col justify-start mt-2 -right-6 top-0"> <div
<component :is="iconMap.delete" class="cursor-pointer" @click="deleteParamRow(idx)" /> class="absolute left-0 top-0.25 py-1 px-1.5 rounded-md border-1 border-gray-100"
:class="{
'text-gray-400 cursor-not-allowed bg-gray-50': vModel.length === 1,
'text-gray-600 cursor-pointer hover:bg-gray-50 hover:text-black': vModel.length !== 1,
}"
@click="deleteParamRow(idx)"
>
<component :is="iconMap.delete" />
</div> </div>
</td> </td>
</tr> </tr>
<tr> <tr>
<td :colspan="12" class="text-center"> <td :colspan="12" class="">
<a-button type="default" class="!bg-gray-100 rounded-md border-none mr-1 mb-3" @click="addParamRow"> <NcButton size="small" type="secondary" @click="addParamRow">
<template #icon> <div class="flex flex-row items-center gap-x-1">
<div>Add Parameter</div>
<component :is="iconMap.plus" class="flex mx-auto" /> <component :is="iconMap.plus" class="flex mx-auto" />
</template> </div>
</a-button> </NcButton>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
</template> </template>
<style lang="scss" scoped>
.form-item {
@apply !mb-3;
}
</style>

6
packages/nc-gui/components/cell/Checkbox.vue

@ -35,7 +35,7 @@ const isForm = inject(IsFormInj)
const readOnly = inject(ReadonlyInj) const readOnly = inject(ReadonlyInj)
const checkboxMeta = $computed(() => { const checkboxMeta = computed(() => {
return { return {
icon: { icon: {
checked: 'mdi-check-circle-outline', checked: 'mdi-check-circle-outline',
@ -46,7 +46,7 @@ const checkboxMeta = $computed(() => {
} }
}) })
let vModel = $computed<boolean | number>({ const vModel = computed<boolean | number>({
get: () => !!props.modelValue && props.modelValue !== '0' && props.modelValue !== 0, get: () => !!props.modelValue && props.modelValue !== '0' && props.modelValue !== 0,
set: (val: any) => emits('update:modelValue', isMssql(column?.value?.base_id) ? +val : val), set: (val: any) => emits('update:modelValue', isMssql(column?.value?.base_id) ? +val : val),
}) })
@ -59,7 +59,7 @@ function onClick(force?: boolean, event?: MouseEvent) {
return return
} }
if (!readOnly?.value && (force || active.value)) { if (!readOnly?.value && (force || active.value)) {
vModel = !vModel vModel.value = !vModel.value
} }
} }

2
packages/nc-gui/components/cell/ClampedText.vue

@ -13,7 +13,7 @@ const props = defineProps<{
'-webkit-line-clamp': props.lines || 1, '-webkit-line-clamp': props.lines || 1,
'-webkit-box-orient': 'vertical', '-webkit-box-orient': 'vertical',
'overflow': 'hidden', 'overflow': 'hidden',
'white-space': 'pre', 'word-break': 'break-all',
}" }"
> >
{{ props.value || '' }} {{ props.value || '' }}

2
packages/nc-gui/components/cell/Currency.vue

@ -59,7 +59,7 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputEle
const submitCurrency = () => { const submitCurrency = () => {
if (lastSaved.value !== vModel.value) { if (lastSaved.value !== vModel.value) {
lastSaved.value = vModel.value vModel.value = lastSaved.value = vModel.value ?? null
emit('save') emit('save')
} }
editEnabled.value = false editEnabled.value = false

24
packages/nc-gui/components/cell/DatePicker.vue

@ -30,22 +30,24 @@ const columnMeta = inject(ColumnInj, null)!
const readOnly = inject(ReadonlyInj, ref(false)) const readOnly = inject(ReadonlyInj, ref(false))
const isLockedMode = inject(IsLockedInj, ref(false))
const active = inject(ActiveCellInj, ref(false)) const active = inject(ActiveCellInj, ref(false))
const editable = inject(EditModeInj, ref(false)) const editable = inject(EditModeInj, ref(false))
let isDateInvalid = $ref(false) const isDateInvalid = ref(false)
const dateFormat = $computed(() => parseProp(columnMeta?.value?.meta)?.date_format ?? 'YYYY-MM-DD') const dateFormat = computed(() => parseProp(columnMeta?.value?.meta)?.date_format ?? 'YYYY-MM-DD')
let localState = $computed({ const localState = computed({
get() { get() {
if (!modelValue) { if (!modelValue) {
return undefined return undefined
} }
if (!dayjs(modelValue).isValid()) { if (!dayjs(modelValue).isValid()) {
isDateInvalid = true isDateInvalid.value = true
return undefined return undefined
} }
@ -77,7 +79,7 @@ watch(
{ flush: 'post' }, { flush: 'post' },
) )
const placeholder = computed(() => (modelValue === null && showNull.value ? 'NULL' : isDateInvalid ? 'Invalid date' : '')) const placeholder = computed(() => (modelValue === null && showNull.value ? 'NULL' : isDateInvalid.value ? 'Invalid date' : ''))
useSelectedCellKeyupListener(active, (e: KeyboardEvent) => { useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
switch (e.key) { switch (e.key) {
@ -110,7 +112,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
} }
break break
case 'ArrowLeft': case 'ArrowLeft':
if (!localState) { if (!localState.value) {
;(document.querySelector('.nc-picker-date.active .ant-picker-header-prev-btn') as HTMLButtonElement)?.click() ;(document.querySelector('.nc-picker-date.active .ant-picker-header-prev-btn') as HTMLButtonElement)?.click()
} else { } else {
const prevEl = document.querySelector('.nc-picker-date.active .ant-picker-cell-selected') const prevEl = document.querySelector('.nc-picker-date.active .ant-picker-cell-selected')
@ -133,7 +135,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
} }
break break
case 'ArrowRight': case 'ArrowRight':
if (!localState) { if (!localState.value) {
;(document.querySelector('.nc-picker-date.active .ant-picker-header-next-btn') as HTMLButtonElement)?.click() ;(document.querySelector('.nc-picker-date.active .ant-picker-header-next-btn') as HTMLButtonElement)?.click()
} else { } else {
const nextEl = document.querySelector('.nc-picker-date.active .ant-picker-cell-selected') const nextEl = document.querySelector('.nc-picker-date.active .ant-picker-cell-selected')
@ -156,15 +158,15 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
} }
break break
case 'ArrowUp': case 'ArrowUp':
if (!localState) if (!localState.value)
(document.querySelector('.nc-picker-date.active .ant-picker-header-super-prev-btn') as HTMLButtonElement)?.click() (document.querySelector('.nc-picker-date.active .ant-picker-header-super-prev-btn') as HTMLButtonElement)?.click()
break break
case 'ArrowDown': case 'ArrowDown':
if (!localState) if (!localState.value)
(document.querySelector('.nc-picker-date.active .ant-picker-header-super-next-btn') as HTMLButtonElement)?.click() (document.querySelector('.nc-picker-date.active .ant-picker-header-super-next-btn') as HTMLButtonElement)?.click()
break break
case ';': case ';':
localState = dayjs(new Date()) localState.value = dayjs(new Date())
break break
} }
}) })
@ -206,7 +208,7 @@ const clickHandler = () => {
:allow-clear="!readOnly && !localState && !isPk" :allow-clear="!readOnly && !localState && !isPk"
:input-read-only="true" :input-read-only="true"
:dropdown-class-name="`${randomClass} nc-picker-date ${open ? 'active' : ''}`" :dropdown-class-name="`${randomClass} nc-picker-date ${open ? 'active' : ''}`"
:open="(readOnly || (localState && isPk)) && !active && !editable ? false : open" :open="((readOnly || (localState && isPk)) && !active && !editable) || isLockedMode ? false : open"
@click="clickHandler" @click="clickHandler"
@update:open="updateOpen" @update:open="updateOpen"
> >

24
packages/nc-gui/components/cell/DateTimePicker.vue

@ -36,11 +36,13 @@ const active = inject(ActiveCellInj, ref(false))
const editable = inject(EditModeInj, ref(false)) const editable = inject(EditModeInj, ref(false))
const isLockedMode = inject(IsLockedInj, ref(false))
const column = inject(ColumnInj)! const column = inject(ColumnInj)!
let isDateInvalid = $ref(false) const isDateInvalid = ref(false)
const dateTimeFormat = $computed(() => { const dateTimeFormat = computed(() => {
const dateFormat = parseProp(column?.value?.meta)?.date_format ?? dateFormats[0] const dateFormat = parseProp(column?.value?.meta)?.date_format ?? dateFormats[0]
const timeFormat = parseProp(column?.value?.meta)?.time_format ?? timeFormats[0] const timeFormat = parseProp(column?.value?.meta)?.time_format ?? timeFormats[0]
return `${dateFormat} ${timeFormat}` return `${dateFormat} ${timeFormat}`
@ -48,14 +50,14 @@ const dateTimeFormat = $computed(() => {
let localModelValue = modelValue ? dayjs(modelValue).utc().local() : undefined let localModelValue = modelValue ? dayjs(modelValue).utc().local() : undefined
let localState = $computed({ const localState = computed({
get() { get() {
if (!modelValue) { if (!modelValue) {
return undefined return undefined
} }
if (!dayjs(modelValue).isValid()) { if (!dayjs(modelValue).isValid()) {
isDateInvalid = true isDateInvalid.value = true
return undefined return undefined
} }
@ -129,7 +131,7 @@ watch(
{ flush: 'post' }, { flush: 'post' },
) )
const placeholder = computed(() => (modelValue === null && showNull.value ? 'NULL' : isDateInvalid ? 'Invalid date' : '')) const placeholder = computed(() => (modelValue === null && showNull.value ? 'NULL' : isDateInvalid.value ? 'Invalid date' : ''))
useSelectedCellKeyupListener(active, (e: KeyboardEvent) => { useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
switch (e.key) { switch (e.key) {
@ -158,7 +160,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
} }
break break
case 'ArrowLeft': case 'ArrowLeft':
if (!localState) { if (!localState.value) {
;(document.querySelector('.nc-picker-datetime.active .ant-picker-header-prev-btn') as HTMLButtonElement)?.click() ;(document.querySelector('.nc-picker-datetime.active .ant-picker-header-prev-btn') as HTMLButtonElement)?.click()
} else { } else {
const prevEl = document.querySelector('.nc-picker-datetime.active .ant-picker-cell-selected') const prevEl = document.querySelector('.nc-picker-datetime.active .ant-picker-cell-selected')
@ -181,7 +183,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
} }
break break
case 'ArrowRight': case 'ArrowRight':
if (!localState) { if (!localState.value) {
;(document.querySelector('.nc-picker-datetime.active .ant-picker-header-next-btn') as HTMLButtonElement)?.click() ;(document.querySelector('.nc-picker-datetime.active .ant-picker-header-next-btn') as HTMLButtonElement)?.click()
} else { } else {
const nextEl = document.querySelector('.nc-picker-datetime.active .ant-picker-cell-selected') const nextEl = document.querySelector('.nc-picker-datetime.active .ant-picker-cell-selected')
@ -204,15 +206,15 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
} }
break break
case 'ArrowUp': case 'ArrowUp':
if (!localState) if (!localState.value)
(document.querySelector('.nc-picker-datetime.active .ant-picker-header-super-prev-btn') as HTMLButtonElement)?.click() (document.querySelector('.nc-picker-datetime.active .ant-picker-header-super-prev-btn') as HTMLButtonElement)?.click()
break break
case 'ArrowDown': case 'ArrowDown':
if (!localState) if (!localState.value)
(document.querySelector('.nc-picker-datetime.active .ant-picker-header-super-next-btn') as HTMLButtonElement)?.click() (document.querySelector('.nc-picker-datetime.active .ant-picker-header-super-next-btn') as HTMLButtonElement)?.click()
break break
case ';': case ';':
localState = dayjs(new Date()) localState.value = dayjs(new Date())
break break
} }
}) })
@ -248,7 +250,7 @@ const clickHandler = () => {
:allow-clear="!readOnly && !localState && !isPk" :allow-clear="!readOnly && !localState && !isPk"
:input-read-only="true" :input-read-only="true"
:dropdown-class-name="`${randomClass} nc-picker-datetime ${open ? 'active' : ''}`" :dropdown-class-name="`${randomClass} nc-picker-datetime ${open ? 'active' : ''}`"
:open="readOnly || (localState && isPk) ? false : open && (active || editable)" :open="readOnly || (localState && isPk) || isLockedMode ? false : open && (active || editable)"
:disabled="readOnly || (localState && isPk)" :disabled="readOnly || (localState && isPk)"
@click="clickHandler" @click="clickHandler"
@ok="open = !open" @ok="open = !open"

69
packages/nc-gui/components/cell/Decimal.vue

@ -21,8 +21,24 @@ const { showNull } = useGlobal()
const editEnabled = inject(EditModeInj) const editEnabled = inject(EditModeInj)
const column = inject(ColumnInj, null)!
const domRef = ref<HTMLElement>()
const meta = computed(() => {
return typeof column?.value.meta === 'string' ? JSON.parse(column.value.meta) : column?.value.meta ?? {}
})
const _vModel = useVModel(props, 'modelValue', emits) const _vModel = useVModel(props, 'modelValue', emits)
const displayValue = computed(() => {
if (_vModel.value === null) return null
if (isNaN(Number(_vModel.value))) return null
return Number(_vModel.value).toFixed(meta.value.precision ?? 1)
})
const vModel = computed({ const vModel = computed({
get: () => _vModel.value, get: () => _vModel.value,
set: (value) => { set: (value) => {
@ -36,9 +52,39 @@ const vModel = computed({
}, },
}) })
const precision = computed(() => {
const meta = typeof column?.value.meta === 'string' ? JSON.parse(column.value.meta) : column?.value.meta ?? {}
const _precision = meta.precision ?? 1
return Number(0.1 ** _precision).toFixed(_precision)
})
const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))! const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))!
// Handle the arrow keys as its default behavior is to increment/decrement the value
const onKeyDown = (e: any) => {
if (e.key === 'ArrowDown') {
e.preventDefault()
// Move the cursor to the end of the input
e.target.type = 'text'
e.target?.setSelectionRange(e.target.value.length, e.target.value.length)
e.target.type = 'number'
} else if (e.key === 'ArrowUp') {
e.preventDefault()
e.target.type = 'text'
e.target?.setSelectionRange(0, 0)
e.target.type = 'number'
}
}
const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputElement)?.focus() const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputElement)?.focus()
watch(isExpandedFormOpen, () => {
if (!isExpandedFormOpen.value) {
domRef.value?.focus()
}
})
</script> </script>
<template> <template>
@ -46,14 +92,15 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputEle
v-if="editEnabled" v-if="editEnabled"
:ref="focus" :ref="focus"
v-model="vModel" v-model="vModel"
class="outline-none px-2 border-none w-full h-full text-sm" class="outline-none !p-0 border-none w-full h-full text-sm"
type="number" type="number"
step="0.1" :step="precision"
style="letter-spacing: 0.06rem"
@blur="editEnabled = false" @blur="editEnabled = false"
@keydown.down.stop @keydown.down.stop="onKeyDown"
@keydown.left.stop @keydown.left.stop
@keydown.right.stop @keydown.right.stop
@keydown.up.stop @keydown.up.stop="onKeyDown"
@keydown.delete.stop @keydown.delete.stop
@keydown.ctrl.z.stop @keydown.ctrl.z.stop
@keydown.meta.z.stop @keydown.meta.z.stop
@ -61,11 +108,23 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputEle
@mousedown.stop @mousedown.stop
/> />
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span> <span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<span v-else class="text-sm">{{ vModel }}</span> <span v-else class="text-sm">{{ displayValue }}</span>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
input[type='number']:focus { input[type='number']:focus {
@apply ring-transparent; @apply ring-transparent;
} }
/* Chrome, Safari, Edge, Opera */
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
input[type='number'] {
-moz-appearance: textfield;
}
</style> </style>

2
packages/nc-gui/components/cell/Duration.vue

@ -15,7 +15,7 @@ import {
interface Props { interface Props {
modelValue: number | string | null | undefined modelValue: number | string | null | undefined
showValidationError: boolean showValidationError?: boolean
} }
const { modelValue, showValidationError = true } = defineProps<Props>() const { modelValue, showValidationError = true } = defineProps<Props>()

6
packages/nc-gui/components/cell/Email.vue

@ -10,6 +10,8 @@ const { modelValue: value } = defineProps<Props>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const rowHeight = inject(RowHeightInj, ref(undefined))
const { t } = useI18n() const { t } = useI18n()
const { showNull } = useGlobal() const { showNull } = useGlobal()
@ -73,8 +75,8 @@ watch(
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span> <span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<a v-else-if="validEmail" class="text-sm underline hover:opacity-75" :href="`mailto:${vModel}`" target="_blank"> <a v-else-if="validEmail" class="text-sm underline hover:opacity-75" :href="`mailto:${vModel}`" target="_blank">
{{ vModel }} <LazyCellClampedText :value="vModel" :lines="rowHeight" />
</a> </a>
<span v-else>{{ vModel }}</span> <LazyCellClampedText v-else :value="vModel" :lines="rowHeight" />
</template> </template>

18
packages/nc-gui/components/cell/GeoData.vue

@ -16,17 +16,17 @@ const emits = defineEmits<Emits>()
const vModel = useVModel(props, 'modelValue', emits) const vModel = useVModel(props, 'modelValue', emits)
let isExpanded = $ref(false) const isExpanded = ref(false)
let isLoading = $ref(false) const isLoading = ref(false)
let isLocationSet = $ref(false) const isLocationSet = ref(false)
const [latitude, longitude] = (vModel.value || '').split(';') const [latitude, longitude] = (vModel.value || '').split(';')
const latLongStr = computed(() => { const latLongStr = computed(() => {
const [latitude, longitude] = (vModel.value || '').split(';') const [latitude, longitude] = (vModel.value || '').split(';')
if (latitude) isLocationSet = true if (latitude) isLocationSet.value = true
return latitude && longitude ? `${latitude}; ${longitude}` : 'Set location' return latitude && longitude ? `${latitude}; ${longitude}` : 'Set location'
}) })
@ -37,28 +37,28 @@ const formState = reactive({
const handleFinish = () => { const handleFinish = () => {
vModel.value = latLongToJoinedString(parseFloat(formState.latitude), parseFloat(formState.longitude)) vModel.value = latLongToJoinedString(parseFloat(formState.latitude), parseFloat(formState.longitude))
isExpanded = false isExpanded.value = false
} }
const clear = () => { const clear = () => {
isExpanded = false isExpanded.value = false
formState.latitude = latitude formState.latitude = latitude
formState.longitude = longitude formState.longitude = longitude
} }
const onClickSetCurrentLocation = () => { const onClickSetCurrentLocation = () => {
isLoading = true isLoading.value = true
const onSuccess: PositionCallback = (position: GeolocationPosition) => { const onSuccess: PositionCallback = (position: GeolocationPosition) => {
const crd = position.coords const crd = position.coords
formState.latitude = `${crd.latitude}` formState.latitude = `${crd.latitude}`
formState.longitude = `${crd.longitude}` formState.longitude = `${crd.longitude}`
isLoading = false isLoading.value = false
} }
const onError: PositionErrorCallback = (err: GeolocationPositionError) => { const onError: PositionErrorCallback = (err: GeolocationPositionError) => {
console.error(`ERROR(${err.code}): ${err.message}`) console.error(`ERROR(${err.code}): ${err.message}`)
isLoading = false isLoading.value = false
} }
const options = { const options = {

51
packages/nc-gui/components/cell/Integer.vue

@ -23,6 +23,14 @@ const editEnabled = inject(EditModeInj)
const _vModel = useVModel(props, 'modelValue', emits) const _vModel = useVModel(props, 'modelValue', emits)
const displayValue = computed(() => {
if (_vModel.value === null) return null
if (isNaN(Number(_vModel.value))) return null
return Number(_vModel.value)
})
const vModel = computed({ const vModel = computed({
get: () => _vModel.value, get: () => _vModel.value,
set: (value) => { set: (value) => {
@ -40,17 +48,33 @@ const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))!
const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputElement)?.focus() const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputElement)?.focus()
function onKeyDown(evt: KeyboardEvent) { function onKeyDown(e: any) {
const cmdOrCtrl = isMac() ? evt.metaKey : evt.ctrlKey const cmdOrCtrl = isMac() ? e.metaKey : e.ctrlKey
if (cmdOrCtrl && !evt.altKey) { if (cmdOrCtrl && !e.altKey) {
switch (evt.keyCode) { switch (e.keyCode) {
case 90: { case 90: {
evt.stopPropagation() e.stopPropagation()
break break
} }
} }
} }
return evt.key === '.' && evt.preventDefault() if (e.key === '.') {
return e.preventDefault()
}
if (e.key === 'ArrowDown') {
e.preventDefault()
// Move the cursor to the end of the input
e.target.type = 'text'
e.target?.setSelectionRange(e.target.value.length, e.target.value.length)
e.target.type = 'number'
} else if (e.key === 'ArrowUp') {
e.preventDefault()
e.target.type = 'text'
e.target?.setSelectionRange(0, 0)
e.target.type = 'number'
}
} }
</script> </script>
@ -61,6 +85,7 @@ function onKeyDown(evt: KeyboardEvent) {
v-model="vModel" v-model="vModel"
class="outline-none p-0 border-none w-full h-full text-sm" class="outline-none p-0 border-none w-full h-full text-sm"
type="number" type="number"
style="letter-spacing: 0.06rem"
@blur="editEnabled = false" @blur="editEnabled = false"
@keydown="onKeyDown" @keydown="onKeyDown"
@keydown.down.stop @keydown.down.stop
@ -72,11 +97,23 @@ function onKeyDown(evt: KeyboardEvent) {
@mousedown.stop @mousedown.stop
/> />
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span> <span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<span v-else class="text-sm">{{ vModel }}</span> <span v-else class="text-sm">{{ displayValue }}</span>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
input[type='number']:focus { input[type='number']:focus {
@apply ring-transparent; @apply ring-transparent;
} }
/* Chrome, Safari, Edge, Opera */
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
input[type='number'] {
-moz-appearance: textfield;
}
</style> </style>

47
packages/nc-gui/components/cell/Json.vue

@ -39,25 +39,25 @@ const vModel = useVModel(props, 'modelValue', emits)
const localValueState = ref<string | undefined>() const localValueState = ref<string | undefined>()
let error = $ref<string | undefined>() const error = ref<string | undefined>()
let isExpanded = $ref(false) const isExpanded = ref(false)
const localValue = computed<string | Record<string, any> | undefined>({ const localValue = computed<string | Record<string, any> | undefined>({
get: () => localValueState.value, get: () => localValueState.value,
set: (val: undefined | string | Record<string, any>) => { set: (val: undefined | string | Record<string, any>) => {
localValueState.value = typeof val === 'object' ? JSON.stringify(val, null, 2) : val localValueState.value = typeof val === 'object' ? JSON.stringify(val, null, 2) : val
/** if form and not expanded then sync directly */ /** if form and not expanded then sync directly */
if (isForm.value && !isExpanded) { if (isForm.value && !isExpanded.value) {
vModel.value = val vModel.value = val
} }
}, },
}) })
const clear = () => { const clear = () => {
error = undefined error.value = undefined
isExpanded = false isExpanded.value = false
editEnabled.value = false editEnabled.value = false
@ -66,44 +66,59 @@ const clear = () => {
const formatJson = (json: string) => { const formatJson = (json: string) => {
try { try {
return JSON.stringify(JSON.parse(json), null, 2) json = json
.trim()
.replace(/^\{\s*|\s*\}$/g, '')
.replace(/\n\s*/g, '')
json = `{${json}}`
return json
} catch (e) { } catch (e) {
console.log(e)
return json return json
} }
} }
const onSave = () => { const onSave = () => {
isExpanded = false isExpanded.value = false
editEnabled.value = false editEnabled.value = false
localValue.value = localValue ? formatJson(localValue.value as string) : localValue vModel.value = localValue ? formatJson(localValue.value as string) : localValue
}
vModel.value = localValue.value const setLocalValue = (val: any) => {
try {
localValue.value = typeof val === 'string' ? JSON.stringify(JSON.parse(val), null, 2) : val
} catch (e) {
localValue.value = val
}
} }
watch( watch(
vModel, vModel,
(val) => { (val) => {
localValue.value = val setLocalValue(val)
}, },
{ immediate: true }, { immediate: true },
) )
watch(localValue, (val) => { watch([localValue, editEnabled], () => {
try { try {
JSON.parse(val as string) JSON.parse(localValue.value as string)
error = undefined error.value = undefined
} catch (e: any) { } catch (e: any) {
error = e if (localValue.value === undefined) return
error.value = e
} }
}) })
watch(editEnabled, () => { watch(editEnabled, () => {
isExpanded = false isExpanded.value = false
localValue.value = vModel.value setLocalValue(vModel.value)
}) })
useSelectedCellKeyupListener(active, (e) => { useSelectedCellKeyupListener(active, (e) => {

34
packages/nc-gui/components/cell/MultiSelect.vue

@ -4,6 +4,7 @@ import { message } from 'ant-design-vue'
import tinycolor from 'tinycolor2' import tinycolor from 'tinycolor2'
import type { Select as AntSelect } from 'ant-design-vue' import type { Select as AntSelect } from 'ant-design-vue'
import type { SelectOptionType, SelectOptionsType } from 'nocodb-sdk' import type { SelectOptionType, SelectOptionsType } from 'nocodb-sdk'
import { WorkspaceUserRoles } from 'nocodb-sdk'
import { import {
ActiveCellInj, ActiveCellInj,
CellClickHookInj, CellClickHookInj,
@ -46,6 +47,8 @@ const column = inject(ColumnInj)!
const readOnly = inject(ReadonlyInj)! const readOnly = inject(ReadonlyInj)!
const isLockedMode = inject(IsLockedInj, ref(false))
const isEditable = inject(EditModeInj, ref(false)) const isEditable = inject(EditModeInj, ref(false))
const activeCell = inject(ActiveCellInj, ref(false)) const activeCell = inject(ActiveCellInj, ref(false))
@ -99,7 +102,15 @@ const isOptionMissing = computed(() => {
return (options.value ?? []).every((op) => op.title !== searchVal.value) return (options.value ?? []).every((op) => op.title !== searchVal.value)
}) })
const hasEditRoles = computed(() => hasRole('owner', true) || hasRole('creator', true) || hasRole('editor', true)) const hasEditRoles = computed(
() =>
hasRole('owner', true) ||
hasRole('creator', true) ||
hasRole('editor', true) ||
hasRole(WorkspaceUserRoles.OWNER, true) ||
hasRole(WorkspaceUserRoles.CREATOR, true) ||
hasRole(WorkspaceUserRoles.EDITOR, true),
)
const editAllowed = computed(() => (hasEditRoles.value || isForm.value) && active.value) const editAllowed = computed(() => (hasEditRoles.value || isForm.value) && active.value)
@ -334,7 +345,11 @@ const selectedOpts = computed(() => {
</script> </script>
<template> <template>
<div class="nc-multi-select h-full w-full flex items-center" :class="{ 'read-only': readOnly }" @click="toggleMenu"> <div
class="nc-multi-select h-full w-full flex items-center"
:class="{ 'read-only': readOnly || isLockedMode }"
@click="toggleMenu"
>
<div <div
v-if="!active" v-if="!active"
class="flex flex-wrap" class="flex flex-wrap"
@ -372,9 +387,9 @@ const selectedOpts = computed(() => {
:bordered="false" :bordered="false"
clear-icon clear-icon
show-search show-search
:show-arrow="editAllowed && !readOnly" :show-arrow="editAllowed && !(readOnly || isLockedMode)"
:open="isOpen && editAllowed" :open="isOpen && editAllowed"
:disabled="readOnly || !editAllowed" :disabled="readOnly || !editAllowed || isLockedMode"
:class="{ 'caret-transparent': !hasEditRoles }" :class="{ 'caret-transparent': !hasEditRoles }"
:dropdown-class-name="`nc-dropdown-multi-select-cell ${isOpen ? 'active' : ''}`" :dropdown-class-name="`nc-dropdown-multi-select-cell ${isOpen ? 'active' : ''}`"
@search="search" @search="search"
@ -409,7 +424,10 @@ const selectedOpts = computed(() => {
isOptionMissing && isOptionMissing &&
!isPublic && !isPublic &&
!disableOptionCreation && !disableOptionCreation &&
(hasRole('owner', true) || hasRole('creator', true)) (hasRole('owner', true) ||
hasRole('creator', true) ||
hasRole(WorkspaceUserRoles.OWNER, true) ||
hasRole(WorkspaceUserRoles.CREATOR, true))
" "
:key="searchVal" :key="searchVal"
:value="searchVal" :value="searchVal"
@ -481,6 +499,12 @@ const selectedOpts = computed(() => {
color: rgba(0, 0, 0, 0.45); color: rgba(0, 0, 0, 0.45);
} }
.read-only {
.ms-close-icon {
display: none;
}
}
.rounded-tag { .rounded-tag {
@apply py-0 px-[12px] rounded-[12px]; @apply py-0 px-[12px] rounded-[12px];
} }

9
packages/nc-gui/components/cell/Rating.vue

@ -11,6 +11,8 @@ const emits = defineEmits(['update:modelValue'])
const column = inject(ColumnInj)! const column = inject(ColumnInj)!
const readonly = inject(ReadonlyInj, ref(false))
const ratingMeta = computed(() => { const ratingMeta = computed(() => {
return { return {
icon: { icon: {
@ -37,7 +39,12 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEven
</script> </script>
<template> <template>
<a-rate v-model:value="vModel" :count="ratingMeta.max" :style="`color: ${ratingMeta.color}; padding: 0px 5px`"> <a-rate
v-model:value="vModel"
:disabled="readonly"
:count="ratingMeta.max"
:style="`color: ${ratingMeta.color}; padding: 0px 5px`"
>
<template #character> <template #character>
<MdiStar v-if="ratingMeta.icon.full === 'mdi-star'" class="text-sm" /> <MdiStar v-if="ratingMeta.icon.full === 'mdi-star'" class="text-sm" />
<MdiHeart v-if="ratingMeta.icon.full === 'mdi-heart'" class="text-sm" /> <MdiHeart v-if="ratingMeta.icon.full === 'mdi-heart'" class="text-sm" />

31
packages/nc-gui/components/cell/SingleSelect.vue

@ -4,6 +4,7 @@ import { message } from 'ant-design-vue'
import tinycolor from 'tinycolor2' import tinycolor from 'tinycolor2'
import type { Select as AntSelect } from 'ant-design-vue' import type { Select as AntSelect } from 'ant-design-vue'
import type { SelectOptionType } from 'nocodb-sdk' import type { SelectOptionType } from 'nocodb-sdk'
import { WorkspaceUserRoles } from 'nocodb-sdk'
import { import {
ActiveCellInj, ActiveCellInj,
CellClickHookInj, CellClickHookInj,
@ -40,6 +41,8 @@ const column = inject(ColumnInj)!
const readOnly = inject(ReadonlyInj)! const readOnly = inject(ReadonlyInj)!
const isLockedMode = inject(IsLockedInj, ref(false))
const isEditable = inject(EditModeInj, ref(false)) const isEditable = inject(EditModeInj, ref(false))
const activeCell = inject(ActiveCellInj, ref(false)) const activeCell = inject(ActiveCellInj, ref(false))
@ -73,7 +76,13 @@ const { isPg, isMysql } = useProject()
const tempSelectedOptState = ref<string>() const tempSelectedOptState = ref<string>()
const isNewOptionCreateEnabled = computed( const isNewOptionCreateEnabled = computed(
() => !isPublic.value && !disableOptionCreation && (hasRole('owner', true) || hasRole('creator', true)), () =>
!isPublic.value &&
!disableOptionCreation &&
(hasRole('owner', true) ||
hasRole('creator', true) ||
hasRole(WorkspaceUserRoles.OWNER, true) ||
hasRole(WorkspaceUserRoles.CREATOR, true)),
) )
const options = computed<(SelectOptionType & { value: string })[]>(() => { const options = computed<(SelectOptionType & { value: string })[]>(() => {
@ -94,7 +103,15 @@ const isOptionMissing = computed(() => {
return (options.value ?? []).every((op) => op.title !== searchVal.value) return (options.value ?? []).every((op) => op.title !== searchVal.value)
}) })
const hasEditRoles = computed(() => hasRole('owner', true) || hasRole('creator', true) || hasRole('editor', true)) const hasEditRoles = computed(
() =>
hasRole('owner', true) ||
hasRole('creator', true) ||
hasRole('editor', true) ||
hasRole(WorkspaceUserRoles.OWNER, true) ||
hasRole(WorkspaceUserRoles.CREATOR, true) ||
hasRole(WorkspaceUserRoles.EDITOR, true),
)
const editAllowed = computed(() => (hasEditRoles.value || isForm.value) && active.value) const editAllowed = computed(() => (hasEditRoles.value || isForm.value) && active.value)
@ -256,7 +273,11 @@ const selectedOpt = computed(() => {
</script> </script>
<template> <template>
<div class="h-full w-full flex items-center nc-single-select" :class="{ 'read-only': readOnly }" @click="toggleMenu"> <div
class="h-full w-full flex items-center nc-single-select"
:class="{ 'read-only': readOnly || isLockedMode }"
@click="toggleMenu"
>
<div v-if="!(active || isEditable)"> <div v-if="!(active || isEditable)">
<a-tag v-if="selectedOpt" class="rounded-tag" :color="selectedOpt.color"> <a-tag v-if="selectedOpt" class="rounded-tag" :color="selectedOpt.color">
<span <span
@ -282,8 +303,8 @@ const selectedOpt = computed(() => {
:allow-clear="!column.rqd && editAllowed" :allow-clear="!column.rqd && editAllowed"
:bordered="false" :bordered="false"
:open="isOpen && editAllowed" :open="isOpen && editAllowed"
:disabled="readOnly || !editAllowed" :disabled="readOnly || !editAllowed || isLockedMode"
:show-arrow="hasEditRoles && !readOnly && active && vModel === null" :show-arrow="hasEditRoles && !(readOnly || isLockedMode) && active && vModel === null"
:dropdown-class-name="`nc-dropdown-single-select-cell ${isOpen && active ? 'active' : ''}`" :dropdown-class-name="`nc-dropdown-single-select-cell ${isOpen && active ? 'active' : ''}`"
:show-search="isOpen && active" :show-search="isOpen && active"
@select="onSelect" @select="onSelect"

130
packages/nc-gui/components/cell/TextArea.vue

@ -8,6 +8,8 @@ const props = defineProps<{
const emits = defineEmits(['update:modelValue']) const emits = defineEmits(['update:modelValue'])
const column = inject(ColumnInj)
const editEnabled = inject(EditModeInj) const editEnabled = inject(EditModeInj)
const rowHeight = inject(RowHeightInj, ref(undefined)) const rowHeight = inject(RowHeightInj, ref(undefined))
@ -19,35 +21,111 @@ const vModel = useVModel(props, 'modelValue', emits, { defaultValue: '' })
const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))! const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))!
const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLTextAreaElement)?.focus() const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLTextAreaElement)?.focus()
const height = computed(() => {
if (!rowHeight.value) return 60
return rowHeight.value * 60
})
const isVisible = ref(false)
const inputWrapperRef = ref<HTMLElement | null>(null)
const inputRef = ref<HTMLTextAreaElement | null>(null)
watch(isVisible, () => {
if (isVisible.value) {
setTimeout(() => {
inputRef.value?.focus()
}, 100)
}
})
onClickOutside(inputWrapperRef, (e) => {
if ((e.target as HTMLElement)?.className.includes('nc-long-text-toggle-expand')) return
isVisible.value = false
})
</script> </script>
<template> <template>
<textarea <NcDropdown v-model:visible="isVisible" class="overflow-visible" :trigger="[]" placement="bottomLeft">
v-if="editEnabled" <div
:ref="focus" class="flex flex-row pt-0.5"
v-model="vModel" :class="{
rows="4" 'min-h-10': rowHeight !== 1,
class="h-full w-full min-h-[60px] outline-none border-none" 'min-h-6.5': rowHeight === 1,
:class="{ 'p-2': editEnabled }" }"
@blur="editEnabled = false" >
@keydown.alt.enter.stop <textarea
@keydown.shift.enter.stop v-if="editEnabled && !isVisible"
@keydown.down.stop :ref="focus"
@keydown.left.stop v-model="vModel"
@keydown.right.stop rows="4"
@keydown.up.stop class="h-full w-full outline-none border-none"
@keydown.delete.stop :class="`${editEnabled ? 'p-2' : ''}`"
@keydown.ctrl.z.stop :style="{
@keydown.meta.z.stop minHeight: `${height}px`,
@selectstart.capture.stop }"
@mousedown.stop @blur="editEnabled = false"
/> @keydown.alt.enter.stop
@keydown.shift.enter.stop
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span> @keydown.down.stop
@keydown.left.stop
<LazyCellClampedText v-else-if="rowHeight" :value="vModel" :lines="rowHeight" /> @keydown.right.stop
@keydown.up.stop
<span v-else>{{ vModel }}</span> @keydown.delete.stop
@keydown.ctrl.z.stop
@keydown.meta.z.stop
@selectstart.capture.stop
@mousedown.stop
/>
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<LazyCellClampedText v-else-if="rowHeight" :value="vModel" :lines="rowHeight" class="mr-7" />
<span v-else>{{ vModel }}</span>
<NcButton
class="!absolute right-0 bottom-0 nc-long-text-toggle-expand !duration-0"
:class="{
'top-1': rowHeight !== 1,
'mt-2': editEnabled,
'top-0.15': rowHeight === 1,
'!hidden': isExpandedFormOpen,
}"
type="text"
size="xsmall"
@click.stop="isVisible = !isVisible"
>
<GeneralIcon v-if="isVisible" icon="shrink" class="nc-long-text-toggle-expand h-3.75 w-3.75 !text-xs" />
<GeneralIcon v-else icon="expand" class="nc-long-text-toggle-expand h-3.75 w-3.75 !text-xs" />
</NcButton>
</div>
<template #overlay>
<div ref="inputWrapperRef" class="flex flex-col min-w-120 min-h-70 py-3 pl-3 pr-1">
<div
v-if="column"
class="flex flex-row gap-x-1 items-center font-medium pb-2.5 mb-1 py-1 mr-3 ml-1 border-b-1 border-gray-100"
>
<SmartsheetHeaderCellIcon class="flex" />
<div class="flex">
{{ column.title }}
</div>
</div>
<a-textarea
ref="inputRef"
v-model:value="vModel"
placeholder="Enter text"
class="p-1 !pt-1 !pr-3 !border-0 !border-r-0 !focus:outline-transparent nc-scrollbar-md"
:bordered="false"
:auto-size="{ minRows: 20, maxRows: 20 }"
@keydown.stop
@keydown.escape="isVisible = false"
/>
</div>
</template>
</NcDropdown>
</template> </template>
<style> <style>

8
packages/nc-gui/components/cell/TimePicker.vue

@ -23,11 +23,11 @@ const editable = inject(EditModeInj, ref(false))
const column = inject(ColumnInj)! const column = inject(ColumnInj)!
let isTimeInvalid = $ref(false) const isTimeInvalid = ref(false)
const dateFormat = isMysql(column.value.base_id) ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ' const dateFormat = isMysql(column.value.base_id) ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ'
const localState = $computed({ const localState = computed({
get() { get() {
if (!modelValue) { if (!modelValue) {
return undefined return undefined
@ -41,7 +41,7 @@ const localState = $computed({
dateTime = dayjs(`1999-01-01 ${modelValue}`) dateTime = dayjs(`1999-01-01 ${modelValue}`)
} }
if (!dateTime.isValid()) { if (!dateTime.isValid()) {
isTimeInvalid = true isTimeInvalid.value = true
return undefined return undefined
} }
@ -76,7 +76,7 @@ watch(
{ flush: 'post' }, { flush: 'post' },
) )
const placeholder = computed(() => (modelValue === null && showNull.value ? 'NULL' : isTimeInvalid ? 'Invalid time' : '')) const placeholder = computed(() => (modelValue === null && showNull.value ? 'NULL' : isTimeInvalid.value ? 'Invalid time' : ''))
useSelectedCellKeyupListener(active, (e: KeyboardEvent) => { useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
switch (e.key) { switch (e.key) {

8
packages/nc-gui/components/cell/Url.vue

@ -38,6 +38,8 @@ const disableOverlay = inject(CellUrlDisableOverlayInj, ref(false))
// Used in the logic of when to display error since we are not storing the url if it's not valid // Used in the logic of when to display error since we are not storing the url if it's not valid
const localState = ref(value) const localState = ref(value)
const rowHeight = inject(RowHeightInj, ref(undefined))
const isSurveyForm = inject(IsSurveyFormInj, ref(false)) const isSurveyForm = inject(IsSurveyFormInj, ref(false))
const vModel = computed({ const vModel = computed({
@ -109,7 +111,7 @@ watch(
:to="url" :to="url"
:target="cellUrlOptions?.behavior === 'replace' ? undefined : '_blank'" :target="cellUrlOptions?.behavior === 'replace' ? undefined : '_blank'"
> >
{{ value }} <LazyCellClampedText :value="value" :lines="rowHeight" />
</nuxt-link> </nuxt-link>
<nuxt-link <nuxt-link
@ -120,10 +122,10 @@ watch(
:to="url" :to="url"
:target="cellUrlOptions?.behavior === 'replace' ? undefined : '_blank'" :target="cellUrlOptions?.behavior === 'replace' ? undefined : '_blank'"
> >
{{ cellUrlOptions.overlay }} <LazyCellClampedText :value="cellUrlOptions.overlay" :lines="rowHeight" />
</nuxt-link> </nuxt-link>
<span v-else class="w-9/10 overflow-ellipsis overflow-hidden">{{ value }}</span> <span v-else class="w-9/10 overflow-ellipsis overflow-hidden"><LazyCellClampedText :value="value" :lines="rowHeight" /></span>
<div v-if="column.meta?.validate && !isValid && value?.length && !editEnabled" class="mr-1 w-1/10"> <div v-if="column.meta?.validate && !isValid && value?.length && !editEnabled" class="mr-1 w-1/10">
<a-tooltip placement="top"> <a-tooltip placement="top">

8
packages/nc-gui/components/cell/YearPicker.vue

@ -19,9 +19,9 @@ const active = inject(ActiveCellInj, ref(false))
const editable = inject(EditModeInj, ref(false)) const editable = inject(EditModeInj, ref(false))
let isYearInvalid = $ref(false) const isYearInvalid = ref(false)
const localState = $computed({ const localState = computed({
get() { get() {
if (!modelValue) { if (!modelValue) {
return undefined return undefined
@ -29,7 +29,7 @@ const localState = $computed({
const yearDate = dayjs(modelValue.toString(), 'YYYY') const yearDate = dayjs(modelValue.toString(), 'YYYY')
if (!yearDate.isValid()) { if (!yearDate.isValid()) {
isYearInvalid = true isYearInvalid.value = true
return undefined return undefined
} }
@ -62,7 +62,7 @@ watch(
{ flush: 'post' }, { flush: 'post' },
) )
const placeholder = computed(() => (modelValue === null && showNull.value ? 'NULL' : isYearInvalid ? 'Invalid year' : '')) const placeholder = computed(() => (modelValue === null && showNull.value ? 'NULL' : isYearInvalid.value ? 'Invalid year' : ''))
useSelectedCellKeyupListener(active, (e: KeyboardEvent) => { useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
switch (e.key) { switch (e.key) {

22
packages/nc-gui/components/cell/attachment/Carousel.vue

@ -1,12 +1,14 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onKeyDown } from '@vueuse/core' import { onKeyDown } from '@vueuse/core'
import { useAttachmentCell } from './utils' import { useAttachmentCell } from './utils'
import { computed, iconMap, isImage, onClickOutside, ref, useAttachment } from '#imports' import { computed, iconMap, isImage, onClickOutside, ref, useAttachment, useEventListener } from '#imports'
const { selectedImage, visibleItems, downloadFile } = useAttachmentCell()! const { selectedImage, visibleItems, downloadFile } = useAttachmentCell()!
const carouselRef = ref() const carouselRef = ref()
const container = ref()
const imageItems = computed(() => visibleItems.value.filter((item) => isImage(item.title, item.mimetype))) const imageItems = computed(() => visibleItems.value.filter((item) => isImage(item.title, item.mimetype)))
const { getPossibleAttachmentSrc } = useAttachment() const { getPossibleAttachmentSrc } = useAttachment()
@ -43,15 +45,17 @@ const setCarouselRef = (el: Element) => {
} }
/** close overlay view when clicking outside of image */ /** close overlay view when clicking outside of image */
onClickOutside(carouselRef, () => { useEventListener(container, 'click', (e) => {
selectedImage.value = false if (!(e.target as HTMLElement)?.closest('.keep-open') && !(e.target as HTMLElement)?.closest('img')) {
selectedImage.value = false
}
}) })
</script> </script>
<template> <template>
<GeneralOverlay v-model="selectedImage" :z-index="1001"> <GeneralOverlay v-model="selectedImage" :z-index="1001" class="bg-gray-500 bg-opacity-50">
<template v-if="selectedImage"> <template v-if="selectedImage">
<div class="overflow-hidden p-12 text-center relative"> <div ref="container" class="overflow-hidden p-12 text-center relative">
<div class="text-white group absolute top-5 right-5"> <div class="text-white group absolute top-5 right-5">
<component <component
:is="iconMap.closeCircle" :is="iconMap.closeCircle"
@ -61,7 +65,7 @@ onClickOutside(carouselRef, () => {
</div> </div>
<div <div
class="select-none group hover:(ring-1 ring-accent) ring-opacity-100 cursor-pointer leading-8 inline-block px-3 py-1 bg-gray-300 text-white mb-4 text-center rounded shadow" class="keep-open select-none group hover:(ring-1 ring-accent) ring-opacity-100 cursor-pointer leading-8 inline-block px-3 py-1 bg-gray-300 text-white mb-4 text-center rounded shadow"
@click.stop="downloadFile(selectedImage)" @click.stop="downloadFile(selectedImage)"
> >
<h3 class="group-hover:text-primary">{{ selectedImage && selectedImage.title }}</h3> <h3 class="group-hover:text-primary">{{ selectedImage && selectedImage.title }}</h3>
@ -75,13 +79,13 @@ onClickOutside(carouselRef, () => {
arrows arrows
> >
<template #prevArrow> <template #prevArrow>
<div class="custom-slick-arrow left-2 z-1"> <div class="custom-slick-arrow left-2 z-1 keep-open">
<MaterialSymbolsArrowCircleLeftRounded class="rounded-full" /> <MaterialSymbolsArrowCircleLeftRounded class="rounded-full" />
</div> </div>
</template> </template>
<template #nextArrow> <template #nextArrow>
<div class="custom-slick-arrow !right-2 z-1"> <div class="custom-slick-arrow !right-2 z-1 keep-open">
<MaterialSymbolsArrowCircleRightRounded class="rounded-full" /> <MaterialSymbolsArrowCircleRightRounded class="rounded-full" />
</div> </div>
</template> </template>
@ -133,7 +137,7 @@ onClickOutside(carouselRef, () => {
filter: grayscale(100%); filter: grayscale(100%);
} }
.ant-carousel :deep .slick-thumb li.slick-active img { .ant-carousel :deep(.slick-thumb li.slick-active img) {
filter: grayscale(0%); filter: grayscale(0%);
} }

61
packages/nc-gui/components/cell/attachment/Modal.vue

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onKeyDown } from '@vueuse/core' import { onKeyDown, useEventListener } from '@vueuse/core'
import { useAttachmentCell } from './utils' import { useAttachmentCell } from './utils'
import { useSortable } from './sort' import { useSortable } from './sort'
import { iconMap, isImage, ref, useAttachment, useDropZone, useUIPermission, watch } from '#imports' import { iconMap, isImage, ref, useAttachment, useDropZone, useUIPermission, watch } from '#imports'
@ -25,8 +25,7 @@ const {
renameFile, renameFile,
} = useAttachmentCell()! } = useAttachmentCell()!
// todo: replace placeholder var const isLocked = inject(IsLockedInj, ref(false))
const isLocked = ref(false)
const dropZoneRef = ref<HTMLDivElement>() const dropZoneRef = ref<HTMLDivElement>()
@ -59,21 +58,29 @@ function onClick(item: Record<string, any>) {
}) })
} }
const isModalOpen = ref(false)
const filetoDelete = reactive({
title: '',
i: 0,
})
function onRemoveFileClick(title: any, i: number) { function onRemoveFileClick(title: any, i: number) {
Modal.confirm({ isModalOpen.value = true
title: `Do you want to delete '${title}'?`, filetoDelete.i = i
wrapClassName: 'nc-modal-attachment-delete', filetoDelete.title = title
okText: 'Yes', }
okType: 'danger',
cancelText: 'No', // when user paste on modal
onOk() { useEventListener(dropZoneRef, 'paste', (event: ClipboardEvent) => {
try { if (event.clipboardData?.files) {
removeFile(i) onDrop(event.clipboardData.files)
} catch (e: any) { }
message.error(e.message) })
} const handleFileDelete = (i: number) => {
}, removeFile(i)
}) isModalOpen.value = false
filetoDelete.i = 0
filetoDelete.title = ''
} }
</script> </script>
@ -105,12 +112,11 @@ function onRemoveFileClick(title: any, i: number) {
</div> </div>
<div v-if="selectedVisibleItems.includes(true)" class="flex flex-1 items-center gap-3 justify-end mr-[30px]"> <div v-if="selectedVisibleItems.includes(true)" class="flex flex-1 items-center gap-3 justify-end mr-[30px]">
<a-button type="primary" class="nc-attachment-download-all" @click="bulkDownloadFiles"> Bulk Download </a-button> <NcButton type="primary" class="nc-attachment-download-all" @click="bulkDownloadFiles"> Bulk Download </NcButton>
</div> </div>
</div> </div>
</template> </template>
<div ref="dropZoneRef" tabindex="0">
<div ref="dropZoneRef">
<template v-if="isSharedForm || (!readOnly && !dragging)"> <template v-if="isSharedForm || (!readOnly && !dragging)">
<general-overlay <general-overlay
v-model="isOverDropZone" v-model="isOverDropZone"
@ -197,6 +203,21 @@ function onRemoveFileClick(title: any, i: number) {
</div> </div>
</div> </div>
</div> </div>
<GeneralDeleteModal v-model:visible="isModalOpen" entity-name="File" :on-delete="() => handleFileDelete(filetoDelete.i)">
<template #entity-preview>
<span>
<div class="flex flex-row items-center py-2.25 px-2.5 bg-gray-50 rounded-lg text-gray-700 mb-4">
<GeneralIcon icon="file" class="nc-view-icon"></GeneralIcon>
<div
class="capitalize text-ellipsis overflow-hidden select-none w-full pl-1.75"
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }"
>
{{ filetoDelete.title }}
</div>
</div>
</span>
</template>
</GeneralDeleteModal>
</a-modal> </a-modal>
</template> </template>

38
packages/nc-gui/components/cell/attachment/RenameFile.vue

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { generateUniqueName, onKeyStroke, onMounted, reactive, ref } from '#imports' import { onKeyStroke, onMounted, reactive, ref } from '#imports'
const props = defineProps<{ const props = defineProps<{
title: string title: string
@ -23,9 +23,10 @@ function renameFile(fileName: string) {
emit('rename', fileName) emit('rename', fileName)
} }
async function useRandomName() { // generate random name for file
form.title = await generateUniqueName() // async function useRandomName() {
} // form.title = await generateUniqueName()
// }
const rules = { const rules = {
title: [{ required: true, message: 'title is required.' }], title: [{ required: true, message: 'title is required.' }],
@ -45,34 +46,21 @@ onMounted(() => {
</script> </script>
<template> <template>
<a-modal <GeneralModal v-model:visible="visible" class="nc-attachment-rename-modal !w-[30rem]">
:visible="visible" <div class="flex flex-col items-center justify-center h-full p-8">
:closable="false" <div class="text-lg font-semibold self-start mb-4">Rename File</div>
:mask-closable="false"
destroy-on-close
title="Rename file"
class="nc-attachment-rename-modal"
width="min(100%, 620px)"
:footer="null"
centered
@cancel="onCancel"
>
<div class="flex flex-col items-center justify-center h-full">
<a-form class="w-full h-full" no-style :model="form" @finish="renameFile(form.title)"> <a-form class="w-full h-full" no-style :model="form" @finish="renameFile(form.title)">
<a-form-item class="w-full" name="title" :rules="rules.title"> <a-form-item class="w-full" name="title" :rules="rules.title">
<a-input ref="inputEl" v-model:value="form.title" class="w-full" :placeholder="$t('general.rename')" /> <a-input ref="inputEl" v-model:value="form.title" class="w-full" :placeholder="$t('general.rename')" />
</a-form-item> </a-form-item>
<div class="flex items-center justify-center gap-6 w-full mt-4"> <div class="flex flex-row gap-x-2 mt-2.5 pt-2.5 justify-end">
<button class="scaling-btn bg-opacity-100" type="submit"> <NcButton key="back" html-type="back" type="secondary">{{ $t('general.cancel') }}</NcButton>
<span>{{ $t('general.confirm') }}</span> <NcButton key="submit" html-type="submit" type="primary">{{ $t('general.confirm') }}</NcButton>
</button>
<button class="scaling-btn bg-opacity-100" type="button" @click="useRandomName">
<span>{{ $t('title.generateRandomName') }}</span>
</button>
</div> </div>
</a-form> </a-form>
</div> </div>
</a-modal> </GeneralModal>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">

13
packages/nc-gui/components/cell/attachment/index.vue

@ -2,11 +2,11 @@
import { onKeyDown } from '@vueuse/core' import { onKeyDown } from '@vueuse/core'
import { useProvideAttachmentCell } from './utils' import { useProvideAttachmentCell } from './utils'
import { useSortable } from './sort' import { useSortable } from './sort'
import { RowHeightInj } from '~/context'
import { import {
ActiveCellInj, ActiveCellInj,
CurrentCellInj, CurrentCellInj,
DropZoneRef, DropZoneRef,
RowHeightInj,
iconMap, iconMap,
inject, inject,
isImage, isImage,
@ -40,6 +40,8 @@ const sortableRef = ref<HTMLDivElement>()
const currentCellRef = inject(CurrentCellInj, dropZoneInjection.value) const currentCellRef = inject(CurrentCellInj, dropZoneInjection.value)
const isLockedMode = inject(IsLockedInj, ref(false))
const { isSharedForm } = useSmartsheetStoreOrThrow()! const { isSharedForm } = useSmartsheetStoreOrThrow()!
const { getPossibleAttachmentSrc, openAttachment } = useAttachment() const { getPossibleAttachmentSrc, openAttachment } = useAttachment()
@ -56,11 +58,15 @@ const {
open, open,
FileIcon, FileIcon,
selectedImage, selectedImage,
isReadonly, isReadonly: _isReadonly,
storedFiles, storedFiles,
} = useProvideAttachmentCell(updateModelValue) } = useProvideAttachmentCell(updateModelValue)
const { dragging } = useSortable(sortableRef, visibleItems, updateModelValue, isReadonly) const { dragging } = useSortable(sortableRef, visibleItems, updateModelValue, _isReadonly)
const isReadonly = computed(() => {
return isLockedMode.value || _isReadonly.value
})
const { state: rowState } = useSmartsheetRowStoreOrThrow() const { state: rowState } = useSmartsheetRowStoreOrThrow()
@ -137,6 +143,7 @@ const rowHeight = inject(RowHeightInj, ref(1.8))
<template> <template>
<div <div
ref="attachmentCellRef" ref="attachmentCellRef"
tabindex="0"
:style="{ :style="{
height: isForm ? undefined : `max(${(rowHeight || 1) * 1.8}rem, 41px)`, height: isForm ? undefined : `max(${(rowHeight || 1) * 1.8}rem, 41px)`,
}" }"

8
packages/nc-gui/components/cell/attachment/sort.ts

@ -10,18 +10,18 @@ export function useSortable(
updateModelValue: (data: string | Record<string, any>[]) => void, updateModelValue: (data: string | Record<string, any>[]) => void,
isReadonly: MaybeRef<boolean> = false, isReadonly: MaybeRef<boolean> = false,
) { ) {
let dragging = $ref(false) const dragging = ref(false)
function onSortStart(evt: SortableEvent) { function onSortStart(evt: SortableEvent) {
evt.stopImmediatePropagation() evt.stopImmediatePropagation()
evt.preventDefault() evt.preventDefault()
dragging = true dragging.value = true
} }
async function onSortEnd(evt: SortableEvent) { async function onSortEnd(evt: SortableEvent) {
evt.stopImmediatePropagation() evt.stopImmediatePropagation()
evt.preventDefault() evt.preventDefault()
dragging = false dragging.value = false
const _items = unref(items) const _items = unref(items)
@ -59,7 +59,7 @@ export function useSortable(
}) })
return { return {
dragging: $$(dragging), dragging,
initSortable, initSortable,
} }
} }

7
packages/nc-gui/components/cell/attachment/utils.ts

@ -1,4 +1,5 @@
import type { AttachmentType } from 'nocodb-sdk' import type { AttachmentType } from 'nocodb-sdk'
import { readonly } from '@vue/reactivity'
import RenameFile from './RenameFile.vue' import RenameFile from './RenameFile.vue'
import { import {
ColumnInj, ColumnInj,
@ -33,6 +34,8 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
(updateModelValue: (data: string | Record<string, any>[]) => void) => { (updateModelValue: (data: string | Record<string, any>[]) => void) => {
const isReadonly = inject(ReadonlyInj, ref(false)) const isReadonly = inject(ReadonlyInj, ref(false))
const isLockedMode = inject(IsLockedInj, ref(false))
const isPublic = inject(IsPublicInj, ref(false)) const isPublic = inject(IsPublicInj, ref(false))
const isForm = inject(IsFormInj, ref(false)) const isForm = inject(IsFormInj, ref(false))
@ -216,7 +219,9 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
} }
/** save files on drop */ /** save files on drop */
async function onDrop(droppedFiles: File[] | null) { async function onDrop(droppedFiles: FileList | File[] | null) {
if (isReadonly.value) return
if (droppedFiles) { if (droppedFiles) {
// set files // set files
await onFileSelect(droppedFiles) await onFileSelect(droppedFiles)

3
packages/nc-gui/components/cmd-k/index.vue

@ -0,0 +1,3 @@
<template>
<div />
</template>

148
packages/nc-gui/components/dashboard/Sidebar.vue

@ -0,0 +1,148 @@
<script lang="ts" setup>
import { storeToRefs } from 'pinia'
import { useGlobal } from '#imports'
const router = useRouter()
const route = router.currentRoute
const workspaceStore = useWorkspace()
const { activeWorkspace, isWorkspaceOwnerOrCreator } = storeToRefs(workspaceStore)
const projectStore = useProject()
const { isSharedBase } = storeToRefs(projectStore)
const { navigateToWorkspaceSettings } = useWorkspace()
const { isUIAllowed } = useUIPermission()
const dialogOpen = ref(false)
const openDialogKey = ref<string>('')
const dataSourcesState = ref<string>('')
const projectId = ref<string>()
const isCreateProjectOpen = ref(false)
function toggleDialog(value?: boolean, key?: string, dsState?: string, pId?: string) {
dialogOpen.value = value ?? !dialogOpen.value
openDialogKey.value = key || ''
dataSourcesState.value = dsState || ''
projectId.value = pId || ''
}
// todo:
const currentVersion = ref('')
const isTreeViewOnScrollTop = ref(true)
const onTreeViewScrollTop = (onScrollTop: boolean) => {
isTreeViewOnScrollTop.value = !onScrollTop
}
const { appInfo } = useGlobal()
const navigateToSettings = () => {
navigateToWorkspaceSettings()
}
</script>
<template>
<div
class="nc-sidebar flex flex-col bg-gray-50 outline-r-1 outline-gray-100 select-none"
:style="{
outlineWidth: '1px',
}"
>
<div class="flex flex-col" :style="{ height: isSharedBase ? 'auto' : 'var(--sidebar-top-height)' }">
<div style="border-bottom-width: 1px" class="flex items-center px-1 nc-sidebar-header !border-0 py-1.25 pl-2">
<div class="flex flex-row flex-grow hover:bg-gray-100 pl-2 pr-1 py-0.5 rounded-md max-w-full">
<a
v-if="isSharedBase"
class="w-[40px] min-w-[40px] transition-all duration-200 p-1 cursor-pointer transform hover:scale-105"
href="https://github.com/nocodb/nocodb"
target="_blank"
>
<a-tooltip placement="bottom">
<template #title>
{{ currentVersion }}
</template>
<img width="25" alt="NocoDB" src="~/assets/img/icons/512x512-trans.png" />
</a-tooltip>
</a>
<WorkspaceMenu :workspace="activeWorkspace" :is-open="true">
<template #brandIcon>
<div
v-if="!isSharedBase"
v-e="['c:navbar:home']"
data-testid="nc-noco-brand-icon"
class="w-[29px] min-w-[29px] nc-noco-brand-icon"
>
<img width="25" class="mr-0" alt="NocoDB" src="~/assets/img/icons/512x512.png" />
</div>
</template>
</WorkspaceMenu>
</div>
</div>
<template v-if="!isSharedBase">
<div class="w-full mt-2"></div>
<div class="h-17.5">
<div
v-if="isWorkspaceOwnerOrCreator"
role="button"
class="nc-sidebar-top-button"
data-testid="nc-sidebar-team-settings-btn"
@click="navigateToSettings"
>
<GeneralIcon icon="settings" class="!h-3.9" />
<div>Team & Settings</div>
</div>
<WorkspaceCreateProjectBtn
v-if="isUIAllowed('projectCreate', false)"
v-model:is-open="isCreateProjectOpen"
modal
type="text"
class="!p-0 mx-1"
data-testid="nc-sidebar-create-project-btn"
:active-workspace-id="route.params.typeOrId"
>
<div
class="gap-x-2 flex flex-row w-full items-center nc-sidebar-top-button !my-0 !mx-0"
:class="{
'bg-gray-100': isCreateProjectOpen,
}"
>
<MdiPlus class="!h-4" />
<div class="flex">{{ $t('title.newProj') }}</div>
</div>
</WorkspaceCreateProjectBtn>
</div>
<div class="flex flex-grow"></div>
<div class="text-gray-500 mx-5 font-medium mb-1.5">{{ $t('objects.projects') }}</div>
</template>
<div
class="w-full border-b-1"
:class="{
'border-gray-200': !isTreeViewOnScrollTop,
'border-transparent': isTreeViewOnScrollTop,
}"
></div>
</div>
<LazyDashboardTreeViewNew
@create-base-dlg="toggleDialog(true, 'dataSources', undefined, projectId)"
@on-scroll-top="onTreeViewScrollTop"
/>
</div>
</template>
<style lang="scss" scoped>
.nc-sidebar-top-button {
@apply flex flex-row mx-1 px-3.5 rounded-md items-center py-0.75 my-0.5 gap-x-2 hover:bg-gray-200 cursor-pointer;
}
</style>

235
packages/nc-gui/components/dashboard/TreeView.vue

@ -1,11 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { nextTick } from '@vue/runtime-core' import { nextTick } from '@vue/runtime-core'
import { Icon as IconifyIcon } from '@iconify/vue'
import type { TableType } from 'nocodb-sdk' import type { TableType } from 'nocodb-sdk'
import type { Input } from 'ant-design-vue' import type { Input } from 'ant-design-vue'
import { Dropdown, Tooltip, message } from 'ant-design-vue' import { Dropdown, Tooltip, message } from 'ant-design-vue'
import Sortable from 'sortablejs' import Sortable from 'sortablejs'
import GithubButton from 'vue-github-button' // import GithubButton from 'vue-github-button'
import { Icon as IconifyIcon } from '@iconify/vue'
import type { VNodeRef } from '#imports' import type { VNodeRef } from '#imports'
import { import {
ClientType, ClientType,
@ -62,17 +62,17 @@ const { addUndo, defineProjectScope } = useUndoRedo()
const toggleDialog = inject(ToggleDialogInj, () => {}) const toggleDialog = inject(ToggleDialogInj, () => {})
const keys = $ref<Record<string, number>>({}) const keys = ref<Record<string, number>>({})
const activeKey = ref<string[]>([]) const activeKey = ref<string[]>([])
const menuRefs = $ref<HTMLElement[] | HTMLElement>() const menuRefs = ref<HTMLElement[] | HTMLElement>()
let filterQuery = $ref('') const filterQuery = ref('')
const activeTable = computed(() => ([TabType.TABLE, TabType.VIEW].includes(activeTab.value?.type) ? activeTab.value.id : null)) const activeTable = computed(() => ([TabType.TABLE, TabType.VIEW].includes(activeTab.value?.type) ? activeTab.value.id : null))
const tablesById = $computed(() => const tablesById = computed(() =>
tables.value?.reduce<Record<string, TableType>>((acc, table) => { tables.value?.reduce<Record<string, TableType>>((acc, table) => {
acc[table.id!] = table acc[table.id!] = table
@ -80,9 +80,9 @@ const tablesById = $computed(() =>
}, {}), }, {}),
) )
const filteredTables = $computed(() => const filteredTables = computed(() =>
tables.value?.filter( tables.value?.filter(
(table) => !searchActive.value || !filterQuery || table.title.toLowerCase().includes(filterQuery.toLowerCase()), (table) => !searchActive.value || !filterQuery.value || table.title.toLowerCase().includes(filterQuery.value.toLowerCase()),
), ),
) )
@ -100,7 +100,7 @@ const initSortable = (el: Element) => {
if (newIndex === oldIndex) return if (newIndex === oldIndex) return
const itemEl = evt.item as HTMLLIElement const itemEl = evt.item as HTMLLIElement
const item = tablesById[itemEl.dataset.id as string] const item = tablesById.value[itemEl.dataset.id as string]
// store the old order for undo // store the old order for undo
const oldOrder = item.order const oldOrder = item.order
@ -116,8 +116,8 @@ const initSortable = (el: Element) => {
const itemAfterEl = children[newIndex + 1] as HTMLLIElement const itemAfterEl = children[newIndex + 1] as HTMLLIElement
// get items meta of before and after the moved item // get items meta of before and after the moved item
const itemBefore = itemBeforeEl && tablesById[itemBeforeEl.dataset.id as string] const itemBefore = itemBeforeEl && tablesById.value[itemBeforeEl.dataset.id as string]
const itemAfter = itemAfterEl && tablesById[itemAfterEl.dataset.id as string] const itemAfter = itemAfterEl && tablesById.value[itemAfterEl.dataset.id as string]
// set new order value based on the new order of the items // set new order value based on the new order of the items
if (children.length - 1 === evt.newIndex) { if (children.length - 1 === evt.newIndex) {
@ -143,10 +143,10 @@ const initSortable = (el: Element) => {
} }
// force re-render the list // force re-render the list
if (keys[base_id]) { if (keys.value[base_id]) {
keys[base_id] = keys[base_id] + 1 keys.value[base_id] = keys.value[base_id] + 1
} else { } else {
keys[base_id] = 1 keys.value[base_id] = 1
} }
// update the item order // update the item order
@ -203,11 +203,11 @@ const initSortable = (el: Element) => {
} }
watchEffect(() => { watchEffect(() => {
if (menuRefs) { if (menuRefs.value) {
if (menuRefs instanceof HTMLElement) { if (menuRefs.value instanceof HTMLElement) {
initSortable(menuRefs) initSortable(menuRefs.value)
} else { } else {
menuRefs.forEach((el) => initSortable(el)) menuRefs.value.forEach((el) => initSortable(el))
} }
} }
}) })
@ -221,9 +221,9 @@ const icon = (table: TableType) => {
} }
} }
const contextMenuTarget = reactive<{ type?: 'table' | 'main'; value?: any }>({}) const contextMenuTarget = reactive<{ type?: 'base' | 'table' | 'main' | 'layout'; value?: any }>({})
const setMenuContext = (type: 'table' | 'main', value?: any) => { const setMenuContext = (type: 'base' | 'table' | 'main', value?: any) => {
contextMenuTarget.type = type contextMenuTarget.type = type
contextMenuTarget.value = value contextMenuTarget.value = value
} }
@ -244,6 +244,7 @@ function openRenameTableDialog(table: TableType, baseId?: string, rightClick = f
const isOpen = ref(true) const isOpen = ref(true)
const { close } = useDialog(resolveComponent('DlgTableRename'), { const { close } = useDialog(resolveComponent('DlgTableRename'), {
'v-if': table && (baseId || bases.value[0].id),
'modelValue': isOpen, 'modelValue': isOpen,
'tableMeta': table, 'tableMeta': table,
'baseId': baseId || bases.value[0].id, 'baseId': baseId || bases.value[0].id,
@ -309,6 +310,7 @@ function openTableCreateDialog(baseId?: string) {
const isOpen = ref(true) const isOpen = ref(true)
const { close } = useDialog(resolveComponent('DlgTableCreate'), { const { close } = useDialog(resolveComponent('DlgTableCreate'), {
'v-if': baseId || bases.value[0].id,
'modelValue': isOpen, 'modelValue': isOpen,
'baseId': baseId || bases.value[0].id, 'baseId': baseId || bases.value[0].id,
'onUpdate:modelValue': closeDialog, 'onUpdate:modelValue': closeDialog,
@ -327,12 +329,56 @@ function openTableCreateDialog(baseId?: string) {
} }
} }
function openTableCreateMagicDialog(baseId?: string) {
$e('c:table:create:navdraw')
const isOpen = ref(true)
const { close } = useDialog(resolveComponent('DlgTableMagic'), {
'v-if': baseId || bases.value[0].id,
'modelValue': isOpen,
'baseId': baseId || bases.value[0].id,
'onUpdate:modelValue': closeDialog,
})
function closeDialog() {
isOpen.value = false
close(1000)
}
}
function openSchemaMagicDialog(baseId?: string) {
$e('c:table:create:navdraw')
const isOpen = ref(true)
const { close } = useDialog(resolveComponent('DlgSchemaMagic'), {
'modelValue': isOpen,
'baseId': baseId || bases.value[0].id,
'onUpdate:modelValue': closeDialog,
})
function closeDialog() {
isOpen.value = false
close(1000)
}
}
/*
function openErdView(base?: BaseType) {
if (!base) base = bases.value?.filter((base: BaseType) => base.enabled)[0]
navigateTo(`/${route.params.projectType}/${route.params.projectId}/erd/${base.id}`)
}
*/
const searchInputRef: VNodeRef = (vnode: typeof Input) => vnode?.$el?.focus() const searchInputRef: VNodeRef = (vnode: typeof Input) => vnode?.$el?.focus()
const beforeSearch = ref<string[]>([]) const beforeSearch = ref<string[]>([])
const onSearchCloseIconClick = () => { const onSearchCloseIconClick = () => {
filterQuery = '' filterQuery.value = ''
toggleSearchActive(false) toggleSearchActive(false)
activeKey.value = beforeSearch.value activeKey.value = beforeSearch.value
} }
@ -393,7 +439,7 @@ watch(
} }
} }
if (project.value.title && tableTitle) { if (project.value.title && tableTitle) {
document.title = `${project.value.title}: ${tableTitle} | NocoDB` document.title = `${project.value.title}: ${tableTitle}`
} else { } else {
document.title = 'NocoDB' document.title = 'NocoDB'
} }
@ -421,12 +467,21 @@ const setIcon = async (icon: string, table: TableType) => {
} }
} }
const handleContext = (e: MouseEvent) => {
if (!document.querySelector('.base-context, .table-context')?.contains(e.target as Node)) {
setMenuContext('main')
}
}
useEventListener(document, 'contextmenu', handleContext, true)
const duplicateTable = async (table: TableType) => { const duplicateTable = async (table: TableType) => {
if (!table || !table.id || !table.project_id) return if (!table || !table.id || !table.project_id) return
const isOpen = ref(true) const isOpen = ref(true)
const { close } = useDialog(resolveComponent('DlgTableDuplicate'), { const { close } = useDialog(resolveComponent('DlgTableDuplicate'), {
'v-if': table,
'modelValue': isOpen, 'modelValue': isOpen,
'table': table, 'table': table,
'onOk': async (jobData: { id: string }) => { 'onOk': async (jobData: { id: string }) => {
@ -463,8 +518,8 @@ const duplicateTable = async (table: TableType) => {
<div class="pt-2 pl-2 pb-2 flex-1 overflow-y-auto flex flex-col scrollbar-thin-dull" :class="{ 'mb-[20px]': isSharedBase }"> <div class="pt-2 pl-2 pb-2 flex-1 overflow-y-auto flex flex-col scrollbar-thin-dull" :class="{ 'mb-[20px]': isSharedBase }">
<div <div
v-if="bases[0] && bases[0].enabled && !bases.slice(1).filter((el) => el.enabled)?.length" v-if="bases[0] && bases[0].enabled && !bases.slice(1).filter((el) => el.enabled)?.length"
class="min-h-[36px] py-1 px-3 flex w-full items-center gap-1 cursor-pointer" class="base-context min-h-[36px] py-1 px-3 flex w-full items-center gap-1 cursor-pointer"
@contextmenu="setMenuContext('main')" @contextmenu="setMenuContext('base', bases[0])"
> >
<Transition name="slide-left" mode="out-in"> <Transition name="slide-left" mode="out-in">
<a-input <a-input
@ -559,7 +614,7 @@ const duplicateTable = async (table: TableType) => {
</div> </div>
</a-menu-item> </a-menu-item>
<a-menu-item <a-menu-item
v-if="appInfo.ee" v-if="appInfo.ee && false"
key="connect-new-source" key="connect-new-source"
@click="toggleDialog(true, 'dataSources', ClientType.SNOWFLAKE)" @click="toggleDialog(true, 'dataSources', ClientType.SNOWFLAKE)"
> >
@ -607,6 +662,29 @@ const duplicateTable = async (table: TableType) => {
<template #overlay> <template #overlay>
<a-menu class="!py-0 rounded text-sm"> <a-menu class="!py-0 rounded text-sm">
<a-menu-item-group class="!px-0 !mx-0">
<template #title>
<div class="flex items-center">
Noco
<GeneralIcon icon="magic" class="ml-1 text-orange-400" />
</div>
</template>
<a-menu-item key="table-magic" @click="openTableCreateMagicDialog(bases[0].id)">
<div class="color-transition nc-project-menu-item group">
<GeneralIcon icon="magic1" class="group-hover:text-accent" />
Create table
</div>
</a-menu-item>
<a-menu-item key="schema-magic" @click="openSchemaMagicDialog(bases[0].id)">
<div class="color-transition nc-project-menu-item group">
<GeneralIcon icon="magic1" class="group-hover:text-accent" />
Create schema
</div>
</a-menu-item>
</a-menu-item-group>
<a-menu-divider class="my-0" />
<!-- Quick Import From --> <!-- Quick Import From -->
<a-menu-item-group :title="$t('title.quickImportFrom')" class="!px-0 !mx-0"> <a-menu-item-group :title="$t('title.quickImportFrom')" class="!px-0 !mx-0">
<a-menu-item <a-menu-item
@ -682,7 +760,7 @@ const duplicateTable = async (table: TableType) => {
</div> </div>
</a-menu-item> </a-menu-item>
<a-menu-item <a-menu-item
v-if="appInfo.ee" v-if="appInfo.ee && false"
key="connect-new-source" key="connect-new-source"
@click="toggleDialog(true, 'dataSources', ClientType.SNOWFLAKE)" @click="toggleDialog(true, 'dataSources', ClientType.SNOWFLAKE)"
> >
@ -740,7 +818,7 @@ const duplicateTable = async (table: TableType) => {
> >
<GeneralTooltip class="pl-2 pr-3 py-2" modifier-key="Alt"> <GeneralTooltip class="pl-2 pr-3 py-2" modifier-key="Alt">
<template #title>{{ table.table_name }}</template> <template #title>{{ table.table_name }}</template>
<div class="flex items-center gap-2 h-full" @contextmenu="setMenuContext('table', table)"> <div class="table-context flex items-center gap-2 h-full" @contextmenu="setMenuContext('table', table)">
<div class="flex w-auto" :data-testid="`tree-view-table-draggable-handle-${table.title}`"> <div class="flex w-auto" :data-testid="`tree-view-table-draggable-handle-${table.title}`">
<component <component
:is="isUIAllowed('tableIconCustomisation') ? Dropdown : 'div'" :is="isUIAllowed('tableIconCustomisation') ? Dropdown : 'div'"
@ -803,12 +881,8 @@ const duplicateTable = async (table: TableType) => {
</div> </div>
</a-menu-item> </a-menu-item>
<a-menu-item <a-menu-item v-if="isUIAllowed('table-duplicate')" @click="duplicateTable(table)">
v-if="isUIAllowed('table-duplicate') && !table.mm" <div class="nc-project-menu-item" :data-testid="`sidebar-table-duplicate-${table.title}`">
v-e="['c:table:duplicate']"
@click="duplicateTable(table)"
>
<div class="nc-project-menu-item">
{{ $t('general.duplicate') }} {{ $t('general.duplicate') }}
</div> </div>
</a-menu-item> </a-menu-item>
@ -855,11 +929,19 @@ const duplicateTable = async (table: TableType) => {
> >
<a-collapse-panel :key="`collapse-${base.id}`"> <a-collapse-panel :key="`collapse-${base.id}`">
<template #header> <template #header>
<div v-if="index === '0'" class="flex items-center gap-2 text-gray-500 font-bold"> <div
v-if="index === '0'"
class="base-context flex items-center gap-2 text-gray-500 font-bold"
@contextmenu="setMenuContext('base', base)"
>
<GeneralBaseLogo :base-type="base.type" /> <GeneralBaseLogo :base-type="base.type" />
Default ({{ tables.filter((table) => table.base_id === base.id).length || '0' }}) Default ({{ tables.filter((table) => table.base_id === base.id).length || '0' }})
</div> </div>
<div v-else class="flex items-center gap-2 text-gray-500 font-bold"> <div
v-else
class="base-context flex items-center gap-2 text-gray-500 font-bold"
@contextmenu="setMenuContext('base', base)"
>
<GeneralBaseLogo :base-type="base.type" /> <GeneralBaseLogo :base-type="base.type" />
{{ base.alias || '' }} {{ base.alias || '' }}
({{ tables.filter((table) => table.base_id === base.id).length || '0' }}) ({{ tables.filter((table) => table.base_id === base.id).length || '0' }})
@ -889,6 +971,29 @@ const duplicateTable = async (table: TableType) => {
<template #overlay> <template #overlay>
<a-menu class="!py-0 rounded text-sm"> <a-menu class="!py-0 rounded text-sm">
<a-menu-item-group class="!px-0 !mx-0">
<template #title>
<div class="flex items-center">
Noco
<GeneralIcon icon="magic" class="ml-1 text-orange-400" />
</div>
</template>
<a-menu-item key="table-magic" @click="openTableCreateMagicDialog(bases[0].id)">
<div class="color-transition nc-project-menu-item group">
<GeneralIcon icon="magic1" class="group-hover:text-accent" />
Create table
</div>
</a-menu-item>
<a-menu-item key="schema-magic" @click="openSchemaMagicDialog(bases[0].id)">
<div class="color-transition nc-project-menu-item group">
<GeneralIcon icon="magic1" class="group-hover:text-accent" />
Create schema
</div>
</a-menu-item>
</a-menu-item-group>
<a-menu-divider class="my-0" />
<!-- Quick Import From --> <!-- Quick Import From -->
<a-menu-item-group :title="$t('title.quickImportFrom')" class="!px-0 !mx-0"> <a-menu-item-group :title="$t('title.quickImportFrom')" class="!px-0 !mx-0">
<a-menu-item <a-menu-item
@ -978,6 +1083,29 @@ const duplicateTable = async (table: TableType) => {
<template #overlay> <template #overlay>
<a-menu class="!py-0 rounded text-sm"> <a-menu class="!py-0 rounded text-sm">
<a-menu-item-group class="!px-0 !mx-0">
<template #title>
<div class="flex items-center">
Noco
<GeneralIcon icon="magic" class="ml-1 text-orange-400" />
</div>
</template>
<a-menu-item key="table-magic" @click="openTableCreateMagicDialog(base.id)">
<div class="color-transition nc-project-menu-item group">
<GeneralIcon icon="magic1" class="group-hover:text-accent" />
Create table
</div>
</a-menu-item>
<a-menu-item key="schema-magic" @click="openSchemaMagicDialog(base.id)">
<div class="color-transition nc-project-menu-item group">
<GeneralIcon icon="magic1" class="group-hover:text-accent" />
Create schema
</div>
</a-menu-item>
</a-menu-item-group>
<a-menu-divider class="my-0" />
<!-- Quick Import From --> <!-- Quick Import From -->
<a-menu-item-group :title="$t('title.quickImportFrom')" class="!px-0 !mx-0"> <a-menu-item-group :title="$t('title.quickImportFrom')" class="!px-0 !mx-0">
<a-menu-item <a-menu-item
@ -1067,7 +1195,7 @@ const duplicateTable = async (table: TableType) => {
> >
<GeneralTooltip class="pl-8 pr-3 py-2" modifier-key="Alt"> <GeneralTooltip class="pl-8 pr-3 py-2" modifier-key="Alt">
<template #title>{{ table.table_name }}</template> <template #title>{{ table.table_name }}</template>
<div class="flex items-center gap-2 h-full" @contextmenu="setMenuContext('table', table)"> <div class="table-context flex items-center gap-2 h-full" @contextmenu="setMenuContext('table', table)">
<div class="flex w-auto" :data-testid="`tree-view-table-draggable-handle-${table.title}`"> <div class="flex w-auto" :data-testid="`tree-view-table-draggable-handle-${table.title}`">
<component <component
:is="isUIAllowed('tableIconCustomisation') ? Dropdown : 'div'" :is="isUIAllowed('tableIconCustomisation') ? Dropdown : 'div'"
@ -1163,7 +1291,15 @@ const duplicateTable = async (table: TableType) => {
<template v-if="!isSharedBase" #overlay> <template v-if="!isSharedBase" #overlay>
<a-menu class="!py-0 rounded text-sm"> <a-menu class="!py-0 rounded text-sm">
<template v-if="contextMenuTarget.type === 'table'"> <template v-if="contextMenuTarget.type === 'base'">
<!--
<a-menu-item @click="openErdView(contextMenuTarget.value)">
<div class="nc-project-menu-item">ERD View</div>
</a-menu-item>
-->
</template>
<template v-else-if="contextMenuTarget.type === 'table'">
<a-menu-item <a-menu-item
v-if="isUIAllowed('table-rename')" v-if="isUIAllowed('table-rename')"
@click="openRenameTableDialog(contextMenuTarget.value, bases[0].id, true)" @click="openRenameTableDialog(contextMenuTarget.value, bases[0].id, true)"
@ -1211,24 +1347,29 @@ const duplicateTable = async (table: TableType) => {
v-if="!isMobileMode" v-if="!isMobileMode"
class="color-transition px-2 text-gray-500 cursor-pointer select-none hover:text-accent" class="color-transition px-2 text-gray-500 cursor-pointer select-none hover:text-accent"
/> />
<!--
todo: enable it back later
disable at the moment to avoid issue with navigation
<GithubButton <GithubButton
v-if="!isMobileMode" v-if="!isMobileMode"
class="ml-2 py-1" class="ml-2 py-1"
href="https://github.com/nocodb/nocodb" href="https://github.com/nocodb/nocodb"
data-icon="octicon-star" data-icon="octicon-star"
data-show-count="true" data-show-count="true"
data-size="large" data-size="large"
> v-if="$route.name"
Star >
</GithubButton> Star
</GithubButton>
-->
</div> </div>
</div> </div>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
.nc-treeview-container { .nc-treeview-container {
@apply h-[calc(100vh_-_var(--header-height))]; // @apply h-[calc(100vh_-_var(--sidebar-top-height))];
border-right: 1px solid var(--navbar-border) !important; border-right: 1px solid var(--navbar-border) !important;
} }

262
packages/nc-gui/components/dashboard/TreeViewNew/AddNewTableNode.vue

@ -0,0 +1,262 @@
<script lang="ts" setup>
import type { ProjectType } from 'nocodb-sdk'
import { storeToRefs } from 'pinia'
import { toRef } from '@vue/reactivity'
import { resolveComponent } from '@vue/runtime-core'
import { ref } from 'vue'
import { ProjectRoleInj, useDialog, useUIPermission } from '#imports'
const props = withDefaults(
defineProps<{
project: ProjectType
baseIndex?: number
}>(),
{
baseIndex: 0,
},
)
const emit = defineEmits<{
openTableCreateDialog: () => void
}>()
const { isUIAllowed } = useUIPermission()
const project = toRef(props, 'project')
const { $e } = useNuxtApp()
const projectStore = useProject()
const { isSharedBase } = storeToRefs(projectStore)
const projectRole = inject(ProjectRoleInj)
function openSchemaMagicDialog(baseId?: string) {
if (!baseId) return
$e('c:table:create:navdraw')
const isOpen = ref(true)
const { close } = useDialog(resolveComponent('DlgSchemaMagic'), {
'modelValue': isOpen,
'baseId': baseId,
'onUpdate:modelValue': closeDialog,
})
function closeDialog() {
isOpen.value = false
close(1000)
}
}
function openQuickImportDialog(type: string, baseId?: string) {
if (!baseId) return
$e(`a:actions:import-${type}`)
const isOpen = ref(true)
const { close } = useDialog(resolveComponent('DlgQuickImport'), {
'modelValue': isOpen,
'importType': type,
'baseId': baseId,
'onUpdate:modelValue': closeDialog,
})
function closeDialog() {
isOpen.value = false
close(1000)
}
}
function openAirtableImportDialog(baseId?: string) {
if (!baseId) return
$e('a:actions:import-airtable')
const isOpen = ref(true)
const { close } = useDialog(resolveComponent('DlgAirtableImport'), {
'modelValue': isOpen,
'baseId': baseId,
'onUpdate:modelValue': closeDialog,
})
function closeDialog() {
isOpen.value = false
close(1000)
}
}
function openTableCreateMagicDialog(baseId?: string) {
if (!baseId) return
$e('c:table:create:navdraw')
const isOpen = ref(true)
const { close } = useDialog(resolveComponent('DlgTableMagic'), {
'modelValue': isOpen,
'baseId': baseId,
'onUpdate:modelValue': closeDialog,
})
function closeDialog() {
isOpen.value = false
close(1000)
}
}
</script>
<template>
<div
v-if="isUIAllowed('table-create', false, projectRole)"
class="group flex items-center gap-2 pl-2 pr-4.75 py-1 text-primary/70 hover:(text-primary/100) cursor-pointer select-none"
@click="emit('openTableCreateDialog')"
>
<PhPlusThin class="w-5 ml-2" />
<span class="text-gray-500 group-hover:(text-primary/100) flex-1 nc-add-new-table">{{ $t('tooltip.addTable') }}</span>
<a-dropdown v-if="!isSharedBase" :trigger="['click']" overlay-class-name="nc-dropdown-import-menu" @click.stop>
<GeneralIcon
icon="threeDotVertical"
class="transition-opacity opacity-0 group-hover:opacity-100 nc-import-menu outline-0"
/>
<template #overlay>
<a-menu class="!py-0 rounded text-sm">
<a-menu-item-group class="!px-0 !mx-0">
<template #title>
<div class="flex items-center">
Noco
<GeneralIcon icon="magic" class="ml-1 text-orange-400" />
</div>
</template>
<a-menu-item key="table-magic" @click="openTableCreateMagicDialog(project.bases[baseIndex].id)">
<div class="color-transition nc-project-menu-item group">
<GeneralIcon icon="magic1" class="group-hover:text-accent" />
Create table
</div>
</a-menu-item>
<a-menu-item key="schema-magic" @click="openSchemaMagicDialog(project.bases[baseIndex].id)">
<div class="color-transition nc-project-menu-item group">
<GeneralIcon icon="magic1" class="group-hover:text-accent" />
Create schema
</div>
</a-menu-item>
</a-menu-item-group>
<a-menu-divider class="my-0" />
<!-- Quick Import From -->
<a-menu-item-group :title="$t('title.quickImportFrom')" class="!px-0 !mx-0">
<a-menu-item
v-if="isUIAllowed('airtableImport', false, projectRole)"
key="quick-import-airtable"
@click="openAirtableImportDialog(project.bases[baseIndex].id)"
>
<div class="color-transition nc-project-menu-item group">
<GeneralIcon icon="airtable" class="group-hover:text-accent" />
Airtable
</div>
</a-menu-item>
<a-menu-item
v-if="isUIAllowed('csvImport', false, projectRole)"
key="quick-import-csv"
@click="openQuickImportDialog('csv', project.bases[baseIndex].id)"
>
<div class="color-transition nc-project-menu-item group">
<GeneralIcon icon="csv" class="group-hover:text-accent" />
CSV file
</div>
</a-menu-item>
<a-menu-item
v-if="isUIAllowed('jsonImport', false, projectRole)"
key="quick-import-json"
@click="openQuickImportDialog('json', project.bases[baseIndex].id)"
>
<div class="color-transition nc-project-menu-item group">
<GeneralIcon icon="json" class="group-hover:text-accent" />
JSON file
</div>
</a-menu-item>
<a-menu-item
v-if="isUIAllowed('excelImport', false, projectRole)"
key="quick-import-excel"
@click="openQuickImportDialog('excel', project.bases[baseIndex].id)"
>
<div class="color-transition nc-project-menu-item group">
<GeneralIcon icon="excel" class="group-hover:text-accent" />
Microsoft Excel
</div>
</a-menu-item>
</a-menu-item-group>
<a-menu-divider class="my-0" />
<!-- <a-menu-item-group title="Connect to new datasource" class="!px-0 !mx-0">
<a-menu-item key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.MYSQL, project.id)">
<div class="color-transition nc-project-menu-item group">
<LogosMysqlIcon class="group-hover:text-accent" />
MySQL
</div>
</a-menu-item>
<a-menu-item key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.PG, project.id)">
<div class="color-transition nc-project-menu-item group">
<LogosPostgresql class="group-hover:text-accent" />
Postgres
</div>
</a-menu-item>
<a-menu-item key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.SQLITE, project.id)">
<div class="color-transition nc-project-menu-item group">
<VscodeIconsFileTypeSqlite class="group-hover:text-accent" />
SQLite
</div>
</a-menu-item>
<a-menu-item key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.MSSQL, project.id)">
<div class="color-transition nc-project-menu-item group">
<SimpleIconsMicrosoftsqlserver class="group-hover:text-accent" />
MSSQL
</div>
</a-menu-item>
<a-menu-item
v-if="appInfo.ee"
key="connect-new-source"
@click="toggleDialog(true, 'dataSources', ClientType.SNOWFLAKE, project.id)"
>
<div class="color-transition nc-project-menu-item group">
<LogosSnowflakeIcon class="group-hover:text-accent" />
Snowflake
</div>
</a-menu-item>
</a-menu-item-group>
<a-menu-divider class="my-0" /> -->
<a-menu-item v-if="isUIAllowed('importRequest', false, projectRole)" key="add-new-table" class="py-1 rounded-b">
<a
v-e="['e:datasource:import-request']"
href="https://github.com/nocodb/nocodb/issues/2052"
target="_blank"
class="prose-sm hover:(!text-primary !opacity-100) color-transition nc-project-menu-item group after:(!rounded-b)"
>
<GeneralIcon icon="openInNew" class="group-hover:text-accent" />
<!-- Request a data source you need? -->
{{ $t('labels.requestDataSource') }}
</a>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
</template>

179
packages/nc-gui/components/dashboard/TreeViewNew/BaseOptions.vue

@ -0,0 +1,179 @@
<script lang="ts" setup>
import type { BaseType, ProjectType } from 'nocodb-sdk'
const props = defineProps<{
base: BaseType
project: ProjectType
}>()
const base = toRef(props, 'base')
const { isUIAllowed } = useUIPermission()
const projectRole = inject(ProjectRoleInj)
const { $e } = useNuxtApp()
function openAirtableImportDialog(baseId?: string) {
if (!baseId) return
$e('a:actions:import-airtable')
const isOpen = ref(true)
const { close } = useDialog(resolveComponent('DlgAirtableImport'), {
'modelValue': isOpen,
'baseId': baseId,
'onUpdate:modelValue': closeDialog,
})
function closeDialog() {
isOpen.value = false
close(1000)
}
}
function openQuickImportDialog(type: string) {
if (!base.value?.id) return
$e(`a:actions:import-${type}`)
const isOpen = ref(true)
const { close } = useDialog(resolveComponent('DlgQuickImport'), {
'modelValue': isOpen,
'importType': type,
'baseId': base.value.id,
'onUpdate:modelValue': closeDialog,
})
function closeDialog() {
isOpen.value = false
close(1000)
}
}
</script>
<template>
<a-menu-divider class="my-0" />
<!-- Quick Import From -->
<a-sub-menu class="py-0">
<template #title>
<div class="nc-project-menu-item group">
<GeneralIcon icon="download" class="-ml-0.25" />
<div class="-ml-0.5">
{{ $t('title.quickImportFrom') }}
</div>
<MaterialSymbolsChevronRightRounded class="transform group-hover:(scale-115 text-accent) text-xl text-gray-400" />
</div>
</template>
<template #expandIcon></template>
<a-menu-item
v-if="isUIAllowed('airtableImport', false, projectRole)"
key="quick-import-airtable"
@click="openAirtableImportDialog(base.id)"
>
<div class="color-transition nc-project-menu-item group">
<GeneralIcon icon="airtable" class="group-hover:text-black" />
Airtable
</div>
</a-menu-item>
<a-menu-item v-if="isUIAllowed('csvImport', false, projectRole)" key="quick-import-csv" @click="openQuickImportDialog('csv')">
<div class="color-transition nc-project-menu-item group">
<GeneralIcon icon="csv" class="group-hover:text-black" />
CSV file
</div>
</a-menu-item>
<a-menu-item
v-if="isUIAllowed('jsonImport', false, projectRole)"
key="quick-import-json"
@click="openQuickImportDialog('json')"
>
<div class="color-transition nc-project-menu-item group">
<GeneralIcon icon="code" class="group-hover:text-black" />
JSON file
</div>
</a-menu-item>
<a-menu-item
v-if="isUIAllowed('excelImport', false, projectRole)"
key="quick-import-excel"
@click="openQuickImportDialog('excel')"
>
<div class="color-transition nc-project-menu-item group">
<GeneralIcon icon="excel" class="group-hover:text-black" />
Microsoft Excel
</div>
</a-menu-item>
</a-sub-menu>
<a-menu-divider v-if="false" class="my-0" />
<!-- Connect to new datasource -->
<!-- <a-sub-menu>
<template #title>
<div class="nc-project-menu-item group">
<GeneralIcon icon="datasource" class="group-hover:text-black" />
Connect to new datasource
<div class="flex-1" />
<MaterialSymbolsChevronRightRounded class="transform group-hover:(scale-115 text-accent) text-xl text-gray-400" />
</div>
</template>
<template #expandIcon></template>
<a-menu-item key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.MYSQL, project.id)">
<div class="color-transition nc-project-menu-item group">
<LogosMysqlIcon class="group-hover:text-black" />
MySQL
</div>
</a-menu-item>
<a-menu-item key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.PG, project.id)">
<div class="color-transition nc-project-menu-item group">
<LogosPostgresql class="group-hover:text-black" />
Postgres
</div>
</a-menu-item>
<a-menu-item key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.SQLITE, project.id)">
<div class="color-transition nc-project-menu-item group">
<VscodeIconsFileTypeSqlite class="group-hover:text-black" />
SQLite
</div>
</a-menu-item>
<a-menu-item key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.MSSQL, project.id)">
<div class="color-transition nc-project-menu-item group">
<SimpleIconsMicrosoftsqlserver class="group-hover:text-black" />
MSSQL
</div>
</a-menu-item>
<a-menu-item
v-if="appInfo.ee"
key="connect-new-source"
@click="toggleDialog(true, 'dataSources', ClientType.SNOWFLAKE, project.id)"
>
<div class="color-transition nc-project-menu-item group">
<LogosSnowflakeIcon class="group-hover:text-black" />
Snowflake
</div>
</a-menu-item>
<a-menu-item v-if="isUIAllowed('importRequest', false, projectRole)" key="add-new-table" class="py-1 rounded-b">
<a
v-e="['e:datasource:import-request']"
href="https://github.com/nocodb/nocodb/issues/2052"
target="_blank"
class="prose-sm hover:(!text-primary !opacity-100) color-transition nc-project-menu-item group after:(!rounded-b)"
>
<GeneralIcon icon="openInNew" class="group-hover:text-black" />
{{ $t('labels.requestDataSource') }}
</a>
</a-menu-item>
</a-sub-menu> -->
</template>

742
packages/nc-gui/components/dashboard/TreeViewNew/ProjectNode.vue

@ -0,0 +1,742 @@
<script lang="ts" setup>
import { nextTick } from '@vue/runtime-core'
import { message } from 'ant-design-vue'
import type { BaseType, ProjectType, TableType } from 'nocodb-sdk'
import { LoadingOutlined } from '@ant-design/icons-vue'
import { useTitle } from '@vueuse/core'
import {
NcProjectType,
ProjectInj,
ProjectRoleInj,
ToggleDialogInj,
extractSdkResponseErrorMsg,
isElementInvisible,
openLink,
storeToRefs,
useProjects,
} from '#imports'
import type { NcProject } from '#imports'
import { useNuxtApp } from '#app'
const indicator = h(LoadingOutlined, {
class: '!text-gray-400',
style: {
fontSize: '0.85rem',
},
spin: true,
})
const router = useRouter()
const route = router.currentRoute
const { setMenuContext, openRenameTableDialog, duplicateTable, contextMenuTarget } = inject(TreeViewInj)!
const project = inject(ProjectInj)!
const projectsStore = useProjects()
const { loadProject, loadProjects, createProject: _createProject, updateProject, getProjectMetaInfo } = projectsStore
const { projects } = storeToRefs(projectsStore)
const { loadProjectTables } = useTablesStore()
const { activeTable } = storeToRefs(useTablesStore())
const { appInfo, navigateToProject } = useGlobal()
useTabs()
const editMode = ref(false)
const tempTitle = ref('')
const { t } = useI18n()
const input = ref<HTMLInputElement>()
const { isUIAllowed } = useUIPermission()
const projectRole = inject(ProjectRoleInj)
const { activeProjectId } = storeToRefs(useProjects())
const { projectUrl } = useProject()
const toggleDialog = inject(ToggleDialogInj, () => {})
const { $e } = useNuxtApp()
const isOptionsOpen = ref(false)
const isBasesOptionsOpen = ref<Record<string, boolean>>({})
const activeKey = ref<string[]>([])
const [searchActive] = useToggle()
const filterQuery = ref('')
const keys = ref<Record<string, number>>({})
const isTableDeleteDialogVisible = ref(false)
const isProjectDeleteDialogVisible = ref(false)
// If only project is open, i.e in case of docs, project view is open and not the page view
const projectViewOpen = computed(() => {
const routeNameSplit = String(route.value?.name).split('projectId-index-index')
if (routeNameSplit.length <= 1) return false
const routeNameAfterProjectView = routeNameSplit[routeNameSplit.length - 1]
return routeNameAfterProjectView.split('-').length === 2 || routeNameAfterProjectView.split('-').length === 1
})
const enableEditMode = () => {
editMode.value = true
tempTitle.value = project.value.title!
nextTick(() => {
input.value?.focus()
input.value?.select()
input.value?.scrollIntoView()
})
}
const updateProjectTitle = async () => {
if (!tempTitle.value) return
try {
await updateProject(project.value.id!, {
title: tempTitle.value,
})
editMode.value = false
tempTitle.value = ''
$e('a:project:rename')
useTitle(`${project.value?.title}`)
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
const { copy } = useCopy(true)
const copyProjectInfo = async () => {
try {
if (
await copy(
Object.entries(await getProjectMetaInfo(project.value.id!)!)
.map(([k, v]) => `${k}: **${v}**`)
.join('\n'),
)
) {
// Copied to clipboard
message.info(t('msg.info.copiedToClipboard'))
}
} catch (e: any) {
console.error(e)
message.error(e.message)
}
}
defineExpose({
enableEditMode,
})
const setIcon = async (icon: string, project: ProjectType) => {
try {
const meta = {
...((project.meta as object) || {}),
icon,
}
projectsStore.updateProject(project.id!, { meta: JSON.stringify(meta) })
$e('a:project:icon:navdraw', { icon })
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
function openTableCreateDialog(baseIndex?: number | undefined) {
$e('c:table:create:navdraw')
const isOpen = ref(true)
let baseId = project.value!.bases?.[0].id
if (typeof baseIndex === 'number') {
baseId = project.value!.bases?.[baseIndex].id
}
if (!baseId || !project.value?.id) return
const { close } = useDialog(resolveComponent('DlgTableCreate'), {
'modelValue': isOpen,
baseId, // || bases.value[0].id,
'projectId': project.value!.id,
'onCreate': closeDialog,
'onUpdate:modelValue': () => closeDialog(),
})
function closeDialog(table?: TableType) {
isOpen.value = false
if (!table) return
if (!activeKey.value || !activeKey.value.includes(`collapse-${baseId}`)) {
activeKey.value.push(`collapse-${baseId}`)
}
// TODO: Better way to know when the table node dom is available
setTimeout(() => {
const newTableDom = document.querySelector(`[data-table-id="${table.id}"]`)
if (!newTableDom) return
// Verify that table node is not in the viewport
if (isElementInvisible(newTableDom)) {
// Scroll to the table node
newTableDom?.scrollIntoView({ behavior: 'smooth' })
}
}, 1000)
close(1000)
}
}
const isAddNewProjectChildEntityLoading = ref(false)
const addNewProjectChildEntity = async () => {
if (isAddNewProjectChildEntityLoading.value) return
isAddNewProjectChildEntityLoading.value = true
try {
openTableCreateDialog()
if (!project.value.isExpanded) {
project.value.isExpanded = true
}
} finally {
isAddNewProjectChildEntityLoading.value = false
}
}
// todo: temp
const isSharedBase = ref(false)
const onProjectClick = async (project: NcProject, ignoreNavigation?: boolean, toggleIsExpanded?: boolean) => {
if (!project) {
return
}
if (toggleIsExpanded) {
project.isExpanded = !project.isExpanded
} else {
project.isExpanded = true
}
const isProjectPopulated = projectsStore.isProjectPopulated(project.id!)
let isSharedBase = false
// if shared base ignore navigation
if (route.value.params.typeOrId === 'base') {
isSharedBase = true
}
if (!isProjectPopulated) project.isLoading = true
if (!ignoreNavigation) {
await navigateTo(
projectUrl({
id: project.id!,
type: 'database',
isSharedBase,
}),
)
}
if (!isProjectPopulated) {
await loadProject(project.id!)
await loadProjectTables(project.id!)
}
if (!isProjectPopulated) {
const updatedProject = projects.value.get(project.id!)!
updatedProject.isLoading = false
}
}
function openErdView(base: BaseType) {
navigateTo(`/nc/${base.project_id}/erd/${base.id}`)
}
async function openProjectErdView(_project: ProjectType) {
if (!_project.id) return
if (!projectsStore.isProjectPopulated(_project.id)) {
await loadProject(_project.id)
}
const project = projects.value.get(_project.id)
const base = project?.bases?.[0]
if (!base) return
navigateTo(`/nc/${base.project_id}/erd/${base.id}`)
}
const reloadTables = async () => {
$e('a:table:refresh:navdraw')
// await loadTables()
}
const contextMenuBase = computed(() => {
if (contextMenuTarget.type === 'base') {
return contextMenuTarget.value
} else if (contextMenuTarget.type === 'table') {
const base = project.value?.bases?.find((b) => b.id === contextMenuTarget.value.base_id)
if (base) return base
}
return null
})
watch(
() => activeTable.value?.id,
async () => {
if (!activeTable.value) return
const baseId = activeTable.value.base_id
if (!baseId) return
if (!activeKey.value.includes(`collapse-${baseId}`)) {
activeKey.value.push(`collapse-${baseId}`)
}
},
{
immediate: true,
},
)
onKeyStroke('Escape', () => {
if (isOptionsOpen.value) {
isOptionsOpen.value = false
}
for (const key of Object.keys(isBasesOptionsOpen.value)) {
isBasesOptionsOpen.value[key] = false
}
})
const isDuplicateDlgOpen = ref(false)
const selectedProjectToDuplicate = ref()
const duplicateProject = (project: ProjectType) => {
selectedProjectToDuplicate.value = project
isDuplicateDlgOpen.value = true
}
const { $jobs } = useNuxtApp()
const DlgProjectDuplicateOnOk = async (jobData: { id: string; project_id: string }) => {
await loadProjects('workspace')
$jobs.subscribe({ id: jobData.id }, undefined, async (status: string) => {
if (status === JobStatus.COMPLETED) {
await loadProjects('workspace')
const project = projects.value.get(jobData.project_id)
// open project after duplication
if (project) {
await navigateToProject({
projectId: project.id,
type: project.type,
})
}
} else if (status === JobStatus.FAILED) {
message.error('Failed to duplicate project')
await loadProjects('workspace')
}
})
$e('a:project:duplicate')
}
</script>
<template>
<a-dropdown :trigger="['contextmenu']" overlay-class-name="nc-dropdown-tree-view-context-menu">
<div
class="mx-1 nc-project-sub-menu rounded-md"
:class="{ active: project.isExpanded }"
:data-testid="`nc-sidebar-project-${project.title}`"
:data-project-id="project.id"
>
<div class="flex items-center gap-0.75 py-0.25 cursor-pointer" @contextmenu="setMenuContext('project', project)">
<div
ref="projectNodeRefs"
:class="{
'bg-primary-selected active': activeProjectId === project.id && projectViewOpen,
'hover:bg-gray-200': !(activeProjectId === project.id && projectViewOpen),
}"
:data-testid="`nc-sidebar-project-title-${project.title}`"
class="project-title-node h-7.25 flex-grow rounded-md group flex items-center w-full"
>
<div
class="nc-sidebar-expand ml-0.75 min-h-5.75 min-w-5.75 px-1.5 text-gray-500 hover:(hover:bg-gray-500 hover:bg-opacity-15 !text-black) rounded-md relative"
@click="onProjectClick(project, true, true)"
>
<PhTriangleFill
class="absolute top-2.25 left-2 invisible group-hover:visible cursor-pointer transform transition-transform duration-500 h-1.5 w-1.75 rotate-90"
:class="{ '!rotate-180': project.isExpanded, '!visible': isOptionsOpen }"
/>
</div>
<div class="flex items-center mr-1" @click="onProjectClick(project)">
<div class="flex items-center select-none w-6 h-full">
<a-spin
v-if="project.isLoading"
class="nc-sidebar-icon !flex !flex-row !items-center !my-0.5 !mx-1.5 w-8"
:indicator="indicator"
/>
<LazyGeneralEmojiPicker
:key="project.meta?.icon"
:emoji="project.meta?.icon"
:readonly="true"
size="small"
@emoji-selected="setIcon($event, project)"
>
<template #default>
<GeneralProjectIcon :type="project.type" />
</template>
</LazyGeneralEmojiPicker>
</div>
</div>
<input
v-if="editMode"
ref="input"
v-model="tempTitle"
class="flex-grow leading-1 outline-0 ring-none capitalize !text-inherit !bg-transparent w-4/5"
:class="{ 'text-black font-semibold': activeProjectId === project.id && projectViewOpen }"
@click.stop
@keyup.enter="updateProjectTitle"
@keyup.esc="updateProjectTitle"
@blur="updateProjectTitle"
/>
<span
v-else
class="capitalize text-ellipsis overflow-hidden select-none"
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }"
:class="{ 'text-black font-semibold': activeProjectId === project.id && projectViewOpen }"
@click="onProjectClick(project)"
>
{{ project.title }}
</span>
<div :class="{ 'flex flex-grow h-full': !editMode }" @click="onProjectClick(project)"></div>
<a-dropdown v-if="isUIAllowed('tableCreate', false, projectRole)" v-model:visible="isOptionsOpen" trigger="click">
<MdiDotsHorizontal
class="min-w-5.75 min-h-5.75 px-0.5 py-0.5 mr-0.25 !ring-0 focus:!ring-0 !focus:border-0 !focus:outline-0 opacity-0 group-hover:(opacity-100) hover:text-black text-gray-600 rounded-md hover:(bg-gray-500 bg-opacity-15)"
:class="{ '!text-black !opacity-100': isOptionsOpen }"
data-testid="nc-sidebar-context-menu"
@click.stop
/>
<template #overlay>
<a-menu
class="nc-scrollbar-md"
:style="{
maxHeight: '70vh',
overflow: 'overlay',
}"
@click="isOptionsOpen = false"
>
<template v-if="!isSharedBase">
<a-menu-item @click="enableEditMode">
<div class="nc-project-menu-item group">
<GeneralIcon icon="edit" class="group-hover:text-black" />
{{ $t('general.edit') }}
</div>
</a-menu-item>
<!-- Copy Project Info -->
<a-menu-item v-if="!isEeUI" key="copy">
<div v-e="['c:navbar:user:copy-proj-info']" class="nc-project-menu-item group" @click.stop="copyProjectInfo">
<GeneralIcon icon="copy" class="group-hover:text-black" />
{{ $t('activity.account.projInfo') }}
</div>
</a-menu-item>
<a-menu-item
v-if="isUIAllowed('duplicateProject', true, projectRole)"
@click="duplicateProject(project)"
>
<div class="nc-menu-item-wrapper">
<GeneralIcon icon="duplicate" class="text-gray-700" />
{{ $t('general.duplicate') }} {{ $t('objects.project') }}
</div>
</a-menu-item>
<a-menu-divider v-if="false" />
<!-- ERD View -->
<a-menu-item key="erd" @click="openProjectErdView(project)">
<div class="nc-project-menu-item group">
<GeneralIcon icon="erd" />
Relations
</div>
</a-menu-item>
<!-- Swagger: Rest APIs -->
<a-menu-item key="api">
<div
v-if="isUIAllowed('apiDocs')"
v-e="['e:api-docs']"
class="nc-project-menu-item group"
@click.stop="openLink(`/api/v1/db/meta/projects/${project.id}/swagger`, appInfo.ncSiteUrl)"
>
<GeneralIcon icon="snippet" class="group-hover:text-black" />
{{ $t('activity.account.swagger') }}
</div>
</a-menu-item>
</template>
<!-- Team & Settings -->
<a-menu-item key="teamAndSettings">
<div
v-if="isUIAllowed('settings')"
v-e="['c:navdraw:project-settings']"
class="nc-project-menu-item group"
@click="toggleDialog(true, 'teamAndAuth', undefined, project.id)"
>
<GeneralIcon icon="settings" class="group-hover:text-black" />
{{ $t('activity.settings') }}
</div>
</a-menu-item>
<template v-if="project.bases && project.bases[0]">
<DashboardTreeViewNewBaseOptions v-model:project="project" :base="project.bases[0]" />
<a-menu-divider />
</template>
<a-menu-divider v-if="false" />
<a-menu-item v-if="isUIAllowed('projectDelete', false, projectRole)" @click="isProjectDeleteDialogVisible = true">
<div class="nc-project-menu-item group text-red-500">
<GeneralIcon icon="delete" />
{{ $t('general.delete') }}
</div>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<div
v-if="isUIAllowed('tableCreate', false, projectRole)"
class="min-h-5.75 min-w-5.75 mr-1 flex flex-row items-center justify-center gap-x-2 cursor-pointer hover:(text-black) text-gray-600 text-sm invisible !group-hover:visible rounded-md hover:(bg-gray-500 bg-opacity-15)"
data-testid="nc-sidebar-add-project-entity"
:class="{ '!text-black !visible': isAddNewProjectChildEntityLoading, '!visible': isOptionsOpen }"
@click.stop="addNewProjectChildEntity"
>
<div v-if="isAddNewProjectChildEntityLoading" class="flex flex-row items-center">
<a-spin class="!flex !flex-row !items-center !my-0.5" :indicator="indicator" />
</div>
<MdiPlus v-else class="min-w-5 min-h-5 py-0.25" />
</div>
</div>
</div>
<div
v-if="project.id && !project.isLoading"
key="g1"
class="overflow-x-hidden transition-max-height"
:class="{ 'max-h-0': !project.isExpanded }"
>
<template v-if="project && project?.bases">
<div class="flex-1 overflow-y-auto overflow-x-hidden flex flex-col" :class="{ 'mb-[20px]': isSharedBase }">
<div v-if="project?.bases?.[0]?.enabled" class="flex-1">
<div class="transition-height duration-200">
<DashboardTreeViewNewTableList :project="project" :base-index="0" />
</div>
</div>
<div v-if="project?.bases?.slice(1).filter((el) => el.enabled)?.length" class="transition-height duration-200">
<div class="border-none sortable-list">
<div v-for="(base, baseIndex) of project.bases" :key="`base-${base.id}`">
<template v-if="baseIndex === 0"></template>
<a-collapse
v-else-if="base && base.enabled"
v-model:activeKey="activeKey"
class="!mx-0 !px-0 nc-sidebar-base-node"
:class="[{ hidden: searchActive && !!filterQuery }]"
expand-icon-position="left"
:bordered="false"
ghost
>
<template #expandIcon="{ isActive }">
<div class="flex flex-row items-center -mt-2">
<PhTriangleFill
class="nc-sidebar-base-node-btns -mt-0.75 invisible cursor-pointer transform transition-transform duration-500 h-1.5 w-1.5 text-gray-500 rotate-90"
:class="{ '!rotate-180': isActive }"
/>
</div>
</template>
<a-collapse-panel :key="`collapse-${base.id}`">
<template #header>
<div class="min-w-20 w-full flex flex-row">
<div
v-if="baseIndex === 0"
class="base-context flex items-center gap-2 text-gray-800"
@contextmenu="setMenuContext('base', base)"
>
<GeneralBaseLogo :base-type="base.type" />
Default
</div>
<div
v-else
class="base-context flex flex-grow items-center gap-1.75 text-gray-800 min-w-1/20 max-w-full"
@contextmenu="setMenuContext('base', base)"
>
<GeneralBaseLogo :base-type="base.type" class="min-w-4" />
<div
:data-testid="`nc-sidebar-project-${base.alias}`"
class="flex capitalize text-ellipsis overflow-hidden select-none"
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }"
>
{{ base.alias || '' }}
</div>
<a-tooltip>
<template #title>External DB</template>
<div>
<GeneralIcon icon="info" class="text-gray-400 -mt-0.5 hover:text-gray-700 mr-1" />
</div>
</a-tooltip>
</div>
<div
v-if="isUIAllowed('tableCreate', false, projectRole)"
class="flex flex-row items-center gap-x-0.25 w-12.25"
>
<a-dropdown
:visible="isBasesOptionsOpen[base!.id!]"
trigger="click"
@update:visible="isBasesOptionsOpen[base!.id!] = $event"
>
<MdiDotsHorizontal
class="min-w-6 min-h-6 mt-0.15 invisible nc-sidebar-base-node-btns !ring-0 focus:!ring-0 !focus:border-0 !focus:outline-0 hover:text-black py-0.25 px-0.5 rounded-md text-gray-600 hover:(bg-gray-400 bg-opacity-20)"
:class="{ '!text-black !opacity-100': isBasesOptionsOpen[base!.id!] }"
@click.stop="isBasesOptionsOpen[base!.id!] = !isBasesOptionsOpen[base!.id!]"
/>
<template #overlay>
<a-menu
class="nc-scrollbar-md"
:style="{
maxHeight: '70vh',
overflow: 'overlay',
}"
@click="isBasesOptionsOpen[base!.id!] = false"
>
<!-- ERD View -->
<a-menu-item key="erd" @click="openErdView(base)">
<div class="nc-project-menu-item group">
<GeneralIcon icon="erd" />
Relations
</div>
</a-menu-item>
<DashboardTreeViewNewBaseOptions v-model:project="project" :base="base" />
</a-menu>
</template>
</a-dropdown>
<div
v-if="isUIAllowed('tableCreate', false, projectRole)"
class="flex invisible nc-sidebar-base-node-btns !focus:outline-0 text-gray-600 hover:text-black px-0.35 rounded-md hover:(bg-gray-500 bg-opacity-15) min-h-6 mt-0.15 min-w-6"
@click.stop="openTableCreateDialog(baseIndex)"
>
<component :is="iconMap.plus" class="text-inherit mt-0.25 h-5.5 w-5.5 py-0.5 !focus:outline-0" />
</div>
</div>
</div>
</template>
<!-- <AddNewTableNode
:project="project"
:base-index="baseIndex"
@open-table-create-dialog="openTableCreateDialog()"
/> -->
<div
ref="menuRefs"
:key="`sortable-${base.id}-${base.id && base.id in keys ? keys[base.id] : '0'}`"
:nc-base="base.id"
>
<DashboardTreeViewNewTableList :project="project" :base-index="baseIndex" />
</div>
</a-collapse-panel>
</a-collapse>
</div>
</div>
</div>
</div>
</template>
</div>
</div>
<template v-if="!isSharedBase" #overlay>
<a-menu class="!py-0 rounded text-sm">
<template v-if="contextMenuTarget.type === 'project' && project.type === 'database'"></template>
<template v-else-if="contextMenuTarget.type === 'base'"></template>
<template v-else-if="contextMenuTarget.type === 'table'">
<a-menu-item v-if="isUIAllowed('table-rename')" @click="openRenameTableDialog(contextMenuTarget.value, true)">
<div class="nc-project-menu-item">
<GeneralIcon icon="edit" class="text-gray-700" />
{{ $t('general.rename') }}
</div>
</a-menu-item>
<a-menu-item
v-if="isUIAllowed('table-duplicate') && (contextMenuBase?.is_meta || contextMenuBase?.is_local)"
@click="duplicateTable(contextMenuTarget.value)"
>
<div class="nc-project-menu-item">
<GeneralIcon icon="duplicate" class="text-gray-700" />
{{ $t('general.duplicate') }}
</div>
</a-menu-item>
<a-menu-item v-if="isUIAllowed('table-delete')" @click="isTableDeleteDialogVisible = true">
<div class="nc-project-menu-item text-red-600">
<GeneralIcon icon="delete" />
{{ $t('general.delete') }}
</div>
</a-menu-item>
</template>
<template v-else>
<a-menu-item @click="reloadTables">
<div class="nc-project-menu-item">
{{ $t('general.reload') }}
</div>
</a-menu-item>
</template>
</a-menu>
</template>
</a-dropdown>
<DlgTableDelete
v-if="contextMenuTarget.value?.id && project?.id"
v-model:visible="isTableDeleteDialogVisible"
:table-id="contextMenuTarget.value?.id"
:project-id="project?.id"
/>
<DlgProjectDelete v-model:visible="isProjectDeleteDialogVisible" :project-id="project?.id" />
<DlgProjectDuplicate
v-if="selectedProjectToDuplicate"
v-model="isDuplicateDlgOpen"
:project="selectedProjectToDuplicate"
:on-ok="DlgProjectDuplicateOnOk"
/>
</template>
<style lang="scss" scoped>
.nc-sidebar-icon {
@apply ml-0.5 mr-1;
}
:deep(.ant-collapse-header) {
@apply !mx-0 !pl-8.75 !pr-1 !py-0.75 hover:bg-gray-100 !rounded-md;
}
:deep(.ant-collapse-header:hover .nc-sidebar-base-node-btns) {
@apply visible;
}
:deep(.ant-dropdown-menu-submenu-title) {
@apply !py-0;
}
</style>

19
packages/nc-gui/components/dashboard/TreeViewNew/ProjectWrapper.vue

@ -0,0 +1,19 @@
<script lang="ts" setup>
import type { ProjectType } from 'nocodb-sdk'
import { ProjectInj, ProjectRoleInj } from '#imports'
const props = defineProps<{
projectRole: string | string[]
project: ProjectType
}>()
const projectRole = toRef(props, 'projectRole')
const project = toRef(props, 'project')
provide(ProjectRoleInj, projectRole)
provide(ProjectInj, project)
</script>
<template>
<slot />
</template>

172
packages/nc-gui/components/dashboard/TreeViewNew/TableList.vue

@ -0,0 +1,172 @@
<script setup lang="ts">
import type { ProjectType, TableType } from 'nocodb-sdk'
import { storeToRefs } from 'pinia'
import Sortable from 'sortablejs'
import TableNode from './TableNode.vue'
import { useNuxtApp } from '#app'
import { toRef } from '#imports'
const props = withDefaults(
defineProps<{
project: ProjectType
baseIndex?: number
}>(),
{
baseIndex: 0,
},
)
const project = toRef(props, 'project')
const baseIndex = toRef(props, 'baseIndex')
const base = computed(() => project.value?.bases?.[baseIndex.value])
const { projectTables } = storeToRefs(useTablesStore())
const tables = computed(() => projectTables.value.get(project.value.id!) ?? [])
const { $api } = useNuxtApp()
const { openTable } = useTableNew({
projectId: project.value.id!,
})
const tablesById = computed(() =>
tables.value.reduce<Record<string, TableType>>((acc, table) => {
acc[table.id!] = table
return acc
}, {}),
)
const keys = ref<Record<string, number>>({})
const menuRefs = ref<HTMLElement[] | HTMLElement>()
const sortables: Record<string, Sortable> = {}
// todo: replace with vuedraggable
const initSortable = (el: Element) => {
const base_id = el.getAttribute('nc-base')
if (!base_id) return
if (sortables[base_id]) sortables[base_id].destroy()
Sortable.create(el as HTMLLIElement, {
onEnd: async (evt) => {
const offset = tables.value.findIndex((table) => table.base_id === base_id)
const { newIndex = 0, oldIndex = 0 } = evt
if (newIndex === oldIndex) return
const itemEl = evt.item as HTMLLIElement
const item = tablesById.value[itemEl.dataset.id as string]
// get the html collection of all list items
const children: HTMLCollection = evt.to.children
// skip if children count is 1
if (children.length < 2) return
// get items before and after the moved item
const itemBeforeEl = children[newIndex - 1] as HTMLLIElement
const itemAfterEl = children[newIndex + 1] as HTMLLIElement
// get items meta of before and after the moved item
const itemBefore = itemBeforeEl && tablesById.value[itemBeforeEl.dataset.id as string]
const itemAfter = itemAfterEl && tablesById.value[itemAfterEl.dataset.id as string]
// set new order value based on the new order of the items
if (children.length - 1 === evt.newIndex) {
item.order = (itemBefore.order as number) + 1
} else if (newIndex === 0) {
item.order = (itemAfter.order as number) / 2
} else {
item.order = ((itemBefore.order as number) + (itemAfter.order as number)) / 2
}
// update the order of the moved item
tables.value?.splice(newIndex + offset, 0, ...tables.value?.splice(oldIndex + offset, 1))
// force re-render the list
if (keys.value[base_id]) {
keys.value[base_id] = keys.value[base_id] + 1
} else {
keys.value[base_id] = 1
}
// update the item order
await $api.dbTable.reorder(item.id as string, {
order: item.order,
})
},
animation: 150,
setData(dataTransfer, dragEl) {
dataTransfer.setData(
'text/json',
JSON.stringify({
id: dragEl.dataset.id,
title: dragEl.dataset.title,
type: dragEl.dataset.type,
baseId: dragEl.dataset.baseId,
}),
)
},
revertOnSpill: true,
})
}
watchEffect(() => {
if (menuRefs.value) {
if (menuRefs.value instanceof HTMLElement) {
initSortable(menuRefs.value)
} else {
menuRefs.value.forEach((el) => initSortable(el))
}
}
})
const availableTables = computed(() => {
return tables.value.filter((table) => table.base_id === project.value?.bases?.[baseIndex.value].id)
})
</script>
<template>
<div class="border-none sortable-list">
<template v-if="project">
<div
v-if="availableTables.length === 0"
class="py-0.5 text-gray-500"
:class="{
'ml-13.55': baseIndex === 0,
'ml-19.25': baseIndex !== 0,
}"
>
Empty
</div>
<div
v-if="project.bases?.[baseIndex] && project!.bases[baseIndex].enabled"
ref="menuRefs"
:key="`sortable-${base?.id}-${base?.id && base?.id in keys ? keys[base?.id] : '0'}`"
:nc-base="base?.id"
>
<TableNode
v-for="table of availableTables"
:key="table.id"
v-e="['a:table:open']"
class="nc-tree-item text-sm cursor-pointer group"
:data-order="table.order"
:data-id="table.id"
:data-testid="`tree-view-table-${table.title}`"
:table="table"
:project="project"
:base-index="baseIndex"
:data-title="table.title"
:data-base-id="base?.id"
:data-type="table.type"
@click="openTable(table)"
>
</TableNode>
</div>
</template>
</div>
</template>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save