fix: wallet create pazzle list layout #37

Closed
laurin wants to merge 107 commits from fix/wallet-create-pazzle-display into master
  1. 1
      .gitignore
  2. 14
      .prettierrc.json
  3. 290
      Cargo.lock
  4. 48
      README.md
  5. 3
      nextgraph/.gitignore
  6. 6
      nextgraph/Cargo.toml
  7. 36
      nextgraph/src/lib.rs
  8. 1094
      nextgraph/src/local_broker.rs
  9. 1
      nextgraph/src/local_broker_dev_env.rs
  10. 25
      ng-app/index-native.html
  11. 57
      ng-app/index-web.html
  12. 16
      ng-app/package.json
  13. 5
      ng-app/src-tauri/Cargo.toml
  14. 2
      ng-app/src-tauri/build.rs
  15. 1
      ng-app/src-tauri/gen/android/app/src/main/AndroidManifest.xml
  16. 3
      ng-app/src-tauri/gen/android/buildSrc/src/main/java/org/nextgraph/ng_app_native/kotlin/BuildTask.kt
  17. 312
      ng-app/src-tauri/src/lib.rs
  18. 33
      ng-app/src/App.svelte
  19. 134
      ng-app/src/api.ts
  20. 37
      ng-app/src/apps/ContainerView.svelte
  21. 125
      ng-app/src/apps/SparqlQueryEditor.svelte
  22. 78
      ng-app/src/apps/SparqlUpdateEditor.svelte
  23. 82
      ng-app/src/apps/TurtleViewer.svelte
  24. 2
      ng-app/src/assets/nextgraph-nofill.svg
  25. 59
      ng-app/src/base64url.js
  26. 486
      ng-app/src/classes.ts
  27. 16
      ng-app/src/lib/CenteredLayout.svelte
  28. 187
      ng-app/src/lib/DataClassIcon.svelte
  29. 110
      ng-app/src/lib/Document.svelte
  30. 1247
      ng-app/src/lib/FullLayout.svelte
  31. 80
      ng-app/src/lib/Home.svelte
  32. 33
      ng-app/src/lib/Install.svelte
  33. 820
      ng-app/src/lib/Login.svelte
  34. 2
      ng-app/src/lib/MobileBottomBar.svelte
  35. 2
      ng-app/src/lib/MobileBottomBarItem.svelte
  36. 23
      ng-app/src/lib/NoWallet.svelte
  37. 38
      ng-app/src/lib/Pane.svelte
  38. 757
      ng-app/src/lib/Test.svelte
  39. 91
      ng-app/src/lib/components/CopyToClipboard.svelte
  40. 40
      ng-app/src/lib/components/Logo.svelte
  41. 32
      ng-app/src/lib/components/MenuItem.svelte
  42. 64
      ng-app/src/lib/components/Message.svelte
  43. 93
      ng-app/src/lib/components/NavBar.svelte
  44. 49
      ng-app/src/lib/components/PaneHeader.svelte
  45. 89
      ng-app/src/lib/components/PasswordInput.svelte
  46. 38
      ng-app/src/lib/components/Spinner.svelte
  47. 3
      ng-app/src/lib/icons/BrailleIcon.svelte
  48. 13
      ng-app/src/lib/icons/BranchIcon.svelte
  49. 8
      ng-app/src/lib/icons/ChemistryIcon.svelte
  50. 187
      ng-app/src/lib/icons/DataClassIcon.svelte
  51. 57
      ng-app/src/lib/icons/DeviceIcon.svelte
  52. 4
      ng-app/src/lib/icons/GuitarIcon.svelte
  53. 3
      ng-app/src/lib/icons/JsIcon.svelte
  54. 4
      ng-app/src/lib/icons/JsonIcon.svelte
  55. 10
      ng-app/src/lib/icons/JsonLdIcon.svelte
  56. 6
      ng-app/src/lib/icons/MarkdownIcon.svelte
  57. 64
      ng-app/src/lib/icons/NavIcon.svelte
  58. 3
      ng-app/src/lib/icons/PdfIcon.svelte
  59. 9
      ng-app/src/lib/icons/RdfIcon.svelte
  60. 3
      ng-app/src/lib/icons/ReactIcon.svelte
  61. 3
      ng-app/src/lib/icons/RustIcon.svelte
  62. 3
      ng-app/src/lib/icons/SvelteIcon.svelte
  63. 3
      ng-app/src/lib/icons/TsIcon.svelte
  64. 35
      ng-app/src/lib/icons/TurtleIcon.svelte
  65. 5
      ng-app/src/lib/icons/TxtIcon.svelte
  66. 19
      ng-app/src/lib/icons/ZeraIcon.svelte
  67. 238
      ng-app/src/lib/panes/Files.svelte
  68. 155
      ng-app/src/lib/panes/History.svelte
  69. 0
      ng-app/src/lib/panes/history/LICENSE.md
  70. 0
      ng-app/src/lib/panes/history/gitgraph-core/branch.ts
  71. 0
      ng-app/src/lib/panes/history/gitgraph-core/branches-paths.ts
  72. 0
      ng-app/src/lib/panes/history/gitgraph-core/commit.ts
  73. 59
      ng-app/src/lib/panes/history/gitgraph-core/gitgraph-user-api.ts
  74. 0
      ng-app/src/lib/panes/history/gitgraph-core/gitgraph.ts
  75. 0
      ng-app/src/lib/panes/history/gitgraph-core/graph-rows.ts
  76. 0
      ng-app/src/lib/panes/history/gitgraph-core/index.ts
  77. 0
      ng-app/src/lib/panes/history/gitgraph-core/regular-graph-rows.ts
  78. 0
      ng-app/src/lib/panes/history/gitgraph-core/template.ts
  79. 0
      ng-app/src/lib/panes/history/gitgraph-core/utils.ts
  80. 21
      ng-app/src/lib/panes/history/gitgraph-js/gitgraph.ts
  81. 7
      ng-app/src/lib/panes/history/gitgraph-js/svg-elements.ts
  82. 2
      ng-app/src/lib/panes/history/gitgraph-js/tooltip.ts
  83. 600
      ng-app/src/locales/de.json
  84. 861
      ng-app/src/locales/en.json
  85. 231
      ng-app/src/locales/es.json
  86. 231
      ng-app/src/locales/fr.json
  87. 231
      ng-app/src/locales/it.json
  88. 231
      ng-app/src/locales/pt.json
  89. 231
      ng-app/src/locales/ru.json
  90. 231
      ng-app/src/locales/zh.json
  91. 365
      ng-app/src/routes/AccountInfo.svelte
  92. 15
      ng-app/src/routes/Home.svelte
  93. 4
      ng-app/src/routes/Install.svelte
  94. 2
      ng-app/src/routes/Invitation.svelte
  95. 61
      ng-app/src/routes/NURI.svelte
  96. 21
      ng-app/src/routes/NotFound.svelte
  97. 148
      ng-app/src/routes/ScanQR.svelte
  98. 54
      ng-app/src/routes/Shared.svelte
  99. 46
      ng-app/src/routes/Site.svelte
  100. 214
      ng-app/src/routes/User.svelte
  101. Some files were not shown because too many files have changed in this diff Show More

1
.gitignore vendored

@ -14,3 +14,4 @@ node_modules
*/tests/*.mnemonic
*/ng-example/*
.vscode/settings.json
.env.local

@ -0,0 +1,14 @@
{
"plugins": ["prettier-plugin-svelte"],
"overrides": [
{
"files": "*",
"excludeFiles": ["*.svelte", "*.html", "*.json"],
"options": {
"tabWidth": 4
}
},
{ "files": "*.svelte", "options": { "parser": "svelte" } }
],
"trailingComma": "es5"
}

290
Cargo.lock generated

@ -520,6 +520,12 @@ version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64-url"
version = "2.0.0"
@ -556,15 +562,6 @@ dependencies = [
"syn 2.0.58",
]
[[package]]
name = "bit-vec"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
dependencies = [
"serde",
]
[[package]]
name = "bit_field"
version = "0.10.2"
@ -664,17 +661,6 @@ dependencies = [
"log",
]
[[package]]
name = "bloomfilter"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b64d54e47a7f4fd723f082e8f11429f3df6ba8adaeca355a76556f9f0602bbcf"
dependencies = [
"bit-vec",
"getrandom 0.2.10",
"siphasher 1.0.1",
]
[[package]]
name = "brotli"
version = "3.3.4"
@ -1421,6 +1407,12 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]]
name = "data-url"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a"
[[package]]
name = "debug_print"
version = "1.0.0"
@ -1731,6 +1723,12 @@ dependencies = [
"getrandom 0.2.10",
]
[[package]]
name = "fastrange-rs"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e90a1392cd6ec5ebe42ccaf251f2b7ba6be654c377f05c913f3898bfb2172512"
[[package]]
name = "fdeflate"
version = "0.3.0"
@ -1791,6 +1789,12 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "float-cmp"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
[[package]]
name = "flume"
version = "0.10.14"
@ -2620,6 +2624,12 @@ dependencies = [
"tiff",
]
[[package]]
name = "imagesize"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284"
[[package]]
name = "indexmap"
version = "1.9.3"
@ -2797,14 +2807,13 @@ checksum = "f850fafca79ebacd70eab9d80cb75a33aeda38bde8f3dd784c1837cdf0bde631"
[[package]]
name = "json-patch"
version = "1.0.0"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f54898088ccb91df1b492cc80029a6fdf1c48ca0db7c6822a8babad69c94658"
checksum = "ec9ad60d674508f3ca8f380a928cfe7b096bc729c4e2dbfe3852bc45da3ab30b"
dependencies = [
"serde",
"serde_json",
"thiserror",
"treediff",
]
[[package]]
@ -2831,6 +2840,16 @@ dependencies = [
"selectors",
]
[[package]]
name = "kurbo"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e5aa9f0f96a938266bdb12928a67169e8d22c6a786fda8ed984b85e6ba93c3c"
dependencies = [
"arrayvec",
"smallvec",
]
[[package]]
name = "kv-log-macro"
version = "1.0.7"
@ -2942,9 +2961,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.19"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
dependencies = [
"value-bag",
]
@ -3264,6 +3283,7 @@ dependencies = [
"async-trait",
"base64-url",
"futures",
"lazy_static",
"ng-client-ws",
"ng-net",
"ng-repo",
@ -3271,9 +3291,14 @@ dependencies = [
"ng-verifier",
"ng-wallet",
"once_cell",
"pdf-writer",
"qrcode",
"serde_bare",
"serde_bytes",
"serde_json",
"svg2pdf",
"web-time",
"whoami",
"zeroize",
]
@ -3287,13 +3312,17 @@ dependencies = [
"ng-net",
"ng-repo",
"ng-wallet",
"oxrdf",
"serde",
"serde_bare",
"serde_bytes",
"serde_json",
"sys-locale",
"tauri",
"tauri-build",
"tauri-plugin-barcode-scanner",
"tauri-plugin-window",
"tauri-utils",
]
[[package]]
@ -3399,6 +3428,7 @@ dependencies = [
"libc",
"md-5",
"memchr",
"ng-repo",
"ng-rocksdb",
"oxilangtag",
"oxiri",
@ -3420,7 +3450,6 @@ version = "0.1.0-preview.1"
dependencies = [
"base64-url",
"blake3",
"bloomfilter",
"chacha20",
"crypto_box",
"current_platform",
@ -3430,11 +3459,13 @@ dependencies = [
"futures",
"getrandom 0.2.10",
"gloo-timers",
"lazy_static",
"log",
"num_enum",
"once_cell",
"os_info",
"rand 0.7.3",
"sbbf-rs-safe",
"serde",
"serde_bare",
"serde_bytes",
@ -3467,6 +3498,7 @@ name = "ng-sdk-js"
version = "0.1.0-preview.1"
dependencies = [
"async-std",
"futures",
"getrandom 0.1.16",
"gloo-timers",
"js-sys",
@ -3482,7 +3514,6 @@ dependencies = [
"serde-wasm-bindgen",
"serde_bare",
"serde_bytes",
"serde_json",
"sys-locale",
"wasm-bindgen",
"wasm-bindgen-futures",
@ -3524,7 +3555,6 @@ dependencies = [
"async-std",
"async-trait",
"automerge",
"bloomfilter",
"either",
"futures",
"getrandom 0.2.10",
@ -3533,9 +3563,11 @@ dependencies = [
"ng-repo",
"ng-storage-rocksdb",
"rand 0.7.3",
"sbbf-rs-safe",
"serde",
"serde_bare",
"serde_bytes",
"serde_json",
"web-time",
"yrs",
]
@ -3547,6 +3579,7 @@ dependencies = [
"aes-gcm-siv",
"argon2",
"async-std",
"base64-url",
"blake3",
"chacha20poly1305",
"crypto_box",
@ -3783,6 +3816,15 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "num_threads"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
dependencies = [
"libc",
]
[[package]]
name = "objc"
version = "0.2.7"
@ -4040,6 +4082,18 @@ version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
[[package]]
name = "pdf-writer"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af6a7882fda7808481d43c51cadfc3ec934c6af72612a1fe6985ce329a2f0469"
dependencies = [
"bitflags 2.5.0",
"itoa 1.0.6",
"memchr",
"ryu",
]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
@ -4197,6 +4251,12 @@ dependencies = [
"siphasher 0.3.10",
]
[[package]]
name = "pico-args"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
[[package]]
name = "pin-project"
version = "1.1.0"
@ -4436,6 +4496,12 @@ dependencies = [
"bytemuck",
]
[[package]]
name = "qrcode"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec"
[[package]]
name = "quick-xml"
version = "0.28.2"
@ -4608,6 +4674,15 @@ dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_users"
version = "0.4.3"
@ -4690,6 +4765,12 @@ dependencies = [
"winreg 0.10.1",
]
[[package]]
name = "roxmltree"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
[[package]]
name = "rust-embed"
version = "6.7.0"
@ -4821,6 +4902,25 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "sbbf-rs"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5525db49c7719816ac719ea8ffd0d0b4586db1a3f5d3e7751593230dacc642fd"
dependencies = [
"cpufeatures",
"fastrange-rs",
]
[[package]]
name = "sbbf-rs-safe"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9902ffeb2cff3f8c072c60c7d526ac9560fc9a66fe1dfc3c240eba5e2151ba3c"
dependencies = [
"sbbf-rs",
]
[[package]]
name = "schannel"
version = "0.1.22"
@ -5139,6 +5239,15 @@ version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f"
[[package]]
name = "simplecss"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d"
dependencies = [
"log",
]
[[package]]
name = "siphasher"
version = "0.3.10"
@ -5150,9 +5259,6 @@ name = "siphasher"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
dependencies = [
"serde",
]
[[package]]
name = "sized-chunks"
@ -5271,6 +5377,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strict-num"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
dependencies = [
"float-cmp",
]
[[package]]
name = "string_cache"
version = "0.8.7"
@ -5309,6 +5424,29 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]]
name = "svg2pdf"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e31565956eb1dc398c0d9776ee1d1bac4e34759af63dcbe0520df32313a5b53b"
dependencies = [
"log",
"miniz_oxide",
"once_cell",
"pdf-writer",
"usvg",
]
[[package]]
name = "svgtypes"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fae3064df9b89391c9a76a0425a69d124aee9c5c28455204709e72c39868a43c"
dependencies = [
"kurbo",
"siphasher 1.0.1",
]
[[package]]
name = "swift-rs"
version = "1.0.6"
@ -5575,6 +5713,20 @@ dependencies = [
"tauri-utils",
]
[[package]]
name = "tauri-plugin-barcode-scanner"
version = "2.0.0-alpha.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "058922dd9cafc89a865593c99507177c4cdbdad9d22a911ac41872ed7dbf0348"
dependencies = [
"log",
"serde",
"serde_json",
"tauri",
"tauri-build",
"thiserror",
]
[[package]]
name = "tauri-plugin-window"
version = "2.0.0-alpha.1"
@ -5787,7 +5939,10 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
dependencies = [
"deranged",
"itoa 1.0.6",
"js-sys",
"libc",
"num-conv",
"num_threads",
"powerfmt",
"serde",
"time-core",
@ -5819,6 +5974,17 @@ dependencies = [
"crunchy",
]
[[package]]
name = "tiny-skia-path"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
dependencies = [
"arrayref",
"bytemuck",
"strict-num",
]
[[package]]
name = "tinytemplate"
version = "1.2.1"
@ -6043,15 +6209,6 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "treediff"
version = "4.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52984d277bdf2a751072b5df30ec0377febdb02f7696d64c2d7d54630bac4303"
dependencies = [
"serde_json",
]
[[package]]
name = "try-lock"
version = "0.2.4"
@ -6168,6 +6325,28 @@ dependencies = [
"serde",
]
[[package]]
name = "usvg"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b84ea542ae85c715f07b082438a4231c3760539d902e11d093847a0b22963032"
dependencies = [
"base64 0.22.1",
"data-url",
"flate2",
"imagesize",
"kurbo",
"log",
"pico-args",
"roxmltree",
"simplecss",
"siphasher 1.0.1",
"strict-num",
"svgtypes",
"tiny-skia-path",
"xmlwriter",
]
[[package]]
name = "utf-8"
version = "0.7.6"
@ -6198,9 +6377,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "value-bag"
version = "1.4.0"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4d330786735ea358f3bc09eea4caa098569c1c93f342d9aca0514915022fe7e"
checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101"
[[package]]
name = "vcpkg"
@ -6325,6 +6504,12 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
[[package]]
name = "wasm-bindgen"
version = "0.2.87"
@ -6536,9 +6721,20 @@ dependencies = [
[[package]]
name = "weezl"
version = "0.1.7"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
[[package]]
name = "whoami"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9"
dependencies = [
"redox_syscall 0.4.1",
"wasite",
"web-sys",
]
[[package]]
name = "winapi"
@ -6999,6 +7195,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "xmlwriter"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
[[package]]
name = "xsalsa20poly1305"
version = "0.9.1"

@ -43,9 +43,9 @@ Read our [getting started guide](https://docs.nextgraph.org/en/getting-started/)
## For contributors
- [Install Rust](https://www.rust-lang.org/tools/install) minimum required MSRV 1.74.0
- [Install Nodejs](https://nodejs.org/en/download/)
- [Install LLVM](https://rust-lang.github.io/rust-bindgen/requirements.html)
- [Install Rust](https://www.rust-lang.org/tools/install) minimum required MSRV 1.74.0
- [Install Nodejs](https://nodejs.org/en/download/)
- [Install LLVM](https://rust-lang.github.io/rust-bindgen/requirements.html)
On openbsd, for LLVM you need to choose llvm-17.
@ -57,6 +57,14 @@ cargo install wasm-pack --git https://github.com/rustwasm/wasm-pack.git --rev c2
then :
create a file called `nextgraph/src/local_broker_dev_env.rs` with the content :
```
pub const PEER_ID: &str = "FtdzuDYGewfXWdoPuXIPb0wnd0SAg1WoA2B14S7jW3MA";
```
once your ngd server will run in your dev env, replace the above string with the actual PEER ID of your ngd server.
```
cargo install cargo-watch
// optionally, if you want a Rust REPL: cargo install evcxr_repl
@ -70,20 +78,20 @@ cargo build
The crates are organized as follow :
- [nextgraph](nextgraph/README.md) : Client library. Use this crate to embed NextGraph client in your Rust application
- [ngcli](ngcli/README.md) : CLI tool to manipulate the local documents and repos and administrate the server
- [ngd](ngd/README.md) : binary executable of the daemon (that can run a broker, verifier and/or Rust services)
- [ng-app](ng-app/README.md) : all the native apps, based on Tauri, and the official web app.
- [ng-sdk-js](ng-sdk-js/DEV.md) : contains the JS SDK, with example for: web app, react app, or node service.
- ng-repo : Repositories common library
- ng-net : Network common library
- ng-verifier : Verifier library, that exposes the document API to the app
- ng-wallet : keeps the secret keys of all identities of the user in a safe wallet
- ng-broker : Core and Server Broker library
- ng-client-ws : Websocket client library
- ng-storage-rocksdb : RocksDB backed stores. see also dependency [repo here](https://git.nextgraph.org/NextGraph/rust-rocksdb)
- ngone : server for nextgraph.one. helps user bootstrap into the right app. Not useful to you. Published here for transparency
- ngaccount : server for nextgraph's Broker Service Provider account manager. Not useful to you. Published here for transparency
- [nextgraph](nextgraph/README.md) : Client library. Use this crate to embed NextGraph client in your Rust application
- [ngcli](ngcli/README.md) : CLI tool to manipulate the local documents and repos and administrate the server
- [ngd](ngd/README.md) : binary executable of the daemon (that can run a broker, verifier and/or Rust services)
- [ng-app](ng-app/README.md) : all the native apps, based on Tauri, and the official web app.
- [ng-sdk-js](ng-sdk-js/DEV.md) : contains the JS SDK, with example for: web app, react app, or node service.
- ng-repo : Repositories common library
- ng-net : Network common library
- ng-verifier : Verifier library, that exposes the document API to the app
- ng-wallet : keeps the secret keys of all identities of the user in a safe wallet
- ng-broker : Core and Server Broker library
- ng-client-ws : Websocket client library
- ng-storage-rocksdb : RocksDB backed stores. see also dependency [repo here](https://git.nextgraph.org/NextGraph/rust-rocksdb)
- ngone : server for nextgraph.one. helps user bootstrap into the right app. Not useful to you. Published here for transparency
- ngaccount : server for nextgraph's Broker Service Provider account manager. Not useful to you. Published here for transparency
### Run
@ -224,9 +232,9 @@ additional terms or conditions.
Licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE2](LICENSE-APACHE2) or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
- Apache License, Version 2.0 ([LICENSE-APACHE2](LICENSE-APACHE2) or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
`SPDX-License-Identifier: Apache-2.0 OR MIT`

@ -1 +1,2 @@
tests
tests
local_broker_dev_env_peer_id.rs

@ -18,6 +18,7 @@ maintenance = { status = "actively-developed" }
[dependencies]
serde_bare = "0.5.0"
serde_json = "1.0"
serde_bytes = "0.11.7"
base64-url = "2.0.0"
once_cell = "1.17.1"
zeroize = { version = "1.7.0", features = ["zeroize_derive"] }
@ -25,7 +26,12 @@ futures = "0.3.24"
async-std = { version = "1.12.0", features = [ "attributes", "unstable" ] }
async-trait = "0.1.64"
async-once-cell = "0.5.3"
lazy_static = "1.4.0"
web-time = "0.2.0"
whoami = "1.5.1"
qrcode = { version = "0.14.1", default-features = false, features = ["svg"] }
svg2pdf = { version = "0.11.0", default-features = false }
pdf-writer = "0.10.0"
ng-repo = { path = "../ng-repo", version = "0.1.0-preview.1" }
ng-net = { path = "../ng-net", version = "0.1.0-preview.1" }
ng-wallet = { path = "../ng-wallet", version = "0.1.0-preview.5" }

@ -93,8 +93,44 @@ pub mod verifier {
pub mod protocol {
pub use ng_net::app_protocol::*;
}
pub use ng_verifier::prepare_app_response_for_js;
pub use ng_verifier::triples_ser_to_json_string;
}
pub mod wallet {
pub use ng_wallet::*;
}
pub fn get_device_name() -> String {
let mut list: Vec<String> = Vec::with_capacity(3);
#[cfg(not(target_arch = "wasm32"))]
if let Ok(realname) = whoami::fallible::realname() {
list.push(realname);
} else {
#[cfg(not(target_arch = "wasm32"))]
if let Ok(username) = whoami::fallible::username() {
list.push(username);
}
}
if let Ok(devicename) = whoami::fallible::devicename() {
list.push(devicename);
} else {
#[cfg(not(target_arch = "wasm32"))]
if let Ok(hostname) = whoami::fallible::hostname() {
list.push(hostname);
} else {
if let Ok(distro) = whoami::fallible::distro() {
list.push(distro);
}
}
}
#[cfg(target_arch = "wasm32")]
if let Ok(distro) = whoami::fallible::distro() {
list.push(distro);
}
list.join(" ")
}
#[cfg(debug_assertions)]
mod local_broker_dev_env;

File diff suppressed because it is too large Load Diff

@ -0,0 +1 @@
pub const PEER_ID: &str = "FtdzuDYGewfXWdoPuXIPb0wnd0SAg1WoA2B14S7jW3MA";

@ -56,9 +56,34 @@
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>NextGraph</title>
<style>
.splashing {
height: 95vh;display: grid;
}
</style>
</head>
<body>
<div id="splash" class="splashing">
<div style="margin: auto; display: grid;">
<svg
style="width:100px;height:100px;margin: 0 auto 20px ;"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 225 225"
>
<g>
<circle
r="106.98013"
cy="112.90476"
cx="109.88096"
style="fill:#ffffff;stroke:none;stroke-width:0.268375" />
<path
d="M 98.343352,190.26108 C 80.403778,187.53354 65.011938,179.57839 52.608228,166.62327 38.602093,151.99448 31.178059,133.41381 31.178059,112.98841 c 0,-10.21889 1.700058,-19.44396 5.221234,-28.332119 4.28678,-10.820699 10.037295,-19.39063 18.535095,-27.62263 4.72982,-4.58187 6.60687,-6.10643 11.28099,-9.16256 11.89869,-7.779841 24.173884,-11.879991 38.095802,-12.724761 19.80437,-1.2017 39.11165,5.11306 54.60284,17.858751 1.50718,1.24006 2.72951,2.35934 2.71628,2.48729 -0.0132,0.12795 -3.85821,3.63443 -8.54442,7.79217 -4.6862,4.157729 -10.04724,8.96276 -11.91342,10.677819 -1.86617,1.715071 -3.54094,3.11831 -3.7217,3.11831 -0.18075,0 -1.39985,-0.745188 -2.70911,-1.655969 -7.53011,-5.23834 -15.99428,-7.82188 -25.62597,-7.82188 -12.731628,0 -23.249192,4.3379 -32.143882,13.257541 -6.39594,6.413868 -10.70387,14.555268 -12.50018,23.623578 -0.69099,3.48832 -0.68968,13.53072 0.002,17.00893 3.70508,18.62577 18.31886,33.10194 36.642322,36.29729 4.16439,0.72621 11.98099,0.71223 15.98975,-0.0286 14.03187,-2.59311 25.86047,-11.36806 32.26533,-23.93578 0.77379,-1.51834 1.26018,-2.88461 1.08086,-3.03616 -0.17934,-0.15156 -6.87448,-1.1779 -14.87813,-2.28078 -9.7795,-1.34758 -14.92353,-2.21379 -15.68471,-2.64117 -1.52067,-0.85379 -2.83611,-2.88806 -2.83611,-4.3859 0,-1.1732 2.02687,-15.86876 2.49085,-18.05962 0.29676,-1.40127 2.42559,-3.4934 3.84317,-3.77691 0.62227,-0.12445 8.82712,0.85555 18.28065,2.18348 9.43343,1.32511 17.26269,2.29453 17.39833,2.15427 0.13566,-0.14026 1.11808,-6.54833 2.18313,-14.24014 1.10778,-8.000208 2.20407,-14.60184 2.56177,-15.426229 0.34392,-0.792599 1.11019,-1.849131 1.70287,-2.34782 2.06321,-1.736079 3.1433,-1.785011 12.20439,-0.55291 9.63637,1.310309 10.70873,1.56224 12.28077,2.88503 1.64359,1.382979 2.2732,2.810909 2.25906,5.123309 -0.007,1.10173 -0.92172,8.29645 -2.03332,15.98826 -1.11158,7.69182 -1.97159,14.04091 -1.91113,14.1091 0.0605,0.0682 7.16644,1.11143 15.79109,2.31832 11.10566,1.55407 16.00827,2.38757 16.80223,2.85657 1.53015,0.90389 2.48023,2.64785 2.45017,4.49756 -0.0462,2.84349 -2.41252,18.12279 -2.97521,19.21089 -0.66164,1.27949 -2.60244,2.54696 -3.92109,2.56074 -0.51973,0.005 -7.87449,-0.95937 -16.34391,-2.144 -8.46944,-1.18464 -15.47588,-2.077 -15.56986,-1.98301 -0.094,0.094 -1.18792,7.34163 -2.43097,16.10589 -1.44004,10.15311 -2.49792,16.43621 -2.91556,17.31631 -0.72531,1.52848 -2.76261,3.06291 -4.53817,3.41802 -0.95688,0.19138 -10.90014,-0.92798 -13.59859,-1.53084 -0.5471,-0.12223 -1.89146,0.67252 -4.50941,2.66588 -11.2627,8.57562 -24.34195,13.90917 -38.35741,15.64164 -4.40038,0.54395 -15.72658,0.43298 -19.853658,-0.19451 z"
style="fill:#4972a5;fill-opacity:1;stroke:#4972a5;stroke-width:0.377976;stroke-opacity:1" />
</g>
</svg>
</div>
</div>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>

@ -57,9 +57,62 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>NextGraph</title>
</head>
<style>
.splashing {
height: 95vh;display: grid;
}
.error-no-wasm-hidden {
display:none;
}
</style>
<body>
<div id="app"></div>
<div id="splash" class="splashing">
<div style="margin: auto; display: grid;">
<svg
style="width:100px;height:100px;margin: 0 auto 20px ;"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 225 225"
>
<g>
<circle
r="106.98013"
cy="112.90476"
cx="109.88096"
style="fill:#ffffff;stroke:none;stroke-width:0.268375" />
<path
d="M 98.343352,190.26108 C 80.403778,187.53354 65.011938,179.57839 52.608228,166.62327 38.602093,151.99448 31.178059,133.41381 31.178059,112.98841 c 0,-10.21889 1.700058,-19.44396 5.221234,-28.332119 4.28678,-10.820699 10.037295,-19.39063 18.535095,-27.62263 4.72982,-4.58187 6.60687,-6.10643 11.28099,-9.16256 11.89869,-7.779841 24.173884,-11.879991 38.095802,-12.724761 19.80437,-1.2017 39.11165,5.11306 54.60284,17.858751 1.50718,1.24006 2.72951,2.35934 2.71628,2.48729 -0.0132,0.12795 -3.85821,3.63443 -8.54442,7.79217 -4.6862,4.157729 -10.04724,8.96276 -11.91342,10.677819 -1.86617,1.715071 -3.54094,3.11831 -3.7217,3.11831 -0.18075,0 -1.39985,-0.745188 -2.70911,-1.655969 -7.53011,-5.23834 -15.99428,-7.82188 -25.62597,-7.82188 -12.731628,0 -23.249192,4.3379 -32.143882,13.257541 -6.39594,6.413868 -10.70387,14.555268 -12.50018,23.623578 -0.69099,3.48832 -0.68968,13.53072 0.002,17.00893 3.70508,18.62577 18.31886,33.10194 36.642322,36.29729 4.16439,0.72621 11.98099,0.71223 15.98975,-0.0286 14.03187,-2.59311 25.86047,-11.36806 32.26533,-23.93578 0.77379,-1.51834 1.26018,-2.88461 1.08086,-3.03616 -0.17934,-0.15156 -6.87448,-1.1779 -14.87813,-2.28078 -9.7795,-1.34758 -14.92353,-2.21379 -15.68471,-2.64117 -1.52067,-0.85379 -2.83611,-2.88806 -2.83611,-4.3859 0,-1.1732 2.02687,-15.86876 2.49085,-18.05962 0.29676,-1.40127 2.42559,-3.4934 3.84317,-3.77691 0.62227,-0.12445 8.82712,0.85555 18.28065,2.18348 9.43343,1.32511 17.26269,2.29453 17.39833,2.15427 0.13566,-0.14026 1.11808,-6.54833 2.18313,-14.24014 1.10778,-8.000208 2.20407,-14.60184 2.56177,-15.426229 0.34392,-0.792599 1.11019,-1.849131 1.70287,-2.34782 2.06321,-1.736079 3.1433,-1.785011 12.20439,-0.55291 9.63637,1.310309 10.70873,1.56224 12.28077,2.88503 1.64359,1.382979 2.2732,2.810909 2.25906,5.123309 -0.007,1.10173 -0.92172,8.29645 -2.03332,15.98826 -1.11158,7.69182 -1.97159,14.04091 -1.91113,14.1091 0.0605,0.0682 7.16644,1.11143 15.79109,2.31832 11.10566,1.55407 16.00827,2.38757 16.80223,2.85657 1.53015,0.90389 2.48023,2.64785 2.45017,4.49756 -0.0462,2.84349 -2.41252,18.12279 -2.97521,19.21089 -0.66164,1.27949 -2.60244,2.54696 -3.92109,2.56074 -0.51973,0.005 -7.87449,-0.95937 -16.34391,-2.144 -8.46944,-1.18464 -15.47588,-2.077 -15.56986,-1.98301 -0.094,0.094 -1.18792,7.34163 -2.43097,16.10589 -1.44004,10.15311 -2.49792,16.43621 -2.91556,17.31631 -0.72531,1.52848 -2.76261,3.06291 -4.53817,3.41802 -0.95688,0.19138 -10.90014,-0.92798 -13.59859,-1.53084 -0.5471,-0.12223 -1.89146,0.67252 -4.50941,2.66588 -11.2627,8.57562 -24.34195,13.90917 -38.35741,15.64164 -4.40038,0.54395 -15.72658,0.43298 -19.853658,-0.19451 z"
style="fill:#4972a5;fill-opacity:1;stroke:#4972a5;stroke-width:0.377976;stroke-opacity:1" />
</g>
</svg>
<br/>
<div id="error-no-wasm" class="error-no-wasm-hidden" style="text-align: center;">
Your browser is too old and does not support NextGraph. <br/>Please upgrade to a newer version of this browser,<br/> try with another browser, <br/>or <a href="https://nextgraph.org/download">install our native app</a>.
</div>
<noscript style="text-align: center;">
NextGraph cannot load as Javascript is deactivated
</noscript>
</div>
</div>
<div id="app">
</div>
<script>
const supported = (() => {
try {
if (typeof WebAssembly === "object"
&& typeof WebAssembly.instantiate === "function") {
const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00));
if (module instanceof WebAssembly.Module)
return new WebAssembly.Instance(module) instanceof WebAssembly.Instance;
}
} catch (e) {
}
return false;
})();
if (!supported || RegExp().hasIndices === undefined) {
window.document.getElementById("error-no-wasm").className="";
}
</script>
<script type="module" src="/src/main-web.ts"></script>
</body>
</html>

@ -16,14 +16,28 @@
"tauri": "tauri"
},
"dependencies": {
"@codemirror/autocomplete": "^6.17.0",
"@codemirror/commands": "^6.6.0",
"@codemirror/language": "^6.10.2",
"@codemirror/legacy-modes": "^6.4.0",
"@codemirror/lint": "^6.8.1",
"@codemirror/search": "^6.5.6",
"@codemirror/state": "^6.4.1",
"@codemirror/view": "^6.29.1",
"@popperjs/core": "^2.11.8",
"@tauri-apps/api": "2.0.0-alpha.8",
"@tauri-apps/plugin-barcode-scanner": "2.0.0-alpha.0",
"@tauri-apps/plugin-window": "2.0.0-alpha.1",
"async-proxy": "^0.4.1",
"classnames": "^2.3.2",
"codemirror": "^6.0.1",
"flowbite": "^1.6.5",
"flowbite-svelte": "^0.43.3",
"html5-qrcode": "^2.3.8",
"ng-sdk-js": "workspace:^0.1.0-preview.1",
"svelte-codemirror-editor": "^1.4.0",
"svelte-i18n": "^4.0.0",
"svelte-inview": "^4.0.2",
"svelte-spa-router": "^3.3.0",
"vite-plugin-top-level-await": "^1.3.1"
},
@ -35,6 +49,7 @@
"autoprefixer": "^10.4.14",
"cross-env": "^7.0.3",
"dayjs": "^1.11.10",
"highlight.js": "^11.10.0",
"internal-ip": "^7.0.0",
"node-gzip": "^1.1.2",
"postcss": "^8.4.23",
@ -43,6 +58,7 @@
"svelte": "^3.54.0",
"svelte-check": "^3.0.0",
"svelte-heros-v2": "^0.10.12",
"svelte-highlight": "^7.7.0",
"svelte-preprocess": "^5.0.3",
"svelte-time": "^0.8.0",
"tailwindcss": "^3.3.1",

@ -21,10 +21,11 @@ crate-type = ["staticlib", "cdylib", "rlib"]
tauri-build = { version = "2.0.0-alpha.8", features = [] }
# tauri-macros = { version = "=2.0.0-alpha.6" }
# tauri-codegen = { version = "=2.0.0-alpha.6" }
# tauri-utils = { version = "=2.0.0-alpha.6" }
tauri-utils = { version = "=2.0.0-alpha.7" }
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_bare = "0.5.0"
serde_json = "1.0"
serde_bytes = "0.11.7"
async-std = { version = "1.12.0", features = ["attributes", "unstable"] }
@ -32,6 +33,7 @@ sys-locale = { version = "0.3.1" }
ng-async-tungstenite = { git = "https://git.nextgraph.org/NextGraph/async-tungstenite.git", branch = "nextgraph", features = ["async-std-runtime", "async-native-tls"] }
tauri = { version = "2.0.0-alpha.14", features = [] }
tauri-plugin-window = "2.0.0-alpha.1"
tauri-plugin-barcode-scanner = "=2.0.0-alpha.0"
# tauri-plugin-window = { git = "https://git.nextgraph.org/NextGraph/plugins-workspace.git", branch="window-alpha.1-nextgraph" }
# tauri = { git = "https://git.nextgraph.org/NextGraph/tauri.git", branch="alpha.11-nextgraph", features = ["no-ipc-custom-protocol"] }
# tauri = { git = "https://github.com/simonhyll/tauri.git", branch="fix/ipc-mixup", features = [] }
@ -39,6 +41,7 @@ ng-repo = { path = "../../ng-repo" }
ng-net = { path = "../../ng-net" }
ng-wallet = { path = "../../ng-wallet" }
nextgraph = { path = "../../nextgraph" }
oxrdf = { git = "https://git.nextgraph.org/NextGraph/oxigraph.git", branch="main", features = ["rdf-star", "oxsdatatypes"] }
[features]
# this feature is used for production builds or when `devPath` points to the filesystem

@ -1,3 +1,3 @@
fn main() {
tauri_build::build()
tauri_build::build()
}

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<application
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"

@ -16,7 +16,8 @@ open class BuildTask : DefaultTask() {
@TaskAction
fun assemble() {
val executable = """/Users/nl/.cargo/bin/cargo-tauri""";
val homePath = System.getProperty("user.home");
val executable = "$homePath/.cargo/bin/cargo-tauri";
try {
runTauriCli(executable)
} catch (e: Exception) {

@ -11,6 +11,7 @@ use std::collections::HashMap;
use std::fs::write;
use async_std::stream::StreamExt;
use oxrdf::Triple;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use sys_locale::get_locales;
@ -108,6 +109,31 @@ async fn wallet_open_with_pazzle(
Ok(wallet)
}
#[tauri::command(rename_all = "snake_case")]
async fn wallet_open_with_mnemonic(
wallet: Wallet,
mnemonic: [u16; 12],
pin: [u8; 4],
_app: tauri::AppHandle,
) -> Result<SensitiveWallet, String> {
let wallet =
ng_wallet::open_wallet_with_mnemonic(&wallet, mnemonic, pin).map_err(|e| e.to_string())?;
Ok(wallet)
}
#[tauri::command(rename_all = "snake_case")]
async fn wallet_open_with_mnemonic_words(
wallet: Wallet,
mnemonic_words: Vec<String>,
pin: [u8; 4],
_app: tauri::AppHandle,
) -> Result<SensitiveWallet, String> {
let wallet =
nextgraph::local_broker::wallet_open_with_mnemonic_words(&wallet, &mnemonic_words, pin)
.map_err(|e| e.to_string())?;
Ok(wallet)
}
#[tauri::command(rename_all = "snake_case")]
async fn wallet_get_file(wallet_name: String, app: tauri::AppHandle) -> Result<(), String> {
let ser = nextgraph::local_broker::wallet_get_file(&wallet_name)
@ -181,6 +207,55 @@ async fn wallet_import(
.map_err(|e: NgError| e.to_string())
}
#[tauri::command(rename_all = "snake_case")]
async fn wallet_export_rendezvous(
session_id: u64,
code: String,
_app: tauri::AppHandle,
) -> Result<(), String> {
nextgraph::local_broker::wallet_export_rendezvous(session_id, code)
.await
.map_err(|e: NgError| e.to_string())
}
#[tauri::command(rename_all = "snake_case")]
async fn wallet_export_get_qrcode(
session_id: u64,
size: u32,
_app: tauri::AppHandle,
) -> Result<String, String> {
nextgraph::local_broker::wallet_export_get_qrcode(session_id, size)
.await
.map_err(|e: NgError| e.to_string())
}
#[tauri::command(rename_all = "snake_case")]
async fn wallet_export_get_textcode(
session_id: u64,
_app: tauri::AppHandle,
) -> Result<String, String> {
nextgraph::local_broker::wallet_export_get_textcode(session_id)
.await
.map_err(|e: NgError| e.to_string())
}
#[tauri::command(rename_all = "snake_case")]
async fn wallet_import_rendezvous(
size: u32,
_app: tauri::AppHandle,
) -> Result<(String, String), String> {
nextgraph::local_broker::wallet_import_rendezvous(size)
.await
.map_err(|e: NgError| e.to_string())
}
#[tauri::command(rename_all = "snake_case")]
async fn wallet_import_from_code(code: String, _app: tauri::AppHandle) -> Result<Wallet, String> {
nextgraph::local_broker::wallet_import_from_code(code)
.await
.map_err(|e: NgError| e.to_string())
}
#[tauri::command(rename_all = "snake_case")]
async fn get_wallets(
app: tauri::AppHandle,
@ -263,6 +338,25 @@ async fn decode_invitation(invite: String) -> Option<Invitation> {
decode_invitation_string(invite)
}
#[tauri::command(rename_all = "snake_case")]
async fn file_get(
session_id: u64,
stream_id: &str,
reference: BlockRef,
branch_nuri: String,
app: tauri::AppHandle,
) -> Result<(), String> {
let branch_nuri =
NuriV0::new_from(&branch_nuri).map_err(|e| format!("branch_nuri: {}", e.to_string()))?;
let mut nuri = NuriV0::new_from_obj_ref(&reference);
nuri.copy_target_from(&branch_nuri);
let mut request = AppRequest::new(AppRequestCommandV0::FileGet, nuri, None);
request.set_session_id(session_id);
app_request_stream(request, stream_id, app).await
}
#[tauri::command(rename_all = "snake_case")]
async fn app_request_stream(
request: AppRequest,
@ -290,6 +384,7 @@ async fn app_request_stream(
main_window: tauri::Window,
) -> ResultSend<()> {
while let Some(app_response) = reader.next().await {
let app_response = nextgraph::verifier::prepare_app_response_for_js(app_response)?;
main_window.emit(&stream_id, app_response).unwrap();
}
@ -306,6 +401,69 @@ async fn app_request_stream(
Ok(())
}
#[tauri::command(rename_all = "snake_case")]
async fn file_save_to_downloads(
session_id: u64,
reference: ObjectRef,
filename: String,
branch_nuri: String,
app: tauri::AppHandle,
) -> Result<(), String> {
let branch_nuri =
NuriV0::new_from(&branch_nuri).map_err(|e| format!("branch_nuri: {}", e.to_string()))?;
let mut nuri = NuriV0::new_from_obj_ref(&reference);
nuri.copy_target_from(&branch_nuri);
let mut request = AppRequest::new(AppRequestCommandV0::FileGet, nuri, None);
request.set_session_id(session_id);
let (mut reader, cancel) = nextgraph::local_broker::app_request_stream(request)
.await
.map_err(|e| e.to_string())?;
let mut file_vec: Vec<u8> = vec![];
while let Some(app_response) = reader.next().await {
match app_response {
AppResponse::V0(AppResponseV0::FileMeta(filemeta)) => {
file_vec = Vec::with_capacity(filemeta.size as usize);
}
AppResponse::V0(AppResponseV0::FileBinary(mut bin)) => {
if !bin.is_empty() {
file_vec.append(&mut bin);
}
}
AppResponse::V0(AppResponseV0::EndOfStream) => break,
_ => return Err("invalid response".to_string()),
}
}
let mut i: usize = 0;
loop {
let dest_filename = if i == 0 {
filename.clone()
} else {
filename
.rsplit_once(".")
.map(|(l, r)| format!("{l} ({}).{r}", i.to_string()))
.or_else(|| Some(format!("{filename} ({})", i.to_string())))
.unwrap()
};
let path = app
.path()
.resolve(dest_filename, BaseDirectory::Download)
.unwrap();
if path.exists() {
i = i + 1;
} else {
write(path, &file_vec).map_err(|e| e.to_string())?;
break;
}
}
Ok(())
}
#[tauri::command(rename_all = "snake_case")]
async fn doc_fetch_private_subscribe() -> Result<AppRequest, String> {
let request = AppRequest::new(
@ -317,17 +475,108 @@ async fn doc_fetch_private_subscribe() -> Result<AppRequest, String> {
}
#[tauri::command(rename_all = "snake_case")]
async fn doc_fetch_repo_subscribe(repo_id: String) -> Result<AppRequest, String> {
async fn doc_fetch_repo_subscribe(repo_o: String) -> Result<AppRequest, String> {
let request = AppRequest::new(
AppRequestCommandV0::Fetch(AppFetchContentV0::get_or_subscribe(true)),
NuriV0::new_repo_target_from_string(repo_id).map_err(|e| e.to_string())?,
NuriV0::new_from(&repo_o).map_err(|e| e.to_string())?,
None,
);
Ok(request)
}
#[tauri::command(rename_all = "snake_case")]
async fn app_request(request: AppRequest, _app: tauri::AppHandle) -> Result<AppResponse, String> {
async fn branch_history(session_id: u64, nuri: String) -> Result<AppHistoryJs, String> {
let request = AppRequest::V0(AppRequestV0 {
command: AppRequestCommandV0::new_history(),
nuri: NuriV0::new_from(&nuri).map_err(|e| e.to_string())?,
payload: None,
session_id,
});
let res = nextgraph::local_broker::app_request(request)
.await
.map_err(|e: NgError| e.to_string())?;
let AppResponse::V0(res) = res;
//log_debug!("{:?}", res);
match res {
AppResponseV0::History(s) => Ok(s.to_js()),
_ => Err("invalid response".to_string()),
}
}
#[tauri::command(rename_all = "snake_case")]
async fn sparql_update(session_id: u64, sparql: String, nuri: String) -> Result<(), String> {
let nuri = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?;
let request = AppRequest::V0(AppRequestV0 {
command: AppRequestCommandV0::new_write_query(),
nuri,
payload: Some(AppRequestPayload::new_sparql_query(sparql)),
session_id,
});
let res = nextgraph::local_broker::app_request(request)
.await
.map_err(|e: NgError| e.to_string())?;
if let AppResponse::V0(AppResponseV0::Error(e)) = res {
Err(e)
} else {
Ok(())
}
}
#[tauri::command(rename_all = "snake_case")]
async fn sparql_query(
session_id: u64,
sparql: String,
nuri: Option<String>,
) -> Result<Value, String> {
let nuri = if nuri.is_some() {
NuriV0::new_from(&nuri.unwrap()).map_err(|e| e.to_string())?
} else {
NuriV0::new_entire_user_site()
};
let request = AppRequest::V0(AppRequestV0 {
command: AppRequestCommandV0::new_read_query(),
nuri,
payload: Some(AppRequestPayload::new_sparql_query(sparql)),
session_id,
});
let response = nextgraph::local_broker::app_request(request)
.await
.map_err(|e: NgError| e.to_string())?;
let AppResponse::V0(res) = response;
match res {
AppResponseV0::False => return Ok(Value::Bool(false)),
AppResponseV0::True => return Ok(Value::Bool(true)),
AppResponseV0::Graph(graph) => {
let triples: Vec<Triple> = serde_bare::from_slice(&graph)
.map_err(|_| "Deserialization error of graph".to_string())?;
Ok(Value::Array(
triples
.into_iter()
.map(|t| Value::String(t.to_string()))
.collect(),
))
}
AppResponseV0::QueryResult(buf) => {
let string = String::from_utf8(buf)
.map_err(|_| "Deserialization error of JSON QueryResult String".to_string())?;
Ok(serde_json::from_str(&string)
.map_err(|_| "Parsing error of JSON QueryResult String".to_string())?)
}
AppResponseV0::Error(e) => Err(e.to_string().into()),
_ => Err("invalid AppResponse".to_string().into()),
}
}
#[tauri::command(rename_all = "snake_case")]
async fn app_request(request: AppRequest) -> Result<AppResponse, String> {
//log_debug!("app request {:?}", request);
nextgraph::local_broker::app_request(request)
@ -335,19 +584,40 @@ async fn app_request(request: AppRequest, _app: tauri::AppHandle) -> Result<AppR
.map_err(|e| e.to_string())
}
#[tauri::command(rename_all = "snake_case")]
async fn app_request_with_nuri_command(
nuri: String,
command: AppRequestCommandV0,
session_id: u64,
payload: Option<AppRequestPayloadV0>,
) -> Result<AppResponse, String> {
let nuri = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?;
let payload = payload.map(|p| AppRequestPayload::V0(p));
let request = AppRequest::V0(AppRequestV0 {
session_id,
command,
nuri,
payload,
});
app_request(request).await
}
#[tauri::command(rename_all = "snake_case")]
async fn upload_chunk(
session_id: u64,
upload_id: u32,
chunk: serde_bytes::ByteBuf,
nuri: NuriV0,
nuri: String,
_app: tauri::AppHandle,
) -> Result<AppResponse, String> {
//log_debug!("upload_chunk {:?}", chunk);
let mut request = AppRequest::new(
AppRequestCommandV0::FilePut,
nuri,
NuriV0::new_from(&nuri).map_err(|e| e.to_string())?,
Some(AppRequestPayload::V0(
AppRequestPayloadV0::RandomAccessFilePutChunk((upload_id, chunk)),
)),
@ -467,6 +737,11 @@ fn client_info_rust() -> Result<Value, String> {
Ok(ng_repo::os_info::get_os_info())
}
#[tauri::command(rename_all = "snake_case")]
fn get_device_name() -> Result<String, String> {
Ok(nextgraph::get_device_name())
}
#[derive(Default)]
pub struct AppBuilder {
setup: Option<SetupHook>,
@ -493,7 +768,9 @@ impl AppBuilder {
pub fn run(self) {
let setup = self.setup;
tauri::Builder::default()
#[allow(unused_mut)]
let mut builder = tauri::Builder::default()
.setup(move |app| {
if let Some(setup) = setup {
(setup)(app)?;
@ -509,18 +786,32 @@ impl AppBuilder {
}
Ok(())
})
.plugin(tauri_plugin_window::init())
.plugin(tauri_plugin_window::init());
#[cfg(mobile)]
{
builder = builder.plugin(tauri_plugin_barcode_scanner::init());
}
builder
.invoke_handler(tauri::generate_handler![
test,
locales,
wallet_gen_shuffle_for_pazzle_opening,
wallet_gen_shuffle_for_pin,
wallet_open_with_pazzle,
wallet_open_with_mnemonic,
wallet_open_with_mnemonic_words,
wallet_was_opened,
wallet_create,
wallet_read_file,
wallet_get_file,
wallet_import,
wallet_export_rendezvous,
wallet_export_get_qrcode,
wallet_export_get_textcode,
wallet_import_rendezvous,
wallet_import_from_code,
wallet_close,
encode_create_account,
session_start,
@ -537,8 +828,15 @@ impl AppBuilder {
doc_fetch_repo_subscribe,
cancel_stream,
app_request_stream,
file_get,
file_save_to_downloads,
app_request,
app_request_with_nuri_command,
upload_chunk,
get_device_name,
sparql_query,
sparql_update,
branch_history,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");

@ -11,12 +11,13 @@
<script lang="ts">
import { push, default as Router } from "svelte-spa-router";
import { isLoading } from "svelte-i18n";
import { onMount, tick, onDestroy } from "svelte";
import {
wallets,
active_wallet,
opened_wallets,
active_session,
close_active_session,
disconnections_subscribe,
select_default_lang,
@ -30,22 +31,36 @@
import WalletCreate from "./routes/WalletCreate.svelte";
import Invitation from "./routes/Invitation.svelte";
import WalletLogin from "./routes/WalletLogin.svelte";
import WalletInfo from "./routes/WalletInfo.svelte";
import User from "./routes/User.svelte";
import UserRegistered from "./routes/UserRegistered.svelte";
import Install from "./routes/Install.svelte";
import ScanQR from "./routes/ScanQR.svelte";
import Shared from "./routes/Shared.svelte";
import Site from "./routes/Site.svelte";
import ng from "./api";
import AccountInfo from "./routes/AccountInfo.svelte";
import WalletLoginQr from "./routes/WalletLoginQr.svelte";
import WalletLoginTextCode from "./routes/WalletLoginTextCode.svelte";
const routes = new Map();
routes.set("/", Home);
routes.set("/test", Test);
routes.set("/wallet/login", WalletLogin);
routes.set("/wallet/login-qr", WalletLoginQr);
routes.set("/wallet/login-text-code", WalletLoginTextCode);
routes.set("/wallet/create", WalletCreate);
routes.set("/i/:invitation", Invitation);
routes.set("/user", User);
routes.set("/user/registered", UserRegistered);
routes.set("/wallet", WalletInfo);
routes.set("/user/accounts", AccountInfo);
routes.set("/wallet/scanqr", ScanQR);
if (import.meta.env.NG_APP_WEB) routes.set("/install", Install);
routes.set(/^\/did:ng(.*)/i, NURI);
routes.set("/shared", Shared);
routes.set("/site", Site);
routes.set(/^\/did:ng:(.*)/i, NURI);
routes.set("*", NotFound);
let unsubscribe = () => {};
@ -59,10 +74,12 @@
// };
onMount(async () => {
//window.document.getElementById("splash").className="splash-loaded";
try {
await select_default_lang();
await disconnections_subscribe();
await select_default_lang();
} catch (e) {
console.warn(e);
//console.log("called disconnections_subscribe twice");
}
let tauri_platform = import.meta.env.TAURI_PLATFORM;
@ -262,6 +279,9 @@
unsubscribe();
if (unsub_main_close) unsub_main_close();
});
// import { to_debug } from "./wallet_emojis";
// to_debug();
</script>
<!-- <p>
@ -271,4 +291,9 @@
{JSON.stringify(Object.keys($opened_wallets))}
{JSON.stringify($active_session)}
</p> -->
<Router {routes} />
{#if $isLoading}
<p class="text-center">Loading translations...</p>
{:else}
<Router {routes} />
{/if}

@ -16,11 +16,18 @@ const mapping = {
"wallet_gen_shuffle_for_pazzle_opening": ["pazzle_length"],
"wallet_gen_shuffle_for_pin": [],
"wallet_open_with_pazzle": ["wallet","pazzle","pin"],
"wallet_open_with_mnemonic_words": ["wallet","mnemonic_words","pin"],
"wallet_open_with_mnemonic": ["wallet","mnemonic","pin"],
"wallet_was_opened": ["opened_wallet"],
"wallet_create": ["params"],
"wallet_read_file": ["file"],
"wallet_get_file": ["wallet_name"],
"wallet_import": ["encrypted_wallet","opened_wallet","in_memory"],
"wallet_export_rendezvous": ["session_id", "code"],
"wallet_export_get_qrcode": ["session_id", "size"],
"wallet_export_get_textcode": ["session_id"],
"wallet_import_rendezvous": ["size"],
"wallet_import_from_code": ["code"],
"wallet_close": ["wallet_name"],
"encode_create_account": ["payload"],
"session_start": ["wallet_name","user"],
@ -32,9 +39,15 @@ const mapping = {
"user_connect": ["info","user_id","location"],
"user_disconnect": ["user_id"],
"app_request": ["request"],
"app_request_with_nuri_command": ["nuri", "command", "session_id", "payload"],
"sparql_query": ["session_id","sparql","nuri"],
"sparql_update": ["session_id","sparql","nuri"],
"test": [ ],
"get_device_name": [],
"doc_fetch_private_subscribe": [],
"doc_fetch_repo_subscribe": ["repo_id"],
"doc_fetch_repo_subscribe": ["repo_o"],
"branch_history": ["session_id", "nuri"],
"file_save_to_downloads": ["session_id", "reference", "filename", "branch_nuri"],
}
@ -59,11 +72,28 @@ const handler = {
// } else if (path[0] === "wallet_create") {
// let res = await Reflect.apply(sdk[path], caller, args);
// return res;
} else if (path[0] === "app_request_stream") {
let callback = args[1];
let new_callback = (event) => {
if (event.V0.State?.graph?.triples) {
let json_str = new TextDecoder().decode(event.V0.State.graph.triples);
event.V0.State.graph.triples = JSON.parse(json_str);
} else if (event.V0.Patch?.graph) {
let inserts_json_str = new TextDecoder().decode(event.V0.Patch.graph.inserts);
event.V0.Patch.graph.inserts = JSON.parse(inserts_json_str);
let removes_json_str = new TextDecoder().decode(event.V0.Patch.graph.removes);
event.V0.Patch.graph.removes = JSON.parse(removes_json_str);
}
callback(event).then(()=> {})
};
args[1] = new_callback;
return Reflect.apply(sdk[path], caller, args)
} else {
return Reflect.apply(sdk[path], caller, args)
}
} else {
let tauri = await import("@tauri-apps/api/tauri");
try {
if (path[0] === "client_info") {
let from_rust = await tauri.invoke("client_info_rust",{});
@ -72,7 +102,7 @@ const handler = {
switch (tauri_platform) {
case 'macos': client_type = "NativeMacOS";break;
case 'linux': client_type = "NativeLinux";break;
case 'windows': client_type = "NativeWindows";break;
case 'windows': client_type = "NativeWin";break;
case 'android': client_type = "NativeAndroid";break;
case 'ios': client_type = "NativeIos";break;
}
@ -97,6 +127,11 @@ const handler = {
};
//console.log(info,res);
return res;
} else if (path[0] === "get_device_name") {
let tauri_platform = import.meta.env.TAURI_PLATFORM;
if (tauri_platform == 'android') return "Android Phone";
else if (tauri_platform == 'ios') return "iPhone";
else return await tauri.invoke(path[0],{});
} else if (path[0] === "locales") {
let from_rust = await tauri.invoke("locales",{});
let from_js = window.navigator.languages;
@ -129,7 +164,42 @@ const handler = {
}
return ret;
}
else if (path[0] === "app_request_stream") {
else if (path[0] === "file_get") {
let stream_id = (lastStreamId += 1).toString();
//console.log("stream_id",stream_id);
let { getCurrent } = await import("@tauri-apps/plugin-window");
//let session_id = args[0];
let callback = args[3];
let unlisten = await getCurrent().listen(stream_id, async (event) => {
//console.log(event.payload);
if (event.payload.V0.FileBinary) {
event.payload.V0.FileBinary = Uint8Array.from(event.payload.V0.FileBinary);
}
let ret = callback(event.payload);
if (ret === true) {
await tauri.invoke("cancel_stream", {stream_id});
} else if (ret.then) {
ret.then(async (val)=> {
if (val === true) {
await tauri.invoke("cancel_stream", {stream_id});
}
});
}
})
try {
await tauri.invoke("file_get",{stream_id, session_id:args[0], reference: args[1], branch_nuri:args[2]});
} catch (e) {
unlisten();
await tauri.invoke("cancel_stream", {stream_id});
throw e;
}
return () => {
unlisten();
tauri.invoke("cancel_stream", {stream_id});
}
} else if (path[0] === "app_request_stream") {
let stream_id = (lastStreamId += 1).toString();
//console.log("stream_id",stream_id);
let { getCurrent } = await import("@tauri-apps/plugin-window");
@ -137,15 +207,38 @@ const handler = {
let request = args[0];
let callback = args[1];
let unlisten = await getCurrent().listen(stream_id, (event) => {
let unlisten = await getCurrent().listen(stream_id, async (event) => {
//console.log(event.payload);
if (event.payload.V0.FileBinary) {
event.payload.V0.FileBinary = Uint8Array.from(event.payload.V0.FileBinary);
}
callback(event.payload).then(()=> {})
if (event.payload.V0.State?.graph?.triples) {
let json_str = new TextDecoder().decode(Uint8Array.from(event.payload.V0.State.graph.triples));
event.payload.V0.State.graph.triples = JSON.parse(json_str);
} else if (event.payload.V0.Patch?.graph) {
let inserts_json_str = new TextDecoder().decode(Uint8Array.from(event.payload.V0.Patch.graph.inserts));
event.payload.V0.Patch.graph.inserts = JSON.parse(inserts_json_str);
let removes_json_str = new TextDecoder().decode(Uint8Array.from(event.payload.V0.Patch.graph.removes));
event.payload.V0.Patch.graph.removes = JSON.parse(removes_json_str);
}
let ret = callback(event.payload);
if (ret === true) {
await tauri.invoke("cancel_stream", {stream_id});
} else if (ret.then) {
ret.then(async (val)=> {
if (val === true) {
await tauri.invoke("cancel_stream", {stream_id});
}
});
}
})
await tauri.invoke("app_request_stream",{stream_id, request});
try {
await tauri.invoke("app_request_stream",{stream_id, request});
} catch (e) {
unlisten();
await tauri.invoke("cancel_stream", {stream_id});
throw e;
}
return () => {
unlisten();
tauri.invoke("cancel_stream", {stream_id});
@ -158,6 +251,15 @@ const handler = {
}
return res || {};
} else if (path[0] === "wallet_import_from_code") {
let arg = {};
args.map((el,ix) => arg[mapping[path[0]][ix]]=el);
let res = await tauri.invoke(path[0],arg);
if (res) {
res.V0.content.security_img = Uint8Array.from(res.V0.content.security_img);
}
return res || {};
} else if (path[0] === "upload_chunk") {
let session_id = args[0];
let upload_id = args[1];
@ -182,7 +284,7 @@ const handler = {
return false;
} else if (path[0] === "get_local_url") {
return false;
} else if (path[0] === "wallet_open_with_pazzle") {
} else if (path[0] === "wallet_open_with_pazzle" || path[0] === "wallet_open_with_mnemonic_words" || path[0] === "wallet_open_with_mnemonic") {
let arg:any = {};
args.map((el,ix) => arg[mapping[path[0]][ix]]=el)
let img = Array.from(new Uint8Array(arg.wallet.V0.content.security_img));
@ -190,13 +292,21 @@ const handler = {
arg.wallet = {V0:{id:arg.wallet.V0.id, sig:arg.wallet.V0.sig, content:{}}};
Object.assign(arg.wallet.V0.content,old_content);
arg.wallet.V0.content.security_img = img;
return tauri.invoke(path[0],arg)
}
else {
return await tauri.invoke(path[0],arg);
} else {
let arg = {};
args.map((el,ix) => arg[mapping[path[0]][ix]]=el)
return tauri.invoke(path[0],arg)
return await tauri.invoke(path[0],arg)
}
} catch (e) {
let error;
try {
error = JSON.parse(e);
} catch (f) {
error = e;
}
throw error;
}
}
},
};

@ -0,0 +1,37 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import {
} from "../store";
import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte";
export let commits;
</script>
<div class="flex-col">
<h2>ListView</h2>
{#if Array.isArray(commits.history.commits)}
{#each commits.history.commits as c}
<div class="flex"> {c[0]} {JSON.stringify(c[1])}</div>
{/each}
{/if}
<div class="flex">
HEADS: {#each commits.heads as head} {head} , {/each}
</div>
TRIPLES:
{#each commits.graph as triple}
<div class="flex"> {triple}</div>
{/each}
</div>

@ -0,0 +1,125 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { onMount, tick, onDestroy } from "svelte";
import {
sparql_query,
toast_error,
toast_success,
reset_toasts,
display_error,
} from "../store";
import {
in_memory_discrete, open_viewer, set_viewer, reset_in_memory
} from "../tab";
import{ Sun, RocketLaunch } from "svelte-heros-v2";
import { t } from "svelte-i18n";
import { Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell, Toggle } from 'flowbite-svelte';
import CodeMirror from "svelte-codemirror-editor";
import {StreamLanguage} from "@codemirror/language"
import { sparql } from "@codemirror/legacy-modes/mode/sparql";
import {basicSetup} from "codemirror"
import Highlight, { LineNumbers } from "svelte-highlight";
import hljs from "highlight.js";
import { definer } from "../turtle";
import "svelte-highlight/styles/github.css";
import { each } from "svelte/internal";
const language = {
name: "turtle",
register: (hljs) => {
return definer(hljs);
},
};
onMount(()=>{
reset_in_memory();
if (!$in_memory_discrete){
$in_memory_discrete = "SELECT ?subject ?predicate ?object WHERE {\n ?subject ?predicate ?object .\n} LIMIT 10";
}
});
let union = false;
const run = async () => {
try{
await reset_toasts();
results = await sparql_query($in_memory_discrete, union);
} catch(e) {
toast_error(display_error(e));
}
}
const openTurtle = () => {
reset_toasts();
set_viewer("n:g:z:rdf_viewer:turtle");
}
let results = undefined;
</script>
<div class="flex-col">
<CodeMirror bind:value={$in_memory_discrete} lineWrapping useTab={false} extensions={[basicSetup,StreamLanguage.define(sparql)]} styles={{
"&": {
maxWidth: "100%",
},
}}/>
<Toggle class="mt-1 ml-2" bind:checked={union}>Query all docs</Toggle>
<button
on:click={run}
on:keypress={run}
class="select-none ml-2 mt-2 mb-10 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
<RocketLaunch tabindex="-1" class="mr-2 focus:outline-none" />
Run Query
</button>
<button
on:click={openTurtle}
on:keypress={openTurtle}
class="select-none ml-2 mt-2 mb-10 text-gray-600 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
<Sun class="mr-2 focus:outline-none" tabindex="-1" />
View Graph
</button>
{#if results!==undefined}
<div>
<span class="ml-2 font-bold">Results: <br/></span>
{#if Array.isArray(results)}
{#if results.length}
<Highlight {language} code={results.join(" .\r\n") + (results.length ? " .":"")} class="mb-10" let:highlighted >
<LineNumbers {highlighted} wrapLines hideBorder />
</Highlight>
{:else}
Empty
{/if}
{:else if results?.head}
<Table>
<TableHead>
{#each results.head.vars as variable}
<TableHeadCell>{variable}</TableHeadCell>
{/each}
</TableHead>
<TableBody tableBodyClass="divide-y">
{#each results.results.bindings as row}
<TableBodyRow>
{#each results.head.vars as variable}
<TableBodyCell class="px-6 py-4 whitespace-break-spaces font-medium">{row[variable].value}</TableBodyCell>
{/each}
</TableBodyRow>
{/each}
</TableBody>
</Table>
{:else}
<span class="ml-2">{results}</span>
{/if}
</div>
{/if}
</div>

@ -0,0 +1,78 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { onMount, tick, onDestroy } from "svelte";
import {
sparql_update,
toast_error,
toast_success,
reset_toasts,
display_error,
} from "../store";
import {
in_memory_discrete, open_viewer, reset_in_memory
} from "../tab";
import{ Sun, RocketLaunch } from "svelte-heros-v2";
import { t } from "svelte-i18n";
import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte";
import CodeMirror from "svelte-codemirror-editor";
import {StreamLanguage} from "@codemirror/language"
import { sparql } from "@codemirror/legacy-modes/mode/sparql";
import {basicSetup} from "codemirror"
onMount(()=>{
reset_in_memory();
if (!$in_memory_discrete){
$in_memory_discrete = "INSERT DATA { \n <did:ng:test> <test:predicate> \"An example value\".\r}";
}
});
const run = async () => {
try{
await reset_toasts();
await sparql_update($in_memory_discrete);
toast_success($t("app.sparql_update_editor.success"));
} catch(e) {
toast_error(display_error(e));
}
}
const openViewer = () => {
reset_toasts();
open_viewer();
}
</script>
<div class="flex-col">
<CodeMirror bind:value={$in_memory_discrete} lineWrapping useTab={false} extensions={[basicSetup,StreamLanguage.define(sparql)]} styles={{
"&": {
maxWidth: "100%",
},
}}/>
<button
on:click={run}
on:keypress={run}
class="select-none ml-2 mt-2 mb-10 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
<RocketLaunch tabindex="-1" class="mr-2 focus:outline-none" />
Run Update
</button>
<button
on:click={openViewer}
on:keypress={openViewer}
class="select-none ml-2 mt-2 mb-10 text-gray-600 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
<Sun class="mr-2 focus:outline-none" tabindex="-1" />
View Graph
</button>
</div>

@ -0,0 +1,82 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { onMount, tick, onDestroy } from "svelte";
import {
sparql_update,
toast_error,
toast_success
} from "../store";
import {
in_memory_discrete, open_viewer, set_viewer, set_editor, set_view_or_edit, cur_tab
} from "../tab";
import{ PencilSquare, RocketLaunch } from "svelte-heros-v2";
import { t } from "svelte-i18n";
import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte";
import Highlight, { LineNumbers } from "svelte-highlight";
import hljs from "highlight.js";
import { definer } from "../turtle";
import "svelte-highlight/styles/github.css";
const language = {
name: "turtle",
register: (hljs) => {
return definer(hljs);
},
};
export let commits = {graph:[]};
let source = "";
$: source = commits.graph.join(" .\r\n") + (commits.graph.length ? " .":"");
const openQuery = () => {
set_viewer("n:g:z:sparql_query");
}
const openUpdate = () => {
set_editor("n:g:z:sparql_update");
set_view_or_edit(false);
}
onMount(()=>{
});
</script>
<div class="flex-col">
<button
on:click={openQuery}
on:keypress={openQuery}
class="select-none ml-2 mt-2 mb-2 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
<RocketLaunch tabindex="-1" class="mr-2 focus:outline-none" />
SPARQL Query
</button>
{#if $cur_tab.doc.can_edit}
<button
on:click={openUpdate}
on:keypress={openUpdate}
class="select-none ml-2 mt-2 text-gray-600 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
<PencilSquare class="mr-2 focus:outline-none" tabindex="-1" />
SPARQL Update
</button>
{/if}
{#if source}
<Highlight {language} code={source} class="mb-10" let:highlighted >
<LineNumbers {highlighted} wrapLines hideBorder />
</Highlight>
{/if}
</div>

@ -11,6 +11,6 @@
style="fill:#ffffff;stroke:none;stroke-width:0.268375" />
<path
d="M 98.343352,190.26108 C 80.403778,187.53354 65.011938,179.57839 52.608228,166.62327 38.602093,151.99448 31.178059,133.41381 31.178059,112.98841 c 0,-10.21889 1.700058,-19.44396 5.221234,-28.332119 4.28678,-10.820699 10.037295,-19.39063 18.535095,-27.62263 4.72982,-4.58187 6.60687,-6.10643 11.28099,-9.16256 11.89869,-7.779841 24.173884,-11.879991 38.095802,-12.724761 19.80437,-1.2017 39.11165,5.11306 54.60284,17.858751 1.50718,1.24006 2.72951,2.35934 2.71628,2.48729 -0.0132,0.12795 -3.85821,3.63443 -8.54442,7.79217 -4.6862,4.157729 -10.04724,8.96276 -11.91342,10.677819 -1.86617,1.715071 -3.54094,3.11831 -3.7217,3.11831 -0.18075,0 -1.39985,-0.745188 -2.70911,-1.655969 -7.53011,-5.23834 -15.99428,-7.82188 -25.62597,-7.82188 -12.731628,0 -23.249192,4.3379 -32.143882,13.257541 -6.39594,6.413868 -10.70387,14.555268 -12.50018,23.623578 -0.69099,3.48832 -0.68968,13.53072 0.002,17.00893 3.70508,18.62577 18.31886,33.10194 36.642322,36.29729 4.16439,0.72621 11.98099,0.71223 15.98975,-0.0286 14.03187,-2.59311 25.86047,-11.36806 32.26533,-23.93578 0.77379,-1.51834 1.26018,-2.88461 1.08086,-3.03616 -0.17934,-0.15156 -6.87448,-1.1779 -14.87813,-2.28078 -9.7795,-1.34758 -14.92353,-2.21379 -15.68471,-2.64117 -1.52067,-0.85379 -2.83611,-2.88806 -2.83611,-4.3859 0,-1.1732 2.02687,-15.86876 2.49085,-18.05962 0.29676,-1.40127 2.42559,-3.4934 3.84317,-3.77691 0.62227,-0.12445 8.82712,0.85555 18.28065,2.18348 9.43343,1.32511 17.26269,2.29453 17.39833,2.15427 0.13566,-0.14026 1.11808,-6.54833 2.18313,-14.24014 1.10778,-8.000208 2.20407,-14.60184 2.56177,-15.426229 0.34392,-0.792599 1.11019,-1.849131 1.70287,-2.34782 2.06321,-1.736079 3.1433,-1.785011 12.20439,-0.55291 9.63637,1.310309 10.70873,1.56224 12.28077,2.88503 1.64359,1.382979 2.2732,2.810909 2.25906,5.123309 -0.007,1.10173 -0.92172,8.29645 -2.03332,15.98826 -1.11158,7.69182 -1.97159,14.04091 -1.91113,14.1091 0.0605,0.0682 7.16644,1.11143 15.79109,2.31832 11.10566,1.55407 16.00827,2.38757 16.80223,2.85657 1.53015,0.90389 2.48023,2.64785 2.45017,4.49756 -0.0462,2.84349 -2.41252,18.12279 -2.97521,19.21089 -0.66164,1.27949 -2.60244,2.54696 -3.92109,2.56074 -0.51973,0.005 -7.87449,-0.95937 -16.34391,-2.144 -8.46944,-1.18464 -15.47588,-2.077 -15.56986,-1.98301 -0.094,0.094 -1.18792,7.34163 -2.43097,16.10589 -1.44004,10.15311 -2.49792,16.43621 -2.91556,17.31631 -0.72531,1.52848 -2.76261,3.06291 -4.53817,3.41802 -0.95688,0.19138 -10.90014,-0.92798 -13.59859,-1.53084 -0.5471,-0.12223 -1.89146,0.67252 -4.50941,2.66588 -11.2627,8.57562 -24.34195,13.90917 -38.35741,15.64164 -4.40038,0.54395 -15.72658,0.43298 -19.853658,-0.19451 z"
style="fill:#888;fill-opacity:1;stroke:#888;stroke-width:0.377976;stroke-opacity:1" />
/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

@ -0,0 +1,59 @@
/*
* Base64URL-ArrayBuffer
* https://github.com/herrjemand/Base64URL-ArrayBuffer
*
* Copyright (c) 2017 Yuriy Ackermann <ackermann.yuriy@gmail.com>
* Copyright (c) 2012 Niklas von Hertzen
* Licensed under the MIT license.
*
*/
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
// Use a lookup table to find the index.
var lookup = new Uint8Array(256);
for (var i = 0; i < chars.length; i++) {
lookup[chars.charCodeAt(i)] = i;
}
export const encode = function(arraybuffer) {
var bytes = new Uint8Array(arraybuffer),
i, len = bytes.length, base64 = "";
for (i = 0; i < len; i+=3) {
base64 += chars[bytes[i] >> 2];
base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
base64 += chars[bytes[i + 2] & 63];
}
if ((len % 3) === 2) {
base64 = base64.substring(0, base64.length - 1);
} else if (len % 3 === 1) {
base64 = base64.substring(0, base64.length - 2);
}
return base64;
};
export const decode = function(base64) {
var bufferLength = base64.length * 0.75,
len = base64.length, i, p = 0,
encoded1, encoded2, encoded3, encoded4;
var arraybuffer = new ArrayBuffer(bufferLength),
bytes = new Uint8Array(arraybuffer);
for (i = 0; i < len; i+=4) {
encoded1 = lookup[base64.charCodeAt(i)];
encoded2 = lookup[base64.charCodeAt(i+1)];
encoded3 = lookup[base64.charCodeAt(i+2)];
encoded4 = lookup[base64.charCodeAt(i+3)];
bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
}
return arraybuffer;
};

File diff suppressed because it is too large Load Diff

@ -12,7 +12,8 @@
<script lang="ts">
import ng from "../api";
import { onMount, tick } from "svelte";
import { current_lang, available_languages } from "../store";
import { locale, t } from "svelte-i18n";
import { available_languages } from "../store";
import { Language } from "svelte-heros-v2";
export let displayFooter = false;
@ -30,7 +31,7 @@
}
const selectLang = async (lang) => {
current_lang.set(lang);
locale.set(lang);
changingLang = false;
await tick();
scrollToTop();
@ -61,7 +62,7 @@
<div class="mb-20 mt-10">
<button
on:click={changeLang}
class="text-primary-700 bg-[#f6f6f6] bg-none ring-0 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55"
class="text-primary-700 bg-white bg-none ring-0 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55"
>
<Language
tabindex="-1"
@ -71,8 +72,9 @@
<br />
<button
on:click={displayNextgraphOrg}
class="text-primary-700 bg-[#f6f6f6] bg-none ring-0 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
>About NextGraph
class="text-primary-700 bg-white bg-none ring-0 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
>
{$t("common.about_nextgraph")}
</button>
</div>
</div>
@ -104,7 +106,5 @@
text-align: center;
width: fit-content;
}
li.clickable {
cursor: pointer;
}
</style>

@ -1,187 +0,0 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import {
Icon,
BugAnt,
DocumentText,
Window,
CodeBracket,
SquaresPlus,
ViewfinderCircle,
ArrowsPointingOut,
Cube,
Briefcase,
MagnifyingGlass,
RocketLaunch,
Sun,
TableCells,
ListBullet,
RectangleGroup,
Squares2x2,
MapPin,
CircleStack,
Envelope,
GlobeAlt,
DocumentChartBar,
Document,
ClipboardDocumentList,
Photo,
Film,
RectangleStack,
Microphone,
MusicalNote,
Ticket,
CursorArrowRays,
Megaphone,
User,
Clock,
CalendarDays,
Calendar,
Stop,
Flag,
HandRaised,
Newspaper,
PencilSquare,
CubeTransparent,
PresentationChartBar,
QuestionMarkCircle,
CheckCircle,
ChartPie,
Bars3BottomLeft,
Link,
Square2Stack,
Clipboard,
StopCircle,
Bolt,
Heart,
} from "svelte-heros-v2";
export let config = {};
export let dataClass: string;
const exact_mapping = {
page: Window,
"app/z": SquaresPlus,
class: ViewfinderCircle,
contract: Briefcase,
"query/text": MagnifyingGlass,
"query/web": MagnifyingGlass,
"data/graph": Sun,
"data/table": TableCells,
"data/collection": ListBullet,
"data/board": RectangleGroup,
"data/grid": Squares2x2,
"data/geomap": MapPin,
"e/email": Envelope,
"mc/text": Bars3BottomLeft,
"mc/link": Link,
"plato/card": Clipboard,
"plato/pad": Square2Stack,
"media/image": Photo,
"media/reel": Film,
"media/video": Film,
"media/album": RectangleStack,
"media/audio": Microphone,
"media/song": MusicalNote,
"media/subtitle": Ticket,
"media/overlay": CursorArrowRays,
"social/channel": Megaphone,
"social/stream": Bolt,
"social/contact": User,
"social/event": Clock,
"social/calendar": CalendarDays,
"social/scheduler": Calendar,
"social/reaction": Heart,
"prod/task": Stop,
"prod/project": Flag,
"prod/issue": HandRaised,
"prod/form": Newspaper,
"prod/filling": PencilSquare,
"prod/cad": CubeTransparent,
"prod/slides": PresentationChartBar,
"prod/question": QuestionMarkCircle,
"prod/answer": CheckCircle,
"prod/poll": QuestionMarkCircle,
"prod/vote": CheckCircle,
};
const prefix_mapping = {
"post/": DocumentText,
code: CodeBracket,
schema: ArrowsPointingOut,
service: Cube,
"e/": GlobeAlt,
"app/": StopCircle,
"query/": RocketLaunch,
"data/": CircleStack,
"doc/diagram": DocumentChartBar,
"doc/chart": ChartPie,
"doc/viz": ChartPie,
"doc/": ClipboardDocumentList,
file: Document,
};
const find = (t) => {
let e = exact_mapping[t];
if (e) return e;
for (let prefix of Object.entries(prefix_mapping)) {
if (t.startsWith(prefix[0])) return prefix[1];
}
return BugAnt;
};
</script>
<!--
did:ng:n:g:z:[official apps]
did:ng:n:g:ns
did:ng:n:g:x list of context used by nextgraph
rdf: http://www.w3.org/1999/02/22-rdf-syntax-ns#
rdfs: http://www.w3.org/2000/01/rdf-schema#
schema: https://schema.org/
skos: http://www.w3.org/2004/02/skos/core#
owl: http://www.w3.org/2002/07/owl#
foaf: http://xmlns.com/foaf/0.1/
relationship: http://purl.org/vocab/relationship/
dcterms: http://purl.org/dc/terms/
dcmitype: http://purl.org/dc/dcmitype/
sh: http://www.w3.org/ns/shacl#
shex: http://www.w3.org/ns/shex#
xsd: http://www.w3.org/2001/XMLSchema#
as: https://www.w3.org/ns/activitystreams#
ldp: http://www.w3.org/ns/ldp#
vcard: http://www.w3.org/2006/vcard/ns#
sec: https://w3id.org/security#
wgs: http://www.w3.org/2003/01/geo/wgs84_pos#
cc: http://creativecommons.org/ns#
gn: https://www.geonames.org/ontology#
geo: http://www.opengis.net/ont/geosparql#
time: http://www.w3.org/2006/time#
ng: did:ng:n:g:ns# or http://nextgraph.org/ns#
did:ng:n:g:ns#post/rich
ng:class => shortcut for did:ng:n:g:ns#class
a rdfs:Class
a ng:class
did:ng:o:xxxx:yy:yy
did:ng:n:xx.xx#name
did:ng:n:x: curated list of ontologies
did:ng:k common list of things (keyword)
did:ng:n:c common data
did:ng:n:z: curated list of external apps and services
http://nextgraph.org/ns# => the ng: ontology (did:ng:n:g:ns#)
ng:compat -> owl:unionOf rdf:List (alphabetical order, including itself as first element)
-->
<Icon {...config} variation="outline" color="black" icon={find(dataClass)} />

@ -0,0 +1,110 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import {
branch_subscribe,
active_session,
cannot_load_offline,
online,
} from "../store";
import {
Pencil,
} from "svelte-heros-v2";
import { t } from "svelte-i18n";
import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte";
import { inview } from 'svelte-inview';
import { cur_tab, nav_bar, can_have_header, header_icon, header_title, header_description, cur_branch, set_header_in_view, edit_header_button, cur_app, load_official_app } from "../tab";
import NavIcon from "./icons/NavIcon.svelte";
export let nuri = "";
let width;
let commits;
$: commits = $active_session && nuri && branch_subscribe(nuri, true);
const inview_options = {};//{rootMargin: "-44px"};
function openEditHeader() {
//TODO
}
</script>
<div bind:clientWidth={width}>
{#if $cannot_load_offline}
<div class="row p-4">
<Alert color="yellow">
{@html $t("doc.cannot_load_offline")}
<a href="#/user">{$t("pages.user_panel.title")}</a>.
</Alert>
</div>
{:else}
<div class="flex justify-left" class:justify-center={width>1024} use:inview={inview_options} on:inview_change={(event) => {
const { inView, entry, scrollDirection, observer, node} = event.detail;
if ($cur_branch) { set_header_in_view(inView); }
if (inView) $nav_bar.newest = 0;
}}>
<div class="flex flex-col ">
{#if $can_have_header}
<div class="flex p-4 max-w-screen-lg justify-start flex-wrap" class:w-[1024px]={width>1024} >
{#if $header_icon}
<NavIcon img={$header_icon} config={{
tabindex:"-1",
class:"w-8 h-8 mr-2 mb-2 flex-none focus:outline-none"
}}/>
{/if}
{#if $cur_tab.doc.can_edit}
<button class="p-1 mr-2 mb-2 w-8 h-8 flex-none" on:click={openEditHeader} title={$t($edit_header_button)}>
<Pencil tabindex=-1 class="w-5 h-5 focus:outline-none" />
</button>{#if !$header_title}<span role="button" on:click={openEditHeader} on:keypress={openEditHeader} tabindex="-1" class="h-8 py-1 inline-block align-middle ">{$t($edit_header_button)}</span> {/if}
{/if}
{#if $header_title}
<h1 class="grow text-left text-2xl">{$header_title}</h1>
{/if}
</div>
{#if $header_description}
<div class="flex p-4 max-w-screen-lg text-left text-gray-600 dark:text-white" class:w-[1024px]={width>1024}>
{$header_description}
</div>
{/if}
{/if}
{#if commits}
{#await commits.load()}
<div class="row p-4 max-w-screen-lg text-gray-600" class:w-[1024px]={width>1024}>
<p>{$t("connectivity.loading")}...</p>
</div>
{:then}
{#if $cur_app}
{#await load_official_app($cur_app) then app}
<div class="flex max-w-screen-lg" style="overflow-wrap: anywhere;" class:w-[1024px]={width>1024} >
<svelte:component this={app} commits={$commits}/>
</div>
{/await}
{/if}
{/await}
{/if}
</div>
</div>
{/if}
</div>

File diff suppressed because it is too large Load Diff

@ -10,20 +10,30 @@
-->
<script lang="ts">
import { online } from "../store";
import { onMount, tick } from "svelte";
import { t } from "svelte-i18n";
import FullLayout from "./FullLayout.svelte";
import Test from "./Test.svelte";
import Document from "./Document.svelte";
import {
active_session,
} from "../store";
import {
change_nav_bar,reset_in_memory
} from "../tab";
import {
PaperAirplane,
Bell,
ArrowRightOnRectangle,
Users,
User,
Bookmark,
Sparkles,
Square3Stack3d,
ArchiveBox,
} from "svelte-heros-v2";
// @ts-ignore
import Logo from "../assets/nextgraph.svg?component";
// @ts-ignore
import LogoGray from "../assets/nextgraph-gray.svg?component";
import Logo from "./components/Logo.svelte";
import NavBar from "./components/NavBar.svelte";
let top;
let width: number;
let breakPoint: number = 662;
let mobile = false;
@ -32,35 +42,41 @@
} else {
mobile = true;
}
function scrollToTop() {
top.scrollIntoView();
}
onMount(() => {
change_nav_bar("nav:private",$t("doc.private_store"), false);
reset_in_memory();
});
let nuri = $active_session && ("o:"+$active_session.private_store_id);
</script>
<FullLayout>
<FullLayout withoutNavBar={true}>
{#if mobile}
<nav
class="border-t border-solid border-gray-200 bg-white dark:bg-gray-900 text-gray-700 dark:text-gray-200 dark:border-gray-700 divide-gray-100 dark:divide-gray-700 px-2 sm:px-4 py-2.5 w-full"
<nav bind:this={top}
style="background-color: #f6f6f6;" class="border-t border-solid border-gray-200 text-gray-700 dark:text-gray-200 dark:border-gray-700 divide-gray-100 dark:divide-gray-700 px-2 sm:px-4 py-2.5 w-full"
>
<div
class="mx-auto flex flex-wrap justify-between items-center w-full px-2 xxs:px-8 xs:px-10"
class="mx-auto flex flex-wrap justify-between items-center w-full xxs:px-8 xs:px-10"
>
<a href="#/user" class="flex items-center" on:click>
{#if $online}
<Logo class="w-7 h-7 tall:w-10 tall:h-10" />
{:else}
<LogoGray class="w-7 h-7 tall:w-10 tall:h-10" />
{/if}
<a href="#/user" class="flex items-center" >
<Logo className="w-7 h-7 tall:w-10 tall:h-10" />
<span
class="ml-4 self-center text-lg font-normal text-gray-900 rounded-lg dark:text-white whitespace-nowrap"
class="ml-2 self-center text-base font-normal text-gray-900 rounded-lg dark:text-white whitespace-nowrap"
>NextGraph</span
>
</a>
<div class="w-auto flex row">
<a href="#/shared" class="row items-center" on:click>
<Users
<a href="#/site" class="row items-center" on:click={scrollToTop}>
<User
tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white focus:outline-none"
/>
</a>
<a href="#/messages" class="ml-6 row items-center" on:click>
<a href="#/messages" class="ml-4 row items-center" >
<PaperAirplane
tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white focus:outline-none"
@ -72,7 +88,7 @@
</span>
</a>
<a href="#/notifications" class="ml-4 row items-center" on:click>
<a href="#/notifications" class="ml-4 row items-center" >
<Bell
tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white focus:outline-none"
@ -86,9 +102,23 @@
</div>
</div>
</nav>
{/if}
<div />
<div class="sticky top-0 w-full" style="z-index:39;">
<Test />
<NavBar {scrollToTop}/>
</div>
{/if}
<div class="bg-gray-100 flex p-1 justify-around md:justify-start h-11 gap-0 xs:gap-3 text-gray-500">
<div class="overflow-hidden w-24 sm:ml-3 flex justify-start mr-1" role="button" tabindex="0">
<Bookmark tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"><div style="overflow-wrap: anywhere;" class="max-h-8 xs:max-h-10">{$t("doc.header.buttons.bookmarked")}</div></div>
</div>
<div class="overflow-hidden w-32 sm:ml-3 flex justify-start mr-1" role="button" tabindex="0" title={$t("doc.menu.items.mc.desc")}>
<Sparkles tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"><div style="overflow-wrap: anywhere;" class="max-h-8 xs:max-h-10">{$t("doc.menu.items.mc.label")}</div></div>
</div>
<div class="overflow-hidden w-28 sm:ml-3 flex justify-start" role="button" tabindex="0">
<Square3Stack3d tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"><div style="overflow-wrap: anywhere;" class="max-h-8 xs:max-h-10">{$t("doc.header.buttons.all_docs")}</div></div>
</div>
</div>
<Document {nuri}/>
</FullLayout>
<svelte:window bind:innerWidth={width} />

File diff suppressed because one or more lines are too long

@ -9,26 +9,52 @@
// according to those terms.
-->
<!--
The Login Procedure.
Has multiple states (steps) through the user flow.
-->
<script lang="ts">
import { Alert, Toggle } from "flowbite-svelte";
import { onMount, createEventDispatcher, tick } from "svelte";
import { Alert, Toggle, Button } from "flowbite-svelte";
import { onMount, createEventDispatcher } from "svelte";
import { t } from "svelte-i18n";
import ng from "../api";
import { emoji_cat, emojis, load_svg } from "../wallet_emojis";
import { PuzzlePiece } from "svelte-heros-v2";
import { emoji_cat, emojis, load_svg, type Emoji } from "../wallet_emojis";
import {
PuzzlePiece,
XCircle,
Backspace,
ArrowPath,
LockOpen,
CheckCircle,
ArrowLeft,
} from "svelte-heros-v2";
import PasswordInput from "./components/PasswordInput.svelte";
import Spinner from "./components/Spinner.svelte";
import { display_error } from "../store";
//import Worker from "../worker.js?worker&inline";
export let wallet;
export let for_import = false;
let top;
function scrollToTop() {
top.scrollIntoView();
}
let tauri_platform = import.meta.env.TAURI_PLATFORM;
let mobile = tauri_platform == "android" || tauri_platform == "ios";
const dispatch = createEventDispatcher();
onMount(async () => {
loaded = false;
await load_svg();
if (for_import) {
device_name = await ng.get_device_name();
}
load_svg();
//console.log(wallet);
await init();
});
async function init() {
@ -46,19 +72,31 @@
}
emojis2 = emojis2;
display = 0;
pazzlePage = 0;
selection = [];
error = undefined;
scrollToTop();
// This is only for awaiting that SVGs are loaded.
await load_svg();
loaded = true;
}
function letsgo() {
function start_with_pazzle() {
loaded = false;
step = "pazzle";
unlockWith = "pazzle";
scrollToTop();
}
async function start_with_mnemonic() {
loaded = false;
step = "mnemonic";
unlockWith = "mnemonic";
scrollToTop();
}
let emojis2 = [];
let emojis2: Emoji[][] = [];
let shuffle;
@ -68,29 +106,35 @@
let pazzle_length = 9;
let display = 0;
let pazzlePage = 0;
let selection = [];
/** The selected emojis by category (one for each pazzle page). First will be the selected of first pazzle page. */
let selection = [].fill(null, 0, pazzle_length);
let pin_code = [];
/** The selected order from the order page. */
let ordered = [];
let last_one = {};
let shuffle_pin;
let error;
let trusted = false;
let trusted = true;
let mnemonic = "";
let unlockWith: "pazzle" | "mnemonic" | undefined;
let device_name;
function order() {
step = "order";
ordered = [];
last_one = {};
for (let i = 0; i < pazzle_length; i++) {
last_one[i] = true;
}
// In case, this is called by the cancel button, we need to reset the selection.
selection.forEach((emoji) => (emoji.sel = undefined));
selection = selection;
scrollToTop();
}
async function start_pin() {
@ -101,20 +145,19 @@
//console.log(shuffle_pin);
}
/** Called on selecting emoji in a category. */
function select(val) {
//console.log(emojis2[display][val]);
let cat_idx = shuffle.category_indices[display];
let cat_idx = shuffle.category_indices[pazzlePage];
let cat = emojis[emoji_cat[cat_idx]];
let idx = shuffle.emoji_indices[display][val];
//console.log(cat_idx, emoji_cat[cat_idx], idx, cat[idx].code);
let idx = shuffle.emoji_indices[pazzlePage][val];
selection.push({ cat: cat_idx, index: idx });
//console.log(selection);
selection[pazzlePage] = { cat: cat_idx, index: idx };
if (display == pazzle_length - 1) {
if (pazzlePage == pazzle_length - 1) {
order();
} else {
display = display + 1;
pazzlePage = pazzlePage + 1;
}
}
@ -122,22 +165,24 @@
step = "opening";
let pazzle = [];
for (const emoji of ordered) {
pazzle.push((emoji.cat << 4) + emoji.index);
}
//console.log(pazzle);
//console.log(wallet);
const mnemonic_words = mnemonic.split(" ").filter((t) => t !== "");
// open the wallet
try {
if (tauri_platform) {
let opened_wallet = await ng.wallet_open_with_pazzle(
wallet,
pazzle,
pin_code
);
// TODO @niko: Add device_name as param to open_with_* APIs
let opened_wallet =
unlockWith === "pazzle"
? await ng.wallet_open_with_pazzle(wallet, pazzle, pin_code)
: await ng.wallet_open_with_mnemonic_words(
wallet,
mnemonic_words,
pin_code
);
// try {
// let client = await ng.wallet_was_opened(opened_wallet);
// opened_wallet.V0.client = client;
@ -153,6 +198,7 @@
wallet: opened_wallet,
id: opened_wallet.V0.wallet_id,
trusted,
device_name,
});
} else {
let worker_import = await import("../worker.js?worker&inline");
@ -172,7 +218,16 @@
myWorker.onmessage = async (msg) => {
//console.log("Message received from worker", msg.data);
if (msg.data.loaded) {
myWorker.postMessage({ wallet, pazzle, pin_code });
if (unlockWith === "pazzle") {
myWorker.postMessage({ wallet, pazzle, pin_code, device_name });
} else {
myWorker.postMessage({
wallet,
mnemonic_words,
pin_code,
device_name,
});
}
//console.log("postMessage");
} else if (msg.data.success) {
//console.log(msg.data);
@ -191,6 +246,7 @@
wallet: msg.data.success,
id: msg.data.success.V0.wallet_id,
trusted,
device_name,
});
} else {
console.error(msg.data.error);
@ -214,23 +270,20 @@
dispatch("cancel");
}
async function pin(val) {
//console.log(val);
pin_code.push(val);
async function on_pin_key(val) {
pin_code = [...pin_code, val];
if (pin_code.length == 4) {
await finish();
setTimeout(()=>window.document.getElementById("confirm_pin_btn").focus(),50);
}
}
async function select_order(val, pos) {
delete last_one[pos];
//console.log(last_one);
//console.log(val);
async function select_order(val) {
ordered.push(val);
val.sel = ordered.length;
selection = selection;
if (ordered.length == pazzle_length - 1) {
let last = selection[Object.keys(last_one)[0]];
let last = selection.find((emoji) => !emoji.sel);
ordered.push(last);
last.sel = ordered.length;
selection = selection;
@ -238,278 +291,465 @@
await start_pin();
}
}
function go_back() {
if (step === "mnemonic") {
init();
} else if (step === "pazzle") {
// Go to previous pazzle or init page, if on first pazzle.
if (pazzlePage === 0) {
init();
} else {
pazzlePage -= 1;
}
} else if (step === "order") {
if (ordered.length === 0) {
step = "pazzle";
} else {
const last_selected = ordered.pop();
last_selected.sel = null;
ordered = ordered;
selection = selection;
}
} else if (step === "pin") {
if (pin_code.length === 0) {
if (unlockWith === "mnemonic") {
start_with_mnemonic();
} else {
// Unselect the last two elements.
const to_unselect = ordered.slice(-2);
to_unselect.forEach((val) => {
val.sel = null;
});
ordered = ordered.slice(0, -2);
selection = selection;
step = "order";
}
} else {
pin_code = pin_code.slice(0, pin_code.length - 1);
}
}
}
let width: number;
let height: number;
const breakPointWidth: number = 535;
const breakPointHeight: number = 1005;
let mobile = false;
$: if (width >= breakPointWidth && height >= breakPointHeight) {
mobile = false;
} else {
mobile = true;
}
</script>
{#if step == "load"}
<div class=" max-w-xl lg:px-8 mx-auto px-4 mt-10">
<h2 class="pb-5 text-xl">How to open your wallet, step by step :</h2>
<ul class="mb-8 ml-3 space-y-4 text-left list-decimal">
<li>
For each one of the 9 categories of images, you will be presented with
the 15 possible image choices. The categories are shuffled at every
login. They will not always appear in the same order.
</li>
<li>
At each category, only one of the 15 displayed choices is the correct
image that belongs to your pazzle. Find it and tap or click on that one.
The 15 images are shuffled too, they will not appear at the same
position at each login. On a computer, you can also use the tab key on
your keyboard to move to the desired item on the screen, then press the
space bar to select each one.
</li>
<li>
Once you completed the last category, you will be presented with all the
images you have previously selected. Their order is displayed as it was
when you picked them. But this is not the correct order of the images in
your pazzle. You now have to order them correctly.
</li>
<li>
You must remember which image should be the first one in your pazzle.
Find it on the screen and click or tap on it. It will be greyed out and
the number 1 will appear on top of it.
</li>
<li>
Move on to the second image of your pazzle (that you memorized). Find it
on the screen and tap on it. Repeat this step until you reached the last
image.
</li>
<li>
Finally, your PIN code will be asked. enter it by clicking or tapping on
the digits.
</li>
</ul>
</div>
<div class=" max-w-xl lg:px-8 mx-auto px-4 text-primary-700">
{#if !loaded}
Loading pazzle...
<svg
class="animate-spin my-4 h-14 w-14 mx-auto"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
{:else}
<button
on:click={letsgo}
class="mt-1 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
<PuzzlePiece
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 group-hover:text-gray-900 dark:group-hover:text-white"
/>
Open my wallet now!
</button>
{/if}
</div>
{#if for_import}
<div class=" max-w-xl lg:px-8 mx-auto px-4 mb-8">
<span class="text-xl">Do you trust this device? </span> <br />
<div class="flex justify-center items-center my-4">
<Toggle class="" bind:checked={trusted}
>Yes, save my wallet on this device</Toggle
>
</div>
<p class="text-sm">
If you do, if this device is yours or is used by few trusted persons of
your family or workplace, and you would like to login again from this
device in the future, then you can save your wallet on this device. To
the contrary, if this device is public and shared by strangers, do not
save your wallet here. {#if !tauri_platform}By selecting this option,
you agree to save some cookies on your browser.{/if}<br />
</p>
</div>
{/if}
{:else if step == "pazzle"}
<div
class="h-screen aspect-[3/5] pazzleline"
class:min-w-[310px]={mobile}
class:min-w-[500px]={!mobile}
class:max-w-[360px]={mobile}
class:max-w-[600px]={!mobile}
>
{#each [0, 1, 2, 3, 4] as row}
<div class="columns-3 gap-0">
{#each emojis2[display]?.slice(0 + row * 3, 3 + row * 3) || [] as emoji, i}
<div
role="button"
<div
class="flex-col justify-center md:max-w-2xl py-4 sm:px-8"
class:h-screen={step !== "load" && height > 640}
class:flex={height > 640}
bind:this={top}
>
{#if step == "load"}
<div class="flex flex-col justify-center p-4 pt-6">
<h2 class="pb-5 text-xl self-start">
{$t("pages.login.heading")}
</h2>
<h3 class="pb-2 text-lg self-start">{$t("pages.login.with_pazzle")}</h3>
<ul class="mb-8 ml-3 space-y-4 text-justify text-sm list-decimal">
<li>
{$t("pages.login.pazzle_steps.1")}
</li>
<li>
{$t("pages.login.pazzle_steps.2")}
</li>
<li>
{$t("pages.login.pazzle_steps.3")}
</li>
<li>
{$t("pages.login.pazzle_steps.4")}
</li>
<li>
{$t("pages.login.pazzle_steps.5")}
</li>
<li>
{$t("pages.login.pazzle_steps.6")}
</li>
</ul>
<h3 class="pb-2 text-lg self-start">
{$t("pages.login.with_mnemonic")}
</h3>
<ul class="mb-8 ml-3 space-y-4 text-justify text-sm list-decimal">
<li>
{$t("pages.login.mnemonic_steps.1")}
</li>
<li>
{$t("pages.login.mnemonic_steps.2")}
</li>
</ul>
<!-- Save wallet? -->
{#if for_import}
<div class="max-w-xl lg:px-8 mx-auto px-4 mb-2">
<span class="text-xl"
>{$t("pages.wallet_create.save_wallet_options.trust")}
</span> <br />
<p class="text-sm">
{$t("pages.wallet_create.save_wallet_options.trust_description")}
{#if !tauri_platform}
{$t("pages.login.trust_device_allow_cookies")}{/if}<br />
</p>
<div class="flex justify-center items-center my-4">
<Toggle class="" bind:checked={trusted}
>{$t("pages.login.trust_device_yes")}</Toggle
>
</div>
</div>
{/if}
<div class="max-w-xl lg:px-8 mx-auto px-4 text-primary-700">
<div class="flex flex-col justify-centerspace-x-12 mt-4 mb-4">
<!-- Device Name, if trusted-->
{#if for_import}
<label for="device-name-input" class="text-sm text-black">
{$t("pages.login.device_name_label")}
</label>
<input
id="device-name-input"
bind:value={device_name}
placeholder={$t("pages.login.device_name_placeholder")}
type="text"
class="w-full mb-10 lg:px-8 mx-auto px-4 bg-gray-50 border border-gray-300 text-xs rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
/>
{/if}
{#if !loaded}
{$t("pages.login.loading_pazzle")}...
<Spinner className="my-4 h-14 w-14 mx-auto" />
{:else}
<button
on:click={start_with_pazzle}
class="mt-1 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
<PuzzlePiece
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>
{$t("pages.login.open_with_pazzle")}
</button>
{/if}
<button
on:click={cancel}
class="mt-3 mb-2 text-gray-500 dark:text-gray-400 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
><ArrowLeft
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>{$t("pages.login.login_cancel")}</button
>
<span
on:click={start_with_mnemonic}
on:keypress={start_with_mnemonic}
role="link"
tabindex="0"
class="w-full aspect-square emoji focus:outline-none focus:bg-gray-300"
on:click={() => select(row * 3 + i)}
on:keypress={() => select(row * 3 + i)}
class="mt-1 text-lg px-5 py-2.5 text-center inline-flex items-center underline cursor-pointer"
>
<svelte:component this={emoji.svg?.default} />
</div>
{/each}
{$t("pages.login.open_with_mnemonic")}
</span>
</div>
</div>
{/each}
</div>
{:else if step == "order"}
<!-- console.log(cat_idx, emoji_cat[cat_idx], idx, cat[idx].code); -->
<div
class="h-screen aspect-[3/3] pazzleline"
class:min-w-[320px]={mobile}
class:min-w-[500px]={!mobile}
class:max-w-[360px]={mobile}
class:max-w-[600px]={!mobile}
>
{#each [0, 1, 2] as row}
<div class="columns-3 gap-0">
{#each selection.slice(0 + row * 3, 3 + row * 3) || [] as emoji, i}
{#if !emoji.sel}
<div
role="button"
tabindex="0"
class="w-full aspect-square emoji focus:outline-none focus:bg-gray-300"
on:click={() => select_order(emoji, row * 3 + i)}
on:keypress={() => select_order(emoji, row * 3 + i)}
</div>
<!-- The following steps have navigation buttons and fixed layout -->
{:else if step == "pazzle" || step == "order" || step == "pin" || step == "mnemonic"}
<div
class="flex-col justify-center h-screen"
class:flex={height > 640}
class:min-w-[300px]={mobile}
class:min-w-[500px]={!mobile}
class:max-w-[370px]={mobile}
class:max-w-[600px]={!mobile}
>
<div class="mt-auto flex flex-col justify-center">
<!-- Unlock Screens -->
{#if step == "mnemonic"}
<form on:submit|preventDefault={start_pin}>
<label
for="mnemonic-input"
class="block mb-2 text-xl text-gray-900 dark:text-white"
>{$t("pages.login.enter_mnemonic")}</label
>
<svelte:component
this={emojis[emoji_cat[emoji.cat]][emoji.index].svg?.default}
/>
</div>
{:else}
<div class="w-full aspect-square opacity-25 select-none sel-emoji">
<svelte:component
this={emojis[emoji_cat[emoji.cat]][emoji.index].svg?.default}
/>
<span class="sel drop-shadow-[2px_2px_2px_rgba(255,255,255,1)]"
>{emoji.sel}</span
<PasswordInput
id="mnemonic-input"
placeholder={$t("pages.login.mnemonic_placeholder")}
bind:value={mnemonic}
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
auto_complete="mnemonic"
/>
<div class="flex">
<Button
type="submit"
class="mt-3 mb-2 ml-auto text-white bg-primary-700 hover:bg-primary-700/90 disabled:opacity-65 focus:ring-4 focus:ring-blue-500 focus:border-blue-500 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-blue-500 dark:focus:border-blue-500"
on:click={start_pin}
disabled={mnemonic.split(" ").length !== 12}
><CheckCircle
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 group-hover:text-gray-900 dark:group-hover:text-white"
/>{$t("buttons.confirm")}</Button
>
</div>
{/if}
{/each}
</div>
{/each}
</div>
{:else if step == "pin"}
<div class=" max-w-6xl lg:px-8 mx-auto px-3">
<p class="max-w-xl md:mx-auto lg:max-w-2xl">
<span class="text-xl">Enter your PIN code</span>
</p>
<div class="w-[295px] mx-auto">
{#each [0, 1, 2] as row}
<div class="">
{#each shuffle_pin.slice(0 + row * 3, 3 + row * 3) as num}
</form>
{:else if step == "pazzle"}
<p class="max-w-xl mx-auto lg:max-w-2xl">
<span class="text-xl">
{@html $t("pages.login.select_emoji", {
values: {
category: $t(
"emojis.category." +
emoji_cat[shuffle.category_indices[pazzlePage]]
),
},
})}</span
>
</p>
{#each [0, 1, 2, 3, 4] as row}
<div class="columns-3 gap-0">
{#each emojis2[pazzlePage]?.slice(0 + row * 3, 3 + row * 3) || [] as emoji, i (pazzlePage + "-" + row + "-" + i)}
<div
role="button"
tabindex="0"
class="w-full aspect-square emoji focus:outline-none focus:bg-gray-300"
title={$t("emojis.codes." + emoji.code)}
on:click={() => select(row * 3 + i)}
on:keypress={() => select(row * 3 + i)}
>
<svelte:component this={emoji.svg?.default} />
</div>
{/each}
</div>
{/each}
{:else if step == "order"}
<p class="max-w-xl mx-auto lg:max-w-2xl mb-2">
<span class="text-xl">{$t("pages.login.order_emojis")}</span>
</p>
{#each [0, 1, 2] as row}
<div class="columns-3 gap-0">
{#each selection.slice(0 + row * 3, 3 + row * 3) || [] as emoji, i}
{#if !emoji.sel}
<div
role="button"
tabindex="0"
class="w-full aspect-square emoji focus:outline-none focus:bg-gray-300"
on:click={() => select_order(emoji)}
on:keypress={() => select_order(emoji)}
title={$t(
"emojis.codes." +
emojis[emoji_cat[emoji.cat]][emoji.index].code
)}
>
<svelte:component
this={emojis[emoji_cat[emoji.cat]][emoji.index].svg
?.default}
/>
</div>
{:else}
<div
class="w-full aspect-square opacity-25 select-none sel-emoji"
title={$t(
"emojis.codes." +
emojis[emoji_cat[emoji.cat]][emoji.index].code
)}
>
<svelte:component
this={emojis[emoji_cat[emoji.cat]][emoji.index].svg
?.default}
/>
<span
class="sel drop-shadow-[2px_2px_2px_rgba(255,255,255,1)]"
class:text-[8em]={!mobile}
class:text-[6em]={mobile}>{emoji.sel}</span
>
</div>
{/if}
{/each}
</div>
{/each}
{:else if step == "pin"}
<p class="items-center">
<span class="text-xl">{$t("pages.login.enter_pin")}</span>
</p>
<!-- Chrome requires the columns-3 __flex__ to be set, or else it wraps the lines incorrectly.
However, this leads to the width decreasing and the buttons come together in mobile view.
So we need a way to fix the width across all screens. -->
{#each [0, 1, 2] as row}
<div class="columns-3 flex">
{#each shuffle_pin.slice(0 + row * 3, 3 + row * 3) as num}
<button
tabindex="0"
class="pindigit m-1 disabled:opacity-15 disabled:text-gray-200 select-none align-bottom text-7xl p-0 w-full aspect-square border-0"
class:h-[160px]={!mobile}
class:h-[93px]={mobile}
class:text-8xl={!mobile}
on:click={async () => {window.document.activeElement.blur(); await on_pin_key(num)}}
disabled={pin_code.length >= 4}
>
<span>{num}</span>
</button>
{/each}
</div>
{/each}
<div class="columns-3 flex">
<div class="m-1 w-full aspect-square" />
<button
tabindex="0"
class="m-1 select-none align-bottom text-7xl w-[90px] h-[90px] p-0"
on:click={async () => await pin(num)}
class="pindigit disabled:opacity-15 m-1 disabled:text-gray-200 select-none align-bottom text-7xl p-0 w-full aspect-square border-0"
class:h-[160px]={!mobile}
class:h-[93px]={mobile}
class:text-8xl={!mobile}
on:click={async () => {window.document.activeElement.blur();await on_pin_key(shuffle_pin[9])}}
disabled={pin_code.length >= 4}
>
<span>{num}</span>
<span>{shuffle_pin[9]}</span>
</button>
{/each}
</div>
{/each}
<button
tabindex="0"
class="m-1 select-none mx-auto align-bottom text-7xl w-[90px] h-[90px] p-0"
on:click={async () => await pin(shuffle_pin[9])}
>
<span>{shuffle_pin[9]}</span>
</button>
</div>
</div>
{:else if step == "opening"}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-primary-700">
Opening your wallet...<br />
Please wait
<svg
class="animate-spin mt-10 h-14 w-14 mx-auto"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
</div>
{:else if step == "end"}
{#if error}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-red-800">
An error occurred !
<svg
fill="none"
class="animate-bounce mt-10 h-10 w-10 mx-auto"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
/>
</svg>
<Alert color="red" class="mt-5">
{error}
</Alert>
<button class="mt-10 select-none" on:click={init}> Try again </button>
<button class="mt-10 ml-5 select-none" on:click={cancel}> Cancel </button>
<Button
tabindex="0"
id="confirm_pin_btn"
class="w-full bg-green-300 hover:bg-green-300/90 enabled:animate-bounce disabled:bg-gray-200 disabled:opacity-15 m-1 select-none align-bottom text-7xl p-0 aspect-square border-0"
on:click={async () => await finish()}
on:keypress={async () => await finish()}
disabled={pin_code.length < 4}
>
<LockOpen
tabindex="-1"
class="w-[50%] h-[50%] transition duration-75 focus:outline-none select-none group-hover:text-gray-900 dark:group-hover:text-white"
/>
</Button>
</div>
<span class="select-none text-9xl h-[4rem] text-center"
>{#each pin_code as pin_key}*{/each}</span
>
{/if}
</div>
<!-- Navigation Buttons for pazzle, order pin, mnemonic -->
<div class="flex justify-between mb-6 mt-auto">
<button
on:click={cancel}
class="mt-1 bg-red-100 hover:bg-red-100/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg sm:text-lg px-5 py-2.5 text-center select-none inline-flex items-center dark:focus:ring-primary-700/55"
><XCircle
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition focus:outline-none duration-75 group-hover:text-gray-900 dark:group-hover:text-white"
/>{$t("buttons.cancel")}</button
>
<button
class="mt-1 ml-2 min-w-[141px] focus:ring-4 focus:ring-primary-100/50 rounded-lg sm:text-lg px-5 py-2.5 text-center select-none inline-flex items-center dark:focus:ring-primary-700/55"
on:click={go_back}
><Backspace
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition focus:outline-none duration-75 group-hover:text-gray-900 dark:group-hover:text-white"
/>
{#if step === "mnemonic" || (step === "pazzle" && pazzlePage === 0)}
{$t("buttons.go_back")}
{:else}
{$t("buttons.correct")}
{/if}
</button>
</div>
</div>
{:else}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-green-800">
Your wallet is opened! <br />Please wait while the app is loading...
<svg
class="my-10 h-16 w-16 mx-auto"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9 12.75L11.25 15 15 9.75M21 12c0 1.268-.63 2.39-1.593 3.068a3.745 3.745 0 01-1.043 3.296 3.745 3.745 0 01-3.296 1.043A3.745 3.745 0 0112 21c-1.268 0-2.39-.63-3.068-1.593a3.746 3.746 0 01-3.296-1.043 3.745 3.745 0 01-1.043-3.296A3.745 3.745 0 013 12c0-1.268.63-2.39 1.593-3.068a3.745 3.745 0 011.043-3.296 3.746 3.746 0 013.296-1.043A3.746 3.746 0 0112 3c1.268 0 2.39.63 3.068 1.593a3.746 3.746 0 013.296 1.043 3.746 3.746 0 011.043 3.296A3.745 3.745 0 0121 12z"
/>
</svg>
{:else if step == "opening"}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-primary-700">
{@html $t("pages.login.opening_wallet")}
<Spinner className="mt-10 h-14 w-14 mx-auto" />
</div>
{:else if step == "end"}
{#if error}
<div class=" max-w-6xl lg:px-8 mx-auto text-red-800">
<div class="mt-auto max-w-6xl lg:px-8">
{$t("errors.an_error_occurred")}
<svg
fill="none"
class="animate-bounce mt-10 h-10 w-10 mx-auto"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
/>
</svg>
<Alert color="red" class="mt-5">
{display_error(error)}
</Alert>
</div>
<div class="flex justify-between mt-auto gap-4">
<button
on:click={cancel}
class="mt-10 bg-red-100 hover:bg-red-100/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
><XCircle
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>{$t("buttons.cancel")}</button
>
<button
class="mt-10 ml-2 select-none text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
on:click={init}
>
<ArrowPath
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>
{$t("buttons.try_again")}
</button>
</div>
</div>
{:else}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-green-800">
{@html $t("pages.login.wallet_opened")}
<svg
class="my-10 h-16 w-16 mx-auto"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9 12.75L11.25 15 15 9.75M21 12c0 1.268-.63 2.39-1.593 3.068a3.745 3.745 0 01-1.043 3.296 3.745 3.745 0 01-3.296 1.043A3.745 3.745 0 0112 21c-1.268 0-2.39-.63-3.068-1.593a3.746 3.746 0 01-3.296-1.043 3.745 3.745 0 01-1.043-3.296A3.745 3.745 0 013 12c0-1.268.63-2.39 1.593-3.068a3.745 3.745 0 011.043-3.296 3.746 3.746 0 013.296-1.043A3.746 3.746 0 0112 3c1.268 0 2.39.63 3.068 1.593a3.746 3.746 0 013.296 1.043 3.746 3.746 0 011.043 3.296A3.745 3.745 0 0121 12z"
/>
</svg>
</div>
{/if}
{/if}
{/if}
</div>
<svelte:window bind:innerWidth={width} bind:innerHeight={height} />
<style>
.pazzleline {
.pindigit {
min-height: 93px;
}
/* .pazzleline {
margin-right: auto;
margin-left: auto;
}
} */
.sel {
position: absolute;
display: flex;
width: 100%;
top: 45%;
height: 100%;
top: 0;
left: 0;
font-size: 100px;
font-weight: 700;
justify-content: center;
align-items: center;
}
.sel-emoji {

@ -24,7 +24,7 @@
</script>
<nav
class="border-t border-solid border-gray-200 bg-white dark:bg-gray-900 text-gray-700 dark:text-gray-200 dark:border-gray-700 divide-gray-100 dark:divide-gray-700 px-2 sm:px-4 py-2.5 w-full fixed bottom-0 left-0 z-20"
style="background-color: #f6f6f6;" class="border-t border-solid border-gray-200 text-gray-700 dark:text-gray-200 dark:border-gray-700 divide-gray-100 dark:divide-gray-700 px-2 sm:px-4 py-2.5 w-full fixed bottom-0 left-0 z-20"
>
<div
class="mx-auto flex flex-wrap justify-between items-center w-full px-2 xxs:px-8 xs:px-10"

@ -29,7 +29,7 @@
$: active = sidebarUrl ? href === sidebarUrl : false;
</script>
<a {href} class="flex items-center" on:click>
<a {href} class="flex items-center">
<Icon
tabindex="-1"
color="black"

@ -9,32 +9,37 @@
// according to those terms.
-->
<!--
Component to inform the user, that no wallet is registered on this device.
Offers login or create wallet buttons.
-->
<script>
// @ts-ignore
import Logo from "../assets/nextgraph.svg?component";
import { link } from "svelte-spa-router";
import CenteredLayout from "./CenteredLayout.svelte";
import { t } from "svelte-i18n";
</script>
<CenteredLayout displayFooter={true}>
<div class="container3">
<div class="row">
<Logo class="logo block h-40" alt="NextGraph Logo" />
<Logo class="logo block h-40" alt={$t("common.logo")} />
</div>
<h1 class="text-2xl mb-10">Welcome to NextGraph</h1>
<h1 class="text-2xl mb-10">{$t("pages.no_wallet.welcome")}</h1>
<p class="max-w-sm">
We could not find a wallet saved on this device.<br /> If you already have
a wallet, select "Log in", otherwise, select "Create Wallet" here below
{@html $t("pages.no_wallet.description")}
</p>
<div class="row mt-5">
<a href="/wallet/create" use:link>
<button
tabindex="-1"
class="text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mr-2 mb-2"
class="text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
<svg
class="w-8 h-8 mr-2 -ml-1"
class="w-8 h-8 -ml-1 mr-2"
fill="none"
stroke="currentColor"
stroke-width="1.5"
@ -48,7 +53,7 @@
d="M19 7.5v3m0 0v3m0-3h3m-3 0h-3m-2.25-4.125a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zM4 19.235v-.11a6.375 6.375 0 0112.75 0v.109A12.318 12.318 0 0110.374 21c-2.331 0-4.512-.645-6.374-1.766z"
/>
</svg>
Create wallet
{$t("pages.no_wallet.create_wallet")}
</button>
</a>
</div>
@ -56,7 +61,7 @@
<a href="/wallet/login" use:link>
<button
tabindex="-1"
class="text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mr-2 mb-2"
class="text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
>
<svg
class="w-8 h-8 mr-2 -ml-1"
@ -73,7 +78,7 @@
d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z"
/>
</svg>
Log in
{$t("buttons.login")}
</button>
</a>
</div>

@ -0,0 +1,38 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
export let pane_name = "";
const panes = {
"history": "History",
"files": "Files",
};
const load_pane = async (pane_name) => {
let component = await import(`./panes/${panes[pane_name]}.svelte`);
return component.default;
};
</script>
<div>
{#if pane_name}
{#await load_pane(pane_name) then pane}
<div class="flex w-full" style="overflow-wrap: anywhere;">
<svelte:component this={pane}/>
</div>
{/await}
{/if}
</div>

@ -10,767 +10,24 @@
-->
<script lang="ts">
import {
createGitgraph,
templateExtend,
TemplateName,
} from "../history/gitgraph-js/gitgraph";
import ng from "../api";
import {
branch_subs,
branch_subscribe,
active_session,
cannot_load_offline,
online,
} from "../store";
import { link } from "svelte-spa-router";
import { onMount, onDestroy, tick } from "svelte";
import { Button } from "flowbite-svelte";
import DataClassIcon from "./DataClassIcon.svelte";
import { onMount, onDestroy, tick } from "svelte";
import { Button, Progressbar, Spinner } from "flowbite-svelte";
import { t } from "svelte-i18n";
let is_tauri = import.meta.env.TAURI_PLATFORM;
let files = $active_session && branch_subs($active_session.private_store_id);
let img_map = {};
let gitgraph;
let next = [
{
hash: "I",
subject: "niko2",
author: "",
parents: ["G"],
},
{
hash: "T",
subject: "niko2",
author: "",
parents: ["D", "H"],
},
{
hash: "Z",
subject: "niko2",
author: "",
parents: ["E"],
},
{
hash: "L",
subject: "niko2",
author: "",
parents: ["H"],
},
{
hash: "J",
subject: "niko2",
author: "",
parents: ["L", "Z", "I"],
},
{
hash: "K",
subject: "niko2",
author: "",
parents: ["G", "E"],
},
{
hash: "X",
subject: "niko2",
author: "",
parents: ["I"],
},
{
hash: "Q",
subject: "niko2",
author: "",
parents: ["L", "X"],
},
];
function add() {
let n = next.shift();
if (n) gitgraph.commit(n);
}
onMount(async () => {
const graphContainer = document.getElementById("graph-container");
gitgraph = createGitgraph(graphContainer, {
template: templateExtend(TemplateName.Metro, {
branch: { label: { display: false } },
commit: { message: { displayAuthor: false } },
}),
});
gitgraph.swimlanes(["A", "F", "C"]);
gitgraph.import([
{
hash: "A",
subject: "niko2",
branch: "A",
parents: [],
author: "",
x: 0,
y: 0,
},
{
hash: "B",
subject: "niko2",
branch: "A",
author: "",
parents: ["A"],
x: 0,
y: 1,
},
{
hash: "D",
subject: "niko2",
branch: "A",
author: "",
parents: ["B"],
x: 0,
y: 2,
},
{
hash: "C",
subject: "niko2",
branch: "C",
author: "",
parents: ["A"],
x: 2,
y: 3,
},
{
hash: "F",
subject: "niko2",
branch: "F",
author: "",
parents: ["B", "C"],
x: 1,
y: 4,
},
{
hash: "G",
subject: "niko2",
branch: "F",
parents: ["F"],
author: "",
x: 1,
y: 5,
},
{
hash: "E",
subject: "niko2",
branch: "C",
author: "",
parents: ["C"],
x: 2,
y: 6,
},
// {
// hash: "H",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["D", "G"],
// x: 0,
// y: 7,
// },
// {
// hash: "I",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["D", "H"],
// x: 0,
// y: 8,
// },
// {
// hash: "H",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["D", "G", "E"],
// x: 0,
// y: 7,
// },
]);
// window.setTimeout(() => {
// gitgraph.commit({
// hash: "H",
// subject: "niko2",
// author: "",
// parents: ["D", "G", "E"],
// });
// }, 0);
window.setTimeout(() => {
gitgraph.commit({
hash: "H",
subject: "niko2",
author: "",
parents: ["G", "E"],
});
}, 0);
// window.setTimeout(() => {
// gitgraph.commit({
// hash: "H",
// subject: "niko2",
// author: "",
// parents: ["G"],
// });
// }, 0);
// gitgraph.swimlanes(["A", "B", false, "D"]);
// gitgraph.import([
// {
// hash: "A",
// subject: "niko2",
// branch: "A",
// parents: [],
// author: "",
// x: 0,
// y: 0,
// },
// {
// hash: "C",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["A"],
// x: 0,
// y: 1,
// },
// {
// hash: "D",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["C"],
// x: 0,
// y: 2,
// },
// {
// hash: "E",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["D"],
// x: 0,
// y: 3,
// },
// {
// hash: "B",
// subject: "niko2",
// branch: "C",
// author: "",
// parents: ["A"],
// x: 2,
// y: 4,
// },
// {
// hash: "G",
// subject: "niko2",
// branch: "C",
// parents: ["B"],
// author: "",
// x: 2,
// y: 5,
// },
// {
// hash: "F",
// subject: "niko2",
// branch: "B",
// author: "",
// parents: ["D", "G"],
// x: 1,
// y: 6,
// },
// {
// hash: "H",
// subject: "niko2",
// branch: "D",
// author: "",
// parents: ["G"],
// x: 3,
// y: 7,
// },
// // {
// // hash: "I",
// // subject: "niko2",
// // branch: "A",
// // author: "",
// // parents: ["E", "F", "H"],
// // x: 0,
// // y: 8,
// // },
// ]);
// gitgraph.swimlanes([
// "A",
// false,
// false,
// false,
// false,
// false,
// false,
// false,
// false,
// false,
// false,
// false,
// false,
// ]);
// gitgraph.import([
// {
// hash: "A",
// subject: "niko2",
// branch: "A",
// parents: [],
// author: "",
// x: 0,
// y: 0,
// },
// {
// hash: "B",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["A"],
// x: 0,
// y: 1,
// },
// {
// hash: "C",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["B"],
// x: 0,
// y: 2,
// },
// {
// hash: "D",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["C"],
// x: 0,
// y: 3,
// },
// {
// hash: "E",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["D"],
// x: 0,
// y: 4,
// },
// {
// hash: "J",
// subject: "niko2",
// branch: "J",
// parents: ["A"],
// author: "",
// x: 2,
// y: 5,
// },
// {
// hash: "K",
// subject: "niko2",
// branch: "J",
// author: "",
// parents: ["J"],
// x: 2,
// y: 6,
// },
// {
// hash: "L",
// subject: "niko2",
// branch: "L",
// author: "",
// parents: ["A"],
// x: 3,
// y: 7,
// },
// {
// hash: "M",
// subject: "niko2",
// branch: "L",
// author: "",
// parents: ["L"],
// x: 3,
// y: 8,
// },
// {
// hash: "G",
// subject: "niko2",
// branch: "G",
// author: "",
// parents: ["C", "K", "M"],
// x: 1,
// y: 9,
// },
// {
// hash: "H",
// subject: "niko2",
// branch: "G",
// author: "",
// parents: ["G"],
// x: 1,
// y: 10,
// },
// {
// hash: "I",
// subject: "niko2",
// branch: "G",
// author: "",
// parents: ["H"],
// x: 1,
// y: 11,
// },
// {
// hash: "F",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["E", "I"],
// x: 0,
// y: 12,
// },
// {
// hash: "1",
// subject: "niko2",
// branch: "1",
// author: "",
// parents: ["A"],
// x: 4,
// y: 13,
// },
// {
// hash: "2",
// subject: "niko2",
// branch: "2",
// author: "",
// parents: ["A"],
// x: 5,
// y: 14,
// },
// {
// hash: "3",
// subject: "niko2",
// branch: "3",
// author: "",
// parents: ["A"],
// x: 6,
// y: 15,
// },
// {
// hash: "4",
// subject: "niko2",
// branch: "4",
// author: "",
// parents: ["A"],
// x: 7,
// y: 16,
// },
// {
// hash: "5",
// subject: "niko2",
// branch: "5",
// author: "",
// parents: ["A"],
// x: 8,
// y: 17,
// },
// {
// hash: "6",
// subject: "niko2",
// branch: "6",
// author: "",
// parents: ["A"],
// x: 9,
// y: 18,
// },
// {
// hash: "7",
// subject: "niko2",
// branch: "7",
// author: "",
// parents: ["A"],
// x: 10,
// y: 19,
// },
// {
// hash: "8",
// subject: "niko2",
// branch: "8",
// author: "",
// parents: ["A"],
// x: 11,
// y: 20,
// },
// {
// hash: "9",
// subject: "niko2",
// branch: "9",
// author: "",
// parents: ["A"],
// x: 12,
// y: 21,
// },
// ]);
});
async function get_img(ref) {
if (!ref) return false;
let cache = img_map[ref.nuri];
if (cache) {
return cache;
}
let prom = new Promise(async (resolve) => {
try {
let nuri = {
target: "PrivateStore",
entire_store: false,
access: [{ Key: ref.reference.key }],
locator: [],
object: ref.reference.id,
};
let file_request = {
V0: {
command: "FileGet",
nuri,
session_id: $active_session.session_id,
},
};
let final_blob;
let content_type;
let unsub = await ng.app_request_stream(file_request, async (blob) => {
//console.log("GOT APP RESPONSE", blob);
if (blob.V0.FileMeta) {
content_type = blob.V0.FileMeta.content_type;
final_blob = new Blob([], { type: content_type });
} else if (blob.V0.FileBinary) {
if (blob.V0.FileBinary.byteLength > 0) {
final_blob = new Blob([final_blob, blob.V0.FileBinary], {
type: content_type,
});
}
} else if (blob.V0 == "EndOfStream") {
var imageUrl = URL.createObjectURL(final_blob);
resolve(imageUrl);
}
});
} catch (e) {
console.error(e);
resolve(false);
}
});
img_map[ref.nuri] = prom;
return prom;
}
let fileinput;
function uploadFile(upload_id, nuri, file, success) {
let chunkSize = 1048564;
let fileSize = file.size;
let offset = 0;
let readBlock = null;
let onLoadHandler = async function (event) {
let result = event.target.result;
if (event.target.error == null) {
offset += event.target.result.byteLength;
//console.log("chunk", event.target.result);
let res = await ng.upload_chunk(
$active_session.session_id,
upload_id,
event.target.result,
nuri
);
//console.log("chunk upload res", res);
// if (onChunkRead) {
// onChunkRead(result);
// }
} else {
// if (onChunkError) {
// onChunkError(event.target.error);
// }
return;
}
if (offset >= fileSize) {
//console.log("file uploaded");
let res = await ng.upload_chunk(
$active_session.session_id,
upload_id,
[],
nuri
);
//console.log("end upload res", res);
if (success) {
success(res);
}
return;
}
readBlock(offset, chunkSize, file);
};
readBlock = function (offset, length, file) {
let fileReader = new FileReader();
let blob = file.slice(offset, length + offset);
fileReader.onload = onLoadHandler;
fileReader.readAsArrayBuffer(blob);
};
readBlock(offset, chunkSize, file);
return;
}
const onFileSelected = async (e) => {
let image = e.target.files[0];
if (!image) return;
//console.log(image);
let nuri = {
target: "PrivateStore",
entire_store: false,
access: [],
locator: [],
};
let start_request = {
V0: {
command: "FilePut",
nuri,
payload: {
V0: {
RandomAccessFilePut: image.type,
},
},
session_id: $active_session.session_id,
},
};
let start_res = await ng.app_request(start_request);
let upload_id = start_res.V0.FileUploading;
uploadFile(upload_id, nuri, image, async (reference) => {
if (reference) {
let request = {
V0: {
command: "FilePut",
nuri,
payload: {
V0: {
AddFile: {
filename: image.name,
object: reference.V0.FileUploaded,
},
},
},
session_id: $active_session.session_id,
},
};
await ng.app_request(request);
}
});
fileinput.value = "";
};
</script>
<div>
<div id="graph-container"></div>
{#if $cannot_load_offline}
<div class="row p-4">
<p>
You are offline and using the web app. You need to connect to the broker
at least once before you can start using the app locally because the web
app does not keep a local copy of your documents.<br /><br />
Once connected, if you lose connectivity again, you will be able to have
limited access to some functionalities. Sending binary files won't be possible,
because the limit of local storage in your browser is around 5MB.<br
/><br />
All those limitations will be lifted once the "UserStorage for Web" feature
will be released. Stay tuned! <br /><br />
Check your connection status in the <a href="#/user">User panel</a>.
</p>
</div>
{:else}
<div class="row pt-2">
<!-- <a use:link href="/">
<button tabindex="-1" class=" mr-5 select-none"> Back home </button>
</a> -->
<Button
type="button"
on:click={() => {
add();
}}
class="text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mr-2 mb-2"
>
g
</Button>
<Button
disabled={!$online && !is_tauri}
type="button"
on:click={() => {
fileinput.click();
}}
class="text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mr-2 mb-2"
>
<svg
class="w-8 h-8 mr-2 -ml-1"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z"
/>
</svg>
Add image
</Button>
<input
style="display:none"
type="file"
accept=".jpg, .jpeg, .png"
on:change={(e) => onFileSelected(e)}
bind:this={fileinput}
/>
</div>
{#if files}
{#await files.load()}
<p>Currently loading...</p>
{:then}
{#each $files as file}
<p>
{file.name}
{#await get_img(file) then url}
{#if url}
<img src={url} title={file.nuri} alt={file.name} />
{/if}
{/await}
</p>
{/each}
{/await}
{/if}
{/if}
</div>

@ -0,0 +1,91 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
export let value: string = "";
export let id: string | undefined = undefined;
export let rows: number = 3;
let has_success: boolean = false;
const tauri_platform = import.meta.env.TAURI_PLATFORM;
const setClipboard = async (text: string) => {
if (tauri_platform) {
// TODO: this won't work for tauri platform.
// const { writeText } = await import("@tauri-apps/api/clipboard");
// await writeText(text);
} else {
navigator.clipboard.writeText(text);
}
};
const on_click = (e) => {
has_success = true;
setTimeout(() => (has_success = false), 2_000);
setClipboard(value);
};
</script>
<div class="w-full mt-2">
<div class="relative">
<textarea
{id}
{rows}
style="resize: none;"
{value}
class="col-span-6 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500"
class:pr-11={!tauri_platform}
disabled
readonly
/>
{#if !tauri_platform}
<button
on:click={on_click}
class="absolute inset-y-0 right-0 p-3 flex items-center text-sm leading-5 bg-transparent shadow-none"
>
<span id="default-icon" class:hidden={has_success}>
<svg
class="w-3.5 h-3.5"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 18 20"
>
<path
d="M16 1h-3.278A1.992 1.992 0 0 0 11 0H7a1.993 1.993 0 0 0-1.722 1H2a2 2 0 0 0-2 2v15a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2Zm-3 14H5a1 1 0 0 1 0-2h8a1 1 0 0 1 0 2Zm0-4H5a1 1 0 0 1 0-2h8a1 1 0 1 1 0 2Zm0-5H5a1 1 0 0 1 0-2h2V2h4v2h2a1 1 0 1 1 0 2Z"
/>
</svg>
</span>
<span
id="success-icon"
class="inline-flex items-center"
class:hidden={!has_success}
>
<svg
class="w-3.5 h-3.5 text-blue-700 dark:text-blue-500"
aria-hidden={!has_success}
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 16 12"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M1 5.917 5.724 10.5 15 1.5"
/>
</svg>
</span>
</button>
{/if}
</div>
</div>

@ -0,0 +1,40 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<!--
@component Logo
The NextGraph Logo svg with color changing between blue and gray,
depending on connection status:
- connected: blue
- connecting: pulse between blue and gray
- disconnected: gray
Provide classes using the `className` prop.
-->
<script lang="ts">
import { connection_status } from "../../store";
// @ts-ignore
import Logo from "../../assets/nextgraph-nofill.svg?component";
export let className: string = "";
let connection_status_class = "logo-blue";
// Color is adjusted to connection status.
$: if ($connection_status === "connecting" || $connection_status === "starting") {
connection_status_class = "logo-pulse";
} else if ($connection_status === "disconnected") {
connection_status_class = "logo-gray";
} else {
connection_status_class = "logo-blue";
}
</script>
<Logo class={`${className} ${connection_status_class}`} />

@ -0,0 +1,32 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
export let clickable;
export let extraClass = "";
export let selected = false;
export let title = "";
</script>
{#if clickable}
<li {title} role="menuitem" tabindex="0" class:text-primary-600={selected} class:text-gray-800={!selected} class:dark:text-white={!selected} class:dark:text-primary-300={selected}
class="{extraClass} select-none clickable focus:outline-2 focus:outline flex items-center px-2 py-1 text-base font-normal rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 mt-1"
on:click={(e) => { e.currentTarget.blur(); clickable();}} on:keypress={clickable} on:keydown={(e) => {if (e.code=='Space') { e.preventDefault(); clickable();} }}>
<slot />
</li>
{:else}
<li class="{extraClass} select-none flex items-center px-2 py-1 text-base font-normal text-primary-600 rounded-lg dark:text-primary-300 mt-1">
<slot />
</li>
{/if}

@ -0,0 +1,64 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import {
Toast,
} from "flowbite-svelte";
import {
remove_toast
} from "../../store";
import { onMount, onDestroy, tick } from "svelte";
const toast_color = {
"error":"red",
"warning":"orange",
"success":"green",
"info":"blue"
};
const toast_icon = {
"error": XCircle,
"warning": ExclamationCircle,
"success": CheckCircle,
"info": InformationCircle,
}
import {
CheckCircle,
XCircle,
ExclamationCircle,
InformationCircle,
Icon,
} from "svelte-heros-v2";
export let toast;
export let i;
onMount(()=>{
toast.i = i;
if (toast.level=="success")
{
toast.timer = setTimeout(()=>{remove_toast(i);}, toast.timeout || 10000);
}
});
</script>
<div class="toast fixed flex w-full max-w-xs" style="top:{16+i*74}px;"
on:click|capture|stopPropagation={()=>{remove_toast(toast.i);}}
on:keypress={()=>{}}
>
<Toast color="{toast_color[toast.level]}" >
<Icon tabindex="-1" slot="icon" class="w-8 h-8 p-1 focus:outline-none" variation="outline" color="currentColor" icon={toast_icon[toast.level]} />
{toast.text}
</Toast>
</div>

@ -0,0 +1,93 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import {
ArrowLeft,
ChevronDoubleUp,
CheckCircle,
CheckBadge,
EllipsisVertical,
ExclamationTriangle,
} from "svelte-heros-v2";
import NavIcon from "../icons/NavIcon.svelte";
import {
Popover,
} from "flowbite-svelte";
import {save, nav_bar, showMenu, cur_tab, cur_tab_store_name_override, cur_tab_store_icon_override} from "../../tab";
export let scrollToTop = () => {};
const back = () => {
// going back
window.history.go(-1);
}
const closeErrorPopup = () => {
window.document.getElementById("error_popover_btn").click();
}
</script>
<div style="background-color: #fbfbfb;" class="h-11 pb-1 flex text-center text-gray-700 dark:text-white">
{#if $nav_bar.back}
<div role="button" tabindex="0" on:click={back} on:keypress={back} class="flex-none w-10 flex justify-center items-center">
<ArrowLeft tabindex="-1" class="w-8 h-8 focus:outline-none"/>
</div>
{/if}
{#if $cur_tab_store_icon_override || $nav_bar.icon}
<div style="cursor:pointer;" class:w-10={!$nav_bar.back} class:ml-3={!$nav_bar.back} class="flex-none w-8 m-1 " on:click={scrollToTop} on:keypress={scrollToTop}>
<NavIcon img={$cur_tab_store_icon_override || $nav_bar.icon} config={{
tabindex:"-1",
class:"w-8 h-8 focus:outline-none"
}}/>
</div>
{/if}
<div style="cursor:pointer;" class:pl-3={!$nav_bar.back && !$nav_bar.icon} class="grow w-10 items-center flex px-1" on:click={scrollToTop} on:keypress={scrollToTop}>
<span class="inline-block truncate" > {$cur_tab_store_name_override || $nav_bar.title} </span>
</div>
{#if $nav_bar.newest && !$cur_tab.header_in_view}
<div role="button" tabindex="0" class="flex-none m-1 rounded-full bg-primary-700 text-white dark:bg-primary-700" on:click={scrollToTop} on:keypress={scrollToTop}>
<div class="flex items-center grow pr-2">
<ChevronDoubleUp tabindex="-1" class="w-6 h-6 m-1 focus:outline-none"/>
<span class="inline-block">{@html $nav_bar.newest < 100 ? "+"+$nav_bar.newest : "<span class=\"text-xl\">&infin;</span>"}</span>
</div>
</div>
{/if}
{#if $cur_tab.persistent_error}
<div id="error_popover_btn" tabindex="0" class="flex-none w-10" role="button" title={$cur_tab.persistent_error.title + ". Click for more details"}>
<ExclamationTriangle variation="outline" tabindex="-1" strokeWidth="2" class="w-9 h-9 mt-1 text-red-700 focus:outline-none"/>
</div>
<Popover class="text-left text-black w-[300px] text-sm error-popover" title={$cur_tab.persistent_error.title} triggeredBy="#error_popover_btn" trigger="click" placement = 'bottom'
open={true}
>{@html $cur_tab.persistent_error.desc}
<br/><br/><span class="text-primary-700" on:click={closeErrorPopup} on:keypress={closeErrorPopup} role="button" tabindex="0">Dismiss</span>
</Popover>
{:else if $nav_bar.save !== undefined}
{#if $nav_bar.save }
<div tabindex="0" class="flex-none w-10" role="button" on:click={save} on:keypress={save} title="Save">
<CheckCircle variation="solid" tabindex="-1" strokeWidth="3" class="w-10 h-10 text-primary-400 focus:outline-none"/>
</div>
{:else}
<div class="flex-none w-10" title="Saved!">
<CheckBadge tabindex="-1" class="w-8 h-8 m-1 text-green-500 focus:outline-none"/>
</div>
{/if}
{/if}
<div tabindex="0" class="flex-none w-10 " role="button" title="Open Menu" on:click={showMenu} on:keypress={showMenu}>
<EllipsisVertical tabindex="-1" class="w-8 h-8 my-1 mr-2"/>
</div>
</div>

@ -0,0 +1,49 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import {
XMark,
Icon,
CheckCircle,
CheckBadge,
EllipsisVertical,
} from "svelte-heros-v2";
import { t } from "svelte-i18n";
import {cur_tab_update} from "../../tab";
export let pane_name = "";
export let pane_items = {};
const closePane = (pane:string|boolean) => {
cur_tab_update(($cur_tab) => {
if (pane=="folders") {
$cur_tab.folders_pane = false;
} else if (pane=="toc") {
$cur_tab.toc_pane = false;
} else {
$cur_tab.right_pane = "";
}
return $cur_tab;
});
}
</script>
<div style="height:44px; background-color: rgb(251, 251, 251);" class={`${$$props.class || ''} fixed top-0 w-10 h-10 p-1 bg-white dark:bg-black; rounded-bl-xl`} role="button" aria-label="Close pane" title="Close pane"
on:click={()=>closePane(pane_name)}
on:keypress={()=>closePane(pane_name)}
tabindex="0">
<XMark class="w-8 h-8 p-1 text-gray-400 focus:outline-none dark:text-white"/>
</div>
<div style="height:44px; background-color: rgb(251, 251, 251);" class="p-1 flex items-center">
<Icon tabindex="-1" class="w-8 h-8 text-gray-400 dark:text-white focus:outline-none " variation="outline" color="currentColor" icon={pane_items[pane_name]} />
<span class="ml-2 inline-block text-gray-500 select-none dark:text-white">{$t(`doc.menu.items.${pane_name}.label`)}</span>
</div>

@ -0,0 +1,89 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
export let value: string | undefined = undefined;
export let placeholder: string | undefined = undefined;
export let className: string | undefined = undefined;
export let id: string | undefined = undefined;
export let auto_complete: string | undefined = undefined;
export let show: boolean = false;
let input;
let type: "password" | "text" = "password";
$: type = show ? "text" : "password";
function handleInput(event: Event) {
const target = event.target as HTMLInputElement;
value = target.value;
}
async function toggle() {
let { selectionStart, selectionEnd } = input;
show = !show;
input.focus();
setTimeout(function () {
input.selectionStart = selectionStart;
input.selectionEnd = selectionEnd;
}, 0);
}
</script>
<div class="relative">
<!-- svelte-ignore a11y-autofocus -->
<input
bind:this={input}
{value}
{placeholder}
{id}
{type}
autofocus
on:input={handleInput}
class={`${className} pr-12 text-md block`}
autocomplete={auto_complete}
/>
<div
class="absolute inset-y-0 right-0 pr-3 flex items-center text-sm leading-5"
>
<svg
fill="none"
on:click={toggle}
on:keypress={toggle}
class={`${show ? "block" : "hidden"} h-6 text-gray-700`}
xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 576 512"
>
<path
fill="currentColor"
d="M572.52 241.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400a144 144 0 1 1 144-144 143.93 143.93 0 0 1-144 144zm0-240a95.31 95.31 0 0 0-25.31 3.79 47.85 47.85 0 0 1-66.9 66.9A95.78 95.78 0 1 0 288 160z"
>
</path>
</svg>
<svg
fill="none"
class={`${!show ? "block" : "hidden"} h-6 text-gray-700`}
on:click={toggle}
on:keypress={toggle}
xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 640 512"
>
<path
fill="currentColor"
d="M320 400c-75.85 0-137.25-58.71-142.9-133.11L72.2 185.82c-13.79 17.3-26.48 35.59-36.72 55.59a32.35 32.35 0 0 0 0 29.19C89.71 376.41 197.07 448 320 448c26.91 0 52.87-4 77.89-10.46L346 397.39a144.13 144.13 0 0 1-26 2.61zm313.82 58.1l-110.55-85.44a331.25 331.25 0 0 0 81.25-102.07 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64a308.15 308.15 0 0 0-147.32 37.7L45.46 3.37A16 16 0 0 0 23 6.18L3.37 31.45A16 16 0 0 0 6.18 53.9l588.36 454.73a16 16 0 0 0 22.46-2.81l19.64-25.27a16 16 0 0 0-2.82-22.45zm-183.72-142l-39.3-30.38A94.75 94.75 0 0 0 416 256a94.76 94.76 0 0 0-121.31-92.21A47.65 47.65 0 0 1 304 192a46.64 46.64 0 0 1-1.54 10l-73.61-56.89A142.31 142.31 0 0 1 320 112a143.92 143.92 0 0 1 144 144c0 21.63-5.29 41.79-13.9 60.11z"
>
</path>
</svg>
</div>
</div>

@ -0,0 +1,38 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
export let className: string = "";
export let color: string = "currentColor";
export let width: number = 24;
</script>
<svg
class={"animate-spin " + className}
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke={color}
viewBox={`0 0 ${width} ${width}`}
>
<circle
class="opacity-25"
cx={width / 2}
cy={width / 2}
r={width / 2.4}
stroke={color}
stroke-width={width / 6}
/>
<path
class="opacity-75"
fill={color}
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>

@ -0,0 +1,3 @@
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" fill="currentColor" width="24" height="24" tabindex="-1" class={$$props.class || ''} style={$$props.style || ''}>
<path d="M4 21.25c-1.519 0-2.75 1.231-2.75 2.75s1.231 2.75 2.75 2.75c1.519 0 2.75-1.231 2.75-2.75v0c-0.002-1.518-1.232-2.748-2.75-2.75h-0zM4 25.25c-0.69 0-1.25-0.56-1.25-1.25s0.56-1.25 1.25-1.25c0.69 0 1.25 0.56 1.25 1.25v0c-0.001 0.69-0.56 1.249-1.25 1.25h-0zM20 21.25c-1.519 0-2.75 1.231-2.75 2.75s1.231 2.75 2.75 2.75c1.519 0 2.75-1.231 2.75-2.75v0c-0.002-1.518-1.232-2.748-2.75-2.75h-0zM20 25.25c-0.69 0-1.25-0.56-1.25-1.25s0.56-1.25 1.25-1.25c0.69 0 1.25 0.56 1.25 1.25v0c-0.001 0.69-0.56 1.249-1.25 1.25h-0zM12 13.25c-1.519 0-2.75 1.231-2.75 2.75s1.231 2.75 2.75 2.75c1.519 0 2.75-1.231 2.75-2.75v0c-0.002-1.518-1.232-2.748-2.75-2.75h-0zM12 17.25c-0.69 0-1.25-0.56-1.25-1.25s0.56-1.25 1.25-1.25c0.69 0 1.25 0.56 1.25 1.25v0c-0.001 0.69-0.56 1.249-1.25 1.25h-0zM20 13.25c-1.519 0-2.75 1.231-2.75 2.75s1.231 2.75 2.75 2.75c1.519 0 2.75-1.231 2.75-2.75v0c-0.002-1.518-1.232-2.748-2.75-2.75h-0zM20 17.25c-0.69 0-1.25-0.56-1.25-1.25s0.56-1.25 1.25-1.25c0.69 0 1.25 0.56 1.25 1.25v0c-0.001 0.69-0.56 1.249-1.25 1.25h-0zM28 5.25c-1.519 0-2.75 1.231-2.75 2.75s1.231 2.75 2.75 2.75c1.519 0 2.75-1.231 2.75-2.75v0c-0.002-1.518-1.232-2.748-2.75-2.75h-0zM28 9.25c-0.69 0-1.25-0.56-1.25-1.25s0.56-1.25 1.25-1.25c0.69 0 1.25 0.56 1.25 1.25v0c-0.001 0.69-0.56 1.249-1.25 1.25h-0zM4 5.25c-1.519 0-2.75 1.231-2.75 2.75s1.231 2.75 2.75 2.75c1.519 0 2.75-1.231 2.75-2.75v0c-0.002-1.518-1.232-2.748-2.75-2.75h-0zM4 9.25c-0.69 0-1.25-0.56-1.25-1.25s0.56-1.25 1.25-1.25c0.69 0 1.25 0.56 1.25 1.25v0c-0.001 0.69-0.56 1.249-1.25 1.25h-0zM20 5.25c-1.519 0-2.75 1.231-2.75 2.75s1.231 2.75 2.75 2.75c1.519 0 2.75-1.231 2.75-2.75v0c-0.002-1.518-1.232-2.748-2.75-2.75h-0zM20 9.25c-0.69 0-1.25-0.56-1.25-1.25s0.56-1.25 1.25-1.25c0.69 0 1.25 0.56 1.25 1.25v0c-0.001 0.69-0.56 1.249-1.25 1.25h-0z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512" fill="currentColor" width="24" height="24" tabindex="-1" class={$$props.class || ''} style={$$props.style || ''}>
<path d="M365.1,74.6c-43.8,0-80.2,36.4-80.2,80.2c0,38.2,27,70.9,64.3,78.3c-0.9,21.4-12.1,33.6-30.8,48.5
c-23.3,17.7-53.2,23.3-74.6,27c-46.6,8.4-71.8,30.8-83,45.7V159.5c16.8-2.8,32.6-12.1,44.8-25.2c13.1-14.9,20.5-33.6,20.5-54.1
C226.2,36.4,189.8,0,146,0S65.7,36.4,65.7,80.2c0,19.6,7.5,38.2,19.6,53.2c11.2,13.1,26.1,21.4,42.9,25.2v195.8
c-16.8,3.7-31.7,13.1-42.9,25.2c-13.1,14.9-19.6,33.6-19.6,52.2c0,43.8,36.4,80.2,80.2,80.2s80.2-36.4,80.2-80.2
c0-27-13.1-51.3-35.4-66.2c10.3-11.2,28-22.4,58.8-28c25.2-4.7,60.6-11.2,88.6-32.6c27-20.5,42-42,43.8-73.7
c37.3-7.5,64.3-40.1,64.3-78.3C445.3,110,408.9,74.6,365.1,74.6L365.1,74.6z M97.5,81.1c0-26.1,21.4-48.5,48.5-48.5
c26.1,0,48.5,21.4,48.5,48.5S173,129.6,146,129.6C118.9,129.6,97.5,107.2,97.5,81.1z M193.5,433.7c0,26.1-21.4,48.5-48.5,48.5
c-26.1,0-48.5-21.4-48.5-48.5s21.4-48.5,48.5-48.5C172.1,386.1,193.5,407.5,193.5,433.7z M365.1,202.4c-26.1,0-48.5-21.4-48.5-48.5
c0-26.1,21.4-48.5,48.5-48.5c26.1,0,48.5,21.4,48.5,48.5C412.7,180.9,391.2,202.4,365.1,202.4z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@ -0,0 +1,8 @@
<svg viewBox="0 0 360.99 360.99" xml:space="preserve" fill="currentColor" width="24" height="24" tabindex="-1" class={$$props.class || ''} style={$$props.style || ''} xmlns="http://www.w3.org/2000/svg">
<path d="M360.99,189.609l-9.322-11.75l-43.003,34.118l-56.418-32.573v-64.916l45.639-36.209l-9.322-11.75l-44.455,35.27
l-57.419-33.151V22.754h-15v48.673l-52.578,30.356L74.676,66.529l-9.322,11.75l45.592,36.171v63.564l-65.558,37.851v73.894
L0,325.768l9.322,11.75l44.426-35.246l62.29,35.964l64.355-37.156l64.354,37.156l70.651-40.791v-71.664L360.99,189.609z
M125.945,115.158l55.651-32.13l55.65,32.13v64.246l-55.662,32.137l-55.639-32.123V115.158z M171.689,288.785l-55.651,32.131
l-55.65-32.131v-64.26l55.65-32.131l55.651,32.131V288.785z M300.398,288.785l-55.651,32.131l-55.65-32.131v-64.246l55.663-32.137
l55.639,32.124V288.785z"/>
</svg>

After

Width:  |  Height:  |  Size: 841 B

@ -0,0 +1,187 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import {
Icon,
BugAnt,
DocumentText,
Window,
CodeBracket,
SquaresPlus,
ViewfinderCircle,
ArrowsPointingOut,
Cube,
ClipboardDocumentCheck,
MagnifyingGlass,
RocketLaunch,
Sun,
TableCells,
ListBullet,
RectangleGroup,
Squares2x2,
MapPin,
CircleStack,
Envelope,
GlobeAlt,
DocumentChartBar,
Document,
ClipboardDocumentList,
Photo,
Film,
RectangleStack,
SpeakerWave,
MusicalNote,
Ticket,
CursorArrowRays,
Megaphone,
User,
Clock,
CalendarDays,
Calendar,
Stop,
Flag,
HandRaised,
Newspaper,
PencilSquare,
CubeTransparent,
PresentationChartBar,
QuestionMarkCircle,
CheckCircle,
ChartPie,
Bars3BottomLeft,
Link,
Square2Stack,
Clipboard,
StopCircle,
Bolt,
Heart,
Cog,
Square3Stack3d,
ChatBubbleLeftRight,
Fire,
ReceiptPercent,
ArrowTrendingUp,
CursorArrowRipple,
VideoCamera,
Variable,
Language,
QueueList,
} from "svelte-heros-v2";
import PdfIcon from "./PdfIcon.svelte";
import BrailleIcon from "./BrailleIcon.svelte";
import ChemistryIcon from "./ChemistryIcon.svelte";
import GuitarIcon from "./GuitarIcon.svelte";
import JsonIcon from "./JsonIcon.svelte";
import JsIcon from "./JsIcon.svelte";
import TsIcon from "./TsIcon.svelte";
import RustIcon from "./RustIcon.svelte";
import SvelteIcon from "./SvelteIcon.svelte";
import ReactIcon from "./ReactIcon.svelte";
export let config = {};
export let dataClass: string;
export let color = "currentColor";
const exact_mapping = {
page: Window,
"app": Cog,
"app:z": SquaresPlus,
class: ViewfinderCircle,
contract: ClipboardDocumentCheck,
"query:text": MagnifyingGlass,
"query:web": MagnifyingGlass,
"data:graph": Sun,
"data:json": JsonIcon,
"data:table": TableCells,
"data:collection": ListBullet,
"data:container": Square3Stack3d,
"data:board": RectangleGroup,
"data:grid": Squares2x2,
"data:geomap": MapPin,
"e:email": Envelope,
"e:link": Link,
"mc:text": Bars3BottomLeft,
"mc:link": Link,
"plato/card": Clipboard,
"plato/pad": Square2Stack,
"media:image": Photo,
"media:reel": VideoCamera,
"media:video": Film,
"media:album": RectangleStack,
"media:audio": SpeakerWave,
"media:song": MusicalNote,
"media:subtitle": Ticket,
"media:overlay": CursorArrowRays,
"social:channel": Megaphone,
"social:stream": Bolt,
"social:contact": User,
"social:event": Clock,
"social:calendar": CalendarDays,
"social:scheduler": Calendar,
"social:reaction": Heart,
"social:chatroom": ChatBubbleLeftRight,
"social:live": Fire,
"prod:task": Stop,
"prod:project": Flag,
"prod:issue": HandRaised,
"prod:form": Newspaper,
"prod:filling": PencilSquare,
"prod:cad": CubeTransparent,
"prod:slides": PresentationChartBar,
"prod:question": QuestionMarkCircle,
"prod:answer": CheckCircle,
"prod:poll": CursorArrowRipple,
"prod:vote": CheckCircle,
"prod:spreadsheet": ReceiptPercent,
"doc:compose": QueueList,
"doc:maths": Variable,
"doc:music:abc": MusicalNote,
"doc:pdf": PdfIcon,
"doc:braille": BrailleIcon,
"doc:ancientscript": Language,
"doc:chemistry": ChemistryIcon,
"doc:music:guitar": GuitarIcon,
"code:js": JsIcon,
"code:ts": TsIcon,
"code:rust": RustIcon,
"code:svelte": SvelteIcon,
"code:react": ReactIcon,
};
const prefix_mapping = {
"post:": DocumentText,
code: CodeBracket,
schema: ArrowsPointingOut,
service: Cube,
"e:": GlobeAlt,
"app:": StopCircle,
"query:": RocketLaunch,
"data:": CircleStack,
"doc:diagram": DocumentChartBar,
"doc:chart": ChartPie,
"doc:viz": ArrowTrendingUp,
"doc:": ClipboardDocumentList,
file: Document,
};
const find = (t) => {
let e = exact_mapping[t];
if (e) return e;
for (let prefix of Object.entries(prefix_mapping)) {
if (t.startsWith(prefix[0])) return prefix[1];
}
return BugAnt;
};
</script>
<Icon {...config} variation="outline" {color} icon={find(dataClass)} />

@ -0,0 +1,57 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<!--
@component DeviceIcon
Display an icon for a device class provided by the `device` attribute.
Pass `config` for custom attributes.
-->
<script lang="ts">
import {
Icon,
Cube,
GlobeAlt,
QuestionMarkCircle,
DevicePhoneMobile,
ComputerDesktop,
ServerStack,
Key,
CommandLine,
} from "svelte-heros-v2";
export let config = {};
export let device: string;
const mapping = {
Web: GlobeAlt,
NativeIos: DevicePhoneMobile,
NativeAndroid: DevicePhoneMobile,
NativeMacOS: ComputerDesktop,
NativeLinux: ComputerDesktop,
NativeWin: ComputerDesktop,
NativeService: ServerStack,
NodeService: ServerStack,
Verifier: ServerStack,
VerifierLocal: ServerStack,
ClientBroker: ServerStack,
WalletMaster: ServerStack,
Box: Cube,
Stick: Key, // VerifierStick
Cli: CommandLine,
};
const find = (dataClass: string) => {
return mapping[dataClass] || QuestionMarkCircle;
};
</script>
<Icon {...config} variation="outline" color="black" icon={find(device)} />

@ -0,0 +1,4 @@
<svg viewBox="0 0 32 32" fill="currentColor" width="24" height="24" tabindex="-1" class={$$props.class || ''} style={$$props.style || ''} xmlns="http://www.w3.org/2000/svg">
<path d="M18.448,26.655c0.001,-0.001 0.001,-0.001 0.001,-0.002c2.407,-3.406 6.792,-9.888 8.717,-14.401c0.535,-1.252 0.882,-2.367 0.983,-3.246c0.088,-0.778 -0.016,-1.414 -0.263,-1.881c-0.359,-0.676 -1.158,-1.309 -2.339,-1.783c-2.201,-0.881 -5.872,-1.342 -9.545,-1.342c-3.673,-0 -7.344,0.461 -9.545,1.342c-1.181,0.474 -1.98,1.107 -2.338,1.783c-0.248,0.467 -0.352,1.103 -0.263,1.881c0.1,0.879 0.447,1.994 0.982,3.246c1.925,4.513 6.31,10.995 8.716,14.402c0.001,0 0.001,0.001 0.001,0.002c0.563,0.792 1.475,1.263 2.447,1.263c0.972,-0 1.884,-0.471 2.446,-1.264Zm-1.631,-1.157c-0.188,0.264 -0.491,0.421 -0.815,0.421c-0.324,0 -0.628,-0.157 -0.815,-0.421c-2.346,-3.321 -6.633,-9.632 -8.51,-14.03c-0.375,-0.88 -0.652,-1.675 -0.781,-2.345c-0.086,-0.449 -0.142,-0.813 -0.01,-1.061c0.109,-0.207 0.339,-0.37 0.627,-0.536c0.422,-0.242 0.961,-0.45 1.585,-0.632c2.061,-0.601 4.983,-0.894 7.904,-0.894c2.921,-0 5.843,0.293 7.904,0.894c0.624,0.182 1.163,0.39 1.585,0.632c0.288,0.166 0.518,0.329 0.628,0.536c0.131,0.248 0.075,0.612 -0.011,1.061c-0.129,0.67 -0.406,1.465 -0.781,2.345c-1.877,4.398 -6.164,10.709 -8.51,14.03l-0,-0Z"/>
<path d="M15.993,8.956l0,0.001c-0.001,0.018 -0.001,0.036 -0.001,0.054l-0,4.601c-0.236,-0.066 -0.485,-0.101 -0.742,-0.101c-1.518,-0 -2.75,1.232 -2.75,2.75c0,1.517 1.232,2.75 2.75,2.75c1.518,-0 2.75,-1.233 2.75,-2.75c0,-0.073 -0.008,-4.835 -0.008,-4.835l1.301,1.301c0.39,0.39 1.024,0.39 1.414,-0c0.39,-0.391 0.39,-1.024 0,-1.414l-3.008,-3.009c-0.39,-0.39 -1.024,-0.39 -1.414,0c-0.182,0.181 -0.279,0.415 -0.292,0.652Zm-0.743,6.555c0.414,-0 0.75,0.336 0.75,0.75c0,0.414 -0.336,0.75 -0.75,0.75c-0.414,-0 -0.75,-0.336 -0.75,-0.75c0,-0.414 0.336,-0.75 0.75,-0.75Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@ -0,0 +1,3 @@
<svg viewBox="0 0 16 16" fill="none" width="24" height="24" tabindex="-1" class={$$props.class || ''} style={$$props.style || ''} xmlns="http://www.w3.org/2000/svg">
<path fill-rule="nonzero" clip-rule="nonzero" d="M0 1.75C0 0.783501 0.783502 0 1.75 0H14.25C15.2165 0 16 0.783502 16 1.75V3.75C16 4.16421 15.6642 4.5 15.25 4.5C14.8358 4.5 14.5 4.16421 14.5 3.75V1.75C14.5 1.61193 14.3881 1.5 14.25 1.5H1.75C1.61193 1.5 1.5 1.61193 1.5 1.75V14.25C1.5 14.3881 1.61193 14.5 1.75 14.5H15.25C15.6642 14.5 16 14.8358 16 15.25C16 15.6642 15.6642 16 15.25 16H1.75C0.783501 16 0 15.2165 0 14.25V1.75ZM8.25 5.75C8.66421 5.75 9 6.08579 9 6.5V10.5C9 11.5048 8.72399 12.2584 8.15514 12.7324C7.61223 13.1848 6.95384 13.25 6.5 13.25C6.08579 13.25 5.75 12.9142 5.75 12.5C5.75 12.0858 6.08579 11.75 6.5 11.75C6.84617 11.75 7.06277 11.6902 7.19486 11.5801C7.301 11.4916 7.5 11.2452 7.5 10.5V6.5C7.5 6.08579 7.83578 5.75 8.25 5.75ZM11.2757 6.58011C11.6944 6.08164 12.3507 5.75 13.25 5.75C14.0849 5.75 14.7148 6.03567 15.1394 6.48481C15.4239 6.78583 15.4105 7.26052 15.1095 7.54505C14.8085 7.82958 14.3338 7.81621 14.0493 7.51519C13.9394 7.39898 13.7204 7.25 13.25 7.25C12.7493 7.25 12.5306 7.41836 12.4243 7.54489C12.2934 7.70065 12.25 7.896 12.25 8C12.25 8.104 12.2934 8.29935 12.4243 8.45511C12.5306 8.58164 12.7493 8.75 13.25 8.75C13.3257 8.75 13.3988 8.76121 13.4676 8.78207C14.1307 8.87646 14.6319 9.17251 14.9743 9.58011C15.3684 10.0493 15.5 10.604 15.5 11C15.5 11.396 15.3684 11.9507 14.9743 12.4199C14.5556 12.9184 13.8993 13.25 13 13.25C12.1651 13.25 11.5352 12.9643 11.1106 12.5152C10.8261 12.2142 10.8395 11.7395 11.1405 11.4549C11.4415 11.1704 11.9162 11.1838 12.2007 11.4848C12.3106 11.601 12.5296 11.75 13 11.75C13.5007 11.75 13.7194 11.5816 13.8257 11.4551C13.9566 11.2993 14 11.104 14 11C14 10.896 13.9566 10.7007 13.8257 10.5449C13.7194 10.4184 13.5007 10.25 13 10.25C12.9243 10.25 12.8512 10.2388 12.7824 10.2179C12.1193 10.1235 11.6181 9.82749 11.2757 9.41989C10.8816 8.95065 10.75 8.396 10.75 8C10.75 7.604 10.8816 7.04935 11.2757 6.58011Z" fill="currentColor" fill-opacity="0.80"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@ -0,0 +1,4 @@
<svg viewBox="0 0 24 24" fill="none" width="24" height="24" tabindex="-1" class={$$props.class || ''} style={$$props.style || ''} xmlns="http://www.w3.org/2000/svg">
<path d="M14 19H16C17.1046 19 18 18.1046 18 17V14.5616C18 13.6438 18.6246 12.8439 19.5149 12.6213L21.0299 12.2425C21.2823 12.1794 21.2823 11.8206 21.0299 11.7575L19.5149 11.3787C18.6246 11.1561 18 10.3562 18 9.43845V5H14" stroke="currentColor"/>
<path d="M10 5H8C6.89543 5 6 5.89543 6 7V9.43845C6 10.3562 5.37541 11.1561 4.48507 11.3787L2.97014 11.7575C2.71765 11.8206 2.71765 12.1794 2.97014 12.2425L4.48507 12.6213C5.37541 12.8439 6 13.6438 6 14.5616V19H10" stroke="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 665 B

@ -0,0 +1,10 @@
<svg fill="none" width="24" height="24" tabindex="-1" class={$$props.class || ''} style={$$props.style || ''} viewBox="-10 255 1034 734" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M278 256q-95 0 -127.5 28t-32.5 113v92q0 57 -20 79t-74 22h-24v55h24q53 0 73.5 22t20.5 80v92q0 84 32.5 112.5t127.5 28.5h25v-55h-27q-54 0 -70 -16.5t-16 -71.5v-95q0 -59 -17.5 -86.5t-59.5 -37.5q42 -10 59.5 -37t17.5 -87v-95q0 -55 16 -71.5t70 -16.5h27v-55
h-25zM697 256v55h27q53 0 69.5 16.5t16.5 71.5v95q0 60 17 87t60 37q-42 10 -59.5 37t-17.5 87v95q0 55 -16.5 71.5t-69.5 16.5h-27v55h24q95 0 127 -28.5t32 -112.5l1 -92q-1 -58 20 -80t75 -22h24v-55h-24q-54 0 -75 -22t-20 -79v-92q-1 -85 -33 -113t-127 -28h-24z
M617 341q-27 0 -51 14t-37 39q-16 31 -11 64l-1 -2v5q-1 6 -5 13q-5 10 -16 21q-13 13 -35 26q-38 24 -68 24q-14 0 -21 -5v0l2 3l-6 -3q-37 -20 -77 -7.5t-60 49.5t-7.5 77t49.5 60q27 14 57.5 11t53.5 -22l-1 2l5 -3q6 -2 15 -2q12 0 27 4q18 5 41 17q33 18 48 39
q10 13 11 26q-1 28 13.5 53.5t40.5 39.5q37 19 77 7t60 -49t7.5 -77.5t-49.5 -59.5q-5 -3 -10 -5h2l-3 -3q-5 -4 -8 -11q-6 -11 -9 -25q-4 -19 -4.5 -44t2.5 -44q3 -15 8 -26q3 -7 8 -12l3 -3h-3q28 -14 43 -43q20 -37 7.5 -77t-49.5 -60q-23 -12 -49 -11zM619 357
q27 0 46 17v0q-10 -9 -31 -7t-41 15q6 6 6 14.5t-5.5 15t-14.5 6.5q-3 0 -6 -1h-1q-9 -3 -15 6q-10 19 -11 37t8 29q-19 -21 -18 -51t22 -53q13 -14 29 -21t32 -7zM550 524q7 0 11 2l10 6q9 4 19 7q21 20 23 81q0 36 -8 59q-6 15 -15 23q-14 6 -25 16q-10 3 -25 1
q-24 -4 -56 -21q-34 -19 -51 -38q-10 -12 -11 -22q1 -13 -2 -26l1 1v-5q1 -5 5 -12q6 -9 17 -19q13 -13 34 -26q42 -26 70 -27h3zM323 548q27 0 46 17h-1q-10 -9 -30.5 -7t-40.5 15q5 6 5.5 14t-5.5 14.5t-15 7.5q-2 0 -5 -1h-2q-8 -3 -15 6q-9 18 -10 36.5t8 28.5
q-20 -20 -19 -50t23 -53q12 -14 28 -21t33 -7zM637 709q27 0 46 17h-1q-10 -9 -30.5 -6.5t-40.5 15.5q5 5 5.5 13.5t-5.5 15t-15 6.5h-5l-2 -1q-8 -2 -15 6q-10 19 -10.5 37t8.5 29q-20 -21 -19 -50.5t23 -53.5q12 -13 28 -20.5t33 -7.5z" />
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

@ -0,0 +1,6 @@
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentColor" width="24" height="24" tabindex="-1" class={$$props.class || ''} style={$$props.style || ''}>
<path d="M14 3a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h12zM2 2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H2z"/>
<path fill-rule="evenodd" d="M9.146 8.146a.5.5 0 0 1 .708 0L11.5 9.793l1.646-1.647a.5.5 0 0 1 .708.708l-2 2a.5.5 0 0 1-.708 0l-2-2a.5.5 0 0 1 0-.708z"/>
<path fill-rule="evenodd" d="M11.5 5a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-1 0v-4a.5.5 0 0 1 .5-.5z"/>
<path d="M3.56 11V7.01h.056l1.428 3.239h.774l1.42-3.24h.056V11h1.073V5.001h-1.2l-1.71 3.894h-.039l-1.71-3.894H2.5V11h1.06z"/>
</svg>

After

Width:  |  Height:  |  Size: 725 B

@ -0,0 +1,64 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<!--
@component DeviceIcon
Display an icon for a device class provided by the `device` attribute.
Pass `config` for custom attributes.
-->
<script lang="ts">
import {
Icon,
Bolt,
Megaphone,
QuestionMarkCircle,
ExclamationCircle,
Key,
LockClosed,
GlobeAlt,
UserGroup,
PaperAirplane,
} from "svelte-heros-v2";
import DataClassIcon from "./DataClassIcon.svelte";
export let config = {};
export let img: string;
const mapping = {
stream: Bolt,
channel: Megaphone,
private: Key,
protected: LockClosed,
public: GlobeAlt,
group: UserGroup,
dialog: PaperAirplane,
unknown_doc: ExclamationCircle,
};
const find = (dataClass: string) => {
return mapping[dataClass] || QuestionMarkCircle;
};
</script>
{#if img.startsWith("blob:")}
<img style="aspect-ratio:1;" class="rounded-full" src={img} alt="profile"/>
{:else if img.startsWith("class:")}
<DataClassIcon {config} dataClass={img.slice(6)} />
{:else if img.startsWith("nav:")}
<Icon {...config} variation="outline" color="currentColor" icon={find(img.slice(4))} />
{:else}
<QuestionMarkCircle {...config}/>
{/if}

@ -0,0 +1,3 @@
<svg viewBox="-4 0 40 40" fill="currentColor" width="24" height="24" tabindex="-1" class={$$props.class || ''} style={$$props.style || ''} xmlns="http://www.w3.org/2000/svg">
<path d="M25.6686 26.0962C25.1812 26.2401 24.4656 26.2563 23.6984 26.145C22.875 26.0256 22.0351 25.7739 21.2096 25.403C22.6817 25.1888 23.8237 25.2548 24.8005 25.6009C25.0319 25.6829 25.412 25.9021 25.6686 26.0962ZM17.4552 24.7459C17.3953 24.7622 17.3363 24.7776 17.2776 24.7939C16.8815 24.9017 16.4961 25.0069 16.1247 25.1005L15.6239 25.2275C14.6165 25.4824 13.5865 25.7428 12.5692 26.0529C12.9558 25.1206 13.315 24.178 13.6667 23.2564C13.9271 22.5742 14.193 21.8773 14.468 21.1894C14.6075 21.4198 14.7531 21.6503 14.9046 21.8814C15.5948 22.9326 16.4624 23.9045 17.4552 24.7459ZM14.8927 14.2326C14.958 15.383 14.7098 16.4897 14.3457 17.5514C13.8972 16.2386 13.6882 14.7889 14.2489 13.6185C14.3927 13.3185 14.5105 13.1581 14.5869 13.0744C14.7049 13.2566 14.8601 13.6642 14.8927 14.2326ZM9.63347 28.8054C9.38148 29.2562 9.12426 29.6782 8.86063 30.0767C8.22442 31.0355 7.18393 32.0621 6.64941 32.0621C6.59681 32.0621 6.53316 32.0536 6.44015 31.9554C6.38028 31.8926 6.37069 31.8476 6.37359 31.7862C6.39161 31.4337 6.85867 30.8059 7.53527 30.2238C8.14939 29.6957 8.84352 29.2262 9.63347 28.8054ZM27.3706 26.1461C27.2889 24.9719 25.3123 24.2186 25.2928 24.2116C24.5287 23.9407 23.6986 23.8091 22.7552 23.8091C21.7453 23.8091 20.6565 23.9552 19.2582 24.2819C18.014 23.3999 16.9392 22.2957 16.1362 21.0733C15.7816 20.5332 15.4628 19.9941 15.1849 19.4675C15.8633 17.8454 16.4742 16.1013 16.3632 14.1479C16.2737 12.5816 15.5674 11.5295 14.6069 11.5295C13.948 11.5295 13.3807 12.0175 12.9194 12.9813C12.0965 14.6987 12.3128 16.8962 13.562 19.5184C13.1121 20.5751 12.6941 21.6706 12.2895 22.7311C11.7861 24.0498 11.2674 25.4103 10.6828 26.7045C9.04334 27.3532 7.69648 28.1399 6.57402 29.1057C5.8387 29.7373 4.95223 30.7028 4.90163 31.7107C4.87693 32.1854 5.03969 32.6207 5.37044 32.9695C5.72183 33.3398 6.16329 33.5348 6.6487 33.5354C8.25189 33.5354 9.79489 31.3327 10.0876 30.8909C10.6767 30.0029 11.2281 29.0124 11.7684 27.8699C13.1292 27.3781 14.5794 27.011 15.985 26.6562L16.4884 26.5283C16.8668 26.4321 17.2601 26.3257 17.6635 26.2153C18.0904 26.0999 18.5296 25.9802 18.976 25.8665C20.4193 26.7844 21.9714 27.3831 23.4851 27.6028C24.7601 27.7883 25.8924 27.6807 26.6589 27.2811C27.3486 26.9219 27.3866 26.3676 27.3706 26.1461ZM30.4755 36.2428C30.4755 38.3932 28.5802 38.5258 28.1978 38.5301H3.74486C1.60224 38.5301 1.47322 36.6218 1.46913 36.2428L1.46884 3.75642C1.46884 1.6039 3.36763 1.4734 3.74457 1.46908H20.263L20.2718 1.4778V7.92396C20.2718 9.21763 21.0539 11.6669 24.0158 11.6669H30.4203L30.4753 11.7218L30.4755 36.2428ZM28.9572 10.1976H24.0169C21.8749 10.1976 21.7453 8.29969 21.7424 7.92417V2.95307L28.9572 10.1976ZM31.9447 36.2428V11.1157L21.7424 0.871022V0.823357H21.6936L20.8742 0H3.74491C2.44954 0 0 0.785336 0 3.75711V36.2435C0 37.5427 0.782956 40 3.74491 40H28.2001C29.4952 39.9997 31.9447 39.2143 31.9447 36.2428Z" />
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

@ -0,0 +1,9 @@
<svg fill="none" width="24" height="24" tabindex="-1" class={$$props.class || ''} style={$$props.style || ''} viewBox="-10 255 1034 734" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M704 227q-40 0 -74.5 20.5t-53.5 56.5q-23 43 -15 92l-2 -2v6q-1 9 -7 19q-7 15 -23 30q-20 19 -50.5 38t-57.5 28q-22 7 -40 7q-14 0 -24 -4l-8 -3l3 3l-8 -4q-35 -19 -73.5 -17.5t-72 22t-52 55.5t-17 74t22 72t55.5 52q40 21 83.5 16.5t76.5 -31.5l-1 2l7 -4
q9 -3 21 -3q17 -1 39 5q27 8 59 25q48 26 70 56q15 19 16 37q-1 42 19.5 78.5t58.5 56.5q35 18 73.5 16.5t72 -22t52 -55.5t17 -74t-22 -72t-55.5 -51q-7 -4 -15 -7l3 -1l-5 -4q-6 -5 -11 -16q-8 -15 -12 -36q-6 -27 -7 -63t3 -63q4 -22 11 -37q5 -11 12 -18l5 -4h-5
q41 -21 62 -62q19 -35 17.5 -73.5t-22 -71.5t-55.5 -52q-33 -18 -70 -17zM706 250q39 0 66 25v0q-15 -13 -44.5 -10t-58.5 22q8 8 8.5 20t-8 21.5t-21.5 9.5h-8l-2 -1q-12 -4 -21 8q-16 29 -16 56.5t15 42.5q-20 -19 -27 -46.5t0.5 -56t28.5 -51.5q18 -19 41 -29.5t47 -10.5
zM605 491q11 0 18 3.5t14 7.5q13 7 27 11q11 11 20 33q11 33 12.5 84t-10.5 84q-9 22 -22 34q-20 9 -36 23q-14 5 -36 1q-34 -6 -81 -31q-49 -26 -73 -54q-14 -17 -17 -32q2 -18 -1 -37v1l1 -6q1 -8 7 -18q8 -13 24 -28q20 -18 50 -37q62 -38 103 -39zM279 525q39 0 66 25
h-1q-14 -13 -44 -10t-58 22q7 8 7.5 20t-8 21.5t-20.5 9.5q-4 1 -8 0l-2 -1q-13 -4 -22 8q-15 29 -15.5 56.5t14.5 42.5q-20 -19 -26.5 -46.5t1 -56t28.5 -51.5q18 -19 41 -29.5t47 -10.5zM731 758q39 0 66 25v0q-14 -13 -44 -10t-59 22q8 8 8.5 20t-8 21.5t-21.5 9.5
q-4 0 -8 -1h-2q-12 -4 -21 8q-16 29 -16 56.5t15 42.5q-20 -19 -27 -46.5t0.5 -56t28.5 -51.5q18 -19 41 -29.5t47 -10.5z" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@ -0,0 +1,3 @@
<svg fill="currentColor" width="24" height="24" tabindex="-1" class={$$props.class || ''} style={$$props.style || ''} viewBox="0 -1.5 27 27" xmlns="http://www.w3.org/2000/svg">
<path d="m15.902 11.974c0 1.331-1.079 2.41-2.41 2.41s-2.41-1.079-2.41-2.41 1.079-2.41 2.41-2.41 2.41 1.079 2.41 2.41z"/><path d="m13.491 17.494c-.201.005-.438.008-.676.008-2.811 0-5.522-.425-8.073-1.214l.193.051c-1.351-.436-2.522-1.079-3.541-1.903l.021.016c-.789-.571-1.32-1.455-1.41-2.467l-.001-.013c0-1.594 1.747-3.154 4.681-4.172 2.512-.827 5.403-1.304 8.405-1.304.139 0 .278.001.417.003h-.021c.116-.002.252-.003.389-.003 2.965 0 5.82.469 8.496 1.337l-.195-.055c1.305.424 2.438 1.036 3.433 1.814l-.024-.018c.775.546 1.298 1.404 1.386 2.387l.001.013c0 1.656-1.954 3.332-5.103 4.374-2.363.729-5.08 1.149-7.895 1.149-.17 0-.339-.002-.508-.005h.025zm0-9.84c-.106-.002-.232-.002-.358-.002-2.881 0-5.656.452-8.259 1.289l.191-.053c-2.698.941-3.908 2.228-3.908 3.087 0 .893 1.301 2.3 4.153 3.274 2.249.697 4.834 1.099 7.513 1.099.235 0 .469-.003.702-.009l-.034.001c.146.003.318.005.49.005 2.684 0 5.274-.4 7.715-1.143l-.187.049c2.96-.984 4.32-2.391 4.32-3.28-.102-.638-.462-1.176-.966-1.515l-.008-.005c-.866-.674-1.876-1.217-2.97-1.574l-.07-.02c-2.368-.766-5.093-1.208-7.92-1.208-.142 0-.283.001-.424.003h.021z"/><path d="m8.023 23.986c-.016 0-.035.001-.053.001-.453 0-.878-.119-1.245-.327l.013.007c-1.378-.8-1.858-3.092-1.28-6.141.697-3.128 1.827-5.894 3.344-8.4l-.069.124c1.499-2.655 3.31-4.926 5.425-6.872l.02-.018c.923-.844 2.003-1.537 3.187-2.028l.073-.027c.407-.196.885-.31 1.39-.31.501 0 .976.113 1.4.314l-.02-.008c1.435.826 1.911 3.36 1.238 6.606-.72 2.967-1.818 5.58-3.262 7.959l.07-.123c-1.452 2.63-3.209 4.882-5.266 6.819l-.014.013c-.958.877-2.083 1.59-3.321 2.082l-.074.026c-.46.181-.992.292-1.549.305h-.006zm1.205-14.447.499.288c-1.381 2.28-2.465 4.926-3.106 7.74l-.034.18c-.533 2.809-.019 4.498.72 4.926.191.102.417.161.657.161.02 0 .041 0 .061-.001h-.003c.955 0 2.458-.605 4.196-2.122 1.976-1.867 3.654-4.023 4.972-6.404l.068-.135c1.318-2.156 2.371-4.653 3.025-7.307l.038-.182c.629-3.058.086-4.93-.686-5.378-.238-.094-.514-.148-.802-.148-.364 0-.708.087-1.012.24l.013-.006c-1.118.465-2.078 1.086-2.907 1.846l.007-.006c-2.041 1.881-3.772 4.057-5.136 6.468l-.069.132z"/><path d="m18.96 24c-1.306 0-2.96-.787-4.69-2.276-2.175-1.983-4.017-4.282-5.464-6.834l-.072-.137c-1.43-2.349-2.551-5.075-3.215-7.975l-.036-.185c-.162-.709-.255-1.522-.255-2.357 0-.527.037-1.044.108-1.551l-.007.058c.085-.998.608-1.858 1.372-2.399l.01-.007c1.43-.83 3.865.024 6.342 2.228 2.022 1.914 3.748 4.113 5.118 6.54l.072.138c1.428 2.297 2.557 4.961 3.242 7.798l.038.186c.163.709.257 1.524.257 2.36 0 .577-.044 1.143-.13 1.696l.008-.062c-.103 1.022-.644 1.901-1.429 2.456l-.011.007c-.348.199-.764.316-1.207.316-.018 0-.036 0-.054-.001h.003zm-9.228-9.823c1.458 2.571 3.217 4.771 5.271 6.656l.02.018c2.166 1.863 3.884 2.266 4.628 1.834.773-.446 1.339-2.276.754-5.233-.702-2.89-1.787-5.434-3.219-7.741l.065.112c-1.379-2.456-3.027-4.562-4.948-6.384l-.012-.011c-2.334-2.074-4.225-2.54-4.998-2.094-.473.385-.784.952-.825 1.593v.007c-.055.39-.086.84-.086 1.298 0 .757.086 1.493.248 2.201l-.013-.066c.673 2.952 1.747 5.559 3.182 7.924l-.066-.117z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

@ -0,0 +1,3 @@
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="none" width="24" height="24" tabindex="-1" class={$$props.class || ''} style={$$props.style || ''}>
<path d="M8 .1c.422 0 .765.342.765.765v.916c.296.035.587.091.87.166l.283-.683a.764.764 0 111.413.585l-.276.665c.287.157.56.335.817.533l.633-.633a.765.765 0 011.081 1.081l-.62.62c.24.298.453.618.637.956l.631-.261a.765.765 0 11.585 1.413l-.654.27c.072.277.126.56.16.849h.81a.765.765 0 010 1.529h-.81a6.334 6.334 0 01-.21 1.027l.664.275a.765.765 0 11-.585 1.413l-.672-.278c-.13.24-.278.471-.44.693l.504.504a.765.765 0 01-1.081 1.081l-.465-.465c-.3.253-.627.48-.978.677l.269.65a.765.765 0 11-1.413.586l-.27-.65a6.332 6.332 0 01-.883.181v.57a.765.765 0 01-1.53 0v-.555a6.386 6.386 0 01-1.087-.218l-.278.672a.765.765 0 11-1.413-.585l.285-.688a6.38 6.38 0 01-.84-.581l-.407.406a.765.765 0 01-1.081-1.081l.428-.428a6.348 6.348 0 01-.459-.703l-.748.31a.765.765 0 01-.585-1.413l.748-.31a6.339 6.339 0 01-.205-1.09H.865a.765.765 0 010-1.53h.762c.044-.298.108-.593.192-.88l-.81-.336a.765.765 0 01.585-1.413l.815.337c.181-.33.39-.643.625-.934l-.62-.62a.765.765 0 011.081-1.081l.633.633c.205-.158.42-.303.645-.435l-.316-.763a.765.765 0 011.413-.585L6.175 2a6.34 6.34 0 011.06-.22V.865C7.235.442 7.578.1 8 .1zm-.765 3.354a4.854 4.854 0 00-3.487 2.36l.61.199a.765.765 0 11-.472 1.454l-.653-.212a4.823 4.823 0 001.294 4.225l.458-.63a.764.764 0 011.237.898l-.47.648A4.818 4.818 0 008 12.948c.812 0 1.577-.2 2.249-.552l-.352-.484a.765.765 0 011.237-.899l.34.467c.349-.36.642-.773.867-1.227a4.843 4.843 0 00.34-2.97l-.567.184a.764.764 0 11-.472-1.454l.5-.163a4.827 4.827 0 00-3.377-2.374v.447a.765.765 0 11-1.53 0v-.469z" fill="currentColor" fill-opacity="0.80"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@ -0,0 +1,3 @@
<svg viewBox="0 0 15 15" fill="none" width="24" height="24" tabindex="-1" class={$$props.class || ''} style={$$props.style || ''} xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.2829 1.94409C14.2083 3.42511 14.046 5.27647 13.0128 6.56576C13.8628 8.37306 13.2462 10.5713 11.5104 11.656L7.27012 14.3056C5.39667 15.4763 2.92892 14.9066 1.75825 13.0331C0.832798 11.5521 0.99519 9.70071 2.02833 8.41145C1.17829 6.60415 1.79488 4.40587 3.53076 3.32117L7.771 0.671576C9.64446 -0.499089 12.1122 0.070635 13.2829 1.94409ZM8.30092 1.51962C9.70601 0.641625 11.5568 1.06892 12.4348 2.47401C13.0645 3.48168 13.023 4.71945 12.4301 5.66263C11.8046 4.92992 10.9523 4.46839 10.0447 4.31762L10.7691 3.86494L10.2392 3.0169L5.15088 6.19641L5.6808 7.04446L7.80091 5.71967C9.206 4.84167 11.0568 5.26899 11.9348 6.67408C12.8128 8.07918 12.3855 9.92999 10.9804 10.808L6.7402 13.4576C5.33511 14.3356 3.4843 13.9083 2.6063 12.5032C1.97665 11.4955 2.01814 10.2578 2.61103 9.31458C3.23653 10.0473 4.08885 10.5088 4.99651 10.6596L4.27222 11.1122L4.80214 11.9602L9.89043 8.78071L9.36051 7.93267L7.24023 9.25756C5.83514 10.1356 3.98429 9.70821 3.10629 8.30312C2.2283 6.89803 2.65559 5.04722 4.06068 4.16922L8.30092 1.51962Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@ -0,0 +1,3 @@
<svg viewBox="0 0 16 16" fill="none" width="24" height="24" tabindex="-1" class={$$props.class || ''} style={$$props.style || ''} xmlns="http://www.w3.org/2000/svg">
<path fill-rule="nonzero" clip-rule="nonzero" d="M0 1.75C0 0.783501 0.783502 0 1.75 0H14.25C15.2165 0 16 0.783502 16 1.75V3.75C16 4.16421 15.6642 4.5 15.25 4.5C14.8358 4.5 14.5 4.16421 14.5 3.75V1.75C14.5 1.61193 14.3881 1.5 14.25 1.5H1.75C1.61193 1.5 1.5 1.61193 1.5 1.75V14.25C1.5 14.3881 1.61193 14.5 1.75 14.5H15.25C15.6642 14.5 16 14.8358 16 15.25C16 15.6642 15.6642 16 15.25 16H1.75C0.783501 16 0 15.2165 0 14.25V1.75ZM4.75 6.5C4.75 6.08579 5.08579 5.75 5.5 5.75H9.25C9.66421 5.75 10 6.08579 10 6.5C10 6.91421 9.66421 7.25 9.25 7.25H8.25V12.5C8.25 12.9142 7.91421 13.25 7.5 13.25C7.08579 13.25 6.75 12.9142 6.75 12.5V7.25H5.5C5.08579 7.25 4.75 6.91421 4.75 6.5ZM11.2757 6.58011C11.6944 6.08164 12.3507 5.75 13.25 5.75C14.0849 5.75 14.7148 6.03567 15.1394 6.48481C15.4239 6.78583 15.4105 7.26052 15.1095 7.54505C14.8085 7.82958 14.3338 7.81621 14.0493 7.51519C13.9394 7.39898 13.7204 7.25 13.25 7.25C12.7493 7.25 12.5306 7.41836 12.4243 7.54489C12.2934 7.70065 12.25 7.896 12.25 8C12.25 8.104 12.2934 8.29935 12.4243 8.45511C12.5306 8.58164 12.7493 8.75 13.25 8.75C13.3257 8.75 13.3988 8.76121 13.4676 8.78207C14.1307 8.87646 14.6319 9.17251 14.9743 9.58011C15.3684 10.0493 15.5 10.604 15.5 11C15.5 11.396 15.3684 11.9507 14.9743 12.4199C14.5556 12.9184 13.8993 13.25 13 13.25C12.1651 13.25 11.5352 12.9643 11.1106 12.5152C10.8261 12.2142 10.8395 11.7395 11.1405 11.4549C11.4415 11.1704 11.9162 11.1838 12.2007 11.4848C12.3106 11.601 12.5296 11.75 13 11.75C13.5007 11.75 13.7194 11.5816 13.8257 11.4551C13.9566 11.2993 14 11.104 14 11C14 10.896 13.9566 10.7007 13.8257 10.5449C13.7194 10.4184 13.5007 10.25 13 10.25C12.9243 10.25 12.8512 10.2388 12.7824 10.2179C12.1193 10.1235 11.6181 9.82749 11.2757 9.41989C10.8816 8.95065 10.75 8.396 10.75 8C10.75 7.604 10.8816 7.04935 11.2757 6.58011Z" fill="currentColor" fill-opacity="0.80"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@ -0,0 +1,35 @@
<svg fill="currentColor" width="24" height="24" tabindex="-1" class={$$props.class || ''} style={$$props.style || ''} viewBox="0 0 493.249 493.249" xmlns="http://www.w3.org/2000/svg">
<path d="M463.007,168.49c0-28.703-33.46-51.188-76.174-51.188c-18.346,0-35.576,4.216-49.249,11.953
c-13.401-9.919-28.469-17.705-44.7-22.856c6.058-13.7,9.44-29.27,9.44-44.106C302.323,19.261,274.349,0,246.625,0
s-55.699,19.261-55.699,62.292c0,14.836,3.383,30.406,9.44,44.106c-16.231,5.152-31.299,12.938-44.7,22.857
c-13.674-7.737-30.904-11.954-49.249-11.954c-42.715,0-76.175,22.484-76.175,51.188c0,13.253,10.782,22.164,32.047,26.485
c12.216,2.482,27.183,3.384,40.87,3.714c-6.298,16.744-9.754,34.868-9.754,53.787c0,24.754,5.938,52.82,16.436,80.015
c-8.104,2.426-16.193,5.903-23.818,10.306c-17.027,9.83-30.687,23.381-38.463,38.157c-8.446,16.049-9.126,31.767-1.913,44.26
c2.842,4.922,7.37,8.331,13.094,9.856c2.395,0.638,4.977,0.941,7.73,0.941c19.806,0,48.526-15.642,80.912-34.457
c0.346-0.201,0.679-0.394,1.021-0.593c26.628,33.774,57.808,53.408,89.722,56.382v27.408c0,4.694,3.806,8.5,8.5,8.5
c4.694,0,8.5-3.806,8.5-8.5v-27.408c31.914-2.974,63.094-22.607,89.721-56.382c0.343,0.199,0.677,0.393,1.023,0.594
c32.387,18.815,61.104,34.456,80.91,34.456c2.752,0,5.336-0.302,7.73-0.94c5.724-1.525,10.252-4.934,13.094-9.856
c14.351-24.858-3.385-61.06-40.376-82.416c-7.626-4.403-15.715-7.88-23.818-10.307c10.497-27.194,16.435-55.26,16.435-80.015
c0-18.919-3.456-37.042-9.754-53.787c13.688-0.331,28.653-1.232,40.87-3.714C452.225,190.654,463.007,181.743,463.007,168.49z
M382.844,252.476c0,3.003-0.11,6.077-0.306,9.2h-63.661l-29.605-51.278l53.91-53.91
C367.681,181.13,382.844,215.064,382.844,252.476z M341.784,376.22l-50.758-50.758l27.012-46.786h62.58
C375.449,310.854,361.534,346.538,341.784,376.22z M112.518,277.981h62.293l27.413,47.479l-50.759,50.759
C131.574,346.325,117.597,310.34,112.518,277.981z M110.405,252.476c0-36.355,14.316-69.428,37.606-93.881l54.442,54.442
l-27.681,47.944h-64.103C110.501,258.098,110.405,255.256,110.405,252.476z M246.625,116.256c31.587,0,60.696,10.81,83.827,28.92
l-57.812,57.813h-56.193l-55.958-55.959C183.976,127.807,213.976,116.256,246.625,116.256z M218.07,318.907l-28.556-49.459
l28.556-49.459h57.11l28.555,49.459l-28.555,49.459H218.07z M207.926,62.292c0-27.938,14.829-45.292,38.699-45.292
s38.698,17.355,38.698,45.292c0,13.275-3.384,27.74-9.191,39.826c-9.552-1.871-19.415-2.862-29.507-2.862
c-10.092,0-19.955,0.991-29.507,2.862C211.31,90.031,207.926,75.567,207.926,62.292z M65.674,178.315
c-11.542-2.345-18.432-6.018-18.432-9.825c0-18.532,27.099-34.188,59.175-34.188c12.666,0,24.903,2.444,35.035,6.858
c-12.375,11.699-22.804,25.432-30.752,40.66C96.007,181.647,78.785,180.979,65.674,178.315z M66.876,419.08
c-1.383,0-2.644-0.141-3.758-0.438c-1.699-0.453-2.325-1.195-2.749-1.929c-4.19-7.256-3.397-17.145,2.234-27.842
c6.301-11.972,17.636-23.105,31.92-31.352c7.061-4.077,14.547-7.218,21.969-9.265c5.949,12.995,12.908,25.592,20.732,37.305
c0.361,0.541,0.729,1.065,1.092,1.6C117.117,399.474,83.236,419.082,66.876,419.08z M246.625,440.753
c-32.18,0-61.76-20.875-85.077-50.575l54.271-54.271h61.611l54.272,54.271C308.385,419.878,278.805,440.753,246.625,440.753z
M398.727,357.519c27.779,16.038,43.419,43.145,34.154,59.194c-0.424,0.734-1.051,1.477-2.749,1.929
c-14.29,3.806-52.207-18.127-75.199-31.483c0.363-0.536,0.731-1.059,1.092-1.6c7.824-11.712,14.783-24.31,20.732-37.305
C384.179,350.3,391.666,353.442,398.727,357.519z M382.549,181.82c-7.948-15.228-18.376-28.962-30.752-40.66
c10.132-4.414,22.37-6.858,35.036-6.858c32.076,0,59.174,15.656,59.174,34.188c0,3.808-6.89,7.48-18.432,9.825
C414.464,180.979,397.242,181.647,382.549,181.82z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

@ -0,0 +1,5 @@
<svg fill="currentColor" width="24" height="24" tabindex="-1" class={$$props.class || ''} style={$$props.style || ''} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<polygon points="21 11 24 11 24 23 26 23 26 11 29 11 29 9 21 9 21 11"/>
<polygon points="20 9 18 9 16 15 14 9 12 9 14.75 16 12 23 14 23 16 17 18 23 20 23 17.25 16 20 9"/>
<polygon points="3 11 6 11 6 23 8 23 8 11 11 11 11 9 3 9 3 11"/>
</svg>

After

Width:  |  Height:  |  Size: 429 B

@ -70,28 +70,37 @@
CloudArrowDown,
Beaker,
Eye,
Square3Stack3d,
QueueList,
} from "svelte-heros-v2";
import JsonIcon from "./JsonIcon.svelte";
import JsonLdIcon from "./JsonLdIcon.svelte";
import RdfIcon from "./RdfIcon.svelte";
import TurtleIcon from "./TurtleIcon.svelte";
export let config = {};
export let zera: string;
const exact_mapping = {
json_ld_editor: TableCells,
json_editor: TableCells,
triple_editor: Share,
json_ld_editor: JsonLdIcon,
json_editor: JsonIcon,
triple_editor: RdfIcon,
turtle_viewer: TurtleIcon,
rdf_viewer: CircleStack,
graph_viewer: Sun,
compose: QueueList,
sparql_query: RocketLaunch,
sparnatural: CursorArrowRays,
graphql: Cube,
invoke: Play,
ontology_viewer: ArrowsPointingOut,
download: DocumentArrowDown,
post_edit: PencilSquare,
edit: PencilSquare,
file: Document,
source: CodeBracket,
post: DocumentText,
pad: Square2Stack,
container: Square3Stack3d,
card: Clipboard,
gallery: RectangleStack,
load_graph: CloudArrowUp,
@ -117,4 +126,4 @@
};
</script>
<Icon {...config} variation="outline" color="black" icon={find(zera)} />
<Icon {...config} variation="outline" color="currentColor" icon={find(zera)} />

@ -0,0 +1,238 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import ng from "../../api";
import {
branch_subscribe,
active_session,
online,
get_blob,
} from "../../store";
import { cur_tab } from "../../tab";
import {
ExclamationTriangle,
ArrowDownTray,
ArrowUpTray,
} from "svelte-heros-v2";
import { onMount, onDestroy, tick } from "svelte";
import { Button, Progressbar, Spinner } from "flowbite-svelte";
import { t } from "svelte-i18n";
let is_tauri = import.meta.env.TAURI_PLATFORM;
let upload_progress: null | { total: number; current: number; error?: any } = null;
let commits = $active_session && branch_subscribe($cur_tab.branch.nuri, false);
let fileinput;
let file_urls = {};
const prepare_url = (nuri) => {
if (!file_urls[nuri]) {
file_urls[nuri] = {
click: false
};
}
return true;
}
const download = async (file) => {
if (is_tauri) {
await ng.file_save_to_downloads($active_session.session_id, file.reference, file.name, "did:ng:"+$cur_tab.branch.nuri);
} else {
file_urls[file.nuri].url = await get_blob(file, false);
//console.log(file.name);
//console.log(file_urls[file.nuri].click);
await tick();
file_urls[file.nuri].click.click();
}
}
const isImage = async (url) : Promise<boolean> => {
if ( typeof url === 'string' ) {
let blob = await fetch(url).then(r => r.blob());
return blob.type.startsWith("image/");
}
return false;
}
function uploadFile(upload_id, nuri, file, success) {
console.log(nuri);
let chunkSize = 1_048_564;
let fileSize = file.size;
let offset = 0;
let readBlock = null;
upload_progress = { total: fileSize, current: offset };
let onLoadHandler = async function (event) {
let result = event.target.result;
if (event.target.error == null) {
offset += result.byteLength;
upload_progress = { total: fileSize, current: offset };
// console.log("chunk", result);
let res = await ng.upload_chunk(
$active_session.session_id,
upload_id,
result,
nuri
);
//console.log("chunk upload res", res);
// if (onChunkRead) {
// onChunkRead(result);
// }
} else {
// if (onChunkError) {
// onChunkError(event.target.error);
// }
return;
}
// If finished:
if (offset >= fileSize) {
//console.log("file uploaded");
let res = await ng.upload_chunk(
$active_session.session_id,
upload_id,
[],
nuri
);
//console.log("end upload res", res);
if (success) {
upload_progress = { total: fileSize, current: fileSize };
success(res);
} else {
upload_progress = { total: fileSize, current: fileSize, error: true };
}
// Make progress bar disappear
setTimeout(() => {
upload_progress = null;
}, 1_000);
return;
}
readBlock(offset, chunkSize, file);
};
readBlock = function (offset, length, file) {
let fileReader = new FileReader();
let blob = file.slice(offset, length + offset);
fileReader.onload = onLoadHandler;
fileReader.readAsArrayBuffer(blob);
};
readBlock(offset, chunkSize, file);
return;
}
const onFileSelected = async (e) => {
let image = e.target.files[0];
if (!image) return;
//console.log(image.type);
let start_request_payload = {
RandomAccessFilePut: image.type,
};
let nuri = "did:ng:"+$cur_tab.branch.nuri;
let start_res = await ng.app_request_with_nuri_command(nuri, "FilePut", $active_session.session_id, start_request_payload);
let upload_id = start_res.V0.FileUploading;
uploadFile(upload_id, nuri, image, async (reference) => {
if (reference) {
let file_put_payload = {
AddFile: {
filename: image.name,
object: reference.V0.FileUploaded,
},
};
await ng.app_request_with_nuri_command(nuri, "FilePut", $active_session.session_id, file_put_payload);
}
});
fileinput.value = "";
};
</script>
<div class="w-full">
{#if $cur_tab.doc.can_edit}
<div class="row pt-2 w-full">
<Button
disabled={!$online && !is_tauri}
type="button"
on:click={() => {
fileinput.click();
}}
class="text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mr-2 mb-2"
>
<ArrowUpTray class="w-8 h-8 mr-2 -ml-1"/>
{$t("doc.file.upload")}
</Button>
<input
style="display:none"
type="file"
on:change={(e) => onFileSelected(e)}
bind:this={fileinput}
/>
</div>
{/if}
{#if upload_progress !== null}
<div class="mx-6 mt-2">
<Progressbar
progress={(
(100 * upload_progress.current) /
upload_progress.total
).toFixed(0)}
labelOutside={$t("doc.file.upload_progress")}
/>
</div>
{/if}
{#if commits}
{#await commits.load()}
<p>{$t("connectivity.loading")}...</p>
{:then}
{#each $commits.files as file}
<p class="mb-5">
{#await get_blob(file, true)}
<div class="ml-2">
<Spinner />
</div>
{:then url}
{#await isImage(url) then is}
{#if is}
<img src={url} title={file.nuri} alt={file.name} />
{/if}
{/await}
<span class="ml-2 text-gray-600">{file.name}<br/></span>
{#if url === false}
<span><ExclamationTriangle tabindex="-1" class="ml-2 w-6 h-8 focus:outline-none" style="display:inline"/>{$t("errors.cannot_load_this_file")}</span>
{:else if prepare_url(file.nuri)}
<a bind:this={file_urls[file.nuri].click}
href={file_urls[file.nuri].url || ""}
target="_blank"
download={file.name}
></a>
<button class="ml-2 select-none p-1 pb-0 pt-0 text-gray-600" style="box-shadow:none;" on:click={()=>download(file)}>
<span><ArrowDownTray tabindex="-1" class="w-6 h-8 mr-3 focus:outline-none" style="display:inline"/>{$t("doc.file.download")}</span>
</button>
{/if}
{/await}
</p>
{/each}
{/await}
{/if}
</div>

@ -0,0 +1,155 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import {
branch_subscribe,
active_session,
} from "../../store";
import { get } from "svelte/store";
import { onMount, onDestroy, tick } from "svelte";
import {
Sun,
Cloud,
DocumentPlus,
DocumentMinus,
CircleStack,
Funnel,
FingerPrint,
Key,
Cog,
Icon,
ShieldCheck,
} from "svelte-heros-v2";
import BranchIcon from "../icons/BranchIcon.svelte";
import { t } from "svelte-i18n";
import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte";
import { cur_tab } from "../../tab";
import ng from "../../api";
import {
createGitgraph,
templateExtend,
TemplateName,
} from "./history/gitgraph-js/gitgraph";
let gitgraph;
let history = [];
let unsub = () => {};
onMount(async ()=>{
setTimeout(async()=> {
const graphContainer = document.getElementById("graph-container");
gitgraph = createGitgraph(graphContainer, {
template: templateExtend(TemplateName.Metro, {
branch: { label: { display: false } },
commit: { message: { displayAuthor: false, displayHash: false } },
}),
});
let res = await ng.branch_history($active_session.session_id, "did:ng:"+$cur_tab.branch.nuri);
// for (const h of res.history) {
// console.log(h[0], h[1]);
// }
//console.log(res.swimlane_state);
history = [...res.history].reverse();
gitgraph.swimlanes(res.swimlane_state.map((s)=> s || false));
gitgraph.import(res.history.map((h)=>{return {
hash:h[0],
branch:h[1].branch,
author:h[1].author,
parents:h[1].past,
x:h[1].x,
y:h[1].y,
subject:h[1].timestamp,
onClick:()=>openCommit(h[0]),
onMessageClick:()=>openCommit(h[0])
};}));
let branch = branch_subscribe($cur_tab.branch.nuri,false);
unsub();
unsub = branch.subscribe((b) => {
//console.log("subscription callbak",b.history.commits);
if (Array.isArray(b.history.commits)) {
for (var h; h = b.history.commits.pop(); ) {
//console.log(h);
history.unshift(h);
history = history;
gitgraph.commit({
hash: h[0],
author:h[1].author,
parents:h[1].past,
subject:h[1].timestamp,
onClick:()=>openCommit(h[0]),
onMessageClick:()=>openCommit(h[0])
});
}
}
});
get(branch).history.start();
},1);
});
onDestroy( ()=>{
let branch = branch_subscribe($cur_tab.branch.nuri,false);
get(branch).history.stop();
unsub();
});
const openCommit = (id:string) => {
console.log("open commit",id);
}
const commit_type_icons = {
"TransactionGraph": Sun,
"TransactionDiscrete": Cloud,
"TransactionBoth": Sun,
"FileAdd": DocumentPlus,
"FileRemove": DocumentMinus,
"Snapshot": CircleStack,
"Compact": Funnel,
"AsyncSignature": FingerPrint,
"SyncSignature": FingerPrint,
"Branch": BranchIcon,
"UpdateBranch": BranchIcon,
"BranchCapRefresh": Key,
"CapRefreshed": Key,
"Other": Cog,
};
</script>
<div style="width:120px; min-width:120px;font-family: monospace; font: Courier; font-size:16px;">
{#each history as commit}
<div class="w-full commit relative text-gray-500" style="height:60px;" role="button" title={commit[0]} tabindex=0 on:click={()=>openCommit(commit[0])} on:keypress={()=>openCommit(commit[0])}>
{#if commit[1].final_consistency}<ShieldCheck tabindex="-1" class="w-5 h-5 absolute text-primary-600" style="top:9px;right:20px;" />
{:else if commit[1].signature}<ShieldCheck tabindex="-1" class="w-5 h-5 absolute text-green-600" style="top:9px;right:20px;" />
{/if}
<Icon tabindex="-1" class="w-5 h-5 focus:outline-none absolute " style="top:9px;right:0px;" variation="outline" color="currentColor" icon={commit_type_icons[commit[1].commit_type]} />
{#if commit[1].commit_type==="TransactionBoth"}<Cloud tabindex="-1" class="w-5 h-5 absolute " style="top:28px;right:0px;" />{/if}
<b>{commit[0].substring(0,7)}</b><br/>
<span class="text-xs leading-tight">{commit[1].author.substring(0,9)}</span>
</div>
{/each}
</div>
<div style="cursor:pointer;" id="graph-container"></div>
<style>
.commit {
padding: 8px;
}
</style>

@ -97,7 +97,14 @@ class GitgraphUserApi<TNode> {
data["parents"].forEach((parent) => {
let new_lane = this._graph.last_on_swimlanes.indexOf(parent);
if ( new_lane < lane ) {
if (lane != Number.MAX_VALUE)
this._graph.swimlanes[lane] = undefined;
lane = new_lane;
} else {
// we close that lane
//this._graph.swimlanes
this._graph.swimlanes[new_lane] = undefined;
}
});
branch = this._graph.swimlanes[lane];
@ -108,35 +115,39 @@ class GitgraphUserApi<TNode> {
this._graph.last_on_swimlanes[lane] = data["hash"];
} else {
branch = data["hash"];
// this._graph.swimlanes.some((b, col) => {
// console.log("is empty? ",col,!b);
// if (!b) {
// lane = col;
// return true;
// }
// });
// if (!lane) {
this._graph.swimlanes.some((b, col) => {
//console.log("is empty? ",col,!b);
if (!b) {
let r = this._graph.rows.getRowOf(this._graph.last_on_swimlanes[col]);
if (data["parents"].some( (parent) => this._graph.rows.getRowOf(parent) < r ))
return false;
lane = col;
return true;
}
});
if (!lane) {
lane = this._graph.swimlanes.length;
this._graph.swimlanes.push(branch);
this._graph.last_on_swimlanes.push(branch);
// } else {
// this._graph.swimlanes[lane] = branch;
// this._graph.last_on_swimlanes[lane] = branch;
// }
} else {
this._graph.swimlanes[lane] = branch;
this._graph.last_on_swimlanes[lane] = branch;
}
}
data["parents"].forEach((parent) => {
let r = this._graph.rows.getRowOf(parent);
let c = this._graph.commits[r];
let b = c.branch;
if (branch!=b) {
this._graph.swimlanes.forEach((bb, col) => {
if (bb == b) {
this._graph.swimlanes[col] = undefined;
}
});
}
});
// data["parents"].forEach((parent) => {
// let r = this._graph.rows.getRowOf(parent);
// let c = this._graph.commits[r];
// let b = c.branch;
// if (branch!=b) {
// this._graph.swimlanes.forEach((bb, col) => {
// if (bb == b) {
// this._graph.swimlanes[col] = undefined;
// }
// });
// }
// });
// if (!this._graph.branches.has(branch)) {
// this._graph.createBranch({name:branch});

@ -100,7 +100,8 @@ function createGitgraph(
createG({
// Translate graph left => left-most branch label is not cropped (horizontal)
// Translate graph down => top-most commit tooltip is not cropped
translate: { x: 0, y: TOOLTIP_PADDING },
translate: { x: 10, y: TOOLTIP_PADDING },
scale: 0.75,
children: [renderBranchesPaths(branchesPaths), $commits],
}),
);
@ -312,8 +313,17 @@ function createGitgraph(
return message;
}
let msg = commit.message.split(" ");
const text = createText({
content: commit.message,
content: msg[0],
fill: commit.style.message.color || "",
font: commit.style.message.font,
onClick: commit.onMessageClick,
});
const text2 = createText({
content: msg[1],
fill: commit.style.message.color || "",
font: commit.style.message.font,
onClick: commit.onMessageClick,
@ -324,6 +334,13 @@ function createGitgraph(
children: [text],
});
let message2 = createG({
translate: { x: 0, y: commit.style.dot.size*2 },
children: [text2],
});
message.appendChild(message2);
if (commit.body) {
const body = createForeignObject({
width: 600,

@ -49,6 +49,7 @@ interface GOptions {
x: number;
y: number;
};
scale?: number;
fill?: string;
stroke?: string;
strokeWidth?: number;
@ -60,11 +61,11 @@ interface GOptions {
function createG(options: GOptions): SVGGElement {
const g = document.createElementNS(SVG_NAMESPACE, "g");
options.children.forEach((child) => child && g.appendChild(child));
let scale = options.scale ? ` scale(${options.scale})`: "";
if (options.translate) {
g.setAttribute(
"transform",
`translate(${options.translate.x}, ${options.translate.y})`,
`translate(${options.translate.x}, ${options.translate.y})${scale}`,
);
}
@ -118,7 +119,7 @@ function createText(options: TextOptions): SVGTextElement {
}
if (options.font) {
text.setAttribute("style", `font: ${options.font}`);
text.setAttribute("style", `font-family:monospace;font: Courier`);
}
if (options.anchor) {

@ -11,7 +11,7 @@ function createTooltip(commit: Commit): SVGElement {
const path = createPath({ d: "", fill: "#EEE" });
const text = createText({
translate: { x: OFFSET + PADDING, y: 0 },
content: `${commit.hashAbbrev} - ${commit.subject}`,
content: `${commit.hashAbbrev}`,
fill: "#333",
});

@ -0,0 +1,600 @@
{
"pages": {
"not_found": {
"title": "Seite nicht gefunden",
"message": "Die gesuchte Seite existiert nicht."
},
"user_panel": {
"title": "Nutzerbereich",
"personal": "Privat"
},
"user_registered": {
"success": "Registrierung erfolgreich.",
"success_with_invitation": "Du wurdest erfolgreich für {invitation_name} registriert."
},
"wallet_info": {
"title": "Dein Wallet",
"download": "Wallet downloaden",
"download_failed": "Download fehlgeschlagen:<br/>{error}",
"download_in_progress": "Download läuft...",
"download_successful": "Das Wallet wurde gespeichert unter: \"{wallet_file}\".",
"download_file_button": "Klicken zum Download des Wallets",
"remove_wallet": "Wallet vom Gerät entfernen",
"remove_wallet_modal.title": "Wirklich entfernen?",
"remove_wallet_modal.confirm": "Bist du sicher, dass du das Wallet vom Gerät entfernen möchtest?",
"create_text_code": "Export: TextCode erstellen",
"scan_qr.title": "Export: QR-Code scannen",
"scan_qr.notes": "Scanne einen QR-Code vom Gerät, auf das du dich anmelden möchtest.",
"scan_qr.scanner.title": "QR-Code Scannen für Wallet-Export",
"scan_qr.scanner.loading": "QR-Scanner wird geladen",
"scan_qr.syncing": "Wallet wird übertragen",
"scan_qr.scan_successful": "Erfolg!<br />Du kannst dich nun auf dem neuen Gerät anmelden.",
"gen_qr.title": "Export: QR-Code erstellen",
"gen_qr.notes": "Scanne den QR-Code auf dem Gerät, auf dem du dich anmelden möchtest.",
"gen_qr.img_title": "Dein Export-QR-Code",
"gen_qr.img_alt": "Dein Export QR-Code zum Scannen",
"gen_qr.gen_button": "QR-Code laden",
"gen_text_code.title": "Wallet mit TextCode übertragen",
"gen_text_code.gen_btn": "TextCode erstellen",
"gen_text_code.label": "Dein TextCode:"
},
"settings": {
"title": "Einstellungen"
},
"admin": {
"title": "Admin"
},
"accounts": {
"title": "Konten"
},
"full_layout": {
"home": "Startseite",
"stream": "Stream",
"search": "Suche",
"create": "Erstellen",
"shared": "Mit mir geteilt",
"site": "Seite",
"messages": "Nachrichten",
"notifications": "Benachrichtigungen"
},
"install": {
"app_availability": "<b>NextGraph App</b> gibt es zum Download für dein Handy, Tablet, Laptop, PC und Mac.<br />Egal ob iOS, Android, Linux, macOS, Windows oder eine andere Platform, die einen modernen Web-Browser unterstützt.",
"has_wallet_warning": "A wallet is saved in this browser. If it is yours,<br /> once the installation of the app will be finished,<br /> choose the option \"Login\" on the app.<br /> (do not create another wallet from the app).",
"android_play_store": "Android Play Store",
"download_apk": "Download APK",
"ios_app_store": "iOS App Store",
"download_mac_os": "Download für MacOS",
"download_linux": "Download als Linux Package",
"download_windows": "Download für Windows",
"other_platforms": "Andere Plattformen"
},
"no_wallet": {
"welcome": "Willkommen bei NextGraph",
"description": "Auf diesen Gerät scheint es noch kein Wallet zu geben.<br />Wenn du bereits ein Wallet hast, wähle \"Anmelden\". Andernfalls wähle \"Wallet erstellen\".",
"create_wallet": "Wallet erstellen"
},
"login": {
"heading": "Wie öffente ich mein Wallet? Es gibt zwei Optionen:",
"with_pazzle": "Mit deinem Pazzle",
"pazzle_steps": {
"1": "Unter 9 Emoji-Kategorien bekommst du je 15 Emoji-Bilder angezeigt. Die Kategorien werden dir bei jedem Login zur Sicherheit in zufälliger Reihenfolge gezeigt.",
"2": "Zu jeder Kategorie gehört dir genau ein Emoji. Klicke oder tippe dieses an. Die 15 Emojis werden dir ebenfalls in zufälliger Reihenfolge angezeigt. Auf Computern kannst du zum Wählen der Emojis auch die Tab und die Leertaste nutzen.",
"3": "Sobald du in allen Kategorien dein Emoji ausgewählt hast, musst du die richtige Reihenfolge angeben.",
"4": "Diese wurde dir beim Erstellen deines Wallets mitgegeben. Klicke oder tippe die Emojis in der richtigen Reihenfolge an. Nach dem Auswählen eines Emojis wird dieses grau und die Zahl in der Reihenfolge wird angezeigt.",
"5": "Wähle das zweite, dritte, ... Emoji der Reihe nach aus, bis alle ausgewählt wurden.",
"6": "Zuletzt musst du deinen Pin eingeben. Klicke oder tippe dafür auf die Pin-Zahlen."
},
"with_mnemonic": "Mit deiner 12-Wort-Mnemonik (Passphrase)",
"mnemonic_steps": {
"1": "Gib deine 12-wortige Mnemonik in das Eingabefeld ein. Die Wörter müssen mit Leerzeichen getrennt sein.",
"2": "Gib anschließend den PIN ein, den du beim Erstellen deines Wallet gewählt hast."
},
"trust_device_allow_cookies": "Mit dieser Option stimmst du dem Speichern von Cookies zu.",
"trust_device_yes": "Yes, speichere das Wallet auf diesem Gerät",
"device_name_label": "Name des Geräts. Du kannst ihn hier ändern",
"device_name_placeholder": "Gerätename",
"loading_pazzle": "Pazzle wird geladen",
"open_with_pazzle": "Mit Pazzle öffnen",
"login_cancel": "Login abbrechen",
"open_with_mnemonic": "Mit Mnemonik öffnen",
"enter_mnemonic": "Enter your 12 words mnemonic",
"mnemonic_placeholder": "12 Wörter mit Leerzeichen getrennt",
"select_emoji": "Wähle dein Bild für die Kategorie:<br />{category}",
"order_emojis": "Wähle die Bilder in der richtigen Reihenfolge",
"enter_pin": "Gib deinen PIN ein",
"opening_wallet": "Wallet wird geöffnet...<br />Bitte warten",
"wallet_opened": "Dein Wallet is geöffnet! <br />Die App wird geladen...",
"qr_code": "Wallet QR-Code",
"qr_modal_title": "Mein Wallet QQ-Code",
"qr_modal_description": "Nutze diesen QR-Code, um dich auf anderen Geräten anzumelden",
"keep_wallet": "Auf Gerät speichern, um dich später wieder anmelden zu können."
},
"account_info": {
"title": "Deine Konten",
"site": "{name} Account",
"devices": "Geräte",
"brokers": "Broker",
"no_brokers_connected": "Keine Broker verbunden"
},
"wallet_create": {
"redirecting_to_registration_page": "Du wirst zur Brokerregistrierungsseite weitergeleitet",
"complete_in_popup": "Schließe die Registrierung im Popup ab",
"own_your_ngbox": "Mit NG-Box selbst hosten",
"self-host_broker": "Broker selbst hosten",
"tos_ng_one": "AGB NextGraph.one",
"wallet_description": "Ein <b>NextGraph Wallet</b> ist für jede Person einzigartig. Es speichert deine Anmeldedaten und Berechtigungen für den Zugriff auf Dokumente. Du benötigst eines, um NextGraph nutzen zu können.<br /><br />Falls du bereits ein Wallet hast, solltest du kein neues erstellen. <a href=\"#/wallet/login\">Melden dich stattdessen hier mit deinem bestehenden Wallet an.</a> Wenn du noch kein NextGraph Wallet besitzt, folge bitte den nachstehenden Anweisungen, um dein persönliches Wallet zu erstellen.",
"has_wallet": "Es sind bereits Wallets auf diesem Gerät gespeichert.<br />Um dich mit einem davon anzumelden, <a href=\"#/wallet/login\" use:link>klicke hier.</a>",
"wallet_about": {
"title": "Was ist ein Wallet?",
"please_read": "Bitte lesen",
"1": "Dein Wallet ist eine sichere und verschlüsselte kleine Datei, die wichtige Informationen enthält, auf die nur du Zugriff haben sollten.",
"2": "In deinem Wallet werden alle Berechtigungen gespeichert für den Zugriff auf Dokumente, die dir gewährt wurden oder die du selbst erstellt hast.",
"3": "Um es zu öffnen, musst du dein <b>Pazzle</b> und einen <b>PIN</b> aus 4 Ziffern eingeben. Deom persönliches Pazzle (eine Kombination aus Puzzle und Passwort) besteht aus 9 Emoji-Bildern, die du dir merken musst. Die Reihenfolge der Bilder ist ebenfalls wichtig.",
"4": "Keine Sorge, es ist einfacher, sich 9 Emojis zu merken als ein Passwort wie \"69$g&ms%C*%\", und es hat die gleiche Sicherheits-Stärke wie ein komplexes Passwort. Die Entropie deines Pazzles beträgt <b>66 Bits</b>, was nach allen Standards als sehr hoch gilt.",
"5": "Du solltest <b>nicht mehrere Wallets für dich selbst erstellen</b>. Alle deine Konten, Identitäten und Berechtigungen werden deinem einzigartigen Wallet später hinzugefügt. Erstelle kein weiteres Wallet, wenn du bereits eins besitzt. Du kannst stattdessen dein bestehendes Wallet in alle Apps und Websites <b>importieren</b>, in denen du es benötigst.",
"6": "Dein Wallet kann mit Hilfe einer kleinen Datei, die herunterlädst, oder mit einem QR-Code importiert werden. In jedem Fall solltest du diese Datei oder den QR-Code niemals mit jemand anderem teilen.",
"7": "Wir bei NextGraph werden den Inhalt Ihres Wallets nie sehen. Es ist verschlüsselt und wir kennen Ihr Pazzle nicht, daher können wir nicht sehen, was sich darin befindet.",
"8": "Aus dem gleichen Grund können wir dir nicht helfen, wenn du dein Pazzle oder deinen PIN-Code vergessen hast oder du deine Wallet-Datei verlierst. <span class=\"text-bold\">Es gibt in diesem Fall keine Option zur \"Passwort-Wiederherstellung\"</span>. Du kannst dein Pazzle auf einem Stück Papier notieren, bis du es dir gemerkt hast, aber vergesse nicht, es zu vernichten sobald du es dir sehr sicher eingeprägt hast."
},
"create_wallet_now": "Ich erstelle jetzt mein Wallet!",
"select_server": "NextGraph basiert auf einem effizienten dezentralen P2P-Netzwerk, und um diesem Netzwerk beizutreten und die App zu nutzen, musst du zunächst einen <b>Broker-Server</b> auswählen.",
"broker_about": {
"title": "Was ist ein Broker?",
"please_read": "Bitte lesen",
"1": "Der Broker hilft dir, alle deine Daten <b>synchron</b> zu halten, da er 24/7 mit dem Internet verbunden ist und eine Kopie der Aktualisierungen für dich aufbewahrt. Auf diese Weise kannst du dir auch dann die Änderungen von anderen sehen, wenn deren Geräte offline sind.",
"2": "Alle deine Daten sind sicher und <b>Ende-zu-Ende verschlüsselt</b>, und der Broker kann den Inhalt der Dokumente nicht sehen, da er nicht über die Schlüssel zu deren Entschlüsselung verfügt.",
"3": "Der Broker hilft dir, deine <b>Privatsphäre</b> zu wahren, indem er deine Internetadresse (IP) vor anderen verbirgt, mit denen du Dokumente teilst.",
"4": "In Zukunft wird es möglich sein, NextGraph alternativ auch ohne Broker zu nutzen und direkte Verbindungen zwischen Geräten herzustellen, aber dies wird eine weniger reibungslose Erfahrung zur Folge haben.",
"5": "Du kannst jederzeit beschließen, zu einem anderen Broker-Dienstanbieter zu wechseln oder selbst zu hosten. Deine Daten sind vollständig <b>portabel</b> und können frei zu einem anderen Broker verschoben werden.",
"6": "Bald werden wir die Möglichkeit bieten, deinen eigenen Broker <b>zuhause</b> oder im <b>Büro</b> zu hosten. Anstatt einen \"Broker-Dienstanbieter\" zu nutzen, besitzt du dann ein kleines Gerät, das du hinter deinen Internet-Router anschließt. Es heißt <b>NG Box</b> und ist in Kürze erhältlich.",
"7": "Organisationen und Unternehmen haben die Möglichkeit, einen Broker <b>vor Ort</b> oder in der <b>Cloud</b> zu hosten, da die Software Open Source ist. Einzelpersonen können auch einen Broker auf jedem VPS-Server oder zu Hause auf ihrer eigenen Hardware <b>selbst hosten</b>."
},
"choose_broker": "Bitte wähle einen Broker aus der Liste",
"register_with_broker": "Beim Broker {broker} registrieren",
"for_eu_citizens": "Bürger der Europäischen Union",
"for_rest": "Für den Rest der Welt",
"enter_invite_link": "Gib einen Einladungslink ein",
"scan_invite_qr": "Scanne einen Einladungs-QR-Code",
"self_hosted_broker": "Selbst gehosteter Broker",
"ng_box": "NG Box (im Besitz oder eingeladen)",
"registration_success": "Du wurdest erfolgreich bei {broker} registriert",
"choose_pin": {
"title": "Beginne mit dem Erstellen deines Wallets und wähle einen PIN",
"description": "Wir empfehlen, einen PIN zu wählen, den du bereits sehr gut kennst.<br /> Dein Kreditkarten-PIN ist zum Beispiel eine gute Wahl.<br />Wir bei NextGraph werden deinen PIN niemals sehen.",
"rules": "Hier sind die Regeln für den PIN:",
"1": "Er darf keine Zahlenfolge wie 1234 oder 8765 sein.",
"2": "Die gleiche Ziffer darf nicht mehr als einmal wiederholt werden. Zum Beispiel ist 4484 ungültig.",
"3": "Versuche, Geburtsdaten, letzte Ziffern von Telefonnummern oder Postleitzahlen zu vermeiden"
},
"chosen_pin": "Dein gewählter PIN:",
"confirm_pin": "Bitte bestätigen.",
"confirm_pin_description": "Gib den gleichen PIN erneut ein.",
"pin_confirmed_as": "Dein bestätigter PIN: ",
"choose_security_phrase_and_image": {
"title": "Wähle als nächstes ein Sicherheitssatz und -bild",
"description": "Um dein Wallet zu erkennen, wird dir beim Öffnen dein Sicherheitssatz und dein Sicherheitsbild gezeigt. Wenn du also dein Pazzle und PIN eingibst, achte darauf, dass Sicherheitssatz und -bild stimmen.",
"warning": "Wenn du dein Wallet öffnen willst und nicht dein Sicherheitssatz und -bild siehst (oder ein falsches), brich den Vorgang ab. So vermeidest du, Opfer von Phishing-Angriffen zu werden.",
"rules_title": "Beachte die folgenden Regeln für Sicherheitssatz- und Passwort:",
"1": "Der Sicherheitssatz sollte mindestens 10 Zeichen lang sein.",
"2": "Er sollte etwas sein, das du dir merken kannst, aber nichts zu Persönliches.",
"3": "Gib nicht deinen vollständigen Namen, deine Adresse oder Telefonnummer ein.",
"4": "Stattdessen kannst du ein Zitat, einen kurzen Satz, den du magst, oder etwas für andere Bedeutungsloses, aber für dich Einzigartiges eingeben.",
"5": "Das Bild sollte mindestens 150x150px groß sein. Es muss nicht größer als 400x400px sein, da es ohnehin verkleinert wird.",
"6": "Wir akzeptieren verschiedene Formate wie JPEG, PNG, GIF, WEBP und mehr.",
"7": "Das Bild sollte für dich einzigartig sein. Es sollte aber auch nicht zu persönlich sein.",
"8": "Lade kein Bild von deinem Gesicht hoch, dies ist kein Profilbild.",
"9": "Am besten wäre eine Landschaft, die du magst, oder ein anderes Bild, das du als einzigartig erkennst.",
"10": "Bitte beachte, dass andere Personen, die dieses Gerät mit dir teilen, in der Lage sein werden, dieses Bild und den Satz zu sehen."
},
"type_security_phrase_placeholder": "Gib einen Sicherheitssatz ein...",
"save_security_phrase_and_image": "Sicherheitssatz & Bild speichern",
"tap_to_upload": "Tippe zum Hochladen eines Bildes",
"click_to_upload": "Klicke, um ein Bild auszuwählen",
"or_drag_drop": "oder ziehe und lege es ab",
"pins_no_match": "Du hast nicht zweimal die gleiche PIN eingegeben",
"almost_done": "Wir sind fast fertig!",
"save_wallet_options": {
"description": "Es gibt 2 Optionen, die du auswählen musst, bevor wir dein Wallet erstellen können.",
"trust": "Vertraust du diesem Gerät?",
"trust_description": "Wenn ja, wenn dieses Gerät dir gehört oder von wenigen vertrauenswürdigen Personen deiner Familie oder deines Arbeitsplatzes genutzt wird und du dich in Zukunft wieder von diesem Gerät aus anmelden möchtest, dann kannst du dein Wallet auf diesem Gerät speichern. Wenn dieses Gerät hingegen öffentlich ist und von Fremden geteilt wird, speichere dein Wallet nicht hier.",
"trust_toggle": "Mein Wallet auf diesem Gerät speichern?",
"device_name_description": "Um zu sehen, mit welchen Geräten du verbunden bist, sollte jedes Gerät einen Namen haben (z.B. Bobs Laptop). Bitte gib ihn hier ein.",
"cloud": "Eine Kopie in der Cloud aufbewahren?",
"cloud_description": "Hast du Angst, dass du die Datei mit deinem Wallet verlierst? Wenn das passieren würde, wäre dein Wallet für immer verloren, zusammen mit allen deinen Dokumenten. Wir können eine verschlüsselte Kopie deines Wallets in unserer Cloud aufbewahren. Nur du wirst in der Lage sein, es mit einem speziellen Link herunterzuladen. Du müsstest diesen Link allerdings sicher aufbewahren. Durch die Auswahl dieser Option erklärst du dich einverstanden mit den",
"cloud_toggle": "Mein Wallet in der Cloud speichern?",
"cloud_tos": "Nutzungsbedingungen unserer Cloud",
"pdf": "Eine PDF-Datei deines Wallets erstellen?",
"pdf_description": "Wir können für dich eine PDF-Datei vorbereiten, die alle Informationen deines Wallets unverschlüsselt enthält. Du solltest diese Datei ausdrucken und dann die PDF-Datei löschen (und den Papierkorb leeren). Bewahre dieses ausgedruckte Dokument an einem sicheren Ort auf. Es enthält alle Informationen, um dein Wallet zu regenerieren, falls du den Zugang dazu verlierst.",
"pdf_toggle": "Eine PDF meines Wallets erstellen?",
"link": "Einen Link erstellen, um einfach auf dein Wallet zuzugreifen?",
"link_description": "Wenn du dein Wallet im Web oder von anderen Geräten aus nutzen möchtest, können wir dir helfen, dein Wallet zu finden, indem wir einen einfachen Link erstellen, der von überall zugänglich ist. Nur du wirst Zugang zu diesem Link haben. Um dies zu tun, werden wir deine Wallet-ID und einige Informationen über deinen Broker auf unseren Cloud-Servern speichern. Wenn du dies lieber nicht möchtest, deaktiviere einfach diese Option. Durch die Auswahl dieser Option erklärst du dich einverstanden mit den",
"link_toggle": "Einen Link zu meinem Wallet erstellen?"
},
"lets_create": "Jetzt das Wallet erstellen",
"creating": "Dein Wallet wird erstellt",
"ready": "Dein Wallet ist bereit!",
"download_wallet_description": "Bitte lade dein Wallet herunter und bewahre es an einem sicheren Ort auf",
"download_wallet": "Mein Wallet herunterladen",
"download_wallet_done": "Deine Wallet-Datei wurde in deinen \"Downloads\"-Ordner heruntergeladen mit dem Namen<br /><span class=\"text-black\"> {download_name}</span ><br /> <span class=\"font-bold\" >Bitte verschiebe sie an einen sicheren und dauerhaften Ort.</span ><br />",
"your_pazzle": "Hier ist dein Pazzle. <br /> Die <span class=\"font-bold\">Reihenfolge</span> jedes Bildes ist <span class=\"font-bold\">wichtig</span>!",
"your_mnemonic": "Und hier ist deine Mnemonik (eine alternative Passphrase zum Pazzle):",
"unlock_tips_1": "Du kannst sowohl das Pazzle als auch die Mnemonik verwenden, um dein Wallet zu entsperren. Das Pazzle ist leichter zu merken. Die Mnemonik ist in einigen speziellen Fällen nützlich. Wir empfehlen, dass du das Pazzle verwendest. <span class=\"font-bold text-xl\" >Kopiere beides auf ein Stück Papier.</span > Du solltest versuchen, dir das Pazzle zu merken. Sobald du das getan hast, brauchst du das Papier nicht mehr.",
"unlock_tips_2": "Klicke jetzt auf \"Weiter zur Anmeldung\" und wähle dein neues Wallet aus.",
"unlock_tips_3": "Es ist wichtig, dass du dich mit diesem Wallet <span class=\"font-bold\">mindestens einmal</span> von diesem {platform, select, browser {Browser-Tab} other {Gerät}} aus <span class=\"font-bold\">anmeldest</span>, während du mit dem Internet verbunden bist, damit dein Konto auf deinem Broker registriert werden kann.",
"continue_to_login": "Weiter zur Anmeldung",
"continue_confirm": "Hast du deine Anmeldedaten aufgeschrieben?",
"continue_confirm_description": "Das Pazzle und die Mnemonik <span class=\"font-bold\">werden dir nicht noch einmal gezeigt</span >. Bitte schreite nur fort, dass du sie aufgeschrieben hast.",
"continue_confirm_go_back": "Zurück",
"continue_confirm_yes": "Ja, das habe ich"
},
"wallet_login": {
"select_wallet": "Wähle ein Wallet zur Anmeldung aus",
"from_import.title": "Dein Wallet wurde übertragen",
"from_import.description": "Dein Wallet wurde empfangen:",
"from_import.instruction": "Um den Import abzuschließen, melde dich bitte an.",
"with_another_wallet": "Mit einem anderen Wallet anmelden",
"import_wallet": "Dein Wallet importieren",
"import_file": "Wallet-Datei importieren",
"import_qr": "Mit QR-Code importieren",
"import_link": "Mit TextCode importieren",
"new_wallet": "Ein neues Wallet erstellen",
"logged_in": "Du bist angemeldet.<br />Bitte warte, während die App geladen wird."
},
"wallet_login_qr": {
"title": "Wallet mit QR-Code importieren",
"scan.description": "Um dein Wallet von einem anderen Gerät zu importieren, erzeuge dort einen Export-QR-Code. Gehe zum Erstellen zu<br /><span class=\"path\">Nutzerbereich > Wallet > Export: QR-Code erstellen</span>.",
"scan.modal.title": "Export-QR-Code scannen",
"gen.description": "Um dein Wallet von einem anderen Gerät zu importieren, kannst du hier auf diesem Gerät einen Import-QR-Code generieren und ihn dann mit deinem anderen Gerät scannen. Gehe auf dem anderen Gerät zu<br /><span class=\"path\">Nutzerbereich > Wallet > Export: QR scannen</span>, um zu exportieren.",
"gen.generated": "Scanne diesen QR-Code vom anderen Gerät aus.",
"success_btn": "Weiter zur Anmeldung"
},
"wallet_login_textcode": {
"title": "Wallet aus TextCode importieren",
"description": "Um einen TextCode zu generieren, öffne ein angemeldetes Gerät und gehe zu<br /><span class=\"path\">Nutzerbereich > Wallet > TextCode generieren</span>, um zu exportieren.",
"import_btn": "Mit TextCode importieren"
},
"scan_qr": {
"scanning": "QR-Code scannen"
}
},
"buttons": {
"ok": "Ok",
"go_back": "Zurück",
"back": "Zurück",
"confirm": "Bestätigen",
"cancel": "Abbrechen",
"remove": "Entfernen",
"correct": "Korrigieren",
"try_again": "Erneut versuchen",
"back_to_homepage": "Zurück zur Startseite",
"logout": "Abmelden",
"login": "Anmelden",
"start_over": "Neu beginnen",
"generate": "Generieren",
"scan_qr": "QR-Code scannen"
},
"errors": {
"an_error_occurred": "Ein Fehler ist aufgetreten",
"error_occurred": "Ein Fehler ist aufgetreten:<br />{message}",
"AlreadyExists": "Du bist bereits beim ausgewählten Broker registriert.<br />Versuche stattdessen, dich anzumelden.",
"InvalidSignature": "Die Signatur ist ungültig.",
"IncompleteSignature": "Die Signatur ist unvollständig.",
"SerializationError": "Das Objekt konnte nicht serialisiert werden.",
"EncryptionError": "Dein Wallet konnte nicht geöffnet werden. Du hast wahrscheinlich einen Fehler gemacht.",
"DecryptionError": "Fehler bei der Entschlüsselung.",
"InvalidValue": "Der Wert ist ungültig.",
"ConnectionNotFound": "Die Verbindung wurde nicht gefunden.",
"InvalidKey": "Der Schlüssel ist ungültig.",
"InvalidInvitation": "Die Einladung ist ungültig.",
"InvalidCreateAccount": "Bei der Erstellung des Kontos ist ein Fehler aufgetreten.",
"InvalidFileFormat": "Das Dateiformat ist ungültig.",
"InvalidArgument": "Der Parameter ist ungültig.",
"PermissionDenied": "Zugriff verweigert.",
"InvalidPazzle": "Das Pazzle ist nicht korrekt.",
"InvalidMnemonic": "Die Mnemonik ist nicht korrekt.",
"CommitLoadError": "Fehler beim Laden des Commits.",
"ObjectParseError": "Fehler beim Parsen des Objekts.",
"StorageError": "Speicherfehler.",
"NotFound": "Nicht gefunden",
"JsStorageKeyNotFound": "JavaScript-Speicherschlüssel konnte nicht gefunden werden.",
"IoError": "Ein-/Ausgabefehler.",
"CommitVerifyError": "Überprüfung des Commits fehlgeschlagen.",
"LocalBrokerNotInitialized": "Der lokale Broker ist nicht initialisiert.",
"JsStorageReadError": "Lesen aus dem JavaScript-Speicher nicht möglich.",
"JsStorageWriteError": "Schreiben in den JavaScript-Speicher nicht möglich.",
"CannotSaveWhenInMemoryConfig": "Speichern bei In-Memory-Konfiguration nicht möglich.",
"WalletNotFound": "Das Wallet wurde nicht gefunden.",
"WalletAlreadyAdded": "Das Wallet wurde bereits hinzugefügt.",
"WalletAlreadyOpened": "Das Wallet ist bereits geöffnet.",
"WalletError": "Fehler mit dem Wallet.",
"BrokerError": "Fehler mit dem Broker.",
"SessionNotFound": "Die Sitzung kann nicht gefunden werden.",
"SessionAlreadyStarted": "Die Sitzung wurde bereits gestartet.",
"RepoNotFound": "Das Repository kann nicht gefunden werden.",
"BranchNotFound": "Der Branch kann nicht gefunden werden.",
"StoreNotFound": "Der Speicher kann nicht gefunden werden.",
"UserNotFound": "User kann nicht gefunden werden.",
"TopicNotFound": "Das Thema kann nicht gefunden werden.",
"NotConnected": "Es besteht keine Verbindung.",
"ActorError": "Actor-Fehler.",
"ProtocolError": "Protokoll-Fehler.",
"ServerError": "Server-Fehler.",
"InvalidResponse": "Ungültige Antwort erhalten.",
"BootstrapError": "Fehler beim Bootstrapping",
"NotAServerError": "Kein Server-Fehler.",
"VerifierError": "Fehler während der Überprüfung.",
"SiteNotFoundOnBroker": "Die Seite kann nicht auf dem Broker gefunden werden",
"BrokerConfigErrorStr": "{error}",
"BrokerConfigError": "Fehler in der Broker-Konfiguration.",
"MalformedEvent": "Das Ereignis hat ein ungültiges Format.",
"InvalidPayload": "Die Nutzlast ist ungültig.",
"WrongUploadId": "Die Upload-ID ist falsch.",
"FileError": "Fehler mit der Datei.",
"InternalError": "Interner Fehler",
"OxiGraphError": "Fehler in der OxiGraph-Datenbank.",
"ConfigError": "Fehler in der Konfiguration.",
"LocalBrokerIsHeadless": "Der lokale Broker ist headless.",
"LocalBrokerIsNotHeadless": "Der lokale Broker ist nicht headless.",
"InvalidNuri": "Ungültige NextGraph-URI.",
"InvalidTarget": "Ziel kann nicht aufgelöst werden.",
"ExportWalletTimeOut": "Die Wallet-Exportsession ist abgelaufen. Versuche es erneut.",
"ConnectionError": "Verbindung zum Server nicht möglich.",
"IncompatibleQrCode": "Du hast einen NextGraph-QR-Code gescannt, der den falschen Typ hat.",
"NotARendezVous": "Du hast einen ungültigen QR-Code gescannt."
},
"connectivity": {
"stopped": "Unterbrochen",
"personal": "Persönlich",
"connecting": "Verbindung wird hergestellt",
"connected": "Verbunden",
"loading": "Wird geladen",
"connection_error_short": "{error}",
"online": "Online",
"offline": "Offline"
},
"common": {
"version": "Version: {version}",
"logo": "NextGraph-Logo",
"support_nextgraph": "NextGraph unterstützen",
"about_nextgraph": "Über NextGraph",
"donate_nextgraph": "An NextGraph spenden"
},
"wallet_sync": {
"offline_warning": "Du kannst dein Wallet nicht übertragen, wenn du offline bist.<br />Bitte stelle sicher, dass du zuerst verbunden bist.",
"textcode.usage_warning": "Du musst diesen TextCode mit dem anderen Gerät auf beliebige Weise austauschen (E-Mail, andere Messenger-Apps usw.). Es wird dringend empfohlen, ein Ende-zu-Ende-verschlüsseltes Tool zu verwenden. Wenn möglich, solltest du stattdessen die Option \"Mit QR-Code importieren\" nutzen, da diese sicherer ist. Wenn deine Geräte nicht mit dem Internet verbunden sind, kannst du die Option \"Wallet-Datei importieren\" verwenden. In diesem Fall überträgst du die Wallet-Datei mit einem USB-Stick von einem Gerät zum anderen, oder bei Mobilgeräten durch Verbinden deines Mobilgeräts per USB-Kabel mit dem Computer und anschließender Übertragung der Datei mit dem Dateiübertragungsprogramm auf Android oder AirDrop/Finder/iTunes auf einem iPhone/Mac/PC. Wir empfehlen nicht, deine Wallet-Datei in einen Cloud-Dienst hochzuladen.",
"server_transfer_notice": "Beide Geräte müssen online sein.<br />Während dieses Wallet-Imports wird dein Wallet vorübergehend und sicher für bis zu 5 Minuten auf unseren Servern gespeichert, wobei zwei Verschlüsselungsebenen verwendet werden.",
"importing": "Wallet wird importiert",
"error": "Beim Synchronisieren deines Wallets ist ein Fehler aufgetreten:<br />{error}"
},
"emojis": {
"codes": {
"happy": "grinsendes Gesicht",
"happy_tears": "Gesicht mit Freudentränen",
"halo": "lächelndes Gesicht mit Heiligenschein",
"three_hearts": "lächelndes Gesicht mit Herzen",
"with_two_hearts": "lächelndes Gesicht mit zwei herzförmigen Augen",
"one_heart": "Kuss zuwerfendes Gesicht",
"with_tongue": "Gesicht mit herausgestreckter Zunge und zusammengekniffenen Augen",
"with_two_hands": "Gesicht mit umarmenden Händen",
"one_hand": "verlegen kicherndes Gesicht",
"silenced": "Gesicht mit Reißverschlussmund",
"celebrating": "Partygesicht",
"sunglasses": "lächelndes Gesicht mit Sonnenbrille",
"eyes_up": "Augen verdrehendes Gesicht",
"monocle": "Gesicht mit Monokel",
"sleeping": "schlafendes Gesicht",
"mask": "Gesicht mit Atemschutzmaske",
"fever": "Gesicht mit Fieberthermometer",
"bandage": "Gesicht mit Kopfverband",
"vomit": "kotzendes Gesicht",
"tissue": "niesendes Gesicht",
"hot": "rotes, schwitzendes Gesicht",
"cold": "frierendes Gesicht",
"crossed_eyes": "benommenes Gesicht mit Kreuzaugen",
"exploding": "explodierender Kopf",
"sad": "düsteres Gesicht",
"long_nose": "lügendes Gesicht",
"many_tears": "heulendes Gesicht",
"fear": "vor Angst schreiendes Gesicht",
"tired": "gähnendes Gesicht",
"annoyed": "schnaubendes Gesicht",
"clown": "Clown-Gesicht",
"ghost": "Gespenst",
"dog": "Hundegesicht",
"happy_cat": "grinsende Katze mit lachenden Augen",
"scared_cat": "erschöpfte Katze",
"sad_cat": "weinende Katze",
"monkey_no_see": "Augen zuhaltendes Affengesicht",
"monkey_no_hear": "Ohren zuhaltendes Affengesicht",
"monkey_no_talk": "Mund zuhaltendes Affengesicht",
"builder": "Bauarbeiter(in)",
"princess": "Prinzessin",
"firefighter": "Feierwehrperson",
"mage": "Magier(in)",
"mermaid": "Wassermensch",
"fairy": "Märchenfee",
"letter_heart": "Liebesbrief",
"red_heart": "rotes Herz",
"two_hearts": "zwei Herzen",
"kiss": "Kussabdruck",
"hundred": "100 Punkte",
"explosion": "Explosion",
"drops": "Schweißtropfen",
"handshake": "Handschlag",
"hand_five_fingers": "Hand mit 5 gespreizten Fingern",
"hand_two_fingers": "Victory-Geste mit zwei Fingern",
"thumbs_up": "Daumen hoch",
"fist": "erhobene Faust",
"two_hands": "zwei offene Hände",
"writing": "schreibende Hand",
"praying": "zusammengelegte betende Handflächen",
"arm": "angespannter Arm",
"leg": "Bein",
"foot": "Fuß",
"ear": "Ohr",
"nose": "Nase",
"brain": "Gehirn",
"tooth": "Zahn",
"bone": "Knochen",
"eye": "Auge",
"tongue": "Zunge",
"mouth": "Mund",
"shirt": "T-Shirt",
"pants": "Jeans",
"dress": "Kleid",
"shoe": "Sportschuh",
"fencing": "Fechter(in)",
"horse_riding": "Pferderennen",
"ski": "Skifahrer(in)",
"rowing_boat": "Person im Ruderboot",
"swim": "Schwimmer(in)",
"surf": "Surfer(in)",
"gym": "Gewichtheber(in)",
"wrestling": "Ringer(in)",
"bike": "Radfahrer(in)",
"parachute": "Fallschirm",
"football": "Fußball",
"basketball": "Basketball",
"tennis": "Tennisball",
"ping_pong": "Tischtennis",
"martial": "Kampfsportanzug",
"lion": "Löwe",
"leopard": "Leopard",
"horse": "Pferdegesicht",
"zebra": "Zebra",
"pig": "Schwein",
"goat": "Ziege",
"sheep": "Schaf",
"camel": "Kamel",
"giraffe": "Giraffe",
"elephant": "Elefant",
"rhinoceros": "Nashorn",
"flamingo": "Flamingo",
"whale": "blasender Wal",
"dolphin": "Delfin",
"bear": "Bär",
"rooster": "Hahn",
"chick": "schlüpfendes Küken",
"eagle": "Adler",
"duck": "Ente",
"owl": "Eule",
"rabbit": "Hase",
"penguin": "Pinguin",
"lizard": "Eidechse",
"turtle": "Schildkröte",
"snake": "Schlange",
"hedgehog": "Igel",
"bat": "Fledermaus",
"fish": "Fisch",
"shell": "Schneckenhaus",
"octopus": "Oktopus",
"snail": "Schnecke",
"butterfly": "Schmetterling",
"ant": "Ameise",
"bee": "Biene",
"beetle": "Marienkäfer",
"rose": "Rose",
"sunflower": "Sonnenblume",
"fir": "Nadelbaum",
"palm_tree": "Palme",
"cactus": "Kaktus",
"clover": "Glücksklee",
"potted_plant": "Topfpflanze",
"bouquet": "Blumenstrauß",
"three_leaves": "Laub",
"mushroom": "Fliegenpilz",
"grapes": "Trauben",
"watermelon": "Wassermelone",
"lemon": "Zitrone",
"banana": "Banane",
"pineapple": "Ananas",
"apple": "roter Apfel",
"cherries": "Kirschen",
"strawberry": "Erdbeere",
"three_blueberries": "Blaubeeren",
"kiwi": "Kiwi",
"avocado": "Avocado",
"eggplant": "Aubergine",
"carrot": "Karotte",
"corn": "Maiskolben",
"pepper": "Peperoni",
"croissant": "Croissant",
"bread": "Baguette",
"pretzel": "Brezel",
"cheese": "Käsestück",
"pizza": "Pizza",
"egg": "Spiegelei in Bratpfanne",
"ice_cream": "Softeis",
"cookie": "Keks",
"cake": "Torte",
"chocolate": "Schokoladentafel",
"sweet": "Bonbon",
"coffee": "Heißgetränk",
"champagne_bottle": "Flasche mit knallendem Korken",
"glass_wine": "Weinglas",
"two_glasses": "Sektgläser",
"mountain": "schneebedeckter Berg",
"camping": "Camping",
"beach": "Strand mit Sonnenschirm",
"compass": "Kompass",
"museum": "antikes Gebäude",
"house": "Haus mit Garten",
"fountain": "Springbrunnen",
"circus": "Zirkuszelt",
"train": "Dampflokomotive",
"taxi": "Taxi",
"motorcycle": "Motorrad",
"sailboat": "Segelboot",
"airplane": "Flugzeug",
"helicopter": "Hubschrauber",
"rocket": "Rakete",
"sun": "Sonne",
"moon": "Mondsichel",
"planet": "Ringplanet",
"star": "weißer mittelgroßer Stern",
"night_sky": "Milchstraße",
"cloud": "Wolke mit Regen",
"umbrella": "Regenschirm im Regen",
"lightning": "Hochspannung",
"snowflake": "Schneeflocke",
"snowman": "Schneemann",
"thermometer": "Thermometer",
"fire": "Feuer",
"balloon": "Luftballon",
"kite": "Drachen",
"rainbow": "Regenbogen",
"guitar": "Gitarre",
"saxophone": "Saxofon",
"music": "Musiknote",
"painting": "Mischpalette",
"chess": "Bauer Schach",
"gift": "Geschenk",
"die": "Spielwürfel",
"puzzle": "Puzzleteil",
"teddy_bear": "Teddybär",
"firecracker": "Feuerwerkskörper",
"bullseye": "Darts",
"roller_skate": "Rollschuh",
"kick_scooter": "Tretroller",
"anchor": "Anker",
"scuba_diving": "Tauchmaske",
"broom": "Besen",
"magnifying_glass": "Lupe nach links",
"bulb": "Glühbirne",
"three_books": "Bücherstapel",
"package": "Paket",
"pencil": "Bleistift",
"pin": "Reißzwecke",
"paperclip": "Büroklammer",
"scissors": "Schere",
"key": "Schlüssel",
"lock": "offenes Schloss",
"chair": "Stuhl",
"bathtub": "Badewanne",
"sponge": "Schwamm",
"shopping_cart": "Einkaufswagen"
}
}
}

@ -0,0 +1,861 @@
{
"app": {
"sparql_update_editor" : {
"success": "SPARQL Update successful!"
}
},
"doc": {
"doc": "Document",
"protected_store": "Protected Profile",
"public_store": "Public Site",
"private_store": "Private Store",
"group_store": "Group Store",
"dialog_store": "Dialog Store",
"rich": "Post or Article",
"markdown": "Markdown",
"text": "Plain Text",
"social": "Social",
"group": "Group",
"pro": "Pro",
"media": "Media",
"chart": "Chart",
"viz": "Vizualitation",
"diagram": "Diagram",
"other": "Other file formats",
"data": "Data",
"code": "Code",
"apps": "Apps",
"new_app": "Create a new App",
"record_reel": "Record Reel",
"record_voice": "Record Audio",
"take_picture": "Take a picture",
"select_class": "Select a type of document to create",
"destination": {
"mc": "Keep in Magic Carpet",
"stream": "Send in current Stream",
"dialog": "Send in Dialog",
"store": "Save in current Store"
},
"not_found" : "Document not found",
"not_found_details_online" : "The document could not be found locally on this device, nor on the broker.",
"not_found_details_offline" : "The document could not be found locally on this device, and it seems like you are offline, so it could not be retrieved from any broker neither.<br/><br/>If you are opening this document for the first time on this device, you have to be online now so the document can be fetched.<br/><br/> We will try connecting and fetching it every 20 seconds.",
"cannot_load_offline": "You are offline and using the web app. There is currently a limitation on local storage within the Web App, and you need to connect to the broker every time you login with the Web App.<br/><br/>For now, the Web App does not keep a local copy of your documents. due to the limit of 5MB in localStorage. We will remove this limitation soon. Stay tuned!<br/><br/>Check your connectivity status in the ",
"header": {
"buttons": {
"edit": "Edit title, icon, intro",
"edit_profile": "Edit profile",
"bookmarked": "Saved",
"all_docs": "All Docs",
"groups": "Groups",
"channels": "Channels",
"inbox": "Inbox",
"chat": "Chat"
}
},
"file": {
"download": "Download",
"upload_progress": "Uploading...",
"upload": "Upload file"
},
"errors": {
"InvalidNuri": "Invalid NURI",
"no_session": "No active session"
},
"errors_details": {
"InvalidNuri": "The provided NextGraph URI is invalid",
"no_session": "Please refresh the page or close and reopen the app"
},
"graph" : "Graph",
"discrete" : "Document",
"menu" : {
"edit_with": "Edit with",
"view_as": "View as",
"install_app_to_edit": "Install App to edit",
"live_editing": "Live editing (real-time)",
"live_editing_description": "Collaborate in real-time with other users. If deactivated, you will have to save manually your edits",
"items": {
"new_block": {
"label": "New Block",
"desc": "Create a new block that can be embedded/included in this doc or other docs"
},
"editor_chat": {
"label": "Editors' Chat",
"desc": "Chat with other Editors about the Document you are editing"
},
"folders": {
"label": "Folders",
"desc": "Organize your documents in folders and platos"
},
"toc": {
"label": "Table of Content",
"desc": "Table of Content for Graph and Document"
},
"files": {
"label": "Attachments and Files",
"desc": "List all attached files and resources files, and download them if needed"
},
"share": {
"label": "Share",
"desc": "Share with others, or react"
},
"comments": {
"label": "Comments",
"desc": "See comments or annotations, replies, and write your own."
},
"branches": {
"label": "Branches",
"desc": "View, edit or fork branches and blocks"
},
"history": {
"label": "History",
"desc": "Open history of modifications, and travel back in time, to previous revisions"
},
"find": {
"label": "Find",
"desc": "Information about the document, statistics, and keywords"
},
"bookmark": {
"label": "Save",
"desc": "Keep a bookmark of this Document in your private store."
},
"annotate": {
"label": "Annotate",
"desc": "Write private annotations on this Document. Only you can see those annotations. You will also have an option to share them with others."
},
"info": {
"label": "Information",
"desc": "Information about the document, statistics, and keywords"
},
"repost": {
"label": "Post in my Stream",
"desc": "Post in your Stream (publish, repost, reply, boost)"
},
"dm": {
"label": "Send in Direct Message",
"desc": "Send as Direct Message to a contact or in a group chat"
},
"react": {
"label": "Like and React",
"desc": "Like, react, rate"
},
"author": {
"label": "Reply to Author(s)",
"desc": "Sends a private message to the author(s) in reply to this Document"
},
"quote": {
"label": "Quote/Mention in new Post",
"desc": "Write a Post that quotes/cites this Document"
},
"forward": {
"label": "Share via other apps",
"desc": "Share this document to users with other platforms and apps"
},
"link": {
"label": "Links",
"desc": "Get the links to access this Document on the web"
},
"qr": {
"label": "QR-Code",
"desc": "Display or download a QR-Code that links to this Document"
},
"download": {
"label": "Export and Download",
"desc": "Export to several formats and Download the file, or create a self-contained HTML file of this Document that works offline"
},
"notifs": {
"label": "Notifications",
"desc": "Change your notification settings for this Document"
},
"permissions": {
"label": "Permissions",
"desc": "See and change permissions and members for this Document"
},
"settings": {
"label": "Settings",
"desc": "Change settings for this Document"
},
"copy": {
"label": "Copy or Move",
"desc": "Copy, Move, Duplicate, Fork"
},
"embed": {
"label": "Embed",
"desc": "Embed this Document or Block into another Document, or insert a Block into this Document"
},
"schema": {
"label": "Schema",
"desc": "See and manage the Schema of this Document (Model, Ontology, Vocabulary)"
},
"signature": {
"label": "Signature",
"desc": "Obtain and verify the Signature of the Document to prove its authenticity. Or request a signature if there isn't any."
},
"translations": {
"label": "Translations",
"desc": "Manage the translations of your document in order to offer it in multiple languages"
},
"services": {
"label": "Services",
"desc": "See and invoke services available for this Document. Services can be read-only, or read-write"
},
"print": {
"label": "Print",
"desc": "Print or save to PDF"
},
"console": {
"label": "Console",
"desc": "Open a console and run interactive commands"
},
"source": {
"label": "Source",
"desc": "View and Download the source code of this Document"
},
"dev": {
"label": "Developer",
"desc": "Open the developer toolbox"
},
"tools": {
"label": "Tools",
"desc": "Select a tool"
},
"mc": {
"label": "Magic Carpet",
"desc": "Opens the Magic Carpet (like a clipboard, but better)"
},
"archive": {
"label": "Archive",
"desc": "Archive documents that you don't need anymore"
}
}
}
},
"pages": {
"not_found": {
"title": "Page Not Found",
"message": "The page you are looking for does not exist.<br/>It has probably not been implemented yet"
},
"user_panel": {
"title": "User Panel",
"personal": "Personal"
},
"user_registered": {
"success": "You have been successfully registered.",
"success_with_invitation": "You have been successfully registered to {invitation_name}."
},
"wallet_info": {
"title": "Wallet",
"scan_qr": "Scan QR to export",
"generate_qr": "Generate QR to export",
"download": "Download Wallet File",
"download_failed": "Download Failed:<br/>{error}",
"download_in_progress": "Download in progress...",
"download_successful": "You will find the file named \"{wallet_file}\" in your Downloads folder",
"download_file_button": "Click here to download the wallet file",
"remove_wallet": "Remove wallet from Device",
"remove_wallet_modal.title": "Remove wallet?",
"remove_wallet_modal.confirm": "Are you sure you want to remove this wallet from your device?",
"create_text_code": "Export by generating a TextCode",
"scan_qr.title": "Export by scanning a QR-Code",
"scan_qr.no_camera": "If to the contrary, the other device does not have a camera, ",
"scan_qr.other_has_camera": "If the other device where you want to import the Wallet, has a camera, then you can just click on the Back button and select <span class=\"path\">Generate QR to export</span>",
"scan_qr.notes": "You will now scan the QR-Code that appears on the screen of the other device (the one you want to transfer your wallet to). You will be asked to allow the app to use your camera.",
"scan_qr.scanner.title": "Scan your QR-Code",
"scan_qr.scanner.loading": "Loading scanner",
"scan_qr.syncing": "Synchronizing wallet",
"scan_qr.scan_successful": "Success!<br />Your wallet has been transferred to the other device.",
"gen_qr.title": "Export by generating a QR-Code",
"gen_qr.notes": "In order to transfer your wallet to another device, you will now display a QR-Code here on this device, and you will then scan it with the other device where you want to transfer your wallet to.",
"gen_qr.no_camera": "If the device where you want to import the Wallet, does not have a camera, then you will have to choose another method.",
"gen_qr.img_title": "Your Export QR-Code to Scan",
"gen_qr.img_alt": "Your Export QR-Code to Scan",
"gen_qr.gen_button": "Display QR-Code",
"gen_text_code.title": "Export with TextCode",
"gen_text_code.gen_btn": "Generate TextCode",
"gen_text_code.label": "Your TextCode:"
},
"settings": {
"title": "Settings"
},
"admin": {
"title": "Admin"
},
"accounts": {
"title": "Accounts"
},
"full_layout": {
"home": "Home",
"stream": "Stream",
"search": "Search",
"create": "Create",
"shared": "Shared",
"site": "Site",
"messages": "Messages",
"notifications": "Notifications"
},
"install": {
"app_availability": "<b>NextGraph App</b> is available for download as a native app for your mobile, tablet, laptop and desktop.<br /> The app supports iOS, Android, Linux, macOS, Windows, or any other platform with a modern browser.",
"has_wallet_warning": "A wallet is saved in this browser. If it is yours,<br /> once the installation of the app will be finished,<br /> choose the option \"Login\" on the app.<br /> (do not create another wallet from the app).",
"android_play_store": "Android Play Store",
"download_apk": "Download APK",
"ios_app_store": "iOS App Store",
"download_mac_os": "Download for MacOS",
"download_linux": "Download Linux Package",
"download_windows": "Download for Windows",
"other_platforms": "Other platforms"
},
"no_wallet": {
"welcome": "Welcome to NextGraph",
"description": "We could not find a wallet saved on this device.<br /> If you already have a wallet, select \"Log in\", otherwise, select \"Create Wallet\" here below.",
"create_wallet": "Create Wallet"
},
"login": {
"heading": "How to open your wallet? You have 2 options:",
"with_pazzle": "With your Pazzle",
"pazzle_steps": {
"1": "For each one of the 9 categories of images, you will be presented with the 15 possible image choices. The categories are shuffled at every login. They will not always appear in the same order.",
"2": "At each category, only one of the 15 displayed choices is the correct image that belongs to your pazzle. Find it and tap or click on that one. The 15 images are shuffled too, they will not appear at the same position at each login. On a computer, you can also use the tab key on your keyboard to move to the desired item on the screen, then press the space bar to select each one.",
"3": "Once you completed the last category, you will be presented with all the images you have previously selected. Their order is displayed as it was when you picked them. But this is not the correct order of the images in your pazzle. You now have to order them correctly.",
"4": "You must remember which image should be the first one in your pazzle. Find it on the screen and click or tap on it. It will be greyed out and the number 1 will appear on top of it.",
"5": "Move on to the second image of your pazzle (that you memorized). Find it on the screen and tap on it. Repeat this step until you reached the last image.",
"6": "Finally, you are asked for your PIN code. Enter it by clicking or tapping on the digits."
},
"with_mnemonic": "With your 12 words Mnemonic (passphrase)",
"mnemonic_steps": {
"1": "Enter your twelve words mnemonic in the input field. The words must be separated by spaces.",
"2": "Enter the PIN code that you chose when you created your wallet."
},
"trust_device_allow_cookies": "By selecting this option, you agree to saving some cookies on your browser.",
"trust_device_yes": "Yes, save my wallet on this device",
"device_name_label": "Name of this device. You can edit it now",
"device_name_placeholder": "Enter name of this device",
"loading_pazzle": "Loading Pazzle",
"open_with_pazzle": "Open With Pazzle",
"login_cancel": "Cancel Login",
"open_with_mnemonic": "Open with Mnemonic instead",
"enter_mnemonic": "Enter your 12 words mnemonic",
"mnemonic_placeholder": "12 words separated by spaces",
"select_emoji": "Select your image for category:<br />{category}",
"order_emojis": "Select each image in the correct order",
"enter_pin": "Enter your PIN code",
"opening_wallet": "Opening your wallet...<br /> Please wait",
"wallet_opened": "Your wallet is opened! <br />Please wait while the app is loading...",
"qr_code": "Wallet QRCode",
"qr_modal_title": "My Wallet QRCode",
"qr_modal_description": "Use this QRCode to log in with your wallet on new devices.",
"keep_wallet": "Save to Device for Future Logins"
},
"account_info": {
"title": "Accounts Info",
"site": "{name} account",
"devices": "Devices",
"brokers": "Brokers",
"no_brokers_connected": "No brokers connected"
},
"wallet_create": {
"redirecting_to_registration_page": "Redirecting to the Broker Service Provider registration page",
"complete_in_popup": "Complete the registration in the popup window",
"own_your_ngbox": "Own your NG-Box",
"self-host_broker": "Self-host a broker",
"tos_ng_one": "Terms of Service NextGraph.one",
"wallet_description": "A <b>NextGraph Wallet</b> is unique to each person. It stores your credentials and authorizations to access documents. You need one in order to start using NextGraph.<br /><br />If you already have a wallet, you should not create a new one. Instead, <a href=\"#/wallet/login\" >login here with your existing wallet.</a > If you never created a NextGraph Wallet before, please follow the instructions below in order to create your unique personal wallet.",
"has_wallet": "Some wallets are saved on this device,<br /> to log in with one of them, <a href=\"#/wallet/login\" use:link>click here.</a>",
"wallet_about": {
"title": "What is a wallet?",
"please_read": "Please read",
"1": "Your wallet is a secure and encrypted small file that contains some important information that only you should have access to.",
"2": "In your wallet, we store all the permissions to access documents you have been granted with, or that you have created yourself.",
"3": "In order to open it, you will need to enter your <b >pazzle</b > and a <b>PIN code</b> of 4 digits. Your personal pazzle (contraction of puzzle and password) is composed of 9 images you should remember. The order of the images is important too.",
"4": "Don't worry, it is easier to remember 9 images than a password like \"69$g&ms%C*%\", and it has the same strength as a complex password. The entropy of your pazzle is <b >66bits</b >, which is considered very high by all standards.",
"5": "You should only create <b>one unique wallet for yourself</b >. All your accounts, identities and permissions will be added to this unique wallet later on. Do not create another wallet if you already have one. Instead, you will <b>import</b> your unique existing wallet in all the apps and websites where you need it.",
"6": "Your wallet can be transferred from one device to another with the help of a small file that you download, or with a QRcode, or with a TextCode that you copy/paste. In any case, you should never share this file or QRcode with anybody else.",
"7": "We at NextGraph will never see the content of your wallet. It is encrypted and we do not know your pazzle, so we cannot see what is inside.",
"8": "For the same reason, we won't be able to help you if you forget your pazzle or PIN code, or if you loose the wallet file. <span class=\"text-bold\"> There is no \"password recovery\" option</span > in this case. You can note your pazzle down on a piece of paper until you remember it, but don't forget to destroy this note after a while."
},
"create_wallet_now": "I create my wallet now!",
"select_server": "NextGraph is based on an efficient decentralized P2P network, and in order to join this network and start using the app, you need to first select a <b>broker&nbsp;server</b>.",
"broker_about": {
"title": "What is a broker?",
"please_read": "Please read",
"1": "The broker helps you keep all your data in <b>sync</b>, as it is connected to the internet 24/7 and keeps a copy of the updates for you. This way, even if the devices of the other participants are offline, you can still see their changes",
"2": "All your data is secure and <b>end-to-end encrypted</b>, and the broker cannot see the content of the documents as it does not have the keys to decrypt them.",
"3": "The broker helps you enforce your <b>privacy</b> as it hides your internet address (IP) from other users you share documents with.",
"4": "It will be possible in the future to use NextGraph without any broker and to have direct connections between peers, but this will imply a less smooth experience.",
"5": "At anytime you can decide to switch to another broker service provider or host it yourself. Your data is totally <b >portable</b > and can freely move to another broker.",
"6": "Soon we will offer you the opportunity to host your own broker at <b>home</b> or <b>office</b>. Instead of using a \"broker service provider\", you will own a small device that you connect behind your internet router. It is called <b>NG Box</b> and will be available soon.",
"7": "Organizations and companies have the opportunity to host a broker <b>on-premise</b> or in the <b>cloud</b>, as the software is open source. Individuals can also <b>self-host</b> a broker on any VPS server or at home, on their dedicated hardware."
},
"choose_broker": "Please choose one broker among the list",
"register_with_broker": "Register with {broker}",
"for_eu_citizens": "European Union Server",
"for_rest": "For the rest of the world",
"enter_invite_link": "Enter an invitation link",
"scan_invite_qr": "Scan an invitation QR-code",
"self_hosted_broker": "Self-hosted broker",
"ng_box": "NG Box (owned or invited)",
"registration_success": "You have been successfully registered to {broker}",
"choose_pin": {
"title": "Let's start creating your wallet by choosing a PIN code",
"description": "We recommend you to choose a PIN code that you already know very well. <br /> Your credit card PIN, by example, is a good choice.<br />We at NextGraph will never see your PIN.",
"rules": "Here are the rules for the PIN:",
"1": "It cannot be a series like 1234 or 8765.",
"2": "The same digit cannot repeat more than once. By example 4484 is invalid.",
"3": "Try to avoid birth dates, last digits of phone numbers, or zip codes"
},
"chosen_pin": "You have chosen:",
"confirm_pin": "Please confirm your PIN code.",
"confirm_pin_description": "Enter the same PIN again.",
"pin_confirmed_as": "You PIN is confirmed as: ",
"choose_security_phrase_and_image": {
"title": "Now let's enter a security phrase and a security image",
"description": "As a verification step, this phrase and image will be presented to you every time you are about to enter your pazzle and PIN in order to unlock your wallet.<br /> This security measure will prevent you from entering your pazzle and PIN on malicious sites and apps.",
"warning": "Every time you will use your wallet, if you do not see and recognize your own security phrase and image before entering your pazzle, please stop and DO NOT enter your pazzle, as you would be the victim of a phishing attempt.",
"rules_title": "Here are the rules for the security phrase and image:",
"1": "The phrase should be at least 10 characters long",
"2": "It should be something you will remember, but not something too personal.",
"3": "Do not enter your full name, nor address, nor phone number.",
"4": "Instead, you can enter a quote, a small sentence that you like, or something meaningless to others, but unique to you.",
"5": "The image should be minimum 150x150px. There is no need to provide more than 400x400px as it will be scaled down anyway.",
"6": "We accept several formats like JPEG, PNG, GIF, WEBP and more.",
"7": "The image should be unique to you. But it should not be too personal neither.",
"8": "Do not upload your face picture, this is not a profile pic.",
"9": "The best would be a landscape you like or any other picture that you will recognize as unique.",
"10": "Please be aware that other people who are sharing this device with you, will be able to see this image and phrase."
},
"type_security_phrase_placeholder": "Type a security phrase...",
"save_security_phrase_and_image": "Save security phrase & image",
"tap_to_upload": "Tap to upload an image",
"click_to_upload": "Click to select an image",
"or_drag_drop": "or drag and drop",
"pins_no_match": "You didn't enter the same PIN twice",
"almost_done": "We are almost done!",
"save_wallet_options": {
"description": "There are 2 options to choose before we can create your wallet.",
"trust": "Do you trust this device?",
"trust_description": "If you do, if this device is yours, or it is used by a few trusted persons of your family or workplace, and you would like to login again from this device in the future, then you can save your wallet on this device. To the contrary, if this device is public and shared by strangers, do not save your wallet here.",
"trust_toggle": "Yes, save my wallet on this device",
"device_name_description": "To see which devices you are connected with, every device should have a name (e.g. Bob's laptop). Please enter it here.",
"cloud": "Keep a copy in the cloud?",
"cloud_description": "Are you afraid that you will loose the file containing your wallet? If this would happen, your wallet would be lost forever, together with all your documents. We can keep an encrypted copy of your wallet in our cloud. Only you will be able to download it with a special link. You would have to keep this link safely though. By selecting this option, you agree to the",
"cloud_toggle": "Save my wallet in the cloud?",
"cloud_tos": "Terms of Service of our cloud",
"pdf": "Should we create a PDF file of your wallet?",
"pdf_description": "We can prepare for you a PDF file containing all the information of your wallet, unencrypted. In the next screen, once your wallet is ready, you will be able to download such PDF file. You should print this file and then delete the PDF (and empty the trash). Keep this printed document in a safe place. It contains all the information to regenerate your wallet in case you lost access to it.",
"pdf_toggle": "Yes, create a PDF of my wallet",
"link": "Create a link to access your wallet easily?",
"link_description": "When you want to use your wallet on the web or from other devices, we can help you find your wallet by creating a simple link accessible from anywhere. Only you will have access to this link. In order to do so, we will keep your wallet ID and some information about your broker on our cloud servers. If you prefer to opt out, just uncheck this option. By selecting this option, you agree to the",
"link_toggle": "Create a link to my wallet?"
},
"lets_create": "Let's create this wallet",
"creating": "Your wallet is being created",
"ready": "Your wallet is ready!",
"download_wallet_description": "Please download your wallet and keep it in a safe location",
"download_wallet": "Download my wallet",
"download_wallet_done": "Your wallet file has been downloaded into your \"Downloads\" folder, with the name<br /><span class=\"text-black\"> {download_name}</span ><br /> <span class=\"font-bold\" >Please move it to a safe and durable place.</span ><br />",
"your_pazzle": "Here below is your Pazzle. <br /> The <span class=\"font-bold\">order</span> of each image is <span class=\"font-bold\">important</span>!",
"your_mnemonic": "And here is your mnemonic (your alternative passphrase):",
"unlock_tips_1": "You can use both the pazzle or the mnemonic to unlock your wallet. The pazzle is easier to remember. The mnemonic is useful in some special cases. We recommend that you use the pazzle. <span class=\"font-bold text-xl\" >Copy both on a piece of paper.</span > You should try to memorize the pazzle. Once you did, you won't need the paper anymore.",
"unlock_tips_2": "Now click on \"Continue to Login\" and select your new wallet.",
"unlock_tips_3": "It is important that you <span class=\"font-bold\">login</span> with this wallet <span class=\"font-bold\">at least once</span> from this {platform, select, browser {browser tab} other {device}}, while connected to the internet, so your personal site can be created on your broker.",
"continue_to_login": "Continue to Login",
"continue_confirm": "Did you write down your login credentials?",
"continue_confirm_description": "The pazzle and the mnemonic <span class=\"font-bold\"> will not be shown to you again</span >. Please make sure, you have written them down.",
"continue_confirm_go_back": "Take me back",
"continue_confirm_yes": "Yes, I did"
},
"wallet_login": {
"select_wallet": "Select a wallet to login with",
"from_import.title": "Your wallet has been transferred",
"from_import.description": "Your wallet has been received from the other device!",
"from_import.instruction": "To finish the import, please log in.",
"with_another_wallet": "Log in with another wallet",
"import_wallet": "Import your wallet",
"import_file": "Import a Wallet File",
"import_qr": "Import with QR-Code",
"import_link": "Import with TextCode",
"new_wallet": "Create a new Wallet",
"logged_in": "You are logged in.<br /> please wait while the app is loading"
},
"wallet_login_qr": {
"title": "Import Wallet with QR-Code",
"scan.description": "To import your wallet from another device, generate an export QR-Code there. On the other device, go to<br /><span class=\"path\">User Panel > Wallet > Generate QR</span> to export.",
"scan.modal.title": "Scan Wallet QR-Code",
"gen.button": "Generate QR-Code",
"gen.description": "To import your wallet from another device, you have to generate a QR-Code here on this device, and then scan it with your other device (the one where your wallet is located for now).<br/>If your other device does not have a camera, then you have to use another method for importing your wallet here.",
"offline_advice": "If you do not have internet on this device, you can use the \"Import a Wallet file\" method instead.",
"gen.letsgo": "Ready? On your other device, you first have to be logged-in (wallet is opened) and then you go to<br /><span class=\"path\">User Panel > Wallet > Scan QR to export</span>.<br />Then on this present device, click below on the<br/><span class=\"path\">Generate QR-Code</span> button.",
"gen.generated": "Scan this QR-Code from the other device.<br/>You have 5 minutes to do so.",
"success_btn": "Continue to Login"
},
"wallet_login_textcode": {
"title": "Import Wallet from TextCode",
"description": "To generate a TextCode, open a logged in device and go to<br /><span class=\"path\">User Panel > Wallet > Export by generating a TextCode</span>.",
"import_btn": "Import with TextCode",
"enter_here": "Enter your TextCode here below and click on Import button"
},
"scan_qr": {
"scanning": "Scanning the QR-Code"
}
},
"buttons": {
"ok": "Ok",
"go_back": "Go back",
"back": "Back",
"confirm": "Confirm",
"cancel": "Cancel",
"remove": "Remove",
"correct": "Correct",
"try_again": "Try again",
"back_to_homepage": "Go back to Homepage",
"logout": "Logout",
"login": "Login",
"start_over": "Start over",
"scan_qr": "Scan QR-Code"
},
"errors": {
"an_error_occurred": "An error occurred",
"error_occurred": "An error occurred:<br />{message}",
"AlreadyExists": "The user is already registered with the selected broker.<br />Try logging in instead.",
"InvalidSignature": "The signature is invalid.",
"IncompleteSignature": "The signature is incomplete.",
"SerializationError": "The data could not be serialized.",
"EncryptionError": "Your wallet could not be opened. You probably did a mistake.",
"DecryptionError": "Error with decryption.",
"InvalidValue": "The value is invalid.",
"ConnectionNotFound": "The connection was not found.",
"InvalidKey": "The key is invalid.",
"InvalidInvitation": "The invitation is invalid.",
"InvalidCreateAccount": "An error occurred creating the account.",
"InvalidFileFormat": "The file format is invalid.",
"InvalidArgument": "The parameter is invalid.",
"PermissionDenied": "Permission denied.",
"InvalidPazzle": "The pazzle is not correct.",
"InvalidMnemonic": "The mnemonic is not correct.",
"CommitLoadError": "Error loading commit.",
"ObjectParseError": "Error parsing object.",
"StorageError": "Storage error.",
"NotFound": "Not Found",
"JsStorageKeyNotFound": "Could not find JavaScript storage key.",
"IoError": "Input/Output error.",
"CommitVerifyError": "Verifying commit failed.",
"LocalBrokerNotInitialized": "The local broker is not initialized.",
"JsStorageReadError": "Could not read from JavaScript Storage.",
"JsStorageWriteError": "Could not write to JavaScript Storage.",
"CannotSaveWhenInMemoryConfig": "Saving while configured for In-Memory is not possible.",
"WalletNotFound": "The wallet was not found.",
"WalletAlreadyAdded": "The wallet is already added.",
"WalletAlreadyOpened": "The wallet is already open.",
"WalletError": "Error with wallet.",
"BrokerError": "Error with broker.",
"SessionNotFound": "The session cannot be found.",
"SessionAlreadyStarted": "The session is already started.",
"RepoNotFound": "The repository cannot be found.",
"BranchNotFound": "The branch cannot be found.",
"StoreNotFound": "The store cannot be found.",
"UserNotFound": "The user cannot be found.",
"TopicNotFound": "The topic cannot be found.",
"NotConnected": "You are not connected.",
"ActorError": "Actor error.",
"ProtocolError": "Protocol error.",
"ServerError": "Server error.",
"InvalidResponse": "Received an invalid response.",
"BootstrapError": "Error while bootstrapping",
"NotAServerError": "Not a server.",
"VerifierError": "Error during verification.",
"SiteNotFoundOnBroker": "The site cannot be found on the broker",
"BrokerConfigErrorStr": "{error}",
"BrokerConfigError": "Error in the broker configuration.",
"MalformedEvent": "The event has an invalid format.",
"InvalidPayload": "The payload is invalid.",
"WrongUploadId": "The upload ID is incorrect.",
"FileError": "Error with file.",
"InternalError": "Internal Error",
"OxiGraphError": "Error in OxiGraph database.",
"ConfigError": "Error in configuration.",
"LocalBrokerIsHeadless": "The local broker is headless.",
"LocalBrokerIsNotHeadless": "The local broker is not headless.",
"InvalidNuri": "Invalid NextGraph URI.",
"InvalidTarget": "Cannot resolve target.",
"ExportWalletTimeOut": "The wallet-export session has expired. Try again.",
"ConnectionError": "Could not connect to the server.",
"IncompatibleQrCode": "You scanned a NextGraph QR-Code that is of the wrong type.",
"NotARendezVous": "You scanned an invalid QR-Code.",
"camera_unavailable": "Camera is unavailable.",
"ServerAlreadyRunningInOtherProcess": "App is already running on this device. Check it and close it.",
"cannot_load_this_file": "Cannot load this file",
"graph_not_found": "Graph not found"
},
"connectivity": {
"stopped": "Stopped",
"personal": "Personal",
"connecting": "Connecting",
"connected": "Connected",
"loading": "Loading",
"connection_error_short": "{error}",
"online": "Online",
"offline": "Offline"
},
"common": {
"version": "Version: {version}",
"logo": "NextGraph Logo",
"support_nextgraph": "Support NextGraph",
"about_nextgraph": "About NextGraph",
"donate_nextgraph": "Donate to NextGraph"
},
"wallet_sync": {
"offline_warning": "You cannot transfer your wallet when offline.<br />Please make sure you are connected to the internet first.",
"textcode.usage_warning": "You have to exchange this TextCode with the other device by any means available to you (email, other messenger apps, etc...). It is highly recommended to use a tool that is end-to-end encrypted. If you can, you should use the \"Import with QR-Code\" option instead, as it is safer and simpler. If your devices are not connected to the internet, then you can use the \"Import a Wallet File\" option. In this case, you will transfer the wallet file with a USB key, from one device to the other, or for mobile, by connecting your mobile with the USB cable, to the computer, and then transferring the file with the File Transfer utility on Android, or AirDrop/Finder/iTunes on an iPhone/mac/PC. We do not recommend uploading your wallet file to any cloud service.",
"server_transfer_notice": "Both devices need to be online.<br />During this wallet transfer, your wallet will be temporarily and securely stored on our servers for up to 5 minutes, using two levels of encryption.<br/> We at NextGraph will never be able to read your wallet, your PIN, your pazzle nor your mnemonic.",
"importing": "Importing wallet",
"expiry": "The TextCode will be valid for 5 minutes.",
"error": "An error occurred while synchronizing your wallet:<br />{error}",
"no_camera": "Unfortunately, your device does not have a camera.<br /> You cannot scan any QR-Code.<br /> In order to export your wallet to another device, you will have to use another method.",
"no_camera_alternatives": "You have 2 other options: \"Import a Wallet file\" (transfer it using a USB key by example, useful if you are offline) or \"Import a TextCode\" (which is a text you will have to transfer with another messaging app)."
},
"emojis": {
"category": {
"face": "Face",
"face_unwell": "Bad Face",
"face_costume": "Costumed",
"emotion": "Emotion",
"body": "Body",
"sport": "Sport",
"bigger_animal": "Big Animal",
"smaller_animal": "Small Animal",
"plants": "Plants and Insects",
"fruits": "Fruits and Veggies",
"food": "Food and Drinks",
"travel": "Travel",
"sky": "Sky and Weather",
"play": "Leisure",
"house": "Household"
},
"codes": {
"COMMENT": "YOU CAN FIND I18N UNICODE DESCRIPTIONS AT https://github.com/unicode-org/cldr-json",
"happy": "happy face",
"happy_tears": "face with tears of joy",
"halo": "smiling face with halo ring",
"three_hearts": "face with 3 hearts",
"with_two_hearts": "face with 2 heart-eyes",
"one_heart": "face blowing one heart kiss",
"with_tongue": "face with tongue",
"with_two_hands": "face with two hands",
"one_hand": "face with hand over mouth",
"silenced": "silenced zipper-mouth face",
"celebrating": "partying face",
"sunglasses": "face with sunglasses",
"eyes_up": "face with rolling eyes up",
"monocle": "face with monocle",
"sleeping": "sleeping face",
"mask": "face with medical mask",
"fever": "fever face with thermometer",
"bandage": "face with head-bandage",
"vomit": "face vomiting",
"tissue": "tissue sneezing face",
"hot": "red face with sweat",
"cold": "cold face",
"crossed_eyes": "face with crossed-out eyes",
"exploding": "exploding head",
"sad": "frowning sad face",
"long_nose": "long nose lying face",
"many_tears": "loudly crying face",
"fear": "face screaming in fear",
"tired": "tired yawning face",
"annoyed": "annoyed face with steam from nose",
"clown": "clown face",
"ghost": "ghost",
"dog": "dog face",
"happy_cat": "happy cat with smiling eyes",
"scared_cat": "weary scared cat",
"sad_cat": "crying cat",
"monkey_no_see": "see-no-evil monkey",
"monkey_no_hear": "hear-no-evil monkey",
"monkey_no_talk": "speak-no-evil monkey",
"builder": "construction worker",
"princess": "princess",
"firefighter": "firefighter",
"mage": "mage",
"mermaid": "mermaid",
"fairy": "fairy",
"letter_heart": "love letter",
"red_heart": "red heart",
"two_hearts": "two hearts",
"kiss": "kiss mark",
"hundred": "hundred points",
"explosion": "explosion",
"drops": "sweat droplets",
"handshake": "handshake",
"hand_five_fingers": "hand with 5 fingers splayed",
"hand_two_fingers": "victory hand with 2 fingers",
"thumbs_up": "thumbs up",
"fist": "raised fist",
"two_hands": "two open hands",
"writing": "writing hand",
"praying": "praying folded hands",
"arm": "flexed arm",
"leg": "leg",
"foot": "foot",
"ear": "ear",
"nose": "nose",
"brain": "brain",
"tooth": "tooth",
"bone": "bone",
"eye": "eye",
"tongue": "tongue",
"mouth": "mouth",
"shirt": "t-shirt",
"pants": "pants",
"dress": "dress",
"shoe": "running shoe",
"fencing": "fencing",
"horse_riding": "horse riding",
"ski": "skier",
"rowing_boat": "rowing boat",
"swim": "swimming",
"surf": "surfing",
"gym": "lifting weights",
"wrestling": "two people wrestling",
"bike": "biking",
"parachute": "parachute",
"football": "football",
"basketball": "basketball",
"tennis": "tennis",
"ping_pong": "ping pong",
"martial": "judo uniform kimono",
"lion": "lion",
"leopard": "leopard",
"horse": "horse face",
"zebra": "zebra",
"pig": "pig",
"goat": "goat",
"sheep": "sheep",
"camel": "camel",
"giraffe": "giraffe",
"elephant": "elephant",
"rhinoceros": "rhinoceros",
"flamingo": "flamingo",
"whale": "whale",
"dolphin": "dolphin",
"bear": "bear",
"rooster": "rooster",
"chick": "hatching chick",
"eagle": "eagle",
"duck": "duck",
"owl": "owl",
"rabbit": "rabbit",
"penguin": "penguin",
"lizard": "lizard",
"turtle": "turtle",
"snake": "snake",
"hedgehog": "hedgehog",
"bat": "bat",
"fish": "fish",
"shell": "shell",
"octopus": "octopus",
"snail": "snail",
"butterfly": "butterfly",
"ant": "ant",
"bee": "honeybee",
"beetle": "beetle",
"rose": "rose",
"sunflower": "sunflower",
"fir": "evergreen fir tree",
"palm_tree": "palm tree",
"cactus": "cactus",
"clover": "four leaf clover",
"potted_plant": "potted plant",
"bouquet": "bouquet",
"three_leaves": "three fallen leaf",
"mushroom": "mushroom",
"grapes": "grapes",
"watermelon": "watermelon",
"lemon": "lemon",
"banana": "banana",
"pineapple": "pineapple",
"apple": "red apple",
"cherries": "cherries",
"strawberry": "strawberry",
"three_blueberries": "three blueberries",
"kiwi": "kiwi",
"avocado": "avocado",
"eggplant": "eggplant",
"carrot": "carrot",
"corn": "corn",
"pepper": "red hot pepper",
"croissant": "croissant",
"bread": "baguette bread",
"pretzel": "pretzel",
"cheese": "cheese",
"pizza": "slice of pizza",
"egg": "fried egg",
"ice_cream": "ice cream",
"cookie": "cookie",
"cake": "piece of cake",
"chocolate": "chocolate",
"sweet": "candy",
"coffee": "coffee",
"champagne_bottle": "champagne bottle",
"glass_wine": "wine glass",
"two_glasses": "two glasses of cava",
"mountain": "mountain",
"camping": "camping",
"beach": "beach",
"compass": "compass",
"museum": "museum",
"house": "house",
"fountain": "fountain",
"circus": "circus",
"train": "train",
"taxi": "taxi",
"motorcycle": "motorcycle",
"sailboat": "sailboat",
"airplane": "airplane",
"helicopter": "helicopter",
"rocket": "rocket",
"sun": "sun",
"moon": "moon",
"planet": "planet",
"star": "star",
"night_sky": "night sky",
"cloud": "cloud with rain",
"umbrella": "umbrella with rain drops",
"lightning": "lightning",
"snowflake": "snowflake",
"snowman": "snowman",
"thermometer": "thermometer",
"fire": "fire",
"balloon": "balloon",
"kite": "kite",
"rainbow": "rainbow",
"guitar": "guitar",
"saxophone": "saxophone",
"music": "musical note",
"painting": "painting",
"chess": "chess",
"gift": "gift",
"die": "game die",
"puzzle": "puzzle",
"teddy_bear": "teddy bear",
"firecracker": "firecracker",
"bullseye": "bullseye",
"roller_skate": "roller skate",
"kick_scooter": "kick scooter",
"anchor": "anchor",
"scuba_diving": "scuba diving",
"broom": "broom",
"magnifying_glass": "magnifying glass",
"bulb": "light bulb",
"three_books": "three books",
"package": "package",
"pencil": "pencil",
"pin": "pin",
"paperclip": "paperclip",
"scissors": "scissors",
"key": "key",
"lock": "lock",
"chair": "chair",
"bathtub": "bathtub",
"sponge": "sponge",
"shopping_cart": "shopping cart"
}
}
}

@ -0,0 +1,231 @@
{
"emojis": {
"codes": {
"happy": "cara sonriendo",
"happy_tears": "cara llorando de risa",
"halo": "cara sonriendo con aureola",
"three_hearts": "cara sonriendo con corazones",
"with_two_hearts": "cara sonriendo con ojos de corazón",
"one_heart": "cara lanzando un beso",
"with_tongue": "cara con ojos cerrados y lengua fuera",
"with_two_hands": "cara con manos abrazando",
"one_hand": "cara con mano sobre la boca",
"silenced": "cara con la boca cerrada con cremallera",
"celebrating": "cara de fiesta",
"sunglasses": "cara sonriendo con gafas de sol",
"eyes_up": "cara con ojos en blanco",
"monocle": "cara con monóculo",
"sleeping": "cara durmiendo",
"mask": "cara con mascarilla médica",
"fever": "cara con termómetro",
"bandage": "cara con la cabeza vendada",
"vomit": "cara vomitando",
"tissue": "cara estornudando",
"hot": "cara con calor",
"cold": "cara con frío",
"crossed_eyes": "cara mareada",
"exploding": "cabeza explotando",
"sad": "cara con el ceño fruncido",
"long_nose": "cara de mentiroso",
"many_tears": "cara llorando fuerte",
"fear": "cara gritando de miedo",
"tired": "cara de bostezo",
"annoyed": "cara resoplando",
"clown": "cara de payaso",
"ghost": "fantasma",
"dog": "cara de perro",
"happy_cat": "gato sonriendo con ojos sonrientes",
"scared_cat": "gato asustado",
"sad_cat": "gato llorando",
"monkey_no_see": "mono con los ojos tapados",
"monkey_no_hear": "mono con los oídos tapados",
"monkey_no_talk": "mono con la boca tapada",
"builder": "profesional de la construcción",
"princess": "princesa",
"firefighter": "persona adulta",
"mage": "persona maga",
"mermaid": "persona sirena",
"fairy": "hada",
"letter_heart": "carta de amor",
"red_heart": "corazón rojo",
"two_hearts": "dos corazones",
"kiss": "marca de beso",
"hundred": "cien puntos",
"explosion": "colisión",
"drops": "gotas de sudor",
"handshake": "apretón de manos",
"hand_five_fingers": "mano abierta",
"hand_two_fingers": "mano con señal de victoria",
"thumbs_up": "pulgar hacia arriba",
"fist": "puño en alto",
"two_hands": "manos abiertas",
"writing": "mano escribiendo",
"praying": "manos en oración",
"arm": "bíceps flexionado",
"leg": "pierna",
"foot": "pie",
"ear": "oreja",
"nose": "nariz",
"brain": "cerebro",
"tooth": "diente",
"bone": "hueso",
"eye": "ojo",
"tongue": "lengua",
"mouth": "boca",
"shirt": "camiseta",
"pants": "vaqueros",
"dress": "vestido",
"shoe": "zapatilla deportiva",
"fencing": "persona haciendo esgrima",
"horse_riding": "carrera de caballos",
"ski": "persona esquiando",
"rowing_boat": "persona remando en un bote",
"swim": "persona nadando",
"surf": "persona haciendo surf",
"gym": "persona levantando pesas",
"wrestling": "personas luchando",
"bike": "persona en bicicleta",
"parachute": "paracaídas",
"football": "balón de fútbol",
"basketball": "balón de baloncesto",
"tennis": "pelota de tenis",
"ping_pong": "tenis de mesa",
"martial": "uniforme de artes marciales",
"lion": "león",
"leopard": "leopardo",
"horse": "cara de caballo",
"zebra": "cebra",
"pig": "cerdo",
"goat": "cabra",
"sheep": "oveja",
"camel": "dromedario",
"giraffe": "jirafa",
"elephant": "elefante",
"rhinoceros": "rinoceronte",
"flamingo": "flamenco",
"whale": "ballena soltando un chorro",
"dolphin": "delfín",
"bear": "oso",
"rooster": "gallo",
"chick": "pollito rompiendo el cascarón",
"eagle": "águila",
"duck": "pato",
"owl": "búho",
"rabbit": "conejo",
"penguin": "pingüino",
"lizard": "lagarto",
"turtle": "tortuga",
"snake": "serpiente",
"hedgehog": "erizo",
"bat": "murciélago",
"fish": "pez",
"shell": "caracola",
"octopus": "pulpo",
"snail": "caracol",
"butterfly": "mariposa",
"ant": "hormiga",
"bee": "abeja",
"beetle": "mariquita",
"rose": "rosa",
"sunflower": "girasol",
"fir": "árbol de hoja perenne",
"palm_tree": "palmera",
"cactus": "cactus",
"clover": "trébol de cuatro hojas",
"potted_plant": "planta de maceta",
"bouquet": "ramo de flores",
"three_leaves": "hojas caídas",
"mushroom": "champiñón",
"grapes": "uvas",
"watermelon": "sandía",
"lemon": "limón",
"banana": "plátano",
"pineapple": "piña",
"apple": "manzana roja",
"cherries": "cerezas",
"strawberry": "fresa",
"three_blueberries": "arándanos",
"kiwi": "kiwi",
"avocado": "aguacate",
"eggplant": "berenjena",
"carrot": "zanahoria",
"corn": "espiga de maíz",
"pepper": "chile picante",
"croissant": "cruasán",
"bread": "baguete",
"pretzel": "bretzel",
"cheese": "cuña de queso",
"pizza": "pizza",
"egg": "cocinar",
"ice_cream": "cucurucho de helado",
"cookie": "galleta",
"cake": "trozo de tarta",
"chocolate": "tableta de chocolate",
"sweet": "caramelo",
"coffee": "bebida caliente",
"champagne_bottle": "botella descorchada",
"glass_wine": "copa de vino",
"two_glasses": "copas brindando",
"mountain": "montaña con nieve",
"camping": "camping",
"beach": "playa y sombrilla",
"compass": "brújula",
"museum": "edificio clásico",
"house": "casa con jardín",
"fountain": "fuente",
"circus": "carpa de circo",
"train": "locomotora de vapor",
"taxi": "taxi",
"motorcycle": "moto",
"sailboat": "velero",
"airplane": "avión",
"helicopter": "helicóptero",
"rocket": "cohete",
"sun": "sol",
"moon": "luna",
"planet": "planeta con anillos",
"star": "estrella",
"night_sky": "Vía Láctea",
"cloud": "nube con lluvia",
"umbrella": "paraguas con gotas de lluvia",
"lightning": "alto voltaje",
"snowflake": "copo de nieve",
"snowman": "muñeco de nieve",
"thermometer": "termómetro",
"fire": "fuego",
"balloon": "globo",
"kite": "cometa",
"rainbow": "arcoíris",
"guitar": "guitarra",
"saxophone": "saxofón",
"music": "nota musical",
"painting": "paleta de pintor",
"chess": "peón de ajedrez",
"gift": "regalo",
"die": "dado",
"puzzle": "pieza de puzle",
"teddy_bear": "osito de peluche",
"firecracker": "petardo",
"bullseye": "diana",
"roller_skate": "patines",
"kick_scooter": "patinete",
"anchor": "ancla",
"scuba_diving": "máscara de buceo",
"broom": "escoba",
"magnifying_glass": "lupa orientada hacia la izquierda",
"bulb": "bombilla",
"three_books": "libros",
"package": "paquete",
"pencil": "lápiz",
"pin": "chincheta",
"paperclip": "clip",
"scissors": "tijeras",
"key": "llave",
"lock": "candado abierto",
"chair": "silla",
"bathtub": "bañera",
"sponge": "esponja",
"shopping_cart": "carrito de la compra"
}
}
}

@ -0,0 +1,231 @@
{
"emojis": {
"codes": {
"happy": "visage rieur",
"happy_tears": "visage riant aux larmes",
"halo": "visage souriant avec auréole",
"three_hearts": "visage souriant avec cœurs",
"with_two_hearts": "visage souriant avec yeux en forme de cœur",
"one_heart": "visage envoyant un bisou",
"with_tongue": "visage qui tire la langue les yeux plissés",
"with_two_hands": "visage qui fait un câlin",
"one_hand": "visage avec une main sur la bouche",
"silenced": "visage avec bouche fermeture éclair",
"celebrating": "visage festif",
"sunglasses": "visage avec lunettes de soleil",
"eyes_up": "visage roulant des yeux",
"monocle": "visage avec un monocle",
"sleeping": "visage somnolent",
"mask": "visage avec masque",
"fever": "visage avec thermomètre",
"bandage": "visage avec bandage autour de la tête",
"vomit": "visage qui vomit",
"tissue": "visage qui éternue",
"hot": "visage rouge et chaud",
"cold": "visage bleu et froid",
"crossed_eyes": "visage étourdi",
"exploding": "tête qui explose",
"sad": "visage mécontent",
"long_nose": "visage de menteur",
"many_tears": "visage qui pleure à chaudes larmes",
"fear": "visage qui hurle de peur",
"tired": "visage bâillant",
"annoyed": "visage avec fumée sortant des narines",
"clown": "visage de clown",
"ghost": "fantôme",
"dog": "tête de chien",
"happy_cat": "chat qui sourit avec des yeux rieurs",
"scared_cat": "chat fatigué",
"sad_cat": "chat qui pleure",
"monkey_no_see": "singe ne rien voir",
"monkey_no_hear": "singe ne rien entendre",
"monkey_no_talk": "singe ne rien dire",
"builder": "personnel du bâtiment",
"princess": "princesse",
"firefighter": "adulte",
"mage": "mage",
"mermaid": "créature aquatique",
"fairy": "personnage féérique",
"letter_heart": "lettre d’amour",
"red_heart": "cœur rouge",
"two_hearts": "deux cœurs",
"kiss": "trace de rouge à lèvres",
"hundred": "cent points",
"explosion": "explosion",
"drops": "gouttes de sueur",
"handshake": "poignée de main",
"hand_five_fingers": "main levée doigts écartés",
"hand_two_fingers": "V de la victoire",
"thumbs_up": "pouce vers le haut",
"fist": "poing levé",
"two_hands": "mains ouvertes",
"writing": "main qui écrit",
"praying": "mains en prière",
"arm": "biceps contracté",
"leg": "jambe",
"foot": "pied",
"ear": "oreille",
"nose": "nez",
"brain": "cerveau",
"tooth": "dent",
"bone": "os",
"eye": "œil",
"tongue": "langue",
"mouth": "bouche",
"shirt": "T-shirt",
"pants": "jean",
"dress": "robe",
"shoe": "chaussure de sport",
"fencing": "escrimeur",
"horse_riding": "course hippique",
"ski": "skieur",
"rowing_boat": "personne ramant dans une barque",
"swim": "personne nageant",
"surf": "personne faisant du surf",
"gym": "haltérophile",
"wrestling": "personnes faisant de la lutte",
"bike": "cycliste",
"parachute": "parachute",
"football": "ballon de football",
"basketball": "basket",
"tennis": "tennis",
"ping_pong": "ping-pong",
"martial": "tenue d’arts martiaux",
"lion": "tête de lion",
"leopard": "léopard",
"horse": "tête de cheval",
"zebra": "zèbre",
"pig": "cochon",
"goat": "chèvre",
"sheep": "mouton",
"camel": "dromadaire",
"giraffe": "girafe",
"elephant": "éléphant",
"rhinoceros": "rhinocéros",
"flamingo": "flamant",
"whale": "baleine soufflant par son évent",
"dolphin": "dauphin",
"bear": "ours",
"rooster": "coq",
"chick": "poussin qui éclôt",
"eagle": "aigle",
"duck": "canard",
"owl": "chouette",
"rabbit": "lapin",
"penguin": "pingouin",
"lizard": "lézard",
"turtle": "tortue",
"snake": "serpent",
"hedgehog": "hérisson",
"bat": "chauve-souris",
"fish": "poisson",
"shell": "coquille en spirale",
"octopus": "pieuvre",
"snail": "escargot",
"butterfly": "papillon",
"ant": "fourmi",
"bee": "abeille",
"beetle": "coccinelle",
"rose": "rose",
"sunflower": "tournesol",
"fir": "conifère",
"palm_tree": "palmier",
"cactus": "cactus",
"clover": "trèfle à quatre feuilles",
"potted_plant": "plante en pot",
"bouquet": "bouquet",
"three_leaves": "feuille morte",
"mushroom": "champignon",
"grapes": "raisin",
"watermelon": "pastèque",
"lemon": "citron",
"banana": "banane",
"pineapple": "ananas",
"apple": "pomme rouge",
"cherries": "cerises",
"strawberry": "fraise",
"three_blueberries": "myrtilles",
"kiwi": "kiwi",
"avocado": "avocat",
"eggplant": "aubergine",
"carrot": "carotte",
"corn": "épi de maïs",
"pepper": "piment rouge",
"croissant": "croissant",
"bread": "baguette",
"pretzel": "bretzel",
"cheese": "part de fromage",
"pizza": "pizza",
"egg": "œuf au plat",
"ice_cream": "glace italienne",
"cookie": "cookie",
"cake": "gâteau sablé",
"chocolate": "barre chocolatée",
"sweet": "bonbon",
"coffee": "boisson chaude",
"champagne_bottle": "bouteille de champagne",
"glass_wine": "verre de vin",
"two_glasses": "trinquer",
"mountain": "montagne enneigée",
"camping": "camping",
"beach": "plage avec parasol",
"compass": "boussole",
"museum": "monument classique",
"house": "maison avec jardin",
"fountain": "fontaine",
"circus": "chapiteau",
"train": "locomotive",
"taxi": "taxi",
"motorcycle": "moto",
"sailboat": "voilier",
"airplane": "avion",
"helicopter": "hélicoptère",
"rocket": "fusée",
"sun": "soleil",
"moon": "croissant de lune",
"planet": "planète à anneaux",
"star": "étoile",
"night_sky": "voie lactée",
"cloud": "nuage avec pluie",
"umbrella": "parapluie avec gouttes de pluie",
"lightning": "haute tension",
"snowflake": "flocon",
"snowman": "bonhomme de neige sans neige",
"thermometer": "thermomètre",
"fire": "feu",
"balloon": "ballon gonflable",
"kite": "cerf-volant",
"rainbow": "arc-en-ciel",
"guitar": "guitare",
"saxophone": "saxophone",
"music": "note de musique",
"painting": "palette de peinture",
"chess": "pion d’échec",
"gift": "cadeau",
"die": "dés",
"puzzle": "pièce de puzzle",
"teddy_bear": "ours en peluche",
"firecracker": "pétard",
"bullseye": "dans le mille",
"roller_skate": "patin à roulettes",
"kick_scooter": "trottinette",
"anchor": "ancre",
"scuba_diving": "masque de plongée",
"broom": "balai",
"magnifying_glass": "loupe orientée à gauche",
"bulb": "ampoule",
"three_books": "livres",
"package": "colis",
"pencil": "crayon",
"pin": "punaise",
"paperclip": "trombone",
"scissors": "ciseaux",
"key": "clé",
"lock": "cadenas ouvert",
"chair": "chaise",
"bathtub": "baignoire",
"sponge": "éponge",
"shopping_cart": "chariot"
}
}
}

@ -0,0 +1,231 @@
{
"emojis": {
"codes": {
"happy": "faccina con un gran sorriso",
"happy_tears": "faccina con lacrime di gioia",
"halo": "faccina con sorriso e aureola",
"three_hearts": "faccina con cuoricini",
"with_two_hearts": "faccina con sorriso e occhi a cuore",
"one_heart": "faccina che manda un bacio",
"with_tongue": "faccina che strizza gli occhi e mostra la lingua",
"with_two_hands": "faccina che abbraccia",
"one_hand": "faccina con mano sulla bocca",
"silenced": "faccina con bocca con cerniera",
"celebrating": "faccina che festeggia",
"sunglasses": "faccina con sorriso e occhiali da sole",
"eyes_up": "faccina con occhi al cielo",
"monocle": "faccina con monocolo",
"sleeping": "faccina che dorme",
"mask": "faccina con mascherina",
"fever": "faccina con termometro",
"bandage": "faccina con la testa bendata",
"vomit": "faccina che vomita",
"tissue": "faccina che starnutisce",
"hot": "faccina accaldata",
"cold": "faccina congelata",
"crossed_eyes": "faccina frastornata",
"exploding": "testa che esplode",
"sad": "faccina imbronciata",
"long_nose": "faccina bugiarda",
"many_tears": "faccina disperata",
"fear": "faccina terrorizzata",
"tired": "faccina che sbadiglia",
"annoyed": "faccina che sbuffa",
"clown": "faccina pagliaccio",
"ghost": "fantasma",
"dog": "muso di cane",
"happy_cat": "gatto che sogghigna",
"scared_cat": "gatto esterrefatto",
"sad_cat": "gatto che piange",
"monkey_no_see": "non vedo",
"monkey_no_hear": "non sento",
"monkey_no_talk": "non parlo",
"builder": "operaio edile",
"princess": "principessa",
"firefighter": "persona",
"mage": "mago",
"mermaid": "sirena",
"fairy": "fata",
"letter_heart": "lettera d’amore",
"red_heart": "cuore rosso",
"two_hearts": "due cuori",
"kiss": "impronta della bocca",
"hundred": "100 punti",
"explosion": "collisione",
"drops": "gocce di sudore",
"handshake": "stretta di mano",
"hand_five_fingers": "mano aperta",
"hand_two_fingers": "vittoria",
"thumbs_up": "pollice in su",
"fist": "pugno",
"two_hands": "mani aperte",
"writing": "mano che scrive",
"praying": "mani giunte",
"arm": "bicipite",
"leg": "gamba",
"foot": "piede",
"ear": "orecchio",
"nose": "naso",
"brain": "cervello",
"tooth": "dente",
"bone": "osso",
"eye": "occhio",
"tongue": "lingua",
"mouth": "bocca",
"shirt": "t-shirt",
"pants": "jeans",
"dress": "vestito",
"shoe": "scarpa sportiva",
"fencing": "schermidore",
"horse_riding": "ippica",
"ski": "sciatore",
"rowing_boat": "persona in barca a remi",
"swim": "persona che nuota",
"surf": "persona che fa surf",
"gym": "persona che solleva pesi",
"wrestling": "persone che fanno la lotta",
"bike": "ciclista",
"parachute": "paracadute",
"football": "pallone da calcio",
"basketball": "palla da pallacanestro",
"tennis": "tennis",
"ping_pong": "ping pong",
"martial": "kimono per arti marziali",
"lion": "leone",
"leopard": "leopardo",
"horse": "muso di cavallo",
"zebra": "zebra",
"pig": "maiale",
"goat": "capra",
"sheep": "pecora",
"camel": "dromedario",
"giraffe": "giraffa",
"elephant": "elefante",
"rhinoceros": "rinoceronte",
"flamingo": "fenicottero",
"whale": "balena che spruzza acqua",
"dolphin": "delfino",
"bear": "orso",
"rooster": "gallo",
"chick": "pulcino che nasce",
"eagle": "aquila",
"duck": "anatra",
"owl": "gufo",
"rabbit": "coniglio",
"penguin": "pinguino",
"lizard": "lucertola",
"turtle": "tartaruga",
"snake": "serpente",
"hedgehog": "riccio",
"bat": "pipistrello",
"fish": "pesce",
"shell": "conchiglia",
"octopus": "polpo",
"snail": "lumaca",
"butterfly": "farfalla",
"ant": "formica",
"bee": "ape",
"beetle": "coccinella",
"rose": "rosa",
"sunflower": "girasole",
"fir": "albero sempreverde",
"palm_tree": "palma",
"cactus": "cactus",
"clover": "quadrifoglio",
"potted_plant": "pianta in vaso",
"bouquet": "mazzo di fiori",
"three_leaves": "foglia caduta",
"mushroom": "fungo",
"grapes": "uva",
"watermelon": "anguria",
"lemon": "limone",
"banana": "banana",
"pineapple": "ananas",
"apple": "mela rossa",
"cherries": "ciliegie",
"strawberry": "fragola",
"three_blueberries": "mirtilli",
"kiwi": "kiwi",
"avocado": "avocado",
"eggplant": "melanzana",
"carrot": "carota",
"corn": "pannocchia",
"pepper": "peperoncino",
"croissant": "croissant",
"bread": "baguette",
"pretzel": "pretzel",
"cheese": "fetta di formaggio",
"pizza": "pizza",
"egg": "cucinare",
"ice_cream": "cono gelato",
"cookie": "biscotto",
"cake": "fetta di torta",
"chocolate": "cioccolato",
"sweet": "caramella",
"coffee": "bevanda calda",
"champagne_bottle": "bottiglia stappata",
"glass_wine": "bicchiere di vino",
"two_glasses": "brindisi",
"mountain": "montagna innevata",
"camping": "campeggio",
"beach": "spiaggia con ombrellone",
"compass": "bussola",
"museum": "edificio classico",
"house": "casa con giardino",
"fountain": "fontana",
"circus": "circo",
"train": "locomotiva",
"taxi": "taxi",
"motorcycle": "motocicletta",
"sailboat": "barca a vela",
"airplane": "aeroplano",
"helicopter": "elicottero",
"rocket": "razzo",
"sun": "sole",
"moon": "spicchio di luna",
"planet": "pianeta con satellite",
"star": "stella",
"night_sky": "Via Lattea",
"cloud": "pioggia",
"umbrella": "ombrello con gocce di pioggia",
"lightning": "alta tensione",
"snowflake": "fiocco di neve",
"snowman": "pupazzo di neve senza neve",
"thermometer": "termometro",
"fire": "fuoco",
"balloon": "palloncino",
"kite": "aquilone",
"rainbow": "arcobaleno",
"guitar": "chitarra",
"saxophone": "sassofono",
"music": "nota musicale",
"painting": "tavolozza",
"chess": "pedina degli scacchi",
"gift": "regalo",
"die": "dado",
"puzzle": "pezzo di puzzle",
"teddy_bear": "orsetto",
"firecracker": "petardo",
"bullseye": "bersaglio",
"roller_skate": "pattini a rotelle",
"kick_scooter": "monopattino",
"anchor": "ancora",
"scuba_diving": "maschera da sub",
"broom": "scopa",
"magnifying_glass": "lente di ingrandimento rivolta a sinistra",
"bulb": "lampadina",
"three_books": "libri",
"package": "pacco",
"pencil": "matita",
"pin": "puntina",
"paperclip": "graffetta",
"scissors": "forbici",
"key": "chiave",
"lock": "lucchetto aperto",
"chair": "sedia",
"bathtub": "vasca",
"sponge": "spugna",
"shopping_cart": "carrello"
}
}
}

@ -0,0 +1,231 @@
{
"emojis": {
"codes": {
"happy": "rosto risonho",
"happy_tears": "rosto chorando de rir",
"halo": "rosto sorridente com auréola",
"three_hearts": "rosto sorridente com 3 corações",
"with_two_hearts": "rosto sorridente com olhos de coração",
"one_heart": "rosto mandando um beijo",
"with_tongue": "rosto com olhos semicerrados e língua para fora",
"with_two_hands": "rosto abraçando",
"one_hand": "rosto com a mão sobre a boca",
"silenced": "rosto com boca de zíper",
"celebrating": "rosto festivo",
"sunglasses": "rosto sorridente com óculos escuros",
"eyes_up": "rosto com olhos revirados",
"monocle": "rosto com monóculo",
"sleeping": "rosto dormindo",
"mask": "rosto com máscara médica",
"fever": "rosto com termômetro",
"bandage": "rosto com atadura na cabeça",
"vomit": "rosto vomitando",
"tissue": "rosto espirrando",
"hot": "rosto fervendo de calor",
"cold": "rosto gelado",
"crossed_eyes": "rosto atordoado",
"exploding": "cabeça explodindo",
"sad": "rosto descontente",
"long_nose": "rosto de mentiroso",
"many_tears": "rosto chorando aos berros",
"fear": "rosto gritando de medo",
"tired": "rosto bocejando",
"annoyed": "rosto soltando vapor pelo nariz",
"clown": "rosto de palhaço",
"ghost": "fantasma",
"dog": "rosto de cachorro",
"happy_cat": "rosto de gato sorrindo com olhos sorridentes",
"scared_cat": "rosto de gato desolado",
"sad_cat": "rosto de gato chorando",
"monkey_no_see": "macaco que não vê nada",
"monkey_no_hear": "macaco que não ouve nada",
"monkey_no_talk": "macaco que não fala nada",
"builder": "trabalhador de construção civil",
"princess": "princesa",
"firefighter": "pessoa",
"mage": "mago",
"mermaid": "pessoa sereia",
"fairy": "fada",
"letter_heart": "carta de amor",
"red_heart": "coração vermelho",
"two_hearts": "dois corações",
"kiss": "marca de beijo",
"hundred": "cem pontos",
"explosion": "colisão",
"drops": "pingos de suor",
"handshake": "aperto de mãos",
"hand_five_fingers": "mão aberta com os dedos separados",
"hand_two_fingers": "mão em V de vitória",
"thumbs_up": "polegar para cima",
"fist": "punho levantado",
"two_hands": "mãos abertas",
"writing": "escrevendo à mão",
"praying": "mãos juntas",
"arm": "bíceps",
"leg": "perna",
"foot": "pé",
"ear": "orelha",
"nose": "nariz",
"brain": "cérebro",
"tooth": "dente",
"bone": "osso",
"eye": "olho",
"tongue": "língua",
"mouth": "boca",
"shirt": "camiseta",
"pants": "jeans",
"dress": "vestido",
"shoe": "tênis de corrida",
"fencing": "esgrimista",
"horse_riding": "corrida de cavalos",
"ski": "esquiador",
"rowing_boat": "pessoa remando",
"swim": "pessoa nadando",
"surf": "surfista",
"gym": "pessoa levantando peso",
"wrestling": "pessoas lutando",
"bike": "ciclista",
"parachute": "paraquedas",
"football": "bola de futebol",
"basketball": "bola de basquete",
"tennis": "tênis",
"ping_pong": "pingue-pongue",
"martial": "quimono de artes marciais",
"lion": "rosto de leão",
"leopard": "leopardo",
"horse": "rosto de cavalo",
"zebra": "zebra",
"pig": "porco",
"goat": "cabra",
"sheep": "ovelha",
"camel": "camelo",
"giraffe": "girafa",
"elephant": "elefante",
"rhinoceros": "rinoceronte",
"flamingo": "flamingo",
"whale": "baleia esguichando água",
"dolphin": "golfinho",
"bear": "rosto de urso",
"rooster": "galo",
"chick": "pintinho chocando",
"eagle": "águia",
"duck": "pato",
"owl": "coruja",
"rabbit": "coelho",
"penguin": "pinguim",
"lizard": "lagartixa",
"turtle": "tartaruga",
"snake": "cobra",
"hedgehog": "porco-espinho",
"bat": "morcego",
"fish": "peixe",
"shell": "caramujo",
"octopus": "polvo",
"snail": "caracol",
"butterfly": "borboleta",
"ant": "formiga",
"bee": "abelha",
"beetle": "joaninha",
"rose": "rosa",
"sunflower": "girassol",
"fir": "conífera",
"palm_tree": "palmeira",
"cactus": "cacto",
"clover": "trevo de quatro folhas",
"potted_plant": "vaso com planta",
"bouquet": "buquê",
"three_leaves": "folhas caídas",
"mushroom": "cogumelo",
"grapes": "uvas",
"watermelon": "melancia",
"lemon": "limão",
"banana": "banana",
"pineapple": "abacaxi",
"apple": "maçã vermelha",
"cherries": "cereja",
"strawberry": "morango",
"three_blueberries": "mirtilos",
"kiwi": "kiwi",
"avocado": "abacate",
"eggplant": "berinjela",
"carrot": "cenoura",
"corn": "milho",
"pepper": "pimenta",
"croissant": "croissant",
"bread": "baguete",
"pretzel": "pretzel",
"cheese": "queijo",
"pizza": "pizza",
"egg": "ovo frito",
"ice_cream": "sorvete italiano",
"cookie": "biscoito",
"cake": "pão de ló de morango",
"chocolate": "chocolate",
"sweet": "bala",
"coffee": "café",
"champagne_bottle": "garrafa de champanhe",
"glass_wine": "vinho",
"two_glasses": "taças brindando",
"mountain": "montanha com neve",
"camping": "acampamento",
"beach": "praia e guarda-sol",
"compass": "bússola",
"museum": "prédio grego",
"house": "casa com jardim",
"fountain": "fonte",
"circus": "circo",
"train": "locomotiva",
"taxi": "táxi",
"motorcycle": "motocicleta",
"sailboat": "barco a vela",
"airplane": "avião",
"helicopter": "helicóptero",
"rocket": "foguete",
"sun": "sol",
"moon": "lua crescente",
"planet": "planeta com anéis",
"star": "estrela branca média",
"night_sky": "via láctea",
"cloud": "nuvem com chuva",
"umbrella": "sombrinha na chuva",
"lightning": "alta tensão",
"snowflake": "floco de neve",
"snowman": "boneco de neve sem neve",
"thermometer": "termômetro",
"fire": "fogo",
"balloon": "balão",
"kite": "pipa",
"rainbow": "arco-íris",
"guitar": "guitarra",
"saxophone": "saxofone",
"music": "nota musical",
"painting": "paleta de tintas",
"chess": "peão de xadrez",
"gift": "presente",
"die": "jogo de dado",
"puzzle": "quebra-cabeça",
"teddy_bear": "ursinho de pelúcia",
"firecracker": "bombinha",
"bullseye": "no alvo",
"roller_skate": "patins de rodas",
"kick_scooter": "patinete",
"anchor": "âncora",
"scuba_diving": "máscara de mergulho",
"broom": "vassoura",
"magnifying_glass": "lupa para a esquerda",
"bulb": "lâmpada",
"three_books": "livros",
"package": "pacote",
"pencil": "lápis",
"pin": "tacha",
"paperclip": "clipe de papel",
"scissors": "tesoura",
"key": "chave",
"lock": "cadeado aberto",
"chair": "cadeira",
"bathtub": "banheira",
"sponge": "esponja",
"shopping_cart": "carrinho de compras"
}
}
}

@ -0,0 +1,231 @@
{
"emojis": {
"codes": {
"happy": "широко улыбается",
"happy_tears": "смеется до слез",
"halo": "с нимбом",
"three_hearts": "улыбающееся лицо с сердечками",
"with_two_hearts": "влюбленное лицо",
"one_heart": "воздушный поцелуй",
"with_tongue": "морщится и показывает язык",
"with_two_hands": "обнимает",
"one_hand": "прикрывает рот рукой",
"silenced": "рот на замке",
"celebrating": "на вечеринке",
"sunglasses": "лицо в темных очках",
"eyes_up": "закатывает глаза",
"monocle": "с моноклем",
"sleeping": "спит",
"mask": "в медицинской маске",
"fever": "с градусником во рту",
"bandage": "с перевязанной головой",
"vomit": "рвота",
"tissue": "чихает",
"hot": "жар",
"cold": "мерзнет",
"crossed_eyes": "головокружение",
"exploding": "взрыв мозга",
"sad": "грустит",
"long_nose": "лжец",
"many_tears": "слезы рекой",
"fear": "в ужасе",
"tired": "зевает",
"annoyed": "в ожидании успеха",
"clown": "клоун",
"ghost": "привидение",
"dog": "морда собаки",
"happy_cat": "смеющийся кот",
"scared_cat": "кот в шоке",
"sad_cat": "плачущий кот",
"monkey_no_see": "ничего не вижу",
"monkey_no_hear": "ничего не слышу",
"monkey_no_talk": "ничего никому не скажу",
"builder": "строитель",
"princess": "принцесса",
"firefighter": "взрослый",
"mage": "маг",
"mermaid": "русалка",
"fairy": "фея",
"letter_heart": "любовное письмо",
"red_heart": "алое сердце",
"two_hearts": "два сердца",
"kiss": "след от поцелуя",
"hundred": "сто баллов",
"explosion": "взрыв",
"drops": "капли пота",
"handshake": "рукопожатие",
"hand_five_fingers": "раскрытая ладонь",
"hand_two_fingers": "жест V",
"thumbs_up": "большой палец вверх",
"fist": "поднятый кулак",
"two_hands": "ладони в стороны",
"writing": "пишущая рука",
"praying": "сложенные руки",
"arm": "бицепс",
"leg": "нога",
"foot": "щиколотка",
"ear": "ухо",
"nose": "нос",
"brain": "мозг",
"tooth": "зуб",
"bone": "кость",
"eye": "глаз",
"tongue": "язык",
"mouth": "рот",
"shirt": "футболка",
"pants": "джинсы",
"dress": "платье",
"shoe": "кроссовки",
"fencing": "фехтовальщик",
"horse_riding": "скачки",
"ski": "горные лыжи",
"rowing_boat": "гребля",
"swim": "плавание",
"surf": "серфинг",
"gym": "тяжелоатлет",
"wrestling": "борцы",
"bike": "велосипедист",
"parachute": "парашют",
"football": "футбол",
"basketball": "баскетбол",
"tennis": "теннис",
"ping_pong": "настольный теннис",
"martial": "спортивное кимоно",
"lion": "морда льва",
"leopard": "леопард",
"horse": "морда лошади",
"zebra": "зебра",
"pig": "свинья",
"goat": "коза",
"sheep": "овца",
"camel": "одногорбый верблюд",
"giraffe": "жираф",
"elephant": "слон",
"rhinoceros": "носорог",
"flamingo": "фламинго",
"whale": "кит с фонтанчиком",
"dolphin": "дельфин",
"bear": "морда медведя",
"rooster": "петух",
"chick": "цыпленок в яйце",
"eagle": "орел",
"duck": "утка",
"owl": "сова",
"rabbit": "кролик",
"penguin": "пингвин",
"lizard": "ящерица",
"turtle": "черепаха",
"snake": "змея",
"hedgehog": "еж",
"bat": "летучая мышь",
"fish": "рыба",
"shell": "раковина",
"octopus": "осьминог",
"snail": "улитка",
"butterfly": "бабочка",
"ant": "муравей",
"bee": "пчела",
"beetle": "божья коровка",
"rose": "роза",
"sunflower": "подсолнух",
"fir": "елка",
"palm_tree": "пальма",
"cactus": "кактус",
"clover": "четырехлистный клевер",
"potted_plant": "растение в горшке",
"bouquet": "букет",
"three_leaves": "падающие листья",
"mushroom": "гриб",
"grapes": "виноград",
"watermelon": "арбуз",
"lemon": "лимон",
"banana": "банан",
"pineapple": "ананас",
"apple": "красное яблоко",
"cherries": "вишня",
"strawberry": "клубника",
"three_blueberries": "голубика",
"kiwi": "киви",
"avocado": "авокадо",
"eggplant": "баклажан",
"carrot": "морковь",
"corn": "кукурузный початок",
"pepper": "острый перец",
"croissant": "круассан",
"bread": "багет",
"pretzel": "крендель",
"cheese": "сыр",
"pizza": "пицца",
"egg": "яичница на сковороде",
"ice_cream": "мороженое в стаканчике",
"cookie": "печенье",
"cake": "кусочек торта",
"chocolate": "шоколад",
"sweet": "конфета",
"coffee": "горячий напиток",
"champagne_bottle": "шампанское",
"glass_wine": "бокал вина",
"two_glasses": "чокающиеся бокалы",
"mountain": "гора со снежной шапкой",
"camping": "кемпинг",
"beach": "пляж",
"compass": "компас",
"museum": "античное здание",
"house": "дом с садом",
"fountain": "фонтан",
"circus": "цирковой шатер",
"train": "паровоз",
"taxi": "такси",
"motorcycle": "мотоцикл",
"sailboat": "парусник",
"airplane": "самолет",
"helicopter": "вертолет",
"rocket": "ракета",
"sun": "солнце",
"moon": "полумесяц",
"planet": "планета с кольцом",
"star": "желтая звезда",
"night_sky": "Млечный Путь",
"cloud": "дождь",
"umbrella": "зонт под дождем",
"lightning": "высокое напряжение",
"snowflake": "снежинка",
"snowman": "снеговик",
"thermometer": "термометр",
"fire": "огонь",
"balloon": "воздушный шарик",
"kite": "воздушный змей",
"rainbow": "радуга",
"guitar": "гитара",
"saxophone": "саксофон",
"music": "нота",
"painting": "палитра с красками",
"chess": "пешка",
"gift": "подарок",
"die": "игральная кость",
"puzzle": "пазл",
"teddy_bear": "плюшевый мишка",
"firecracker": "динамитная шашка",
"bullseye": "мишень",
"roller_skate": "роликовые коньки",
"kick_scooter": "самокат",
"anchor": "якорь",
"scuba_diving": "маска с трубкой",
"broom": "метла",
"magnifying_glass": "лупа, наклоненная влево",
"bulb": "лампочка",
"three_books": "книги",
"package": "посылка",
"pencil": "карандаш",
"pin": "канцелярская кнопка",
"paperclip": "скрепка",
"scissors": "ножницы",
"key": "ключ",
"lock": "открытый замок",
"chair": "стул",
"bathtub": "ванна",
"sponge": "губка",
"shopping_cart": "тележка для покупок"
}
}
}

@ -0,0 +1,231 @@
{
"emojis": {
"codes": {
"happy": "嘿嘿",
"happy_tears": "笑哭了",
"halo": "微笑天使",
"three_hearts": "喜笑颜开",
"with_two_hearts": "花痴",
"one_heart": "飞吻",
"with_tongue": "眯眼吐舌",
"with_two_hands": "抱抱",
"one_hand": "不说",
"silenced": "闭嘴",
"celebrating": "聚会笑脸",
"sunglasses": "墨镜笑脸",
"eyes_up": "翻白眼",
"monocle": "带单片眼镜的脸",
"sleeping": "睡着了",
"mask": "感冒",
"fever": "发烧",
"bandage": "受伤",
"vomit": "呕吐",
"tissue": "打喷嚏",
"hot": "脸发烧",
"cold": "冷脸",
"crossed_eyes": "晕头转向",
"exploding": "爆炸头",
"sad": "不满",
"long_nose": "说谎",
"many_tears": "放声大哭",
"fear": "吓死了",
"tired": "打呵欠",
"annoyed": "傲慢",
"clown": "小丑脸",
"ghost": "鬼",
"dog": "狗脸",
"happy_cat": "微笑的猫",
"scared_cat": "疲倦的猫",
"sad_cat": "哭泣的猫",
"monkey_no_see": "非礼勿视",
"monkey_no_hear": "非礼勿听",
"monkey_no_talk": "非礼勿言",
"builder": "建筑工人",
"princess": "公主",
"firefighter": "成人",
"mage": "法师",
"mermaid": "人鱼",
"fairy": "精灵",
"letter_heart": "情书",
"red_heart": "红心",
"two_hearts": "两颗心",
"kiss": "唇印",
"hundred": "一百分",
"explosion": "爆炸",
"drops": "汗滴",
"handshake": "握手",
"hand_five_fingers": "手掌",
"hand_two_fingers": "胜利手势",
"thumbs_up": "拇指向上",
"fist": "举起拳头",
"two_hands": "张开双手",
"writing": "写字",
"praying": "双手合十",
"arm": "肌肉",
"leg": "腿",
"foot": "脚",
"ear": "耳朵",
"nose": "鼻子",
"brain": "脑",
"tooth": "牙齿",
"bone": "骨头",
"eye": "眼睛",
"tongue": "舌头",
"mouth": "嘴",
"shirt": "T恤",
"pants": "牛仔裤",
"dress": "连衣裙",
"shoe": "跑鞋",
"fencing": "击剑选手",
"horse_riding": "赛马",
"ski": "滑雪的人",
"rowing_boat": "划艇",
"swim": "游泳",
"surf": "冲浪",
"gym": "举重",
"wrestling": "摔跤选手",
"bike": "骑自行车",
"parachute": "降落伞",
"football": "足球",
"basketball": "篮球",
"tennis": "网球",
"ping_pong": "乒乓球",
"martial": "练武服",
"lion": "狮子",
"leopard": "豹子",
"horse": "马头",
"zebra": "斑马",
"pig": "猪",
"goat": "山羊",
"sheep": "母羊",
"camel": "骆驼",
"giraffe": "长颈鹿",
"elephant": "大象",
"rhinoceros": "犀牛",
"flamingo": "火烈鸟",
"whale": "喷水的鲸",
"dolphin": "海豚",
"bear": "熊",
"rooster": "公鸡",
"chick": "小鸡破壳",
"eagle": "鹰",
"duck": "鸭子",
"owl": "猫头鹰",
"rabbit": "兔子",
"penguin": "企鹅",
"lizard": "蜥蜴",
"turtle": "龟",
"snake": "蛇",
"hedgehog": "刺猬",
"bat": "蝙蝠",
"fish": "鱼",
"shell": "海螺",
"octopus": "章鱼",
"snail": "蜗牛",
"butterfly": "蝴蝶",
"ant": "蚂蚁",
"bee": "蜜蜂",
"beetle": "瓢虫",
"rose": "玫瑰",
"sunflower": "向日葵",
"fir": "松树",
"palm_tree": "棕榈树",
"cactus": "仙人掌",
"clover": "四叶草",
"potted_plant": "盆栽植物",
"bouquet": "花束",
"three_leaves": "落叶",
"mushroom": "蘑菇",
"grapes": "葡萄",
"watermelon": "西瓜",
"lemon": "柠檬",
"banana": "香蕉",
"pineapple": "菠萝",
"apple": "红苹果",
"cherries": "樱桃",
"strawberry": "草莓",
"three_blueberries": "蓝莓",
"kiwi": "猕猴桃",
"avocado": "鳄梨",
"eggplant": "茄子",
"carrot": "胡萝卜",
"corn": "玉米",
"pepper": "红辣椒",
"croissant": "羊角面包",
"bread": "法式长棍面包",
"pretzel": "椒盐卷饼",
"cheese": "芝士",
"pizza": "披萨",
"egg": "煎蛋",
"ice_cream": "圆筒冰激凌",
"cookie": "饼干",
"cake": "水果蛋糕",
"chocolate": "巧克力",
"sweet": "糖",
"coffee": "热饮",
"champagne_bottle": "开香槟",
"glass_wine": "葡萄酒",
"two_glasses": "碰杯",
"mountain": "雪山",
"camping": "露营",
"beach": "沙滩伞",
"compass": "指南针",
"museum": "古典建筑",
"house": "别墅",
"fountain": "喷泉",
"circus": "马戏团帐篷",
"train": "蒸汽火车",
"taxi": "出租车",
"motorcycle": "摩托车",
"sailboat": "帆船",
"airplane": "飞机",
"helicopter": "直升机",
"rocket": "火箭",
"sun": "太阳",
"moon": "弯月",
"planet": "有环行星",
"star": "星星",
"night_sky": "银河",
"cloud": "下雨",
"umbrella": "雨伞",
"lightning": "高压",
"snowflake": "雪花",
"snowman": "雪人",
"thermometer": "温度计",
"fire": "火焰",
"balloon": "气球",
"kite": "风筝",
"rainbow": "彩虹",
"guitar": "吉他",
"saxophone": "萨克斯管",
"music": "音符",
"painting": "调色盘",
"chess": "兵",
"gift": "礼物",
"die": "骰子",
"puzzle": "拼图",
"teddy_bear": "泰迪熊",
"firecracker": "爆竹",
"bullseye": "正中靶心的飞镖",
"roller_skate": "四轮滑冰鞋",
"kick_scooter": "滑板车",
"anchor": "锚",
"scuba_diving": "潜水面罩",
"broom": "扫帚",
"magnifying_glass": "左斜的放大镜",
"bulb": "灯泡",
"three_books": "书",
"package": "包裹",
"pencil": "铅笔",
"pin": "图钉",
"paperclip": "回形针",
"scissors": "剪刀",
"key": "钥匙",
"lock": "打开的锁",
"chair": "椅子",
"bathtub": "浴缸",
"sponge": "海绵",
"shopping_cart": "购物车"
}
}
}

@ -0,0 +1,365 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<!--
@component
"Accounts Info" user panel sub menu.
Provides info about wallet, broker, etc. and download option.
-->
<script lang="ts">
import { link, push } from "svelte-spa-router";
import CenteredLayout from "../lib/CenteredLayout.svelte";
import { ArrowLeft, ServerStack } from "svelte-heros-v2";
import { onMount, tick } from "svelte";
import { Sidebar, SidebarGroup, SidebarWrapper } from "flowbite-svelte";
import { t } from "svelte-i18n";
import { active_session, active_wallet, connections, display_error } from "../store";
import { default as ng } from "../api";
import DeviceIcon from "../lib/icons/DeviceIcon.svelte";
let error;
let nonActiveClass =
"flex items-center p-2 text-base font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700";
let top;
async function scrollToTop() {
await tick();
top.scrollIntoView();
}
onMount(async () => {
if (!$active_session) {
push("#/");
} else {
await scrollToTop();
}
});
$: wallet_unlocked = $active_wallet?.wallet?.V0;
$: personal_site_id = wallet_unlocked?.personal_site_id;
/**
* brokers:
ZBY5Y8DTyhMo5xfo5K4FCsJHVaN3O15vKeQBwZxr76YA: [
ServerV0:
can_forward: true
can_verify: false
peer_id: Object { Ed25519PubKey: (32) [] }
server_type: Object {
Domain
Localhost: 1440 // if domain not exist
BoxPrivate // one IPv4 and optionally one IPv6 to connect to an NGbox on private LAN rs type Vec<BindAddress>
Public // one IPv4 and optionally one IPv6 to connect to an NGbox on public (edge) internet. rs type Vec<BindAddress>
BoxPublicDyn // same but with dynamic IPs that can be retrieved with a special API. rs type Vec<BindAddress>
}
]
*/
/*
* Connections Is a record of string to those objects:
error: undefined
server_id: "ZBY5Y8DTyhMo5xfo5K4FCsJHVaN3O15vKeQBwZxr76YA"
server_ip: "ws://localhost:1440"
since: Date Fri Jul 05 2024 09:46:30 GMT+0200 (Central European Summer Time)
*/
// let connections;
/**
* bootstraps: Array [ {} ]
* cores: Array [ (2) […] ]
* id: Object { Ed25519PubKey: (32) [] }
* name: "Personal"
* private: Object { id: {}, store_type: "Private" }
* protected: Object { id: {}, store_type: "Protected" }
* public: Object { id: {}, store_type: "Public" }
* site_type: Object { Individual: (2) [] } // Some key data as well
*/
$: walletSites = wallet_unlocked?.sites;
/** Type:
* client_type: "Web"
* details: '{"browser":{"name":"Firefox","version":"127.0","appVersion":"5.0 (X11)","arch":"Linux x86_64","vendor":"","ua":"Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0"},"os":{"name":"Linux"},"platform":{"type":"desktop"},"engine":{"name":"Gecko","version":"20100101","sdk":"0.1.0-preview.1"}}'
* timestamp_install: 0
* timestamp_updated: 0
* version: "0.1.0"
*/
var device_info;
$: display_sites = Object.entries(walletSites || {})
?.map(([user_id, site]) => {
// Try to extract device details (for now only of the connected device).
// TODO: API for all devices
const devices = (!device_info ? [] : [device_info.V0]).map((device) => {
const device_details = JSON.parse(device.details);
return {
name: device.name, // TODO: API device.name is not provided
peer_id: device.id, // TODO: API device id is is not provided
version: device.version,
details: device_details,
device_name:
device.client_type === "web"
? `${device_details?.browser?.name}${" - " + device_details?.browser.arch || ""}`
: `${device_details?.os?.name_uname || device_details?.os?.name_rust || device_details?.os?.name} - ${device_details?.os?.version_uname || device_details?.os?.version_rust}`,
type: device.client_type,
};
});
return {
id: user_id,
connection: $connections[user_id], // error, server_id, server_ip, since
devices,
// @ts-ignore
name: site.name,
};
})
.filter((site) => site.id === personal_site_id);
$: display_brokers = Object.entries(wallet_unlocked?.brokers || {}).map(
// @ts-ignore
([broker_id, [broker]]) => {
//TODO: there can be several broker definitions for the same broker_id (if the broker can be reached by different means)
return {
id: broker_id,
can_forward: broker.ServerV0.can_forward,
can_verify: broker.ServerV0.can_verify,
address:
broker.ServerV0.server_type.Domain ||
`localhost:${broker.ServerV0.server_type.Localhost}`,
last_connected: new Date("1970-01-01T00:00:00Z").toLocaleString(), // TODO: API
};
}
);
// $: console.info(JSON.stringify(device_info));
// $: console.debug(
// "info",
// device_info,
// "walletSites",
// walletSites,
// "wallet",
// $active_wallet,
// "connections",
// $connections,
// "display_brokers",
// display_brokers,
// "display_sites",
// display_sites
// );
onMount(async () => {
ng.client_info().then((res) => {
device_info = res;
});
});
</script>
<CenteredLayout>
<div class="container3" bind:this={top}>
<div class="row mb-20">
<Sidebar {nonActiveClass}>
<SidebarWrapper
divClass="bg-gray-60 overflow-y-auto py-4 px-3 rounded dark:bg-gray-800"
>
<!-- Go Back-->
<SidebarGroup ulClass="space-y-2" role="menu">
<li>
<h2 class="text-xl mb-6">{$t("pages.account_info.title")}</h2>
</li>
<li
tabindex="0"
role="menuitem"
class="flex items-center p-2 text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
on:keypress={() => window.history.go(-1)}
on:click={() => window.history.go(-1)}
>
<ArrowLeft
tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/>
<span class="ml-3">{$t("buttons.back")}</span>
</li>
</SidebarGroup>
<!-- For now this will only consist of the `Personal` one-->
{#each display_sites as site}
<li
class="flex items-center p-2 text-base font-normal text-gray-900"
>
<h3 class="flex items-center mt-2 text-lg font-normal">
{$t("pages.account_info.site", { values: { name: site.name } })}
</h3>
</li>
<!-- Device Details -->
<SidebarGroup ulClass="space-y-1">
<li
class="flex items-center p-2 text-base font-normal text-gray-900"
>
<h4
class="flex items-center mt-2 text-base font-normal text-gray-600"
>
{$t("pages.account_info.devices")}
</h4>
</li>
{#each site.devices as device, index}
<li
class="flex items-center p-2 text-base font-normal text-gray-900 bg-white shadow-md rounded-lg"
class:border-b={index !== site.devices.length - 1}
>
<div>
<DeviceIcon device={device.type} />
</div>
<div
class="flex flex-col ml-3 items-start text-left overflow-auto"
>
<div>
<span class="text-gray-500">Name</span>
<span class="break-all">{device.name}</span>
</div>
<div>
<span class="text-gray-500">ID</span>
<span class="break-all">{device.peer_id}</span>
</div>
<div>
<span class="text-gray-500">Version</span>
<span>{device.version}</span>
</div>
<div>
<span class="text-gray-500">System</span>
<span> {device.device_name}</span>
</div>
</div>
</li>
{/each}
</SidebarGroup>
<!-- Broker Details -->
<SidebarGroup ulClass="space-y-1">
<li
class="flex items-center p-2 text-base font-normal text-gray-900"
>
<h4
class="flex items-center mt-2 text-base font-normal text-gray-600"
>
Brokers
</h4>
</li>
{#if display_brokers.length > 0}
{#each Object.values(display_brokers) as broker, index}
<!--
(peerId, IP/port or domain, last time connected)
-->
<li
class="flex items-center p-2 text-base font-normal text-gray-900 bg-white shadow-md rounded-lg"
class:border-b={index !== display_brokers.length - 1}
>
<div>
<ServerStack
tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/>
</div>
<div class="flex flex-col ml-3 items-start text-left">
<div>
<span class="text-gray-500">Address</span><br />
<span class="break-all">{broker.address}</span>
</div>
<div>
<span class="text-gray-500">Last Connected</span><br />
<span class="break-all">{broker.last_connected}</span>
</div>
<div>
<span class="text-gray-500">ID</span>
<span class="break-all">{broker.id}</span>
</div>
<!-- <div>
<span class="text-gray-500">Can Forward?</span>
<span>{broker.can_forward}</span>
</div>
<div>
<span class="text-gray-500">Can Verify?</span>
<span>{broker.can_verify}</span>
</div> -->
</div>
</li>
{/each}
{:else}
<li
class="flex items-center p-2 text-base font-normal text-gray-900"
>
<span class="ml-3"
>{$t("pages.account_info.no_brokers_connected")}</span
>
</li>
{/if}
</SidebarGroup>
{/each}
</SidebarWrapper>
</Sidebar>
</div>
{#if error}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-red-800">
<svg
class="animate-bounce mt-10 h-16 w-16 mx-auto"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
/>
</svg>
{#if error == "AlreadyExists"}
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5">
{@html $t("errors.AlreadyExists")}
</p>
<a use:link href="/">
<button
tabindex="-1"
class="text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
Login
</button>
</a>
{:else}
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5">
{@html $t("errors.error_occurred", {
values: { message: display_error(error) },
})}
</p>
<a use:link href="/">
<button
tabindex="-1"
class="text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
{$t("buttons.back_to_homepage")}
</button>
</a>
{/if}
</div>
{/if}
</div>
</CenteredLayout>
<style>
</style>

@ -9,18 +9,27 @@
// according to those terms.
-->
<!--
Home page to display for logged in users.
Redirects to no-wallet or login page, if not logged in.
-->
<script>
import { Button } from "flowbite-svelte";
import { link } from "svelte-spa-router";
import Home from "../lib/Home.svelte";
import NoWallet from "../lib/NoWallet.svelte";
import { push } from "svelte-spa-router";
import { onMount, onDestroy } from "svelte";
import { active_wallet, has_wallets, derived } from "../store";
import {
active_wallet,
has_wallets,
derived,
cannot_load_offline,
} from "../store";
let display_login_create = !$has_wallets || !$active_wallet;
let unsubscribe;
onMount(() => {
//setTimeout(function () {}, 2);
const combined = derived([active_wallet, has_wallets], ([$s1, $s2]) => [
$s1,
$s2,

@ -10,16 +10,12 @@
-->
<script type="ts">
import { Button } from "flowbite-svelte";
import { link } from "svelte-spa-router";
import Install from "../lib/Install.svelte";
import { push } from "svelte-spa-router";
import { onMount, onDestroy } from "svelte";
import CenteredLayout from "../lib/CenteredLayout.svelte";
import { has_wallets } from "../store";
let display_has_wallets_warning = $has_wallets != 0;
let unsubscribe;
onMount(() => {});
onDestroy(() => {});

@ -10,7 +10,7 @@
-->
<script lang="ts">
export let params = {};
export let params: { invitation: string } = { invitation: "" };
import { onMount } from "svelte";
onMount(
() => (window.location.href = "/#/wallet/create?i=" + params.invitation)

@ -9,16 +9,59 @@
// according to those terms.
-->
<script>
import { onMount } from "svelte";
import { push } from "svelte-spa-router";
import FullLayout from "../lib/FullLayout.svelte";
import Document from "../lib/Document.svelte";
import { t } from "svelte-i18n";
// The params prop contains values matched from the URL
export let params = {};
console.log(params);
import {
active_session,
} from "../store";
import {
change_nav_bar, cur_tab, reset_in_memory
} from "../tab";
import {
Square3Stack3d,
Megaphone,
InboxArrowDown,
ChatBubbleLeftRight,
Phone,
VideoCamera,
} from "svelte-heros-v2";
//console.log(params);
let nuri = "";
$: if ($active_session && params[1]) { if (params[1].startsWith("o:"+$active_session.private_store_id)) push("#/");
else if (params[1].startsWith("o:"+$active_session.protected_store_id)) push("#/shared");
else if (params[1].startsWith("o:"+$active_session.public_store_id)) push("#/site"); else nuri = params[1]; }
onMount(() => {
change_nav_bar("nav:unknown_doc",$t("doc.doc"), true);
reset_in_memory();
});
</script>
<div class="fix">
<div
class="h-screen aspect-[3/5] pazzleline max-w-[720px] bg-yellow-300 inner"
>
<p>Nextgraph URI {params[1]}</p>
</div>
</div>
<FullLayout>
{#if nuri && $cur_tab.doc.is_store && $cur_tab.store.store_type === "group"}
<div class="bg-gray-100 flex p-1 justify-around md:justify-start h-11 gap-0 xs:gap-3 md:gap-10 text-gray-500">
<div class="overflow-hidden w-16 xs:ml-3 flex justify-start" role="button" tabindex="0">
<ChatBubbleLeftRight tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"><div style="overflow-wrap: anywhere;" class="max-h-8 xs:max-h-10">{$t("doc.header.buttons.chat")}</div></div>
</div>
<div class="overflow-hidden w-8 xs:ml-2 flex justify-start" role="button" tabindex="0">
<Phone tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"></div>
</div>
<div class="overflow-hidden w-8 xs:ml-2 flex justify-start" role="button" tabindex="0">
<VideoCamera tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"></div>
</div>
<div class="overflow-hidden w-8 xs:ml-3 flex justify-start" role="button" tabindex="0">
<Megaphone tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"></div>
</div>
<div class="overflow-hidden w-16 xxs:w-20 xs:w-24 xs:ml-2 flex justify-start" role="button" tabindex="0">
<Square3Stack3d tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"><div style="overflow-wrap: anywhere;" class="max-h-8 xs:max-h-10">{$t("doc.header.buttons.all_docs")}</div></div>
</div>
</div>
{/if}
{#if nuri}
<Document {nuri}/>
{/if}
</FullLayout>

@ -12,12 +12,29 @@
<script>
import { Alert } from "flowbite-svelte";
import CenteredLayout from "../lib/CenteredLayout.svelte";
import { t } from "svelte-i18n";
import {
ArrowLeft,
} from "svelte-heros-v2";
export let params;
</script>
<CenteredLayout displayFooter={true}>
<div class="p-8">
<div class="p-8 flex flex-col justify-center">
<Alert color="red">
<span class="font-medium">404</span> Page not found.
<span class="font-medium">404</span> {$t("pages.not_found.title")}
<br />
<span class="text-sm">{@html $t("pages.not_found.message")}</span>
</Alert>
<button
class="mt-10 text-center justify-center align-center flex shrink items-center p-2 text-base font-normal text-white bg-primary-700 dark:bg-primary-400 clickable rounded-lg dark:text-white hover:bg-primary-500 dark:hover:bg-gray-700"
on:click={() => window.history.go(-1)}
>
<ArrowLeft
tabindex="-1"
class="w-7 h-7 text-white transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/>
<span class="ml-3">{$t("buttons.back")}</span>
</button>
</div>
</CenteredLayout>

@ -0,0 +1,148 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<!--
@component
QR Scanner Component and Route
-->
<script lang="ts">
import { onMount, onDestroy } from "svelte";
import { t } from "svelte-i18n";
import { scanned_qr_code } from "../store";
import { ArrowLeft, ExclamationTriangle } from "svelte-heros-v2";
import { Spinner } from "flowbite-svelte";
let tauri_platform = import.meta.env.TAURI_PLATFORM;
let mobile = tauri_platform == "android" || tauri_platform == "ios";
let webScanner;
let nativeScanner;
let error = false;
function on_qr_scanned(content) {
scanned_qr_code.set(content);
window.history.go(-1);
}
onMount(async () => {
if (mobile) {
// Load Native Scanner
nativeScanner = await import("@tauri-apps/plugin-barcode-scanner");
let perms = await nativeScanner.requestPermissions();
console.log(perms);
scanned_qr_code.set("");
let result = await nativeScanner.scan({
windowed: false,
cameraDirection: "back",
formats: [nativeScanner.Format.QRCode],
});
console.log(result);
on_qr_scanned(result.content);
} else {
// Load Web Scanner
const { Html5QrcodeScanner, Html5Qrcode } = await import("html5-qrcode");
// Init scanner object
// webScanner = new Html5QrcodeScanner(
// "scanner-div",
// { fps: 10, qrbox: { width: 300, height: 300 }, formatsToSupport: [0] },
// false
// );
try {
webScanner = new Html5Qrcode ("scanner-div");
await webScanner.start({ facingMode: { exact: "environment"} }, { fps: 10, qrbox: { width: 300, height: 300 }, formatsToSupport: [0] }, (decoded_text, decoded_result) => {
//console.log(decoded_result);
// Handle scan result
on_qr_scanned(decoded_text);
});
} catch (e) {
try {
webScanner = new Html5Qrcode ("scanner-div");
await webScanner.start({ facingMode: "environment" }, { fps: 10, qrbox: { width: 300, height: 300 }, formatsToSupport: [0] }, (decoded_text, decoded_result) => {
//console.log(decoded_result);
// Handle scan result
on_qr_scanned(decoded_text);
});
} catch (e) {
webScanner = null;
error = true;
}
}
// // Add scanner to Screen.
// webScanner.render((decoded_text, decoded_result) => {
// //console.log(decoded_result);
// // Handle scan result
// on_qr_scanned(decoded_text);
// }, (error) => {
// //console.error(error);
// });
// Auto-Request camera permissions (there's no native way, unfortunately...)
// setTimeout(() => {
// // Auto-start by clicking button
// document
// .getElementById("html5-qrcode-button-camera-permission")
// ?.click();
// }, 100);
// setTimeout(check_ready_and_start, 1000);
}
});
// const check_ready_and_start = () => {
// // Auto-start by clicking button
// let start_btn = document
// .getElementById("html5-qrcode-button-camera-start");
// if (start_btn) {
// start_btn.click();
// } else {
// setTimeout(check_ready_and_start, 1000);
// }
// };
onDestroy(async () => {
if (mobile) {
if (nativeScanner) await nativeScanner.cancel();
} else {
if (webScanner) webScanner.stop();
}
});
</script>
<div class="text-center max-w-4xl mx-auto">
<div>
<h2 class="text-xl mb-6">{$t("pages.scan_qr.scanning")}</h2>
</div>
{#if !error}<Spinner />{/if}
<!-- Web Scanner -->
<div id="scanner-div"></div>
{#if error}
<div class="max-w-6xl lg:px-8 mx-auto px-4 text-red-800">
<ExclamationTriangle class="animate-bounce mt-10 h-16 w-16 mx-auto" />
{@html $t("errors.camera_unavailable")}
</div>
{/if}
<div class="mx-auto max-w-xs">
<button
on:click={() => window.history.go(-1)}
class="mt-8 w-full text-gray-500 dark:text-gray-400 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
><ArrowLeft
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>{$t("buttons.back")}</button
>
</div>
</div>

@ -0,0 +1,54 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { onMount, tick } from "svelte";
import { t } from "svelte-i18n";
import FullLayout from "../lib/FullLayout.svelte";
import Document from "../lib/Document.svelte";
import {
active_session,
} from "../store";
import {
change_nav_bar,reset_in_memory
} from "../tab";
import {
Square3Stack3d,
Megaphone,
UserGroup,
InboxArrowDown,
} from "svelte-heros-v2";
onMount(() => {
change_nav_bar("nav:protected",$t("doc.protected_store"), false);
reset_in_memory();
});
let nuri = $active_session && ("o:"+$active_session.protected_store_id);
</script>
<FullLayout>
<div class="bg-gray-100 flex p-1 justify-around md:justify-start h-11 gap-0 xs:gap-3 text-gray-500">
<div class="overflow-hidden w-20 xxs:w-28 xs:w-36 xs:ml-3 flex justify-start" role="button" tabindex="0">
<UserGroup tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"><div style="overflow-wrap: anywhere;" class="max-h-8 xs:max-h-10">{$t("doc.header.buttons.groups")}</div></div>
</div>
<div class="overflow-hidden w-20 xxs:w-28 xs:w-36 xs:ml-2 flex justify-start" role="button" tabindex="0">
<InboxArrowDown tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"><div style="overflow-wrap: anywhere;" class="max-h-8 xs:max-h-10">{$t("doc.header.buttons.inbox")}</div></div>
</div>
<div class="overflow-hidden w-24 xxs:w-28 xs:w-36 xs:ml-2 flex justify-start" role="button" tabindex="0">
<Megaphone tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"><div style="overflow-wrap: anywhere;" class="max-h-8 xs:max-h-10">{$t("doc.header.buttons.channels")}</div></div>
</div>
<div class="overflow-hidden w-16 xxs:w-28 xs:w-36 xs:ml-2 flex justify-start" role="button" tabindex="0">
<Square3Stack3d tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"><div style="overflow-wrap: anywhere;" class="max-h-8 xs:max-h-10">{$t("doc.header.buttons.all_docs")}</div></div>
</div>
</div>
<Document {nuri}/>
</FullLayout>

@ -0,0 +1,46 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { onMount, tick } from "svelte";
import { t } from "svelte-i18n";
import FullLayout from "../lib/FullLayout.svelte";
import Document from "../lib/Document.svelte";
import {
active_session,
} from "../store";
import {
change_nav_bar,reset_in_memory
} from "../tab";
import {
Square3Stack3d,
Megaphone,
} from "svelte-heros-v2";
onMount(() => {
change_nav_bar("nav:public",$t("doc.public_store"), false);
reset_in_memory();
});
let nuri = $active_session && ("o:"+$active_session.public_store_id);
</script>
<FullLayout>
<div class="bg-gray-100 flex p-1 justify-start h-11 gap-3 text-gray-500">
<div class="overflow-hidden w-32 ml-3 flex justify-start mr-1" role="button" tabindex="0">
<Megaphone tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-sm flex items-center"><div style="overflow-wrap: anywhere;" class="max-h-10">{$t("doc.header.buttons.channels")}</div></div>
</div>
<div class="overflow-hidden w-32 ml-3 flex justify-start" role="button" tabindex="0">
<Square3Stack3d tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-sm flex items-center"><div style="overflow-wrap: anywhere;" class="max-h-10">{$t("doc.header.buttons.all_docs")}</div></div>
</div>
</div>
<Document {nuri}/>
</FullLayout>

@ -8,6 +8,13 @@
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<!--
@component
"User Panel" page.
Provides wallet, logout, offline/online switch, and other user actions.
-->
<script>
// @ts-nocheck
@ -16,6 +23,7 @@
import CenteredLayout from "../lib/CenteredLayout.svelte";
import { version } from "../../package.json";
import Time from "svelte-time";
import { t } from "svelte-i18n";
// @ts-ignore
import Logo from "../assets/nextgraph.svg?component";
import {
@ -49,6 +57,7 @@
active_wallet,
connections,
reconnect,
display_error,
} from "../store";
import {
@ -88,30 +97,6 @@
push("#/wallet/login");
}
let downloading = false;
let wallet_file_ready = false;
let download_link = false;
let download_error = false;
async function download_wallet() {
try {
downloading = true;
let file = await ng.wallet_get_file($active_wallet.id);
// @ts-ignore
wallet_file_ready = "wallet-" + $active_wallet.id + ".ngw";
if (!tauri_platform) {
const blob = new Blob([file], {
type: "application/octet-stream",
});
// @ts-ignore
download_link = URL.createObjectURL(blob);
} else {
download_link = true;
}
} catch (e) {
download_error = e;
}
}
$: personal_site = $active_wallet?.wallet?.V0.personal_site_id;
$: personal_site_id = $active_wallet?.wallet?.V0.personal_site;
@ -126,10 +111,13 @@
}
};
const donate = async () => {
await displayPopup("https://nextgraph.org/donate", "Support NextGraph");
await displayPopup(
"https://nextgraph.org/donate",
$t("common.support_nextgraph")
);
};
const about = async () => {
await displayPopup("https://nextgraph.org", "About NextGraph");
await displayPopup("https://nextgraph.org", $t("common.about_nextgraph"));
};
</script>
@ -140,22 +128,22 @@
<SidebarWrapper
divClass="bg-gray-60 overflow-y-auto py-4 px-3 rounded dark:bg-gray-800"
>
<SidebarGroup ulClass="space-y-2">
<SidebarGroup ulClass="space-y-2" role="menu">
<li>
<h2 class="text-xl mb-6">User panel</h2>
<h2 class="text-xl mb-6">{$t("pages.user_panel.title")}</h2>
</li>
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<li
tabindex="0"
role="menuitem"
class="flex items-center p-2 text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
on:keypress={() => window.history.go(-1)}
on:click={() => window.history.go(-1)}
>
<ArrowLeft
tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
class="w-7 h-7 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/>
<span class="ml-3">Back</span>
<span class="ml-3">{$t("buttons.back")}</span>
</li>
<li
@ -164,32 +152,34 @@
{#if $online}
<Signal
tabindex="-1"
class="w-7 h-7 text-green-600 transition duration-75 dark:text-green-400 "
class="w-7 h-7 text-green-600 transition duration-75 focus:outline-none dark:text-green-400 "
/>
<span class="ml-3 text-green-600 dark:text-green-400"
>Online</span
>{$t("connectivity.online")}</span
>
{:else}
<SignalSlash
tabindex="-1"
class="w-7 h-7 text-red-600 transition duration-75 dark:text-red-400 "
class="w-7 h-7 text-red-600 transition duration-75 focus:outline-none dark:text-red-400 "
/>
<span class="ml-3 text-red-600 dark:text-red-400">Offline</span>
<span class="ml-3 text-red-600 dark:text-red-400"
>{$t("connectivity.offline")}</span
>
{/if}
</li>
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<li
tabindex="0"
role="menuitem"
class="flex items-center p-2 text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
on:keypress={logout}
on:click={logout}
>
<ArrowRightOnRectangle
tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
class="w-7 h-7 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/>
<span class="ml-3">Logout</span>
<span class="ml-3">{$t("buttons.logout")}</span>
</li>
<!-- <li
tabindex="0"
@ -203,106 +193,52 @@
/>
<span class="ml-3">Switch wallet</span>
</li> -->
{#if !downloading}
<li
tabindex="0"
role="menuitem"
class="flex items-center p-2 text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
on:keypress={download_wallet}
on:click={download_wallet}
>
<DocumentArrowDown
tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/>
<span class="ml-3">Download wallet file</span>
</li>
{:else if download_error}
<li
tabindex="-1"
class="flex items-center p-2 text-base font-normal text-red-700 rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
>
<NoSymbol
tabindex="-1"
class="w-7 h-7 text-red-700 transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/>
<span class="ml-3 text-left"
>Download failed:<br /> {download_error}</span
>
</li>
{:else if !wallet_file_ready}
<li
tabindex="-1"
class="flex items-center p-2 text-base font-normal text-blue-700 rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
>
<DocumentArrowDown
tabindex="-1"
class="w-7 h-7 text-blue-700 transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/>
<span class="ml-3 text-left">Download in progress...</span>
</li>
{:else if download_link === true}
<li
tabindex="-1"
class="flex p-2 text-sm text-left break-all font-normal text-blue-700 rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
>
<span
>You will find the file named "{wallet_file_ready}" <br />in
your Downloads folder</span
>
</li>
{:else}
<li
tabindex="-1"
class="flex items-center text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
>
<a
href={download_link}
target="_blank"
download={wallet_file_ready}
>
<button
tabindex="-1"
class=" text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
<DocumentArrowDown
tabindex="-1"
class="w-14 h-14 transition duration-75 dark:text-white dark:group-hover:text-white"
/>
Click here to download the wallet file
</button>
</a>
</li>
{/if}
<SidebarItem label="Settings" href="#/user/settings" class="p-2">
<SidebarItem
label="Settings"
href="#/user/settings"
class="p-2 opacity-50 pointer-events-none"
disabled
>
<svelte:fragment slot="icon">
<Cog6Tooth
tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
class="w-7 h-7 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/>
</svelte:fragment>
</SidebarItem>
<SidebarItem label="Wallet" href="#/wallet" class="p-2">
<SidebarItem
label={$t("pages.wallet_info.title")}
href="#/wallet"
class="p-2"
>
<svelte:fragment slot="icon">
<PuzzlePiece
tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
class="w-7 h-7 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/>
</svelte:fragment>
</SidebarItem>
<SidebarItem label="Admin" href="#/user/admin" class="p-2">
<SidebarItem
label={$t("pages.admin.title")}
href="#/user/admin"
class="p-2 opacity-50 pointer-events-none"
>
<svelte:fragment slot="icon">
<Key
tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
class="w-7 h-7 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/>
</svelte:fragment>
</SidebarItem>
<SidebarItem label="Accounts" href="#/user/accounts" class="p-2">
<SidebarItem
label={$t("pages.accounts.title")}
href="#/user/accounts"
class="p-2"
>
<svelte:fragment slot="icon">
<User
tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
class="w-7 h-7 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/>
</svelte:fragment>
</SidebarItem>
@ -312,7 +248,7 @@
personal_site_status.server_ip +
" " +
personal_site_status.server_id) ||
"offline"}
$t("pages.user_panel.offline")}
>
<Toggle
on:change={async () => {
@ -320,14 +256,17 @@
$connections[personal_site].connecting = true;
await reconnect();
} else {
$connections[personal_site].error = "Stopped";
$connections[personal_site].error = $t(
"connectivity.stopped"
);
personal_site_status.since = new Date();
await ng.user_disconnect(personal_site);
}
}}
checked={personal_site_status &&
(personal_site_status.connecting ||
!personal_site_status.error)}>Personal</Toggle
!personal_site_status.error)}
>{$t("connectivity.personal")}</Toggle
>
</li>
{#if personal_site_status}
@ -335,12 +274,14 @@
class="site-cnx-details flex items-center px-2 text-sm text-left font-normal text-gray-600"
>
{#if personal_site_status.connecting}
Connecting...
{$t("connectivity.connecting")}...
{:else}
{#if !personal_site_status.error}
Connected
{$t("connectivity.connected")}
{:else}
{personal_site_status.error}
{$t("connectivity.connection_error_short", {
values: { error: personal_site_status.error },
})}
{/if}
<Time
style="display:contents;"
@ -363,9 +304,9 @@
>
<Gift
tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
class="w-7 h-7 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/>
<span class="ml-3">Donate to NextGraph</span>
<span class="ml-3">{$t("common.donate_nextgraph")}</span>
</li>
<li
@ -377,15 +318,17 @@
>
<InformationCircle
tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
class="w-7 h-7 text-black transition duration-75 focus:outline-none dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/>
<span class="ml-3">About NextGraph</span>
<span class="ml-3">
{$t("common.about_nextgraph")}
</span>
</li>
<li
class="flex items-center p-2 text-base font-normal text-gray-900"
>
Version: {version}
{$t("common.version", { values: { version } })}
</li>
</SidebarGroup>
</SidebarWrapper>
@ -410,27 +353,28 @@
</svg>
{#if error == "AlreadyExists"}
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5">
The user is already registered with the selected broker.<br /> Try logging
in instead
{@html $t("errors.AlreadyExists")}
</p>
<a use:link href="/">
<button
tabindex="-1"
class="text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
Login
{$t("buttons.login")}
</button>
</a>
{:else}
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5">
An error occurred:<br />{error}
{@html $t("errors.error_occurred", {
values: { message: display_error(error) },
})}
</p>
<a use:link href="/">
<button
tabindex="-1"
class="text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
Go back to homepage
{$t("buttons.back_to_homepage")}
</button>
</a>
{/if}
@ -440,9 +384,7 @@
</CenteredLayout>
<style>
li.clickable {
cursor: pointer;
}
.site-cnx-details {
@apply mt-0 !important;
}

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

Loading…
Cancel
Save