mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
128 files changed
+1701
-1645
No files matched your search
+5
-5
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/svelte": "^1.1.24",
|
||||
"@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@8836b0c",
|
||||
"@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@935b30f",
|
||||
"@appwrite.io/pink-icons": "0.25.0",
|
||||
"@appwrite.io/pink-icons-svelte": "^2.0.0-RC.1",
|
||||
"@appwrite.io/pink-legacy": "^1.0.3",
|
||||
@@ -57,20 +57,20 @@
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/svelte": "^5.2.4",
|
||||
"@testing-library/svelte": "^5.2.8",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/deep-equal": "^1.0.4",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"@types/remarkable": "^2.0.8",
|
||||
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
||||
"@typescript-eslint/parser": "^8.28.0",
|
||||
"@vitest/ui": "^3.0.9",
|
||||
"@vitest/ui": "^3.2.4",
|
||||
"color": "^5.0.0",
|
||||
"eslint": "^9.31.0",
|
||||
"eslint-config-prettier": "^10.1.0",
|
||||
"eslint-plugin-svelte": "^3.3.3",
|
||||
"globals": "^16.0.0",
|
||||
"jsdom": "^26.0.0",
|
||||
"jsdom": "^26.1.0",
|
||||
"kleur": "^4.1.5",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
@@ -94,5 +94,5 @@
|
||||
"svelte-preprocess"
|
||||
]
|
||||
},
|
||||
"packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6"
|
||||
"packageManager": "pnpm@10.13.1"
|
||||
}
|
||||
Generated
+66
-89
@@ -12,8 +12,8 @@ importers:
|
||||
specifier: ^1.1.24
|
||||
version: 1.1.24(svelte@5.25.3)(zod@3.24.3)
|
||||
'@appwrite.io/console':
|
||||
specifier: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@8836b0c
|
||||
version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@8836b0c
|
||||
specifier: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@935b30f
|
||||
version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@935b30f
|
||||
'@appwrite.io/pink-icons':
|
||||
specifier: 0.25.0
|
||||
version: 0.25.0
|
||||
@@ -112,8 +112,8 @@ importers:
|
||||
specifier: ^6.6.3
|
||||
version: 6.6.3
|
||||
'@testing-library/svelte':
|
||||
specifier: ^5.2.4
|
||||
version: 5.2.7(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))(vitest@3.0.9)
|
||||
specifier: ^5.2.8
|
||||
version: 5.2.8(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))(vitest@3.0.9)
|
||||
'@testing-library/user-event':
|
||||
specifier: ^14.6.1
|
||||
version: 14.6.1(@testing-library/dom@10.4.0)
|
||||
@@ -133,8 +133,8 @@ importers:
|
||||
specifier: ^8.28.0
|
||||
version: 8.28.0(eslint@9.31.0)(typescript@5.8.2)
|
||||
'@vitest/ui':
|
||||
specifier: ^3.0.9
|
||||
version: 3.0.9(vitest@3.0.9)
|
||||
specifier: ^3.2.4
|
||||
version: 3.2.4(vitest@3.0.9)
|
||||
color:
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0
|
||||
@@ -151,8 +151,8 @@ importers:
|
||||
specifier: ^16.0.0
|
||||
version: 16.0.0
|
||||
jsdom:
|
||||
specifier: ^26.0.0
|
||||
version: 26.0.0
|
||||
specifier: ^26.1.0
|
||||
version: 26.1.0
|
||||
kleur:
|
||||
specifier: ^4.1.5
|
||||
version: 4.1.5
|
||||
@@ -194,7 +194,7 @@ importers:
|
||||
version: 6.2.3(@types/node@22.13.14)(sass@1.86.0)
|
||||
vitest:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.9(@types/node@22.13.14)(@vitest/ui@3.0.9)(jsdom@26.0.0)(sass@1.86.0)
|
||||
version: 3.0.9(@types/node@22.13.14)(@vitest/ui@3.2.4)(jsdom@26.1.0)(sass@1.86.0)
|
||||
|
||||
packages:
|
||||
|
||||
@@ -257,8 +257,8 @@ packages:
|
||||
'@analytics/type-utils@0.6.2':
|
||||
resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==}
|
||||
|
||||
'@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@8836b0c':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@8836b0c}
|
||||
'@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@935b30f':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@935b30f}
|
||||
version: 1.9.0
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@fee5c539136e15c93cd3f2398d25cb217caa8dc8':
|
||||
@@ -1282,8 +1282,8 @@ packages:
|
||||
resolution: {integrity: sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==}
|
||||
engines: {node: '>=14', npm: '>=6', yarn: '>=1'}
|
||||
|
||||
'@testing-library/svelte@5.2.7':
|
||||
resolution: {integrity: sha512-aGhUaFmEXEVost4QOsbHUUbHLwi7ZZRRxAHFDO2Cmr0BZD3/3+XvaYEPq70Rdw0NRNjdqZHdARBEcrCOkPuAqw==}
|
||||
'@testing-library/svelte@5.2.8':
|
||||
resolution: {integrity: sha512-ucQOtGsJhtawOEtUmbR4rRh53e6RbM1KUluJIXRmh6D4UzxR847iIqqjRtg9mHNFmGQ8Vkam9yVcR5d1mhIHKA==}
|
||||
engines: {node: '>= 10'}
|
||||
peerDependencies:
|
||||
svelte: ^3 || ^4 || ^5 || ^5.0.0-next.0
|
||||
@@ -1478,6 +1478,9 @@ packages:
|
||||
'@vitest/pretty-format@3.0.9':
|
||||
resolution: {integrity: sha512-OW9F8t2J3AwFEwENg3yMyKWweF7oRJlMyHOMIhO5F3n0+cgQAJZBjNgrF8dLwFTEXl5jUqBLXd9QyyKv8zEcmA==}
|
||||
|
||||
'@vitest/pretty-format@3.2.4':
|
||||
resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==}
|
||||
|
||||
'@vitest/runner@3.0.9':
|
||||
resolution: {integrity: sha512-NX9oUXgF9HPfJSwl8tUZCMP1oGx2+Sf+ru6d05QjzQz4OwWg0psEzwY6VexP2tTHWdOkhKHUIZH+fS6nA7jfOw==}
|
||||
|
||||
@@ -1487,14 +1490,17 @@ packages:
|
||||
'@vitest/spy@3.0.9':
|
||||
resolution: {integrity: sha512-/CcK2UDl0aQ2wtkp3YVWldrpLRNCfVcIOFGlVGKO4R5eajsH393Z1yiXLVQ7vWsj26JOEjeZI0x5sm5P4OGUNQ==}
|
||||
|
||||
'@vitest/ui@3.0.9':
|
||||
resolution: {integrity: sha512-FpZD4aIv/qNpwkV3XbLV6xldWFHMgoNWAJEgg5GmpObmAOLAErpYjew9dDwXdYdKOS3iZRKdwI+P3JOJcYeUBg==}
|
||||
'@vitest/ui@3.2.4':
|
||||
resolution: {integrity: sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==}
|
||||
peerDependencies:
|
||||
vitest: 3.0.9
|
||||
vitest: 3.2.4
|
||||
|
||||
'@vitest/utils@3.0.9':
|
||||
resolution: {integrity: sha512-ilHM5fHhZ89MCp5aAaM9uhfl1c2JdxVxl3McqsdVyVNN6JffnEen8UMCdRTzOhGXNQGo5GNL9QugHrz727Wnng==}
|
||||
|
||||
'@vitest/utils@3.2.4':
|
||||
resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==}
|
||||
|
||||
'@vue/compiler-core@3.5.13':
|
||||
resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==}
|
||||
|
||||
@@ -1625,9 +1631,6 @@ packages:
|
||||
resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
asynckit@0.4.0:
|
||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
|
||||
autolinker@3.16.2:
|
||||
resolution: {integrity: sha512-JiYl7j2Z19F9NdTmirENSUUIIL/9MytEWtmzhfmsKPCp9E+G35Y0UNCMoM9tFigxT59qSc8Ml2dlZXOCVTYwuA==}
|
||||
|
||||
@@ -1750,10 +1753,6 @@ packages:
|
||||
resolution: {integrity: sha512-16BlyiuyLq3MLxpRWyOTiWsO3ii/eLQLJUQXBSNcxMBBSnyt1ee9YUdaozQp03ifwm5woztEZGDbk9RGVuCsdw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
comma-separated-tokens@2.0.3:
|
||||
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
|
||||
|
||||
@@ -1970,10 +1969,6 @@ packages:
|
||||
delaunator@5.0.1:
|
||||
resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==}
|
||||
|
||||
delayed-stream@1.0.0:
|
||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
dequal@2.0.3:
|
||||
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -2037,10 +2032,6 @@ packages:
|
||||
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-set-tostringtag@2.1.0:
|
||||
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
esbuild@0.25.1:
|
||||
resolution: {integrity: sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -2183,6 +2174,14 @@ packages:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
fdir@6.4.6:
|
||||
resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==}
|
||||
peerDependencies:
|
||||
picomatch: ^3 || ^4
|
||||
peerDependenciesMeta:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
fflate@0.8.2:
|
||||
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
|
||||
|
||||
@@ -2212,10 +2211,6 @@ packages:
|
||||
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
form-data@4.0.2:
|
||||
resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
forwarded-parse@2.1.2:
|
||||
resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==}
|
||||
|
||||
@@ -2485,8 +2480,8 @@ packages:
|
||||
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
||||
hasBin: true
|
||||
|
||||
jsdom@26.0.0:
|
||||
resolution: {integrity: sha512-BZYDGVAIriBWTpIxYzrXjv3E/4u8+/pSG5bQdIYCbNCGOvsPkDQfTVLAIXAf9ETdCpduCVTkDe2NNZ8NIwUVzw==}
|
||||
jsdom@26.1.0:
|
||||
resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
canvas: ^3.0.0
|
||||
@@ -2554,6 +2549,9 @@ packages:
|
||||
loupe@3.1.3:
|
||||
resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==}
|
||||
|
||||
loupe@3.1.4:
|
||||
resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==}
|
||||
|
||||
lru-cache@10.4.3:
|
||||
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||
|
||||
@@ -2615,14 +2613,6 @@ packages:
|
||||
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
||||
engines: {node: '>=8.6'}
|
||||
|
||||
mime-db@1.52.0:
|
||||
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mime-types@2.1.35:
|
||||
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
min-indent@1.0.1:
|
||||
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -3256,8 +3246,8 @@ packages:
|
||||
tinyexec@0.3.2:
|
||||
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
|
||||
|
||||
tinyglobby@0.2.12:
|
||||
resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==}
|
||||
tinyglobby@0.2.14:
|
||||
resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
tinypool@1.0.2:
|
||||
@@ -3661,7 +3651,7 @@ snapshots:
|
||||
|
||||
'@analytics/type-utils@0.6.2': {}
|
||||
|
||||
'@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@8836b0c': {}
|
||||
'@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@935b30f': {}
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@fee5c539136e15c93cd3f2398d25cb217caa8dc8(svelte@5.25.3)':
|
||||
dependencies:
|
||||
@@ -4765,13 +4755,13 @@ snapshots:
|
||||
lodash: 4.17.21
|
||||
redent: 3.0.0
|
||||
|
||||
'@testing-library/svelte@5.2.7(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))(vitest@3.0.9)':
|
||||
'@testing-library/svelte@5.2.8(svelte@5.25.3)(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))(vitest@3.0.9)':
|
||||
dependencies:
|
||||
'@testing-library/dom': 10.4.0
|
||||
svelte: 5.25.3
|
||||
optionalDependencies:
|
||||
vite: 6.2.3(@types/node@22.13.14)(sass@1.86.0)
|
||||
vitest: 3.0.9(@types/node@22.13.14)(@vitest/ui@3.0.9)(jsdom@26.0.0)(sass@1.86.0)
|
||||
vitest: 3.0.9(@types/node@22.13.14)(@vitest/ui@3.2.4)(jsdom@26.1.0)(sass@1.86.0)
|
||||
|
||||
'@testing-library/user-event@14.6.1(@testing-library/dom@10.4.0)':
|
||||
dependencies:
|
||||
@@ -5015,6 +5005,10 @@ snapshots:
|
||||
dependencies:
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
'@vitest/pretty-format@3.2.4':
|
||||
dependencies:
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
'@vitest/runner@3.0.9':
|
||||
dependencies:
|
||||
'@vitest/utils': 3.0.9
|
||||
@@ -5030,16 +5024,16 @@ snapshots:
|
||||
dependencies:
|
||||
tinyspy: 3.0.2
|
||||
|
||||
'@vitest/ui@3.0.9(vitest@3.0.9)':
|
||||
'@vitest/ui@3.2.4(vitest@3.0.9)':
|
||||
dependencies:
|
||||
'@vitest/utils': 3.0.9
|
||||
'@vitest/utils': 3.2.4
|
||||
fflate: 0.8.2
|
||||
flatted: 3.3.3
|
||||
pathe: 2.0.3
|
||||
sirv: 3.0.1
|
||||
tinyglobby: 0.2.12
|
||||
tinyglobby: 0.2.14
|
||||
tinyrainbow: 2.0.0
|
||||
vitest: 3.0.9(@types/node@22.13.14)(@vitest/ui@3.0.9)(jsdom@26.0.0)(sass@1.86.0)
|
||||
vitest: 3.0.9(@types/node@22.13.14)(@vitest/ui@3.2.4)(jsdom@26.1.0)(sass@1.86.0)
|
||||
|
||||
'@vitest/utils@3.0.9':
|
||||
dependencies:
|
||||
@@ -5047,6 +5041,12 @@ snapshots:
|
||||
loupe: 3.1.3
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
'@vitest/utils@3.2.4':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 3.2.4
|
||||
loupe: 3.1.4
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
'@vue/compiler-core@3.5.13':
|
||||
dependencies:
|
||||
'@babel/parser': 7.27.0
|
||||
@@ -5198,8 +5198,6 @@ snapshots:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
asynckit@0.4.0: {}
|
||||
|
||||
autolinker@3.16.2:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
@@ -5332,10 +5330,6 @@ snapshots:
|
||||
color-convert: 3.1.0
|
||||
color-string: 2.0.1
|
||||
|
||||
combined-stream@1.0.8:
|
||||
dependencies:
|
||||
delayed-stream: 1.0.0
|
||||
|
||||
comma-separated-tokens@2.0.3: {}
|
||||
|
||||
commander@7.2.0: {}
|
||||
@@ -5580,8 +5574,6 @@ snapshots:
|
||||
dependencies:
|
||||
robust-predicates: 3.0.2
|
||||
|
||||
delayed-stream@1.0.0: {}
|
||||
|
||||
dequal@2.0.3: {}
|
||||
|
||||
detect-libc@1.0.3:
|
||||
@@ -5640,13 +5632,6 @@ snapshots:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
|
||||
es-set-tostringtag@2.1.0:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
get-intrinsic: 1.3.0
|
||||
has-tostringtag: 1.0.2
|
||||
hasown: 2.0.2
|
||||
|
||||
esbuild@0.25.1:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.25.1
|
||||
@@ -5828,6 +5813,10 @@ snapshots:
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.2
|
||||
|
||||
fdir@6.4.6(picomatch@4.0.2):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.2
|
||||
|
||||
fflate@0.8.2: {}
|
||||
|
||||
file-entry-cache@8.0.0:
|
||||
@@ -5858,13 +5847,6 @@ snapshots:
|
||||
dependencies:
|
||||
is-callable: 1.2.7
|
||||
|
||||
form-data@4.0.2:
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
es-set-tostringtag: 2.1.0
|
||||
mime-types: 2.1.35
|
||||
|
||||
forwarded-parse@2.1.2: {}
|
||||
|
||||
framesync@6.1.2:
|
||||
@@ -6132,12 +6114,11 @@ snapshots:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
|
||||
jsdom@26.0.0:
|
||||
jsdom@26.1.0:
|
||||
dependencies:
|
||||
cssstyle: 4.3.0
|
||||
data-urls: 5.0.0
|
||||
decimal.js: 10.5.0
|
||||
form-data: 4.0.2
|
||||
html-encoding-sniffer: 4.0.0
|
||||
http-proxy-agent: 7.0.2
|
||||
https-proxy-agent: 7.0.6
|
||||
@@ -6203,6 +6184,8 @@ snapshots:
|
||||
|
||||
loupe@3.1.3: {}
|
||||
|
||||
loupe@3.1.4: {}
|
||||
|
||||
lru-cache@10.4.3: {}
|
||||
|
||||
lru-cache@5.1.1:
|
||||
@@ -6271,12 +6254,6 @@ snapshots:
|
||||
braces: 3.0.3
|
||||
picomatch: 2.3.1
|
||||
|
||||
mime-db@1.52.0: {}
|
||||
|
||||
mime-types@2.1.35:
|
||||
dependencies:
|
||||
mime-db: 1.52.0
|
||||
|
||||
min-indent@1.0.1: {}
|
||||
|
||||
minimatch@3.1.2:
|
||||
@@ -6897,9 +6874,9 @@ snapshots:
|
||||
|
||||
tinyexec@0.3.2: {}
|
||||
|
||||
tinyglobby@0.2.12:
|
||||
tinyglobby@0.2.14:
|
||||
dependencies:
|
||||
fdir: 6.4.3(picomatch@4.0.2)
|
||||
fdir: 6.4.6(picomatch@4.0.2)
|
||||
picomatch: 4.0.2
|
||||
|
||||
tinypool@1.0.2: {}
|
||||
@@ -7063,7 +7040,7 @@ snapshots:
|
||||
optionalDependencies:
|
||||
vite: 6.2.3(@types/node@22.13.14)(sass@1.86.0)
|
||||
|
||||
vitest@3.0.9(@types/node@22.13.14)(@vitest/ui@3.0.9)(jsdom@26.0.0)(sass@1.86.0):
|
||||
vitest@3.0.9(@types/node@22.13.14)(@vitest/ui@3.2.4)(jsdom@26.1.0)(sass@1.86.0):
|
||||
dependencies:
|
||||
'@vitest/expect': 3.0.9
|
||||
'@vitest/mocker': 3.0.9(vite@6.2.3(@types/node@22.13.14)(sass@1.86.0))
|
||||
@@ -7087,8 +7064,8 @@ snapshots:
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/node': 22.13.14
|
||||
'@vitest/ui': 3.0.9(vitest@3.0.9)
|
||||
jsdom: 26.0.0
|
||||
'@vitest/ui': 3.2.4(vitest@3.0.9)
|
||||
jsdom: 26.1.0
|
||||
transitivePeerDependencies:
|
||||
- jiti
|
||||
- less
|
||||
|
||||
@@ -148,9 +148,9 @@ export enum Click {
|
||||
ConnectRepositoryClick = 'click_connect_repository',
|
||||
CreditsRedeemClick = 'click_credits_redeem',
|
||||
CloudSignupClick = 'click_cloud_signup',
|
||||
DatabaseAttributeDelete = 'click_attribute_delete',
|
||||
DatabaseColumnDelete = 'click_column_delete',
|
||||
DatabaseIndexDelete = 'click_index_delete',
|
||||
DatabaseCollectionDelete = 'click_collection_delete',
|
||||
DatabaseTableDelete = 'click_table_delete',
|
||||
DatabaseDatabaseDelete = 'click_database_delete',
|
||||
DatabaseImportCsv = 'click_database_import_csv',
|
||||
DomainCreateClick = 'click_domain_create',
|
||||
@@ -273,22 +273,22 @@ export enum Submit {
|
||||
DatabaseDelete = 'submit_database_delete',
|
||||
DatabaseUpdateName = 'submit_database_update_name',
|
||||
DatabaseImportCsv = 'submit_database_import_csv',
|
||||
AttributeCreate = 'submit_attribute_create',
|
||||
AttributeUpdate = 'submit_attribute_update',
|
||||
AttributeDelete = 'submit_attribute_delete',
|
||||
DocumentCreate = 'submit_document_create',
|
||||
DocumentDelete = 'submit_document_delete',
|
||||
DocumentUpdate = 'submit_document_update',
|
||||
DocumentUpdatePermissions = 'submit_document_update_permissions',
|
||||
ColumnCreate = 'submit_column_create',
|
||||
ColumnUpdate = 'submit_column_update',
|
||||
ColumnDelete = 'submit_column_delete',
|
||||
RowCreate = 'submit_row_create',
|
||||
RowDelete = 'submit_row_delete',
|
||||
RowUpdate = 'submit_row_update',
|
||||
RowUpdatePermissions = 'submit_row_update_permissions',
|
||||
IndexCreate = 'submit_index_create',
|
||||
IndexDelete = 'submit_index_delete',
|
||||
CollectionCreate = 'submit_collection_create',
|
||||
CollectionDelete = 'submit_collection_delete',
|
||||
CollectionUpdateName = 'submit_collection_update_name',
|
||||
CollectionUpdatePermissions = 'submit_collection_update_permissions',
|
||||
CollectionUpdateSecurity = 'submit_collection_update_security',
|
||||
CollectionUpdateEnabled = 'submit_collection_update_enabled',
|
||||
CollectionUpdateDisplayNames = 'submit_collection_update_display_names',
|
||||
TableCreate = 'submit_row_create',
|
||||
TableDelete = 'submit_row_delete',
|
||||
TableUpdateName = 'submit_row_update_name',
|
||||
TableUpdatePermissions = 'submit_row_update_permissions',
|
||||
TableUpdateSecurity = 'submit_row_update_security',
|
||||
TableUpdateEnabled = 'submit_row_update_enabled',
|
||||
TableUpdateDisplayNames = 'submit_row_update_display_names',
|
||||
FunctionCreate = 'submit_function_create',
|
||||
FunctionDelete = 'submit_function_delete',
|
||||
FunctionUpdateName = 'submit_function_update_name',
|
||||
|
||||
@@ -28,10 +28,10 @@ const groups = [
|
||||
'integrations',
|
||||
'migrations',
|
||||
'users',
|
||||
'collections',
|
||||
'attributes',
|
||||
'tables',
|
||||
'columns',
|
||||
'indexes',
|
||||
'documents',
|
||||
'rows',
|
||||
'teams',
|
||||
'security',
|
||||
'buckets',
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
const examples = [
|
||||
'How to add platform in the console?',
|
||||
'How can I manage users, permissions, and access control in Appwrite?',
|
||||
'How can I set up database collections and documents in Appwrite?',
|
||||
'How can I set up database tables and rows in Appwrite?',
|
||||
'How do I configure and manage server-side functions in Appwrite?',
|
||||
'How to add custom domain in the console?'
|
||||
];
|
||||
|
||||
+4
-4
@@ -1,16 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { initCreateAttribute } from '$routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+layout.svelte';
|
||||
import { attributeOptions } from '$routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/attributes/store';
|
||||
import { initCreateColumn } from '$routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.svelte';
|
||||
import { columnOptions } from '$routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/store';
|
||||
import Template from './template.svelte';
|
||||
|
||||
let search = '';
|
||||
|
||||
let options = attributeOptions.map((option) => {
|
||||
let options = columnOptions.map((option) => {
|
||||
return {
|
||||
label: option.name,
|
||||
icon: option.icon,
|
||||
callback() {
|
||||
initCreateAttribute(option.name);
|
||||
initCreateColumn(option.name);
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -37,16 +37,16 @@ export const DatabasesPanel: SubPanel = {
|
||||
component: Databases
|
||||
};
|
||||
|
||||
import Collections from './collections.svelte';
|
||||
export const CollectionsPanel: SubPanel = {
|
||||
name: 'Collections',
|
||||
component: Collections
|
||||
import Tables from './tables.svelte';
|
||||
export const TablesPanel: SubPanel = {
|
||||
name: 'Tables',
|
||||
component: Tables
|
||||
};
|
||||
|
||||
import CreateAttribute from './createAttribute.svelte';
|
||||
export const CreateAttributePanel: SubPanel = {
|
||||
name: 'Create Attribute',
|
||||
component: CreateAttribute
|
||||
import CreateColumn from './createColumn.svelte';
|
||||
export const CreateColumnPanel: SubPanel = {
|
||||
name: 'Create Column',
|
||||
component: CreateColumn
|
||||
};
|
||||
|
||||
import Users from './users.svelte';
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { initSearcher } from '../commands';
|
||||
import { collectionsSearcher } from '../searchers';
|
||||
import { tablesSearcher } from '../searchers';
|
||||
import Template from './template.svelte';
|
||||
|
||||
const { search, results } = initSearcher(collectionsSearcher);
|
||||
const { search, results } = initSearcher(tablesSearcher);
|
||||
</script>
|
||||
|
||||
<Template options={$results} bind:search={$search} />
|
||||
@@ -1,29 +0,0 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { database } from '$routes/(console)/project-[region]-[project]/databases/database-[database]/store';
|
||||
import { get } from 'svelte/store';
|
||||
import type { Searcher } from '../commands';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/state';
|
||||
|
||||
export const collectionsSearcher = (async (query: string) => {
|
||||
const databaseId = get(database).$id;
|
||||
const { collections } = await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.listCollections(databaseId);
|
||||
|
||||
return collections
|
||||
.filter((col) => col.name.toLowerCase().includes(query.toLowerCase()))
|
||||
.map(
|
||||
(col) =>
|
||||
({
|
||||
group: 'collections',
|
||||
label: col.name,
|
||||
callback: () => {
|
||||
goto(
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}/collection-${col.$id}`
|
||||
);
|
||||
}
|
||||
}) as const
|
||||
);
|
||||
}) satisfies Searcher;
|
||||
@@ -1,7 +1,7 @@
|
||||
export * from './databases';
|
||||
export * from './users';
|
||||
export * from './organizations';
|
||||
export * from './collections';
|
||||
export * from './tables';
|
||||
export * from './projects';
|
||||
export * from './teams';
|
||||
export * from './functions';
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import type { Searcher } from '../commands';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/state';
|
||||
|
||||
export const tablesSearcher = (async (query: string) => {
|
||||
const databaseId = page.params.database;
|
||||
const { tables } = await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.tables.list(databaseId);
|
||||
|
||||
return tables
|
||||
.filter((table) => table.name.toLowerCase().includes(query.toLowerCase()))
|
||||
.map(
|
||||
(table) =>
|
||||
({
|
||||
group: 'tables',
|
||||
label: table.name,
|
||||
callback: () => {
|
||||
goto(
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}/table-${table.$id}`
|
||||
);
|
||||
}
|
||||
}) as const
|
||||
);
|
||||
}) satisfies Searcher;
|
||||
@@ -57,6 +57,7 @@
|
||||
}
|
||||
|
||||
function updateOrAddItem(payload: Payload) {
|
||||
// todo: @itznotabug - might need a change to $table?
|
||||
const { $id, status, $collection, policyId } = payload;
|
||||
if ($collection === 'archives' && policyId !== null) {
|
||||
return;
|
||||
@@ -117,9 +118,9 @@
|
||||
}
|
||||
|
||||
function backupName(item: BackupArchive | BackupRestoration, key: string) {
|
||||
const attribute = key === 'archives' ? '$createdAt' : 'startedAt';
|
||||
const column = key === 'archives' ? '$createdAt' : 'startedAt';
|
||||
|
||||
return toLocaleDate(item[attribute]);
|
||||
return toLocaleDate(item[column]);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
|
||||
@@ -8,19 +8,18 @@
|
||||
|
||||
let {
|
||||
columns,
|
||||
isCustomCollection = false,
|
||||
isCustomTable = false,
|
||||
allowNoColumns = false,
|
||||
children
|
||||
}: {
|
||||
columns: Writable<Column[]>;
|
||||
isCustomCollection?: boolean;
|
||||
isCustomTable?: boolean;
|
||||
allowNoColumns?: boolean;
|
||||
children: Snippet<[toggle: () => void, selectedColumnsNumber: number]>;
|
||||
} = $props();
|
||||
|
||||
let maxHeight = $state('none');
|
||||
let containerRef = $state<HTMLElement>(null);
|
||||
const collectionId = $derived(page.params.collection);
|
||||
|
||||
const calcMaxHeight = () => {
|
||||
if (containerRef) {
|
||||
@@ -35,16 +34,16 @@
|
||||
const saveColumnPreferences = () => {
|
||||
const shownColumns = $columns.filter((n) => n.hide === true).map((n) => n.id);
|
||||
|
||||
if (isCustomCollection) {
|
||||
preferences.setCustomCollectionColumns(collectionId, shownColumns);
|
||||
if (isCustomTable) {
|
||||
preferences.setCustomTableColumns(page.params.table, shownColumns);
|
||||
} else {
|
||||
preferences.setColumns(shownColumns);
|
||||
}
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
if (isCustomCollection) {
|
||||
const shownColumns = preferences.getCustomCollectionColumns(collectionId);
|
||||
if (isCustomTable) {
|
||||
const shownColumns = preferences.getCustomTableColumns(page.params.table);
|
||||
|
||||
columns.update((n) =>
|
||||
n.map((column) => {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
type ImportItem = {
|
||||
status: string;
|
||||
collection?: string;
|
||||
table?: string;
|
||||
};
|
||||
|
||||
type ImportItemsMap = Map<string, ImportItem>;
|
||||
@@ -22,15 +22,11 @@
|
||||
* Keeps a track of the active and ongoing csv migrations.
|
||||
*
|
||||
* The structure is as follows -
|
||||
* `{ migrationId: { status: status, collection: collection } }`
|
||||
* `{ migrationId: { status: status, table: table } }`
|
||||
*/
|
||||
const importItems: Writable<ImportItemsMap> = writable(new Map());
|
||||
|
||||
async function showCompletionNotification(
|
||||
database: string,
|
||||
collection: string,
|
||||
payload: Payload
|
||||
) {
|
||||
async function showCompletionNotification(database: string, table: string, payload: Payload) {
|
||||
const isSuccess = payload.status === 'completed';
|
||||
const isError = !isSuccess && !!payload.errors;
|
||||
|
||||
@@ -48,20 +44,20 @@
|
||||
|
||||
const type = isSuccess ? 'success' : 'error';
|
||||
const message = isError ? errorMessage : 'CSV import finished successfully.';
|
||||
const url = `${base}/project-${page.params.region}-${page.params.project}/databases/database-${database}/collection-${collection}`;
|
||||
const url = `${base}/project-${page.params.region}-${page.params.project}/databases/database-${database}/table-${table}`;
|
||||
|
||||
addNotification({
|
||||
type,
|
||||
message,
|
||||
isHtml: true,
|
||||
buttons:
|
||||
isSuccess && collection !== page.params.collection
|
||||
? [{ name: 'View documents', method: () => goto(url) }]
|
||||
isSuccess && table !== page.params.table
|
||||
? [{ name: 'View rows', method: () => goto(url) }]
|
||||
: undefined
|
||||
});
|
||||
|
||||
if (isSuccess) {
|
||||
await invalidate(Dependencies.DOCUMENTS);
|
||||
await invalidate(Dependencies.ROWS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,19 +66,19 @@
|
||||
|
||||
const status = importData.status;
|
||||
const resourceId = importData.resourceId ?? '';
|
||||
const [databaseId, collectionId] = resourceId.split(':') ?? [];
|
||||
const [databaseId, tableId] = resourceId.split(':') ?? [];
|
||||
|
||||
const current = $importItems.get(importData.$id);
|
||||
let collectionName = current?.collection ?? null;
|
||||
let tableName = current?.table ?? null;
|
||||
|
||||
if (!collectionName && collectionId) {
|
||||
if (!tableName && tableId) {
|
||||
try {
|
||||
const collection = await sdk
|
||||
const table = await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.getCollection(databaseId, collectionId);
|
||||
collectionName = collection.name;
|
||||
.tables.get(databaseId, tableId);
|
||||
tableName = table.name;
|
||||
} catch {
|
||||
collectionName = null;
|
||||
tableName = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,12 +95,12 @@
|
||||
if (shouldSkip) return items;
|
||||
|
||||
const next = new Map(items);
|
||||
next.set(importData.$id, { status, collection: collectionName ?? undefined });
|
||||
next.set(importData.$id, { status, table: tableName ?? undefined });
|
||||
return next;
|
||||
});
|
||||
|
||||
if (status === 'completed' || status === 'failed') {
|
||||
await showCompletionNotification(databaseId, collectionId, importData);
|
||||
await showCompletionNotification(databaseId, tableId, importData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +168,7 @@
|
||||
<header class="upload-box-header">
|
||||
<h4 class="upload-box-title">
|
||||
<Typography.Text variant="m-500">
|
||||
Importing documents ({$importItems.size})
|
||||
Importing rows ({$importItems.size})
|
||||
</Typography.Text>
|
||||
</h4>
|
||||
<button
|
||||
@@ -198,7 +194,7 @@
|
||||
<div
|
||||
class="progress-bar-top-line u-flex u-gap-8 u-main-space-between">
|
||||
<Typography.Text>
|
||||
{@html text(value.status, value.collection)}
|
||||
{@html text(value.status, value.table)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -26,10 +26,10 @@
|
||||
...services.flatMap((s) => s.actions.map((a) => a.name)),
|
||||
...services.flatMap((s) => s.resources.flatMap((r) => r.actions.map((a) => a.name)))
|
||||
];
|
||||
const attributeNames = [
|
||||
...services.flatMap((s) => s.actions.flatMap((a) => a.attributes ?? [])),
|
||||
const columnNames = [
|
||||
...services.flatMap((s) => s.actions.flatMap((a) => a.columns ?? [])),
|
||||
...services.flatMap((s) =>
|
||||
s.resources.flatMap((r) => r.actions.flatMap((a) => a.attributes ?? []))
|
||||
s.resources.flatMap((r) => r.actions.flatMap((a) => a.columns ?? []))
|
||||
)
|
||||
];
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
const isService = (s: string) => serviceNames.includes(s);
|
||||
const isResource = (s: string) => resourceNames.includes(s);
|
||||
const isAction = (s: string) => actionNames.includes(s);
|
||||
const isAttribute = (s: string) => attributeNames.includes(s);
|
||||
const isColumn = (s: string) => columnNames.includes(s);
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
case 'service': {
|
||||
selected.resource = null;
|
||||
selected.action = null;
|
||||
selected.attribute = null;
|
||||
selected.column = null;
|
||||
break;
|
||||
}
|
||||
case 'resource': {
|
||||
@@ -79,25 +79,25 @@
|
||||
|
||||
// We need to check if the selected action name is still
|
||||
// on the available list, and if so, change the action to the
|
||||
// appropriate one, as it may contain different attributes
|
||||
// appropriate one, as it may contain different attributes/columns.
|
||||
const availableAction = availableActions.find(
|
||||
(a) => a.name === selected.action?.name
|
||||
);
|
||||
|
||||
if (!availableAction) {
|
||||
selected.action = null;
|
||||
selected.attribute = null;
|
||||
selected.column = null;
|
||||
} else {
|
||||
selected.action = availableAction;
|
||||
|
||||
if (!selected?.action?.attributes?.includes(selected.attribute)) {
|
||||
selected.attribute = null;
|
||||
if (!selected?.action?.columns?.includes(selected.column)) {
|
||||
selected.column = null;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'action': {
|
||||
selected.attribute = null;
|
||||
selected.column = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -113,16 +113,13 @@
|
||||
if (isService(field)) {
|
||||
selected.service = services.find((s) => s.name === field);
|
||||
} else if (isResource(field)) {
|
||||
const resource = selected.service?.resources.find((r) => r.name === field);
|
||||
selected.resource = resource;
|
||||
selected.resource = selected.service?.resources.find((r) => r.name === field);
|
||||
} else if (isAction(field)) {
|
||||
const action =
|
||||
selected.action =
|
||||
selected.resource?.actions.find((a) => a.name === field) ||
|
||||
selected.service?.actions.find((a) => a.name === field);
|
||||
selected.action = action;
|
||||
} else if (isAttribute(field)) {
|
||||
const attribute = selected.action?.attributes?.find((a) => a === field);
|
||||
selected.attribute = attribute;
|
||||
} else if (isColumn(field)) {
|
||||
selected.column = selected.action?.columns?.find((a) => a === field);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,7 +140,7 @@
|
||||
const secondToLastField = at(fields, index - 2);
|
||||
|
||||
if (index === 0 || isService(currField)) return 'service';
|
||||
if (isAttribute(currField) || isAction(prevField)) return 'attribute';
|
||||
if (isColumn(currField) || isAction(prevField)) return 'attribute';
|
||||
if (isAction(currField)) return 'action';
|
||||
if (isResource(currField)) return 'resource';
|
||||
if (isService(prevField) || isResource(prevField) || index === 1) {
|
||||
@@ -182,7 +179,7 @@
|
||||
service: null as EventService | null,
|
||||
resource: null as EventResource | null,
|
||||
action: null as EventAction | null,
|
||||
attribute: null as string | null
|
||||
column: null as string | null
|
||||
};
|
||||
let helper: string = null;
|
||||
let customInput: string = null;
|
||||
@@ -198,7 +195,7 @@
|
||||
services: services,
|
||||
resources: selected.service?.resources || [],
|
||||
actions: (selected.resource ? selected.resource?.actions : selected.service?.actions) || [],
|
||||
attributes: selected.action?.attributes || []
|
||||
columns: selected.action?.columns || []
|
||||
};
|
||||
|
||||
$: eventString = (function createEventString(): Array<{ value: string; description: string }> {
|
||||
@@ -212,8 +209,8 @@
|
||||
if (selected.service) {
|
||||
fields.push(selected.service.name, '*');
|
||||
}
|
||||
if (selected.resource?.name === 'documents') {
|
||||
fields.push('collections', '*');
|
||||
if (selected.resource?.name === 'rows') {
|
||||
fields.push('tables', '*');
|
||||
}
|
||||
if (selected.resource) {
|
||||
fields.push(selected.resource.name, '*');
|
||||
@@ -221,8 +218,8 @@
|
||||
if (selected.action) {
|
||||
fields.push(selected.action.name);
|
||||
}
|
||||
if (selected.attribute) {
|
||||
fields.push(selected.attribute);
|
||||
if (selected.column) {
|
||||
fields.push(selected.column);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,14 +287,14 @@
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
|
||||
{#if !empty(available.attributes)}
|
||||
{#if !empty(available.columns)}
|
||||
<Layout.Stack gap="s">
|
||||
<Typography.Text variant="m-500">Choose an attribute (optional)</Typography.Text>
|
||||
<Layout.Stack gap="s" wrap="wrap" direction="row">
|
||||
{#each available.attributes as attribute}
|
||||
{#each available.columns as attribute}
|
||||
<Pill
|
||||
disabled={showInput}
|
||||
selected={selected.attribute === attribute}
|
||||
selected={selected.column === attribute}
|
||||
button
|
||||
on:click={() => select('attribute', attribute)}>
|
||||
{attribute}
|
||||
|
||||
@@ -499,7 +499,7 @@
|
||||
size="s"
|
||||
event="empty_documentation"
|
||||
href="https://appwrite.io/docs/products/storage/upload-download"
|
||||
ariaLabel="create document"
|
||||
ariaLabel="upload download"
|
||||
>Documentation</Button>
|
||||
<Button
|
||||
secondary
|
||||
@@ -526,7 +526,7 @@
|
||||
size="s"
|
||||
event="empty_documentation"
|
||||
href="https://appwrite.io/docs/products/storage/buckets"
|
||||
ariaLabel="create document">Documentation</Button>
|
||||
ariaLabel="create buckets">Documentation</Button>
|
||||
|
||||
<Button
|
||||
secondary
|
||||
|
||||
@@ -12,8 +12,8 @@ export type TagValue = {
|
||||
};
|
||||
|
||||
export type Operator = {
|
||||
toTag: (attribute: string, input?: string | number | string[], type?: string) => TagValue;
|
||||
toQuery: (attribute: string, input?: string | number | string[]) => string;
|
||||
toTag: (column: string, input?: string | number | string[], type?: string) => TagValue;
|
||||
toQuery: (column: string, input?: string | number | string[]) => string;
|
||||
types: ColumnType[];
|
||||
hideInput?: boolean;
|
||||
};
|
||||
@@ -134,7 +134,7 @@ export enum ValidTypes {
|
||||
const operatorsDefault = new Map<
|
||||
ValidOperators,
|
||||
{
|
||||
query: (attr: string, input: string | number | string[]) => string;
|
||||
query: (col: string, input: string | number | string[]) => string;
|
||||
types: ColumnType[];
|
||||
hideInput?: boolean;
|
||||
}
|
||||
@@ -249,8 +249,8 @@ export function generateDefaultOperators() {
|
||||
operatorsDefault.forEach((operator, operatorName) => {
|
||||
operators[operatorName] = {
|
||||
toQuery: operator.query,
|
||||
toTag: (attribute, input = null, type = null) => {
|
||||
return generateTag(attribute, operatorName, input, type);
|
||||
toTag: (column, input = null, type = null) => {
|
||||
return generateTag(column, operatorName, input, type);
|
||||
},
|
||||
types: operator.types,
|
||||
hideInput: operator.hideInput
|
||||
@@ -259,24 +259,24 @@ export function generateDefaultOperators() {
|
||||
return operators;
|
||||
}
|
||||
|
||||
export function generateTag(attribute: string, operatorName: string, input = null, type = null) {
|
||||
export function generateTag(column: string, operatorName: string, input = null, type = null) {
|
||||
if (input === null) {
|
||||
return {
|
||||
value: '',
|
||||
tag: `**${attribute}** ${operatorName}`
|
||||
tag: `**${column}** ${operatorName}`
|
||||
};
|
||||
} else if (Array.isArray(input) && input.length > 2) {
|
||||
return {
|
||||
value: input,
|
||||
tag: `**${attribute}** ${operatorName} **${formatArray(input)}** `
|
||||
tag: `**${column}** ${operatorName} **${formatArray(input)}** `
|
||||
};
|
||||
} else if (type === ValidTypes.Datetime) {
|
||||
return {
|
||||
value: input,
|
||||
tag: `**${attribute}** ${operatorName} **${toLocaleDateTime(input.toString())}**`
|
||||
tag: `**${column}** ${operatorName} **${toLocaleDateTime(input.toString())}**`
|
||||
};
|
||||
} else {
|
||||
return { value: input, tag: `**${attribute}** ${operatorName} **${input}**` };
|
||||
return { value: input, tag: `**${column}** ${operatorName} **${input}**` };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
|
||||
export let columns: Writable<Column[]>;
|
||||
export let view: View;
|
||||
export let isCustomCollection = false;
|
||||
export let isCustomTable = false;
|
||||
export let hideView = false;
|
||||
export let hideColumns = false;
|
||||
export let allowNoColumns = false;
|
||||
</script>
|
||||
|
||||
{#if !hideColumns && view === View.Table}
|
||||
<ColumnSelector {columns} {isCustomCollection} {allowNoColumns}>
|
||||
<ColumnSelector {columns} {isCustomTable} {allowNoColumns}>
|
||||
{#snippet children(toggle, selectedColumnsNumber)}
|
||||
<Button.Button
|
||||
size="s"
|
||||
|
||||
+52
-9
@@ -38,9 +38,9 @@ export enum Dependencies {
|
||||
MEMBERSHIPS = 'dependency:memberships',
|
||||
DATABASE = 'dependency:database',
|
||||
DATABASES = 'dependency:databases',
|
||||
COLLECTION = 'dependency:collection',
|
||||
DOCUMENT = 'dependency:document',
|
||||
DOCUMENTS = 'dependency:documents',
|
||||
TABLE = 'dependency:table',
|
||||
ROW = 'dependency:row',
|
||||
ROWS = 'dependency:rows',
|
||||
BUCKET = 'dependency:bucket',
|
||||
FILE = 'dependency:file',
|
||||
FILE_TOKENS = 'dependency:file_tokens',
|
||||
@@ -64,7 +64,7 @@ export enum Dependencies {
|
||||
WEBHOOK = 'dependency:webhook',
|
||||
WEBHOOKS = 'dependency:webhooks',
|
||||
MIGRATIONS = 'dependency:migrations',
|
||||
COLLECTIONS = 'dependency:collections',
|
||||
TABLES = 'dependency:tables',
|
||||
BACKUPS = 'dependency:backups',
|
||||
RUNTIMES = 'dependency:runtimes',
|
||||
CONSOLE_VARIABLES = 'dependency:console_variables',
|
||||
@@ -92,6 +92,8 @@ export const defaultScopes: string[] = [
|
||||
'teams.write',
|
||||
'documents.read',
|
||||
'documents.write',
|
||||
'rows.read',
|
||||
'rows.write',
|
||||
'files.read',
|
||||
'files.write',
|
||||
'projects.read',
|
||||
@@ -111,6 +113,8 @@ export const defaultScopes: string[] = [
|
||||
'databases.write',
|
||||
'collections.read',
|
||||
'collections.write',
|
||||
'tables.read',
|
||||
'tables.write',
|
||||
'buckets.read',
|
||||
'buckets.write',
|
||||
'functions.read',
|
||||
@@ -141,6 +145,9 @@ export const defaultScopes: string[] = [
|
||||
|
||||
export const defaultRoles: string[] = ['owner'];
|
||||
|
||||
// TODO: remove old terminology scopes.
|
||||
// these are kept for backwards compatibility with keys and events that already exists.
|
||||
// for the new ones, we use the new terminology.
|
||||
export const scopes: {
|
||||
scope: string;
|
||||
description: string;
|
||||
@@ -201,6 +208,18 @@ export const scopes: {
|
||||
category: 'Database',
|
||||
icon: 'database'
|
||||
},
|
||||
{
|
||||
scope: 'tables.read',
|
||||
description: "Access to read your project's database tables",
|
||||
category: 'Database',
|
||||
icon: 'database'
|
||||
},
|
||||
{
|
||||
scope: 'tables.write',
|
||||
description: "Access to create, update, and delete your project's database tables",
|
||||
category: 'Database',
|
||||
icon: 'database'
|
||||
},
|
||||
{
|
||||
scope: 'attributes.read',
|
||||
description: "Access to read your project's database collection's attributes",
|
||||
@@ -214,6 +233,18 @@ export const scopes: {
|
||||
category: 'Database',
|
||||
icon: 'database'
|
||||
},
|
||||
{
|
||||
scope: 'columns.read',
|
||||
description: "Access to read your project's database table's columns",
|
||||
category: 'Database',
|
||||
icon: 'database'
|
||||
},
|
||||
{
|
||||
scope: 'columns.write',
|
||||
description: "Access to create, update, and delete your project's database table's columns",
|
||||
category: 'Database',
|
||||
icon: 'database'
|
||||
},
|
||||
{
|
||||
scope: 'indexes.read',
|
||||
description: "Access to read your project's database collection's indexes",
|
||||
@@ -239,6 +270,18 @@ export const scopes: {
|
||||
category: 'Database',
|
||||
icon: 'database'
|
||||
},
|
||||
{
|
||||
scope: 'rows.read',
|
||||
description: "Access to read your project's database rows",
|
||||
category: 'Database',
|
||||
icon: 'database'
|
||||
},
|
||||
{
|
||||
scope: 'rows.write',
|
||||
description: "Access to create, update, and delete your project's database rows",
|
||||
category: 'Database',
|
||||
icon: 'database'
|
||||
},
|
||||
{
|
||||
scope: 'files.read',
|
||||
description: "Access to read your project's storage files and preview images",
|
||||
@@ -430,7 +473,7 @@ export type EventResource = {
|
||||
|
||||
export type EventAction = {
|
||||
name: string;
|
||||
attributes?: string[];
|
||||
columns?: string[];
|
||||
};
|
||||
|
||||
export const eventServices: Array<EventService> = [
|
||||
@@ -448,11 +491,11 @@ export const eventServices: Array<EventService> = [
|
||||
name: 'databases',
|
||||
resources: [
|
||||
{
|
||||
name: 'collections',
|
||||
name: 'tables',
|
||||
actions: [{ name: 'create' }, { name: 'update' }, { name: 'delete' }]
|
||||
},
|
||||
{
|
||||
name: 'documents',
|
||||
name: 'rows',
|
||||
actions: [{ name: 'create' }, { name: 'update' }, { name: 'delete' }]
|
||||
}
|
||||
],
|
||||
@@ -479,7 +522,7 @@ export const eventServices: Array<EventService> = [
|
||||
name: 'memberships',
|
||||
actions: [
|
||||
{ name: 'create' },
|
||||
{ name: 'update', attributes: ['status'] },
|
||||
{ name: 'update', columns: ['status'] },
|
||||
{ name: 'delete' }
|
||||
]
|
||||
}
|
||||
@@ -495,7 +538,7 @@ export const eventServices: Array<EventService> = [
|
||||
],
|
||||
actions: [
|
||||
{ name: 'create' },
|
||||
{ name: 'update', attributes: ['email', 'name', 'password', 'status', 'prefs'] },
|
||||
{ name: 'update', columns: ['email', 'name', 'password', 'status', 'prefs'] },
|
||||
{ name: 'delete' }
|
||||
]
|
||||
},
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
let showDropdown = false;
|
||||
|
||||
// TODO: remove the default billing limits when backend is updated with billing code
|
||||
// TODO: @itznotabug - check with @abnegate, what do we do here? this is billing!
|
||||
const { bandwidth, documents, storage, users, executions } = $organization?.billingLimits ?? {
|
||||
bandwidth: 1,
|
||||
documents: 1,
|
||||
@@ -42,6 +43,8 @@
|
||||
users: 1,
|
||||
executions: 1
|
||||
};
|
||||
|
||||
// TODO: @itznotabug - check with @abnegate, what do we do here? this is billing!
|
||||
const limitedServices = [
|
||||
{ name: 'bandwidth', value: bandwidth },
|
||||
{ name: 'documents', value: documents },
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
view = $bindable(),
|
||||
columns,
|
||||
hideColumns = false,
|
||||
isCustomCollection = false
|
||||
isCustomTable = false
|
||||
}: {
|
||||
show?: boolean;
|
||||
hideView?: boolean;
|
||||
view?: View;
|
||||
columns?: Writable<Column[]>;
|
||||
hideColumns?: boolean;
|
||||
isCustomCollection?: boolean;
|
||||
isCustomTable?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
{#if !hideColumns && $columns?.length}
|
||||
<Layout.Stack gap="xs">
|
||||
<Typography.Text>Columns</Typography.Text>
|
||||
<ColumnSelector {columns} {isCustomCollection}>
|
||||
<ColumnSelector {columns} {isCustomTable}>
|
||||
{#snippet children(toggle, selectedColumnsNumber)}
|
||||
<Button.Button
|
||||
size="s"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
export let title: string;
|
||||
export let columns: Writable<Column[]>;
|
||||
export let view: View;
|
||||
export let isCustomCollection = false;
|
||||
export let isCustomTable = false;
|
||||
export let hideView = false;
|
||||
export let hideColumns = false;
|
||||
export let allowNoColumns = false;
|
||||
@@ -17,13 +17,7 @@
|
||||
<header class="grid-header">
|
||||
<Typography.Title size="m">{title}</Typography.Title>
|
||||
<div class="u-flex u-gap-16 u-contents-mobile">
|
||||
<ViewSelector
|
||||
{view}
|
||||
{columns}
|
||||
{isCustomCollection}
|
||||
{hideView}
|
||||
{hideColumns}
|
||||
{allowNoColumns} />
|
||||
<ViewSelector {view} {columns} {isCustomTable} {hideView} {hideColumns} {allowNoColumns} />
|
||||
<div class="grid-header-col-2">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@@ -366,6 +366,8 @@ export async function checkForUsageLimit(org: Organization) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: @itznotabug - check with @abnegate, what do we do here? this is billing!
|
||||
const { bandwidth, documents, executions, storage, users } = org?.billingLimits ?? {};
|
||||
const resources = [
|
||||
{ value: bandwidth, name: 'bandwidth' },
|
||||
@@ -378,6 +380,7 @@ export async function checkForUsageLimit(org: Organization) {
|
||||
const members = org.total;
|
||||
const plan = get(currentPlan);
|
||||
const membersOverflow =
|
||||
// `plan` can be null on `onboarding/create-organization` route.
|
||||
// nested null checks needed: GitHub Education plan have empty addons.
|
||||
members > plan?.addons?.seats?.limit
|
||||
? members - (plan?.addons?.seats?.limit || members)
|
||||
|
||||
+13
-12
@@ -9,7 +9,7 @@ const initialFormData = {
|
||||
},
|
||||
databases: {
|
||||
root: false,
|
||||
documents: false
|
||||
rows: false
|
||||
},
|
||||
functions: {
|
||||
root: false,
|
||||
@@ -45,18 +45,19 @@ export const ResourcesFriendly = {
|
||||
'environment-variable': { singular: 'Environment Variable', plural: 'Environment Variables' },
|
||||
deployment: { singular: 'Deployment', plural: 'Deployments' },
|
||||
database: { singular: 'Database', plural: 'Databases' },
|
||||
collection: { singular: 'Collection', plural: 'Collections' },
|
||||
table: { singular: 'Table', plural: 'Tables' },
|
||||
index: { singular: 'Index', plural: 'Indexes' },
|
||||
attribute: { singular: 'Attribute', plural: 'Attributes' },
|
||||
document: { singular: 'Document', plural: 'Documents' }
|
||||
column: { singular: 'Column', plural: 'Columns' },
|
||||
row: { singular: 'Row', plural: 'Rows' }
|
||||
};
|
||||
|
||||
const resources = Object.keys(ResourcesFriendly);
|
||||
|
||||
type Resource = (typeof resources)[number];
|
||||
|
||||
// @todo: @itznotabug - check if other resources are correct and work fine!
|
||||
export const providerResources: Record<Provider, Resource[]> = {
|
||||
appwrite: [...resources],
|
||||
appwrite: [...resources], // new terminology, others are ok?
|
||||
supabase: [
|
||||
'user',
|
||||
'database',
|
||||
@@ -91,12 +92,12 @@ export const migrationFormToResources = (
|
||||
}
|
||||
if (formData.databases.root) {
|
||||
addResource('database');
|
||||
addResource('collection');
|
||||
addResource('attribute');
|
||||
addResource('index');
|
||||
addResource('table');
|
||||
addResource('column');
|
||||
addResource('columnIndex');
|
||||
}
|
||||
if (formData.databases.documents) {
|
||||
addResource('document');
|
||||
if (formData.databases.rows) {
|
||||
addResource('row');
|
||||
}
|
||||
if (formData.functions.root) {
|
||||
addResource('function');
|
||||
@@ -150,8 +151,8 @@ export const resourcesToMigrationForm = (
|
||||
if (resources.includes('database')) {
|
||||
formData.databases.root = true;
|
||||
}
|
||||
if (includesAll(resources, ['collection', 'attribute', 'document'])) {
|
||||
formData.databases.documents = true;
|
||||
if (includesAll(resources, ['table', 'column', 'row'])) {
|
||||
formData.databases.rows = true;
|
||||
}
|
||||
if (resources.includes('function') && isVersionAtLeast(version, '1.4.0')) {
|
||||
formData.functions.root = true;
|
||||
|
||||
@@ -42,6 +42,7 @@ export type OrganizationList = {
|
||||
total: number;
|
||||
};
|
||||
|
||||
// TODO: @itznotabug - check with @abnegate, what do we do here? this is billing!
|
||||
export type BillingLimits = {
|
||||
bandwidth: number;
|
||||
documents: number;
|
||||
|
||||
@@ -20,9 +20,13 @@ type TeamPreferences = {
|
||||
|
||||
type PreferencesStore = {
|
||||
[key: string]: Preferences;
|
||||
// kept for backwards compatibility
|
||||
collections?: {
|
||||
[key: string]: Preferences['columns'];
|
||||
};
|
||||
tables?: {
|
||||
[key: string]: Preferences['columns'];
|
||||
};
|
||||
displayNames?: {
|
||||
[key: string]: TeamPreferences['names'];
|
||||
};
|
||||
@@ -106,8 +110,8 @@ function createPreferences() {
|
||||
}
|
||||
);
|
||||
},
|
||||
getCustomCollectionColumns: (collectionId: string): Preferences['columns'] => {
|
||||
return preferences?.collections?.[collectionId] ?? [];
|
||||
getCustomTableColumns: (tableId: string): Preferences['columns'] => {
|
||||
return preferences?.tables?.[tableId] ?? preferences?.collections?.[tableId] ?? [];
|
||||
},
|
||||
setLimit: (limit: Preferences['limit']) =>
|
||||
updateAndSync((n) => {
|
||||
@@ -148,14 +152,15 @@ function createPreferences() {
|
||||
|
||||
return n;
|
||||
}),
|
||||
setCustomCollectionColumns: (collectionId: string, columns: Preferences['columns']) =>
|
||||
setCustomTableColumns: (tableId: string, columns: Preferences['columns']) =>
|
||||
updateAndSync((n) => {
|
||||
if (!n?.collections?.[collectionId]) {
|
||||
n ??= {};
|
||||
n.collections ??= {};
|
||||
}
|
||||
n ??= {};
|
||||
n.collections ??= {};
|
||||
n.tables ??= {};
|
||||
|
||||
n.collections[collectionId] = Array.from(new Set(columns));
|
||||
n.tables[tableId] = Array.from(new Set(columns));
|
||||
// let's not double save
|
||||
// n.collections[tableId] = Array.from(new Set(columns));
|
||||
return n;
|
||||
}),
|
||||
loadTeamPrefs: async (id: string) => {
|
||||
@@ -172,7 +177,7 @@ function createPreferences() {
|
||||
},
|
||||
setDisplayNames: async (
|
||||
orgId: string,
|
||||
collectionId: string,
|
||||
tableId: string,
|
||||
names: TeamPreferences['names']
|
||||
) => {
|
||||
let teamPrefs: Models.Preferences;
|
||||
@@ -183,7 +188,7 @@ function createPreferences() {
|
||||
}
|
||||
|
||||
teamPrefs = n;
|
||||
n.displayNames[collectionId] = names;
|
||||
n.displayNames[tableId] = names;
|
||||
|
||||
return n;
|
||||
});
|
||||
|
||||
@@ -19,13 +19,15 @@ export const canWritePlatforms = derived(scopes, ($scopes) => $scopes.includes('
|
||||
export const canWriteTargets = derived(scopes, ($scopes) => $scopes.includes('targets.write'));
|
||||
export const canWriteUsers = derived(scopes, ($scopes) => $scopes.includes('users.write'));
|
||||
export const canWriteTeams = derived(scopes, ($scopes) => $scopes.includes('teams.write'));
|
||||
export const canWriteCollections = derived(scopes, ($scopes) =>
|
||||
$scopes.includes('collections.write')
|
||||
);
|
||||
export const canWriteTables = derived(scopes, ($scopes) => {
|
||||
return $scopes.includes('collections.write') || $scopes.includes('tables.write');
|
||||
});
|
||||
export const canWriteSites = derived(scopes, ($scopes) => $scopes.includes('sites.write'));
|
||||
export const canWriteDomains = derived(scopes, ($scopes) => $scopes.includes('domains.write')); //TODO: check if correct
|
||||
|
||||
export const canWriteDocuments = derived(scopes, ($scopes) => $scopes.includes('documents.write'));
|
||||
export const canWriteRows = derived(scopes, ($scopes) => {
|
||||
return $scopes.includes('documents.write') || $scopes.includes('rows.write');
|
||||
});
|
||||
export const canWriteExecutions = derived(scopes, ($scopes) =>
|
||||
$scopes.includes('executions.write')
|
||||
);
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
Vcs,
|
||||
Sites,
|
||||
Tokens,
|
||||
Tables,
|
||||
Domains
|
||||
} from '@appwrite.io/console';
|
||||
import { Billing } from '../sdk/billing';
|
||||
@@ -122,7 +123,8 @@ const sdkForProject = {
|
||||
vcs: new Vcs(clientProject),
|
||||
proxy: new Proxy(clientProject),
|
||||
migrations: new Migrations(clientProject),
|
||||
sites: new Sites(clientProject)
|
||||
sites: new Sites(clientProject),
|
||||
tables: new Tables(clientProject)
|
||||
};
|
||||
|
||||
export const realtime = {
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { PageLoad } from './$types';
|
||||
export const load: PageLoad = async ({ url, params, route, depends }) => {
|
||||
depends(Dependencies.ORGANIZATION);
|
||||
depends(Dependencies.MEMBERS);
|
||||
|
||||
const page = getPage(url);
|
||||
const search = getSearch(url);
|
||||
const limit = getLimit(url, route, PAGE_LIMIT);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { capitalize } from '$lib/helpers/string';
|
||||
import { currentPlan } from '$lib/stores/organization';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
|
||||
function getResource(id: string) {
|
||||
id = id.replace('/(console)/project-[region]-[project]/', '');
|
||||
let parts = id.split('/');
|
||||
@@ -39,6 +40,7 @@
|
||||
>{'status' in page.error
|
||||
? page.error.status || 'Invalid Argument'
|
||||
: 'Invalid Argument'}</Typography.Title>
|
||||
<p class="body-text-2 u-bold u-margin-block-start-4">{page.error.message}</p>
|
||||
|
||||
<Typography.Title>{page.error.message}</Typography.Title>
|
||||
{/if}
|
||||
</Container>
|
||||
@@ -82,6 +82,7 @@
|
||||
const database = await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.create(databaseId, name);
|
||||
|
||||
await createPolicies(databaseId);
|
||||
|
||||
showCreate = false;
|
||||
|
||||
+18
-19
@@ -8,13 +8,13 @@
|
||||
registerSearchers,
|
||||
updateCommandGroupRanks
|
||||
} from '$lib/commandCenter';
|
||||
import { collectionsSearcher } from '$lib/commandCenter/searchers';
|
||||
import { tablesSearcher } from '$lib/commandCenter/searchers';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import CreateCollection from './createCollection.svelte';
|
||||
import CreateTable from './createTable.svelte';
|
||||
import { showCreate } from './store';
|
||||
import { CollectionsPanel } from '$lib/commandCenter/panels';
|
||||
import { canWriteCollections, canWriteDatabases } from '$lib/stores/roles';
|
||||
import { TablesPanel } from '$lib/commandCenter/panels';
|
||||
import { canWriteTables, canWriteDatabases } from '$lib/stores/roles';
|
||||
import { showCreateBackup, showCreatePolicy } from './backups/store';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import { currentPlan } from '$lib/stores/organization';
|
||||
@@ -23,17 +23,17 @@
|
||||
const project = page.params.project;
|
||||
const databaseId = page.params.database;
|
||||
|
||||
async function handleCreate(event: CustomEvent<Models.Collection>) {
|
||||
async function handleCreate(event: CustomEvent<Models.Table>) {
|
||||
$showCreate = false;
|
||||
await invalidate(Dependencies.DATABASE);
|
||||
await goto(
|
||||
`${base}/project-${page.params.region}-${project}/databases/database-${databaseId}/collection-${event.detail.$id}`
|
||||
`${base}/project-${page.params.region}-${project}/databases/database-${databaseId}/table-${event.detail.$id}`
|
||||
);
|
||||
}
|
||||
|
||||
$: $registerCommands([
|
||||
{
|
||||
label: 'Create collection',
|
||||
label: 'Create table',
|
||||
callback() {
|
||||
$showCreate = true;
|
||||
if (!page.url.pathname.endsWith(databaseId)) {
|
||||
@@ -43,7 +43,7 @@
|
||||
}
|
||||
},
|
||||
keys: page.url.pathname.endsWith(databaseId) ? ['c'] : ['c', 'c'],
|
||||
disabled: page.url.pathname.includes('collection-') || !$canWriteCollections,
|
||||
disabled: page.url.pathname.includes('table-') || !$canWriteTables,
|
||||
group: 'databases',
|
||||
icon: IconPlus
|
||||
},
|
||||
@@ -80,14 +80,14 @@
|
||||
disabled: !isCloud || !$currentPlan.backupsEnabled
|
||||
},
|
||||
{
|
||||
label: 'Go to collections',
|
||||
label: 'Go to tables',
|
||||
callback() {
|
||||
goto(
|
||||
`${base}/project-${page.params.region}-${project}/databases/database-${databaseId}`
|
||||
);
|
||||
},
|
||||
disabled:
|
||||
page.url.pathname.endsWith(databaseId) || page.url.pathname.includes('collection-'),
|
||||
page.url.pathname.endsWith(databaseId) || page.url.pathname.includes('table-'),
|
||||
keys: ['g', 'c'],
|
||||
group: 'databases'
|
||||
},
|
||||
@@ -98,8 +98,7 @@
|
||||
`${base}/project-${page.params.region}-${project}/databases/database-${databaseId}/usage`
|
||||
);
|
||||
},
|
||||
disabled:
|
||||
page.url.pathname.includes('/usage') || page.url.pathname.includes('collection-'),
|
||||
disabled: page.url.pathname.includes('/usage') || page.url.pathname.includes('table-'),
|
||||
keys: ['g', 'u'],
|
||||
group: 'databases'
|
||||
},
|
||||
@@ -111,7 +110,7 @@
|
||||
);
|
||||
},
|
||||
disabled:
|
||||
page.url.pathname.includes('/backups') || page.url.pathname.includes('collection-'),
|
||||
page.url.pathname.includes('/backups') || page.url.pathname.includes('table-'),
|
||||
keys: ['g', 'b'],
|
||||
group: 'databases'
|
||||
},
|
||||
@@ -124,24 +123,24 @@
|
||||
},
|
||||
disabled:
|
||||
page.url.pathname.includes('/settings') ||
|
||||
page.url.pathname.includes('collection-') ||
|
||||
page.url.pathname.includes('table-') ||
|
||||
!$canWriteDatabases,
|
||||
keys: ['g', 's'],
|
||||
group: 'databases'
|
||||
},
|
||||
{
|
||||
label: 'Find collections',
|
||||
label: 'Find tables',
|
||||
callback: () => {
|
||||
addSubPanel(CollectionsPanel);
|
||||
addSubPanel(TablesPanel);
|
||||
},
|
||||
group: 'databases',
|
||||
rank: -1
|
||||
}
|
||||
]);
|
||||
|
||||
$registerSearchers(collectionsSearcher);
|
||||
$registerSearchers(tablesSearcher);
|
||||
|
||||
$: $updateCommandGroupRanks({ collections: 10 });
|
||||
$: $updateCommandGroupRanks({ tables: 10 });
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -150,4 +149,4 @@
|
||||
|
||||
<slot />
|
||||
|
||||
<CreateCollection bind:showCreate={$showCreate} on:created={handleCreate} />
|
||||
<CreateTable bind:showCreate={$showCreate} on:created={handleCreate} />
|
||||
+12
-12
@@ -2,11 +2,11 @@
|
||||
import { Empty, PaginationWithLimit } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Container, ResponsiveContainerHeader } from '$lib/layout';
|
||||
import { columns, showCreate } from './store';
|
||||
import { tableViewColumns, showCreate } from './store';
|
||||
import Table from './table.svelte';
|
||||
import Grid from './grid.svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { canWriteCollections } from '$lib/stores/roles';
|
||||
import { canWriteTables } from '$lib/stores/roles';
|
||||
import { Icon } from '@appwrite.io/pink-svelte';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
@@ -16,18 +16,18 @@
|
||||
<Container>
|
||||
<ResponsiveContainerHeader
|
||||
bind:view={data.view}
|
||||
{columns}
|
||||
columns={tableViewColumns}
|
||||
hasSearch
|
||||
searchPlaceholder="Search by name or ID">
|
||||
{#if $canWriteCollections}
|
||||
<Button on:click={() => ($showCreate = true)} event="create_collection">
|
||||
{#if $canWriteTables}
|
||||
<Button on:click={() => ($showCreate = true)} event="create_table">
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Create collection
|
||||
Create table
|
||||
</Button>
|
||||
{/if}
|
||||
</ResponsiveContainerHeader>
|
||||
|
||||
{#if data.collections.total}
|
||||
{#if data.tables.total}
|
||||
{#if data.view === 'grid'}
|
||||
<Grid {data} bind:showCreate={$showCreate} />
|
||||
{:else}
|
||||
@@ -35,16 +35,16 @@
|
||||
{/if}
|
||||
|
||||
<PaginationWithLimit
|
||||
name="Collections"
|
||||
name="Tables"
|
||||
limit={data.limit}
|
||||
offset={data.offset}
|
||||
total={data.collections.total} />
|
||||
total={data.tables.total} />
|
||||
{:else}
|
||||
<Empty
|
||||
single
|
||||
allowCreate={$canWriteCollections}
|
||||
href="https://appwrite.io/docs/products/databases/collections"
|
||||
target="collection"
|
||||
allowCreate={$canWriteTables}
|
||||
href="https://appwrite.io/docs/products/databases/tables"
|
||||
target="table"
|
||||
on:click={() => ($showCreate = true)} />
|
||||
{/if}
|
||||
</Container>
|
||||
+5
-4
@@ -5,15 +5,16 @@ import type { PageLoad } from './$types';
|
||||
import { CARD_LIMIT, Dependencies } from '$lib/constants';
|
||||
|
||||
export const load: PageLoad = async ({ params, url, route, depends }) => {
|
||||
depends(Dependencies.COLLECTIONS);
|
||||
depends(Dependencies.TABLES);
|
||||
const page = getPage(url);
|
||||
const search = getSearch(url);
|
||||
const limit = getLimit(url, route, CARD_LIMIT);
|
||||
const view = getView(url, route, View.Grid);
|
||||
const offset = pageToOffset(page, limit);
|
||||
const collections = await sdk
|
||||
|
||||
const tables = await sdk
|
||||
.forProject(params.region, params.project)
|
||||
.databases.listCollections(
|
||||
.tables.list(
|
||||
params.database,
|
||||
[Query.limit(limit), Query.offset(offset), Query.orderDesc('')],
|
||||
search || undefined
|
||||
@@ -24,6 +25,6 @@ export const load: PageLoad = async ({ params, url, route, depends }) => {
|
||||
limit,
|
||||
search,
|
||||
view,
|
||||
collections
|
||||
tables
|
||||
};
|
||||
};
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import { base } from '$app/paths';
|
||||
import type { PageLoad } from './$types';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { AppwriteException } from '@appwrite.io/console';
|
||||
|
||||
const LEGACY_ROUTE_MAPPINGS = [
|
||||
{ pattern: /^collection-([^/]+)/, replacement: 'table-$1' },
|
||||
{ pattern: /^document-([^/]+)/, replacement: 'row-$1' },
|
||||
{ pattern: /^attribute-([^/]+)/, replacement: 'column-$1' }
|
||||
] as const;
|
||||
|
||||
function isLegacyRoute(segments: string[]): boolean {
|
||||
return segments.some((segment) =>
|
||||
LEGACY_ROUTE_MAPPINGS.some((mapping) => mapping.pattern.test(segment))
|
||||
);
|
||||
}
|
||||
|
||||
function rewriteLegacySegments(segments: string[]): string[] {
|
||||
return segments.map((segment) => {
|
||||
for (const mapping of LEGACY_ROUTE_MAPPINGS) {
|
||||
if (mapping.pattern.test(segment)) {
|
||||
return segment.replace(mapping.pattern, mapping.replacement);
|
||||
}
|
||||
}
|
||||
return segment;
|
||||
});
|
||||
}
|
||||
|
||||
export const load: PageLoad = async ({ params, url }) => {
|
||||
const restSegments = params.rest ? params.rest.split('/').filter(Boolean) : [];
|
||||
const baseUrl = `${base}/project-${params.region}-${params.project}/databases/database-${params.database}`;
|
||||
|
||||
if (isLegacyRoute(restSegments)) {
|
||||
const rewrittenSegments = rewriteLegacySegments(restSegments);
|
||||
const newPath = `${baseUrl}/${rewrittenSegments.join('/')}`;
|
||||
|
||||
redirect(308, newPath + url.search);
|
||||
}
|
||||
|
||||
throw new AppwriteException('Not Found', 404);
|
||||
};
|
||||
-63
@@ -1,63 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import Boolean from './attributes/boolean.svelte';
|
||||
import Datetime from './attributes/datetime.svelte';
|
||||
import Enum from './attributes/enum.svelte';
|
||||
import Integer from './attributes/integer.svelte';
|
||||
import Relationship from './attributes/relationship.svelte';
|
||||
import String from './attributes/string.svelte';
|
||||
import Url from './attributes/url.svelte';
|
||||
|
||||
export let id: string;
|
||||
export let label: string;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
export let value: string | number | boolean | null | string[];
|
||||
export let editing = false;
|
||||
export let attribute:
|
||||
| Models.AttributeBoolean
|
||||
| Models.AttributeEmail
|
||||
| Models.AttributeEnum
|
||||
| Models.AttributeFloat
|
||||
| Models.AttributeInteger
|
||||
| Models.AttributeIp
|
||||
| Models.AttributeString
|
||||
| Models.AttributeDatetime
|
||||
| Models.AttributeUrl;
|
||||
|
||||
const attributesTypeMap = {
|
||||
string: String,
|
||||
integer: Integer,
|
||||
double: Integer,
|
||||
boolean: Boolean,
|
||||
datetime: Datetime,
|
||||
relationship: Relationship
|
||||
};
|
||||
|
||||
const attributesFormatMap = {
|
||||
ip: String,
|
||||
url: Url,
|
||||
email: String,
|
||||
enum: Enum
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if attribute.type in attributesTypeMap}
|
||||
{#if 'format' in attribute && attribute.format}
|
||||
<svelte:component
|
||||
this={attributesFormatMap[attribute.format]}
|
||||
{id}
|
||||
{label}
|
||||
{attribute}
|
||||
{optionalText}
|
||||
bind:value />
|
||||
{:else}
|
||||
<svelte:component
|
||||
this={attributesTypeMap[attribute.type]}
|
||||
{id}
|
||||
{editing}
|
||||
{label}
|
||||
{attribute}
|
||||
{optionalText}
|
||||
bind:value />
|
||||
{/if}
|
||||
{/if}
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
import { type Models, Query } from '@appwrite.io/console';
|
||||
import type { Attributes } from '../../store';
|
||||
|
||||
export function isRelationshipToMany(attribute: Models.AttributeRelationship) {
|
||||
if (!attribute) return false;
|
||||
if (!attribute?.relationType) return false;
|
||||
if (attribute?.side === 'child') {
|
||||
return !['oneToOne', 'oneToMany'].includes(attribute?.relationType);
|
||||
} else {
|
||||
return !['oneToOne', 'manyToOne'].includes(attribute?.relationType);
|
||||
}
|
||||
}
|
||||
|
||||
export function isRelationship(attribute: Attributes): attribute is Models.AttributeRelationship {
|
||||
if (!attribute) return false;
|
||||
return attribute?.type === 'relationship';
|
||||
}
|
||||
|
||||
export function isString(attribute: Attributes): attribute is Models.AttributeString {
|
||||
if (!attribute) return false;
|
||||
return attribute?.type === 'string';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns select queries for all main and related fields in a collection.
|
||||
*/
|
||||
export function buildWildcardAttributesQuery(
|
||||
collection: Models.Collection | null = null
|
||||
): string[] {
|
||||
return [
|
||||
...(collection?.attributes
|
||||
?.filter((attr) => isRelationship(attr))
|
||||
?.map((attr) => Query.select([`${attr.key}.*`])) ?? []),
|
||||
|
||||
Query.select(['*'])
|
||||
];
|
||||
}
|
||||
-72
@@ -1,72 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { CardGrid } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, InputSwitch } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { collection } from '../store';
|
||||
|
||||
const databaseId = page.params.database;
|
||||
|
||||
let collectionDocumentSecurity: boolean = null;
|
||||
|
||||
onMount(() => {
|
||||
collectionDocumentSecurity ??= $collection.documentSecurity;
|
||||
});
|
||||
|
||||
async function updateSecurity() {
|
||||
try {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.updateCollection(
|
||||
databaseId,
|
||||
$collection.$id,
|
||||
$collection.name,
|
||||
$collection.$permissions,
|
||||
collectionDocumentSecurity,
|
||||
$collection.enabled
|
||||
);
|
||||
await invalidate(Dependencies.COLLECTION);
|
||||
addNotification({
|
||||
message: 'Security has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent(Submit.CollectionUpdateSecurity);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
trackError(error, Submit.CollectionUpdateSecurity);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<CardGrid>
|
||||
<svelte:fragment slot="title">Document security</svelte:fragment>
|
||||
<svelte:fragment slot="aside">
|
||||
<InputSwitch
|
||||
bind:value={collectionDocumentSecurity}
|
||||
id="security"
|
||||
label="Document security" />
|
||||
|
||||
<p class="text">
|
||||
When document security is enabled, users will be able to access documents for which they
|
||||
have been granted <b>either document or collection permissions</b>.
|
||||
</p>
|
||||
<p class="text">
|
||||
If document security is disabled, users can access documents <b
|
||||
>only if they have collection permissions</b
|
||||
>. Document permissions will be ignored.
|
||||
</p>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={collectionDocumentSecurity === $collection.documentSecurity}
|
||||
on:click={updateSecurity}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
import { page } from '$app/stores';
|
||||
import type { Column } from '$lib/helpers/types';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { derived, writable } from 'svelte/store';
|
||||
|
||||
export type Attributes =
|
||||
| Models.AttributeBoolean
|
||||
| Models.AttributeEmail
|
||||
| Models.AttributeEnum
|
||||
| Models.AttributeFloat
|
||||
| Models.AttributeInteger
|
||||
| Models.AttributeIp
|
||||
| Models.AttributeString
|
||||
| Models.AttributeUrl
|
||||
| (Models.AttributeRelationship & { default?: never });
|
||||
|
||||
type Collection = Omit<Models.Collection, 'attributes'> & {
|
||||
attributes: Array<Attributes>;
|
||||
};
|
||||
|
||||
export const collection = derived(page, ($page) => $page.data.collection as Collection);
|
||||
export const attributes = derived(
|
||||
page,
|
||||
($page) => $page.data.collection.attributes as Attributes[]
|
||||
);
|
||||
export const indexes = derived(page, ($page) => $page.data.collection.indexes as Models.Index[]);
|
||||
|
||||
export const columns = writable<Column[]>([]);
|
||||
|
||||
export const isCsvImportInProgress = writable(false);
|
||||
+11
-9
@@ -24,22 +24,24 @@
|
||||
const create = async () => {
|
||||
error = null;
|
||||
try {
|
||||
const collection = await sdk
|
||||
const table = await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.createCollection(databaseId, id ? id : ID.unique(), name);
|
||||
.tables.create(databaseId, id ? id : ID.unique(), name);
|
||||
|
||||
showCreate = false;
|
||||
dispatch('created', collection);
|
||||
|
||||
dispatch('created', table);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${name} has been created`
|
||||
});
|
||||
name = id = null;
|
||||
trackEvent(Submit.CollectionCreate, {
|
||||
trackEvent(Submit.TableCreate, {
|
||||
customId: !!id
|
||||
});
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
trackError(e, Submit.CollectionCreate);
|
||||
trackError(e, Submit.TableCreate);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -51,11 +53,11 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal title="Create collection" size="m" bind:show={showCreate} onSubmit={create} bind:error>
|
||||
<Modal title="Create table" size="m" bind:show={showCreate} onSubmit={create} bind:error>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="Enter collection name"
|
||||
placeholder="Enter table name"
|
||||
bind:value={name}
|
||||
autofocus
|
||||
required />
|
||||
@@ -66,10 +68,10 @@
|
||||
size="s"
|
||||
on:click={() => {
|
||||
showCustomId = true;
|
||||
}}><Icon icon={IconPencil} /> Collection ID</Tag>
|
||||
}}><Icon icon={IconPencil} /> Table ID</Tag>
|
||||
</div>
|
||||
{/if}
|
||||
<CustomId autofocus bind:show={showCustomId} name="Collection" bind:id />
|
||||
<CustomId autofocus bind:show={showCustomId} name="Table" bind:id />
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (showCreate = false)}>Cancel</Button>
|
||||
+34
-34
@@ -18,17 +18,17 @@
|
||||
let confirmedDeletion = false;
|
||||
|
||||
let error = null;
|
||||
let isLoadingDocumentsCount = false;
|
||||
let isLoadingRowsCount = false;
|
||||
|
||||
$: collectionItems = [];
|
||||
let collections: Models.CollectionList = null;
|
||||
$: tableItems = [];
|
||||
let tables: Models.TableList = null;
|
||||
|
||||
function buildQueries(): string[] {
|
||||
const queries = [Query.orderDesc('$updatedAt')];
|
||||
|
||||
if (collectionItems.length > 0) {
|
||||
if (tableItems.length > 0) {
|
||||
queries.push(Query.limit(25));
|
||||
queries.push(Query.offset(collectionItems.length));
|
||||
queries.push(Query.offset(tableItems.length));
|
||||
} else {
|
||||
queries.push(Query.limit(3));
|
||||
}
|
||||
@@ -36,32 +36,32 @@
|
||||
return queries;
|
||||
}
|
||||
|
||||
async function listCollections() {
|
||||
async function listTables() {
|
||||
// let's just wait...
|
||||
if (isLoadingDocumentsCount) return;
|
||||
if (isLoadingRowsCount) return;
|
||||
|
||||
isLoadingDocumentsCount = true;
|
||||
isLoadingRowsCount = true;
|
||||
|
||||
try {
|
||||
const queries = buildQueries();
|
||||
|
||||
collections = await sdk
|
||||
tables = await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.listCollections(databaseId, queries);
|
||||
.tables.list(databaseId, queries);
|
||||
|
||||
const collectionPromises = collections.collections.map(async (collection) => {
|
||||
const tablePromises = tables.tables.map(async (table) => {
|
||||
return {
|
||||
id: collection.$id,
|
||||
name: collection.name,
|
||||
updatedAt: collection.$updatedAt
|
||||
id: table.$id,
|
||||
name: table.name,
|
||||
updatedAt: table.$updatedAt
|
||||
};
|
||||
});
|
||||
|
||||
collectionItems = [...collectionItems, ...(await Promise.all(collectionPromises))];
|
||||
tableItems = [...tableItems, ...(await Promise.all(tablePromises))];
|
||||
} catch (err) {
|
||||
error = true;
|
||||
} finally {
|
||||
isLoadingDocumentsCount = false;
|
||||
isLoadingRowsCount = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,24 +88,24 @@
|
||||
|
||||
/* reset data on modal close */
|
||||
$: if (!showDelete) {
|
||||
collections = null;
|
||||
collectionItems = [];
|
||||
tables = null;
|
||||
tableItems = [];
|
||||
} else {
|
||||
listCollections();
|
||||
listTables();
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal title="Delete database" bind:show={showDelete} onSubmit={handleDelete}>
|
||||
<p class="text" slot="description">
|
||||
{#if collectionItems.length > 0}
|
||||
The following collections and all data associated with <b>{$database.name}</b>, will be
|
||||
{#if tableItems.length > 0}
|
||||
The following tables and all data associated with <b>{$database.name}</b>, will be
|
||||
permanently deleted.
|
||||
{:else}
|
||||
Are you sure you want to delete <b>{$database.name}</b>?
|
||||
{/if}
|
||||
</p>
|
||||
|
||||
{#if isLoadingDocumentsCount}
|
||||
{#if isLoadingRowsCount}
|
||||
<div class="u-flex u-main-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
@@ -113,36 +113,36 @@
|
||||
<p class="text">
|
||||
Are you sure you want to delete <b>{$database.name}</b>?
|
||||
</p>
|
||||
{:else if collectionItems.length > 0}
|
||||
{:else if tableItems.length > 0}
|
||||
<div class="u-flex-vertical u-gap-16">
|
||||
<Table.Root columns={2} let:root>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
<Table.Header.Cell {root}>Collection</Table.Header.Cell>
|
||||
<Table.Header.Cell {root}>Table</Table.Header.Cell>
|
||||
<Table.Header.Cell {root}>Last Updated</Table.Header.Cell>
|
||||
</svelte:fragment>
|
||||
{#each collectionItems as collection}
|
||||
{#each tableItems as table}
|
||||
<Table.Row.Base {root}>
|
||||
<Table.Cell {root}>{collection.name}</Table.Cell>
|
||||
<Table.Cell {root}>{toLocaleDate(collection.updatedAt)}</Table.Cell>
|
||||
<Table.Cell {root}>{table.name}</Table.Cell>
|
||||
<Table.Cell {root}>{toLocaleDate(table.updatedAt)}</Table.Cell>
|
||||
</Table.Row.Base>
|
||||
{/each}
|
||||
</Table.Root>
|
||||
|
||||
{#if collectionItems.length < collections.total}
|
||||
{#if tableItems.length < tables.total}
|
||||
<div class="u-flex u-gap-16 u-cross-center">
|
||||
<button class="u-underline" on:click={listCollections} type="button">
|
||||
<button class="u-underline" on:click={listTables} type="button">
|
||||
Show more
|
||||
</button>
|
||||
|
||||
{#if isLoadingDocumentsCount}
|
||||
{#if isLoadingRowsCount}
|
||||
<div class="loader is-small"></div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if collectionItems.length > 25}
|
||||
{:else if tableItems.length > 25}
|
||||
<button
|
||||
class="u-underline"
|
||||
on:click={() => {
|
||||
collectionItems = collectionItems.slice(0, 3);
|
||||
tableItems = tableItems.slice(0, 3);
|
||||
}}
|
||||
type="button">
|
||||
Show less
|
||||
@@ -151,7 +151,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if !isLoadingDocumentsCount}
|
||||
{#if !isLoadingRowsCount}
|
||||
<p class="text" data-private>
|
||||
<b>
|
||||
Once deleted, this database and its backups cannot be restored. This action is
|
||||
@@ -162,7 +162,7 @@
|
||||
<div class="input-check-box-friction">
|
||||
<InputCheckbox
|
||||
required
|
||||
id="delete_policy"
|
||||
id="delete_database"
|
||||
bind:checked={confirmedDeletion}
|
||||
label="I understand and confirm" />
|
||||
</div>
|
||||
|
||||
+11
-11
@@ -2,9 +2,9 @@
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/state';
|
||||
import { CardContainer, GridItem1, Id } from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { canWriteCollections } from '$lib/stores/roles';
|
||||
import { canWriteTables } from '$lib/stores/roles';
|
||||
import type { PageData } from './$types';
|
||||
import { Badge } from '@appwrite.io/pink-svelte';
|
||||
export let data: PageData;
|
||||
export let showCreate = false;
|
||||
const projectId = page.params.project;
|
||||
@@ -12,20 +12,20 @@
|
||||
</script>
|
||||
|
||||
<CardContainer
|
||||
disableEmpty={!$canWriteCollections}
|
||||
total={data.collections.total}
|
||||
disableEmpty={!$canWriteTables}
|
||||
total={data.tables.total}
|
||||
on:click={() => (showCreate = true)}
|
||||
event="collection">
|
||||
{#each data.collections.collections as collection}
|
||||
event="table">
|
||||
{#each data.tables.tables as table}
|
||||
<GridItem1
|
||||
href={`${base}/project-${page.params.region}-${projectId}/databases/database-${databaseId}/collection-${collection.$id}`}>
|
||||
<svelte:fragment slot="title">{collection.name}</svelte:fragment>
|
||||
href={`${base}/project-${page.params.region}-${projectId}/databases/database-${databaseId}/table-${table.$id}`}>
|
||||
<svelte:fragment slot="title">{table.name}</svelte:fragment>
|
||||
<svelte:fragment slot="status">
|
||||
{#if !collection.enabled}
|
||||
<Pill>disabled</Pill>
|
||||
{#if !table.enabled}
|
||||
<Badge variant="secondary" content="disabled" />
|
||||
{/if}</svelte:fragment>
|
||||
|
||||
<Id value={collection.$id}>{collection.$id}</Id>
|
||||
<Id value={table.$id}>{table.$id}</Id>
|
||||
</GridItem1>
|
||||
{/each}
|
||||
</CardContainer>
|
||||
+2
-2
@@ -13,8 +13,8 @@
|
||||
const tabs = [
|
||||
{
|
||||
href: path,
|
||||
title: 'Collections',
|
||||
event: 'collections',
|
||||
title: 'Tables',
|
||||
event: 'tables',
|
||||
hasChildren: true
|
||||
},
|
||||
{
|
||||
|
||||
+15
-14
@@ -13,6 +13,7 @@
|
||||
import Delete from '../delete.svelte';
|
||||
import { database } from '../store';
|
||||
import { Query } from '@appwrite.io/console';
|
||||
import { Layout, Skeleton } from '@appwrite.io/pink-svelte';
|
||||
|
||||
let showDelete = false;
|
||||
let showError: false | 'name' | 'email' | 'password' = false;
|
||||
@@ -24,10 +25,10 @@
|
||||
databaseName ??= $database.name;
|
||||
});
|
||||
|
||||
async function loadCollectionCount() {
|
||||
async function loadTableCount() {
|
||||
const { total } = await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.listCollections($database.$id, [Query.limit(1)]);
|
||||
.tables.list($database.$id, [Query.limit(1)]);
|
||||
return total;
|
||||
}
|
||||
|
||||
@@ -95,21 +96,21 @@
|
||||
|
||||
<CardGrid>
|
||||
<svelte:fragment slot="title">Delete database</svelte:fragment>
|
||||
The database will be permanently deleted, including all collections within it. This action
|
||||
is irreversible.
|
||||
The database will be permanently deleted, including all tables within it. This action is
|
||||
irreversible.
|
||||
<svelte:fragment slot="aside">
|
||||
<BoxAvatar>
|
||||
<svelte:fragment slot="title">
|
||||
<h6 class="u-bold u-trim-1">{$database.name}</h6>
|
||||
<span class="u-flex u-gap-8">
|
||||
{#await loadCollectionCount()}
|
||||
<div class="loader is-small"></div>
|
||||
{:then count}
|
||||
{count}
|
||||
{/await}
|
||||
|
||||
Collections
|
||||
</span>
|
||||
<Layout.Stack direction="column" gap="xxs">
|
||||
<h6 class="u-bold u-trim-1">{$database.name}</h6>
|
||||
<Layout.Stack direction="row" gap="s">
|
||||
{#await loadTableCount()}
|
||||
<Skeleton variant="line" width="100%" height={19.5} />
|
||||
{:then count}
|
||||
{count} {count === 1 ? 'Table' : 'Tables'}
|
||||
{/await}
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</svelte:fragment>
|
||||
</BoxAvatar>
|
||||
</svelte:fragment>
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ import { derived, writable } from 'svelte/store';
|
||||
export const database = derived(page, ($page) => $page.data.database as Models.Database);
|
||||
export const showCreate = writable(false);
|
||||
|
||||
export const columns = writable<Column[]>([
|
||||
{ id: '$id', title: 'Collection ID', type: 'string', width: 200 },
|
||||
export const tableViewColumns = writable<Column[]>([
|
||||
{ id: '$id', title: 'Table ID', type: 'string', width: 200 },
|
||||
{ id: 'name', title: 'Name', type: 'string', width: { min: 120 } },
|
||||
{ id: '$createdAt', title: 'Created', type: 'datetime', width: { min: 120 } },
|
||||
{ id: '$updatedAt', title: 'Updated', type: 'datetime', width: { min: 120 } }
|
||||
|
||||
+11
-11
@@ -5,15 +5,15 @@
|
||||
|
||||
export async function submitString(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
tableId: string,
|
||||
key: string,
|
||||
data: Partial<Models.AttributeString>
|
||||
data: Partial<Models.ColumnString>
|
||||
) {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.createStringAttribute(
|
||||
.tables.createStringColumn(
|
||||
databaseId,
|
||||
collectionId,
|
||||
tableId,
|
||||
key,
|
||||
data.size,
|
||||
data.required,
|
||||
@@ -23,15 +23,15 @@
|
||||
}
|
||||
export async function updateString(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
data: Partial<Models.AttributeString>,
|
||||
tableId: string,
|
||||
data: Partial<Models.ColumnString>,
|
||||
originalKey?: string
|
||||
) {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.updateStringAttribute(
|
||||
.tables.updateStringColumn(
|
||||
databaseId,
|
||||
collectionId,
|
||||
tableId,
|
||||
originalKey,
|
||||
data.required,
|
||||
data.default,
|
||||
@@ -42,10 +42,10 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { InputChoice, InputNumber, InputText, InputTextarea } from '$lib/elements/forms';
|
||||
import { createConservative } from '$lib/helpers/stores';
|
||||
import { InputChoice, InputNumber, InputText, InputTextarea } from '$lib/elements/forms';
|
||||
|
||||
export let data: Partial<Models.AttributeString> = {
|
||||
export let data: Partial<Models.ColumnString> = {
|
||||
required: false,
|
||||
size: 0,
|
||||
default: null,
|
||||
@@ -67,7 +67,7 @@
|
||||
const {
|
||||
stores: { required, array },
|
||||
listen
|
||||
} = createConservative<Partial<Models.AttributeString>>({
|
||||
} = createConservative<Partial<Models.ColumnString>>({
|
||||
required: false,
|
||||
array: false,
|
||||
...data
|
||||
|
||||
+54
-52
@@ -1,10 +1,13 @@
|
||||
<script lang="ts" context="module">
|
||||
import { writable } from 'svelte/store';
|
||||
import type { Option } from './columns/store';
|
||||
|
||||
const createAttributeArgs = writable({
|
||||
showCreate: false,
|
||||
selectedOption: null as Option['name'] | null
|
||||
});
|
||||
|
||||
export const initCreateAttribute = (option: Option['name']) => {
|
||||
export const initCreateColumn = (option: Option['name']) => {
|
||||
createAttributeArgs.set({ showCreate: true, selectedOption: option });
|
||||
};
|
||||
|
||||
@@ -19,18 +22,16 @@
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { realtime } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { collection } from './store';
|
||||
import { table } from './store';
|
||||
import { addSubPanel, registerCommands, updateCommandGroupRanks } from '$lib/commandCenter';
|
||||
import CreateAttribute from './createAttribute.svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import type { Option } from './attributes/store';
|
||||
import { CreateAttributePanel } from '$lib/commandCenter/panels';
|
||||
import CreateAttribute from './createColumn.svelte';
|
||||
import { CreateColumnPanel } from '$lib/commandCenter/panels';
|
||||
import { database } from '../store';
|
||||
import { project } from '$routes/(console)/project-[region]-[project]/store';
|
||||
import { page } from '$app/state';
|
||||
import CreateIndex from './indexes/createIndex.svelte';
|
||||
import { base } from '$app/paths';
|
||||
import { canWriteCollections } from '$lib/stores/roles';
|
||||
import { canWriteTables } from '$lib/stores/roles';
|
||||
import { IconEye, IconLockClosed, IconPlus, IconPuzzle } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
onMount(() => {
|
||||
@@ -38,142 +39,143 @@
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.subscribe(['project', 'console'], (response) => {
|
||||
if (
|
||||
response.events.includes('databases.*.collections.*.attributes.*') ||
|
||||
response.events.includes('databases.*.collections.*.indexes.*')
|
||||
response.events.includes('databases.*.tables.*.columns.*') ||
|
||||
response.events.includes('databases.*.tables.*.indexes.*')
|
||||
) {
|
||||
invalidate(Dependencies.COLLECTION);
|
||||
invalidate(Dependencies.TABLE);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: use route ids instead of pathname
|
||||
$: $registerCommands([
|
||||
{
|
||||
label: 'Create document',
|
||||
keys: page.url.pathname.endsWith($collection.$id) ? ['c'] : ['c', 'd'],
|
||||
label: 'Create row',
|
||||
keys: page.url.pathname.endsWith($table.$id) ? ['t'] : ['t', 'd'],
|
||||
callback() {
|
||||
goto(
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${$database?.$id}/collection-${$collection?.$id}/create`
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${$database?.$id}/table-${$table?.$id}/create`
|
||||
);
|
||||
},
|
||||
icon: IconPlus,
|
||||
group: 'documents'
|
||||
group: 'rows'
|
||||
},
|
||||
{
|
||||
label: 'Create attribute',
|
||||
keys: page.url.pathname.endsWith('attributes') ? ['c'] : ['c', 'a'],
|
||||
label: 'Create column',
|
||||
keys: page.url.pathname.endsWith('columns') ? ['t'] : ['t', 'a'],
|
||||
callback() {
|
||||
addSubPanel(CreateAttributePanel);
|
||||
addSubPanel(CreateColumnPanel);
|
||||
},
|
||||
icon: IconPlus,
|
||||
group: 'attributes',
|
||||
disabled: !$canWriteCollections
|
||||
group: 'columns',
|
||||
disabled: !$canWriteTables
|
||||
},
|
||||
{
|
||||
label: 'Go to documents',
|
||||
label: 'Go to rows',
|
||||
keys: ['g', 'd'],
|
||||
callback() {
|
||||
goto(
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${$database?.$id}/collection-${$collection?.$id}`
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${$database?.$id}/table-${$table?.$id}`
|
||||
);
|
||||
},
|
||||
disabled: page.url.pathname.endsWith($collection.$id),
|
||||
group: 'collections'
|
||||
disabled: page.url.pathname.endsWith($table.$id),
|
||||
group: 'tables'
|
||||
},
|
||||
{
|
||||
label: 'Go to attributes',
|
||||
label: 'Go to columns',
|
||||
keys: ['g', 'a'],
|
||||
callback() {
|
||||
goto(
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${$database?.$id}/collection-${$collection?.$id}/attributes`
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${$database?.$id}/table-${$table?.$id}/columns`
|
||||
);
|
||||
},
|
||||
disabled: page.url.pathname.endsWith('attributes'),
|
||||
group: 'collections'
|
||||
disabled: page.url.pathname.endsWith('columns'),
|
||||
group: 'tables'
|
||||
},
|
||||
{
|
||||
label: 'Go to indexes',
|
||||
keys: ['g', 'i'],
|
||||
callback() {
|
||||
goto(
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${$database?.$id}/collection-${$collection?.$id}/indexes`
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${$database?.$id}/table-${$table?.$id}/indexes`
|
||||
);
|
||||
},
|
||||
disabled: page.url.pathname.endsWith('indexes'),
|
||||
group: 'collections'
|
||||
group: 'tables'
|
||||
},
|
||||
{
|
||||
label: 'Go to activity',
|
||||
keys: ['g', 'c'],
|
||||
callback() {
|
||||
goto(
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${$database?.$id}/collection-${$collection?.$id}/activity`
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${$database?.$id}/table-${$table?.$id}/activity`
|
||||
);
|
||||
},
|
||||
disabled: page.url.pathname.endsWith('activity'),
|
||||
group: 'collections'
|
||||
group: 'tables'
|
||||
},
|
||||
{
|
||||
label: 'Go to usage',
|
||||
keys: ['g', 'u'],
|
||||
callback() {
|
||||
goto(
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${$database?.$id}/collection-${$collection?.$id}/usage`
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${$database?.$id}/table-${$table?.$id}/usage`
|
||||
);
|
||||
},
|
||||
disabled: page.url.pathname.endsWith('usage'),
|
||||
group: 'collections'
|
||||
group: 'tables'
|
||||
},
|
||||
{
|
||||
label: 'Go to settings',
|
||||
keys: ['g', 's'],
|
||||
callback() {
|
||||
goto(
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${$database?.$id}/collection-${$collection?.$id}/settings`
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${$database?.$id}/table-${$table?.$id}/settings`
|
||||
);
|
||||
},
|
||||
disabled: page.url.pathname.endsWith('settings') || !$canWriteCollections,
|
||||
group: 'collections'
|
||||
disabled: page.url.pathname.endsWith('settings') || !$canWriteTables,
|
||||
group: 'tables'
|
||||
},
|
||||
{
|
||||
label: 'Display Name',
|
||||
async callback() {
|
||||
await goto(
|
||||
`${base}/project-${$project.region}-${$project.$id}/databases/database-${$database.$id}/collection-${$collection.$id}/settings#display-name`
|
||||
`${base}/project-${$project.region}-${$project.$id}/databases/database-${$database.$id}/table-${$table.$id}/settings#display-name`
|
||||
);
|
||||
},
|
||||
group: 'collections',
|
||||
group: 'tables',
|
||||
disabled:
|
||||
page.url.pathname.endsWith('display-name') ||
|
||||
page.url.pathname.endsWith('settings') ||
|
||||
!$canWriteCollections,
|
||||
!$canWriteTables,
|
||||
icon: IconEye
|
||||
},
|
||||
{
|
||||
label: 'Permissions',
|
||||
async callback() {
|
||||
await goto(
|
||||
`${base}/project-${$project.region}-${$project.$id}/databases/database-${$database.$id}/collection-${$collection.$id}/settings#permissions`
|
||||
`${base}/project-${$project.region}-${$project.$id}/databases/database-${$database.$id}/table-${$table.$id}/settings#permissions`
|
||||
);
|
||||
},
|
||||
group: 'collections',
|
||||
group: 'tables',
|
||||
disabled:
|
||||
page.url.pathname.endsWith('permissions') ||
|
||||
page.url.pathname.endsWith('settings') ||
|
||||
!$canWriteCollections,
|
||||
!$canWriteTables,
|
||||
icon: IconPuzzle
|
||||
},
|
||||
{
|
||||
label: 'Document security',
|
||||
label: 'Row security',
|
||||
async callback() {
|
||||
await goto(
|
||||
`${base}/project-${$project.region}-${$project.$id}/databases/database-${$database.$id}/collection-${$collection.$id}/settings#document-security`
|
||||
`${base}/project-${$project.region}-${$project.$id}/databases/database-${$database.$id}/table-${$table.$id}/settings#row-security`
|
||||
);
|
||||
},
|
||||
group: 'collections',
|
||||
group: 'tables',
|
||||
disabled:
|
||||
page.url.pathname.endsWith('document-security') ||
|
||||
page.url.pathname.endsWith('row-security') ||
|
||||
page.url.pathname.endsWith('settings') ||
|
||||
!$canWriteCollections,
|
||||
!$canWriteTables,
|
||||
icon: IconLockClosed
|
||||
},
|
||||
{
|
||||
@@ -184,20 +186,20 @@
|
||||
},
|
||||
icon: IconPlus,
|
||||
group: 'indexes',
|
||||
disabled: !$canWriteCollections
|
||||
disabled: !$canWriteTables
|
||||
}
|
||||
]);
|
||||
|
||||
$updateCommandGroupRanks({
|
||||
attributes: 1000,
|
||||
documents: 900,
|
||||
collections: 800,
|
||||
columns: 1000,
|
||||
rows: 900,
|
||||
tables: 800,
|
||||
indexes: 700
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$collection?.name ?? 'Collection'} - Appwrite</title>
|
||||
<title>{$table?.name ?? 'Table'} - Appwrite</title>
|
||||
</svelte:head>
|
||||
|
||||
<slot />
|
||||
+7
-8
@@ -7,22 +7,21 @@ import SubNavigation from './subNavigation.svelte';
|
||||
import { Query } from '@appwrite.io/console';
|
||||
|
||||
export const load: LayoutLoad = async ({ params, depends }) => {
|
||||
depends(Dependencies.COLLECTION);
|
||||
depends(Dependencies.TABLE);
|
||||
|
||||
const [table, allTables] = await Promise.all([
|
||||
sdk.forProject(params.region, params.project).tables.get(params.database, params.table),
|
||||
|
||||
const [collection, allCollections] = await Promise.all([
|
||||
sdk
|
||||
.forProject(params.region, params.project)
|
||||
.databases.getCollection(params.database, params.collection),
|
||||
sdk
|
||||
.forProject(params.region, params.project)
|
||||
.databases.listCollections(params.database, [Query.orderDesc(''), Query.limit(100)])
|
||||
.tables.list(params.database, [Query.orderDesc(''), Query.limit(100)])
|
||||
]);
|
||||
|
||||
return {
|
||||
header: Header,
|
||||
breadcrumbs: Breadcrumbs,
|
||||
subNavigation: SubNavigation,
|
||||
collection,
|
||||
allCollections
|
||||
table,
|
||||
allTables
|
||||
};
|
||||
};
|
||||
+61
-63
@@ -6,13 +6,13 @@
|
||||
import type { Column, ColumnType } from '$lib/helpers/types';
|
||||
import { Container } from '$lib/layout';
|
||||
import { preferences } from '$lib/stores/preferences';
|
||||
import { canWriteCollections, canWriteDocuments } from '$lib/stores/roles';
|
||||
import { canWriteTables, canWriteRows } from '$lib/stores/roles';
|
||||
import { Card, Icon, Layout, Empty as PinkEmpty } from '@appwrite.io/pink-svelte';
|
||||
import type { PageData } from './$types';
|
||||
import CreateAttributeDropdown from './attributes/createAttributeDropdown.svelte';
|
||||
import type { Option } from './attributes/store';
|
||||
import CreateAttribute from './createAttribute.svelte';
|
||||
import { collection, columns, isCsvImportInProgress } from './store';
|
||||
import CreateColumnDropdown from './columns/createColumnDropdown.svelte';
|
||||
import type { Option } from './columns/store';
|
||||
import CreateColumn from './createColumn.svelte';
|
||||
import { table, tableColumns, isCsvImportInProgress } from './store';
|
||||
import Table from './table.svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import FilePicker from '$lib/components/filePicker.svelte';
|
||||
@@ -28,26 +28,26 @@
|
||||
export let data: PageData;
|
||||
|
||||
let showImportCSV = false;
|
||||
let showCreateAttribute = false;
|
||||
let selectedAttribute: Option['name'] = null;
|
||||
let showCreateColumn = false;
|
||||
let selectedColumn: Option['name'] = null;
|
||||
|
||||
const filterColumns = writable<Column[]>([]);
|
||||
|
||||
$: selected = preferences.getCustomCollectionColumns(page.params.collection);
|
||||
$: selected = preferences.getCustomTableColumns(page.params.table);
|
||||
|
||||
$: columns.set(
|
||||
$collection.attributes.map((attribute) => ({
|
||||
id: attribute.key,
|
||||
title: attribute.key,
|
||||
type: attribute.type as ColumnType,
|
||||
hide: !!selected?.includes(attribute.key),
|
||||
array: attribute?.array,
|
||||
format: 'format' in attribute && attribute?.format === 'enum' ? attribute.format : null,
|
||||
elements: 'elements' in attribute ? attribute.elements : null
|
||||
$: tableColumns.set(
|
||||
$table.columns.map((column) => ({
|
||||
id: column.key,
|
||||
title: column.key,
|
||||
type: column.type as ColumnType,
|
||||
hide: !!selected?.includes(column.key),
|
||||
array: column?.array,
|
||||
format: 'format' in column && column?.format === 'enum' ? column.format : null,
|
||||
elements: 'elements' in column ? column.elements : null
|
||||
}))
|
||||
);
|
||||
$: filterColumns.set([
|
||||
...$columns,
|
||||
...$tableColumns,
|
||||
...['$id', '$createdAt', '$updatedAt'].map((id) => ({
|
||||
id,
|
||||
title: id,
|
||||
@@ -56,8 +56,8 @@
|
||||
}))
|
||||
]);
|
||||
|
||||
$: hasAttributes = !!$collection.attributes.length;
|
||||
$: hasValidAttributes = $collection?.attributes?.some((attr) => attr.status === 'available');
|
||||
$: hasColumns = !!$table.columns.length;
|
||||
$: hasValidColumns = $table?.columns?.some((col) => col.status === 'available');
|
||||
|
||||
async function onSelect(file: Models.File) {
|
||||
$isCsvImportInProgress = true;
|
||||
@@ -68,12 +68,12 @@
|
||||
.migrations.createCsvMigration(
|
||||
file.bucketId,
|
||||
file.$id,
|
||||
`${page.params.database}:${page.params.collection}`
|
||||
`${page.params.database}:${page.params.table}`
|
||||
);
|
||||
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Documents import from csv has started'
|
||||
message: 'Rows import from csv has started'
|
||||
});
|
||||
|
||||
trackEvent(Submit.DatabaseImportCsv);
|
||||
@@ -89,61 +89,61 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#key page.params.collection}
|
||||
{#key page.params.table}
|
||||
<Container>
|
||||
<Layout.Stack direction="column" gap="xl">
|
||||
<Layout.Stack direction="row" justifyContent="space-between">
|
||||
<Filters
|
||||
query={data.query}
|
||||
columns={filterColumns}
|
||||
disabled={!(hasAttributes && hasValidAttributes)}
|
||||
analyticsSource="database_documents" />
|
||||
disabled={!(hasColumns && hasValidColumns)}
|
||||
analyticsSource="database_rows" />
|
||||
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
|
||||
<ViewSelector view={data.view} {columns} hideView isCustomCollection />
|
||||
<ViewSelector view={data.view} columns={tableColumns} hideView isCustomTable />
|
||||
<Button
|
||||
secondary
|
||||
event={Click.DatabaseImportCsv}
|
||||
disabled={!(hasAttributes && hasValidAttributes)}
|
||||
disabled={!(hasColumns && hasValidColumns)}
|
||||
on:click={() => (showImportCSV = true)}>
|
||||
Import CSV
|
||||
</Button>
|
||||
{#if !$isSmallViewport}
|
||||
<Button
|
||||
disabled={!(hasAttributes && hasValidAttributes)}
|
||||
href={`${base}/project-${page.params.region}-${page.params.project}/databases/database-${page.params.database}/collection-${page.params.collection}/create`}
|
||||
event="create_document">
|
||||
disabled={!(hasColumns && hasValidColumns)}
|
||||
href={`${base}/project-${page.params.region}-${page.params.project}/databases/database-${page.params.database}/table-${page.params.table}/create`}
|
||||
event="create_row">
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Create document
|
||||
Create row
|
||||
</Button>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
{#if $isSmallViewport}
|
||||
<Button
|
||||
disabled={!(hasAttributes && hasValidAttributes)}
|
||||
href={`${base}/project-${page.params.region}-${page.params.project}/databases/database-${page.params.database}/collection-${page.params.collection}/create`}
|
||||
event="create_document">
|
||||
disabled={!(hasColumns && hasValidColumns)}
|
||||
href={`${base}/project-${page.params.region}-${page.params.project}/databases/database-${page.params.database}/table-${page.params.table}/create`}
|
||||
event="create_row">
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Create document
|
||||
Create row
|
||||
</Button>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
|
||||
{#if hasAttributes && hasValidAttributes}
|
||||
{#if data.documents.total}
|
||||
{#if hasColumns && hasValidColumns}
|
||||
{#if data.rows.total}
|
||||
<Table {data} />
|
||||
|
||||
<PaginationWithLimit
|
||||
name="Documents"
|
||||
name="Rows"
|
||||
limit={data.limit}
|
||||
offset={data.offset}
|
||||
total={data.documents.total} />
|
||||
total={data.rows.total} />
|
||||
{:else if $hasPageQueries}
|
||||
<EmptySearch hidePages>
|
||||
<div class="common-section">
|
||||
<div class="u-text-center common-section">
|
||||
<b class="body-text-2 u-bold">Sorry, we couldn't find any documents.</b>
|
||||
<p>There are no documents that match your filters.</p>
|
||||
<b class="body-text-2 u-bold">Sorry, we couldn't find any rows.</b>
|
||||
<p>There are no rows that match your filters.</p>
|
||||
</div>
|
||||
<div class="u-flex common-section u-main-center">
|
||||
<Button
|
||||
@@ -152,7 +152,7 @@
|
||||
queries.clearAll();
|
||||
queries.apply();
|
||||
trackEvent(Submit.FilterClear, {
|
||||
source: 'database_documents'
|
||||
source: 'database_rows'
|
||||
});
|
||||
}}>
|
||||
Clear filters
|
||||
@@ -162,24 +162,24 @@
|
||||
</EmptySearch>
|
||||
{:else}
|
||||
<Empty
|
||||
allowCreate={$canWriteDocuments}
|
||||
allowCreate={$canWriteRows}
|
||||
single
|
||||
href="https://appwrite.io/docs/products/databases/documents"
|
||||
target="document">
|
||||
href="https://appwrite.io/docs/products/databases/rows"
|
||||
target="row">
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
external
|
||||
href="https://appwrite.io/docs/products/databases/documents"
|
||||
href="https://appwrite.io/docs/products/databases/rows"
|
||||
text
|
||||
event="empty_documentation"
|
||||
size="s"
|
||||
ariaLabel="create document">Documentation</Button>
|
||||
ariaLabel="create row">Documentation</Button>
|
||||
<Button
|
||||
href={`${base}/project-${page.params.region}-${page.params.project}/databases/database-${page.params.database}/collection-${page.params.collection}/create`}
|
||||
href={`${base}/project-${page.params.region}-${page.params.project}/databases/database-${page.params.database}/table-${page.params.table}/create`}
|
||||
secondary
|
||||
disabled={!$canWriteDocuments}
|
||||
disabled={!$canWriteRows}
|
||||
size="s">
|
||||
Create document
|
||||
Create row
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</Empty>
|
||||
@@ -187,24 +187,24 @@
|
||||
{:else}
|
||||
<Card.Base padding="none">
|
||||
<PinkEmpty
|
||||
title="Create an attribute to get started."
|
||||
title="Create a column to get started."
|
||||
description="Need a hand? Learn more in our documentation.">
|
||||
<slot name="actions" slot="actions">
|
||||
<Button
|
||||
external
|
||||
href="https://appwrite.io/docs/products/databases/collections#attributes"
|
||||
href="https://appwrite.io/docs/products/databases/tables#columns"
|
||||
text
|
||||
event="empty_documentation"
|
||||
size="s">Documentation</Button>
|
||||
{#if $canWriteCollections}
|
||||
<CreateAttributeDropdown
|
||||
bind:selectedOption={selectedAttribute}
|
||||
bind:showCreate={showCreateAttribute}
|
||||
{#if $canWriteTables}
|
||||
<CreateColumnDropdown
|
||||
bind:selectedOption={selectedColumn}
|
||||
bind:showCreate={showCreateColumn}
|
||||
let:toggle>
|
||||
<Button secondary event="create_attribute" on:click={toggle}>
|
||||
Create attribute
|
||||
<Button secondary event="create_column" on:click={toggle}>
|
||||
Create column
|
||||
</Button>
|
||||
</CreateAttributeDropdown>
|
||||
</CreateColumnDropdown>
|
||||
{/if}
|
||||
</slot>
|
||||
</PinkEmpty>
|
||||
@@ -213,10 +213,8 @@
|
||||
</Container>
|
||||
{/key}
|
||||
|
||||
{#if showCreateAttribute}
|
||||
<CreateAttribute
|
||||
bind:showCreate={showCreateAttribute}
|
||||
bind:selectedOption={selectedAttribute} />
|
||||
{#if showCreateColumn}
|
||||
<CreateColumn bind:showCreate={showCreateColumn} bind:selectedOption={selectedColumn} />
|
||||
{/if}
|
||||
|
||||
{#if showImportCSV}
|
||||
+6
-7
@@ -4,11 +4,11 @@ import { sdk } from '$lib/stores/sdk';
|
||||
import { Query } from '@appwrite.io/console';
|
||||
import type { PageLoad } from './$types';
|
||||
import { queries, queryParamToMap } from '$lib/components/filters';
|
||||
import { buildWildcardAttributesQuery } from './document-[document]/attributes/store';
|
||||
import { buildWildcardColumnsQuery } from './row-[row]/columns/store';
|
||||
|
||||
export const load: PageLoad = async ({ params, depends, url, route, parent }) => {
|
||||
const { collection } = await parent();
|
||||
depends(Dependencies.DOCUMENTS);
|
||||
const { table } = await parent();
|
||||
depends(Dependencies.ROWS);
|
||||
|
||||
const page = getPage(url);
|
||||
const limit = getLimit(url, route, PAGE_LIMIT);
|
||||
@@ -18,7 +18,6 @@ export const load: PageLoad = async ({ params, depends, url, route, parent }) =>
|
||||
|
||||
const paramQueries = url.searchParams.get('query');
|
||||
const parsedQueries = queryParamToMap(paramQueries || '[]');
|
||||
|
||||
queries.set(parsedQueries);
|
||||
|
||||
return {
|
||||
@@ -26,14 +25,14 @@ export const load: PageLoad = async ({ params, depends, url, route, parent }) =>
|
||||
limit,
|
||||
view,
|
||||
query,
|
||||
documents: await sdk
|
||||
rows: await sdk
|
||||
.forProject(params.region, params.project)
|
||||
.databases.listDocuments(params.database, params.collection, [
|
||||
.tables.listRows(params.database, params.table, [
|
||||
Query.limit(limit),
|
||||
Query.offset(offset),
|
||||
Query.orderDesc(''),
|
||||
...parsedQueries.values(),
|
||||
...buildWildcardAttributesQuery(collection)
|
||||
...buildWildcardColumnsQuery(table)
|
||||
])
|
||||
};
|
||||
};
|
||||
File renamed without changes.
+1
-1
@@ -14,7 +14,7 @@ export const load: PageLoad = async ({ params, url, route }) => {
|
||||
limit,
|
||||
logs: await sdk
|
||||
.forProject(params.region, params.project)
|
||||
.databases.listCollectionLogs(params.database, params.collection, [
|
||||
.tables.listLogs(params.database, params.table, [
|
||||
Query.limit(limit),
|
||||
Query.offset(offset)
|
||||
])
|
||||
+3
-3
@@ -3,7 +3,7 @@
|
||||
import { page } from '$app/state';
|
||||
import { Breadcrumbs } from '$lib/layout';
|
||||
import { database } from '../store';
|
||||
import { collection } from './store';
|
||||
import { table } from './store';
|
||||
|
||||
$: breadcrumbs = [
|
||||
{
|
||||
@@ -18,8 +18,8 @@
|
||||
title: $database?.name
|
||||
},
|
||||
{
|
||||
href: `${base}/project-${page.params.region}-${page.params.project}/databases/database-${$database?.$id}/collection-${$collection?.$id}`,
|
||||
title: $collection?.name
|
||||
href: `${base}/project-${page.params.region}-${page.params.project}/databases/database-${$database?.$id}/table-${$table?.$id}`,
|
||||
title: $table?.name
|
||||
}
|
||||
];
|
||||
</script>
|
||||
+55
-55
@@ -4,7 +4,7 @@
|
||||
import { Empty } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import { canWriteCollections } from '$lib/stores/roles';
|
||||
import { canWriteTables } from '$lib/stores/roles';
|
||||
import {
|
||||
ActionMenu,
|
||||
Badge,
|
||||
@@ -16,15 +16,15 @@
|
||||
Tooltip,
|
||||
Typography
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import Create from '../createAttribute.svelte';
|
||||
import { isRelationship, isString } from '../document-[document]/attributes/store';
|
||||
import Create from '../createColumn.svelte';
|
||||
import { isRelationship, isString } from '../row-[row]/columns/store';
|
||||
import FailedModal from '../failedModal.svelte';
|
||||
import CreateIndex from '../indexes/createIndex.svelte';
|
||||
import { attributes, type Attributes, isCsvImportInProgress } from '../store';
|
||||
import CreateAttributeDropdown from './createAttributeDropdown.svelte';
|
||||
import Delete from './deleteAttribute.svelte';
|
||||
import { columns, type Columns, isCsvImportInProgress } from '../store';
|
||||
import CreateColumnDropdown from './createColumnDropdown.svelte';
|
||||
import Delete from './deleteColumn.svelte';
|
||||
import Edit from './edit.svelte';
|
||||
import { attributeOptions, type Option } from './store';
|
||||
import { columnOptions, type Option } from './store';
|
||||
import {
|
||||
IconArrowSmRight,
|
||||
IconDotsHorizontal,
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
let showDropdown = [];
|
||||
let selectedOption: Option['name'] = null;
|
||||
let selectedAttribute: Attributes = null;
|
||||
let selectedColumn: Columns = null;
|
||||
let showCreate = false;
|
||||
let showDelete = false;
|
||||
let showEdit = false;
|
||||
@@ -53,14 +53,14 @@
|
||||
let showFailed = false;
|
||||
let error = '';
|
||||
|
||||
const attributeFormatIcon = {
|
||||
const columnFormatIcon = {
|
||||
ip: IconLocationMarker,
|
||||
url: IconLink,
|
||||
email: IconLink,
|
||||
enum: IconViewList
|
||||
};
|
||||
|
||||
function getAttributeStatusBadge(status: string): ComponentProps<Badge>['type'] {
|
||||
function getColumnStatusBadge(status: string): ComponentProps<Badge>['type'] {
|
||||
switch (status) {
|
||||
case 'processing':
|
||||
return 'warning';
|
||||
@@ -76,13 +76,13 @@
|
||||
|
||||
<Container>
|
||||
<Layout.Stack direction="row" justifyContent="space-between">
|
||||
<Typography.Title>Attributes</Typography.Title>
|
||||
{#if $canWriteCollections}
|
||||
<CreateAttributeDropdown bind:selectedOption bind:showCreate />
|
||||
<Typography.Title>Columns</Typography.Title>
|
||||
{#if $canWriteTables}
|
||||
<CreateColumnDropdown bind:selectedOption bind:showCreate />
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
|
||||
{#if $attributes.length}
|
||||
{#if $columns.length}
|
||||
<Table.Root
|
||||
let:root
|
||||
columns={[
|
||||
@@ -97,27 +97,27 @@
|
||||
<Table.Header.Cell column="default" {root}>Default value</Table.Header.Cell>
|
||||
<Table.Header.Cell column="actions" {root} />
|
||||
</svelte:fragment>
|
||||
{#each $attributes as attribute, index}
|
||||
{@const option = attributeOptions.find((option) => option.type === attribute.type)}
|
||||
{#each $columns as column, index}
|
||||
{@const option = columnOptions.find((option) => option.type === column.type)}
|
||||
<Table.Row.Base {root}>
|
||||
<Table.Cell column="key" {root}>
|
||||
<Layout.Stack direction="row" alignItems="center">
|
||||
{#if isRelationship(attribute)}
|
||||
{#if isRelationship(column)}
|
||||
<Icon
|
||||
size="s"
|
||||
icon={attribute?.twoWay
|
||||
icon={column?.twoWay
|
||||
? IconSwitchHorizontal
|
||||
: IconArrowSmRight} />
|
||||
{:else if 'format' in attribute && attribute.format}
|
||||
{@const icon = attributeFormatIcon[attribute?.format]}
|
||||
{:else if 'format' in column && column.format}
|
||||
{@const icon = columnFormatIcon[column?.format]}
|
||||
<Icon {icon} size="s" />
|
||||
{:else}
|
||||
<Icon icon={option.icon} size="s" />
|
||||
{/if}
|
||||
<Layout.Stack direction="row" alignItems="center" gap="s">
|
||||
<Layout.Stack inline direction="row" alignItems="center" gap="xxs">
|
||||
<span class="text u-trim-1" data-private>{attribute.key}</span>
|
||||
{#if isString(attribute) && attribute.encrypt}
|
||||
<span class="text u-trim-1" data-private>{column.key}</span>
|
||||
{#if isString(column) && column.encrypt}
|
||||
<Tooltip>
|
||||
<Icon
|
||||
size="s"
|
||||
@@ -127,49 +127,49 @@
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{#if attribute.status !== 'available'}
|
||||
{#if column.status !== 'available'}
|
||||
<Badge
|
||||
size="s"
|
||||
variant="secondary"
|
||||
content={attribute.status}
|
||||
type={getAttributeStatusBadge(attribute.status)} />
|
||||
{#if attribute.error}
|
||||
content={column.status}
|
||||
type={getColumnStatusBadge(column.status)} />
|
||||
{#if column.error}
|
||||
<Link.Button
|
||||
variant="muted"
|
||||
on:click={(e) => {
|
||||
e.preventDefault();
|
||||
error = attribute.error;
|
||||
error = column.error;
|
||||
showFailed = true;
|
||||
}}>Details</Link.Button>
|
||||
{/if}
|
||||
{:else if attribute.required}
|
||||
{:else if column.required}
|
||||
<Badge size="xs" variant="secondary" content="required" />
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Table.Cell>
|
||||
<Table.Cell column="type" {root}>
|
||||
{#if 'format' in attribute && attribute.format}
|
||||
<span class="u-capitalize">{attribute.format}</span>
|
||||
{#if 'format' in column && column.format}
|
||||
<span class="u-capitalize">{column.format}</span>
|
||||
{:else}
|
||||
<p>
|
||||
<span class="u-capitalize">{attribute.type}</span>
|
||||
{#if isRelationship(attribute)}
|
||||
<span class="u-capitalize">{column.type}</span>
|
||||
{#if isRelationship(column)}
|
||||
<span>
|
||||
with <a
|
||||
href={`${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}/collection-${attribute?.relatedCollection}`}
|
||||
><b data-private>{attribute?.key}</b></a>
|
||||
href={`${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}/table-${column?.relatedTable}`}
|
||||
><b data-private>{column?.key}</b></a>
|
||||
</span>
|
||||
{/if}
|
||||
</p>
|
||||
{/if}
|
||||
<span>
|
||||
{attribute.array ? '[]' : ''}
|
||||
{column.array ? '[]' : ''}
|
||||
</span>
|
||||
</Table.Cell>
|
||||
<Table.Cell column="default" {root}>
|
||||
{attribute?.default !== null && attribute?.default !== undefined
|
||||
? attribute?.default
|
||||
{column?.default !== null && column?.default !== undefined
|
||||
? column?.default
|
||||
: '-'}
|
||||
</Table.Cell>
|
||||
<Table.Cell column="actions" {root}>
|
||||
@@ -190,32 +190,32 @@
|
||||
on:click={(event) => {
|
||||
toggle(event);
|
||||
showEdit = true;
|
||||
selectedAttribute = attribute;
|
||||
selectedColumn = column;
|
||||
showDropdown[index] = false;
|
||||
}}>
|
||||
Update
|
||||
</ActionMenu.Item.Button>
|
||||
{#if !isRelationship(attribute)}
|
||||
{#if !isRelationship(column)}
|
||||
<ActionMenu.Item.Button
|
||||
leadingIcon={IconPlus}
|
||||
on:click={(event) => {
|
||||
toggle(event);
|
||||
selectedAttribute = attribute;
|
||||
selectedColumn = column;
|
||||
showCreateIndex = true;
|
||||
showDropdown[index] = false;
|
||||
}}>
|
||||
Create index
|
||||
</ActionMenu.Item.Button>
|
||||
{/if}
|
||||
{#if attribute.status !== 'processing'}
|
||||
{#if column.status !== 'processing'}
|
||||
<ActionMenu.Item.Button
|
||||
leadingIcon={IconTrash}
|
||||
on:click={(event) => {
|
||||
toggle(event);
|
||||
showDelete = true;
|
||||
showDropdown[index] = false;
|
||||
selectedAttribute = attribute;
|
||||
trackEvent(Click.DatabaseAttributeDelete);
|
||||
selectedColumn = column;
|
||||
trackEvent(Click.DatabaseColumnDelete);
|
||||
}}>
|
||||
Delete
|
||||
</ActionMenu.Item.Button>
|
||||
@@ -228,20 +228,20 @@
|
||||
{/each}
|
||||
</Table.Root>
|
||||
{:else}
|
||||
<Empty single target="attribute">
|
||||
<Empty single target="column">
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
external
|
||||
href="https://appwrite.io/docs/products/databases/collections#attributes"
|
||||
href="https://appwrite.io/docs/products/databases/tables#columns"
|
||||
text
|
||||
event="empty_documentation"
|
||||
ariaLabel={`create {target}`}>Documentation</Button>
|
||||
{#if $canWriteCollections}
|
||||
<CreateAttributeDropdown bind:selectedOption bind:showCreate let:toggle>
|
||||
<Button secondary event="create_attribute" on:click={toggle}>
|
||||
Create attribute
|
||||
ariaLabel={'create column'}>Documentation</Button>
|
||||
{#if $canWriteTables}
|
||||
<CreateColumnDropdown bind:selectedOption bind:showCreate let:toggle>
|
||||
<Button secondary event="create_column" on:click={toggle}>
|
||||
Create column
|
||||
</Button>
|
||||
</CreateAttributeDropdown>
|
||||
</CreateColumnDropdown>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Empty>
|
||||
@@ -252,14 +252,14 @@
|
||||
<Create bind:showCreate bind:selectedOption />
|
||||
{/if}
|
||||
{#if showDelete}
|
||||
<Delete bind:showDelete {selectedAttribute} />
|
||||
<Delete bind:showDelete {selectedColumn} />
|
||||
{/if}
|
||||
{#if showEdit}
|
||||
<Edit bind:showEdit {selectedAttribute} />
|
||||
<Edit bind:showEdit {selectedColumn} />
|
||||
{/if}
|
||||
{#if showCreateIndex}
|
||||
<CreateIndex bind:showCreateIndex externalAttribute={selectedAttribute} />
|
||||
<CreateIndex bind:showCreateIndex externalColumn={selectedColumn} />
|
||||
{/if}
|
||||
{#if showFailed}
|
||||
<FailedModal bind:show={showFailed} title="Create attribute" header="Creation failed" {error} />
|
||||
<FailedModal bind:show={showFailed} title="Create column" header="Creation failed" {error} />
|
||||
{/if}
|
||||
File renamed without changes.
File renamed without changes.
+10
-10
@@ -5,15 +5,15 @@
|
||||
|
||||
export async function submitBoolean(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
tableId: string,
|
||||
key: string,
|
||||
data: Partial<Models.AttributeBoolean>
|
||||
data: Partial<Models.ColumnBoolean>
|
||||
) {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.createBooleanAttribute(
|
||||
.tables.createBooleanColumn(
|
||||
databaseId,
|
||||
collectionId,
|
||||
tableId,
|
||||
key,
|
||||
data.required,
|
||||
data.default,
|
||||
@@ -22,15 +22,15 @@
|
||||
}
|
||||
export async function updateBoolean(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
data: Partial<Models.AttributeBoolean>,
|
||||
tableId: string,
|
||||
data: Partial<Models.ColumnBoolean>,
|
||||
originalKey?: string
|
||||
) {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.updateBooleanAttribute(
|
||||
.tables.updateBooleanColumn(
|
||||
databaseId,
|
||||
collectionId,
|
||||
tableId,
|
||||
originalKey,
|
||||
data.required,
|
||||
data.default,
|
||||
@@ -43,7 +43,7 @@
|
||||
import { InputSelect } from '$lib/elements/forms';
|
||||
|
||||
export let editing = false;
|
||||
export let data: Partial<Models.AttributeBoolean> = {
|
||||
export let data: Partial<Models.ColumnBoolean> = {
|
||||
required: false,
|
||||
array: false,
|
||||
default: null
|
||||
@@ -66,7 +66,7 @@
|
||||
const {
|
||||
stores: { required, array },
|
||||
listen
|
||||
} = createConservative<Partial<Models.AttributeBoolean>>({
|
||||
} = createConservative<Partial<Models.ColumnBoolean>>({
|
||||
required: false,
|
||||
array: false,
|
||||
...data
|
||||
+4
-4
@@ -1,13 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { ActionMenu, Icon, Popover } from '@appwrite.io/pink-svelte';
|
||||
import { attributeOptions, type Option } from './store';
|
||||
import { columnOptions, type Option } from './store';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import CsvDisabled from '../csvDisabled.svelte';
|
||||
import { isCsvImportInProgress } from '../store';
|
||||
|
||||
export let selectedOption: Option['name'] = null;
|
||||
export let showCreate = false;
|
||||
export let selectedOption: Option['name'] = null;
|
||||
</script>
|
||||
|
||||
{#if $isCsvImportInProgress}
|
||||
@@ -22,11 +22,11 @@
|
||||
<slot {toggle}>
|
||||
<Button on:click={toggle} event="create_attribute">
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Create attribute
|
||||
Create column
|
||||
</Button>
|
||||
</slot>
|
||||
<ActionMenu.Root slot="tooltip">
|
||||
{#each attributeOptions as attribute}
|
||||
{#each columnOptions as attribute}
|
||||
<ActionMenu.Item.Button
|
||||
leadingIcon={attribute.icon}
|
||||
on:click={() => {
|
||||
+10
-10
@@ -5,15 +5,15 @@
|
||||
|
||||
export async function submitDatetime(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
tableId: string,
|
||||
key: string,
|
||||
data: Partial<Models.AttributeDatetime>
|
||||
data: Partial<Models.ColumnDatetime>
|
||||
) {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.createDatetimeAttribute(
|
||||
.tables.createDatetimeColumn(
|
||||
databaseId,
|
||||
collectionId,
|
||||
tableId,
|
||||
key,
|
||||
data.required,
|
||||
data.default,
|
||||
@@ -23,15 +23,15 @@
|
||||
|
||||
export async function updateDatetime(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
data: Partial<Models.AttributeDatetime>,
|
||||
tableId: string,
|
||||
data: Partial<Models.ColumnDatetime>,
|
||||
originalKey?: string
|
||||
) {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.updateDatetimeAttribute(
|
||||
.tables.updateDatetimeColumn(
|
||||
databaseId,
|
||||
collectionId,
|
||||
tableId,
|
||||
originalKey,
|
||||
data.required,
|
||||
data.default,
|
||||
@@ -44,7 +44,7 @@
|
||||
import { InputDateTime } from '$lib/elements/forms';
|
||||
|
||||
export let editing = false;
|
||||
export let data: Partial<Models.AttributeDatetime>;
|
||||
export let data: Partial<Models.ColumnDatetime>;
|
||||
|
||||
import { createConservative } from '$lib/helpers/stores';
|
||||
import { Selector } from '@appwrite.io/pink-svelte';
|
||||
@@ -63,7 +63,7 @@
|
||||
const {
|
||||
stores: { required, array },
|
||||
listen
|
||||
} = createConservative<Partial<Models.AttributeDatetime>>({
|
||||
} = createConservative<Partial<Models.ColumnDatetime>>({
|
||||
required: false,
|
||||
array: false,
|
||||
...data
|
||||
+17
-17
@@ -4,15 +4,16 @@
|
||||
import { page } from '$app/state';
|
||||
import { InputChoice } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { collection } from '../store';
|
||||
import type { Attributes } from '../store';
|
||||
import { table } from '../store';
|
||||
import type { Columns } from '../store';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { isRelationship } from '../document-[document]/attributes/store';
|
||||
import { isRelationship } from '../row-[row]/columns/store';
|
||||
import Confirm from '$lib/components/confirm.svelte';
|
||||
|
||||
export let showDelete = false;
|
||||
export let selectedAttribute: Attributes;
|
||||
export let selectedColumn: Columns;
|
||||
|
||||
const databaseId = page.params.database;
|
||||
|
||||
let error: string;
|
||||
@@ -22,38 +23,37 @@
|
||||
try {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.deleteAttribute(databaseId, $collection.$id, selectedAttribute.key);
|
||||
.tables.deleteColumn(databaseId, $table.$id, selectedColumn.key);
|
||||
|
||||
showDelete = false;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `Attribute has been deleted`
|
||||
message: `Column has been deleted`
|
||||
});
|
||||
trackEvent(Submit.AttributeDelete);
|
||||
trackEvent(Submit.ColumnDelete);
|
||||
await goto(
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}/collection-${page.params.collection}/attributes`
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}/table-${page.params.table}/columns`
|
||||
);
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
trackError(e, Submit.AttributeDelete);
|
||||
trackError(e, Submit.ColumnDelete);
|
||||
}
|
||||
}
|
||||
|
||||
$: isDeleteBtnDisabled =
|
||||
isRelationship(selectedAttribute) && selectedAttribute?.twoWay && !checked;
|
||||
$: isDeleteBtnDisabled = isRelationship(selectedColumn) && selectedColumn?.twoWay && !checked;
|
||||
</script>
|
||||
|
||||
<Confirm
|
||||
bind:open={showDelete}
|
||||
onSubmit={handleDelete}
|
||||
title="Delete attribute"
|
||||
title="Delete column"
|
||||
bind:error
|
||||
disabled={isDeleteBtnDisabled}>
|
||||
<p>
|
||||
Are you sure you want to delete <b data-private>{selectedAttribute?.key}</b> from
|
||||
<b data-private>{$collection?.name}</b>?
|
||||
Are you sure you want to delete <b data-private>{selectedColumn?.key}</b> from
|
||||
<b data-private>{$table?.name}</b>?
|
||||
</p>
|
||||
{#if isRelationship(selectedAttribute) && selectedAttribute?.twoWay}
|
||||
{#if isRelationship(selectedColumn) && selectedColumn?.twoWay}
|
||||
<div class="u-flex u-flex-vertical u-gap-24">
|
||||
<p>
|
||||
This is a two way relationship and the corresponding relationship will also be
|
||||
@@ -62,8 +62,8 @@
|
||||
<p><b>This action is irreversible.</b></p>
|
||||
<ul>
|
||||
<InputChoice id="delete" label="Delete" showLabel={false} bind:value={checked}>
|
||||
Delete relationship between <b data-private>{selectedAttribute.key}</b> to
|
||||
<b data-private>{selectedAttribute.twoWayKey}</b>
|
||||
Delete relationship between <b data-private>{selectedColumn.key}</b> to
|
||||
<b data-private>{selectedColumn.twoWayKey}</b>
|
||||
</InputChoice>
|
||||
</ul>
|
||||
</div>
|
||||
+27
-27
@@ -8,60 +8,60 @@
|
||||
import { Button, InputText } from '$lib/elements/forms';
|
||||
import deepEqual from 'deep-equal';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import type { Attributes } from '../store';
|
||||
import { attributeOptions, type Option } from './store';
|
||||
import type { Columns } from '../store';
|
||||
import { columnOptions, type Option } from './store';
|
||||
|
||||
export let showEdit = false;
|
||||
export let selectedAttribute: Attributes;
|
||||
export let selectedColumn: Columns;
|
||||
|
||||
const databaseId = page.params.database;
|
||||
const collectionId = page.params.collection;
|
||||
const tableId = page.params.table;
|
||||
let originalKey = '';
|
||||
|
||||
let error: string;
|
||||
let currentAttr: Attributes;
|
||||
let currentColumn: Columns;
|
||||
|
||||
$: option = attributeOptions.find((option) => {
|
||||
if (selectedAttribute) {
|
||||
if ('format' in selectedAttribute && selectedAttribute.format) {
|
||||
return option?.format === selectedAttribute?.format;
|
||||
$: option = columnOptions.find((option) => {
|
||||
if (selectedColumn) {
|
||||
if ('format' in selectedColumn && selectedColumn.format) {
|
||||
return option?.format === selectedColumn?.format;
|
||||
} else {
|
||||
return option?.type === selectedAttribute?.type;
|
||||
return option?.type === selectedColumn?.type;
|
||||
}
|
||||
}
|
||||
}) as Option;
|
||||
|
||||
async function submit() {
|
||||
try {
|
||||
await option.update(databaseId, collectionId, selectedAttribute, originalKey);
|
||||
await invalidate(Dependencies.COLLECTION);
|
||||
if (!page.url.pathname.includes('attributes')) {
|
||||
await option.update(databaseId, tableId, selectedColumn, originalKey);
|
||||
await invalidate(Dependencies.TABLE);
|
||||
if (!page.url.pathname.includes('columns')) {
|
||||
await goto(
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}/collection-${collectionId}/attributes`
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}/table-${tableId}/columns`
|
||||
);
|
||||
}
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `Attribute ${selectedAttribute.key} has been updated`
|
||||
message: `Column ${selectedColumn.key} has been updated`
|
||||
});
|
||||
showEdit = false;
|
||||
trackEvent(Submit.AttributeUpdate);
|
||||
trackEvent(Submit.ColumnUpdate);
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
trackError(e, Submit.AttributeUpdate);
|
||||
trackError(e, Submit.ColumnUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
$: onShow(showEdit);
|
||||
$: title = `Update ${attributeOptions.find((v) => v.name === option.name)?.sentenceName ?? ''} attribute`;
|
||||
$: title = `Update ${columnOptions.find((v) => v.name === option.name)?.sentenceName ?? ''} attribute`;
|
||||
|
||||
function onShow(show: boolean) {
|
||||
if (show) {
|
||||
currentAttr ??= { ...selectedAttribute };
|
||||
originalKey = currentAttr.key;
|
||||
currentColumn ??= { ...selectedColumn };
|
||||
originalKey = currentColumn.key;
|
||||
error = null;
|
||||
} else {
|
||||
currentAttr = null;
|
||||
currentColumn = null;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -78,26 +78,26 @@
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
{#if selectedAttribute}
|
||||
{#if selectedAttribute?.type !== 'relationship'}
|
||||
{#if selectedColumn}
|
||||
{#if selectedColumn?.type !== 'relationship'}
|
||||
<InputText
|
||||
id="key"
|
||||
label="Attribute Key"
|
||||
label="Column Key"
|
||||
placeholder="Enter Key"
|
||||
bind:value={selectedAttribute.key}
|
||||
bind:value={selectedColumn.key}
|
||||
autofocus />
|
||||
{/if}
|
||||
{#if option}
|
||||
<svelte:component
|
||||
this={option.component}
|
||||
editing
|
||||
bind:data={selectedAttribute}
|
||||
bind:data={selectedColumn}
|
||||
onclose={() => (option = null)} />
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (showEdit = false)}>Cancel</Button>
|
||||
<Button submit disabled={deepEqual(currentAttr, selectedAttribute)}>Update</Button>
|
||||
<Button submit disabled={deepEqual(currentColumn, selectedColumn)}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
+10
-10
@@ -5,15 +5,15 @@
|
||||
|
||||
export async function submitEmail(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
tableId: string,
|
||||
key: string,
|
||||
data: Partial<Models.AttributeEmail>
|
||||
data: Partial<Models.ColumnEmail>
|
||||
) {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.createEmailAttribute(
|
||||
.tables.createEmailColumn(
|
||||
databaseId,
|
||||
collectionId,
|
||||
tableId,
|
||||
key,
|
||||
data.required,
|
||||
data.default,
|
||||
@@ -23,15 +23,15 @@
|
||||
|
||||
export async function updateEmail(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
data: Partial<Models.AttributeEmail>,
|
||||
tableId: string,
|
||||
data: Partial<Models.ColumnEmail>,
|
||||
originalKey?: string
|
||||
) {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.updateEmailAttribute(
|
||||
.tables.updateEmailColumn(
|
||||
databaseId,
|
||||
collectionId,
|
||||
tableId,
|
||||
originalKey,
|
||||
data.required,
|
||||
data.default,
|
||||
@@ -44,7 +44,7 @@
|
||||
import { InputEmail } from '$lib/elements/forms';
|
||||
|
||||
export let editing = false;
|
||||
export let data: Partial<Models.AttributeEmail>;
|
||||
export let data: Partial<Models.ColumnEmail>;
|
||||
|
||||
import { createConservative } from '$lib/helpers/stores';
|
||||
import { Selector } from '@appwrite.io/pink-svelte';
|
||||
@@ -63,7 +63,7 @@
|
||||
const {
|
||||
stores: { required, array },
|
||||
listen
|
||||
} = createConservative<Partial<Models.AttributeEmail>>({
|
||||
} = createConservative<Partial<Models.ColumnEmail>>({
|
||||
required: false,
|
||||
array: false,
|
||||
...data
|
||||
+10
-10
@@ -5,15 +5,15 @@
|
||||
|
||||
export async function submitEnum(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
tableId: string,
|
||||
key: string,
|
||||
data: Partial<Models.AttributeEnum>
|
||||
data: Partial<Models.ColumnEnum>
|
||||
) {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.createEnumAttribute(
|
||||
.tables.createEnumColumn(
|
||||
databaseId,
|
||||
collectionId,
|
||||
tableId,
|
||||
key,
|
||||
data.elements,
|
||||
data.required,
|
||||
@@ -24,15 +24,15 @@
|
||||
|
||||
export async function updateEnum(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
data: Partial<Models.AttributeEnum>,
|
||||
tableId: string,
|
||||
data: Partial<Models.ColumnEnum>,
|
||||
originalKey?: string
|
||||
) {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.updateEnumAttribute(
|
||||
.tables.updateEnumColumn(
|
||||
databaseId,
|
||||
collectionId,
|
||||
tableId,
|
||||
originalKey,
|
||||
data.elements,
|
||||
data.required,
|
||||
@@ -46,7 +46,7 @@
|
||||
import { InputSelect, InputTags } from '$lib/elements/forms';
|
||||
|
||||
export let editing = false;
|
||||
export let data: Partial<Models.AttributeEnum>;
|
||||
export let data: Partial<Models.ColumnEnum>;
|
||||
|
||||
import { createConservative } from '$lib/helpers/stores';
|
||||
import { Icon, Selector, Tooltip, Typography } from '@appwrite.io/pink-svelte';
|
||||
@@ -66,7 +66,7 @@
|
||||
const {
|
||||
stores: { required, array },
|
||||
listen
|
||||
} = createConservative<Partial<Models.AttributeEnum>>({
|
||||
} = createConservative<Partial<Models.ColumnEnum>>({
|
||||
required: false,
|
||||
array: false,
|
||||
...data
|
||||
+10
-10
@@ -5,15 +5,15 @@
|
||||
|
||||
export async function submitFloat(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
tableId: string,
|
||||
key: string,
|
||||
data: Partial<Models.AttributeFloat>
|
||||
data: Partial<Models.ColumnFloat>
|
||||
) {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.createFloatAttribute(
|
||||
.tables.createFloatColumn(
|
||||
databaseId,
|
||||
collectionId,
|
||||
tableId,
|
||||
key,
|
||||
data.required,
|
||||
data.min,
|
||||
@@ -25,15 +25,15 @@
|
||||
|
||||
export async function updateFloat(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
data: Partial<Models.AttributeFloat>,
|
||||
tableId: string,
|
||||
data: Partial<Models.ColumnFloat>,
|
||||
originalKey?: string
|
||||
) {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.updateFloatAttribute(
|
||||
.tables.updateFloatColumn(
|
||||
databaseId,
|
||||
collectionId,
|
||||
tableId,
|
||||
originalKey,
|
||||
data.required,
|
||||
data.default,
|
||||
@@ -48,7 +48,7 @@
|
||||
import { InputNumber } from '$lib/elements/forms';
|
||||
|
||||
export let editing = false;
|
||||
export let data: Partial<Models.AttributeFloat> = {
|
||||
export let data: Partial<Models.ColumnFloat> = {
|
||||
required: false,
|
||||
min: 0,
|
||||
max: 0,
|
||||
@@ -73,7 +73,7 @@
|
||||
const {
|
||||
stores: { required, array },
|
||||
listen
|
||||
} = createConservative<Partial<Models.AttributeFloat>>({
|
||||
} = createConservative<Partial<Models.ColumnFloat>>({
|
||||
required: false,
|
||||
array: false,
|
||||
...data
|
||||
+10
-10
@@ -5,15 +5,15 @@
|
||||
|
||||
export async function submitInteger(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
tableId: string,
|
||||
key: string,
|
||||
data: Partial<Models.AttributeInteger>
|
||||
data: Partial<Models.ColumnInteger>
|
||||
) {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.createIntegerAttribute(
|
||||
.tables.createIntegerColumn(
|
||||
databaseId,
|
||||
collectionId,
|
||||
tableId,
|
||||
key,
|
||||
data.required,
|
||||
data.min,
|
||||
@@ -25,15 +25,15 @@
|
||||
|
||||
export async function updateInteger(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
data: Partial<Models.AttributeInteger>,
|
||||
tableId: string,
|
||||
data: Partial<Models.ColumnInteger>,
|
||||
originalKey?: string
|
||||
) {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.updateIntegerAttribute(
|
||||
.tables.updateIntegerColumn(
|
||||
databaseId,
|
||||
collectionId,
|
||||
tableId,
|
||||
originalKey,
|
||||
data.required,
|
||||
data.default,
|
||||
@@ -49,7 +49,7 @@
|
||||
|
||||
export let editing = false;
|
||||
|
||||
export let data: Partial<Models.AttributeInteger> = {
|
||||
export let data: Partial<Models.ColumnInteger> = {
|
||||
required: false,
|
||||
min: 0,
|
||||
max: 0,
|
||||
@@ -74,7 +74,7 @@
|
||||
const {
|
||||
stores: { required, array },
|
||||
listen
|
||||
} = createConservative<Partial<Models.AttributeInteger>>({
|
||||
} = createConservative<Partial<Models.ColumnInteger>>({
|
||||
required: false,
|
||||
array: false,
|
||||
...data
|
||||
+10
-10
@@ -5,15 +5,15 @@
|
||||
|
||||
export async function submitIp(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
tableId: string,
|
||||
key: string,
|
||||
data: Partial<Models.AttributeIp>
|
||||
data: Partial<Models.ColumnIp>
|
||||
) {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.createIpAttribute(
|
||||
.tables.createIpColumn(
|
||||
databaseId,
|
||||
collectionId,
|
||||
tableId,
|
||||
key,
|
||||
data.required,
|
||||
data.default,
|
||||
@@ -22,15 +22,15 @@
|
||||
}
|
||||
export async function updateIp(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
data: Partial<Models.AttributeIp>,
|
||||
tableId: string,
|
||||
data: Partial<Models.ColumnIp>,
|
||||
originalKey?: string
|
||||
) {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.updateIpAttribute(
|
||||
.tables.updateIpColumn(
|
||||
databaseId,
|
||||
collectionId,
|
||||
tableId,
|
||||
originalKey,
|
||||
data.required,
|
||||
data.default,
|
||||
@@ -43,7 +43,7 @@
|
||||
import { InputText } from '$lib/elements/forms';
|
||||
|
||||
export let editing = false;
|
||||
export let data: Partial<Models.AttributeIp>;
|
||||
export let data: Partial<Models.ColumnIp>;
|
||||
|
||||
import { createConservative } from '$lib/helpers/stores';
|
||||
import { Selector } from '@appwrite.io/pink-svelte';
|
||||
@@ -62,7 +62,7 @@
|
||||
const {
|
||||
stores: { required, array },
|
||||
listen
|
||||
} = createConservative<Partial<Models.AttributeIp>>({
|
||||
} = createConservative<Partial<Models.ColumnIp>>({
|
||||
required: false,
|
||||
array: false,
|
||||
...data
|
||||
+44
-46
@@ -6,9 +6,9 @@
|
||||
|
||||
export async function submitRelationship(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
tableId: string,
|
||||
_key: string,
|
||||
data: Partial<Models.AttributeRelationship>
|
||||
data: Partial<Models.ColumnRelationship>
|
||||
) {
|
||||
if (!isValueOfStringEnum(RelationshipType, data.relationType)) {
|
||||
throw new Error(
|
||||
@@ -21,10 +21,10 @@
|
||||
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.createRelationshipAttribute(
|
||||
.tables.createRelationshipColumn(
|
||||
databaseId,
|
||||
collectionId,
|
||||
data.relatedCollection,
|
||||
tableId,
|
||||
data.relatedTable,
|
||||
data.relationType,
|
||||
data.twoWay,
|
||||
data.key,
|
||||
@@ -35,8 +35,8 @@
|
||||
|
||||
export async function updateRelationship(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
data: Partial<Models.AttributeRelationship>,
|
||||
tableId: string,
|
||||
data: Partial<Models.ColumnRelationship>,
|
||||
originalKey?: string
|
||||
) {
|
||||
if (!isValueOfStringEnum(RelationMutate, data.onDelete)) {
|
||||
@@ -45,9 +45,9 @@
|
||||
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.updateRelationshipAttribute(
|
||||
.tables.updateRelationshipColumn(
|
||||
databaseId,
|
||||
collectionId,
|
||||
tableId,
|
||||
originalKey,
|
||||
data.onDelete,
|
||||
data.key !== originalKey ? data.key : undefined
|
||||
@@ -59,7 +59,7 @@
|
||||
import { InputText, InputSelect } from '$lib/elements/forms';
|
||||
import { onMount } from 'svelte';
|
||||
import { Box } from '$lib/components';
|
||||
import { collection } from '../store';
|
||||
import { table } from '../store';
|
||||
import arrowOne from './arrow-one.svg';
|
||||
import arrowTwo from './arrow-two.svg';
|
||||
import { camelize } from '$lib/helpers/string';
|
||||
@@ -69,7 +69,7 @@
|
||||
|
||||
// Props
|
||||
export let editing = false;
|
||||
export let data: Models.AttributeRelationship;
|
||||
export let data: Models.ColumnRelationship;
|
||||
|
||||
// Constants
|
||||
const databaseId = page.params.database;
|
||||
@@ -81,47 +81,45 @@
|
||||
];
|
||||
|
||||
const deleteOptions = [
|
||||
{ value: 'setNull', label: 'Set NULL - set document ID as NULL in all related documents' },
|
||||
{ value: 'cascade', label: 'Cascade - delete all related documents' },
|
||||
{ value: 'restrict', label: 'Restrict - document can not be deleted' }
|
||||
{ value: 'setNull', label: 'Set NULL - set row ID as NULL in all related rows' },
|
||||
{ value: 'cascade', label: 'Cascade - delete all related rows' },
|
||||
{ value: 'restrict', label: 'Restrict - row can not be deleted' }
|
||||
];
|
||||
|
||||
// Variables
|
||||
let way = 'one';
|
||||
let search = undefined;
|
||||
let collectionList: Models.CollectionList;
|
||||
let originalCollectionList: Models.CollectionList;
|
||||
let tableList: Models.TableList;
|
||||
let originalTableList: Models.TableList;
|
||||
|
||||
// Lifecycle hooks
|
||||
async function getCollections() {
|
||||
async function getTables() {
|
||||
const queries = [Query.limit(100)];
|
||||
return sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.listCollections(databaseId, queries, search);
|
||||
.tables.list(databaseId, queries, search);
|
||||
}
|
||||
|
||||
const debouncedFetchCollections = debounce(async () => {
|
||||
collectionList = await getCollections();
|
||||
const debouncedFetchTables = debounce(async () => {
|
||||
tableList = await getTables();
|
||||
// reset search
|
||||
search = undefined;
|
||||
}, 500);
|
||||
|
||||
function updateKeyName() {
|
||||
if (!editing) {
|
||||
const collection = collectionList.collections.find(
|
||||
(n) => n.$id === data.relatedCollection
|
||||
);
|
||||
const collection = tableList.tables.find((n) => n.$id === data.relatedTable);
|
||||
data.key = camelize(collection.name);
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
collectionList = await getCollections();
|
||||
originalCollectionList = collectionList;
|
||||
tableList = await getTables();
|
||||
originalTableList = tableList;
|
||||
});
|
||||
|
||||
// Reactive statements
|
||||
$: collections = collectionList?.collections?.filter((n) => n.$id !== $collection.$id) ?? [];
|
||||
$: tables = tableList?.tables?.filter((n) => n.$id !== $table.$id) ?? [];
|
||||
|
||||
$: if (editing) {
|
||||
way = data.twoWay ? 'two' : 'one';
|
||||
@@ -129,26 +127,26 @@
|
||||
if (way === 'two') {
|
||||
data.twoWay = true;
|
||||
if (!data.twoWayKey) {
|
||||
data.twoWayKey = camelize($collection.name);
|
||||
data.twoWayKey = camelize($table.name);
|
||||
}
|
||||
} else {
|
||||
data.twoWay = false;
|
||||
}
|
||||
}
|
||||
|
||||
$: search = data.relatedCollection || undefined;
|
||||
$: search = data.relatedTable || undefined;
|
||||
|
||||
$: if (search) {
|
||||
const exists = collectionList?.collections?.some((c) =>
|
||||
const exists = tableList?.tables?.some((c) =>
|
||||
c.$id.toLocaleLowerCase().includes(search.toLocaleLowerCase())
|
||||
);
|
||||
|
||||
if (!exists) {
|
||||
debouncedFetchCollections();
|
||||
debouncedFetchTables();
|
||||
}
|
||||
} else if (!collectionList?.total) {
|
||||
} else if (!tableList?.total) {
|
||||
// reset to original list!
|
||||
collectionList = originalCollectionList;
|
||||
tableList = originalTableList;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -159,7 +157,7 @@
|
||||
name="one"
|
||||
value="one"
|
||||
icon={IconArrowSmRight}>
|
||||
One Relation attribute within this collection
|
||||
One Relation column within this table
|
||||
</Card.Selector>
|
||||
<Card.Selector
|
||||
title="Two-way relationship"
|
||||
@@ -167,23 +165,23 @@
|
||||
name="two"
|
||||
value="two"
|
||||
icon={IconSwitchHorizontal}>
|
||||
One Relation attribute within this collection and another within the related collection
|
||||
One Relation column within this table and another within the related table
|
||||
</Card.Selector>
|
||||
</Layout.Stack>
|
||||
|
||||
<Input.ComboBox
|
||||
required
|
||||
id="related"
|
||||
label="Related collection"
|
||||
placeholder="Select a collection"
|
||||
bind:value={data.relatedCollection}
|
||||
label="Related table"
|
||||
placeholder="Select a table"
|
||||
bind:value={data.relatedTable}
|
||||
on:change={updateKeyName}
|
||||
options={collections?.map((n) => ({ value: n.$id, label: `${n.name} (${n.$id})` })) ?? []} />
|
||||
options={tables?.map((n) => ({ value: n.$id, label: `${n.name} (${n.$id})` })) ?? []} />
|
||||
|
||||
{#if data?.relatedCollection}
|
||||
{#if data?.relatedTable}
|
||||
<InputText
|
||||
id="key"
|
||||
label="Attribute key"
|
||||
label="Column key"
|
||||
placeholder="Enter key"
|
||||
bind:value={data.key}
|
||||
helper="Allowed characters: a-z, A-Z, 0-9, -, ."
|
||||
@@ -191,11 +189,11 @@
|
||||
{#if way === 'two'}
|
||||
<InputText
|
||||
id="keyRelated"
|
||||
label="Attribute key (related collection)"
|
||||
label="Column key (related table)"
|
||||
placeholder="Enter key"
|
||||
bind:value={data.twoWayKey}
|
||||
required
|
||||
helper="Allowed characters: a-z, A-Z, 0-9, -, . Once created, attribute key cannot be
|
||||
helper="Allowed characters: a-z, A-Z, 0-9, -, . Once created, column key cannot be
|
||||
adjusted to maintain data integrity."
|
||||
readonly={editing} />
|
||||
{/if}
|
||||
@@ -212,7 +210,7 @@
|
||||
<div class="u-flex u-flex-vertical u-gap-16">
|
||||
<Box>
|
||||
<div class="u-flex u-align u-cross-center u-main-center u-gap-32">
|
||||
<span data-private>{camelize($collection.name)}</span>
|
||||
<span data-private>{camelize($table.name)}</span>
|
||||
{#if data.twoWay}
|
||||
<img src={arrowTwo} alt={'Two way relationship'} />
|
||||
{:else}
|
||||
@@ -224,7 +222,7 @@
|
||||
{#if data.relationType}
|
||||
<div>
|
||||
<p class="u-text-center">
|
||||
<b data-private>{camelize($collection.name)}</b> can contain {[
|
||||
<b data-private>{camelize($table.name)}</b> can contain {[
|
||||
'oneToOne',
|
||||
'manyToOne'
|
||||
].includes(data.relationType)
|
||||
@@ -237,14 +235,14 @@
|
||||
can belong to {['oneToOne', 'oneToMany'].includes(data.relationType)
|
||||
? 'one'
|
||||
: 'many'}
|
||||
<b data-private>{camelize($collection.name)}</b>
|
||||
<b data-private>{camelize($table.name)}</b>
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<InputSelect
|
||||
id="deleting"
|
||||
label="On deleting a document"
|
||||
label="On deleting a row"
|
||||
bind:value={data.onDelete}
|
||||
required
|
||||
placeholder="Select a deletion method"
|
||||
+6
-6
@@ -9,7 +9,7 @@ import Ip, { submitIp, updateIp } from './ip.svelte';
|
||||
import String, { submitString, updateString } from './string.svelte';
|
||||
import Url, { submitUrl, updateUrl } from './url.svelte';
|
||||
import Datetime, { submitDatetime, updateDatetime } from './datetime.svelte';
|
||||
import type { Attributes } from '../store';
|
||||
import type { Columns } from '../store';
|
||||
import Relationship, { submitRelationship, updateRelationship } from './relationship.svelte';
|
||||
import {
|
||||
IconCalendar,
|
||||
@@ -41,21 +41,21 @@ export type Option = {
|
||||
type: 'string' | 'integer' | 'double' | 'boolean' | 'datetime' | 'relationship';
|
||||
create: (
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
tableId: string,
|
||||
key: string,
|
||||
data: Partial<Attributes>
|
||||
data: Partial<Columns>
|
||||
) => Promise<void>;
|
||||
update: (
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
data: Partial<Attributes>,
|
||||
tableId: string,
|
||||
data: Partial<Columns>,
|
||||
originalKey: string
|
||||
) => Promise<void>;
|
||||
format?: 'email' | 'ip' | 'url' | 'enum';
|
||||
icon: ComponentType;
|
||||
};
|
||||
|
||||
export const attributeOptions: Option[] = [
|
||||
export const columnOptions: Option[] = [
|
||||
{
|
||||
name: 'String',
|
||||
sentenceName: 'string',
|
||||
+14
-14
@@ -5,15 +5,15 @@
|
||||
|
||||
export async function submitString(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
tableId: string,
|
||||
key: string,
|
||||
data: Partial<Models.AttributeString>
|
||||
data: Partial<Models.ColumnString>
|
||||
) {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.createStringAttribute(
|
||||
.tables.createStringColumn(
|
||||
databaseId,
|
||||
collectionId,
|
||||
tableId,
|
||||
key,
|
||||
data.size,
|
||||
data.required,
|
||||
@@ -24,15 +24,15 @@
|
||||
}
|
||||
export async function updateString(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
data: Partial<Models.AttributeString>,
|
||||
tableId: string,
|
||||
data: Partial<Models.ColumnString>,
|
||||
originalKey?: string
|
||||
) {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.updateStringAttribute(
|
||||
.tables.updateStringColumn(
|
||||
databaseId,
|
||||
collectionId,
|
||||
tableId,
|
||||
originalKey,
|
||||
data.required,
|
||||
data.default,
|
||||
@@ -51,7 +51,7 @@
|
||||
import { InputNumber, InputText, InputTextarea } from '$lib/elements/forms';
|
||||
import { Popover, Layout, Tag, Typography, Link } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let data: Partial<Models.AttributeString> = {
|
||||
export let data: Partial<Models.ColumnString> = {
|
||||
required: false,
|
||||
size: 0,
|
||||
array: false,
|
||||
@@ -74,7 +74,7 @@
|
||||
const {
|
||||
stores: { required, array },
|
||||
listen
|
||||
} = createConservative<Partial<Models.AttributeString>>({
|
||||
} = createConservative<Partial<Models.ColumnString>>({
|
||||
required: false,
|
||||
array: false,
|
||||
...data
|
||||
@@ -96,7 +96,7 @@
|
||||
bind:value={data.size}
|
||||
min={supportsStringEncryption && data.encrypt ? 150 : undefined}
|
||||
helper={supportsStringEncryption && data.encrypt
|
||||
? 'Encrypted string attributes require a minimum size of 150.'
|
||||
? 'Encrypted string columns require a minimum size of 150.'
|
||||
: undefined} />
|
||||
|
||||
<svelte:component
|
||||
@@ -115,7 +115,7 @@
|
||||
label="Required"
|
||||
bind:checked={data.required}
|
||||
disabled={data.array}
|
||||
description="Indicate whether this attribute is required." />
|
||||
description="Indicate whether this column is required." />
|
||||
|
||||
<Selector.Checkbox
|
||||
size="s"
|
||||
@@ -123,7 +123,7 @@
|
||||
label="Array"
|
||||
bind:checked={data.array}
|
||||
disabled={data.required || editing}
|
||||
description="Indicate whether this attribute is an array. Defaults to an empty array." />
|
||||
description="Indicate whether this column is an array. Defaults to an empty array." />
|
||||
|
||||
<Layout.Stack gap="xs" direction="column">
|
||||
<div
|
||||
@@ -167,7 +167,7 @@
|
||||
<ActionMenu.Root width="180px" slot="tooltip">
|
||||
<Typography.Text variant="m-500">
|
||||
Available on Pro plan. <Link.Anchor href={$upgradeURL}>Upgrade</Link.Anchor>
|
||||
to enable encrypted attributes.
|
||||
to enable encrypted columns.
|
||||
</Typography.Text>
|
||||
</ActionMenu.Root>
|
||||
</Popover>
|
||||
+10
-10
@@ -5,15 +5,15 @@
|
||||
|
||||
export async function submitUrl(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
tableId: string,
|
||||
key: string,
|
||||
data: Partial<Models.AttributeUrl>
|
||||
data: Partial<Models.ColumnUrl>
|
||||
) {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.createUrlAttribute(
|
||||
.tables.createUrlColumn(
|
||||
databaseId,
|
||||
collectionId,
|
||||
tableId,
|
||||
key,
|
||||
data.required,
|
||||
data.default,
|
||||
@@ -23,15 +23,15 @@
|
||||
|
||||
export async function updateUrl(
|
||||
databaseId: string,
|
||||
collectionId: string,
|
||||
data: Partial<Models.AttributeUrl>,
|
||||
tableId: string,
|
||||
data: Partial<Models.ColumnUrl>,
|
||||
originalKey?: string
|
||||
) {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.updateUrlAttribute(
|
||||
.tables.updateUrlColumn(
|
||||
databaseId,
|
||||
collectionId,
|
||||
tableId,
|
||||
originalKey,
|
||||
data.required,
|
||||
data.default,
|
||||
@@ -43,7 +43,7 @@
|
||||
<script lang="ts">
|
||||
import { InputURL } from '$lib/elements/forms';
|
||||
|
||||
export let data: Partial<Models.AttributeUrl>;
|
||||
export let data: Partial<Models.ColumnUrl>;
|
||||
export let editing = false;
|
||||
|
||||
import { createConservative } from '$lib/helpers/stores';
|
||||
@@ -63,7 +63,7 @@
|
||||
const {
|
||||
stores: { required, array },
|
||||
listen
|
||||
} = createConservative<Partial<Models.AttributeUrl>>({
|
||||
} = createConservative<Partial<Models.ColumnUrl>>({
|
||||
required: false,
|
||||
array: false,
|
||||
...data
|
||||
+34
-39
@@ -10,10 +10,10 @@
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { writable } from 'svelte/store';
|
||||
import AttributeForm from '../document-[document]/attributeForm.svelte';
|
||||
import ColumnForm from '../row-[row]/columnForm.svelte';
|
||||
import { Permissions } from '$lib/components/permissions';
|
||||
import type { PageData } from './$types';
|
||||
import type { Attributes } from '../store';
|
||||
import type { Columns } from '../store';
|
||||
import { ID } from '@appwrite.io/console';
|
||||
|
||||
export let data: PageData;
|
||||
@@ -24,68 +24,66 @@
|
||||
|
||||
type CreateDocument = {
|
||||
id?: string;
|
||||
document: object;
|
||||
row: object;
|
||||
permissions: string[];
|
||||
attributes: Attributes[];
|
||||
columns: Columns[];
|
||||
};
|
||||
|
||||
function createDocumentWritable() {
|
||||
const availableAttributes = (data.collection.attributes as unknown as Attributes[]).filter(
|
||||
function createRowWritable() {
|
||||
const availableColumns = (data.table.columns as unknown as Columns[]).filter(
|
||||
(a) => a.status === 'available'
|
||||
);
|
||||
const initial = {
|
||||
id: null,
|
||||
document: availableAttributes.reduce((acc, attr) => {
|
||||
row: availableColumns.reduce((acc, attr) => {
|
||||
acc[attr.key] = attr.array ? [] : null;
|
||||
|
||||
return acc;
|
||||
}, {}),
|
||||
permissions: [],
|
||||
attributes: availableAttributes
|
||||
columns: availableColumns
|
||||
};
|
||||
|
||||
const store = writable<CreateDocument>({ ...initial });
|
||||
|
||||
return store;
|
||||
return writable<CreateDocument>({ ...initial });
|
||||
}
|
||||
|
||||
const createDocument = createDocumentWritable();
|
||||
const createRow = createRowWritable();
|
||||
|
||||
async function create() {
|
||||
try {
|
||||
const { $id } = await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.createDocument(
|
||||
.tables.createRow(
|
||||
page.params.database,
|
||||
page.params.collection,
|
||||
$createDocument.id ?? ID.unique(),
|
||||
$createDocument.document,
|
||||
$createDocument.permissions
|
||||
page.params.table,
|
||||
$createRow.id ?? ID.unique(),
|
||||
$createRow.row,
|
||||
$createRow.permissions
|
||||
);
|
||||
|
||||
addNotification({
|
||||
message: 'Document has been created',
|
||||
message: 'Row has been created',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent(Submit.DocumentCreate, {
|
||||
customId: !!$createDocument.id
|
||||
trackEvent(Submit.RowCreate, {
|
||||
customId: !!$createRow.id // todo: @itznotabug - change store name
|
||||
});
|
||||
goto(
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${page.params.database}/collection-${page.params.collection}/document-${$id}`
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${page.params.database}/table-${page.params.table}/row-${$id}`
|
||||
);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
trackError(error, Submit.DocumentCreate);
|
||||
trackError(error, Submit.RowCreate);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Wizard
|
||||
title="Create document"
|
||||
href={`${base}/project-${page.params.region}-${page.params.project}/databases/database-${page.params.database}/collection-${page.params.collection}/`}
|
||||
title="Create row"
|
||||
href={`${base}/project-${page.params.region}-${page.params.project}/databases/database-${page.params.database}/table-${page.params.table}`}
|
||||
bind:showExitModal
|
||||
column
|
||||
columnSize="s"
|
||||
@@ -93,10 +91,10 @@
|
||||
<Form bind:this={formComponent} onSubmit={create} bind:isSubmitting>
|
||||
<Layout.Stack gap="xxl">
|
||||
<Fieldset legend="Data">
|
||||
<AttributeForm
|
||||
bind:formValues={$createDocument.document}
|
||||
attributes={$createDocument.attributes}
|
||||
bind:customId={$createDocument.id} />
|
||||
<ColumnForm
|
||||
bind:formValues={$createRow.row}
|
||||
columns={$createRow.columns}
|
||||
bind:customId={$createRow.id} />
|
||||
</Fieldset>
|
||||
|
||||
<Fieldset legend="Permissions">
|
||||
@@ -105,21 +103,18 @@
|
||||
Choose which permission scopes to grant your application. It is best
|
||||
practice to allow only the permissions you need to meet your project goals.
|
||||
</Typography.Text>
|
||||
{#if data.collection.documentSecurity}
|
||||
{#if data.table.rowSecurity}
|
||||
<Alert.Inline status="info">
|
||||
<svelte:fragment slot="title"
|
||||
>Document security is enabled</svelte:fragment>
|
||||
Users will be able to access this document if they have been granted
|
||||
<b>either document or collection permissions</b>.
|
||||
<svelte:fragment slot="title">Row security is enabled</svelte:fragment>
|
||||
Users will be able to access this row if they have been granted
|
||||
<b>either row or table permissions</b>.
|
||||
</Alert.Inline>
|
||||
<Permissions bind:permissions={$createDocument.permissions} />
|
||||
<Permissions bind:permissions={$createRow.permissions} />
|
||||
{:else}
|
||||
<Alert.Inline status="info">
|
||||
<svelte:fragment slot="title"
|
||||
>Document security is disabled</svelte:fragment>
|
||||
If you want to assign document permissions, navigate to Collection settings
|
||||
and enable document security. Otherwise, only collection permissions will
|
||||
be used.
|
||||
<svelte:fragment slot="title">Row security is disabled</svelte:fragment>
|
||||
If you want to assign row permissions, navigate to Table settings and enable
|
||||
row security. Otherwise, only table permissions will be used.
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
+19
-14
@@ -1,22 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { Modal } from '$lib/components';
|
||||
import { option, attributeOptions, type Option } from './attributes/store';
|
||||
import {
|
||||
option,
|
||||
columnOptions,
|
||||
type Option
|
||||
} from '$routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/store';
|
||||
import { Button, InputText } from '$lib/elements/forms';
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { page } from '$app/state';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { base } from '$app/paths';
|
||||
import type { Attributes } from './store';
|
||||
import type { Columns } from './store';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
|
||||
export let showCreate = false;
|
||||
export let selectedOption: Option['name'] = null;
|
||||
|
||||
$: tableId = page.params.table;
|
||||
const databaseId = page.params.database;
|
||||
$: collectionId = page.params.collection;
|
||||
|
||||
let key: string = null;
|
||||
let data: Partial<Attributes> = {
|
||||
let data: Partial<Columns> = {
|
||||
required: false,
|
||||
array: false,
|
||||
default: null
|
||||
@@ -25,28 +30,28 @@
|
||||
|
||||
async function submit() {
|
||||
try {
|
||||
await $option.create(databaseId, collectionId, key, data);
|
||||
await invalidate(Dependencies.COLLECTION);
|
||||
await $option.create(databaseId, tableId, key, data);
|
||||
await invalidate(Dependencies.TABLE);
|
||||
|
||||
if (!page.url.pathname.includes('attributes')) {
|
||||
if (!page.url.pathname.includes('columns')) {
|
||||
await goto(
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}/collection-${collectionId}/attributes`
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}/table-${tableId}/columns`
|
||||
);
|
||||
}
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `Attribute ${key ?? data?.key} has been created`
|
||||
message: `Column ${key ?? data?.key} has been created`
|
||||
});
|
||||
showCreate = false;
|
||||
trackEvent(Submit.AttributeCreate);
|
||||
trackEvent(Submit.ColumnCreate);
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
trackError(e, Submit.AttributeCreate);
|
||||
trackError(e, Submit.ColumnCreate);
|
||||
}
|
||||
}
|
||||
|
||||
$: if (selectedOption) {
|
||||
$option = attributeOptions.find((option) => option.name === selectedOption);
|
||||
$option = columnOptions.find((option) => option.name === selectedOption);
|
||||
}
|
||||
|
||||
$: if (!showCreate) {
|
||||
@@ -61,14 +66,14 @@
|
||||
};
|
||||
}
|
||||
|
||||
$: title = `Create ${attributeOptions.find((option) => option.name === selectedOption)?.sentenceName ?? ''} attribute`;
|
||||
$: title = `Create ${columnOptions.find((option) => option.name === selectedOption)?.sentenceName ?? ''} column`;
|
||||
</script>
|
||||
|
||||
<Modal {error} bind:show={showCreate} onSubmit={submit} {title}>
|
||||
{#if selectedOption !== 'Relationship'}
|
||||
<InputText
|
||||
id="key"
|
||||
label="Attribute key"
|
||||
label="Column key"
|
||||
placeholder="Enter key"
|
||||
bind:value={key}
|
||||
autofocus
|
||||
File renamed without changes.
File renamed without changes.
+21
-24
@@ -4,33 +4,30 @@
|
||||
import { Id, Tab, Tabs } from '$lib/components';
|
||||
import { isTabSelected } from '$lib/helpers/load';
|
||||
import { Cover, CoverTitle } from '$lib/layout';
|
||||
import { canWriteCollections } from '$lib/stores/roles';
|
||||
import { collection } from './store';
|
||||
import { canWriteTables } from '$lib/stores/roles';
|
||||
import { table } from './store';
|
||||
import { isTabletViewport } from '$lib/stores/viewport';
|
||||
|
||||
let databaseId = $derived.by(() => {
|
||||
return page.params.database;
|
||||
});
|
||||
let collectionId = $derived.by(() => {
|
||||
return page.params.collection;
|
||||
});
|
||||
const databaseId = $derived(page.params.database);
|
||||
|
||||
let path = $derived.by(() => {
|
||||
return `${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}/collection-${collectionId}`;
|
||||
});
|
||||
const tableId = $derived(page.params.table);
|
||||
|
||||
let tabs = $derived.by(() => {
|
||||
return [
|
||||
const path = $derived(
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}/table-${tableId}`
|
||||
);
|
||||
|
||||
const tabs = $derived(
|
||||
[
|
||||
{
|
||||
href: path,
|
||||
title: 'Documents',
|
||||
event: 'documents',
|
||||
title: 'Rows',
|
||||
event: 'rows',
|
||||
hasChildren: true
|
||||
},
|
||||
{
|
||||
href: `${path}/attributes`,
|
||||
title: 'Attributes',
|
||||
event: 'attributes'
|
||||
href: `${path}/columns`,
|
||||
title: 'Columns',
|
||||
event: 'columns'
|
||||
},
|
||||
{
|
||||
href: `${path}/indexes`,
|
||||
@@ -53,10 +50,10 @@
|
||||
href: `${path}/settings`,
|
||||
title: 'Settings',
|
||||
event: 'settings',
|
||||
disabled: !$canWriteCollections
|
||||
disabled: !$canWriteTables
|
||||
}
|
||||
].filter((tab) => !tab.disabled);
|
||||
});
|
||||
].filter((tab) => !tab.disabled)
|
||||
);
|
||||
</script>
|
||||
|
||||
<div style:margin-top={$isTabletViewport ? '48px' : 0}>
|
||||
@@ -64,10 +61,10 @@
|
||||
<svelte:fragment slot="header">
|
||||
<CoverTitle
|
||||
href={`${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}`}>
|
||||
{$collection?.name}
|
||||
{$table?.name}
|
||||
</CoverTitle>
|
||||
{#key $collection?.$id}
|
||||
<Id value={$collection?.$id}>{$collection?.$id}</Id>
|
||||
{#key $table?.$id}
|
||||
<Id value={$table?.$id}>{$table?.$id}</Id>
|
||||
{/key}
|
||||
</svelte:fragment>
|
||||
|
||||
+37
-39
@@ -1,17 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { Empty } from '$lib/components';
|
||||
import { Container } from '$lib/layout';
|
||||
import { collection } from '../store';
|
||||
import { table } from '../store';
|
||||
import Delete from './deleteIndex.svelte';
|
||||
import Create from './createIndex.svelte';
|
||||
import Overview from './overviewIndex.svelte';
|
||||
import CreateAttribute from '../createAttribute.svelte';
|
||||
import CreateColumn from '../createColumn.svelte';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import CreateAttributeDropdown from '../attributes/createAttributeDropdown.svelte';
|
||||
import type { Option } from '../attributes/store';
|
||||
import CreateColumnDropdown from '../columns/createColumnDropdown.svelte';
|
||||
import type { Option } from '../columns/store';
|
||||
import FailedModal from '../failedModal.svelte';
|
||||
import { canWriteCollections } from '$lib/stores/roles';
|
||||
import { canWriteTables } from '$lib/stores/roles';
|
||||
import {
|
||||
ActionMenu,
|
||||
Badge,
|
||||
@@ -33,16 +33,16 @@
|
||||
|
||||
export let data;
|
||||
|
||||
let selectedIndex: Models.Index = null;
|
||||
let showCreateIndex = false;
|
||||
let showOverview = false;
|
||||
let showDelete = false;
|
||||
let showCreateAttribute = false;
|
||||
let selectedAttribute: Option['name'] = null;
|
||||
let showFailed = false;
|
||||
let error = '';
|
||||
let showFailed = false;
|
||||
let showDelete = false;
|
||||
let showOverview = false;
|
||||
let showCreateIndex = false;
|
||||
let showCreateColumn = false;
|
||||
let selectedColumn: Option['name'] = null;
|
||||
let selectedIndex: Models.ColumnIndex = null;
|
||||
|
||||
function getAttributeStatusBadge(status: string): ComponentProps<Badge>['type'] {
|
||||
function getColumnStatusBadge(status: string): ComponentProps<Badge>['type'] {
|
||||
switch (status) {
|
||||
case 'processing':
|
||||
return 'warning';
|
||||
@@ -59,10 +59,10 @@
|
||||
<Container>
|
||||
<Layout.Stack direction="row" justifyContent="space-between">
|
||||
<Typography.Title>Indexes</Typography.Title>
|
||||
{#if $canWriteCollections}
|
||||
{#if $canWriteTables}
|
||||
<Button
|
||||
event="create_index"
|
||||
disabled={!$collection?.attributes?.length}
|
||||
disabled={!$table?.columns?.length}
|
||||
on:click={() => (showCreateIndex = true)}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Create index
|
||||
@@ -70,14 +70,14 @@
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
|
||||
{#if data.collection?.attributes?.length}
|
||||
{#if data.collection.indexes.length}
|
||||
{#if data.table?.columns?.length}
|
||||
{#if data.table.indexes.length}
|
||||
<Table.Root
|
||||
let:root
|
||||
columns={[
|
||||
{ id: 'key' },
|
||||
{ id: 'type' },
|
||||
{ id: 'attributes' },
|
||||
{ id: 'columns' },
|
||||
{ id: 'orders' },
|
||||
{ id: 'lengths' },
|
||||
{ id: 'actions', width: 40 }
|
||||
@@ -85,12 +85,12 @@
|
||||
<svelte:fragment slot="header" let:root>
|
||||
<Table.Header.Cell column="key" {root}>Key</Table.Header.Cell>
|
||||
<Table.Header.Cell column="type" {root}>Type</Table.Header.Cell>
|
||||
<Table.Header.Cell column="attributes" {root}>Attributes</Table.Header.Cell>
|
||||
<Table.Header.Cell column="columns" {root}>Columns</Table.Header.Cell>
|
||||
<Table.Header.Cell column="orders" {root}>Orders</Table.Header.Cell>
|
||||
<Table.Header.Cell column="lengths" {root}>Lengths</Table.Header.Cell>
|
||||
<Table.Header.Cell column="actions" {root} />
|
||||
</svelte:fragment>
|
||||
{#each data.collection.indexes as index}
|
||||
{#each data.table.indexes as index}
|
||||
<Table.Row.Base {root}>
|
||||
<Table.Cell column="key" {root}>
|
||||
<Layout.Stack direction="row" alignItems="center">
|
||||
@@ -100,7 +100,7 @@
|
||||
size="s"
|
||||
variant="secondary"
|
||||
content={index.status}
|
||||
type={getAttributeStatusBadge(index.status)} />
|
||||
type={getColumnStatusBadge(index.status)} />
|
||||
{#if index.error}
|
||||
<Link.Button
|
||||
variant="muted"
|
||||
@@ -114,8 +114,8 @@
|
||||
</Layout.Stack>
|
||||
</Table.Cell>
|
||||
<Table.Cell column="type" {root}>{index.type}</Table.Cell>
|
||||
<Table.Cell column="attributes" {root}>
|
||||
{index.attributes}
|
||||
<Table.Cell column="columns" {root}>
|
||||
{index.columns}
|
||||
</Table.Cell>
|
||||
<Table.Cell column="orders" {root}>
|
||||
{index.orders}
|
||||
@@ -150,30 +150,30 @@
|
||||
</Table.Root>
|
||||
{:else}
|
||||
<Empty
|
||||
allowCreate={$canWriteCollections}
|
||||
allowCreate={$canWriteTables}
|
||||
single
|
||||
href="https://appwrite.io/docs/products/databases/collections#indexes"
|
||||
href="https://appwrite.io/docs/products/databases/tables#indexes"
|
||||
target="index"
|
||||
on:click={() => (showCreateIndex = true)} />
|
||||
{/if}
|
||||
{:else}
|
||||
<Empty single target="attribute">
|
||||
<Empty single target="index">
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
external
|
||||
href="https://appwrite.io/docs/products/databases/collections#attributes"
|
||||
href="https://appwrite.io/docs/products/databases/tables#columns"
|
||||
text
|
||||
event="empty_documentation"
|
||||
ariaLabel={`create {target}`}>Documentation</Button>
|
||||
{#if $canWriteCollections}
|
||||
<CreateAttributeDropdown
|
||||
bind:selectedOption={selectedAttribute}
|
||||
bind:showCreate={showCreateAttribute}
|
||||
ariaLabel="create index">Documentation</Button>
|
||||
{#if $canWriteTables}
|
||||
<CreateColumnDropdown
|
||||
bind:selectedOption={selectedColumn}
|
||||
bind:showCreate={showCreateColumn}
|
||||
let:toggle>
|
||||
<Button secondary event="create_attribute" on:click={toggle}>
|
||||
Create attribute
|
||||
<Button secondary event="create_column" on:click={toggle}>
|
||||
Create column
|
||||
</Button>
|
||||
</CreateAttributeDropdown>
|
||||
</CreateColumnDropdown>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Empty>
|
||||
@@ -187,10 +187,8 @@
|
||||
<Overview bind:showOverview {selectedIndex} />
|
||||
{/if}
|
||||
|
||||
{#if showCreateAttribute}
|
||||
<CreateAttribute
|
||||
bind:showCreate={showCreateAttribute}
|
||||
bind:selectedOption={selectedAttribute} />
|
||||
{#if showCreateColumn}
|
||||
<CreateColumn bind:showCreate={showCreateColumn} bind:selectedOption={selectedColumn} />
|
||||
{/if}
|
||||
|
||||
<FailedModal bind:show={showFailed} title="Create index" header="Creation failed" {error} />
|
||||
+41
-41
@@ -10,14 +10,14 @@
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { IndexType } from '@appwrite.io/console';
|
||||
import { isRelationship } from '../document-[document]/attributes/store';
|
||||
import { type Attributes, collection, indexes } from '../store';
|
||||
import { isRelationship } from '../row-[row]/columns/store';
|
||||
import { type Columns, table, indexes } from '../store';
|
||||
import { Icon, Layout } from '@appwrite.io/pink-svelte';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import { flags } from '$lib/flags';
|
||||
|
||||
export let showCreateIndex = false;
|
||||
export let externalAttribute: Attributes = null;
|
||||
export let externalColumn: Columns = null;
|
||||
|
||||
const databaseId = page.params.database;
|
||||
|
||||
@@ -30,14 +30,14 @@
|
||||
];
|
||||
let selectedType = IndexType.Key;
|
||||
|
||||
let attributeOptions = $collection.attributes
|
||||
.filter((attribute) => !isRelationship(attribute))
|
||||
.map((attribute) => ({
|
||||
value: attribute.key,
|
||||
label: attribute.key
|
||||
let columnOptions = $table.columns
|
||||
.filter((column) => !isRelationship(column))
|
||||
.map((column) => ({
|
||||
value: column.key,
|
||||
label: column.key
|
||||
}));
|
||||
|
||||
let attributeList = [{ value: '', order: '', length: null }];
|
||||
let columnList = [{ value: '', order: '', length: null }];
|
||||
|
||||
const showLengths = flags.showIndexLengths(page.data);
|
||||
|
||||
@@ -53,8 +53,8 @@
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
attributeList = externalAttribute
|
||||
? [{ value: externalAttribute.key, order: 'ASC', length: null }]
|
||||
columnList = externalColumn
|
||||
? [{ value: externalColumn.key, order: 'ASC', length: null }]
|
||||
: [{ value: '', order: 'ASC', length: null }];
|
||||
selectedType = IndexType.Key;
|
||||
key = `index_${$indexes.length + 1}`;
|
||||
@@ -66,32 +66,32 @@
|
||||
key = generateIndexKey();
|
||||
}
|
||||
|
||||
$: addAttributeDisabled = !attributeList.at(-1)?.value || !attributeList.at(-1)?.order;
|
||||
$: addColumnDisabled = !columnList.at(-1)?.value || !columnList.at(-1)?.order;
|
||||
|
||||
async function create() {
|
||||
if (!(key && selectedType && !addAttributeDisabled)) {
|
||||
if (!(key && selectedType && !addColumnDisabled)) {
|
||||
error = 'All fields are required';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await sdk.forProject(page.params.region, page.params.project).databases.createIndex(
|
||||
await sdk.forProject(page.params.region, page.params.project).tables.createIndex(
|
||||
databaseId,
|
||||
$collection.$id,
|
||||
$table.$id,
|
||||
key,
|
||||
selectedType,
|
||||
attributeList.map((a) => a.value),
|
||||
attributeList.map((a) => a.order),
|
||||
attributeList.map((a) => (a.length ? Number(a.length) : null))
|
||||
columnList.map((a) => a.value),
|
||||
columnList.map((a) => a.order),
|
||||
columnList.map((a) => (a.length ? Number(a.length) : null))
|
||||
);
|
||||
|
||||
await Promise.allSettled([
|
||||
invalidate(Dependencies.COLLECTION),
|
||||
invalidate(Dependencies.TABLE),
|
||||
invalidate(Dependencies.DATABASE)
|
||||
]);
|
||||
|
||||
goto(
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}/collection-${$collection.$id}/indexes`
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}/table-${$table.$id}/indexes`
|
||||
);
|
||||
|
||||
addNotification({
|
||||
@@ -106,11 +106,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
function addAttribute() {
|
||||
if (addAttributeDisabled) return;
|
||||
function addColumn() {
|
||||
if (addColumnDisabled) return;
|
||||
|
||||
// We assign instead of pushing to trigger Svelte's reactivity
|
||||
attributeList = [...attributeList, { value: '', order: '', length: null }];
|
||||
columnList = [...columnList, { value: '', order: '', length: null }];
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
<InputSelect required options={types} id="type" label="Index type" bind:value={selectedType} />
|
||||
|
||||
<Layout.Stack gap="s">
|
||||
{#each attributeList as attribute, i}
|
||||
{#each columnList as column, index}
|
||||
<Layout.Stack direction="row">
|
||||
<InputSelect
|
||||
required
|
||||
@@ -133,12 +133,12 @@
|
||||
{ value: '$id', label: '$id' },
|
||||
{ value: '$createdAt', label: '$createdAt' },
|
||||
{ value: '$updatedAt', label: '$updatedAt' },
|
||||
...attributeOptions
|
||||
...columnOptions
|
||||
]}
|
||||
id={`attribute-${i}`}
|
||||
label={i === 0 ? 'Attribute' : undefined}
|
||||
placeholder="Select Attribute"
|
||||
bind:value={attribute.value} />
|
||||
id={`column-${index}`}
|
||||
label={index === 0 ? 'Column' : undefined}
|
||||
placeholder="Select column"
|
||||
bind:value={column.value} />
|
||||
|
||||
<InputSelect
|
||||
options={[
|
||||
@@ -146,25 +146,25 @@
|
||||
{ value: 'DESC', label: 'DESC' }
|
||||
]}
|
||||
required
|
||||
id={`order-${i}`}
|
||||
label={i === 0 ? 'Order' : undefined}
|
||||
bind:value={attribute.order}
|
||||
placeholder="Select Order" />
|
||||
id={`order-${index}`}
|
||||
label={index === 0 ? 'Order' : undefined}
|
||||
bind:value={column.order}
|
||||
placeholder="Select order" />
|
||||
|
||||
<Layout.Stack direction="row" alignItems="flex-end" gap="xs">
|
||||
{#if selectedType === IndexType.Key && showLengths}
|
||||
<InputNumber
|
||||
id={`length-${i}`}
|
||||
label={i === 0 ? 'Length' : undefined}
|
||||
placeholder="Enter Length"
|
||||
bind:value={attribute.length} />
|
||||
id={`length-${index}`}
|
||||
label={index === 0 ? 'Length' : undefined}
|
||||
placeholder="Enter length"
|
||||
bind:value={column.length} />
|
||||
{/if}
|
||||
<Button
|
||||
icon
|
||||
compact
|
||||
disabled={attributeList.length <= 1}
|
||||
disabled={columnList.length <= 1}
|
||||
on:click={() => {
|
||||
attributeList = remove(attributeList, i);
|
||||
columnList = remove(columnList, index);
|
||||
}}>
|
||||
<span class="icon-x" aria-hidden="true"></span>
|
||||
</Button>
|
||||
@@ -172,9 +172,9 @@
|
||||
</Layout.Stack>
|
||||
{/each}
|
||||
<div>
|
||||
<Button compact on:click={addAttribute} disabled={addAttributeDisabled}>
|
||||
<Button compact on:click={addColumn} disabled={addColumnDisabled}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Add attribute
|
||||
Add column
|
||||
</Button>
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { collection } from '../store';
|
||||
import { table } from '../store';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { page } from '$app/state';
|
||||
@@ -11,17 +11,17 @@
|
||||
import { Typography } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let showDelete = false;
|
||||
export let selectedIndex: Models.Index;
|
||||
export let selectedIndex: Models.ColumnIndex;
|
||||
|
||||
const databaseId = page.params.database;
|
||||
let error: string;
|
||||
const databaseId = page.params.database;
|
||||
|
||||
async function handleDelete() {
|
||||
try {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.deleteIndex(databaseId, $collection.$id, selectedIndex.key);
|
||||
await invalidate(Dependencies.COLLECTION);
|
||||
.tables.deleteIndex(databaseId, $table.$id, selectedIndex.key);
|
||||
await invalidate(Dependencies.TABLE);
|
||||
showDelete = false;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
@@ -37,6 +37,6 @@
|
||||
|
||||
<Confirm onSubmit={handleDelete} title="Delete index" bind:open={showDelete} bind:error>
|
||||
<Typography.Text>
|
||||
Are you sure you want to delete <b>{selectedIndex.key}</b> from <b>{$collection.name}</b>?
|
||||
Are you sure you want to delete <b>{selectedIndex.key}</b> from <b>{$table.name}</b>?
|
||||
</Typography.Text>
|
||||
</Confirm>
|
||||
+8
-8
@@ -7,7 +7,7 @@
|
||||
import { flags } from '$lib/flags';
|
||||
|
||||
export let showOverview = false;
|
||||
export let selectedIndex: Models.Index = null;
|
||||
export let selectedIndex: Models.ColumnIndex = null;
|
||||
|
||||
const showLengths = flags.showIndexLengths(page.data);
|
||||
</script>
|
||||
@@ -16,8 +16,8 @@
|
||||
<InputText
|
||||
required
|
||||
id="key"
|
||||
label="Index Key"
|
||||
placeholder="Enter Key"
|
||||
label="Index key"
|
||||
placeholder="Enter key"
|
||||
value={selectedIndex.key}
|
||||
readonly />
|
||||
<InputText
|
||||
@@ -28,14 +28,14 @@
|
||||
value={selectedIndex.type}
|
||||
readonly />
|
||||
|
||||
{#if selectedIndex?.attributes?.length}
|
||||
{#each selectedIndex.attributes as attribute, i}
|
||||
{#if selectedIndex?.columns?.length}
|
||||
{#each selectedIndex.columns as column, i}
|
||||
<Layout.Stack direction="row">
|
||||
<InputText
|
||||
required
|
||||
label={i === 0 ? 'Attribute' : ''}
|
||||
id={`value-${attribute}`}
|
||||
value={attribute}
|
||||
label={i === 0 ? 'Column' : ''}
|
||||
id={`value-${column}`}
|
||||
value={column}
|
||||
readonly />
|
||||
<InputText
|
||||
required
|
||||
+3
-1
@@ -1,5 +1,7 @@
|
||||
<script lang="ts">
|
||||
import Helper from '$lib/elements/forms/helper.svelte';
|
||||
import { Icon } from '@appwrite.io/pink-svelte';
|
||||
import { IconChevronDown } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
export let value: string;
|
||||
export let id: string;
|
||||
@@ -30,7 +32,7 @@
|
||||
<select bind:this={element} {id} bind:value {required} on:invalid={handleInvalid}>
|
||||
<slot />
|
||||
</select>
|
||||
<span class="icon-cheveron-down" aria-hidden="true"></span>
|
||||
<Icon icon={IconChevronDown} size="s" />
|
||||
</div>
|
||||
{#if error}
|
||||
<Helper type="warning">{error}</Helper>
|
||||
+4
-4
@@ -9,14 +9,14 @@
|
||||
|
||||
export let show = false;
|
||||
export let data: Partial<Models.Document>[];
|
||||
export let selectedRelationship: Models.AttributeRelationship = null;
|
||||
export let selectedRelationship: Models.ColumnRelationship = null;
|
||||
const databaseId = page.params.database;
|
||||
const limit = 10;
|
||||
|
||||
$: args =
|
||||
preferences
|
||||
.getDisplayNames()
|
||||
?.[selectedRelationship?.relatedCollection]?.filter((p) => p !== '$id') ?? [];
|
||||
?.[selectedRelationship?.relatedTable]?.filter((p) => p !== '$id') ?? [];
|
||||
|
||||
$: if (!show) {
|
||||
data = null;
|
||||
@@ -36,7 +36,7 @@
|
||||
{#snippet children(paginatedItems: typeof data)}
|
||||
<Table.Root let:root columns={tableColumns}>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
<Table.Header.Cell column="$id" {root}>Document ID</Table.Header.Cell>
|
||||
<Table.Header.Cell column="$id" {root}>Row ID</Table.Header.Cell>
|
||||
{#if args.length}
|
||||
{#each args as arg}
|
||||
<Table.Header.Cell column={arg} {root}>{arg}</Table.Header.Cell>
|
||||
@@ -46,7 +46,7 @@
|
||||
{#each paginatedItems as doc}
|
||||
<Table.Row.Link
|
||||
{root}
|
||||
href={`${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}/collection-${selectedRelationship.relatedCollection}/document-${doc.$id}`}
|
||||
href={`${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}/table-${selectedRelationship.relatedTable}/row-${doc.$id}`}
|
||||
on:click={() => (show = false)}>
|
||||
<Table.Cell column="$id" {root}>
|
||||
<Id value={doc.$id}>{doc.$id}</Id>
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<svelte:head>
|
||||
<title>Document - Appwrite</title>
|
||||
<title>Row - Appwrite</title>
|
||||
</svelte:head>
|
||||
|
||||
<slot />
|
||||
+13
-18
@@ -3,40 +3,35 @@ import { Dependencies } from '$lib/constants';
|
||||
import type { LayoutLoad } from './$types';
|
||||
import Breadcrumbs from './breadcrumbs.svelte';
|
||||
import Header from './header.svelte';
|
||||
import type { Attributes } from '../store';
|
||||
import { buildWildcardAttributesQuery } from './attributes/store';
|
||||
import type { Columns } from '../store';
|
||||
import { buildWildcardColumnsQuery } from './columns/store';
|
||||
|
||||
export const load: LayoutLoad = async ({ params, parent, depends }) => {
|
||||
const { collection } = await parent();
|
||||
depends(Dependencies.DOCUMENT);
|
||||
depends(Dependencies.ROW);
|
||||
const { table } = await parent();
|
||||
|
||||
const document = await sdk
|
||||
const row = await sdk
|
||||
.forProject(params.region, params.project)
|
||||
.databases.getDocument(
|
||||
params.database,
|
||||
params.collection,
|
||||
params.document,
|
||||
buildWildcardAttributesQuery(collection)
|
||||
);
|
||||
.tables.getRow(params.database, params.table, params.row, buildWildcardColumnsQuery(table));
|
||||
|
||||
/**
|
||||
* Sanitize DateTime to remove UTC Timezone section.
|
||||
*/
|
||||
collection.attributes.forEach((attribute) => {
|
||||
const { type, key, array } = attribute as unknown as Attributes;
|
||||
table.columns.forEach((column) => {
|
||||
const { type, key, array } = column as unknown as Columns;
|
||||
if (type === 'datetime') {
|
||||
if (array) {
|
||||
document[key] = document[key].map((n: string) => {
|
||||
row[key] = row[key].map((n: string) => {
|
||||
if (!n) {
|
||||
return null;
|
||||
}
|
||||
return new Date(n).toISOString().slice(0, 23);
|
||||
});
|
||||
} else {
|
||||
if (document[key]) {
|
||||
document[key] = new Date(document[key]).toISOString().slice(0, 23);
|
||||
if (row[key]) {
|
||||
row[key] = new Date(row[key]).toISOString().slice(0, 23);
|
||||
} else {
|
||||
document[key] = null;
|
||||
row[key] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,6 +40,6 @@ export const load: LayoutLoad = async ({ params, parent, depends }) => {
|
||||
return {
|
||||
header: Header,
|
||||
breadcrumbs: Breadcrumbs,
|
||||
document
|
||||
row
|
||||
};
|
||||
};
|
||||
+40
-44
@@ -11,36 +11,38 @@
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { doc } from './store';
|
||||
import { collection, type Attributes } from '../store';
|
||||
import AttributeItem from './attributeItem.svelte';
|
||||
import { isRelationship, isRelationshipToMany } from './attributes/store';
|
||||
import { table, type Columns } from '../store';
|
||||
import ColumnItem from './columnItem.svelte';
|
||||
import { isRelationship, isRelationshipToMany } from './columns/store';
|
||||
import { deepClone } from '$lib/helpers/object';
|
||||
|
||||
export let data;
|
||||
|
||||
const row = data.row;
|
||||
const databaseId = page.params.database;
|
||||
const collectionId = page.params.collection;
|
||||
const documentId = page.params.document;
|
||||
const tableId = page.params.table;
|
||||
const rowId = page.params.row;
|
||||
|
||||
function initWork() {
|
||||
const prohibitedKeys = [
|
||||
'$id',
|
||||
'$collection',
|
||||
'$collectionId',
|
||||
'$tableId',
|
||||
'$databaseId',
|
||||
'$createdAt',
|
||||
'$updatedAt'
|
||||
];
|
||||
|
||||
const filteredKeys = Object.keys($doc).filter((key) => {
|
||||
const filteredKeys = Object.keys(row).filter((key) => {
|
||||
return !prohibitedKeys.includes(key);
|
||||
});
|
||||
|
||||
const result = filteredKeys.reduce((obj, key) => {
|
||||
obj[key] = $doc[key];
|
||||
obj[key] = row[key];
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
return writable(deepClone(result as Models.Document));
|
||||
return writable(deepClone(result as Models.Row));
|
||||
}
|
||||
|
||||
const work = initWork();
|
||||
@@ -49,18 +51,12 @@
|
||||
try {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.updateDocument(
|
||||
databaseId,
|
||||
collectionId,
|
||||
documentId,
|
||||
$work,
|
||||
$work.$permissions
|
||||
);
|
||||
.tables.updateRow(databaseId, tableId, rowId, $work, $work.$permissions);
|
||||
|
||||
invalidate(Dependencies.DOCUMENT);
|
||||
trackEvent(Submit.DocumentUpdate);
|
||||
invalidate(Dependencies.ROW);
|
||||
trackEvent(Submit.RowUpdate);
|
||||
addNotification({
|
||||
message: 'Document has been updated',
|
||||
message: 'Row has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -68,46 +64,46 @@
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
trackError(error, Submit.DocumentUpdate);
|
||||
trackError(error, Submit.RowUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
function compareAttributes(
|
||||
attribute: Attributes,
|
||||
$work: Models.Document,
|
||||
$doc: Models.Document
|
||||
) {
|
||||
if (!attribute) {
|
||||
function compareColumns(column: Columns, related: Models.Row, row: Models.Row) {
|
||||
if (!column) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const workAttribute = $work?.[attribute.key];
|
||||
const docAttribute = $doc?.[attribute.key];
|
||||
const rowColumn = row?.[column.key];
|
||||
const relatedColumn = related?.[column.key];
|
||||
|
||||
if (attribute.array) {
|
||||
return !symmetricDifference(Array.from(workAttribute), Array.from(docAttribute)).length;
|
||||
// undefined for a second, not sure why!
|
||||
if (rowColumn === undefined || relatedColumn === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isRelationship(attribute)) {
|
||||
if (isRelationshipToMany(attribute as Models.AttributeRelationship)) {
|
||||
const workIds = workAttribute.map((doc: string | Record<string, unknown>) =>
|
||||
if (column.array) {
|
||||
return !symmetricDifference(Array.from(relatedColumn), Array.from(rowColumn)).length;
|
||||
}
|
||||
|
||||
if (isRelationship(column)) {
|
||||
if (isRelationshipToMany(column as Models.ColumnRelationship)) {
|
||||
const workIds = relatedColumn.map((doc: string | Record<string, unknown>) =>
|
||||
typeof doc === 'string' ? doc : doc.$id
|
||||
);
|
||||
|
||||
const relatedIds = docAttribute.map((doc: string | Record<string, unknown>) =>
|
||||
const relatedIds = rowColumn.map((doc: string | Record<string, unknown>) =>
|
||||
typeof doc === 'string' ? doc : doc.$id
|
||||
);
|
||||
return !symmetricDifference(workIds, relatedIds).length;
|
||||
} else {
|
||||
const workId =
|
||||
typeof workAttribute === 'string' ? workAttribute : workAttribute?.$id;
|
||||
const relatedId =
|
||||
typeof docAttribute === 'string' ? docAttribute : docAttribute?.$id;
|
||||
typeof relatedColumn === 'string' ? relatedColumn : relatedColumn?.$id;
|
||||
const relatedId = typeof rowColumn === 'string' ? rowColumn : rowColumn?.$id;
|
||||
|
||||
return workId === relatedId;
|
||||
}
|
||||
}
|
||||
return workAttribute === docAttribute;
|
||||
return relatedColumn === rowColumn;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -116,19 +112,19 @@
|
||||
</svelte:head>
|
||||
|
||||
<Container>
|
||||
{#if $collection?.attributes?.length}
|
||||
{#each $collection.attributes as attribute}
|
||||
{@const label = attribute.key}
|
||||
{#if $table?.columns?.length}
|
||||
{#each $table.columns as column}
|
||||
{@const label = column.key}
|
||||
<CardGrid>
|
||||
<svelte:fragment slot="title">{label}</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<AttributeItem {attribute} bind:formValues={$work} {label} editing />
|
||||
<ColumnItem {column} bind:formValues={$work} {label} editing />
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={compareAttributes(attribute, $work, $doc)}
|
||||
disabled={compareColumns(column, $work, row)}
|
||||
on:click={() => updateData()}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
File renamed without changes.
+1
-1
@@ -14,7 +14,7 @@ export const load: PageLoad = async ({ params, url, route }) => {
|
||||
limit,
|
||||
logs: await sdk
|
||||
.forProject(params.region, params.project)
|
||||
.databases.listDocumentLogs(params.database, params.collection, params.document, [
|
||||
.tables.listRowLogs(params.database, params.table, params.row, [
|
||||
Query.limit(limit),
|
||||
Query.offset(offset)
|
||||
])
|
||||
+6
-6
@@ -3,8 +3,8 @@
|
||||
import { page } from '$app/state';
|
||||
import { Breadcrumbs } from '$lib/layout';
|
||||
import { database } from '../../store';
|
||||
import { collection } from '../store';
|
||||
import { doc } from './store';
|
||||
import { table } from '../store';
|
||||
import { row } from './store';
|
||||
|
||||
$: breadcrumbs = [
|
||||
{
|
||||
@@ -15,12 +15,12 @@
|
||||
title: $database?.name
|
||||
},
|
||||
{
|
||||
href: `${base}/project-${page.params.region}-${page.params.project}/databases/database-${$database?.$id}/collection-${$collection?.$id}`,
|
||||
title: $collection?.name
|
||||
href: `${base}/project-${page.params.region}-${page.params.project}/databases/database-${$database?.$id}/table-${$table?.$id}`,
|
||||
title: $table?.name
|
||||
},
|
||||
{
|
||||
href: `${base}/project-${page.params.region}-${page.params.project}/databases/database-${$database?.$id}/collection-${$collection?.$id}/document-${$doc?.$id}`,
|
||||
title: $doc?.$id
|
||||
href: `${base}/project-${page.params.region}-${page.params.project}/databases/database-${$database?.$id}/table-${$table?.$id}/row-${$row?.$id}`,
|
||||
title: $row?.$id
|
||||
}
|
||||
];
|
||||
</script>
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
<script lang="ts">
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import Boolean from './columns/boolean.svelte';
|
||||
import Datetime from './columns/datetime.svelte';
|
||||
import Enum from './columns/enum.svelte';
|
||||
import Integer from './columns/integer.svelte';
|
||||
import Relationship from './columns/relationship.svelte';
|
||||
import String from './columns/string.svelte';
|
||||
import Url from './columns/url.svelte';
|
||||
|
||||
export let id: string;
|
||||
export let label: string;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
export let value: string | number | boolean | null | string[];
|
||||
export let editing = false;
|
||||
export let column:
|
||||
| Models.ColumnBoolean
|
||||
| Models.ColumnEmail
|
||||
| Models.ColumnEnum
|
||||
| Models.ColumnFloat
|
||||
| Models.ColumnInteger
|
||||
| Models.ColumnIp
|
||||
| Models.ColumnString
|
||||
| Models.ColumnDatetime
|
||||
| Models.ColumnUrl;
|
||||
|
||||
const columnsTypeMap = {
|
||||
string: String,
|
||||
integer: Integer,
|
||||
double: Integer,
|
||||
boolean: Boolean,
|
||||
datetime: Datetime,
|
||||
relationship: Relationship
|
||||
};
|
||||
|
||||
const columnsFormatMap = {
|
||||
ip: String,
|
||||
url: Url,
|
||||
email: String,
|
||||
enum: Enum
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if column.type in columnsTypeMap}
|
||||
{#if 'format' in column && column.format}
|
||||
<svelte:component
|
||||
this={columnsFormatMap[column.format]}
|
||||
{id}
|
||||
{label}
|
||||
{column}
|
||||
{optionalText}
|
||||
bind:value />
|
||||
{:else}
|
||||
<svelte:component
|
||||
this={columnsTypeMap[column.type]}
|
||||
{id}
|
||||
{editing}
|
||||
{label}
|
||||
{column}
|
||||
{optionalText}
|
||||
bind:value />
|
||||
{/if}
|
||||
{/if}
|
||||
+9
-9
@@ -1,22 +1,22 @@
|
||||
<script lang="ts">
|
||||
import CustomId from '$lib/components/customId.svelte';
|
||||
import { Icon, Layout, Tag } from '@appwrite.io/pink-svelte';
|
||||
import type { Attributes } from '../store';
|
||||
import AttributeItem from './attributeItem.svelte';
|
||||
import type { Columns } from '../store';
|
||||
import ColumnItem from './columnItem.svelte';
|
||||
import { IconPencil } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
export let attributes: Attributes[] = [];
|
||||
export let columns: Columns[] = [];
|
||||
export let formValues: object = {};
|
||||
export let customId: string | null | undefined = undefined;
|
||||
|
||||
let showCustomId = false;
|
||||
</script>
|
||||
|
||||
{#if attributes.length}
|
||||
{#if columns.length}
|
||||
<Layout.Stack>
|
||||
{#each attributes as attribute}
|
||||
{@const label = attribute.key}
|
||||
<AttributeItem {attribute} bind:formValues {label} />
|
||||
{#each columns as column}
|
||||
{@const label = column.key}
|
||||
<ColumnItem {column} bind:formValues {label} />
|
||||
{/each}
|
||||
|
||||
{#if customId !== undefined}
|
||||
@@ -24,11 +24,11 @@
|
||||
<span>
|
||||
<Tag size="s" on:click={() => (showCustomId = !showCustomId)}>
|
||||
<Icon icon={IconPencil} slot="start" size="s" />
|
||||
Document ID
|
||||
Row ID
|
||||
</Tag>
|
||||
</span>
|
||||
{:else}
|
||||
<CustomId autofocus bind:show={showCustomId} name="Document" bind:id={customId} />
|
||||
<CustomId autofocus bind:show={showCustomId} name="Row" bind:id={customId} />
|
||||
{/if}
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
+25
-25
@@ -3,10 +3,10 @@
|
||||
import { capitalize } from '$lib/helpers/string';
|
||||
import { Icon, Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import Attribute from './attribute.svelte';
|
||||
import type { Attributes } from '../store';
|
||||
import Column from './column.svelte';
|
||||
import type { Columns } from '../store';
|
||||
|
||||
export let attribute: Attributes;
|
||||
export let column: Columns;
|
||||
export let formValues: object = {};
|
||||
export let label: string;
|
||||
export let editing = false;
|
||||
@@ -25,9 +25,9 @@
|
||||
};
|
||||
}
|
||||
|
||||
function getAttributeType(attribute: Attributes) {
|
||||
if ('format' in attribute) {
|
||||
switch (attribute.format) {
|
||||
function getColumnType(column: Columns) {
|
||||
if ('format' in column) {
|
||||
switch (column.format) {
|
||||
case 'ip':
|
||||
return 'IP';
|
||||
case 'email':
|
||||
@@ -41,41 +41,41 @@
|
||||
break;
|
||||
}
|
||||
}
|
||||
return `${capitalize(attribute.type)}${attribute.array ? '[]' : ''}`;
|
||||
return `${capitalize(column.type)}${column.array ? '[]' : ''}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if attribute.array}
|
||||
{#if formValues[attribute.key]?.length === 0}
|
||||
{#if column.array}
|
||||
{#if formValues[column.key]?.length === 0}
|
||||
<Layout.Stack direction="row" alignContent="space-between">
|
||||
<Layout.Stack gap="xxs" direction="row" alignItems="center">
|
||||
<Typography.Text variant="m-500">{label}</Typography.Text>
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-tertiary">
|
||||
{getAttributeType(attribute)}
|
||||
{getColumnType(column)}
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<Button secondary on:click={() => addArrayItem(attribute.key)}>
|
||||
<Button secondary on:click={() => addArrayItem(column.key)}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Add item
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
{:else}
|
||||
<Layout.Stack>
|
||||
{#each [...formValues[attribute.key].keys()] as index}
|
||||
{#each [...(formValues[column.key]?.keys() ?? [])] as index}
|
||||
<Layout.Stack direction="row" alignItems="flex-end" gap="xs">
|
||||
<Attribute
|
||||
{attribute}
|
||||
id={`${attribute.key}-${index}`}
|
||||
optionalText={index === 0 ? getAttributeType(attribute) : undefined}
|
||||
<Column
|
||||
{column}
|
||||
id={`${column.key}-${index}`}
|
||||
optionalText={index === 0 ? getColumnType(column) : undefined}
|
||||
label={index === 0 ? label : ''}
|
||||
bind:value={formValues[attribute.key][index]} />
|
||||
<Button text icon on:click={() => removeArrayItem(attribute.key, index)}>
|
||||
bind:value={formValues[column.key][index]} />
|
||||
<Button text icon on:click={() => removeArrayItem(column.key, index)}>
|
||||
<span class="icon-x" aria-hidden="true"></span>
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
{/each}
|
||||
<div>
|
||||
<Button secondary on:click={() => addArrayItem(attribute.key)}>
|
||||
<Button secondary on:click={() => addArrayItem(column.key)}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Add item
|
||||
</Button>
|
||||
@@ -83,11 +83,11 @@
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
{:else}
|
||||
<Attribute
|
||||
{editing}
|
||||
{attribute}
|
||||
<Column
|
||||
{label}
|
||||
optionalText={getAttributeType(attribute)}
|
||||
id={attribute.key}
|
||||
bind:value={formValues[attribute.key]} />
|
||||
{editing}
|
||||
{column}
|
||||
id={column.key}
|
||||
optionalText={getColumnType(column)}
|
||||
bind:value={formValues[column.key]} />
|
||||
{/if}
|
||||
+3
-3
@@ -5,7 +5,7 @@
|
||||
export let id: string;
|
||||
export let label: string;
|
||||
export let value: boolean;
|
||||
export let attribute: Models.AttributeBoolean;
|
||||
export let column: Models.ColumnBoolean;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
</script>
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
bind:value
|
||||
{optionalText}
|
||||
placeholder="Select a value"
|
||||
required={attribute.required}
|
||||
required={column.required}
|
||||
options={[
|
||||
!attribute.required && { label: 'NULL', value: null },
|
||||
!column.required && { label: 'NULL', value: null },
|
||||
{ label: 'True', value: true },
|
||||
{ label: 'False', value: false }
|
||||
].filter(Boolean)} />
|
||||
+3
-3
@@ -5,7 +5,7 @@
|
||||
export let id: string;
|
||||
export let label: string;
|
||||
export let value: string;
|
||||
export let attribute: Models.AttributeDatetime;
|
||||
export let column: Models.ColumnDatetime;
|
||||
export let type: 'date' | 'time' | 'datetime-local' = 'datetime-local';
|
||||
</script>
|
||||
|
||||
@@ -14,5 +14,5 @@
|
||||
{label}
|
||||
{type}
|
||||
bind:value
|
||||
required={attribute.required}
|
||||
nullable={!attribute.required} />
|
||||
required={column.required}
|
||||
nullable={!column.required} />
|
||||
+3
-3
@@ -5,7 +5,7 @@
|
||||
export let id: string;
|
||||
export let label: string;
|
||||
export let value: string | null;
|
||||
export let attribute: Models.AttributeUrl;
|
||||
export let column: Models.ColumnEmail;
|
||||
</script>
|
||||
|
||||
<InputEmail
|
||||
@@ -13,5 +13,5 @@
|
||||
{label}
|
||||
bind:value
|
||||
placeholder="Enter URL"
|
||||
required={attribute.required}
|
||||
nullable={!attribute.required} />
|
||||
required={column.required}
|
||||
nullable={!column.required} />
|
||||
+4
-4
@@ -5,17 +5,17 @@
|
||||
export let id: string;
|
||||
export let label: string;
|
||||
export let value: string;
|
||||
export let attribute: Models.AttributeEnum;
|
||||
export let column: Models.ColumnEnum;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
|
||||
$: options = [
|
||||
...attribute.elements.map((element) => {
|
||||
...column.elements.map((element) => {
|
||||
return {
|
||||
label: element,
|
||||
value: element
|
||||
};
|
||||
}),
|
||||
!attribute.required && {
|
||||
!column.required && {
|
||||
label: 'NULL',
|
||||
value: null
|
||||
}
|
||||
@@ -28,5 +28,5 @@
|
||||
{id}
|
||||
{label}
|
||||
{optionalText}
|
||||
required={attribute.required}
|
||||
required={column.required}
|
||||
placeholder="Select a value" />
|
||||
+6
-6
@@ -5,15 +5,15 @@
|
||||
export let id: string;
|
||||
export let label: string;
|
||||
export let value: number;
|
||||
export let attribute: Models.AttributeInteger;
|
||||
export let column: Models.ColumnInteger;
|
||||
</script>
|
||||
|
||||
<InputNumber
|
||||
{id}
|
||||
{label}
|
||||
bind:value
|
||||
min={attribute.min}
|
||||
max={attribute.max}
|
||||
required={attribute.required}
|
||||
nullable={!attribute.required}
|
||||
step={attribute.type === 'double' ? 'any' : 1} />
|
||||
min={column.min}
|
||||
max={column.max}
|
||||
required={column.required}
|
||||
nullable={!column.required}
|
||||
step={column.type === 'double' ? 'any' : 1} />
|
||||
+24
-24
@@ -5,7 +5,7 @@
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Query, type Models } from '@appwrite.io/console';
|
||||
import { onMount } from 'svelte';
|
||||
import { doc } from '../store';
|
||||
import { row } from '../store';
|
||||
import { isRelationshipToMany } from './store';
|
||||
import { IconPlus, IconX } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Icon, Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
@@ -15,12 +15,12 @@
|
||||
|
||||
export let editing = false;
|
||||
export let value: string | string[];
|
||||
export let attribute: Models.AttributeRelationship;
|
||||
export let column: Models.ColumnRelationship;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
|
||||
const databaseId = page.params.database;
|
||||
|
||||
let documentList: Models.DocumentList<Models.Document>;
|
||||
let rowList: Models.RowList<Models.Row>;
|
||||
let search: string = null;
|
||||
let displayNames = ['$id'];
|
||||
let relatedList: string[] = [];
|
||||
@@ -32,38 +32,38 @@
|
||||
|
||||
onMount(async () => {
|
||||
if (value) {
|
||||
if (isRelationshipToMany(attribute)) {
|
||||
if (isRelationshipToMany(column)) {
|
||||
relatedList = (value as string[]).slice();
|
||||
} else {
|
||||
singleRel = value as string;
|
||||
}
|
||||
}
|
||||
|
||||
if (editing && $doc?.[attribute.key]) {
|
||||
if ($doc[attribute.key]?.length) {
|
||||
if (editing && $row?.[column.key]) {
|
||||
if ($row[column.key]?.length) {
|
||||
relatedList =
|
||||
$doc[attribute.key]?.map((d: Record<string, unknown>) => {
|
||||
$row[column.key]?.map((d: Record<string, unknown>) => {
|
||||
return d?.$id;
|
||||
}) ?? [];
|
||||
} else {
|
||||
singleRel = $doc[attribute.key]?.$id;
|
||||
singleRel = $row[column.key]?.$id;
|
||||
}
|
||||
}
|
||||
|
||||
displayNames = preferences.getDisplayNames()?.[attribute?.relatedCollection] ?? ['$id'];
|
||||
displayNames = preferences.getDisplayNames()?.[column?.relatedTable] ?? ['$id'];
|
||||
if (!displayNames?.includes('$id')) {
|
||||
displayNames.unshift('$id');
|
||||
}
|
||||
});
|
||||
|
||||
async function getDocuments(search: string = null) {
|
||||
async function getRows(search: string = null) {
|
||||
const queries = search
|
||||
? [Query.select(['$id']), Query.startsWith('$id', search), Query.orderDesc('')]
|
||||
: [Query.select(['$id'])];
|
||||
|
||||
return await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.listDocuments(databaseId, attribute.relatedCollection, queries);
|
||||
.tables.listRows(databaseId, column.relatedTable, queries);
|
||||
}
|
||||
|
||||
function getAvailableOptions(excludeIndex?: number) {
|
||||
@@ -105,7 +105,7 @@
|
||||
}
|
||||
|
||||
// Reactive statements
|
||||
$: getDocuments(search).then((res) => (documentList = res));
|
||||
$: getRows(search).then((res) => (rowList = res));
|
||||
|
||||
$: paginatedItems = editing
|
||||
? relatedList
|
||||
@@ -117,10 +117,10 @@
|
||||
$: totalCount = relatedList?.length ?? 0;
|
||||
|
||||
$: options =
|
||||
documentList?.documents?.map((document) => {
|
||||
rowList?.rows?.map((row) => {
|
||||
const names = displayNames.filter((name) => name !== '$id');
|
||||
const values = names
|
||||
.map((name) => document?.[name])
|
||||
.map((name) => row?.[name])
|
||||
// always supposed to be a string but just being a bit safe here
|
||||
.filter((value) => value != null && typeof value === 'string' && value !== '');
|
||||
|
||||
@@ -130,15 +130,15 @@
|
||||
values.map((value) => (value.length > 5 ? value.slice(0, 5) + '...' : value));
|
||||
|
||||
const label = !values.length
|
||||
? document.$id
|
||||
? row.$id
|
||||
: // values are in `$id (a | b)` format
|
||||
// previously used to have a `$id a | b`.
|
||||
`${document.$id} (${displayValues.join(' | ')})`;
|
||||
`${row.$id} (${displayValues.join(' | ')})`;
|
||||
|
||||
return {
|
||||
label,
|
||||
value: document.$id,
|
||||
data: names.map((name) => document?.[name])
|
||||
value: row.$id,
|
||||
data: names.map((name) => row?.[name])
|
||||
};
|
||||
}) ?? [];
|
||||
|
||||
@@ -150,7 +150,7 @@
|
||||
$: showEmptyInput = editing && totalCount === 0 && !showInput;
|
||||
</script>
|
||||
|
||||
{#if isRelationshipToMany(attribute)}
|
||||
{#if isRelationshipToMany(column)}
|
||||
<Layout.Stack gap="xxl">
|
||||
<Layout.Stack gap="m">
|
||||
<Layout.Stack direction="row" alignContent="space-between">
|
||||
@@ -178,7 +178,7 @@
|
||||
required
|
||||
{options}
|
||||
bind:value={relatedList[0]}
|
||||
placeholder={`Select ${attribute.key}`}
|
||||
placeholder={`Select ${column.key}`}
|
||||
on:change={() => {
|
||||
if (!relatedList[0]) relatedList = [''];
|
||||
updateRelatedList();
|
||||
@@ -196,7 +196,7 @@
|
||||
required
|
||||
options={getAvailableOptions(actualIndex)}
|
||||
bind:value={relatedList[actualIndex]}
|
||||
placeholder={`Select ${attribute.key}`}
|
||||
placeholder={`Select ${column.key}`}
|
||||
on:change={updateRelatedList} />
|
||||
{#if relatedList[actualIndex]}
|
||||
<div style:padding-block-start="0.5rem">
|
||||
@@ -244,7 +244,7 @@
|
||||
<InputSelect
|
||||
{id}
|
||||
required
|
||||
placeholder={`Select ${attribute.key}`}
|
||||
placeholder={`Select ${column.key}`}
|
||||
bind:value={newItemValue}
|
||||
options={getAvailableOptions()}
|
||||
on:change={addNewItem} />
|
||||
@@ -272,8 +272,8 @@
|
||||
{id}
|
||||
{label}
|
||||
{options}
|
||||
required={attribute.required}
|
||||
placeholder={`Select ${attribute.key}`}
|
||||
required={column.required}
|
||||
placeholder={`Select ${column.key}`}
|
||||
bind:value={singleRel}
|
||||
on:change={() => (value = singleRel)} />
|
||||
{/if}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import { type Models, Query } from '@appwrite.io/console';
|
||||
import type { Columns } from '../../store';
|
||||
|
||||
export function isRelationshipToMany(column: Models.ColumnRelationship) {
|
||||
if (!column) return false;
|
||||
if (!column?.relationType) return false;
|
||||
if (column?.side === 'child') {
|
||||
return !['oneToOne', 'oneToMany'].includes(column?.relationType);
|
||||
} else {
|
||||
return !['oneToOne', 'manyToOne'].includes(column?.relationType);
|
||||
}
|
||||
}
|
||||
|
||||
export function isRelationship(column: Columns): column is Models.ColumnRelationship {
|
||||
if (!column) return false;
|
||||
return column?.type === 'relationship';
|
||||
}
|
||||
|
||||
export function isString(column: Columns): column is Models.ColumnString {
|
||||
if (!column) return false;
|
||||
return column?.type === 'string';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns select queries for all main and related fields in a table.
|
||||
*/
|
||||
export function buildWildcardColumnsQuery(table: Models.Table | null = null): string[] {
|
||||
return [
|
||||
...(table?.columns
|
||||
?.filter((col) => isRelationship(col))
|
||||
?.map((col) => Query.select([`${col.key}.*`])) ?? []),
|
||||
|
||||
Query.select(['*'])
|
||||
];
|
||||
}
|
||||
+8
-8
@@ -5,25 +5,25 @@
|
||||
export let id: string;
|
||||
export let label: string;
|
||||
export let value: string;
|
||||
export let attribute: Models.AttributeString;
|
||||
export let column: Models.ColumnString;
|
||||
</script>
|
||||
|
||||
{#if attribute.size >= 50}
|
||||
{#if column.size >= 50}
|
||||
<InputTextarea
|
||||
{id}
|
||||
{label}
|
||||
bind:value
|
||||
placeholder="Enter string"
|
||||
maxlength={attribute.size}
|
||||
required={attribute.required}
|
||||
nullable={!attribute.required} />
|
||||
maxlength={column.size}
|
||||
required={column.required}
|
||||
nullable={!column.required} />
|
||||
{:else}
|
||||
<InputText
|
||||
{id}
|
||||
{label}
|
||||
bind:value
|
||||
placeholder="Enter string"
|
||||
maxlength={attribute.size}
|
||||
required={attribute.required}
|
||||
nullable={!attribute.required} />
|
||||
maxlength={column.size}
|
||||
required={column.required}
|
||||
nullable={!column.required} />
|
||||
{/if}
|
||||
+3
-3
@@ -5,7 +5,7 @@
|
||||
export let id: string;
|
||||
export let label: string;
|
||||
export let value: string | null;
|
||||
export let attribute: Models.AttributeUrl;
|
||||
export let column: Models.ColumnUrl;
|
||||
</script>
|
||||
|
||||
<InputURL
|
||||
@@ -13,5 +13,5 @@
|
||||
{label}
|
||||
bind:value
|
||||
placeholder="Enter URL"
|
||||
required={attribute.required}
|
||||
nullable={!attribute.required} />
|
||||
required={column.required}
|
||||
nullable={!column.required} />
|
||||
+23
-27
@@ -7,9 +7,9 @@
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Alert, Selector, Table } from '@appwrite.io/pink-svelte';
|
||||
import { attributes, collection } from '../store';
|
||||
import { isRelationship, isRelationshipToMany } from './attributes/store';
|
||||
import { Alert, Layout, Selector, Table } from '@appwrite.io/pink-svelte';
|
||||
import { columns, table } from '../store';
|
||||
import { isRelationship, isRelationshipToMany } from './columns/store';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
export let showDelete = false;
|
||||
@@ -20,32 +20,32 @@
|
||||
try {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.deleteDocument(databaseId, page.params.collection, page.params.document);
|
||||
.tables.deleteRow(databaseId, page.params.table, page.params.row);
|
||||
showDelete = false;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `Document has been deleted`
|
||||
message: `Row has been deleted`
|
||||
});
|
||||
trackEvent(Submit.DocumentDelete);
|
||||
trackEvent(Submit.RowDelete);
|
||||
await goto(
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${page.params.database}/collection-${page.params.collection}`
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${page.params.database}/table-${page.params.table}`
|
||||
);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.DocumentDelete);
|
||||
trackError(error, Submit.RowDelete);
|
||||
}
|
||||
};
|
||||
|
||||
enum Deletion {
|
||||
'setNull' = 'Set document ID as NULL in all related documents',
|
||||
'cascade' = 'All related documents will be deleted',
|
||||
'restrict' = 'Document cannot be deleted'
|
||||
'setNull' = 'Set row ID as NULL in all related rows',
|
||||
'cascade' = 'All related rows will be deleted',
|
||||
'restrict' = 'Row cannot be deleted'
|
||||
}
|
||||
|
||||
$: relAttributes = $attributes?.filter(
|
||||
$: relColumns = $columns?.filter(
|
||||
(attribute) =>
|
||||
isRelationship(attribute) &&
|
||||
// One-to-One are always included
|
||||
@@ -56,18 +56,16 @@
|
||||
(attribute.relationType === 'manyToOne' && attribute.side === 'child') ||
|
||||
// Many-to-Many: Only include if the parent is being deleted
|
||||
(isRelationshipToMany(attribute) && attribute.side === 'parent'))
|
||||
) as Models.AttributeRelationship[];
|
||||
) as Models.ColumnRelationship[];
|
||||
</script>
|
||||
|
||||
<Confirm title="Delete document" onSubmit={handleDelete} bind:open={showDelete}>
|
||||
<Confirm title="Delete row" onSubmit={handleDelete} bind:open={showDelete}>
|
||||
<p data-private>
|
||||
Are you sure you want to delete <b
|
||||
>the document from <span data-private>{$collection.name}</span></b
|
||||
>?
|
||||
Are you sure you want to delete <b>the row from <span data-private>{$table.name}</span></b>?
|
||||
</p>
|
||||
|
||||
{#if relAttributes?.length}
|
||||
<p class="text">This document contains the following relationships:</p>
|
||||
{#if relColumns?.length}
|
||||
<p class="text">This row contains the following relationships:</p>
|
||||
<Table.Root
|
||||
let:root
|
||||
columns={[
|
||||
@@ -80,7 +78,7 @@
|
||||
<Table.Header.Cell column="setting" {root}>Setting</Table.Header.Cell>
|
||||
<Table.Header.Cell column="desc" {root} />
|
||||
</svelte:fragment>
|
||||
{#each relAttributes as attr}
|
||||
{#each relColumns as attr}
|
||||
<Table.Row.Base {root}>
|
||||
<Table.Cell column="relations" {root}>
|
||||
<span class="u-flex u-cross-center u-gap-8">
|
||||
@@ -101,18 +99,16 @@
|
||||
</Table.Row.Base>
|
||||
{/each}
|
||||
</Table.Root>
|
||||
<div class="u-flex u-flex-vertical u-gap-16">
|
||||
<Layout.Stack gap="l">
|
||||
<Alert.Inline
|
||||
title="To change these deletion behaviors edit the relationship settings." />
|
||||
<Selector.Checkbox
|
||||
id="delete"
|
||||
label={`Delete document from ${$collection.name}`}
|
||||
bind:checked />
|
||||
</div>
|
||||
|
||||
<Selector.Checkbox id="delete" label="Delete row from ${$table.name}" bind:checked />
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
|
||||
<Button danger submit disabled={relAttributes?.length && !checked}>Delete</Button>
|
||||
<Button danger submit disabled={relColumns?.length && !checked}>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</Confirm>
|
||||
Loaded 100 of 128 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user