Merge pull request #234 from drawdb-io/share

Implement file sharing using gists
This commit is contained in:
1ilit 2024-09-01 21:52:06 +04:00 committed by GitHub
commit 7b9d9c21ec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 678 additions and 22 deletions

View File

@ -1 +1,2 @@
VITE_BACKEND_URL=http://backend.com
VITE_BACKEND_URL=http://backend.com
VITE_GITHUB_ACCESS_TOKEN=my_access_token

366
package-lock.json generated
View File

@ -29,6 +29,7 @@
"jspdf": "^2.5.1",
"lexical": "^0.12.5",
"node-sql-parser": "^5.3.1",
"octokit": "^4.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hotkeys-hook": "^4.4.1",
@ -1519,6 +1520,326 @@
"node": ">= 8"
}
},
"node_modules/@octokit/app": {
"version": "15.1.0",
"resolved": "https://registry.npmjs.org/@octokit/app/-/app-15.1.0.tgz",
"integrity": "sha512-TkBr7QgOmE6ORxvIAhDbZsqPkF7RSqTY4pLTtUQCvr6dTXqvi2fFo46q3h1lxlk/sGMQjqyZ0kEahkD/NyzOHg==",
"dependencies": {
"@octokit/auth-app": "^7.0.0",
"@octokit/auth-unauthenticated": "^6.0.0",
"@octokit/core": "^6.1.2",
"@octokit/oauth-app": "^7.0.0",
"@octokit/plugin-paginate-rest": "^11.0.0",
"@octokit/types": "^13.0.0",
"@octokit/webhooks": "^13.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-app": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-7.1.0.tgz",
"integrity": "sha512-cazGaJPSgeZ8NkVYeM/C5l/6IQ5vZnsI8p1aMucadCkt/bndI+q+VqwrlnWbASRmenjOkf1t1RpCKrif53U8gw==",
"dependencies": {
"@octokit/auth-oauth-app": "^8.1.0",
"@octokit/auth-oauth-user": "^5.1.0",
"@octokit/request": "^9.1.1",
"@octokit/request-error": "^6.1.1",
"@octokit/types": "^13.4.1",
"lru-cache": "^10.0.0",
"universal-github-app-jwt": "^2.2.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-app/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="
},
"node_modules/@octokit/auth-oauth-app": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-8.1.1.tgz",
"integrity": "sha512-5UtmxXAvU2wfcHIPPDWzVSAWXVJzG3NWsxb7zCFplCWEmMCArSZV0UQu5jw5goLQXbFyOr5onzEH37UJB3zQQg==",
"dependencies": {
"@octokit/auth-oauth-device": "^7.0.0",
"@octokit/auth-oauth-user": "^5.0.1",
"@octokit/request": "^9.0.0",
"@octokit/types": "^13.0.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-oauth-device": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-7.1.1.tgz",
"integrity": "sha512-HWl8lYueHonuyjrKKIup/1tiy0xcmQCdq5ikvMO1YwkNNkxb6DXfrPjrMYItNLyCP/o2H87WuijuE+SlBTT8eg==",
"dependencies": {
"@octokit/oauth-methods": "^5.0.0",
"@octokit/request": "^9.0.0",
"@octokit/types": "^13.0.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-oauth-user": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-5.1.1.tgz",
"integrity": "sha512-rRkMz0ErOppdvEfnemHJXgZ9vTPhBuC6yASeFaB7I2yLMd7QpjfrL1mnvRPlyKo+M6eeLxrKanXJ9Qte29SRsw==",
"dependencies": {
"@octokit/auth-oauth-device": "^7.0.1",
"@octokit/oauth-methods": "^5.0.0",
"@octokit/request": "^9.0.1",
"@octokit/types": "^13.0.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-token": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.1.tgz",
"integrity": "sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==",
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-unauthenticated": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-6.1.0.tgz",
"integrity": "sha512-zPSmfrUAcspZH/lOFQnVnvjQZsIvmfApQH6GzJrkIunDooU1Su2qt2FfMTSVPRp7WLTQyC20Kd55lF+mIYaohQ==",
"dependencies": {
"@octokit/request-error": "^6.0.1",
"@octokit/types": "^13.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/core": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.2.tgz",
"integrity": "sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==",
"dependencies": {
"@octokit/auth-token": "^5.0.0",
"@octokit/graphql": "^8.0.0",
"@octokit/request": "^9.0.0",
"@octokit/request-error": "^6.0.1",
"@octokit/types": "^13.0.0",
"before-after-hook": "^3.0.2",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/endpoint": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
"dependencies": {
"@octokit/types": "^13.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/graphql": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.1.1.tgz",
"integrity": "sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==",
"dependencies": {
"@octokit/request": "^9.0.0",
"@octokit/types": "^13.0.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/oauth-app": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-7.1.3.tgz",
"integrity": "sha512-EHXbOpBkSGVVGF1W+NLMmsnSsJRkcrnVmDKt0TQYRBb6xWfWzoi9sBD4DIqZ8jGhOWO/V8t4fqFyJ4vDQDn9bg==",
"dependencies": {
"@octokit/auth-oauth-app": "^8.0.0",
"@octokit/auth-oauth-user": "^5.0.1",
"@octokit/auth-unauthenticated": "^6.0.0-beta.1",
"@octokit/core": "^6.0.0",
"@octokit/oauth-authorization-url": "^7.0.0",
"@octokit/oauth-methods": "^5.0.0",
"@types/aws-lambda": "^8.10.83",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/oauth-authorization-url": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-7.1.1.tgz",
"integrity": "sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA==",
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/oauth-methods": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-5.1.2.tgz",
"integrity": "sha512-C5lglRD+sBlbrhCUTxgJAFjWgJlmTx5bQ7Ch0+2uqRjYv7Cfb5xpX4WuSC9UgQna3sqRGBL9EImX9PvTpMaQ7g==",
"dependencies": {
"@octokit/oauth-authorization-url": "^7.0.0",
"@octokit/request": "^9.1.0",
"@octokit/request-error": "^6.1.0",
"@octokit/types": "^13.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/openapi-types": {
"version": "22.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
"integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg=="
},
"node_modules/@octokit/openapi-webhooks-types": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-8.3.0.tgz",
"integrity": "sha512-vKLsoR4xQxg4Z+6rU/F65ItTUz/EXbD+j/d4mlq2GW8TsA4Tc8Kdma2JTAAJ5hrKWUQzkR/Esn2fjsqiVRYaQg=="
},
"node_modules/@octokit/plugin-paginate-graphql": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-5.2.2.tgz",
"integrity": "sha512-7znSVvlNAOJisCqAnjN1FtEziweOHSjPGAuc5W58NeGNAr/ZB57yCsjQbXDlWsVryA7hHQaEQPcBbJYFawlkyg==",
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": ">=6"
}
},
"node_modules/@octokit/plugin-paginate-rest": {
"version": "11.3.3",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.3.tgz",
"integrity": "sha512-o4WRoOJZlKqEEgj+i9CpcmnByvtzoUYC6I8PD2SA95M+BJ2x8h7oLcVOg9qcowWXBOdcTRsMZiwvM3EyLm9AfA==",
"dependencies": {
"@octokit/types": "^13.5.0"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": ">=6"
}
},
"node_modules/@octokit/plugin-rest-endpoint-methods": {
"version": "13.2.4",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.2.4.tgz",
"integrity": "sha512-gusyAVgTrPiuXOdfqOySMDztQHv6928PQ3E4dqVGEtOvRXAKRbJR4b1zQyniIT9waqaWk/UDaoJ2dyPr7Bk7Iw==",
"dependencies": {
"@octokit/types": "^13.5.0"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": ">=6"
}
},
"node_modules/@octokit/plugin-retry": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-7.1.1.tgz",
"integrity": "sha512-G9Ue+x2odcb8E1XIPhaFBnTTIrrUDfXN05iFXiqhR+SeeeDMMILcAnysOsxUpEWcQp2e5Ft397FCXTcPkiPkLw==",
"dependencies": {
"@octokit/request-error": "^6.0.0",
"@octokit/types": "^13.0.0",
"bottleneck": "^2.15.3"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": ">=6"
}
},
"node_modules/@octokit/plugin-throttling": {
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-9.3.1.tgz",
"integrity": "sha512-Qd91H4liUBhwLB2h6jZ99bsxoQdhgPk6TdwnClPyTBSDAdviGPceViEgUwj+pcQDmB/rfAXAXK7MTochpHM3yQ==",
"dependencies": {
"@octokit/types": "^13.0.0",
"bottleneck": "^2.15.3"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": "^6.0.0"
}
},
"node_modules/@octokit/request": {
"version": "9.1.3",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.3.tgz",
"integrity": "sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==",
"dependencies": {
"@octokit/endpoint": "^10.0.0",
"@octokit/request-error": "^6.0.1",
"@octokit/types": "^13.1.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/request-error": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.4.tgz",
"integrity": "sha512-VpAhIUxwhWZQImo/dWAN/NpPqqojR6PSLgLYAituLM6U+ddx9hCioFGwBr5Mi+oi5CLeJkcAs3gJ0PYYzU6wUg==",
"dependencies": {
"@octokit/types": "^13.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/types": {
"version": "13.5.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz",
"integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==",
"dependencies": {
"@octokit/openapi-types": "^22.2.0"
}
},
"node_modules/@octokit/webhooks": {
"version": "13.3.0",
"resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-13.3.0.tgz",
"integrity": "sha512-TUkJLtI163Bz5+JK0O+zDkQpn4gKwN+BovclUvCj6pI/6RXrFqQvUMRS2M+Rt8Rv0qR3wjoMoOPmpJKeOh0nBg==",
"dependencies": {
"@octokit/openapi-webhooks-types": "8.3.0",
"@octokit/request-error": "^6.0.1",
"@octokit/webhooks-methods": "^5.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/webhooks-methods": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-5.1.0.tgz",
"integrity": "sha512-yFZa3UH11VIxYnnoOYCVoJ3q4ChuSOk2IVBBQ0O3xtKX4x9bmKb/1t+Mxixv2iUhzMdOl1qeWJqEhouXXzB3rQ==",
"engines": {
"node": ">= 18"
}
},
"node_modules/@remix-run/router": {
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.0.tgz",
@ -1743,6 +2064,11 @@
"tslib": "^2.4.0"
}
},
"node_modules/@types/aws-lambda": {
"version": "8.10.143",
"resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.143.tgz",
"integrity": "sha512-u5vzlcR14ge/4pMTTMDQr3MF0wEe38B2F9o84uC4F43vN5DGTy63npRrB6jQhyt+C0lGv4ZfiRcRkqJoZuPnmg=="
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@ -2260,6 +2586,11 @@
"node": ">= 0.6.0"
}
},
"node_modules/before-after-hook": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz",
"integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A=="
},
"node_modules/bezier-easing": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz",
@ -2282,6 +2613,11 @@
"node": ">=8"
}
},
"node_modules/bottleneck": {
"version": "2.19.5",
"resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz",
"integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -4639,6 +4975,26 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/octokit": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/octokit/-/octokit-4.0.2.tgz",
"integrity": "sha512-wbqF4uc1YbcldtiBFfkSnquHtECEIpYD78YUXI6ri1Im5OO2NLo6ZVpRdbJpdnpZ05zMrVPssNiEo6JQtea+Qg==",
"dependencies": {
"@octokit/app": "^15.0.0",
"@octokit/core": "^6.0.0",
"@octokit/oauth-app": "^7.0.0",
"@octokit/plugin-paginate-graphql": "^5.0.0",
"@octokit/plugin-paginate-rest": "^11.0.0",
"@octokit/plugin-rest-endpoint-methods": "^13.0.0",
"@octokit/plugin-retry": "^7.0.0",
"@octokit/plugin-throttling": "^9.0.0",
"@octokit/request-error": "^6.0.0",
"@octokit/types": "^13.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@ -5952,6 +6308,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/universal-github-app-jwt": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-2.2.0.tgz",
"integrity": "sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ=="
},
"node_modules/universal-user-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
},
"node_modules/update-browserslist-db": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",

View File

@ -31,6 +31,7 @@
"jspdf": "^2.5.1",
"lexical": "^0.12.5",
"node-sql-parser": "^5.3.1",
"octokit": "^4.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hotkeys-hook": "^4.4.1",

View File

@ -1,4 +1,4 @@
import { useState } from "react";
import { useContext, useState } from "react";
import {
IconCaretdown,
IconChevronRight,
@ -9,6 +9,7 @@ import {
IconUndo,
IconRedo,
IconEdit,
IconShareStroked,
} from "@douyinfe/semi-icons";
import { Link, useNavigate } from "react-router-dom";
import icon from "../../assets/icon_dark_64.png";
@ -71,6 +72,7 @@ import { databases } from "../../data/databases";
import { jsonToMermaid } from "../../utils/exportAs/mermaid";
import { isRtl } from "../../i18n/utils/rtl";
import { jsonToDocumentation } from "../../utils/exportAs/documentation";
import { IdContext } from "../Workspace";
export default function ControlPanel({
diagramId,
@ -113,6 +115,7 @@ export default function ControlPanel({
const { selectedElement, setSelectedElement } = useSelect();
const { transform, setTransform } = useTransform();
const { t, i18n } = useTranslation();
const { setGistId } = useContext(IdContext);
const navigate = useNavigate();
const invertLayout = (component) =>
@ -782,6 +785,7 @@ export default function ControlPanel({
setEnums([]);
setUndoStack([]);
setRedoStack([]);
setGistId("");
})
.catch(() => Toast.error(t("oops_smth_went_wrong")));
},
@ -1080,7 +1084,7 @@ export default function ControlPanel({
data: result,
extension: "md",
}));
}
},
},
],
function: () => {},
@ -1376,8 +1380,25 @@ export default function ControlPanel({
return (
<>
{layout.header && header()}
{layout.toolbar && toolbar()}
<div>
{layout.header && (
<div className="flex justify-between items-center me-7">
{header()}
{window.name.split(" ")[0] !== "t" && (
<Button
type="primary"
className="text-base me-2 pe-6 ps-5 py-[18px] rounded-md"
size="default"
icon={<IconShareStroked />}
onClick={() => setModal(MODAL.SHARE)}
>
{t("share")}
</Button>
)}
</div>
)}
{layout.toolbar && toolbar()}
</div>
<Modal
modal={modal}
exportData={exportData}
@ -1583,6 +1604,8 @@ export default function ControlPanel({
return t("saving");
case State.ERROR:
return t("failed_to_save");
case State.FAILED_TO_LOAD:
return t("failed_to_load");
default:
return "";
}
@ -1600,7 +1623,7 @@ export default function ControlPanel({
width={54}
src={icon}
alt="logo"
className="ms-8 min-w-[54px]"
className="ms-7 min-w-[54px]"
/>
</Link>
<div className="ms-1 mt-1">

View File

@ -33,6 +33,7 @@ import ImportDiagram from "./ImportDiagram";
import ImportSource from "./ImportSource";
import SetTableWidth from "./SetTableWidth";
import Language from "./Language";
import Share from "./Share";
import CodeMirror from "@uiw/react-codemirror";
import { sql } from "@codemirror/lang-sql";
import { vscodeDark } from "@uiw/codemirror-theme-vscode";
@ -319,7 +320,7 @@ export default function Modal({
);
} else {
return (
<div className="text-center my-3">
<div className="text-center my-3 text-sky-600">
<Spin tip={t("loading")} size="large" />
</div>
);
@ -328,6 +329,8 @@ export default function Modal({
return <SetTableWidth />;
case MODAL.LANGUAGE:
return <Language />;
case MODAL.SHARE:
return <Share title={title} />;
default:
return <></>;
}
@ -371,7 +374,9 @@ export default function Modal({
((modal === MODAL.IMG || modal === MODAL.CODE) && !exportData.data) ||
(modal === MODAL.SAVEAS && saveAsTitle === "") ||
(modal === MODAL.IMPORT_SRC && importSource.src === ""),
hidden: modal === MODAL.SHARE,
}}
hasCancel={modal !== MODAL.SHARE}
cancelText={t("cancel")}
width={getModalWidth(modal)}
bodyStyle={{

View File

@ -0,0 +1,152 @@
import { Button, Input, Spin, Toast } from "@douyinfe/semi-ui";
import { useCallback, useContext, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { IdContext } from "../../Workspace";
import { IconLink } from "@douyinfe/semi-icons";
import {
useAreas,
useDiagram,
useEnums,
useNotes,
useTransform,
useTypes,
} from "../../../hooks";
import { databases } from "../../../data/databases";
import { octokit } from "../../../data/octokit";
export default function Share({ title }) {
const { t } = useTranslation();
const { gistId, setGistId } = useContext(IdContext);
const [loading, setLoading] = useState(true);
const { tables, relationships, database } = useDiagram();
const { notes } = useNotes();
const { areas } = useAreas();
const { types } = useTypes();
const { enums } = useEnums();
const { transform } = useTransform();
const url =
window.location.origin + window.location.pathname + "?shareId=" + gistId;
const diagramToString = useCallback(() => {
return JSON.stringify({
tables: tables,
relationships: relationships,
notes: notes,
subjectAreas: areas,
database: database,
...(databases[database].hasTypes && { types: types }),
...(databases[database].hasEnums && { enums: enums }),
title: title,
transform: transform,
});
}, [
areas,
notes,
tables,
relationships,
database,
title,
enums,
types,
transform,
]);
const updateGist = useCallback(async () => {
setLoading(true);
try {
await octokit.request(`PATCH /gists/${gistId}`, {
gist_id: gistId,
description: "drawDB diagram",
files: {
"share.json": {
content: diagramToString(),
},
},
headers: {
"X-GitHub-Api-Version": "2022-11-28",
},
});
} catch (e) {
console.error(e);
} finally {
setLoading(false);
}
}, [gistId, diagramToString]);
const generateLink = useCallback(async () => {
setLoading(true);
try {
const res = await octokit.request("POST /gists", {
description: "drawDB diagram",
public: false,
files: {
"share.json": {
content: diagramToString(),
},
},
headers: {
"X-GitHub-Api-Version": "2022-11-28",
},
});
setGistId(res.data.id);
} catch (e) {
console.error(e);
} finally {
setLoading(false);
}
}, [setGistId, diagramToString]);
useEffect(() => {
const updateOrGenerateLink = async () => {
try {
if (!gistId || gistId === "") {
await generateLink();
} else {
await updateGist();
}
} catch (e) {
console.error(e);
} finally {
setLoading(false);
}
};
updateOrGenerateLink();
}, [gistId, generateLink, updateGist]);
const copyLink = () => {
navigator.clipboard
.writeText(url)
.then(() => {
Toast.success(t("copied_to_clipboard"));
})
.catch(() => {
Toast.error(t("oops_smth_went_wrong"));
});
};
if (loading)
return (
<div className="text-blue-500 text-center">
<Spin size="middle" />
<div>{t("loading")}</div>
</div>
);
return (
<div>
<div className="flex gap-3">
<Input value={url} size="large" />
<Button
size="large"
theme="solid"
icon={<IconLink />}
onClick={copyLink}
>
{t("copy_link")}
</Button>
</div>
<hr className="opacity-20 mt-3 mb-1" />
<div className="text-xs">{t("share_info")}</div>
</div>
);
}

View File

@ -1,4 +1,4 @@
import { useState, useEffect, useCallback } from "react";
import { useState, useEffect, useCallback, createContext } from "react";
import ControlPanel from "./EditorHeader/ControlPanel";
import Canvas from "./EditorCanvas/Canvas";
import { CanvasContextProvider } from "../context/CanvasContext";
@ -23,9 +23,15 @@ import { Modal } from "@douyinfe/semi-ui";
import { useTranslation } from "react-i18next";
import { databases } from "../data/databases";
import { isRtl } from "../i18n/utils/rtl";
import { useSearchParams } from "react-router-dom";
import { octokit } from "../data/octokit";
export const IdContext = createContext({ gistId: "" });
export default function WorkSpace() {
const [id, setId] = useState(0);
const [gistId, setGistId] = useState("");
const [loadedFromGistId, setLoadedFromGistId] = useState("");
const [title, setTitle] = useState("Untitled Diagram");
const [resize, setResize] = useState(false);
const [width, setWidth] = useState(340);
@ -51,7 +57,7 @@ export default function WorkSpace() {
} = useDiagram();
const { undoStack, redoStack, setUndoStack, setRedoStack } = useUndoRedo();
const { t, i18n } = useTranslation();
let [searchParams, setSearchParams] = useSearchParams();
const handleResize = (e) => {
if (!resize) return;
const w = isRtl(i18n.language) ? window.innerWidth - e.clientX : e.clientX;
@ -64,6 +70,8 @@ export default function WorkSpace() {
const saveAsDiagram = window.name === "" || op === "d" || op === "lt";
if (saveAsDiagram) {
searchParams.delete("shareId");
setSearchParams(searchParams);
if (
(id === 0 && window.name === "") ||
window.name.split(" ")[0] === "lt"
@ -72,6 +80,7 @@ export default function WorkSpace() {
.add({
database: database,
name: title,
gistId: gistId ?? "",
lastModified: new Date(),
tables: tables,
references: relationships,
@ -80,6 +89,7 @@ export default function WorkSpace() {
todos: tasks,
pan: transform.pan,
zoom: transform.zoom,
loadedFromGistId: loadedFromGistId,
...(databases[database].hasEnums && { enums: enums }),
...(databases[database].hasTypes && { types: types }),
})
@ -100,8 +110,10 @@ export default function WorkSpace() {
notes: notes,
areas: areas,
todos: tasks,
gistId: gistId ?? "",
pan: transform.pan,
zoom: transform.zoom,
loadedFromGistId: loadedFromGistId,
...(databases[database].hasEnums && { enums: enums }),
...(databases[database].hasTypes && { types: types }),
})
@ -134,6 +146,8 @@ export default function WorkSpace() {
});
}
}, [
searchParams,
setSearchParams,
tables,
relationships,
notes,
@ -146,6 +160,8 @@ export default function WorkSpace() {
setSaveState,
database,
enums,
gistId,
loadedFromGistId,
]);
const load = useCallback(async () => {
@ -161,6 +177,8 @@ export default function WorkSpace() {
setDatabase(DB.GENERIC);
}
setId(d.id);
setGistId(d.gistId);
setLoadedFromGistId(d.loadedFromGistId);
setTitle(d.name);
setTables(d.tables);
setRelationships(d.references);
@ -196,6 +214,8 @@ export default function WorkSpace() {
setDatabase(DB.GENERIC);
}
setId(diagram.id);
setGistId(diagram.gistId);
setLoadedFromGistId(diagram.loadedFromGistId);
setTitle(diagram.name);
setTables(diagram.tables);
setRelationships(diagram.references);
@ -299,6 +319,61 @@ export default function WorkSpace() {
selectedDb,
]);
const loadFromGist = useCallback(
async (shareId) => {
const existingDiagram = await db.diagrams.get({
loadedFromGistId: shareId,
});
if (existingDiagram) {
window.name = "d " + existingDiagram.id;
} else {
window.name = "";
}
try {
const res = await octokit.request(`GET /gists/${shareId}`, {
gist_id: shareId,
headers: {
"X-GitHub-Api-Version": "2022-11-28",
},
});
const diagramSrc = res.data.files["share.json"].content;
const d = JSON.parse(diagramSrc);
setUndoStack([]);
setRedoStack([]);
setLoadedFromGistId(shareId);
setDatabase(d.database);
setTitle(d.title);
setTables(d.tables);
setRelationships(d.relationships);
setNotes(d.notes);
setAreas(d.subjectAreas);
setTransform(d.transform);
if (databases[d.database].hasTypes) {
setTypes(d.types ?? []);
}
if (databases[d.database].hasEnums) {
setEnums(d.enums ?? []);
}
} catch (e) {
console.log(e);
setSaveState(State.FAILED_TO_LOAD);
}
},
[
setAreas,
setDatabase,
setEnums,
setNotes,
setRelationships,
setTables,
setTypes,
setTransform,
setRedoStack,
setUndoStack,
setSaveState,
],
);
useEffect(() => {
if (
tables?.length === 0 &&
@ -327,28 +402,41 @@ export default function WorkSpace() {
setSaveState,
]);
useEffect(() => {
if (gistId && gistId !== "") {
setSaveState(State.SAVING);
}
}, [gistId, setSaveState]);
useEffect(() => {
if (saveState !== State.SAVING) return;
save();
}, [id, saveState, save]);
}, [id, gistId, saveState, save]);
useEffect(() => {
document.title = "Editor | drawDB";
load();
}, [load]);
const shareId = searchParams.get("shareId");
if (shareId) {
loadFromGist(shareId);
} else {
load();
}
}, [load, searchParams, loadFromGist]);
return (
<div className="h-full flex flex-col overflow-hidden theme">
<ControlPanel
diagramId={id}
setDiagramId={setId}
title={title}
setTitle={setTitle}
lastSaved={lastSaved}
setLastSaved={setLastSaved}
/>
<IdContext.Provider value={{ gistId, setGistId }}>
<ControlPanel
diagramId={id}
setDiagramId={setId}
title={title}
setTitle={setTitle}
lastSaved={lastSaved}
setLastSaved={setLastSaved}
/>
</IdContext.Provider>
<div
className="flex h-full overflow-y-auto"
onPointerUp={(e) => e.isPrimary && setResize(false)}

View File

@ -77,6 +77,7 @@ export const State = {
SAVED: 2,
LOADING: 3,
ERROR: 4,
FAILED_TO_LOAD: 5,
};
export const MODAL = {
@ -91,6 +92,7 @@ export const MODAL = {
IMPORT_SRC: 8,
TABLE_WIDTH: 9,
LANGUAGE: 10,
SHARE: 11,
};
export const STATUS = {

View File

@ -3,8 +3,8 @@ import { templateSeeds } from "./seeds";
export const db = new Dexie("drawDB");
db.version(5).stores({
diagrams: "++id, lastModified",
db.version(6).stores({
diagrams: "++id, lastModified, loadedFromGistId",
templates: "++id, custom",
});

5
src/data/octokit.js Normal file
View File

@ -0,0 +1,5 @@
import { Octokit } from "octokit";
export const octokit = new Octokit({
auth: import.meta.env.VITE_GITHUB_ACCESS_TOKEN,
});

View File

@ -236,7 +236,12 @@ const en = {
empty_index_name: "Declared an index with no name in table '{{tableName}}'",
didnt_find_diagram: "Oops! Didn't find the diagram.",
unsigned: "Unsigned",
share: "Share",
copy_link: "Copy link",
readme: "README",
failed_to_load: "Failed to load. Make sure the link is correct.",
share_info:
"* Sharing this link will not create a live real-time collaboration session.",
},
};

View File

@ -58,6 +58,10 @@
background-color: rgba(var(--semi-blue-6), 1);
}
.semi-spin-wrapper{
color: inherit;
}
::-webkit-scrollbar {
width: 8px;
height: 8px;

View File

@ -23,6 +23,8 @@ export const getModalTitle = (modal) => {
return i18n.t("table_width");
case MODAL.LANGUAGE:
return i18n.t("language");
case MODAL.SHARE:
return i18n.t("share");
default:
return "";
}
@ -55,6 +57,8 @@ export const getOkText = (modal) => {
return i18n.t("save_as");
case MODAL.NEW:
return i18n.t("create");
case MODAL.SHARE:
return i18n.t("share");
default:
return i18n.t("confirm");
}