diff --git a/.env.sample b/.env.sample index c87806f..153c5ae 100644 --- a/.env.sample +++ b/.env.sample @@ -1 +1,2 @@ -VITE_BACKEND_URL=http://backend.com \ No newline at end of file +VITE_BACKEND_URL=http://backend.com +VITE_GITHUB_ACCESS_TOKEN=my_access_token \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index bb91cbf..8c96638 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index eff0007..982a19e 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/EditorHeader/ControlPanel.jsx b/src/components/EditorHeader/ControlPanel.jsx index 64d1676..593e69d 100644 --- a/src/components/EditorHeader/ControlPanel.jsx +++ b/src/components/EditorHeader/ControlPanel.jsx @@ -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()} +
+ {layout.header && ( +
+ {header()} + {window.name.split(" ")[0] !== "t" && ( + + )} +
+ )} + {layout.toolbar && toolbar()} +
diff --git a/src/components/EditorHeader/Modal/Modal.jsx b/src/components/EditorHeader/Modal/Modal.jsx index d8acd8f..009b689 100644 --- a/src/components/EditorHeader/Modal/Modal.jsx +++ b/src/components/EditorHeader/Modal/Modal.jsx @@ -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 ( -
+
); @@ -328,6 +329,8 @@ export default function Modal({ return ; case MODAL.LANGUAGE: return ; + case MODAL.SHARE: + return ; 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={{ diff --git a/src/components/EditorHeader/Modal/Share.jsx b/src/components/EditorHeader/Modal/Share.jsx new file mode 100644 index 0000000..3731a3e --- /dev/null +++ b/src/components/EditorHeader/Modal/Share.jsx @@ -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 ( +
+ +
{t("loading")}
+
+ ); + + return ( +
+
+ + +
+
+
{t("share_info")}
+
+ ); +} diff --git a/src/components/Workspace.jsx b/src/components/Workspace.jsx index 4d8c803..ce9a4f6 100644 --- a/src/components/Workspace.jsx +++ b/src/components/Workspace.jsx @@ -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 (
- + + +
e.isPrimary && setResize(false)} diff --git a/src/data/constants.js b/src/data/constants.js index 6ffb577..e17b7d1 100644 --- a/src/data/constants.js +++ b/src/data/constants.js @@ -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 = { diff --git a/src/data/db.js b/src/data/db.js index 8355a5c..adddd91 100644 --- a/src/data/db.js +++ b/src/data/db.js @@ -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", }); diff --git a/src/data/octokit.js b/src/data/octokit.js new file mode 100644 index 0000000..d922073 --- /dev/null +++ b/src/data/octokit.js @@ -0,0 +1,5 @@ +import { Octokit } from "octokit"; + +export const octokit = new Octokit({ + auth: import.meta.env.VITE_GITHUB_ACCESS_TOKEN, +}); diff --git a/src/i18n/locales/en.js b/src/i18n/locales/en.js index 5ee193a..cebfac6 100644 --- a/src/i18n/locales/en.js +++ b/src/i18n/locales/en.js @@ -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.", }, }; diff --git a/src/index.css b/src/index.css index 11a352b..62a845d 100644 --- a/src/index.css +++ b/src/index.css @@ -58,6 +58,10 @@ background-color: rgba(var(--semi-blue-6), 1); } +.semi-spin-wrapper{ + color: inherit; +} + ::-webkit-scrollbar { width: 8px; height: 8px; diff --git a/src/utils/modalData.js b/src/utils/modalData.js index d72ba6e..f4fbf45 100644 --- a/src/utils/modalData.js +++ b/src/utils/modalData.js @@ -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"); }