Author SHA1 Message Date
Adrien c6effdfa15 🐛 Hide preloader once the app loaded
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/audit Pipeline was successful
2025-05-02 14:51:57 +02:00
Adrien f17986fa16 Merge pull request '🐛 404 error returned on js loading' (#13) from fix/404-on-js-loading into develop
ci/woodpecker/push/dockerize Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful
Reviewed-on: #13
2025-05-02 12:02:34 +00:00
Adrien cc65e7d5ff 🐛 Let dioxus add the preload script during the building process
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/audit Pipeline was successful
2025-05-02 13:29:52 +02:00
Adrien 1f42eaa37c Merge pull request 'Ensure that the linters and builds pass before merging a PR' (#7) from ci/add-checks-on-mr into develop
ci/woodpecker/push/dockerize Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful
Reviewed-on: #7
2025-05-02 07:41:57 +00:00
Adrien acbe15ed69 👷 Split lint and audit jobs and fix woodpecker linter warns
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/audit Pipeline was successful
2025-05-02 09:18:42 +02:00
Adrien 4671a5ee51 👷 Use of the ci-lint-audit docker image
ci/woodpecker/pr/validate Pipeline was successful
2025-05-01 22:32:17 +02:00
Adrien 9e7ba84576 👷 Add Dockerfile for the ci-lint-audit image
ci/woodpecker/pr/validate Pipeline was successful
ci/woodpecker/manual/dependencies Pipeline was successful
ci/woodpecker/manual/lint-audit-image Pipeline was successful
2025-04-28 07:25:17 +02:00
Adrien a8a7b16e9f 👷 Add cargo sort-derives tool
ci/woodpecker/pr/validate Pipeline was successful
2025-04-27 22:10:27 +02:00
Adrien 8684086c74 👷 Add cargo spellcheck tool
ci/woodpecker/pr/validate Pipeline was successful
2025-04-27 21:12:32 +02:00
Adrien cd0a763c0a 👷 Fix lint - dependencies CI job
ci/woodpecker/pr/validate Pipeline was successful
2025-04-27 20:34:40 +02:00
Adrien 4d6d6d3515 👷 Add cargo udeps tool
ci/woodpecker/pr/validate Pipeline failed
2025-04-27 17:34:34 +02:00
Adrien 2bdd0b6a6d 👷 Add the cargo-deny configuration file 2025-04-27 13:27:50 +02:00
Adrien a9996d448c 👷 Run cargo deny only if we're able to build the web docker image
ci/woodpecker/pr/validate Pipeline was successful
2025-04-27 11:52:15 +02:00
Adrien d5e92f282a 👷 For now, Don't block PRs on clippy or cargo deny errors
ci/woodpecker/pr/validate Pipeline failed
2025-04-27 11:50:14 +02:00
Adrien 285d4ba590 📄 Set AGPL-3.0-or-later license
ci/woodpecker/pr/validate Pipeline failed
2025-04-27 11:36:45 +02:00
Adrien 6b8cef176f ✏️ Fix typo 2025-04-27 11:36:27 +02:00
Adrien 60756b7e72 👷 Add cargo deny tool
ci/woodpecker/pr/validate Pipeline was successful
2025-04-27 11:08:34 +02:00
Adrien 18ee33d512 💚 Add credential to pull images from our private Docker registry
ci/woodpecker/manual/dependencies Pipeline was successful
ci/woodpecker/pr/validate Pipeline was successful
2025-04-27 04:27:03 +02:00
Adrien e2c20e4c64 💚 Dry run to ensure that we're able to build the web Docker image
ci/woodpecker/pr/validate Pipeline failed
2025-04-27 04:23:29 +02:00
Adrien 28aa250f58 💚 Use the custom dioxus-cli image to build the webclient dockerfile
ci/woodpecker/pr/validate Pipeline failed
2025-04-27 03:46:57 +02:00
Adrien d29a3a0821 👷 Restore validation trigger
ci/woodpecker/pr/validate Pipeline failed
2025-04-21 18:20:15 +02:00
Adrien 570c792bf4 👷 Test the build for web on PR
ci/woodpecker/manual/dependencies Pipeline was successful
2025-04-21 17:40:06 +02:00
Adrien fb37125740 Merge pull request 'Configure Renovate' (#3) from renovate/configure into develop
ci/woodpecker/push/dockerize Pipeline failed
ci/woodpecker/push/deploy unknown status
ci/woodpecker/cron/dependencies Pipeline was successful
Reviewed-on: #3
2025-04-21 13:19:25 +00:00
renovate-bot 6ce395cf7c Add renovate.json
ci/woodpecker/pr/validate Pipeline was successful
2025-04-21 13:11:42 +00:00
Adrien a4eae624d3 👷 Retry to fix renovate git auth issue by setting RENOVATE_GIT_AUTHOR
ci/woodpecker/push/dockerize Pipeline failed
ci/woodpecker/push/deploy unknown status
ci/woodpecker/cron/dependencies Pipeline was successful
2025-04-21 15:10:57 +02:00
Adrien f53e4fbadf 👷 Try to fix renovate git auth issue by setting RENOVATE_GIT_AUTHOR
ci/woodpecker/push/dockerize Pipeline failed
ci/woodpecker/push/deploy unknown status
ci/woodpecker/cron/dependencies Pipeline failed
2025-04-21 15:06:59 +02:00
Adrien b4bc48d576 👷 Remove the dry run option for renovate
ci/woodpecker/push/dockerize Pipeline failed
ci/woodpecker/push/deploy unknown status
ci/woodpecker/cron/dependencies Pipeline failed
2025-04-21 14:46:36 +02:00
Adrien b435bc73a7 👷 Try to fix renovate token issue (fix RENOVATE_ENDPOINT)
ci/woodpecker/push/dockerize Pipeline was successful
ci/woodpecker/cron/dependencies Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful
2025-04-21 14:02:36 +02:00
Adrien a741c6ea8e 👷 Try to fix renovate token issue (move RENOVATE_TOKEN to env
ci/woodpecker/push/dockerize Pipeline was successful
ci/woodpecker/cron/dependencies Pipeline failed
ci/woodpecker/push/deploy Pipeline was successful
2025-04-21 13:44:01 +02:00
Adrien c761b203cc 👷 Try to fix renovate token issue (Upercase renovate_token)
ci/woodpecker/cron/dependencies Pipeline failed
2025-04-21 13:39:16 +02:00
Adrien d85a2a97b7 👷 Remove command from the dependency scanning job
ci/woodpecker/push/dockerize Pipeline was successful
ci/woodpecker/cron/dependencies Pipeline failed
ci/woodpecker/push/deploy Pipeline was successful
2025-04-21 12:40:42 +02:00
Adrien 403d238463 👷 First try adding renovate to the CI 2025-04-21 12:37:51 +02:00
Adrien 581a3d159a ⬆️ Bump modx version (0.1.2 -> 0.1.4)
ci/woodpecker/push/dockerize Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful
2025-04-21 11:03:34 +02:00
Adrien 4bbe863a56 ⬆️ Bump Dioxus version (0.6.1 -> 0.6.3)
ci/woodpecker/push/dockerize Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful
2025-04-21 10:14:54 +02:00
Adrien 722d98f5d1 ♻️ Use of the assets management introduced by Dioxus 0.6.0
ci/woodpecker/push/dockerize Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful
Cf. https://dioxuslabs.com/blog/release-060#stabilizing-manganis-asset-system
2025-04-21 09:43:25 +02:00
Adrien 219fac87b1 ⬆️ Bump rust version (1.83 -> 1.86)
ci/woodpecker/push/dockerize Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful
2025-04-20 23:45:48 +02:00
Adrien c9deeea36f 🚧 Try to fix CI after the bump of the woodpecker version
ci/woodpecker/push/dockerize Pipeline failed
ci/woodpecker/push/deploy unknown status
2025-04-20 23:28:55 +02:00
Adrien d19a8a7f7d 🚧 Try to fix CI dockerize job 2025-04-20 23:11:59 +02:00
Adrien 5bc8ac409e ⬆️ Force the commit used for matrix-sdk (fa6066b8)
ci/woodpecker/push/dockerize Pipeline failed
ci/woodpecker/push/deploy unknown status
2024-12-29 16:53:12 +01:00
Adrien 6586edf287 ⬆️ Bump dioxus version (main -> 0.6.1)
ci/woodpecker/push/dockerize Pipeline failed
ci/woodpecker/push/deploy unknown status
2024-12-29 16:25:30 +01:00
Adrien a533f1869d 🐛 Disable caching for dockerize
ci/woodpecker/push/dockerize Pipeline failed
ci/woodpecker/push/deploy unknown status
2024-09-25 23:35:01 +02:00
Adrien 6b9ef5dc90 🐛 Don't run validation on commit into default branch
ci/woodpecker/push/dockerize Pipeline failed
ci/woodpecker/push/deploy unknown status
2024-09-25 22:59:24 +02:00
Adrien 93b6027c73 🐛 Use dioxus-cli:6ff7a54 image to dockerize
ci/woodpecker/push/validate Pipeline failed
ci/woodpecker/push/dockerize unknown status
ci/woodpecker/push/deploy unknown status
2024-09-25 22:17:59 +02:00
Adrien 8026b6fa32 🐛 Use stable dioxus-cli
ci/woodpecker/push/validate Pipeline was successful
ci/woodpecker/push/dockerize Pipeline failed
ci/woodpecker/push/deploy unknown status
2024-09-25 21:04:35 +02:00
Adrien 8652d56f51 🐛 Fix wasm-bindgen-cli version in Dockerfile
ci/woodpecker/push/validate Pipeline was successful
ci/woodpecker/push/dockerize Pipeline failed
ci/woodpecker/push/deploy unknown status
2024-09-25 00:11:12 +02:00
Adrien c5045c328c Merge pull request 'Run validation step on push into default branch (2)' (#2) from fix/ci-add-first-checks into develop
ci/woodpecker/push/validate Pipeline was successful
ci/woodpecker/push/dockerize Pipeline failed
ci/woodpecker/push/deploy unknown status
Reviewed-on: #2
2024-09-24 21:37:11 +00:00
Adrien ba96ad77d3 👷 Run validation step on push into default branch
ci/woodpecker/pr/validate Pipeline was successful
2024-09-24 23:18:44 +02:00
Adrien 5ff06c93f4 Merge pull request 'Run tests on PR' (#1) from fix/ci-add-first-checks into develop
Reviewed-on: #1
2024-09-24 21:12:34 +00:00
Adrien 831085e8b6 👷 Disable build step on MR
ci/woodpecker/pr/validate Pipeline was successful
2024-09-24 22:56:04 +02:00
Adrien 691dc7572a 🚨 Fix some clippy warnings
ci/woodpecker/pr/validate Pipeline was successful
2024-09-23 22:16:55 +02:00
Adrien b728f6efcd 👷 Install deps
ci/woodpecker/pr/validate Pipeline failed
2024-09-23 21:58:07 +02:00
Adrien d8e3d49d95 👷 Install clippy
ci/woodpecker/pr/validate Pipeline failed
2024-09-23 20:52:08 +02:00
Adrien 9b2ab337b2 👷 Install rustfmt
ci/woodpecker/pr/validate Pipeline failed
2024-09-23 20:50:51 +02:00
Adrien deb3e273f4 👷 Add format and clippy steps
ci/woodpecker/pr/validate Pipeline failed
2024-09-23 20:47:23 +02:00
Adrien abea905feb 👷 Check that no error occurs during the build of the webapp
ci/woodpecker/pr/validate Pipeline was successful
2024-09-22 23:40:22 +02:00
Adrien c7955d5571 Merge branch 'fix/invalid-wysiwyg-dep' into develop
ci/woodpecker/push/dockerize Pipeline failed
ci/woodpecker/push/deploy unknown status
2024-09-22 22:50:16 +02:00
Adrien f99296bdce 🐛 Fix Cargo.toml 2024-09-22 22:49:09 +02:00
Adrien 7989d86af1 Merge branch 'conversations-panel' into develop
ci/woodpecker/push/dockerize Pipeline failed
ci/woodpecker/push/deploy unknown status
2024-09-22 21:19:37 +02:00
Adrien d2108fa6fc ♻️ Remove duplicated if statement 2024-09-22 19:39:06 +02:00
Adrien 44ba3d4d23 🐛 script_include is not used anymore (since Dioxus #2258) 2024-09-22 19:36:43 +02:00
Adrien 5206fb13c8 ♻️ Use of manganis for wallpaper pattern 2024-09-22 18:51:48 +02:00
Adrien 27934c7fc9 🚨 Fix some clippy warnings 2024-09-08 16:12:33 +02:00
Adrien 9d95bd4481 Add the capability to join a conversation 2024-09-08 16:07:13 +02:00
Adrien 648be8ba72 🐛 Add tail div to dynamic conversation_panels to fix some side effects 2024-09-07 13:01:26 +02:00
Adrien b7b98dff15 Add TabsBar to the LayoutBig (Conversations layout) 2024-09-07 12:58:39 +02:00
Adrien aaafa91cbe ⬆️ Bump turf version (0.8.0 -> 0.9.3) 2024-08-21 23:29:35 +02:00
Adrien 9a5f7ae504 ⬆️ Use of Dioxus main branch instead of 0.5 release 2024-08-21 23:29:33 +02:00
Adrien d5d996eec3 Add topic to the UI store Room 2024-06-27 08:25:11 +02:00
Adrien 73c5b70ba8 🚧 First design template for ConversationOptionsMenu 2024-06-22 20:57:44 +02:00
Adrien f0463213cf 🚧 Use of the use_long_press to open a future room configuration menu 2024-06-22 20:55:19 +02:00
Adrien e55992aed5 💄 Add Reject and Join buttons 2024-06-22 20:52:08 +02:00
Adrien cea60ce695 Add ui use_long_press hook 2024-06-15 16:19:20 +02:00
Adrien 271e865d40 💄 Open/close ChatPanel on click (Conversations layout/big breakpoint) 2024-06-09 12:31:18 +02:00
Adrien ffe759e749 ♻️ Merge Space and HomeSpace components 2024-06-09 11:22:48 +02:00
Adrien 9baa7f290a 💄 Open/close ChatPanel on click 2024-06-09 09:43:46 +02:00
Adrien d566a4927f ♻️ Use of the SCSS variables to compute the width of the inner panels 2024-06-08 13:04:17 +02:00
Adrien 1ad4d444fb 💄 Add first breakpoint management by Conversations layout 2024-06-07 22:18:15 +02:00
Adrien 204e11b8b1 🐛 tracing_forest crate isn't used for wasm 2024-05-26 18:09:05 +02:00
Adrien f566d88df2 Merge branch 'conversations-panel' into develop
ci/woodpecker/push/dockerize Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful
2024-05-26 12:00:55 +02:00
Adrien 89473cfd61 🚨 Fix some clippy warnings 2024-05-26 11:53:47 +02:00
Adrien 62015f8d13 Use of Conversations layout 2024-05-26 11:39:18 +02:00
Adrien c8e8e2da67 Add a first Conversations layout 2024-05-26 11:17:37 +02:00
Adrien df32faa8e6 🐛 Dioxus Props shall implement PartialEq and Clone traits 2024-05-26 10:58:51 +02:00
Adrien 5194899de0 Add a first Conversations component 2024-05-26 10:45:09 +02:00
Adrien ff0ac7f982 🚧 Add a ChatPanel placeholder component 2024-05-25 14:06:40 +02:00
Adrien 8ed4ff3f2a ♻️ Rename views -> layouts 2024-05-24 22:35:10 +02:00
Adrien e7e1a4d663 💄 Use of border-box box sizing
This makes calculating the size of the UI components easier, especially those which have padding, margin or border.
2024-05-23 08:45:51 +02:00
Adrien 19d64d7ac5 ♻️ Render Room avatar using the RoomMember ones, if not set 2024-05-22 16:44:07 +02:00
Adrien 35e191eb62 🗑️ WorkerTask::GetRoomMembers isn't used, remove it 2024-05-22 16:21:13 +02:00
Adrien 8c244ce4a7 ⬆️ Use dioxus main branch for now 2024-05-22 15:32:25 +02:00
Adrien a1fe74f53e ♻️ Use of "target_family" instead of feature to manage wasm platform 2024-05-21 12:40:19 +02:00
Adrien f43f54c120 🚧 Use of local Dioxus repos waiting the #2338 PR merge
Cf https://github.com/DioxusLabs/dioxus/pull/2338
2024-05-21 12:35:00 +02:00
Adrien cd6506fb03 🔊 Trace events from Matrix client callbacks to domain methods 2024-05-21 12:26:40 +02:00
Adrien b5da0ee992 ♻️ Use of cfg_if to manage how to logging according to the platform 2024-05-18 22:03:21 +02:00
Adrien df2d924c65 Cleanup dependencies 2024-05-18 22:00:28 +02:00
Adrien 54c7073b98 💡 Add comments to keep in mind why we can't send Matrix sdk Room 2024-05-18 09:52:40 +02:00
Adrien fdae149c4a 🚧 Add Avatar management and refresh the Matrix client part 2024-05-17 22:41:35 +02:00
Adrien 0b898dce52 Add Invitation value object 2024-05-17 09:31:59 +02:00
Adrien cbe32c250e 🚧 Add relations between store::Room and store::Area 2024-05-16 22:46:59 +02:00
Adrien d77c2a9d12 ♻️ Use of Store interfaces 2024-05-15 19:08:52 +02:00
Adrien bc30670f6e ⬆️ Bumps modx version (git -> 0.1.2) 2024-05-15 19:08:49 +02:00
Adrien 18a797bc3f ♻️ Add Account, Room and Space UI store structs 2024-05-11 18:23:48 +02:00
Adrien bc6b02bc34 ♻️ Rework the Matrix messaging Client 2024-05-10 22:32:35 +02:00
Adrien 0a936dd12b ♻️ Rework the Matrix messaging Requester 2024-05-10 22:20:32 +02:00
Adrien ef41c0bd48 Add events shared by Matrix client and Requester 2024-05-10 22:20:11 +02:00
Adrien e3a6ec9858 Add new messaging WorkerTask 2024-05-10 22:16:49 +02:00
Adrien 692a71faef 🚨 Fix clippy warnings 2024-05-10 20:11:48 +02:00
Adrien c2918fbc78 🚧 Add RoomMember value object 2024-05-10 19:56:39 +02:00
Adrien bfa1539d23 🚧 Add Space identity 2024-05-10 19:29:42 +02:00
Adrien 0190cf9165 ♻️ Rework the Room entity 2024-05-10 19:13:46 +02:00
Adrien 4f9e5c538e 🚧 Add a first version of the mozaik builder service 2024-05-10 18:56:54 +02:00
Adrien 79e8dea622 🚧 Add Account identity and messaging and store interfaces 2024-05-10 18:56:37 +02:00
Adrien 0a0d6e745b 🚧 Remove AvatarSelector component 2024-05-09 22:12:32 +02:00
Adrien 32b633aad6 🚧 Remove Header component 2024-05-08 10:27:01 +02:00
Adrien 4ea4416165 🚧 Remove Loading component 2024-05-08 10:23:18 +02:00
Adrien c4dcb0f87d 🚧 Remove ChatsWindow and ContactsWindow components 2024-05-05 22:20:25 +02:00
Adrien f79ebb0b03 Add SearchIcon, SpacesIcon, ChatsIcon and RoomsIcon elements 2024-04-26 19:42:49 +02:00
Adrien 7078f86cd8 💄 Make the "logo" shape reusable 2024-04-26 19:31:05 +02:00
Adrien 894f32e177 💄 Adjust Login padding 2024-04-26 19:27:11 +02:00
Adrien 3afed02aa8 🚧 Make Button usable outside of the button.rs file 2024-04-26 19:23:34 +02:00
Adrien 7b6781a007 🐛 Fix gap in wallpaper pattern
ci/woodpecker/push/dockerize Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful
2024-04-23 11:35:31 +02:00
Adrien dfe2761a3a Merge branch 'fix/login-invalid-width-on-mobile' into develop
ci/woodpecker/push/dockerize Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful
2024-04-22 15:55:58 +02:00
Adrien 58e12c991d 🐛 Fix the height and radius of the inputs 2024-04-22 15:51:01 +02:00
Adrien badd541424 Merge branch 'fix/login-invalid-width-on-mobile' into develop
ci/woodpecker/push/dockerize Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful
2024-04-22 15:05:52 +02:00
Adrien fcf3d92cf9 🐛 Fix a view in charge to set the Login dimesions according to the screen aspect-ratio 2024-04-22 15:04:26 +02:00
Adrien 6172167ea8 Add a parameter to the Wallpaper widget to show the app version 2024-04-22 14:44:51 +02:00
Adrien 7170332205 Merge branch 'fix/loading-invalid-spinner-position' into develop
ci/woodpecker/push/dockerize Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful
2024-04-22 09:02:38 +02:00
Adrien a3775e35d3 🐛 Update to match to the dioxus_free_icons view_box and xmlns definition 2024-04-22 08:58:29 +02:00
Adrien 724d04c592 Merge branch 'fix/loading-invalid-spinner-position' into develop
ci/woodpecker/push/dockerize Pipeline failed
ci/woodpecker/push/deploy unknown status
2024-04-21 23:12:30 +02:00
Adrien b3330abecc 🐛 The loading spinner isn't aligned with the wallpaper on mobile 2024-04-21 23:08:34 +02:00
Adrien eecb46e4b8 🐛 Displayed versions are always computed as dirty
ci/woodpecker/push/dockerize Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful
2024-04-16 23:21:19 +02:00
Adrien de9d2b3a8a 👷 Run dockerize and deploy CI steps on commits on default branch
ci/woodpecker/push/dockerize Pipeline failed
ci/woodpecker/push/deploy unknown status
2024-04-16 07:29:09 +02:00
Adrien f0d3b91084 🧑‍💻 Add version to the Wallpaper
ci/woodpecker/push/dockerize Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful
This information should be moved to a version panel, when it'll be available...
2024-04-16 07:01:49 +02:00
Adrien cae7a1e244 👷 Optimize building process using the homemade dioxus-cli docker image
ci/woodpecker/push/dockerize Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful
2024-04-15 23:18:40 +02:00
Adrien 6f95e0f57b 👷 Use of the CI to deliver a new image on each commit (temp)
ci/woodpecker/push/dockerize Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful
2024-04-15 22:04:25 +02:00
Adrien d4af06d687 🐛 Fix wallpaper-pattern misalignment 2024-04-12 16:59:45 +02:00
Adrien 53fff64537 🐛 Fix invalid font used in login form on (web platform) 2024-04-12 16:24:28 +02:00
Adrien fb4554aa71 🚀 Update Dockerfile to take the new code organization into account 2024-04-12 14:44:44 +02:00
Adrien 741124e47e ️ Configure cargo to Optimize the size of the generated wasm bundle 2024-04-12 14:42:41 +02:00
Adrien 9951c2fea6 🔥 Remove the use of LoadingPage component
For now, the LoadingPage component is kept, just in case.
2024-04-12 14:41:31 +02:00
Adrien df33e94a12 Add web page to display during the wasm bundle 2024-04-12 14:33:10 +02:00
Adrien 78cb65e054 ♻️ Move fonts and images directories in a common public on 2024-04-12 12:49:07 +02:00
Adrien 77fa0c5fd5 🚀 Add the Dockerfile for the web app 2024-04-11 08:32:57 +02:00
Adrien d3a35cd81f ️ Configure turf to minify the generated CSS files 2024-04-11 08:31:33 +02:00
Adrien b524048563 ⬆️ Bump turf release (0.7.0 -> 0.8.*) 2024-04-11 08:30:29 +02:00
Adrien 4ab4ac5fee ⬆️ Use of the latest matrix-sdk 0.7.* version (master branch before) 2024-04-11 08:30:17 +02:00
Adrien 438416bec1 Merge branch 'web-client' into develop 2024-04-10 17:23:24 +02:00
Adrien c580fba315 ♻️ Add Room domain entity 2024-04-10 17:14:26 +02:00
Adrien a7bccfa779 ♻️ Add Session domain entity 2024-04-10 12:50:15 +02:00
Adrien eb81b3252c Enable on tokio rt and sync features (disable default ones) 2024-04-10 12:41:30 +02:00
Adrien 880195109d ⬆️ Bump dioxus version 2024-04-10 12:41:29 +02:00
Adrien 11e239714b Reuse tracing library to be able to display matrix SDK logs 2024-04-10 12:41:05 +02:00
Adrien 4261e24cd2 ♻️ Clean Cargo.toml file and add target specific dependencies 2024-04-06 12:18:48 +02:00
Adrien 9cfc0841df 💄 Fix some rendering inconsistencies 2024-04-06 12:16:18 +02:00
Adrien 39ff4122c9 🐛 Svg generated using dicebear shall use unique ids 2024-04-06 12:13:10 +02:00
Adrien 46ce890718 ♻️ Make random_svg_generators able to get placeholder according to the target 2024-04-06 12:07:29 +02:00
Adrien 82b15a5509 💄 Manage config per target and remove menu bar from the desktop one 2024-04-06 12:02:43 +02:00
Adrien 912b67ed23 🐛 Remove unused tokio::time import 2024-04-06 11:55:32 +02:00
Adrien 0ec1187fc3 ♻️ Replace tracing dependency with dioxus-logger
tracing package doesn't support web platform when dioxus-logger `will eventually support every target that Dioxus
does. Currently only web and desktop platforms are supported.`
2024-04-06 11:51:46 +02:00
Adrien f78765e553 ⬆️ Bump dioxus-sdk version (0.5.0)
The dioxus-std has been renamed to dioxus-sdk
2024-04-06 11:37:43 +02:00
Adrien b26cb1d982 Use async-std to sleep asynchronously (previously done with tokio) 2024-04-05 17:23:48 +02:00
Adrien fc9411376c Remove dioxus-desktop dependency 2024-04-05 17:14:37 +02:00
Adrien df465d99c0 Disable matrix-sdk unused and default features 2024-04-05 17:13:22 +02:00
Adrien 491e34903f 🔧 Add Dioxus.toml file 2024-04-05 17:06:28 +02:00
Adrien 0c1df908f2 Merge branch 'dioxus-0.5.0' into develop 2024-04-05 16:26:32 +02:00
Adrien d245169345 🚸 Make desktop the default target 2024-04-05 16:16:19 +02:00
Adrien 9eaf79208e Merge branch 'clean-redesign' into develop 2024-04-04 14:39:04 +02:00
Adrien 0ce0764204 🎨 Isolate infra and ui components
The src/base.rs is still to be reworked.
2024-04-04 14:27:58 +02:00
Adrien 92bf860101 Merge branch 'dioxus-0.5.0' into develop 2024-04-01 23:47:13 +02:00
Adrien 014a0c2c57 🎨 Put svg image generation in a dedicated datasource 2024-04-01 19:32:35 +02:00
Adrien 9071b0073c ⬆️ Update the components to take the dioxus 0.5 rework into account 2024-03-31 23:26:10 +02:00
Adrien aad0064a0c Merge branch 'redesign-login-form' into develop 2024-03-30 18:31:15 +01:00
Adrien 83fe388e8d 🚨 Fix clippy warnings 2024-03-30 18:24:04 +01:00
Adrien 448b81b65d Add Login component 2024-03-30 17:40:17 +01:00
Adrien 4e963ce063 🎨 Factorize the definition of the Button components 2024-03-30 14:37:44 +01:00
Adrien cf9737fc76 Add Modal component 2024-03-30 13:46:53 +01:00
Adrien 5c91df206c 💄 Store colors in a nested map to make them reachable using criteria 2024-03-30 08:23:52 +01:00
Adrien 0ab6aaac1c Add PasswordTextInput component
The TextInput component has been reworked to factorize some pieces of code with PasswordTextInput.
2024-03-21 21:12:49 +01:00
Adrien 89b1f10b6e Add Pyramid icon 2024-03-21 21:05:04 +01:00
Adrien 570a969cee 💄 Fix conflicts regarding the generated CSS class names 2024-03-21 18:32:40 +01:00
Adrien ceeda1a771 Redesign Login component and add fields validation 2024-03-15 14:58:58 +01:00
Adrien fc0d3b1212 ♻️ Replace constcat with const_format 2024-03-15 12:33:25 +01:00
Adrien 01f589e789 Add helper_text to TextInput (previously TextEdit) 2024-03-15 12:24:42 +01:00
Adrien c746fb6552 💄 Rework Login component 2024-03-10 12:02:18 +01:00
Adrien 1073a592ed 🔨 Make the Design System color tokens reachable from the rust code
The design system tokens are specified in the _base.scss file. To avoid to duplicate their value in a rust file, a new
step has been added to the building process to generate the `style_vars.rs` file which make the color tokens reachable
to the app.
2024-03-10 11:40:00 +01:00
Adrien dd0754073c Add TextField component 2024-03-10 11:35:25 +01:00
Adrien b05e3efce4 🐛 Add SVG pattern file used to render the Wallpaper 2024-03-10 11:01:33 +01:00
Adrien 0a4969e079 💄 Center and uppercase the content of the Register and Login buttons 2024-03-10 10:42:02 +01:00
Adrien f52733d9a6 💄 Add buttons which will be used by the Login component 2024-03-09 22:46:00 +01:00
Adrien bb56d24f02 💄 Add border variables to the base SCSS file 2024-03-09 22:44:04 +01:00
Adrien 043a721429 Make Spinner animation suspendable 2024-03-09 13:04:01 +01:00
Adrien 46c251ef90 ♻️ Make Spinner reusable (not only by loading view) 2024-03-03 23:35:09 +01:00
Adrien 257b36eae1 ✏️ Fix typo in color names 2024-03-03 23:31:00 +01:00
Adrien ff430edffe 💄 Use Geist font everywhere. 2024-03-03 23:29:49 +01:00
Adrien 5e05b75bde 💄 Add Wallpaper, Spinner and LoadingPage widgets 2024-02-29 23:47:32 +01:00
Adrien 5719cb8254 💄 Add DS colors 2024-02-29 23:46:20 +01:00
Adrien 921003aeac Merge branch 'readme' into develop 2024-01-07 13:30:04 +01:00
Adrien 8ffc977846 📝 Add the reference to the MSN messenger client 2024-01-07 13:28:27 +01:00
Adrien 6e64eb4d97 📝 Add the description of the technical stack to the README 2024-01-07 13:25:52 +01:00
150 changed files with 8231 additions and 2543 deletions
+8
View File
@@ -0,0 +1,8 @@
[profile.release]
opt-level = "z"
debug = false
lto = true
codegen-units = 1
panic = "abort"
strip = true
incremental = false
+5
View File
@@ -0,0 +1,5 @@
# .git directory is not filtered here: we need to copy the .git directory in the builder image to compute the version.
# media directory, README.md and Dockerfile files are not filtered to avoid the dockerized building env to get dirty
# and append the "-modified" suffix to the displayed version.
dist
target
+18
View File
@@ -0,0 +1,18 @@
variables:
- shared-config: &shared-config
image: rg.fr-par.scw.cloud/asr-projects/beau-gosse-du-92/ci-lint-audit:latest
pull: true
steps:
- name: dependencies
<<: *shared-config
commands: |
cargo deny check
# Not ready to block PR on fail
failure: ignore
when:
- event: pull_request
depends_on:
- lint
+18
View File
@@ -0,0 +1,18 @@
steps:
- name: renovate
image: renovate/renovate
pull: true
commands:
- renovate $${CI_REPO}
environment:
RENOVATE_PLATFORM: gitea
RENOVATE_ENDPOINT: https://git.adrien.run
RENOVATE_GIT_AUTHOR: renovate-bot <renovate-bot@adrien.run>
RENOVATE_TOKEN:
from_secret: renovate-bot-mr-token
LOG_LEVEL: debug
when:
- event: cron
cron: renovate
- event: manual
+25
View File
@@ -0,0 +1,25 @@
steps:
deploy:
image: euryecetelecom/woodpeckerci-kubernetes
settings:
kubernetes_server:
from_secret: kubernetes_server
kubernetes_token:
from_secret: kubernetes_token
kubernetes_cert:
from_secret: kubernetes_cert
namespace: bg92
wait: true
wait_timeout: 60s
force: true
deployment: beau-gosse-du-92-web
repo: rg.fr-par.scw.cloud/asr-projects/beau-gosse-du-92-web
container: beau-gosse-du-92-web
tag: ${CI_COMMIT_SHA}
when:
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
depends_on:
- dockerize
+19
View File
@@ -0,0 +1,19 @@
steps:
dockerize:
image: woodpeckerci/plugin-kaniko
settings:
registry: rg.fr-par.scw.cloud
repo: asr-projects/beau-gosse-du-92-web
tags: ${CI_COMMIT_SHA}
auto_tag: true
cache: false
username: nologin
password:
from_secret: registry-password
when:
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
# depends_on:
# - validate
+17
View File
@@ -0,0 +1,17 @@
steps:
dockerize:
image: woodpeckerci/plugin-kaniko
settings:
registry: rg.fr-par.scw.cloud
repo: asr-projects/beau-gosse-du-92/ci-lint-audit
dockerfile: ./docker/Dockerfile.ci-lint-audit
tags: latest
auto_tag: false
cache: false
username: nologin
password:
from_secret: registry-password
when:
- event: push
path: ./docker/Dockerfile.ci-lint-audit
+45
View File
@@ -0,0 +1,45 @@
variables:
- shared-config: &shared-config
image: rg.fr-par.scw.cloud/asr-projects/beau-gosse-du-92/ci-lint-audit:latest
pull: true
steps:
- name: format
<<: *shared-config
commands: |
cargo fmt --all --check
- name: sort derives
<<: *shared-config
commands: |
cargo sort-derives --check
- name: clippy
<<: *shared-config
commands: |
cargo clippy --all --all-features -- -D warnings
# Not ready to block PR on fail
failure: ignore
- name: spellcheck
<<: *shared-config
commands: |
cargo spellcheck
- name: dependencies
<<: *shared-config
commands: |
cargo udeps
- name: dockerizable (web)
image: woodpeckerci/plugin-kaniko
settings:
registry: rg.fr-par.scw.cloud
repo: asr-projects/beau-gosse-du-92-web
username: nologin
password:
from_secret: registry-password
dry-run: true
when:
- event: pull_request
+73 -21
View File
@@ -2,32 +2,84 @@
name = "beau-gosse-du-92" name = "beau-gosse-du-92"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
license = "AGPL-3.0-or-later"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features]
default = []
[package.metadata.spellcheck]
config = "./spellcheck.toml"
[dependencies] [dependencies]
dioxus = "0.4.3" # Errors
dioxus-desktop = "0.4.3"
dioxus-free-icons = { version = "0.7.0", features = ["material-design-icons-navigation", "ionicons"] }
dioxus-std = { version = "0.4.1", features = ["utils"] }
fermi = { version = "0.4.3" }
# matrix-sdk = { version = "0.6.2", features = ["js"] }
matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk.git", branch = "main" , features = ["js"]}
anyhow = "1.0.75" anyhow = "1.0.75"
url = "2.5.0"
dirs = "5.0.1"
ctrlc-async = "3.2.2"
tracing-subscriber = "0.3.18"
thiserror = "1.0.50" thiserror = "1.0.50"
turf = "0.7.0"
tokio = "1.34.0" # Async
log = "0.4.20" async-std = "1.12.0"
tracing = "0.1.40" async-trait = "0.1.80"
futures-util = "0.3.29"
futures = "0.3.29" futures = "0.3.29"
futures-util = "0.3.29"
tokio = { version = "1.34.0", default-features = false, features = ["rt", "sync"] }
tokio-stream = "0.1.15"
[build] # Utils
target = "x86_64-unknown-linux-gnu" base64 = "0.22.0"
const_format = "0.2.32"
rand = "0.8.5"
validator = { version = "0.17.0", features = ["derive"] }
# Http client
reqwest = "0.11.24"
# Password strength estimation
zxcvbn = "2.2.2"
# Image processing/conversion
image = "0.25.1"
# Get the application version
git-version = "0.3.9"
# Conditional compilation
cfg-if = "1.0.0"
# Logging/tracing
tracing = "0.1.40"
tracing-forest = "0.1.6"
# SCSS -> CSS + usage in rust code
turf = "0.9.3"
# Dioxus
dioxus-free-icons = { version = "0.9", features = ["ionicons", "font-awesome-solid"] }
modx = "0.1.4"
[target.'cfg(target_family = "wasm")'.dependencies]
# Logging/tracing
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
tracing-web = "0.1.3"
# Dioxus
dioxus = { version = "0.6.3", features = ["web"] }
web-sys = "0.3.69"
# Matrix
matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk.git", rev = "fa6066b8", default-features = false, features = ["rustls-tls", "js"] }
[target.'cfg(not(target_family = "wasm"))'.dependencies]
# Utils
time = "0.3.36"
# Logging/tracing
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "time"] }
# Dioxus
dioxus = { version = "0.6.3", features = ["desktop"] }
# Matrix
matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk.git", rev = "fa6066b8", default-features = false, features = ["rustls-tls"] }
[build-dependencies]
regex = "1.10.3"
[package.metadata.turf]
minify = true
[package.metadata.turf.class_names]
template = "<original_name>--<id>"
+13
View File
@@ -0,0 +1,13 @@
[application]
name = "beau-gosse-du-92"
default_platform = "desktop"
[web.app]
title = "BG92"
[web.watcher]
reload_html = true
watch_path = ["Dioxus.toml", "public/index.html", "src"]
[[web.proxy]]
backend = "http://localhost:8000/api/"
+24
View File
@@ -0,0 +1,24 @@
FROM rg.fr-par.scw.cloud/asr-projects/dioxus-cli-0.6.3:latest AS builder
ARG JOBS_NB=${JOBS_NB:-default}
# Disable incremental compilation
# Cf. https://doc.rust-lang.org/cargo/reference/profiles.html#incremental
ARG CARGO_INCREMENTAL=0
WORKDIR /usr/src/beau-gosse-du-92
# git is required by the git-version crate
RUN apt update \
&& apt install -y --no-install-recommends git \
&& apt clean
COPY . .
RUN dx build -r --platform web -- -j ${JOBS_NB}
FROM nginx:mainline-alpine-slim
WORKDIR /usr/share/nginx/html
COPY --from=builder /usr/src/beau-gosse-du-92/target/dx/beau-gosse-du-92/release/web/public .
+41 -1
View File
@@ -1,3 +1,43 @@
# Beau-gosse-du-92 # 🚧 Beau-gosse-du-92 🚧
The goal of this project is to propose a new open-source implementation of the famous MSN messenger instant-messaging client.
[![Presentation](medias/presentation.png)](medias/presentation.mp4) [![Presentation](medias/presentation.png)](medias/presentation.mp4)
# Technical stack
## Back-end
This project is based on the [Matrix.org](https://matrix.org/) building blocks (back-end and front-end SDK) to avoid to
reinvent the wheel. This solution provides:
- [Open-source protocol](https://spec.matrix.org/v1.9/).
- Features expected for a messaging solution in 2024 (multi-devices management, emojis, integrations, redaction,
spaces, ...).
- Multi-platforms clients (Android, iOS and web-client).
- SDK available for each platform and a new Rust SDK supporting all the previously listed platforms.
- Conference stack ([Element Call](https://github.com/element-hq/element-call)).
- End-to-end encryption.
- Federation management.
- Capability to host all the back-end infrastructure by ourself.
## Front-end
First, the project involves writing a client compatible with the [Matrix.org (client-server
API)](https://spec.matrix.org/v1.9/client-server-api/) protocol.
Even if the Rust SDK is still in beta, it seems to be the future one (cf. [Element X - experience the future of
Element!](https://element.io/blog/element-x-experience-the-future-of-element/)) and a good choice for someone starting a
new client... from my point of view.
The SDK chosen, a Rust (to avoid to use the bindings provided by the matrix-rust-sdk and mostly because I want to
learn Rust) graphical library should be selected. The [Dioxus](https://dioxuslabs.com/) one seems to do the job:
- React-inspired library for Rust.
- Multi-platforms (use of Web-view or WGPU-enabled renderers).
# TODO
- [ ] Test dioxus-radio.
- [ ] Design system ?
- [ ] Implement MSN messenger features using Matrix.org SDK...
+152 -1
View File
@@ -1,5 +1,156 @@
use std::env;
use std::fmt::Display;
use std::fs::File;
use std::io::Write;
use std::io::{self, BufRead};
use std::path::Path;
use std::path::PathBuf;
use regex::Regex;
fn main() { fn main() {
// Tell Cargo to rerun this build script if any SCSS file // Tell Cargo to rerun this build script if any SCSS file
// in the 'src' directory or its subdirectories changes. // in the 'src' directory or its subdirectories changes.
println!("cargo:rerun-if-changed=src/**/*.scss"); println!("cargo:rerun-if-changed=src/ui/**/*.scss");
let out_dir = env::var("OUT_DIR").unwrap();
// let mut tasks = Vec::new();
let tasks = vec![
// Global tokens
Task::new(
PathBuf::from("src/ui/_base.scss"),
Path::new(&out_dir).join("style_tokens.rs"),
"style".to_string(),
),
// variables defined by the Panel component
Task::new(
PathBuf::from("src/ui/components/_panel.scss"),
Path::new(&out_dir).join("style_component_panel.rs"),
"panel".to_string(),
),
// Variables set by the Conversations layout
Task::new(
PathBuf::from("src/ui/layouts/conversations.scss"),
Path::new(&out_dir).join("style_layout_conversations.rs"),
"conversations".to_string(),
),
];
export_variables(tasks)
}
// From https://doc.rust-lang.org/rust-by-example/std_misc/file/read_lines.html
// The output is wrapped in a Result to allow matching on errors.
// Returns an Iterator to the Reader of the lines of the file.
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where
P: AsRef<Path>,
{
let file = File::open(filename)?;
Ok(io::BufReader::new(file).lines())
}
#[derive(Debug)]
struct ColorVariable {
name: String,
value: String,
}
impl ColorVariable {
pub fn new(name: String, value: String) -> Self {
Self { name, value }
}
}
impl Display for ColorVariable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"const {name}: &str = \"{value}\";",
name = self.name.replace('-', "_").to_uppercase(),
value = self.value
)
}
}
#[derive(Debug)]
struct FloatVariable {
name: String,
value: f64,
}
impl FloatVariable {
pub fn new(name: String, value: f64) -> Self {
Self { name, value }
}
}
impl Display for FloatVariable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"const {name}: f64 = {value};",
name = self.name.replace('-', "_").to_uppercase(),
value = self.value
)
}
}
struct Task {
src_path: PathBuf,
dst_path: PathBuf,
module_name: String,
}
impl Task {
pub fn new(src_path: PathBuf, dst_path: PathBuf, module_name: String) -> Self {
Self {
src_path,
dst_path,
module_name,
}
}
}
// fn export_variables(src_path: &PathBuf, dst_path: &PathBuf) {
fn export_variables(tasks: Vec<Task>) {
let color_re = Regex::new(r"^\$([^:]+):[[:space:]]*#([^$]+);[[:space:]]*$").unwrap();
let variable_re = Regex::new(r"^\$([^:]+):[[:space:]]*([^;]+)[[:space:]]*;").unwrap();
for task in tasks {
let mut dst_file = File::create(task.dst_path).unwrap();
if let Err(err) = dst_file.write_fmt(format_args!(
"#[allow(dead_code)]\nmod {} {{\n",
task.module_name
)) {
println!("{}", err);
return;
};
let mut variables = Vec::<Box<dyn Display>>::new();
if let Ok(lines) = read_lines(task.src_path) {
for line in lines.map_while(Result::ok) {
if let Some(groups) = color_re.captures(&line) {
let var = ColorVariable::new(groups[1].to_string(), groups[2].to_string());
variables.push(Box::new(var));
} else if let Some(groups) = variable_re.captures(&line) {
if let Ok(value) = groups[2].parse::<f64>() {
variables.push(Box::new(FloatVariable::new(groups[1].to_string(), value)));
}
}
}
}
for variable in variables {
if let Err(err) = dst_file.write_fmt(format_args!(" pub {}\n", variable)) {
println!("{}", err);
break;
}
}
if let Err(err) = dst_file.write(b"}\n") {
println!("{}", err);
};
}
} }
+298
View File
@@ -0,0 +1,298 @@
# This template contains all of the possible sections and their default values
# Note that all fields that take a lint level have these possible values:
# * deny - An error will be produced and the check will fail
# * warn - A warning will be produced, but the check will not fail
# * allow - No warning or error will be produced, though in some cases a note
# will be
# The values provided in this template are the default values that will be used
# when any section or field is not specified in your own configuration
# Root options
# The graph table configures how the dependency graph is constructed and thus
# which crates the checks are performed against
[graph]
# If 1 or more target triples (and optionally, target_features) are specified,
# only the specified targets will be checked when running `cargo deny check`.
# This means, if a particular package is only ever used as a target specific
# dependency, such as, for example, the `nix` crate only being used via the
# `target_family = "unix"` configuration, that only having windows targets in
# this list would mean the nix crate, as well as any of its exclusive
# dependencies not shared by any other crates, would be ignored, as the target
# list here is effectively saying which targets you are building for.
targets = [
# The triple can be any string, but only the target triples built in to
# rustc (as of 1.40) can be checked against actual config expressions
#"x86_64-unknown-linux-musl",
# You can also specify which target_features you promise are enabled for a
# particular target. target_features are currently not validated against
# the actual valid features supported by the target architecture.
#{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
]
# When creating the dependency graph used as the source of truth when checks are
# executed, this field can be used to prune crates from the graph, removing them
# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate
# is pruned from the graph, all of its dependencies will also be pruned unless
# they are connected to another crate in the graph that hasn't been pruned,
# so it should be used with care. The identifiers are [Package ID Specifications]
# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html)
#exclude = []
# If true, metadata will be collected with `--all-features`. Note that this can't
# be toggled off if true, if you want to conditionally enable `--all-features` it
# is recommended to pass `--all-features` on the cmd line instead
all-features = false
# If true, metadata will be collected with `--no-default-features`. The same
# caveat with `all-features` applies
no-default-features = false
# If set, these feature will be enabled when collecting metadata. If `--features`
# is specified on the cmd line they will take precedence over this option.
#features = []
# The output table provides options for how/if diagnostics are outputted
[output]
# When outputting inclusion graphs in diagnostics that include features, this
# option can be used to specify the depth at which feature edges will be added.
# This option is included since the graphs can be quite large and the addition
# of features from the crate(s) to all of the graph roots can be far too verbose.
# This option can be overridden via `--feature-depth` on the cmd line
feature-depth = 1
# This section is considered when running `cargo deny check advisories`
# More documentation for the advisories section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
[advisories]
# The path where the advisory databases are cloned/fetched into
#db-path = "$CARGO_HOME/advisory-dbs"
# The url(s) of the advisory databases to use
#db-urls = ["https://github.com/rustsec/advisory-db"]
# A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered.
ignore = [
#"RUSTSEC-0000-0000",
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
]
# If this is true, then cargo deny will use the git executable to fetch advisory database.
# If this is false, then it uses a built-in git library.
# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support.
# See Git Authentication for more information about setting up git authentication.
#git-fetch-with-cli = true
# This section is considered when running `cargo deny check licenses`
# More documentation for the licenses section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
[licenses]
version = 2
# List of explicitly allowed licenses
# See https://spdx.org/licenses/ for list of possible licenses
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
allow = [
# Free software licenses compatible with (A)GPL.
# List extracted from: https://www.gnu.org/licenses/license-list.en.html#GPLCompatibleLicenses
# "GPL-3.0",
# "GPL-2.0",
"LGPL-3.0",
# "LGPL-2.1",
"AGPL-3.0",
# "FSFAP",
"Apache-2.0",
"Apache-2.0 WITH LLVM-exception",
# "Artistic-2.0",
# "ClArtistic",
# "Sleepycat",
"BSL-1.0",
"BSD-3-Clause",
# "CECILL-2.0",
# "BSD-3-Clause-Clear",
# "ECL-2.0",
# "EFL-2.0",
# "EUDatagrid",
"MIT",
"BSD-2-Clause",
# "FTL",
# "HPND",
# "iMatix",
# "Imlib2",
# "IJG",
# "Intel",
"ISC",
"MPL-2.0",
"NCSA",
# "OLDAP-2.7",
# "NIST-PD",
# "CC-PDDC",
"CC0-1.0",
# "Python-2.0",
# "Ruby",
# "SGI-B-2.0",
# "SMLNJ",
# "UPL-1.0",
"Unlicense",
# "Vim",
# "W3C",
# "WTFPL",
# "X11",
# "XFree86-1.1",
"Zlib",
# "zlib-acknowledgement",
# "ZPL-2.0",
# "ZPL-2.1",
# Not expressely listed as (A)GPL compatible in the page above, but
# according to https://opensource.org/licenses/0BSD it is a modification
# of the ISC license, which is compatible. Its text is also extremely
# simple and allows using the code for any purpose
"0BSD",
# Permissive license used by the Unicode consortium, similar in spirit
# to other permissive licenses:
# https://spdx.org/licenses/Unicode-DFS-2016.html
"Unicode-DFS-2016",
# Permissive license used by the Unicode consortium, similar in spirit
# to other permissive licenses:
# https://spdx.org/licenses/Unicode-3.0.html
"Unicode-3.0",
]
# The confidence threshold for detecting a license from license text.
# The higher the value, the more closely the license text must be to the
# canonical license text of a valid SPDX license file.
# [possible values: any between 0.0 and 1.0].
confidence-threshold = 0.8
# Allow 1 or more licenses on a per-crate basis, so that particular licenses
# aren't accepted for every possible crate as with the normal allow list
exceptions = [
# Each entry is the crate and version constraint, and its specific allow
# list
#{ allow = ["Zlib"], crate = "adler32" },
]
# Some crates don't have (easily) machine readable licensing information,
# adding a clarification entry for it allows you to manually specify the
# licensing information
# [[licenses.clarify]]
# The package spec the clarification applies to
# crate = "ring"
# The SPDX expression for the license requirements of the crate
# expression = "MIT AND ISC AND OpenSSL"
# One or more files in the crate's source used as the "source of truth" for
# the license expression. If the contents match, the clarification will be used
# when running the license check, otherwise the clarification will be ignored
# and the crate will be checked normally, which may produce warnings or errors
# depending on the rest of your configuration
# license-files = [
#Each entry is a crate relative path, and the (opaque) hash of its contents
# { path = "LICENSE", hash = 0xbd0eed23 }
# ]
[licenses.private]
# If true, ignores workspace crates that aren't published, or are only
# published to private registries.
# To see how to mark a crate as unpublished (to the official registry),
# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field.
ignore = false
# One or more private registries that you might publish crates to, if a crate
# is only published to private registries, and ignore is true, the crate will
# not have its license(s) checked
registries = [
#"https://sekretz.com/registry
]
# This section is considered when running `cargo deny check bans`.
# More documentation about the 'bans' section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
[bans]
# Lint level for when multiple versions of the same crate are detected
multiple-versions = "warn"
# Lint level for when a crate version requirement is `*`
wildcards = "allow"
# The graph highlighting used when creating dotgraphs for crates
# with multiple versions
# * lowest-version - The path to the lowest versioned duplicate is highlighted
# * simplest-path - The path to the version with the fewest edges is highlighted
# * all - Both lowest-version and simplest-path are used
highlight = "all"
# The default lint level for `default` features for crates that are members of
# the workspace that is being checked. This can be overridden by allowing/denying
# `default` on a crate-by-crate basis if desired.
workspace-default-features = "allow"
# The default lint level for `default` features for external crates that are not
# members of the workspace. This can be overridden by allowing/denying `default`
# on a crate-by-crate basis if desired.
external-default-features = "allow"
# List of crates that are allowed. Use with care!
allow = [
#"ansi_term@0.11.0",
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" },
]
# List of crates to deny
deny = [
#"ansi_term@0.11.0",
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" },
# Wrapper crates can optionally be specified to allow the crate when it
# is a direct dependency of the otherwise banned crate
#{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] },
]
# List of features to allow/deny
# Each entry the name of a crate and a version range. If version is
# not specified, all versions will be matched.
#[[bans.features]]
#crate = "reqwest"
# Features to not allow
#deny = ["json"]
# Features to allow
#allow = [
# "rustls",
# "__rustls",
# "__tls",
# "hyper-rustls",
# "rustls",
# "rustls-pemfile",
# "rustls-tls-webpki-roots",
# "tokio-rustls",
# "webpki-roots",
#]
# If true, the allowed features must exactly match the enabled feature set. If
# this is set there is no point setting `deny`
#exact = true
# Certain crates/versions that will be skipped when doing duplicate detection.
skip = [
#"ansi_term@0.11.0",
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" },
]
# Similarly to `skip` allows you to skip certain crates during duplicate
# detection. Unlike skip, it also includes the entire tree of transitive
# dependencies starting at the specified crate, up to a certain depth, which is
# by default infinite.
skip-tree = [
#"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies
#{ crate = "ansi_term@0.11.0", depth = 20 },
]
# This section is considered when running `cargo deny check sources`.
# More documentation about the 'sources' section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
[sources]
# Lint level for what to happen when a crate from a crate registry that is not
# in the allow list is encountered
unknown-registry = "warn"
# Lint level for what to happen when a crate from a git repository that is not
# in the allow list is encountered
unknown-git = "warn"
# List of URLs for allowed crate registries. Defaults to the crates.io index
# if not specified. If it is specified but empty, no registries are allowed.
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
# List of URLs for allowed Git repositories
allow-git = [
"https://github.com/matrix-org/matrix-rust-sdk.git"
]
[sources.allow-org]
# github.com organizations to allow git sources for
github = []
# gitlab.com organizations to allow git sources for
gitlab = []
# bitbucket.org organizations to allow git sources for
bitbucket = []
+26
View File
@@ -0,0 +1,26 @@
FROM rust:1.86 AS builder
RUN apt update \
&& apt install -y --no-install-recommends libclang-dev hunspell \
&& apt clean
RUN rustup default nightly \
&& rustup component add rustfmt clippy
RUN cargo install cargo-binstall
RUN cargo binstall cargo-sort-derives cargo-spellcheck cargo-udeps cargo-deny
FROM debian:trixie-slim
RUN apt update \
&& apt install -y --no-install-recommends ca-certificates git rustup build-essential \
libssl-dev pkg-config libglib2.0-0 libpango-1.0-0 libatk1.0-dev libgdk-pixbuf-2.0-dev \
libcairo2-dev libgtk-3-dev libsoup-3.0-dev libwebkit2gtk-4.1-dev \
&& apt clean
COPY --from=builder /usr/local/rustup/toolchains/nightly-x86_64-unknown-linux-gnu/ /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/
COPY --from=builder /usr/local/cargo/bin /root/.cargo/bin/
RUN rustup default nightly
Binary file not shown.

Before

Width:  |  Height:  |  Size: 698 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 755 B

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 864 B

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 853 B

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 430 B

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 639 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 388 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 323 B

-1
View File
@@ -1 +0,0 @@
<svg enable-background="new 0 0 48 48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><path d="m36.5 44h-25c-1.1 0-1.8-1.2-1.3-2.2l2.8-4.8h22l2.7 4.8c.6 1-.1 2.2-1.2 2.2z" fill="#455a64"/><circle cx="24" cy="23" fill="#78909c" r="18"/><path d="m24 35c-6.6 0-12-5.4-12-12s5.4-12 12-12 12 5.4 12 12-5.4 12-12 12z" fill="#455a64"/><circle cx="24" cy="23" fill="#42a5f5" r="9"/><path d="m28.8 20c-1.2-1.4-3-2.2-4.8-2.2s-3.6.8-4.8 2.2c-.5.5-.4 1.3.1 1.8s1.3.4 1.8-.1c1.5-1.7 4.3-1.7 5.8 0 .3.3.6.4 1 .4.3 0 .6-.1.9-.3.4-.4.5-1.3 0-1.8z" fill="#90caf9"/></svg>

Before

Width:  |  Height:  |  Size: 562 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 719 B

Symlink
+1
View File
@@ -0,0 +1 @@
public/index.html
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+92
View File
@@ -0,0 +1,92 @@
Geist Sans and Geist Mono Font
(C) 2023 Vercel, made in collaboration with basement.studio
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is available with a FAQ at: http://scripts.sil.org/OFL and copied below
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION AND CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="none" shape-rendering="auto"><desc>"Shapes" by "Florian Körner", licensed under "CC0 1.0". / Remix of the original. - Created with dicebear.com</desc><metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:rdf><cc:work><dc:title>Shapes</dc:title><dc:creator><cc:agent rdf:about="https://www.dicebear.com"><dc:title>Florian Körner</dc:title></cc:agent></dc:creator><dc:source>https://www.dicebear.com</dc:source><cc:license rdf:resource="https://creativecommons.org/publicdomain/zero/1.0/"></cc:license></cc:work></rdf:rdf></metadata><mask id="w6sj6i8m"><rect width="100" height="100" rx="0" ry="0" x="0" y="0" fill="#fff"></rect></mask><g mask="url(#w6sj6i8m)"><rect fill="#E2F2F7" width="100" height="100" x="0" y="0"></rect><g transform="matrix(1.2 0 0 1.2 -10 -10)"><g transform="translate(51, -23) rotate(-38 50 50)"><path d="M0 0h100v100H0V0Z" fill="#83CADE"></path></g></g><g transform="matrix(.8 0 0 .8 10 10)"><g transform="translate(-2, 35) rotate(99 50 50)"><path d="M100 50A50 50 0 1 1 0 50a50 50 0 0 1 100 0Z" fill="#6957A0"></path></g></g><g transform="matrix(.4 0 0 .4 30 30)"><g transform="translate(-18, -6) rotate(-97 50 50)"><path d="m50 7 50 86.6H0L50 7Z" fill="#D53583"></path></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 17 KiB

+13
View File
@@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" height="128" width="384" viewBox="0 0 384 128">
<pattern id="p" width="384" height="128" patternUnits="userSpaceOnUse" stroke="#1B1B1B" stroke-linejoin="round" stroke-width="4">
<path fill="#1DB2CF" d="M 9.736 -15 L -30 3.337 l 23.642 -0.088 L -10.212 15 L 30 -3.425 H 6.834 L 9.736 -15 Z"/>
<path fill="#D53583" d="M 201.736 -15 L 162 3.337 l 23.642 -0.088 L 181.788 15 L 222 -3.425 H 198.834 L 201.736 -15 Z"/>
<path fill="#1DB2CF" d="M 393.736 -15 L 354 3.337 l 23.642 -0.088 L 373.788 15 L 414 -3.425 H 390.834 L 393.736 -15 Z"/>
<path fill="#7E6BB6" d="M 105.736 50 L 66 68.337 l 23.642 -0.088 L 85.788 80 L 126 61.575 H 102.834 L 105.736 50 Z"/>
<path fill="#7E6BB6" d="M 297.736 50 L 258 68.337 l 23.642 -0.088 L 277.788 80 L 318 61.575 H 294.834 L 297.736 50 Z"/>
<path fill="#1DB2CF" d="M 9.736 113 L -30 131.337 l 23.642 -0.088 L -10.212 143 L 30 124.575 H 6.834 L 9.736 113 Z"/>
<path fill="#D53583" d="M 201.736 113 L 162 131.337 l 23.642 -0.088 L 181.788 143 L 222 124.575 H 198.834 L 201.736 113 Z"/>
<path fill="#1DB2CF" d="M 393.736 113 L 354 131.337 l 23.642 -0.088 L 373.788 143 L 414 124.575 H 390.834 L 393.736 113 Z"/>
</pattern>
<rect fill="url(#p)" width="100%" height="100%"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+224
View File
@@ -0,0 +1,224 @@
<!DOCTYPE html>
<html>
<head>
<title>{app_name}</title>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8" />
<style>
html, body {
height: 100%;
width: 100%;
margin: 0;
font-family: "Geist";
font-weight: normal;
}
#main {
height: 100%;
width: 100%;
}
@keyframes multicolor {
0% { fill: #1DB2CF; /* color-primary-100 */ }
33% { fill: #7E6BB6; /* color-secondary-100 */ }
66% { fill: #D53583; /* color-ternary-100 */ }
}
:root {
--wallpaper-pattern-height: 128px;
--spinner-height: 5%;
--window-center-pos: calc(50% + (var(--wallpaper-pattern-height) / 2) - (var(--spinner-height) / 2));
}
/* @media (0px < height <= calc(var(--wallpaper-pattern-height) * 5)) { */
@media (min-height: 0px) and (max-height: 640px) {
:root {
--spinner-top: var(--window-center-pos);
}
}
/* @media (calc($wallpaper-pattern-height * 5) < height <= calc($wallpaper-pattern-height * 6)) { */
@media (min-height: 641px) and (max-height: 768px) {
:root {
--spinner-top: calc(var(--window-center-pos) + (var(--wallpaper-pattern-height) * 2));
}
}
/* @media (calc($wallpaper-pattern-height * 6) < height <= calc($wallpaper-pattern-height * 8)) { */
@media (min-height: 769px) and (max-height: 1024px) {
:root {
--spinner-top: calc(var(--window-center-pos) + (var(--wallpaper-pattern-height) * 2));
}
}
/* @media (calc($wallpaper-pattern-height * 8) < height <= calc($wallpaper-pattern-height * 10)) { */
@media (min-height: 1025px) and (max-height: 1280px) {
:root {
--spinner-top: calc(var(--window-center-pos) + (var(--wallpaper-pattern-height) * 3));
}
}
/* @media (calc($wallpaper-pattern-height * 10) < height <= calc($wallpaper-pattern-height * 12)) { */
@media (min-height: 1281px) and (max-height: 1536px) {
:root {
--spinner-top: calc(var(--window-center-pos) + (var(--wallpaper-pattern-height) * 4));
}
}
/* @media (calc($wallpaper-pattern-height * 12) < height <= calc($wallpaper-pattern-height * 14)) { */
@media (min-height: 1537px) and (max-height: 1792px) {
:root {
--spinner-top: calc(var(--window-center-pos) + (var(--wallpaper-pattern-height) * 5));
}
}
/* @media (calc($wallpaper-pattern-height * 14) < height <= calc($wallpaper-pattern-height * 16)) { */
@media (min-height: 1793px) and (max-height: 2048px) {
:root {
--spinner-top: calc(var(--window-center-pos) + (var(--wallpaper-pattern-height) * 6));
}
}
/* @media (calc($wallpaper-pattern-height * 16) < height <= calc($wallpaper-pattern-height * 18)) { */
@media (min-height: 2049px) and (max-height: 2304px) {
:root {
--spinner-top: calc(var(--window-center-pos) + (var(--wallpaper-pattern-height) * 7));
}
}
/* @media (calc($wallpaper-pattern-height * 18) < height <= calc($wallpaper-pattern-height * 20)) { */
@media (min-height: 2305px) and (max-height: 2560px) {
:root {
--spinner-top: calc(var(--window-center-pos) + (var(--wallpaper-pattern-height) * 8));
}
}
/* @media (calc($wallpaper-pattern-height * 20) < height <= calc($wallpaper-pattern-height * 22)) { */
@media (min-height: 2561px) and (max-height: 2816px) {
:root {
--spinner-top: calc(var(--window-center-pos) + (var(--wallpaper-pattern-height) * 9));
}
}
/* @media (calc($wallpaper-pattern-height * 22) < height <= calc($wallpaper-pattern-height * 24)) { */
@media (min-height: 2817px) and (max-height: 3072px) {
:root {
--spinner-top: calc(var(--window-center-pos) + (var(--wallpaper-pattern-height) * 10));
}
}
/* @media (calc($wallpaper-pattern-height * 24) < height <= calc($wallpaper-pattern-height * 26)) { */
@media (min-height: 3073px) and (max-height: 3328px) {
:root {
--spinner-top: calc(var(--window-center-pos) + (var(--wallpaper-pattern-height) * 11));
}
}
/* @media (calc($wallpaper-pattern-height * 26) < height <= calc($wallpaper-pattern-height * 28)) { */
@media (min-height: 3329px) and (max-height: 3584px) {
:root {
--spinner-top: calc(var(--window-center-pos) + (var(--wallpaper-pattern-height) * 12));
}
}
/* @media (calc($wallpaper-pattern-height * 28) < height <= calc($wallpaper-pattern-height * 30)) { */
@media (min-height: 3585px) and (max-height: 3840px) {
:root {
--spinner-top: calc(var(--window-center-pos) + (var(--wallpaper-pattern-height) * 13));
}
}
/* @media (calc($wallpaper-pattern-height * 30) < height <= calc($wallpaper-pattern-height * 32)) { */
@media (min-height: 3841px) and (max-height: 4096px) {
:root {
--spinner-top: calc(var(--window-center-pos) + (var(--wallpaper-pattern-height) * 14));
}
}
/* @media (calc($wallpaper-pattern-height * 32) < height <= calc($wallpaper-pattern-height * 34)) { */
@media (min-height: 4097px) and (max-height: 4352px) {
:root {
--spinner-top: calc(var(--window-center-pos) + (var(--wallpaper-pattern-height) * 15));
}
}
/* @media (calc($wallpaper-pattern-height * 34) < height <= calc($wallpaper-pattern-height * 36)) { */
@media (min-height: 4353px) and (max-height: 4608px) {
:root {
--spinner-top: calc(var(--window-center-pos) + (var(--wallpaper-pattern-height) * 16));
}
}
/* @media (calc($wallpaper-pattern-height * 36) < height <= calc($wallpaper-pattern-height * 38)) { */
@media (min-height: 4609px) and (max-height: 4864px) {
:root {
--spinner-top: calc(var(--window-center-pos) + (var(--wallpaper-pattern-height) * 17));
}
}
/* @media (calc($wallpaper-pattern-height * 38) < height <= calc($wallpaper-pattern-height * 40)) { */
@media (min-height: 4865px) and (max-height: 5120px) {
:root {
--spinner-top: calc(var(--window-center-pos) + (var(--wallpaper-pattern-height) * 18));
}
}
.loader {
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
.wallpaper {
height: 100%;
width: 100%;
z-index: -1;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
.content {
background-image: url("data:image/svg+xml, <svg xmlns='http://www.w3.org/2000/svg' height='128' width='384' viewBox='0 0 384 128'><pattern id='p' width='384' height='128' patternUnits='userSpaceOnUse' stroke='%231B1B1B' stroke-linejoin='round' stroke-width='4'><path fill='%231DB2CF' d='M 9.736 -15 L -30 3.337 l 23.642 -0.088 L -10.212 15 L 30 -3.425 H 6.834 L 9.736 -15 Z'/><path fill='%23D53583' d='M 201.736 -15 L 162 3.337 l 23.642 -0.088 L 181.788 15 L 222 -3.425 H 198.834 L 201.736 -15 Z'/><path fill='%231DB2CF' d='M 393.736 -15 L 354 3.337 l 23.642 -0.088 L 373.788 15 L 414 -3.425 H 390.834 L 393.736 -15 Z'/><path fill='%237E6BB6' d='M 105.736 50 L 66 68.337 l 23.642 -0.088 L 85.788 80 L 126 61.575 H 102.834 L 105.736 50 Z'/><path fill='%237E6BB6' d='M 297.736 50 L 258 68.337 l 23.642 -0.088 L 277.788 80 L 318 61.575 H 294.834 L 297.736 50 Z'/><path fill='%231DB2CF' d='M 9.736 113 L -30 131.337 l 23.642 -0.088 L -10.212 143 L 30 124.575 H 6.834 L 9.736 113 Z'/><path fill='%23D53583' d='M 201.736 113 L 162 131.337 l 23.642 -0.088 L 181.788 143 L 222 124.575 H 198.834 L 201.736 113 Z'/><path fill='%231DB2CF' d='M 393.736 113 L 354 131.337 l 23.642 -0.088 L 373.788 143 L 414 124.575 H 390.834 L 393.736 113 Z'/></pattern><rect fill='url(%23p)' width='100%' height='100%'/></svg>");
background-position: center;
backgrond-size: var(--wallpaper-pattern-height);
width: 150%;
height: 150%;
}
}
.spinner {
height: var(--spinner-height);
aspect-ratio: 2;
position: absolute;
top: var(--spinner-top);
svg {
--fps: 4;
--duration_sec: 3;
--steps: calc(var(--duration_sec) * var(--fps));
height: 100%;
width: 100%;
fill: #1DB2CF; /* color-primary-100 */
stroke: #1B1B1B; /* greyscale-90 */
animation: 3s multicolor linear infinite;
animation-timing-function: steps(var(--steps), end);
}
}
}
</style>
</head>
<body>
<div id="main">
<div id="preloader" class="loader">
<div class="wallpaper">
<div class="content"></div>
</div>
<div class="spinner">
<svg xmlns="http://www.w3.org/2000/svg" width='184' height='94' viewBox="0 0 184 94">
<path
stroke-linejoin="round"
stroke-width="6"
d="M121.208 2 2 57.011l70.927-.265L61.363 92 182 36.724h-69.498L121.208 2Z"
/>
</svg>
</div>
</div>
</div>
</body>
</html>
+3
View File
@@ -0,0 +1,3 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
}
+6
View File
@@ -0,0 +1,6 @@
5
Dioxus
MSN
renderers
SDK
TODO
+10
View File
@@ -0,0 +1,10 @@
# Also take into account developer comments
dev_comments = false
# Skip the README.md file as defined in the cargo manifest
skip_readme = false
[Hunspell]
lang = "en_US"
search_dirs = [ "." ]
extra_dictionaries = [ "./spellcheck.dic" ]
-50
View File
@@ -1,50 +0,0 @@
$font-size: 100vh * 0.01;
$icon-size: $font-size * 2;
$border-style: thin solid #BED6E0;
body {
height: 100vh;
width: 100vw;
margin: 0px;
padding: 0px;
outline: 0px;
font-family: Tahoma, sans-serif;
}
#main {
height: 100%;
width: 100%;
}
.aeroButton {
height: 50%;
min-height: 16px;
aspect-ratio: 1;
background-color: transparent;
border: 2px solid transparent;
background-size: contain !important;
margin-right: 1%;
}
.aeroButton:hover {
border-image: url(./images/aerobutton_border.png) 2 round;
}
.aeroButton:active {
border-image: url(./images/aerobutton_border_down.png) 2 round;
}
.button {
height: 50%;
min-height: 16px;
aspect-ratio: 1;
background-color: transparent;
border: 2px solid transparent;
background-size: contain !important;
margin-right: 1%;
}
.button:hover {
border-image: url(./images/button_border.png) 2 round;
}
.button:active {
border-image: url(./images/button_border_down.png) 2 round;
}
-272
View File
@@ -1,272 +0,0 @@
// Cf. https://dioxuslabs.com/learn/0.4/reference/use_coroutine
// In order to use/run the rx.next().await statement you will need to extend the [Stream] trait
// (used by [UnboundedReceiver]) by adding 'futures_util' as a dependency to your project
// and adding the use futures_util::stream::StreamExt;
use futures_util::stream::StreamExt;
use std::{collections::HashMap, sync::Arc};
use dioxus::prelude::*;
use fermi::*;
use matrix_sdk::{
room::{Room as MatrixRoom, RoomMember},
ruma::{OwnedRoomId, OwnedUserId},
};
use tokio::select;
use tracing::{debug, error, warn};
use crate::components::chats_window::interface::Interface as ChatsWinInterface;
use crate::matrix_interface::client::{Client, RoomEvent};
use crate::matrix_interface::requester::{Receivers, Requester};
use crate::matrix_interface::worker_tasks::LoginStyle;
// #[derive(Clone, Debug)]
// pub struct UserInfo {
// pub avatar_url: Option<OwnedMxcUri>,
// pub display_name: Option<String>,
// pub blurhash: Option<String>,
// }
// impl UserInfo {
// pub fn new(
// avatar_url: Option<OwnedMxcUri>,
// display_name: Option<String>,
// blurhash: Option<String>,
// ) -> Self {
// Self {
// avatar_url,
// display_name,
// blurhash,
// }
// }
// }
#[derive(Clone)]
pub struct Room {
pub matrix_room: Arc<MatrixRoom>,
pub topic: Option<RefCell<String>>,
pub members: HashMap<OwnedUserId, RoomMember>,
pub is_direct: Option<bool>,
}
impl Room {
pub fn new(
matrix_room: Arc<MatrixRoom>,
topic: Option<RefCell<String>>,
is_direct: Option<bool>,
) -> Self {
Self {
matrix_room,
topic,
members: HashMap::new(),
is_direct,
}
}
pub async fn from_matrix_room(matrix_room: &MatrixRoom) -> Self {
let room_topic = matrix_room.topic().map(RefCell::new);
Self::new(
Arc::new(matrix_room.to_owned()),
room_topic,
matrix_room.is_direct().await.ok(),
)
}
pub fn name(&self) -> Option<String> {
self.matrix_room.name()
}
pub fn id(&self) -> OwnedRoomId {
OwnedRoomId::from(self.matrix_room.room_id())
}
}
impl PartialEq for Room {
fn eq(&self, other: &Self) -> bool {
// TODO: Look for a better way to compare Matrix rooms
self.matrix_room.room_id() == other.matrix_room.room_id()
}
}
pub type ByIdRooms = HashMap<OwnedRoomId, RefCell<Room>>;
// pub type ByIdUserInfos = HashMap<OwnedUserId, UserInfo>;
// #[derive(Clone)]
// pub struct Store {
// pub is_logged: bool,
// pub rooms: ByIdRooms,
// pub user_infos: ByIdUserInfos,
// pub user_id: Option<OwnedUserId>,
// }
// impl Store {
// pub fn new() -> Self {
// Self {
// is_logged: false,
// rooms: HashMap::new(),
// user_infos: HashMap::new(),
// user_id: None,
// }
// }
// }
// impl PartialEq for Store {
// fn eq(&self, other: &Self) -> bool {
// self.is_logged == other.is_logged
// && self.user_id == other.user_id
// && self.user_infos.len() == other.user_infos.len()
// && self
// .user_infos
// .keys()
// .all(|k| other.user_infos.contains_key(k))
// && self.rooms.len() == other.rooms.len()
// && self.rooms.keys().all(|k| other.rooms.contains_key(k))
// }
// }
// impl Eq for Store {}
pub struct AppSettings {
pub requester: Option<RefCell<Requester>>,
}
impl AppSettings {
pub fn new() -> Self {
Self { requester: None }
}
pub fn set_requester(&mut self, requester: RefCell<Requester>) {
self.requester = Some(requester);
}
}
pub static APP_SETTINGS: AtomRef<AppSettings> = AtomRef(|_| AppSettings::new());
async fn on_room(room_id: OwnedRoomId, room: Room, rooms_ref: &UseAtomRef<ByIdRooms>) {
// TODO: Update rooms
rooms_ref
.write()
.insert(room_id, RefCell::<Room>::new(room));
}
async fn on_joining_invitation(
room_id: OwnedRoomId,
room: Room,
rooms_ref: &UseAtomRef<ByIdRooms>,
) {
debug!(
"You're invited to join the \"{}\" room",
room.name().unwrap()
);
// TODO: Update rooms
rooms_ref
.write()
.insert(room_id, RefCell::<Room>::new(room));
}
pub async fn on_room_topic(room_id: OwnedRoomId, topic: String, rooms_ref: &UseAtomRef<ByIdRooms>) {
if let Some(room_ref) = rooms_ref.read().get(&room_id) {
let mut room = room_ref.borrow_mut();
room.topic = Some(RefCell::new(topic));
} else {
warn!("No room found with the \"{}\" id", room_id);
}
}
pub async fn sync_rooms(
mut rx: UnboundedReceiver<bool>,
receivers: Receivers,
rooms_ref: UseAtomRef<ByIdRooms>,
) {
if let Some(_is_logged) = rx.next().await {
let mut rooms_receiver = receivers.room_receiver.borrow_mut();
loop {
// TODO: Remove select if no more receivers will be used.
select! {
res = rooms_receiver.recv() => {
if let Ok(room_event) = res {
match room_event {
RoomEvent::MemberEvent(room_id, room) => on_room(room_id, room, &rooms_ref).await,
RoomEvent::InviteEvent(room_id, room) => on_joining_invitation(room_id, room, &rooms_ref).await,
RoomEvent::TopicEvent(room_id, topic) => on_room_topic(room_id, topic, &rooms_ref).await,
};
}
},
}
}
}
}
pub async fn login(
mut rx: UnboundedReceiver<bool>,
app_settings_ref: UseAtomRef<AppSettings>,
session_ref: UseAtomRef<Session>,
) {
while let Some(is_logged) = rx.next().await {
if !is_logged {
let homeserver_url = session_ref.read().homeserver_url.clone();
let username = session_ref.read().username.clone();
let password = session_ref.read().password.clone();
if homeserver_url.is_some() && username.is_some() && password.is_some() {
let client = Client::spawn(homeserver_url.unwrap()).await;
if let Err(err) = client.init().await {
error!("Following error occureds during client init: {}", err);
}
match client
.login(LoginStyle::Password(username.unwrap(), password.unwrap()))
.await
{
Ok(_) => {
debug!("successfully logged");
session_ref.write().is_logged = true;
}
Err(err) => {
error!("Error during login: {err}");
// invalid_login.modify(|_| true);
}
}
app_settings_ref.write().set_requester(RefCell::new(client));
} else {
warn!("At least one of the following values is/are invalid: homeserver, username or password");
}
} else {
warn!("already logged... skip login");
}
}
error!("=== LOGIN END ===");
}
pub struct Session {
pub homeserver_url: Option<String>,
pub username: Option<String>,
pub password: Option<String>,
pub is_logged: bool,
}
impl Session {
fn new() -> Self {
Self {
homeserver_url: None,
username: None,
password: None,
is_logged: false,
}
}
pub fn update(
&mut self,
homeserver_url: Option<String>,
username: Option<String>,
password: Option<String>,
) {
self.homeserver_url = homeserver_url;
self.username = username;
self.password = password;
}
}
pub static ROOMS: AtomRef<ByIdRooms> = AtomRef(|_| ByIdRooms::new());
pub static SESSION: AtomRef<Session> = AtomRef(|_| Session::new());
pub static CHATS_WIN_INTERFACE: AtomRef<ChatsWinInterface> = AtomRef(|_| ChatsWinInterface::new());
-54
View File
@@ -1,54 +0,0 @@
use dioxus::prelude::*;
turf::style_sheet!("src/components/avatar_selector.scss");
pub fn AvatarSelector(cx: Scope) -> Element {
cx.render(rsx! {
style { STYLE_SHEET },
div {
class: ClassName::SELECTOR,
svg {
view_box: "0 0 100 100",
linearGradient {
id: "avatar-gradient",
x1: 1,
y1: 1,
x2: 0,
y2: 0,
stop {
offset: "0%",
stop_color: "rgb(138, 191, 209)",
}
stop {
offset: "60%",
stop_color: "rgb(236, 246, 249)",
}
},
filter {
id: "avatar-shadow",
feDropShadow {
dx: 2,
dy: 2,
std_deviation: 3,
flood_opacity: 0.5,
},
},
rect {
x: "10",
y: "10",
width: "80",
height: "80",
rx: "12",
fill: "url('#avatar-gradient')",
filter: "url('#avatar-shadow')",
stroke: "grey",
},
},
img {
class: ClassName::PICTURE,
src: "./images/default-avatar.png",
},
},
})
}
-17
View File
@@ -1,17 +0,0 @@
.selector {
position: relative;
height: 100%;
aspect-ratio: 1;
.picture {
$height: 65%;
$margin: calc(100% - $height) / 2;
position: absolute;
height: $height;
aspect-ratio: 1;
top: $margin;
right: $margin;
}
}
@@ -1,94 +0,0 @@
@import "../../_base.scss"
.chats-window {
height: 100%;
width: 100%;
$horizontal-padding-margin: calc((2*100%)/1980);
.tabs {
height: 2%;
width: 100%;
display: flex;
flex-flow: row;
overflow-x: scroll;
&::-webkit-scrollbar {
height: 0px;
}
.tab {
height: 100%;
flex-grow: 1;
padding: 0 $horizontal-padding-margin;
display: flex;
button {
height: 100%;
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
$clamped-horizontal-padding-margin: clamp(5px, $horizontal-padding-margin, $horizontal-padding-margin);
margin: 0 $clamped-horizontal-padding-margin;
padding: 0 $clamped-horizontal-padding-margin;
white-space: nowrap;
background-color: #EFF9F9;
border: $border-style;
$radius: calc((6*100%)/1980);
$clamped-radius: clamp(6px, $radius, $radius);
border-radius: $clamped-radius $clamped-radius 0 0;
font-size: $font-size;
img {
height: $icon-size;
aspect-ratio: 1;
}
}
}
}
.chat {
height: 98%;
width: 100%;
background-color: #ECF6F9;
.header {
height: 7%;
border: $border-style;
.info {
height: 45%;
display: flex;
flex-direction: column;
padding-left: 2%;
background: linear-gradient(180deg, #BFE3EB, #DEFBFE);
font-size: $font-size;
.room-name {
margin: 0;
margin-top: 1%;
font-weight: bold;
}
.room-topic {
margin: 0;
color: darkgrey;
}
}
}
}
}
@@ -1,80 +0,0 @@
use dioxus::prelude::*;
use tracing::debug;
use super::edit_section::EditSection;
use crate::components::avatar_selector::AvatarSelector;
use crate::components::icons::DownArrowIcon;
turf::style_sheet!("src/components/chats_window/conversation.scss");
pub(super) fn Conversation(cx: Scope) -> Element {
debug!("Conversation {} rendering", "TBD");
cx.render(rsx! {
style { STYLE_SHEET },
div {
class: ClassName::CONVERSATION,
div {
class: ClassName::ROOM_EVENTS,
ul {
li {
class: ClassName::ROOM_EVENT,
div {
p {
class: ClassName::TITLE,
"MON POTE says:"
},
p {
class: ClassName::CONTENT,
"Coucou mon pote",
},
},
},
},
},
div {
class: ClassName::OTHER_AVATAR_SELECTOR_CONTAINER,
div {
class: ClassName::AVATAR_SELECTOR,
AvatarSelector {},
},
div {
class: ClassName::WEBCAM,
img {
src: "images/webcam.svg"
},
},
div {
class: ClassName::ARROW_ICON,
DownArrowIcon {}
},
},
div {
class: ClassName::HOLDER,
"••••••"
},
div {
class: ClassName::EDIT_SECTION,
EditSection {},
},
div {
class: ClassName::MY_AVATAR_SELECTOR_CONTAINER,
div {
class: ClassName::AVATAR_SELECTOR,
AvatarSelector {},
},
div {
class: ClassName::WEBCAM,
img {
src: "images/webcam.svg"
},
},
div {
class: ClassName::ARROW_ICON,
DownArrowIcon {}
},
},
},
})
}
@@ -1,113 +0,0 @@
@import "../../_base.scss"
.conversation {
$padding-top: 2%;
height: calc(93% - $padding-top);
padding-left: 2%;
padding-top: $padding-top;
display: grid;
grid-template-columns: 75% 25%;
grid-template-rows: 70% 1% 29%;
cursor: pointer;
.holder {
display: flex;
justify-content: center;
align-items: center;
grid-column: 1;
grid-row: 2;
color: darkgrey;
}
.room-events {
display: flex;
flex-flow: column;
justify-content: flex-start;
border: $border-style;
background-color: #FFFFFF;
ul {
margin: 0;
padding-left: 0;
}
li {
list-style-type: none;
}
.room-event {
display: flex;
flex-flow: column;
justify-content: space-between;
padding-top: 1%;
font-size: $font-size;
.title {
margin: 0;
}
.content {
margin: 0;
padding-left: 2%;
}
}
}
%selector-container {
aspect-ratio: 1;
grid-column: 2;
display: grid;
grid-template-columns: 10% 15% 50% 15% 10%;
grid-template-rows: 80% 20%;
.avatar-selector {
grid-column-start: 1;
grid-column-end: 6;
aspect-ratio: 1;
}
.webcam {
grid-column: 2;
grid-row: 2;
aspect-ratio: 1;
}
.arrow-icon {
grid-column: 4;
grid-row: 2;
svg {
path:last-child {
fill: black;
}
}
}
}
.other-avatar-selector-container {
@extend %selector-container;
grid-row: 1;
}
.my-avatar-selector-container {
@extend %selector-container;
grid-row: 3;
}
.edit-section {
grid-row: 3;
}
}
@@ -1,52 +0,0 @@
use dioxus::prelude::*;
turf::style_sheet!("src/components/chats_window/edit_section.scss");
pub fn EditSection(cx: Scope) -> Element {
cx.render(rsx! {
style { STYLE_SHEET },
div {
class: ClassName::INPUT_AREA,
div {
class: ClassName::BUTTONS,
button {
"😀"
},
button {
"😉"
},
button {
"😴"
},
button {
"🔊"
},
},
textarea {
class: ClassName::EDIT,
placeholder: "Type your message here...",
},
div {
class: ClassName::CMD_BUTTONS,
button {
class: ClassName::SEND_BUTTON,
"Send"
},
button {
"🔎"
},
},
},
})
}
@@ -1,55 +0,0 @@
@import "../../_base.scss"
.input-area {
height: 100%;
width: 100%;
margin-bottom: 2%;
.buttons {
$padding-top-bottom: 0.5%;
height: calc(10% - ($padding-top-bottom * 2));
padding-left: 2%;
padding-top: $padding-top-bottom;
padding-bottom: $padding-top-bottom;
display: flex;
flex-direction: row;
align-items: center;
border: $border-style;
background: linear-gradient(180deg, #F5FDFF, #E3ECF0, #F5FDFF);
button {
@extend .aeroButton;
height: $icon-size;
padding: 0;
margin: 0;
margin-right: 2%;
font-size: larger;
}
}
.edit {
height: 80%;
// Remove border from width
width: calc(100% - 2px);
padding: 0;
margin: 0;
}
.cmd-buttons {
height: 7%;
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.send-button {
width: 15%;
}
}
-32
View File
@@ -1,32 +0,0 @@
use dioxus::prelude::*;
use matrix_sdk::ruma::OwnedRoomId;
use tokio::sync::broadcast::error::SendError;
use tokio::sync::broadcast::{channel, Receiver, Sender};
#[derive(Clone)]
pub enum Tasks {
ToggleRoom(OwnedRoomId),
}
pub struct Interface {
sender: Sender<Tasks>,
receiver: RefCell<Receiver<Tasks>>,
}
impl Interface {
pub fn new() -> Self {
let (sender, receiver) = channel::<Tasks>(32);
Self {
sender,
receiver: RefCell::new(receiver),
}
}
pub(super) fn receiver(&self) -> &RefCell<Receiver<Tasks>> {
&self.receiver
}
pub fn toggle_room(&self, room_id: OwnedRoomId) -> Result<usize, SendError<Tasks>> {
self.sender.send(Tasks::ToggleRoom(room_id))
}
}
-154
View File
@@ -1,154 +0,0 @@
mod conversation;
mod edit_section;
pub mod interface;
mod navbar;
use std::collections::{HashMap, HashSet};
use dioxus::prelude::*;
use fermi::*;
use matrix_sdk::ruma::OwnedRoomId;
use tokio::sync::broadcast::Receiver;
use tracing::{debug, error};
use crate::base::{sync_rooms, Room, ROOMS};
use crate::matrix_interface::requester::Receivers;
use conversation::Conversation;
use navbar::Navbar;
use interface::{Interface, Tasks};
turf::style_sheet!("src/components/chats_window/chats_window.scss");
pub struct ChatsWindowProps {
pub receivers: Receivers,
pub interface: UseAtomRef<Interface>,
}
fn render_rooms_tabs<'a>(
rooms_atom_ref: &'a UseAtomRef<HashMap<OwnedRoomId, RefCell<Room>>>,
displayed_room_ids_ref: &'a UseRef<HashSet<OwnedRoomId>>,
) -> Vec<LazyNodes<'a, 'a>> {
let rooms_ref = rooms_atom_ref.read();
let displayed_room_ids = displayed_room_ids_ref.read();
rooms_ref
.values()
.filter(|room| displayed_room_ids.contains(&room.borrow().id()))
.map(|room| -> LazyNodes {
let room = room.borrow();
let room_name = room.name().unwrap_or(room.id().to_string());
rsx!(
div {
class: ClassName::TAB,
button {
img {
src: "./images/status_online.png",
},
"{room_name}",
},
},
)
})
.collect()
}
async fn handle_controls<'a>(
receiver_ref: &'a RefCell<Receiver<Tasks>>,
displayed_room_ids_ref: &'a UseRef<HashSet<OwnedRoomId>>,
) {
loop {
let result = receiver_ref.borrow_mut().recv().await;
match result {
Ok(task) => match task {
Tasks::ToggleRoom(room_id) => {
error!("ON TOGGLE ROOM {}", room_id);
let mut displayed_room_ids = displayed_room_ids_ref.write();
match displayed_room_ids.take(&room_id) {
Some(_) => {
error!("{} room already dispayed... close it", room_id);
}
None => {
error!("{} room isn't dispayed... open it", room_id);
displayed_room_ids.insert(room_id);
}
}
}
},
Err(err) => error!("{}", err),
}
}
}
pub fn ChatsWindow(cx: Scope<ChatsWindowProps>) -> Element {
debug!("ChatsWindow rendering");
use_init_atom_root(cx);
let receivers = &cx.props.receivers;
let interface_ref = &cx.props.interface;
let rooms_ref = use_atom_ref(cx, &ROOMS);
let displayed_room_ids = use_ref(cx, HashSet::<OwnedRoomId>::new);
let sync_rooms_coro = use_coroutine(cx, |rx| {
to_owned![receivers];
sync_rooms(rx, receivers, rooms_ref.clone())
});
sync_rooms_coro.send(true);
let _: &Coroutine<()> = use_coroutine(cx, |_: UnboundedReceiver<_>| {
to_owned![interface_ref, displayed_room_ids];
async move {
let interface = interface_ref.read();
let receiver = &interface.receiver();
handle_controls(receiver, &displayed_room_ids).await
}
});
let rendered_rooms_tabs = render_rooms_tabs(rooms_ref, displayed_room_ids);
cx.render(rsx! {
style { STYLE_SHEET },
div {
class: ClassName::CHATS_WINDOW,
div {
class: ClassName::TABS,
rendered_rooms_tabs.into_iter(),
},
div {
class: ClassName::CHAT,
div {
class: ClassName::HEADER,
div {
class: ClassName::INFO,
p {
class: ClassName::ROOM_NAME,
"MON POTE",
},
p {
class: ClassName::ROOM_TOPIC,
"LE STATUT A MON POTE",
},
},
Navbar {},
},
Conversation {},
},
},
})
}
-50
View File
@@ -1,50 +0,0 @@
use dioxus::prelude::*;
use tracing::debug;
turf::style_sheet!("src/components/chats_window/navbar.scss");
pub fn Navbar(cx: Scope) -> Element {
debug!("Navbar rendering");
cx.render(rsx! {
style { STYLE_SHEET },
div {
class: ClassName::NAVBAR,
button {
style: "background: url(./images/add_user2.png) center no-repeat",
},
button {
style: "background: url(./images/directory.png) center no-repeat",
},
button {
style: "background: url(./images/phone.png) center no-repeat",
},
button {
style: "background: url(./images/medias.png) center no-repeat",
},
button {
style: "background: url(./images/games.png) center no-repeat",
},
button {
style: "background: url(./images/ban_user.png) center no-repeat",
},
button {
class: ClassName::FLEX_RIGHT_AERO_BUTTON,
style: "background: url(./images/brush.png) center no-repeat",
},
button {
class: ClassName::FLEX_LAST_BUTTON,
style: "background: url(./images/settings.png) center no-repeat",
},
},
})
}
-26
View File
@@ -1,26 +0,0 @@
@import "../../_base.scss"
.navbar {
height: 55%;
padding-left: 2%;
padding-right: 2%;
background: linear-gradient(180deg, #A9D3E0, #F0F9FA);
display: flex;
flex-direction: row;
align-items: center;
button {
@extend .aeroButton;
padding-right: 2%;
}
.flex-right-aero-button {
margin-left: auto;
}
.flex-last-button {
margin: 0;
}
}
@@ -1,25 +0,0 @@
use dioxus::prelude::*;
use tracing::debug;
use crate::components::contacts_window::contacts_section::{
filter_people_conversations, filter_room_conversations, ContactsSection,
};
turf::style_sheet!("src/components/contacts_window/contacts.scss");
pub fn Contacts(cx: Scope) -> Element {
debug!("Contacts rendering");
// TODO: Test overflow
// TODO: Add offline users ?
cx.render(rsx! {
style { STYLE_SHEET },
div {
class: ClassName::CONTACTS,
ContactsSection {name: "Groups", filter: &filter_room_conversations},
ContactsSection {name: "Available", filter: &filter_people_conversations},
},
})
}
@@ -1,6 +0,0 @@
@import "../../_base.scss"
.contacts {
height: 72%;
background-color: white;
}
@@ -1,141 +0,0 @@
use dioxus::prelude::*;
use dioxus_free_icons::icons::io_icons::IoChevronDown;
use dioxus_free_icons::Icon;
use fermi::prelude::*;
use matrix_sdk::{ruma::OwnedRoomId, RoomState};
use tracing::{debug, warn};
use crate::base::{ByIdRooms, Room, CHATS_WIN_INTERFACE, ROOMS};
use crate::components::chats_window::interface::Interface as ChatsWindowInterface;
turf::style_sheet!("src/components/contacts_window/contacts_section.scss");
fn ContactsArrow(cx: Scope) -> Element {
cx.render(rsx! {
style { STYLE_SHEET },
Icon {
icon: IoChevronDown,
},
})
}
static NO_NAME_REPR: &str = "No name";
static NO_SUBJECT_REPR: &str = "No subject";
pub(super) fn filter_people_conversations(rooms_atom: UseAtomRef<ByIdRooms>) -> Vec<RefCell<Room>> {
let rooms = rooms_atom.read();
let mut filtered_rooms = Vec::<RefCell<Room>>::with_capacity(rooms.len());
for room in rooms.values() {
let is_direct = room.borrow().is_direct.unwrap();
if !is_direct {
filtered_rooms.push(room.to_owned());
}
}
filtered_rooms
}
pub(super) fn filter_room_conversations(rooms_atom: UseAtomRef<ByIdRooms>) -> Vec<RefCell<Room>> {
let rooms = rooms_atom.read();
let mut filtered_rooms = Vec::<RefCell<Room>>::with_capacity(rooms.len());
for room in rooms.values() {
let is_direct = room.borrow().is_direct.unwrap();
if is_direct {
filtered_rooms.push(room.to_owned());
}
}
filtered_rooms
}
// TODO: Handle errors
fn on_clicked_room(
room_id: &OwnedRoomId,
chats_window_interface: &UseAtomRef<ChatsWindowInterface>,
) {
let _ = chats_window_interface.read().toggle_room(room_id.clone());
}
#[component]
pub fn ContactsSection<'a>(
cx: Scope,
name: &'a str,
filter: &'a dyn Fn(UseAtomRef<ByIdRooms>) -> Vec<RefCell<Room>>,
) -> Element {
debug!("ContactsSection rendering");
let rooms_atom_ref = use_atom_ref(cx, &ROOMS);
let chats_window_interface_ref = use_atom_ref(cx, &CHATS_WIN_INTERFACE);
let contacts = filter(rooms_atom_ref.clone());
let contacts_len = contacts.len();
let show = use_state(cx, || false);
let classes = [
ClassName::SECTION,
if **show { ClassName::ACTIVE } else { "" },
]
.join(" ");
let rendered_contacts = contacts.into_iter().map(|room_ref| {
let room = room_ref.borrow();
let room_topic = room
.topic
.as_ref()
.unwrap_or(&RefCell::new(NO_SUBJECT_REPR.to_string()))
.borrow()
.to_owned();
let room_name = room.name().unwrap_or(NO_NAME_REPR.to_string());
let room_id = room.id();
let is_invited = room.matrix_room.state() == RoomState::Invited;
let formatted = format!(
"{room_name} - {}",
if is_invited {
"Invited - ".to_string()
} else {
"".to_string()
}
);
rsx!(li {
onclick: move |_| on_clicked_room(&room_id, chats_window_interface_ref),
img {
src: "./images/status_online.png",
},
p {
formatted,
},
p {
style: "color: darkgrey;",
room_topic,
},
})
});
cx.render(rsx! {
style { STYLE_SHEET },
div {
class: "{classes}",
p {
class: ClassName::HEADER,
onclick: move |_| show.set(!show),
ContactsArrow {},
format!("{name} ({contacts_len})"),
},
ul {
rendered_contacts.into_iter(),
},
},
})
}
@@ -1,67 +0,0 @@
@import "../../_base.scss"
.section {
width: 100%;
font-size: $font-size;
&.active {
ul {
height: 0;
opacity: 0;
}
svg {
transform: rotate(180deg);
}
}
.header {
height: 2%;
width: 98%;
display: flex;
flex-direction: row;
align-items: center;
cursor: pointer;
margin: 0;
margin-left: 1%;
padding-top: 1%;
font-weight: bold;
}
ul {
height: 100%;
margin: 0;
overflow: hidden;
opacity: 1;
transition: 0.4s ease;
}
li {
list-style-type: none;
cursor: pointer;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
img {
height: $icon-size;
aspect-ratio: 1;
}
p {
margin: 0;
}
}
svg {
transition: 0.4s ease;
}
.contact {
list-style-type: none;
margin: 0 auto;
text-align: left;
cursor: pointer
}
}
@@ -1,84 +0,0 @@
@import "../../_base.scss";
.contactsWindow {
width: 100%;
height: 100%;
background-color: #ECF6F9;
font-family: "Tahoma", sans-serif;
border: thin solid #707070;
border-radius: 8px;
box-shadow: 0 0 5px #00000050;
.header {
height: 10%;
width: 100%;
.titleBar {
height: 60%;
width: 100%;
background:
linear-gradient(180deg, #7DC5E3, #3883A3);
}
.userInfo {
height: 40%;
width: 100%;
background:
linear-gradient(180deg, #00658B, #0077A6);
}
}
.contactsNav {
height: calc(31/1080*100%);
background:
linear-gradient(180deg, #00658B, #0077A6);
.inner {
margin-left: 1%;
margin-right: 1%;
height: 100%;
display: flex;
align-items: center;
.flexRightAeroButton {
@extend .aeroButton;
margin-left: auto;
}
}
}
.search {
height: calc(38/1080*100%);
width: 100%;
border-bottom: thin solid #e2eaf3;
.inner {
height: 100%;
width: 98%;
padding-left: 1%;
display: flex;
flex-direction: row;
align-items: center;
.searchInput {
height: calc(23/38*100%);
width: 100%;
margin-right: 1%;
border: thin solid #c7c7c7;
box-shadow: inset 0 0 calc(3/1080*100%) #0000002a;
font-size: 8pt;
padding-left: 1%;
}
}
}
.footer {
height: 10%;
}
}
-98
View File
@@ -1,98 +0,0 @@
mod contacts;
mod contacts_section;
mod user_infos;
use dioxus::prelude::*;
use tracing::debug;
use crate::components::contacts_window::contacts::Contacts;
use crate::components::contacts_window::user_infos::UserInfos;
turf::style_sheet!("src/components/contacts_window/contacts_window.scss");
pub fn ContactsWindow(cx: Scope) -> Element {
debug!("ContactsWindow rendering");
cx.render(rsx! {
style { STYLE_SHEET },
div {
class: ClassName::CONTACTS_WINDOW,
div {
class: ClassName::HEADER,
div {
class: ClassName::TITLE_BAR,
},
div {
class: ClassName::USER_INFO,
},
UserInfos {},
},
div {
class: ClassName::CONTACTS_NAV,
div {
class: ClassName::INNER,
button {
class: ClassName::AERO_BUTTON,
style: "background: url(./images/letter.png) center no-repeat",
},
button {
class: ClassName::AERO_BUTTON,
style: "background: url(./images/directory.png) no-repeat center",
},
button {
class: ClassName::AERO_BUTTON,
style: "background: url(./images/news.png) no-repeat center",
},
button {
class: ClassName::FLEX_RIGHT_AERO_BUTTON,
style: "background: url(./images/brush.png) no-repeat center",
},
button {
class: ClassName::AERO_BUTTON,
style: "background: url(./images/settings.png) no-repeat center",
},
},
},
div {
class: ClassName::SEARCH,
div {
class: ClassName::INNER,
input {
class: ClassName::SEARCH_INPUT,
placeholder: "Find a contact...",
r#type: "text",
},
button {
class: ClassName::BUTTON,
style: "background: url(./images/add_user.png) no-repeat center",
},
button {
class: ClassName::BUTTON,
style: "background: url(./images/tbc_transfert.png) no-repeat center",
},
},
},
Contacts {},
div {
class: ClassName::FOOTER,
},
},
})
}
@@ -1,77 +0,0 @@
use dioxus::prelude::*;
// use fermi::*;
use tracing::debug;
// use crate::base::APP_SETTINGS;
use crate::components::avatar_selector::AvatarSelector;
use crate::components::icons::DownArrowIcon;
turf::style_sheet!("src/components/contacts_window/user_infos.scss");
static MESSAGE_PLACEHOLDER: &str = "<Enter a personal message>";
pub fn UserInfos(cx: Scope) -> Element {
debug!("UserInfos rendering");
// let app_settings = use_atom_ref(cx, &APP_SETTINGS);
// let store = &app_settings.read().store;
// println!("----------------------------------");
// println!("UserInfos rendering");
// // println!("store={:?}", &store);
// dbg!(&store.user_id);
// println!("----------------------------------");
// let user_id = store.user_id..as_ref().unwrap();
// let mut user_info_option = None;
let user_display_name_option: Option<bool> = None;
// let user_id_option = &store.user_id;
// if user_id_option.is_some() {
// let user_id = user_id_option.as_ref().unwrap();
// let user_info_option = store.user_infos.get(user_id);
// if user_info_option.is_some() {
// user_display_name_option = user_info_option.unwrap().display_name.as_ref();
// }
// }
cx.render(rsx! {
style { STYLE_SHEET },
div {
class: ClassName::USER_INFO,
div {
class: ClassName::AVATAR_SELECTOR,
AvatarSelector {},
},
div {
class: ClassName::INFO_CONTAINER,
div {
class: ClassName::USER_ID,
p {
class: ClassName::USER_NAME,
if user_display_name_option.is_some() { "{user_display_name}" } else { "AIE" },
},
p {
class: ClassName::USER_STATUS,
"(Busy)",
},
DownArrowIcon {},
},
div {
class: ClassName::USER_MESSAGE,
p {
// TODO: Handle user message
MESSAGE_PLACEHOLDER,
}
DownArrowIcon {},
},
},
},
})
}
@@ -1,63 +0,0 @@
@import "../../_base.scss"
.userInfo {
position: relative;
height: 75%;
width: 99%;
top: -75%;
left: 1%;
aspect-ratio: 1;
z-index: 1;
display: flex;
flex-direction: row;
align-items: center;
.avatarSelector {
height: 100%;
aspect-ratio: 1;
}
.infoContainer {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
height: 100%;
width: 100%;
.userId {
@extend .aeroButton;
height: 30%;
width: fit-content;
display: flex;
text-align: begin;
align-items: center;
.userName {
display: inline-block;
width: fit-content;
color: white;
margin: 0;
}
.userStatus {
display: inline-block;
width: fit-content;
color: #B9DDE7;
}
}
.userMessage {
@extend .aeroButton;
width: fit-content;
height: 30%;
display: flex;
text-align: begin;
align-items: center;
margin: 0;
color: white;
}
}
}
-26
View File
@@ -1,26 +0,0 @@
use dioxus::prelude::*;
turf::style_sheet!("src/components/header.scss");
pub fn Header(cx: Scope) -> Element {
cx.render(rsx! {
style { STYLE_SHEET },
div {
class: ClassName::ROOT,
img {
// src: "./assets/live-logo2.png"
src: "./images/logo-msn.png"
}
svg {
view_box: "0 0 100 10",
text {
y: "55%",
dominant_baseline: "middle",
font_size: "5",
"Windows Live Messenger",
},
},
}
})
}
-13
View File
@@ -1,13 +0,0 @@
.root {
height: 100%;
width: 100%;
display: flex;
img {
height: 100%;
}
svg {
fill: white;
}
}
-16
View File
@@ -1,16 +0,0 @@
use dioxus::prelude::*;
use dioxus_free_icons::icons::md_navigation_icons::MdArrowDropDown;
use dioxus_free_icons::Icon;
turf::style_sheet!("src/components/icons.scss");
pub fn DownArrowIcon(cx: Scope) -> Element {
cx.render(rsx! {
style { STYLE_SHEET },
Icon {
class: ClassName::DOWN_ARROW_ICON,
icon: MdArrowDropDown,
}
})
}
-7
View File
@@ -1,7 +0,0 @@
.down-arrow-icon {
color: transparent;
path:last-child {
fill: white;
}
}
-136
View File
@@ -1,136 +0,0 @@
use std::str::FromStr;
use dioxus::prelude::*;
use fermi::*;
use tracing::debug;
use crate::base::SESSION;
use crate::components::avatar_selector::AvatarSelector;
use crate::components::header::Header;
turf::style_sheet!("src/components/login.scss");
static EMPTY_PLACEHOLDER: &str = "Tmp placeholder";
pub fn Login(cx: Scope) -> Element {
debug!("Login rendering");
let session = use_atom_ref(cx, &SESSION);
let invalid_login = use_state(cx, || false);
let login = use_ref(cx, Login::new);
let password_class = if **invalid_login {
ClassName::INVALID_INPUT
} else {
""
};
let run_matrix_client = move |_| {
cx.spawn({
to_owned![session, login];
async move {
let login_ref = login.read();
session.write().update(
login_ref.homeserver_url.clone(),
login_ref.email.clone(),
login_ref.password.clone(),
);
}
})
};
let login_ref = login.read();
let placeholder = EMPTY_PLACEHOLDER.to_string();
let homeserver_url_value = login_ref.homeserver_url.as_ref().unwrap_or(&placeholder);
let email_value = login_ref.email.as_ref().unwrap_or(&placeholder);
let password_value = login_ref.password.as_ref().unwrap_or(&placeholder);
cx.render(rsx! {
style { STYLE_SHEET },
div {
class: ClassName::ROOT,
div {
class: ClassName::HEADER,
Header {},
},
div {
class: ClassName::BODY,
div {
class: ClassName::AVATAR_SELECTOR_CONTAINER,
AvatarSelector {},
},
p {
"Matrix homeserver:"
},
input {
id: "input-homeserver-url",
r#type: "text",
name: "homeserver URL",
value: "{homeserver_url_value}",
oninput: move |evt| login.write().homeserver_url = Some(evt.value.clone()),
},
p {
"E-mail address:"
},
input {
id: "login-input-email",
r#type: "text",
name: "email",
value: "{email_value}",
oninput: move |evt| login.write().email = Some(evt.value.clone()),
},
p {
"Password:"
},
input {
class: "{password_class}",
id: "login-input-password",
r#type: "password",
name: "Password",
value: "{password_value}",
oninput: move |evt| {
login.write().password = Some(evt.value.clone());
invalid_login.set(false);
},
},
div {
class: ClassName::FOOTER_BUTTONS,
input {
class: ClassName::BUTTON,
onclick: run_matrix_client,
r#type: "submit",
value: "sign in",
},
},
},
},
})
}
#[derive(Debug)]
struct Login {
homeserver_url: Option<String>,
email: Option<String>,
password: Option<String>,
}
impl Login {
fn new() -> Self {
Self {
homeserver_url: None,
email: None,
password: None,
}
}
}
-53
View File
@@ -1,53 +0,0 @@
@import "../_base.scss";
.root {
width: 90%;
height: 98%;
display: flex;
flex-direction: column;
align-items: center;
padding: 5%;
padding-top: 2%;
background: linear-gradient(rgb(138, 191, 209), rgb(236, 246, 249) 10%);
.header {
height: 5%;
width: 100%;
}
.body {
height: 50%;
width: 50%;
max-width: 400px;
display: flex;
flex-direction: column;
justify-content: center;
padding-bottom: 3%;
.invalidInput {
border-color: red;
}
.avatar-selector-container {
height: 30%;
width: 100%;
padding-left: 25%;
}
.footerButtons {
width: 100%;
padding-top: 5%;
display: flex;
justify-content: center;
}
}
}
-23
View File
@@ -1,23 +0,0 @@
use dioxus::prelude::*;
use fermi::*;
use tracing::debug;
use crate::base::SESSION;
use crate::components::contacts_window::ContactsWindow;
use crate::components::login::Login;
pub fn MainWindow(cx: Scope) -> Element {
debug!("MainWindow rendering");
let session_ref = use_atom_ref(cx, &SESSION);
let is_logged = session_ref.read().is_logged;
cx.render(rsx! {
if is_logged {
rsx!(ContactsWindow {})
}
else {
rsx!(Login {})
}
})
}
-7
View File
@@ -1,7 +0,0 @@
pub mod avatar_selector;
pub mod chats_window;
pub mod contacts_window;
pub mod header;
pub mod icons;
pub mod login;
pub mod main_window;
+1
View File
@@ -0,0 +1 @@
pub(crate) mod model;
+136
View File
@@ -0,0 +1,136 @@
use std::{cell::RefCell, collections::HashMap, rc::Rc};
use async_trait::async_trait;
use tracing::{error, instrument, trace};
use super::{
common::PresenceState,
messaging_interface::{
AccountMessagingConsumerInterface, AccountMessagingProviderInterface,
RoomMessagingConsumerInterface, SpaceMessagingConsumerInterface,
},
room::{Room, RoomId},
space::{Space, SpaceId},
store_interface::{
AccountStoreProviderInterface, RoomStoreConsumerInterface, SpaceStoreConsumerInterface,
},
};
type Rooms = HashMap<RoomId, Rc<Room>>;
type Spaces = HashMap<SpaceId, Rc<Space>>;
pub struct Account {
display_name: RefCell<Option<String>>,
avatar: RefCell<Option<Vec<u8>>>,
#[allow(dead_code)]
presence_state: RefCell<Option<PresenceState>>,
by_id_rooms: RefCell<Rooms>,
by_id_spaces: RefCell<Spaces>,
messaging_provider: Option<Rc<dyn AccountMessagingProviderInterface>>,
store: &'static dyn AccountStoreProviderInterface,
}
impl Account {
pub fn new(store: &'static dyn AccountStoreProviderInterface) -> Self {
Self {
display_name: RefCell::new(None),
avatar: RefCell::new(None),
presence_state: RefCell::new(None),
by_id_rooms: RefCell::new(Rooms::new()),
by_id_spaces: RefCell::new(Spaces::new()),
messaging_provider: None,
store,
}
}
pub fn set_messaging_provider(&mut self, provider: Rc<dyn AccountMessagingProviderInterface>) {
self.messaging_provider = Some(provider.clone());
}
#[allow(dead_code)]
pub fn get_room(&self, room_id: &RoomId) -> Option<Rc<Room>> {
self.by_id_rooms.borrow().get(room_id).cloned()
}
pub async fn get_display_name(&self) -> &RefCell<Option<String>> {
if self.display_name.borrow().is_none() {
if let Some(requester) = &self.messaging_provider {
let resp = requester.get_display_name().await;
if let Ok(display_name) = resp {
if let Some(display_name) = display_name {
self.display_name.borrow_mut().replace(display_name);
} else {
self.display_name.borrow_mut().take();
}
} else {
error!("err={:?}", resp);
}
}
}
&self.display_name
}
pub async fn get_avatar(&self) -> &RefCell<Option<Vec<u8>>> {
if self.avatar.borrow().is_none() {
if let Some(requester) = &self.messaging_provider {
let resp = requester.get_avatar().await;
if let Ok(avatar) = resp {
if let Some(avatar) = avatar {
self.avatar.borrow_mut().replace(avatar);
} else {
self.avatar.borrow_mut().take();
}
} else {
error!("err={:?}", resp);
}
}
}
&self.avatar
}
}
#[async_trait(?Send)]
impl AccountMessagingConsumerInterface for Account {
#[instrument(name = "Account", skip_all)]
async fn on_new_room(&self, room: Rc<Room>) -> Rc<dyn RoomMessagingConsumerInterface> {
trace!("on_new_room");
let room_id = room.id().clone();
self.by_id_rooms
.borrow_mut()
.insert(room_id, Rc::clone(&room));
let room_store = self
.store
.on_new_room(Rc::clone(&room) as Rc<dyn RoomStoreConsumerInterface>);
room.set_store(room_store);
room
}
#[instrument(name = "Account", skip_all)]
async fn on_new_space(&self, space: Rc<Space>) -> Rc<dyn SpaceMessagingConsumerInterface> {
trace!("on_new_space");
let space_id = space.id().clone();
self.by_id_spaces
.borrow_mut()
.insert(space_id, Rc::clone(&space));
let space_store = self
.store
.on_new_space(Rc::clone(&space) as Rc<dyn SpaceStoreConsumerInterface>);
space.set_store(space_store);
space
}
}
+7
View File
@@ -0,0 +1,7 @@
use matrix_sdk::ruma::{presence::PresenceState as MatrixPresenceState, OwnedUserId};
pub type Avatar = Vec<u8>;
pub type PresenceState = MatrixPresenceState;
pub type UserId = OwnedUserId;
+69
View File
@@ -0,0 +1,69 @@
use std::rc::Rc;
use async_trait::async_trait;
use tokio::sync::broadcast::Receiver;
use super::{
common::{Avatar, UserId},
room::{Invitation, Room, RoomId},
room_member::{AvatarUrl, RoomMember},
space::Space,
};
use crate::infrastructure::messaging::matrix::account_event::AccountEvent;
#[async_trait(?Send)]
pub trait AccountMessagingConsumerInterface {
async fn on_new_room(&self, room: Rc<Room>) -> Rc<dyn RoomMessagingConsumerInterface>;
async fn on_new_space(&self, space: Rc<Space>) -> Rc<dyn SpaceMessagingConsumerInterface>;
}
#[async_trait(?Send)]
pub trait AccountMessagingProviderInterface {
async fn get_display_name(&self) -> anyhow::Result<Option<String>>;
async fn get_avatar(&self) -> anyhow::Result<Option<Vec<u8>>>;
async fn run_forever(
&self,
account_events_consumer: &dyn AccountMessagingConsumerInterface,
account_events_receiver: Receiver<AccountEvent>,
) -> anyhow::Result<()>;
}
#[async_trait(?Send)]
pub trait RoomMessagingConsumerInterface {
async fn on_invitation(&self, _invitation: Invitation) {}
async fn on_new_topic(&self, _topic: Option<String>) {}
async fn on_new_name(&self, _name: Option<String>) {}
async fn on_new_avatar(&self, _url: Option<Avatar>) {}
#[allow(dead_code)]
async fn on_membership(&self, _member: RoomMember) {}
}
#[async_trait(?Send)]
pub trait RoomMessagingProviderInterface {
async fn get_avatar(&self, id: &RoomId) -> anyhow::Result<Option<Avatar>>;
async fn join(&self, room_id: &RoomId) -> anyhow::Result<bool>;
}
#[async_trait(?Send)]
pub trait SpaceMessagingConsumerInterface {
async fn on_child(&self, _room_id: RoomId) {}
async fn on_new_topic(&self, _topic: Option<String>) {}
async fn on_new_name(&self, _name: Option<String>) {}
}
#[async_trait(?Send)]
pub trait SpaceMessagingProviderInterface {}
// TODO: Rework
#[async_trait(?Send)]
pub trait MemberMessagingProviderInterface {
async fn get_avatar(
&self,
avatar_url: Option<AvatarUrl>,
room_id: RoomId,
user_id: UserId,
) -> anyhow::Result<Option<Avatar>>;
}
+8
View File
@@ -0,0 +1,8 @@
pub(crate) mod account;
pub(crate) mod common;
pub(crate) mod messaging_interface;
pub(crate) mod room;
pub(crate) mod room_member;
pub(crate) mod session;
pub(crate) mod space;
pub(crate) mod store_interface;
+323
View File
@@ -0,0 +1,323 @@
use std::{
cell::RefCell,
collections::HashMap,
fmt::{Debug, Formatter},
rc::Rc,
};
use async_trait::async_trait;
use futures::future::{join, join_all};
use matrix_sdk::{ruma::OwnedRoomId, RoomState as MatrixRoomState};
use tracing::{debug, debug_span, error, instrument, trace};
use super::{
common::{Avatar, UserId},
messaging_interface::{RoomMessagingConsumerInterface, RoomMessagingProviderInterface},
room_member::RoomMember,
space::SpaceId,
store_interface::{RoomStoreConsumerInterface, RoomStoreProviderInterface},
};
use crate::infrastructure::services::mozaik_builder::create_mozaik;
pub type RoomId = OwnedRoomId;
#[derive(Clone, PartialEq)]
pub struct Invitation {
invitee_id: UserId,
sender_id: UserId,
is_account_user: bool,
}
impl Invitation {
pub fn new(invitee_id: UserId, sender_id: UserId, is_account_user: bool) -> Self {
Self {
invitee_id,
sender_id,
is_account_user,
}
}
pub fn is_account_user(&self) -> bool {
self.is_account_user
}
}
impl Debug for Invitation {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
f.debug_tuple("Invitation")
.field(&self.invitee_id)
.field(&self.sender_id)
.field(&self.is_account_user)
.finish()
}
}
pub struct Room {
id: RoomId,
name: RefCell<Option<String>>,
topic: Option<String>,
is_direct: Option<bool>,
state: Option<MatrixRoomState>,
avatar: RefCell<Option<Avatar>>,
invitations: RefCell<HashMap<UserId, Invitation>>,
members: RefCell<HashMap<UserId, RoomMember>>,
spaces: Vec<SpaceId>,
messaging_provider: Option<Rc<dyn RoomMessagingProviderInterface>>,
store: RefCell<Option<Rc<dyn RoomStoreProviderInterface>>>,
}
impl PartialEq for Room {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Room {
pub fn new(
id: RoomId,
// TODO: move space at the end of the list of params
name: Option<String>,
topic: Option<String>,
is_direct: Option<bool>,
state: Option<MatrixRoomState>,
spaces: Vec<SpaceId>,
) -> Self {
Self {
id,
name: RefCell::new(name),
topic,
is_direct,
state,
avatar: RefCell::new(None),
invitations: RefCell::new(HashMap::new()),
members: RefCell::new(HashMap::new()),
spaces,
messaging_provider: None,
store: RefCell::new(None),
}
}
pub fn set_messaging_provider(
&mut self,
messaging_provider: Rc<dyn RoomMessagingProviderInterface>,
) {
self.messaging_provider = Some(messaging_provider);
}
pub fn set_store(&self, store: Rc<dyn RoomStoreProviderInterface>) {
*self.store.borrow_mut() = Some(store);
}
pub fn id(&self) -> &RoomId {
&self.id
}
#[allow(dead_code)]
pub fn name(&self) -> Option<String> {
self.name.borrow().clone()
}
#[allow(dead_code)]
pub fn set_topic(&mut self, topic: Option<String>) {
self.topic = topic;
}
#[allow(dead_code)]
pub fn state(&self) -> &Option<MatrixRoomState> {
&self.state
}
#[allow(dead_code)]
pub fn is_invited(&self) -> Option<bool> {
self.state.map(|state| state == MatrixRoomState::Invited)
}
#[instrument(name = "Room", skip_all)]
fn add_invitation(&self, invitation: Invitation) {
self.members.borrow_mut().remove(&invitation.invitee_id);
self.invitations
.borrow_mut()
.insert(invitation.invitee_id.clone(), invitation.clone());
if let Some(store) = self.store.borrow().as_ref() {
store.on_invitation(invitation);
}
}
#[instrument(name = "Room", skip_all)]
fn add_member(&self, member: RoomMember) {
let mut members = self.members.borrow_mut();
members.insert(member.id().clone(), member.clone());
// USe the member display name to name the room if it's direct and has no name set.
if self.name.borrow().is_none() && members.len() == 1 {
if let Some(member_display_name) = member.display_name() {
let name = Some(member_display_name.clone());
self.name.borrow_mut().clone_from(&name);
if let Some(store) = self.store.borrow().as_ref() {
store.on_new_name(name);
}
}
}
if let Some(store) = self.store.borrow().as_ref() {
store.on_new_member(member);
}
}
pub async fn get_avatar(&self) -> Option<Avatar> {
if self.avatar.borrow().is_none() {
if let Some(requester) = &self.messaging_provider {
let resp = requester.get_avatar(&self.id).await;
if let Ok(avatar) = resp {
if let Some(avatar) = avatar {
return Some(avatar);
} else {
debug!("The room has no avatar... let's generate one");
match self.gen_room_avatar_with_members().await {
Ok(avatar) => {
if let Some(avatar) = avatar {
return Some(avatar);
}
}
Err(err) => {
error!("err={}", err);
}
}
}
} else {
error!("err={:?}", resp);
}
}
}
self.avatar.borrow().clone()
}
#[instrument(name = "Room", skip_all)]
async fn gen_room_avatar_with_members(&self) -> anyhow::Result<Option<Avatar>> {
let mut account_member = None::<&RoomMember>;
let mut other_members = Vec::<&RoomMember>::new();
let members = self.members.borrow();
for member in members.values() {
if member.is_account_user() {
account_member = Some(member);
} else {
other_members.push(member);
}
}
let other_avatars_futures =
join_all(other_members.iter().map(|member| member.get_avatar()));
let (other_avatars, account_avatar) = if let Some(account_member) = account_member {
join(other_avatars_futures, account_member.get_avatar()).await
} else {
(
join_all(other_members.iter().map(|member| member.get_avatar())).await,
None,
)
};
let other_avatars: Vec<Vec<u8>> = other_avatars.into_iter().flatten().collect();
if account_avatar.is_some() || !other_avatars.is_empty() {
let _guard = debug_span!("AvatarRendering").entered();
Ok(Some(
create_mozaik(256, 256, other_avatars, account_avatar).await,
))
} else {
Ok(None)
}
}
}
#[async_trait(?Send)]
impl RoomMessagingConsumerInterface for Room {
#[instrument(name = "Room", skip_all)]
async fn on_invitation(&self, invitation: Invitation) {
trace!("on_invitation");
let sender_id = invitation.sender_id.clone();
self.add_invitation(invitation);
if self.is_direct.unwrap_or(false) {
debug!("1to1 conversation, using the {} avatar", &sender_id);
if let Ok(avatar) = self.gen_room_avatar_with_members().await {
debug!("Avatar successfully generated");
self.avatar.borrow_mut().clone_from(&avatar);
if let Some(store) = self.store.borrow().as_ref() {
store.on_new_avatar(avatar);
}
}
}
}
#[instrument(name = "Room", skip_all)]
async fn on_membership(&self, member: RoomMember) {
trace!("on_membership");
self.add_member(member);
}
#[instrument(name = "Room", skip_all)]
async fn on_new_topic(&self, _topic: Option<String>) {
trace!("on_new_topic");
}
#[instrument(name = "Room", skip_all)]
async fn on_new_name(&self, _name: Option<String>) {
trace!("on_new_name");
}
#[instrument(name = "Room", skip_all)]
async fn on_new_avatar(&self, avatar: Option<Avatar>) {
trace!("on_new_avatar");
self.avatar.borrow_mut().clone_from(&avatar);
if let Some(store) = self.store.borrow().as_ref() {
store.on_new_avatar(avatar);
}
}
}
#[async_trait(?Send)]
impl RoomStoreConsumerInterface for Room {
fn id(&self) -> &RoomId {
&self.id
}
fn is_direct(&self) -> Option<bool> {
self.is_direct
}
fn name(&self) -> Option<String> {
self.name.borrow().clone()
}
fn topic(&self) -> Option<String> {
self.topic.clone()
}
async fn avatar(&self) -> Option<Avatar> {
self.get_avatar().await
}
fn spaces(&self) -> &Vec<SpaceId> {
&self.spaces
}
async fn join(&self) {
if let Some(messaging_provider) = &self.messaging_provider {
let _ = messaging_provider.join(&self.id).await;
}
}
}
+93
View File
@@ -0,0 +1,93 @@
use std::{
cell::RefCell,
fmt::{Debug, Formatter},
rc::Rc,
};
use matrix_sdk::ruma::OwnedMxcUri;
use tracing::error;
use super::{
common::{Avatar, UserId},
messaging_interface::MemberMessagingProviderInterface,
room::RoomId,
};
pub type AvatarUrl = OwnedMxcUri;
#[derive(Clone)]
pub struct RoomMember {
id: UserId,
display_name: Option<String>,
avatar_url: Option<AvatarUrl>,
room_id: RoomId,
is_account_user: bool,
#[allow(dead_code)]
avatar: RefCell<Option<Avatar>>,
messaging_provider: Rc<dyn MemberMessagingProviderInterface>,
}
impl RoomMember {
pub fn new(
id: UserId,
display_name: Option<String>,
avatar_url: Option<AvatarUrl>,
room_id: RoomId,
is_account_user: bool,
messaging_provider: Rc<dyn MemberMessagingProviderInterface>,
) -> Self {
Self {
id,
display_name,
avatar_url,
room_id,
is_account_user,
avatar: RefCell::new(None),
messaging_provider,
}
}
pub fn id(&self) -> &UserId {
&self.id
}
pub fn display_name(&self) -> &Option<String> {
&self.display_name
}
#[allow(dead_code)]
pub fn room_id(&self) -> &RoomId {
&self.room_id
}
pub fn is_account_user(&self) -> bool {
self.is_account_user
}
pub async fn get_avatar(&self) -> Option<Avatar> {
match self
.messaging_provider
.get_avatar(
self.avatar_url.clone(),
self.room_id.clone(),
self.id.clone(),
)
.await
{
Ok(avatar) => avatar,
Err(err) => {
error!("err={}", err);
None
}
}
}
}
impl Debug for RoomMember {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
f.debug_struct("RoomMember").field("id", &self.id).finish()
}
}
+26
View File
@@ -0,0 +1,26 @@
pub struct Session {
pub homeserver_url: Option<String>,
pub username: Option<String>,
pub password: Option<String>,
pub is_logged: bool,
}
impl Session {
pub fn new() -> Self {
Self {
homeserver_url: None,
username: None,
password: None,
is_logged: false,
}
}
pub fn update(
&mut self,
homeserver_url: Option<String>,
username: Option<String>,
password: Option<String>,
) {
self.homeserver_url = homeserver_url;
self.username = username;
self.password = password;
}
}
+107
View File
@@ -0,0 +1,107 @@
use std::{cell::RefCell, collections::HashSet, rc::Rc};
use async_trait::async_trait;
use matrix_sdk::ruma::OwnedRoomId;
use tracing::{instrument, trace};
use super::{
common::Avatar,
messaging_interface::{SpaceMessagingConsumerInterface, SpaceMessagingProviderInterface},
room::RoomId,
store_interface::{SpaceStoreConsumerInterface, SpaceStoreProviderInterface},
};
pub type SpaceId = OwnedRoomId;
// TODO: Add membership?
pub struct Space {
id: SpaceId,
name: RefCell<Option<String>>,
topic: RefCell<Option<String>>,
#[allow(dead_code)]
avatar: RefCell<Option<Avatar>>,
children: RefCell<HashSet<RoomId>>, // We don´t expect to manage nested spaces
messaging_provider: Option<Rc<dyn SpaceMessagingProviderInterface>>,
store: RefCell<Option<Rc<dyn SpaceStoreProviderInterface>>>,
}
impl PartialEq for Space {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Space {
pub fn new(id: SpaceId, name: Option<String>, topic: Option<String>) -> Self {
Self {
id,
name: RefCell::new(name),
topic: RefCell::new(topic),
#[allow(dead_code)]
avatar: RefCell::new(None),
children: RefCell::new(HashSet::new()),
messaging_provider: None,
store: RefCell::new(None),
}
}
pub fn set_messaging_provider(&mut self, provider: Rc<dyn SpaceMessagingProviderInterface>) {
self.messaging_provider = Some(provider);
}
pub fn set_store(&self, store: Rc<dyn SpaceStoreProviderInterface>) {
*self.store.borrow_mut() = Some(store);
}
pub fn id(&self) -> &SpaceId {
&self.id
}
#[allow(dead_code)]
pub fn name(&self) -> Option<String> {
self.name.borrow().clone()
}
}
#[async_trait(?Send)]
impl SpaceMessagingConsumerInterface for Space {
#[instrument(name = "Space", skip_all)]
async fn on_child(&self, room_id: RoomId) {
trace!("on_child");
self.children.borrow_mut().insert(room_id);
}
#[instrument(name = "Space", skip_all)]
async fn on_new_topic(&self, topic: Option<String>) {
trace!("on_new_topic");
*self.topic.borrow_mut() = topic;
}
#[instrument(name = "Space", skip_all)]
async fn on_new_name(&self, name: Option<String>) {
trace!("on_new_name");
self.name.borrow_mut().clone_from(&name);
if let Some(store) = self.store.borrow().as_ref() {
store.set_name(name);
}
}
}
impl SpaceStoreConsumerInterface for Space {
fn id(&self) -> &SpaceId {
&self.id
}
fn name(&self) -> Option<String> {
self.name.borrow().clone()
}
}
+56
View File
@@ -0,0 +1,56 @@
use std::rc::Rc;
use async_trait::async_trait;
use super::{
common::Avatar,
room::{Invitation, RoomId},
room_member::RoomMember,
space::SpaceId,
};
#[allow(dead_code)]
pub trait AccountStoreConsumerInterface {}
pub trait AccountStoreProviderInterface {
fn on_new_room(
&self,
room: Rc<dyn RoomStoreConsumerInterface>,
) -> Rc<dyn RoomStoreProviderInterface>;
fn on_new_space(
&self,
space: Rc<dyn SpaceStoreConsumerInterface>,
) -> Rc<dyn SpaceStoreProviderInterface>;
}
#[async_trait(?Send)]
pub trait RoomStoreConsumerInterface {
fn id(&self) -> &RoomId;
fn is_direct(&self) -> Option<bool>;
fn name(&self) -> Option<String>;
fn topic(&self) -> Option<String>;
fn spaces(&self) -> &Vec<SpaceId>;
#[allow(dead_code)]
async fn avatar(&self) -> Option<Avatar>;
async fn join(&self);
}
pub trait RoomStoreProviderInterface {
fn on_new_name(&self, name: Option<String>);
fn on_new_avatar(&self, avatar: Option<Avatar>);
#[allow(dead_code)]
fn on_new_topic(&self, topic: Option<String>);
fn on_new_member(&self, member: RoomMember);
fn on_invitation(&self, invitation: Invitation);
}
#[allow(dead_code)]
pub trait SpaceStoreConsumerInterface {
fn id(&self) -> &SpaceId;
fn name(&self) -> Option<String>;
}
pub trait SpaceStoreProviderInterface {
fn set_name(&self, _name: Option<String>) {}
}

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