├── .github
└── workflows
│ ├── check.yml
│ ├── deploy.yml
│ └── tests.yml
├── .gitignore
├── .idea
├── .gitignore
├── Daydream.iml
├── inspectionProfiles
│ └── Project_Default.xml
├── jsLibraryMappings.xml
├── jsonSchemas.xml
├── misc.xml
├── modules.xml
└── vcs.xml
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── Makefile.toml
├── README.md
├── cypress.json
├── cypress
├── fixtures
│ └── login.json
├── integration
│ ├── login_spec.js
│ ├── matrix-fake-api.js
│ ├── roomlist_spec.js
│ └── utils.js
├── plugins
│ └── index.js
└── support
│ ├── commands.js
│ └── index.js
├── i18n.toml
├── i18n
├── mo
│ └── de
│ │ └── daydream.mo
├── po
│ └── de
│ │ └── daydream.po
└── pot
│ ├── daydream.pot
│ └── src
│ ├── app
│ ├── components
│ │ ├── event_list.pot
│ │ ├── events
│ │ │ ├── image.pot
│ │ │ ├── mod.pot
│ │ │ ├── notice.pot
│ │ │ ├── text.pot
│ │ │ └── video.pot
│ │ ├── input.pot
│ │ ├── mod.pot
│ │ ├── raw_html.pot
│ │ ├── room_list.pot
│ │ └── room_list
│ │ │ ├── item.pot
│ │ │ └── mod.pot
│ ├── matrix
│ │ ├── login.pot
│ │ ├── mod.pot
│ │ ├── sync.pot
│ │ └── types.pot
│ ├── mod.pot
│ └── views
│ │ ├── login.pot
│ │ ├── main_view.pot
│ │ └── mod.pot
│ ├── bin
│ ├── app.pot
│ └── native_worker.pot
│ ├── constants.pot
│ ├── errors.pot
│ ├── lib.pot
│ └── utils
│ ├── mod.pot
│ ├── notifications.pot
│ ├── ruma.pot
│ └── string_utils.pot
├── media
└── DaydreamLogo.ai
├── netlify.toml
├── package-lock.json
├── package.json
├── src
├── app
│ ├── components
│ │ ├── event_list.rs
│ │ ├── events
│ │ │ ├── image.rs
│ │ │ ├── mod.rs
│ │ │ ├── notice.rs
│ │ │ ├── text.rs
│ │ │ └── video.rs
│ │ ├── input.rs
│ │ ├── mod.rs
│ │ ├── raw_html.rs
│ │ └── room_list
│ │ │ ├── item.rs
│ │ │ └── mod.rs
│ ├── matrix
│ │ ├── login.rs
│ │ ├── mod.rs
│ │ ├── sync.rs
│ │ └── types.rs
│ ├── mod.rs
│ ├── svgs
│ │ ├── DaydreamLogo_v0.svg
│ │ ├── DaydreamLogo_v0_light.svg
│ │ └── loading_animation.svg
│ └── views
│ │ ├── login.rs
│ │ ├── main_view.rs
│ │ └── mod.rs
├── bin
│ ├── app.rs
│ └── native_worker.rs
├── constants.rs
├── errors.rs
├── lib.rs
└── utils
│ ├── mod.rs
│ ├── notifications.rs
│ ├── ruma.rs
│ └── string_utils.rs
├── static
├── custom-css.scss
├── dark_theme
│ ├── _globals.scss
│ └── _variables.scss
├── images
│ ├── login_bg.jpg
│ └── login_bg.webp
├── index.html
├── index_rust_only.html
├── light_theme
│ ├── _globals.scss
│ └── _variables.scss
├── lightbox.scss
├── normalize.css
├── style.scss
├── sun-loading-animation.scss
├── theme_slider.scss
└── variable-ovewrites.scss
└── webdriver.json
/.github/workflows/check.yml:
--------------------------------------------------------------------------------
1 | name: Cargo Check
2 | on: [pull_request]
3 | jobs:
4 | check:
5 | runs-on: ubuntu-latest
6 | steps:
7 | - uses: actions/checkout@v2
8 | - name: Setup Rust
9 | uses: actions-rs/toolchain@v1
10 | with:
11 | toolchain: nightly
12 | - name: Run fmt
13 | run: cargo fmt -- --check
14 | - name: Run clippy
15 | run: cargo clippy -- --deny=warnings
16 | - name: Run check
17 | run: cargo check
18 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy to Github pages
2 |
3 | on:
4 | push:
5 | branches: [main]
6 |
7 | jobs:
8 | deploy:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v2
12 |
13 | - name: Setup Rust
14 | uses: actions-rs/toolchain@v1
15 | with:
16 | toolchain: nightly
17 | target: wasm32-unknown-unknown
18 |
19 | - run: rustup default nightly
20 |
21 | - name: Setup Node
22 | uses: actions/setup-node@v1
23 | with:
24 | node-version: 14
25 |
26 | - name: Install
27 | run: yarn
28 |
29 | - name: Get emscripten SDK
30 | run: wget https://github.com/emscripten-core/emsdk/archive/master.zip && unzip master.zip && ./emsdk-master/emsdk install latest && ./emsdk-master/emsdk activate latest
31 |
32 | - name: Install gettext
33 | run: sudo apt-get update && sudo apt-get install -y gettext
34 |
35 | - name: Build
36 | run: source emsdk-master/emsdk_env.sh && cargo install cargo-make && cargo make dist
37 |
38 | - name: Deploy
39 | uses: peaceiris/actions-gh-pages@v3
40 | with:
41 | github_token: ${{ secrets.GITHUB_TOKEN }}
42 | publish_dir: ./dist
43 | cname: app.daydream.im
44 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | #name: E2E Tests
2 | #on: [push]
3 | #jobs:
4 | # chrome:
5 | # runs-on: ubuntu-latest
6 | # strategy:
7 | # matrix:
8 | # # run 3 copies of the current job in parallel
9 | # containers: [1, 2, 3]
10 | # steps:
11 | # - uses: actions/checkout@v1
12 | # - run: rustup default nightly
13 | # - uses: cypress-io/github-action@v1
14 | # with:
15 | # browser: chrome
16 | # command: npm run test
17 | # env:
18 | # WAIT_ON_TIMEOUT: 600000
19 | # - run: |
20 | # mkdir -p cypress/screenshots
21 | # mkdir -p cypress/videos
22 | # # after the test run completes
23 | # # store videos and any screenshots
24 | # # NOTE: screenshots will be generated only if E2E test failed
25 | # # thus we store screenshots only on failures
26 | # # Alternative: create and commit an empty cypress/screenshots folder
27 | # # to always have something to upload
28 | # - uses: actions/upload-artifact@v2
29 | # if: failure()
30 | # with:
31 | # name: cypress-screenshots
32 | # path: cypress/screenshots
33 | # # Test run video was always captured, so this action uses "always()" condition
34 | # - uses: actions/upload-artifact@v2
35 | # if: always()
36 | # with:
37 | # name: cypress-videos
38 | # path: cypress/videos
39 | # #firefox:
40 | # # runs-on: ubuntu-latest
41 | # # container:
42 | # # image: cypress/browsers:node12.16.1-chrome80-ff73
43 | # # options: --user 1001
44 | # # steps:
45 | # # - uses: actions/checkout@v1
46 | # # - uses: cypress-io/github-action@v1
47 | # # with:
48 | # # browser: firefox
49 | # # command: npm run test:firefox
50 | # # # after the test run completes
51 | # # # store videos and any screenshots
52 | # # # NOTE: screenshots will be generated only if E2E test failed
53 | # # # thus we store screenshots only on failures
54 | # # # Alternative: create and commit an empty cypress/screenshots folder
55 | # # # to always have something to upload
56 | # # - uses: actions/upload-artifact@v1
57 | # # if: failure()
58 | # # with:
59 | # # name: cypress-screenshots
60 | # # path: cypress/screenshots
61 | # # # Test run video was always captured, so this action uses "always()" condition
62 | # # - uses: actions/upload-artifact@v1
63 | # # if: always()
64 | # # with:
65 | # # name: cypress-videos
66 | # # path: cypress/videos
67 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | **/*.rs.bk
3 | pkg/
4 | dist/
5 | wasm-pack.log
6 | node_modules
7 | cypress/videos
8 | cypress/screenshots
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Datasource local storage ignored files
5 | /dataSources/
6 | /dataSources.local.xml
7 | # Editor-based HTTP Client requests
8 | /httpRequests/
9 |
--------------------------------------------------------------------------------
/.idea/Daydream.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/jsLibraryMappings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/jsonSchemas.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "daydream"
3 | version = "0.1.0"
4 | authors = ["MTRNord "]
5 | edition = "2018"
6 | license = "AGPL-v3.0"
7 | repository = "https://github.com/MTRNord/Daydream"
8 | description = "A Matrix Web client written in Rust"
9 |
10 | [[bin]]
11 | name = "daydream_app"
12 | path = "src/bin/app.rs"
13 |
14 | [[bin]]
15 | name = "daydream_worker"
16 | path = "src/bin/native_worker.rs"
17 |
18 | [dependencies]
19 | console_error_panic_hook = { version = "0.1" }
20 | log = "0.4"
21 | tracing = {version = "0.1", features = ["log-always"] }
22 | serde = { version = "1.0", features = ["rc", "derive"] }
23 | serde_json = "1.0"
24 | wasm-bindgen = "0.2"
25 | wasm-bindgen-futures = "0.4"
26 | wasm-logger = "0.2"
27 | #wee_alloc = "0.4"
28 | lazy_static = "1.4.0"
29 | gh-emoji = "1.0.3"
30 |
31 | # Yew
32 | yew = { git = "https://github.com/daydream-mx/yew.git", branch = "MTRNord/daydream-no-bincode" }
33 | yew-router = { git = "https://github.com/daydream-mx/yew.git", branch = "MTRNord/daydream-no-bincode" }
34 | yewtil = { git = "https://github.com/daydream-mx/yew.git", branch = "MTRNord/daydream-no-bincode" }
35 |
36 | # Matrix
37 | #, branch = "daydream"
38 | matrix-sdk = { version = "0.1.0", git = "https://github.com/DevinR528/matrix-rust-sdk", default-features = false, features = ["messages"], branch = "power-ev-overflow"}# rev = "89c9e311408d2c57245f4ec5af7bbd4daa0046d3" # features = ["encryption"]}
39 | url = "2.1.1"
40 | thiserror = "1.0"
41 | futures-locks = { git = "https://github.com/asomers/futures-locks", default-features = false }
42 |
43 | # Markdown
44 | pulldown-cmark = "0.7.2"
45 |
46 | # Translations
47 | tr = { version = "0.1.3", default-features = false, features = ["gettext"]}
48 | i18n-embed = { version = "0.6", features = ["web-sys-requester"] }
49 | rust-embed = { version = "5", features = ["debug-embed", "compression"]}
50 |
51 | # Make links links again!
52 | linkify = "0.4.0"
53 |
54 | # Used for lightboxes
55 | rand = "0.7"
56 |
57 | [dependencies.web-sys]
58 | version = "0.3"
59 | features = [
60 | 'KeyboardEvent',
61 | 'HtmlElement',
62 | 'DomStringMap',
63 | 'Notification',
64 | 'NotificationPermission',
65 | 'NotificationOptions',
66 | 'Window'
67 | ]
68 |
69 | [patch.'https://github.com/seanmonstar/reqwest']
70 | reqwest = { git = "https://github.com/seanmonstar//reqwest", rev = "d42385e7f2cc364efa5e16a7154e7e0cebdd1b57"}
71 |
72 | [profile.release]
73 | # less code to include into binary
74 | panic = 'abort'
75 | # optimization over all codebase ( better optimization, slower build )
76 | codegen-units = 1
77 | # optimization for size ( more aggresive )
78 | opt-level = 'z'
79 | # optimization for size
80 | # opt-level = 's'
81 | # link time optimization using using whole-program analysis
82 | lto = true
83 |
--------------------------------------------------------------------------------
/Makefile.toml:
--------------------------------------------------------------------------------
1 | [config]
2 | skip_core_tasks = true
3 |
4 | [tasks.format]
5 | install_crate = { crate_name = "rustfmt", rustup_component_name = "rustfmt", binary = "rustfmt", test_arg = "--help" }
6 | command = "cargo"
7 | args = ["fmt"]
8 |
9 | [tasks.ruma-docs]
10 | command = "cargo"
11 | args = ["doc", "-p", "ruma", "--open"]
12 |
13 | [tasks.wasm-clean]
14 | script_runner = "@rust"
15 | script = [
16 | '''
17 | fn main() -> std::io::Result<()> {
18 | if std::path::Path::new("./dist").exists() {
19 | std::fs::remove_dir_all("./dist")?;
20 | }
21 | Ok(())
22 | }
23 | '''
24 | ]
25 |
26 | [tasks.dist-folder]
27 | script_runner = "@rust"
28 | script = [
29 | '''
30 | fn main() -> std::io::Result<()> {
31 | std::fs::create_dir_all("./dist")?;
32 | Ok(())
33 | }
34 | '''
35 | ]
36 |
37 | [tasks.dist]
38 | # TODO add scss task when it works
39 | dependencies = ["format", "wasm-clean", "update-translations", "dist-folder", "scss", "pure-build-app", "wasm-build", "pure-build-worker", "wasm-build-worker", "copy-files"]
40 |
41 | [tasks.dist-debug]
42 | # TODO add scss task when it works
43 | dependencies = ["format", "wasm-clean", "update-translations", "dist-folder", "scss", "pure-build-app-debug", "wasm-build-debug", "pure-build-worker-debug", "wasm-build-worker-debug", "copy-files"]
44 |
45 | [tasks.run]
46 | watch = {watch = ["./src", "./static", "./startup_helper"], poll = true}
47 | dependencies = ["dist-debug", "start-server"]
48 |
49 | [tasks.start-server]
50 | script_runner = "@shell"
51 | script = [
52 | '''
53 | cd dist
54 | python3 -m http.server
55 | '''
56 | ]
57 |
58 | [tasks.pure-build-app]
59 | command = "cargo"
60 | args = ["build", "--release", "--target", "wasm32-unknown-unknown", "--bin", "daydream_app"]
61 |
62 | [tasks.pure-build-app-debug]
63 | command = "cargo"
64 | args = ["build", "--target", "wasm32-unknown-unknown", "--bin", "daydream_app"]
65 |
66 | [tasks.pure-build-worker]
67 | command = "cargo"
68 | args = ["build", "--release", "--target", "wasm32-unknown-unknown", "--bin", "daydream_worker"]
69 |
70 | [tasks.pure-build-worker-debug]
71 | command = "cargo"
72 | args = ["build", "--target", "wasm32-unknown-unknown", "--bin", "daydream_worker"]
73 |
74 | [tasks.wasm-build]
75 | install_crate = { crate_name = "wasm-bindgen-cli", binary = "wasm-bindgen", test_arg = "--help" }
76 | command = "wasm-bindgen"
77 | args = ["--no-typescript", "--out-dir", "./dist/", "--target", "web", "--out-name", "daydream", "./target/wasm32-unknown-unknown/release/daydream_app.wasm"]
78 |
79 | [tasks.wasm-build-worker]
80 | install_crate = { crate_name = "wasm-bindgen-cli", binary = "wasm-bindgen", test_arg = "--help" }
81 | command = "wasm-bindgen"
82 | args = ["--no-typescript", "--out-dir", "./dist/", "--target", "no-modules", "--out-name", "worker", "./target/wasm32-unknown-unknown/release/daydream_worker.wasm"]
83 |
84 | [tasks.wasm-build-debug]
85 | install_crate = { crate_name = "wasm-bindgen-cli", binary = "wasm-bindgen", test_arg = "--help" }
86 | command = "wasm-bindgen"
87 | args = ["--no-typescript", "--debug", "--out-dir", "./dist/", "--target", "web", "--out-name", "daydream", "./target/wasm32-unknown-unknown/debug/daydream_app.wasm"]
88 |
89 | [tasks.wasm-build-worker-debug]
90 | install_crate = { crate_name = "wasm-bindgen-cli", binary = "wasm-bindgen", test_arg = "--help" }
91 | command = "wasm-bindgen"
92 | args = ["--no-typescript", "--debug", "--out-dir", "./dist/", "--target", "no-modules", "--out-name", "worker", "./target/wasm32-unknown-unknown/debug/daydream_worker.wasm"]
93 |
94 |
95 | [tasks.copy-files]
96 | # TODO use rust
97 | script_runner = "@shell"
98 | script = [
99 | '''
100 | mkdir -p dist/images
101 | cp static/index_rust_only.html dist/index.html
102 | cp static/images/* dist/images/
103 | cp static/normalize.css dist/normalize.css
104 | '''
105 | ]
106 |
107 | [tasks.update-translations]
108 | condition = { platforms = ["mac", "linux"] }
109 | ignore_errors = true
110 | command = "cargo"
111 | args = ["i18n"]
112 | dependencies = ["install-xtr"]
113 |
114 | [tasks.install-xtr]
115 | condition = { platforms = ["mac", "linux"] }
116 | install_crate = { crate_name = "xtr", binary = "xtr", test_arg = "--help" }
117 |
118 | [tasks.scss]
119 | install_crate = { crate_name = "grass", binary = "grass", test_arg = "--help" }
120 | install_crate_args = ["--git", "https://github.com/connorskees/grass", "grass"]
121 | command = "grass"
122 | args = ["static/style.scss", "dist/style.css"]
123 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | [](https://app.netlify.com/sites/daydream-rs/deploys)
3 | [![Contributors][contributors-shield]][contributors-url]
4 | [![Forks][forks-shield]][forks-url]
5 | [![Stargazers][stars-shield]][stars-url]
6 | [![Issues][issues-shield]][issues-url]
7 | [![GPL-3.0 License][license-shield]][license-url]
8 | [](https://matrix.to/#/#daydream:nordgedanken.dev)
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
Daydream Matrix Client
20 |
21 |
22 | A small first try of writing a Matrix Client in wasm and rust using the Matrix Rust SDK
23 |
25 |
26 |
27 |
29 | Report Bug
30 | ·
31 | Request Feature
32 |
33 |
34 |
35 |
36 |
37 |
38 | ## Table of Contents
39 |
40 | * [About the Project](#about-the-project)
41 | * [Built With](#built-with)
42 | * [Getting Started](#getting-started)
43 | * [Prerequisites](#prerequisites)
44 | * [Setting up a Development Environment](#setting-up-a-development-environment)
45 |
46 | * [Roadmap](#roadmap)
47 | * [Contributing](#contributing)
48 | * [License](#license)
49 | * [Contact](#contact)
50 | * [Acknowledgements](#acknowledgements)
51 |
52 |
53 |
54 |
55 | ## About The Project
56 |
57 |
58 |
59 |
60 | ### Built With
61 |
62 | * [Rust](https://www.rust-lang.org/)
63 | * [Matrix](https://matrix.org)
64 | * [Yew](https://github.com/yewstack/yew)
65 | * [Matrix Rust SDK](https://github.com/matrix-org/matrix-rust-sdk/)
66 | * [Netlify](https://netlify.com)
67 | * [Nodejs](https://nodejs.org/en/)
68 | * [Yarn](https://yarnpkg.com/)
69 |
70 |
71 |
72 |
73 | ## Getting Started
74 |
75 | To get a local copy up and running follow these simple steps.
76 |
77 | ### Prerequisites
78 |
79 | This is an example of how to list things you need to use the software and how to install them.
80 | * Rust
81 | * Nodejs
82 | * Yarn
83 | * gettext (See: https://github.com/kellpossible/cargo-i18n#system-requirements)
84 | * When you want to recompile translations:
85 | * https://github.com/kellpossible/cargo-i18n
86 | * `cargo install xtr`
87 |
88 | ### Setting up a Development Environment
89 |
90 | 1. Clone the repo
91 | ```sh
92 | git clone https://github.com/MTRNord/Daydream.git
93 | ```
94 | 2. Install node dependencies
95 | ```sh
96 | yarn install
97 | ```
98 |
99 | #### 🛠️ Build
100 |
101 | ```sh
102 | yarn run build
103 | ```
104 |
105 | #### 🔬 Serve locally
106 |
107 | ```sh
108 | yarn run start:dev
109 | ```
110 |
111 |
119 |
120 |
121 | ## Roadmap
122 |
123 | See the [open issues](https://github.com/MTRNord/Daydream/issues) for a list of proposed features (and known issues).
124 |
125 |
126 |
127 |
128 | ## Contributing
129 |
130 | Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**.
131 |
132 | 1. Fork the Project
133 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
134 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
135 | 4. Push to the Branch (`git push origin feature/AmazingFeature`)
136 | 5. Open a Pull Request
137 |
138 |
139 |
140 |
141 | ## License
142 |
143 | Distributed under the AGPL-v3.0 License. See `LICENSE` for more information.
144 |
145 |
146 |
147 |
148 | ## Contact
149 |
150 | Matrix Room - [#daydream:nordgedanken.dev](https://matrix.to/#/#daydream:nordgedanken.dev)
151 |
152 | MTRNord - [@mtrnord](https://github.com/mtrnord) - https://matrix.to/#/@mtrnord:nordgedanken.dev
153 |
154 | Project Link: [https://github.com/MTRNord/Daydream](https://github.com/MTRNord/Daydream)
155 |
156 |
157 |
158 |
159 | ## Acknowledgements
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 | [contributors-shield]: https://img.shields.io/github/contributors/MTRNord/Daydream.svg?style=flat-square
168 | [contributors-url]: https://github.com/MTRNord/Daydream/graphs/contributors
169 | [forks-shield]: https://img.shields.io/github/forks/MTRNord/Daydream.svg?style=flat-square
170 | [forks-url]: https://github.com/MTRNord/Daydream/network/members
171 | [stars-shield]: https://img.shields.io/github/stars/MTRNord/Daydream.svg?style=flat-square
172 | [stars-url]: https://github.com/MTRNord/Daydream/stargazers
173 | [issues-shield]: https://img.shields.io/github/issues/MTRNord/Daydream.svg?style=flat-square
174 | [issues-url]: https://github.com/MTRNord/Daydream/issues
175 | [license-shield]: https://img.shields.io/github/license/MTRNord/Daydream.svg?style=flat-square
176 | [license-url]: https://github.com/MTRNord/Daydream/blob/master/LICENSE
177 | [product-screenshot]: images/screenshot.png
178 |
--------------------------------------------------------------------------------
/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseUrl": "http://localhost:8000",
3 | "projectId": "7z38mu",
4 | "testFiles": "**/*_spec.js"
5 | }
6 |
--------------------------------------------------------------------------------
/cypress/fixtures/login.json:
--------------------------------------------------------------------------------
1 | {
2 | "user_id": "@carl:example.com",
3 | "access_token": "123456",
4 | "device_id": "KCZFUCGSLZ"
5 | }
6 |
--------------------------------------------------------------------------------
/cypress/integration/login_spec.js:
--------------------------------------------------------------------------------
1 | import {fake_matrix_api_handler} from './matrix-fake-api';
2 | import {login} from "./utils";
3 | describe('LoginPage', () => {
4 |
5 | beforeEach(function () {
6 | // We use cy.visit({onBeforeLoad: ...}) to stub
7 | // window.fetch before any app code runs
8 | cy.visit('/', {
9 | onBeforeLoad(win) {
10 | let fetch = win.fetch;
11 | cy.stub(win, 'fetch')
12 | .callsFake(args => fake_matrix_api_handler(args, fetch, win));
13 | },
14 | })
15 | })
16 |
17 | it('Does login', () => {
18 | login()
19 | // First show spinner
20 | cy.get('svg#loading').should('be.visible');
21 | // Now it should be gone and instead we see the roomlist
22 | cy.get('svg#loading').should('not.be.visible');
23 | cy.get('ul.scrollable').should('be.visible');
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/cypress/integration/matrix-fake-api.js:
--------------------------------------------------------------------------------
1 | const fake_matrix_api_handler = (arg, fetch, win) => {
2 | if (typeof arg === "string") {
3 | console.log("REQUESTED: " + arg);
4 | if (arg.includes('daydream_bg.wasm') || arg.includes('worker_bg.wasm')) {
5 | console.log("doing fetch");
6 | return fetch(arg);
7 | }
8 | } else if (typeof arg === "object") {
9 | console.log("REQUESTED: ", arg);
10 | console.log("Special mode");
11 | if (arg["url"] === "http://localhost:8448/_matrix/client/r0/login") {
12 | console.log("handling login");
13 | return new Promise((resolve, reject) => {
14 | const resp_data = {
15 | "user_id": "@carl:example.com",
16 | "access_token": "123456",
17 | "device_id": "KCZFUCGSLZ"
18 | };
19 | const resp = new Blob([JSON.stringify(resp_data, null, 2)], {type: 'application/json'});
20 |
21 | const init = {
22 | "status": 200,
23 | "statusText": "Ok",
24 | headers: {'Content-type': 'application/json'}
25 | };
26 | const response = new win.Response(resp, init);
27 | Object.defineProperty(response, "url", {value: arg["url"]});
28 | resolve(response)
29 | }
30 | )
31 | } else {
32 | // TODO handle different test states
33 | console.log("handling sync");
34 | return new Promise((resolve, reject) => {
35 | const resp_data = {
36 | "next_batch": "s72595_4483_1934",
37 | "rooms": {
38 | "join": {
39 | "!726s6s6q:example.com": {
40 | "unread_notifications": {
41 | "highlight_count": 0,
42 | "notification_count": 0,
43 | },
44 | "summary": {
45 | "m.heroes": [
46 | "@alice:example.com",
47 | "@bob:example.com"
48 | ],
49 | "m.joined_member_count": 2,
50 | "m.invited_member_count": 0
51 | },
52 | "state": {
53 | "events": [
54 | {
55 | "content": {
56 | "membership": "join",
57 | "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
58 | "displayname": "Alice Margatroid"
59 | },
60 | "type": "m.room.member",
61 | "event_id": "$143273582443PhrSn:example.org",
62 | "room_id": "!726s6s6q:example.com",
63 | "sender": "@example:example.org",
64 | "origin_server_ts": 1432735824653,
65 | "unsigned": {
66 | "age": 1234
67 | },
68 | "state_key": "@alice:example.org"
69 | }
70 | ]
71 | },
72 | "timeline": {
73 | "events": [
74 | {
75 | "content": {
76 | "membership": "join",
77 | "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
78 | "displayname": "Alice Margatroid"
79 | },
80 | "type": "m.room.member",
81 | "event_id": "$143273582443PhrSn:example.org",
82 | "room_id": "!726s6s6q:example.com",
83 | "sender": "@example:example.org",
84 | "origin_server_ts": 1432735824653,
85 | "unsigned": {
86 | "age": 1234
87 | },
88 | "state_key": "@alice:example.org"
89 | },
90 | {
91 | "content": {
92 | "body": "This is an example text message",
93 | "msgtype": "m.text",
94 | "format": "org.matrix.custom.html",
95 | "formatted_body": "This is an example text message"
96 | },
97 | "type": "m.room.message",
98 | "event_id": "$143273582443PhrSn:example.org",
99 | "room_id": "!726s6s6q:example.com",
100 | "sender": "@example:example.org",
101 | "origin_server_ts": 1432735824653,
102 | "unsigned": {
103 | "age": 1234
104 | }
105 | }
106 | ],
107 | "limited": true,
108 | "prev_batch": "t34-23535_0_0"
109 | },
110 | "ephemeral": {
111 | "events": [
112 | {
113 | "content": {
114 | "user_ids": [
115 | "@alice:matrix.org",
116 | "@bob:example.com"
117 | ]
118 | },
119 | "type": "m.typing",
120 | "room_id": "!jEsUZKDJdhlrceRyVU:example.org"
121 | }
122 | ]
123 | },
124 | "account_data": {
125 | "events": [
126 | {
127 | "content": {
128 | "tags": {
129 | "u.work": {
130 | "order": 0.9
131 | }
132 | }
133 | },
134 | "type": "m.tag"
135 | },
136 | {
137 | "type": "org.example.custom.room.config",
138 | "content": {
139 | "custom_config_key": "custom_config_value"
140 | }
141 | }
142 | ]
143 | }
144 | }
145 | },
146 | "invite": {
147 | "!696r7674:example.com": {
148 | "invite_state": {
149 | "events": [
150 | {
151 | "sender": "@alice:example.com",
152 | "type": "m.room.name",
153 | "state_key": "",
154 | "content": {
155 | "name": "My Room Name"
156 | }
157 | },
158 | {
159 | "sender": "@alice:example.com",
160 | "type": "m.room.member",
161 | "state_key": "@bob:example.com",
162 | "content": {
163 | "membership": "invite"
164 | }
165 | }
166 | ]
167 | }
168 | }
169 | },
170 | "leave": {}
171 | }
172 | };
173 | const resp = new Blob([JSON.stringify(resp_data, null, 2)], {type: 'application/json'});
174 |
175 | const init = {
176 | "status": 200,
177 | "statusText": "Ok",
178 | headers: {'Content-type': 'application/json'}
179 | };
180 | const response = new win.Response(resp, init);
181 | Object.defineProperty(response, "url", {value: arg["url"]});
182 | // Lets wait 2 sec to get a chance to see a spinner
183 | setTimeout(() => {
184 | resolve(response)
185 | }, 2000);
186 | }
187 | )
188 | }
189 | }
190 |
191 | }
192 | export {fake_matrix_api_handler}
193 | export default fake_matrix_api_handler
194 |
--------------------------------------------------------------------------------
/cypress/integration/roomlist_spec.js:
--------------------------------------------------------------------------------
1 | import {fake_matrix_api_handler} from "./matrix-fake-api";
2 | import {login} from "./utils";
3 |
4 | describe('RoomList', () => {
5 | const name_of_user = "Alice Margatroid";
6 | beforeEach(function () {
7 | // We use cy.visit({onBeforeLoad: ...}) to stub
8 | // window.fetch before any app code runs
9 | cy.visit('/', {
10 | onBeforeLoad(win) {
11 | let fetch = win.fetch;
12 | cy.stub(win, 'fetch')
13 | .callsFake(args => fake_matrix_api_handler(args, fetch, win));
14 | },
15 | })
16 | });
17 |
18 | it("does allow clicking a room", () => {
19 | login();
20 | cy.get('ul.scrollable').contains(name_of_user).click();
21 |
22 | cy.log('check if the room title is shown after click');
23 | cy.get('h1').contains(name_of_user).should('be.visible');
24 | });
25 |
26 | it("should allow searching", () => {
27 | login();
28 |
29 | cy.log('check if the room is not shown if anything else is in the search');
30 | cy.get('input.uk-search-input').clear().type("blub");
31 | cy.get("ul.scrollable").contains(name_of_user).should("not.exist");
32 |
33 | cy.log('check if the room is shown if a part of the name is in the search');
34 | cy.get('input.uk-search-input').clear().type("Alice");
35 | cy.get("ul.scrollable").contains(name_of_user).should("exist").should("be.visible");
36 |
37 | cy.log('check if the room is shown if the full name is in the search');
38 | cy.get('input.uk-search-input').clear().type(name_of_user);
39 | cy.get("ul.scrollable").contains(name_of_user).should("exist").should("be.visible");
40 | });
41 | })
42 |
--------------------------------------------------------------------------------
/cypress/integration/utils.js:
--------------------------------------------------------------------------------
1 | const login = () => {
2 | cy.get('form#login_form').within(() => {
3 | // Set homeserverurl
4 | const homeserver_url = "http://localhost:8448";
5 | cy.get('input#homeserver').type(homeserver_url);
6 | cy.get('input#homeserver').should('have.value', homeserver_url);
7 |
8 | // Set username
9 | const username = "@carl:example.com";
10 | cy.get('input#username').type(username);
11 | cy.get('input#username').should('have.value', username);
12 |
13 | // Set password
14 | const password = "12345";
15 | cy.get('input#password').type(username);
16 | cy.get('input#password').should('have.value', username);
17 | });
18 | cy.get('form#login_form').submit();
19 | }
20 |
21 | export {login}
22 |
--------------------------------------------------------------------------------
/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | ///
2 | // ***********************************************************
3 | // This example plugins/index.js can be used to load plugins
4 | //
5 | // You can change the location of this file or turn off loading
6 | // the plugins file with the 'pluginsFile' configuration option.
7 | //
8 | // You can read more here:
9 | // https://on.cypress.io/plugins-guide
10 | // ***********************************************************
11 |
12 | // This function is called when a project is opened or re-opened (e.g. due to
13 | // the project's config changing)
14 |
15 | /**
16 | * @type {Cypress.PluginConfig}
17 | */
18 | module.exports = (on, config) => {
19 | // `on` is used to hook into various events Cypress emits
20 | // `config` is the resolved Cypress config
21 | }
22 |
--------------------------------------------------------------------------------
/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/i18n.toml:
--------------------------------------------------------------------------------
1 | # (Required) The locale/language identifier of the language used in the source
2 | # code.
3 | src_locale = "en-US"
4 |
5 | # (Required) The locales that the software will be translated into.
6 | target_locales = ["de"]
7 |
8 | [gettext]
9 | # (Required) Path to the output directory, relative to `i18n.toml` of the crate
10 | # being localized.
11 | output_dir = "i18n"
12 |
--------------------------------------------------------------------------------
/i18n/mo/de/daydream.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daydream-mx/Daydream/85c2cf395906e944174eb8e7acac1afe6426fefa/i18n/mo/de/daydream.mo
--------------------------------------------------------------------------------
/i18n/po/de/daydream.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: Daydream\n"
4 | "Report-Msgid-Bugs-To: \n"
5 | "POT-Creation-Date: 2020-08-22 14:39+0000\n"
6 | "Language: de\n"
7 | "MIME-Version: 1.0\n"
8 | "Content-Type: text/plain; charset=UTF-8\n"
9 | "Content-Transfer-Encoding: 8bit\n"
10 | "X-Generator: POEditor.com\n"
11 |
12 | #. Placeholder text for the roomlist filtering
13 | #: src/app/components/room_list/mod.rs:180
14 | msgid "Filter Rooms..."
15 | msgstr "Räume filter..."
16 |
17 | #. {0} is the Error that happened on login
18 | #. The error message of the Login page
19 | #: src/app/views/login.rs:157
20 | msgid "Error: {0}"
21 | msgstr "Fehler: {0}"
22 |
23 | #. The Login Button of the Login page
24 | #: src/app/views/login.rs:178 src/app/views/login.rs:248
25 | msgid "Login"
26 | msgstr "Anmelden"
27 |
28 | #. The URL Field of the Login page
29 | #: src/app/views/login.rs:198
30 | msgid "Homeserver URL"
31 | msgstr "Heimserver Adresse"
32 |
33 | #. The Matrix ID Field of the Login page
34 | #: src/app/views/login.rs:217
35 | msgid "MXID"
36 | msgstr "MXID"
37 |
38 | #. The Password Field of the Login page
39 | #: src/app/views/login.rs:236
40 | msgid "Password"
41 | msgstr "Passwort"
42 |
43 | #. A warning for encrypted rooms
44 | #: src/app/views/main_view.rs:81
45 | msgid "Daydream currently does not support encryption."
46 | msgstr "Daydream unterstützt aktuell keine Verschlüsselung"
47 |
48 | #~ msgid "Rooms"
49 | #~ msgstr "Räume"
50 |
--------------------------------------------------------------------------------
/i18n/pot/daydream.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
20 | #. Placeholder text for the roomlist filtering
21 | #: src/app/components/room_list/mod.rs:180
22 | msgid "Filter Rooms..."
23 | msgstr ""
24 |
25 | #. {0} is the Error that happened on login
26 | #. The error message of the Login page
27 | #: src/app/views/login.rs:157
28 | msgid "Error: {0}"
29 | msgstr ""
30 |
31 | #. The Login Button of the Login page
32 | #: src/app/views/login.rs:178 src/app/views/login.rs:248
33 | msgid "Login"
34 | msgstr ""
35 |
36 | #. The URL Field of the Login page
37 | #: src/app/views/login.rs:198
38 | msgid "Homeserver URL"
39 | msgstr ""
40 |
41 | #. The Matrix ID Field of the Login page
42 | #: src/app/views/login.rs:217
43 | msgid "MXID"
44 | msgstr ""
45 |
46 | #. The Password Field of the Login page
47 | #: src/app/views/login.rs:236
48 | msgid "Password"
49 | msgstr ""
50 |
51 | #. A warning for encrypted rooms
52 | #: src/app/views/main_view.rs:81
53 | msgid "Daydream currently does not support encryption."
54 | msgstr ""
55 |
--------------------------------------------------------------------------------
/i18n/pot/src/app/components/event_list.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
--------------------------------------------------------------------------------
/i18n/pot/src/app/components/events/image.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
--------------------------------------------------------------------------------
/i18n/pot/src/app/components/events/mod.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
--------------------------------------------------------------------------------
/i18n/pot/src/app/components/events/notice.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
--------------------------------------------------------------------------------
/i18n/pot/src/app/components/events/text.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
--------------------------------------------------------------------------------
/i18n/pot/src/app/components/events/video.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
--------------------------------------------------------------------------------
/i18n/pot/src/app/components/input.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
--------------------------------------------------------------------------------
/i18n/pot/src/app/components/mod.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
20 | #. Placeholder text for the roomlist filtering
21 | #: ./src/app/components/room_list/mod.rs:180
22 | msgid "Filter Rooms..."
23 | msgstr ""
24 |
--------------------------------------------------------------------------------
/i18n/pot/src/app/components/raw_html.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
--------------------------------------------------------------------------------
/i18n/pot/src/app/components/room_list.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-06-07 12:39+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
20 | #. Placeholder text for the roomlist filtering
21 | #: ./src/app/components/room_list.rs:161
22 | msgid "Filter Rooms..."
23 | msgstr ""
24 |
25 | #. Header of the Roomlist
26 | #: ./src/app/components/room_list.rs:173
27 | msgid "Rooms"
28 | msgstr ""
29 |
--------------------------------------------------------------------------------
/i18n/pot/src/app/components/room_list/item.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
--------------------------------------------------------------------------------
/i18n/pot/src/app/components/room_list/mod.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
20 | #. Placeholder text for the roomlist filtering
21 | #: ./src/app/components/room_list/mod.rs:180
22 | msgid "Filter Rooms..."
23 | msgstr ""
24 |
--------------------------------------------------------------------------------
/i18n/pot/src/app/matrix/login.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
--------------------------------------------------------------------------------
/i18n/pot/src/app/matrix/mod.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
--------------------------------------------------------------------------------
/i18n/pot/src/app/matrix/sync.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
--------------------------------------------------------------------------------
/i18n/pot/src/app/matrix/types.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
--------------------------------------------------------------------------------
/i18n/pot/src/app/mod.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
20 | #. Placeholder text for the roomlist filtering
21 | #: ./src/app/components/room_list/mod.rs:180
22 | msgid "Filter Rooms..."
23 | msgstr ""
24 |
25 | #. {0} is the Error that happened on login
26 | #. The error message of the Login page
27 | #: ./src/app/views/login.rs:157
28 | msgid "Error: {0}"
29 | msgstr ""
30 |
31 | #. The Login Button of the Login page
32 | #: ./src/app/views/login.rs:178 ./src/app/views/login.rs:248
33 | msgid "Login"
34 | msgstr ""
35 |
36 | #. The URL Field of the Login page
37 | #: ./src/app/views/login.rs:198
38 | msgid "Homeserver URL"
39 | msgstr ""
40 |
41 | #. The Matrix ID Field of the Login page
42 | #: ./src/app/views/login.rs:217
43 | msgid "MXID"
44 | msgstr ""
45 |
46 | #. The Password Field of the Login page
47 | #: ./src/app/views/login.rs:236
48 | msgid "Password"
49 | msgstr ""
50 |
51 | #. A warning for encrypted rooms
52 | #: ./src/app/views/main_view.rs:81
53 | msgid "Daydream currently does not support encryption."
54 | msgstr ""
55 |
--------------------------------------------------------------------------------
/i18n/pot/src/app/views/login.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
20 | #. {0} is the Error that happened on login
21 | #. The error message of the Login page
22 | #: ./src/app/views/login.rs:157
23 | msgid "Error: {0}"
24 | msgstr ""
25 |
26 | #. The Login Button of the Login page
27 | #: ./src/app/views/login.rs:178 ./src/app/views/login.rs:248
28 | msgid "Login"
29 | msgstr ""
30 |
31 | #. The URL Field of the Login page
32 | #: ./src/app/views/login.rs:198
33 | msgid "Homeserver URL"
34 | msgstr ""
35 |
36 | #. The Matrix ID Field of the Login page
37 | #: ./src/app/views/login.rs:217
38 | msgid "MXID"
39 | msgstr ""
40 |
41 | #. The Password Field of the Login page
42 | #: ./src/app/views/login.rs:236
43 | msgid "Password"
44 | msgstr ""
45 |
--------------------------------------------------------------------------------
/i18n/pot/src/app/views/main_view.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
20 | #. A warning for encrypted rooms
21 | #: ./src/app/views/main_view.rs:81
22 | msgid "Daydream currently does not support encryption."
23 | msgstr ""
24 |
--------------------------------------------------------------------------------
/i18n/pot/src/app/views/mod.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
20 | #. {0} is the Error that happened on login
21 | #. The error message of the Login page
22 | #: ./src/app/views/login.rs:157
23 | msgid "Error: {0}"
24 | msgstr ""
25 |
26 | #. The Login Button of the Login page
27 | #: ./src/app/views/login.rs:178 ./src/app/views/login.rs:248
28 | msgid "Login"
29 | msgstr ""
30 |
31 | #. The URL Field of the Login page
32 | #: ./src/app/views/login.rs:198
33 | msgid "Homeserver URL"
34 | msgstr ""
35 |
36 | #. The Matrix ID Field of the Login page
37 | #: ./src/app/views/login.rs:217
38 | msgid "MXID"
39 | msgstr ""
40 |
41 | #. The Password Field of the Login page
42 | #: ./src/app/views/login.rs:236
43 | msgid "Password"
44 | msgstr ""
45 |
46 | #. A warning for encrypted rooms
47 | #: ./src/app/views/main_view.rs:81
48 | msgid "Daydream currently does not support encryption."
49 | msgstr ""
50 |
--------------------------------------------------------------------------------
/i18n/pot/src/bin/app.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
--------------------------------------------------------------------------------
/i18n/pot/src/bin/native_worker.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
--------------------------------------------------------------------------------
/i18n/pot/src/constants.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
--------------------------------------------------------------------------------
/i18n/pot/src/errors.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
--------------------------------------------------------------------------------
/i18n/pot/src/lib.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
20 | #. Placeholder text for the roomlist filtering
21 | #: ./src/app/components/room_list/mod.rs:180
22 | msgid "Filter Rooms..."
23 | msgstr ""
24 |
25 | #. {0} is the Error that happened on login
26 | #. The error message of the Login page
27 | #: ./src/app/views/login.rs:157
28 | msgid "Error: {0}"
29 | msgstr ""
30 |
31 | #. The Login Button of the Login page
32 | #: ./src/app/views/login.rs:178 ./src/app/views/login.rs:248
33 | msgid "Login"
34 | msgstr ""
35 |
36 | #. The URL Field of the Login page
37 | #: ./src/app/views/login.rs:198
38 | msgid "Homeserver URL"
39 | msgstr ""
40 |
41 | #. The Matrix ID Field of the Login page
42 | #: ./src/app/views/login.rs:217
43 | msgid "MXID"
44 | msgstr ""
45 |
46 | #. The Password Field of the Login page
47 | #: ./src/app/views/login.rs:236
48 | msgid "Password"
49 | msgstr ""
50 |
51 | #. A warning for encrypted rooms
52 | #: ./src/app/views/main_view.rs:81
53 | msgid "Daydream currently does not support encryption."
54 | msgstr ""
55 |
--------------------------------------------------------------------------------
/i18n/pot/src/utils/mod.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
--------------------------------------------------------------------------------
/i18n/pot/src/utils/notifications.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
--------------------------------------------------------------------------------
/i18n/pot/src/utils/ruma.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
--------------------------------------------------------------------------------
/i18n/pot/src/utils/string_utils.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the daydream package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: daydream 0.1.0\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-08-22 15:44+0000\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
--------------------------------------------------------------------------------
/media/DaydreamLogo.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daydream-mx/Daydream/85c2cf395906e944174eb8e7acac1afe6426fefa/media/DaydreamLogo.ai
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | # Settings in the [build] context are global and are applied to all contexts
2 | # unless otherwise overridden by more specific contexts.
3 | [build]
4 | # Directory that contains the deploy-ready HTML files and assets generated by
5 | # the build. This is relative to the base directory if one has been set, or the
6 | # root directory if a base has not been set. This sample publishes the
7 | # directory located at the absolute path "root/project/build-output"
8 | publish = "dist/"
9 |
10 | # Default build command.
11 | command = "(curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y) && source $HOME/.cargo/env && rustup target add wasm32-unknown-unknown && cargo install cargo-make && cargo make dist"
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "daydream",
3 | "private": true,
4 | "scripts": {
5 | "test": "start-server-and-test 'cargo install cargo-make && cargo make run' http-get://localhost:8000 cy:run:ci",
6 | "cy:open": "cypress open",
7 | "cy:run:ci": "cypress run --browser chrome --record --key 99194b81-775d-4e98-9bac-8f2cc12bc62e --ci-build-id \"${GITHUB_SHA}-${GITHUB_WORKFLOW}-${GITHUB_EVENT_NAME}\" --group github-action-e2e --parallel",
8 | "deploy": "push-dir --dir=dist --branch=gh-pages --cleanup --verbose"
9 | },
10 | "devDependencies": {
11 | "cross-env": "^7.0.2",
12 | "cypress": "^4.9.0",
13 | "push-dir": "^0.4.1",
14 | "start-server-and-test": "^1.11.0"
15 | },
16 | "dependencies": {
17 | "uikit": "^3.5.4"
18 | }
19 | }
--------------------------------------------------------------------------------
/src/app/components/event_list.rs:
--------------------------------------------------------------------------------
1 | use std::{collections::HashMap, rc::Rc};
2 |
3 | use crate::utils::ruma::AnyMessageEventExt;
4 | use log::*;
5 | use matrix_sdk::{
6 | events::{room::message::MessageEventContent, AnyMessageEventContent, AnySyncMessageEvent},
7 | identifiers::RoomId,
8 | Room,
9 | };
10 | use yew::{prelude::*, virtual_dom::VList};
11 |
12 | use crate::app::components::{
13 | events::{image::Image, notice::Notice, text::Text, video::Video},
14 | input::Input,
15 | };
16 | use crate::app::matrix::{MatrixAgent, Request, Response};
17 |
18 | pub struct EventList {
19 | on_submit: Callback,
20 | state: State,
21 | matrix_agent: Box>,
22 | props: Props,
23 | }
24 |
25 | #[derive(Default)]
26 | pub struct State {
27 | // TODO handle all events
28 | pub events: HashMap>,
29 | }
30 |
31 | #[allow(clippy::large_enum_variant)]
32 | pub enum Msg {
33 | NewMessage(Response),
34 | SendMessage(String),
35 | Nope,
36 | }
37 |
38 | #[derive(Clone, PartialEq, Properties, Debug)]
39 | pub struct Props {
40 | pub current_room: Rc,
41 | }
42 |
43 | impl Component for EventList {
44 | type Message = Msg;
45 | type Properties = Props;
46 |
47 | fn create(props: Self::Properties, link: ComponentLink) -> Self {
48 | let matrix_callback = link.callback(Msg::NewMessage);
49 | let mut matrix_agent = MatrixAgent::bridge(matrix_callback);
50 |
51 | let state = State {
52 | events: Default::default(),
53 | };
54 |
55 | let room_id = props.current_room.room_id.clone();
56 | if !state.events.contains_key(&room_id) {
57 | matrix_agent.send(Request::GetOldMessages((room_id, None)));
58 | }
59 |
60 | EventList {
61 | on_submit: link.callback(Msg::SendMessage),
62 | props,
63 | matrix_agent,
64 | state,
65 | }
66 | }
67 |
68 | fn update(&mut self, msg: Self::Message) -> bool {
69 | match msg {
70 | Msg::NewMessage(Response::Sync((room_id, raw_msg))) => {
71 | // TODO handle all events
72 | if let Ok(msg) = raw_msg.deserialize() {
73 | if self.state.events.contains_key(&room_id) {
74 | if !(self.state.events[&room_id]
75 | .iter()
76 | .any(|x| x.event_id() == msg.event_id()))
77 | {
78 | self.state.events.get_mut(&room_id).unwrap().push(msg);
79 | room_id == self.props.current_room.room_id
80 | } else {
81 | false
82 | }
83 | } else {
84 | let msgs = vec![msg];
85 | self.state.events.insert(room_id.clone(), msgs);
86 | room_id == self.props.current_room.room_id
87 | }
88 | } else {
89 | false
90 | }
91 | }
92 | Msg::NewMessage(Response::OldMessages((room_id, messages))) => {
93 | let mut deserialized_messages: Vec = messages
94 | .iter()
95 | .map(|x| x.deserialize())
96 | .filter_map(Result::ok)
97 | .map(|x| x.without_room_id())
98 | .collect();
99 | // This is a clippy false positive
100 | #[allow(clippy::map_entry)]
101 | if self.state.events.contains_key(&room_id) {
102 | self.state
103 | .events
104 | .get_mut(&room_id)
105 | .unwrap()
106 | .append(deserialized_messages.as_mut());
107 | true
108 | } else {
109 | self.state.events.insert(room_id, deserialized_messages);
110 | true
111 | }
112 | }
113 | Msg::NewMessage(_) => false,
114 | Msg::SendMessage(message) => {
115 | info!("Sending Message");
116 | self.matrix_agent.send(Request::SendMessage((
117 | self.props.current_room.room_id.clone(),
118 | message,
119 | )));
120 | false
121 | }
122 | Msg::Nope => false,
123 | }
124 | }
125 |
126 | fn change(&mut self, props: Self::Properties) -> bool {
127 | if self.props != props {
128 | let room_id = props.current_room.room_id.clone();
129 | if !self.state.events.contains_key(&room_id) {
130 | self.matrix_agent
131 | .send(Request::GetOldMessages((room_id, None)));
132 | }
133 |
134 | self.props = props;
135 | true
136 | } else {
137 | false
138 | }
139 | }
140 |
141 | fn view(&self) -> Html {
142 | let events = if self
143 | .state
144 | .events
145 | .contains_key(&self.props.current_room.room_id)
146 | {
147 | let events = &self.state.events[&self.props.current_room.room_id];
148 |
149 | let mut html_nodes = VList::new();
150 | if let Some(event) = events.first() {
151 | html_nodes.add_child(self.get_event(None, event));
152 | }
153 | html_nodes.add_children(
154 | events
155 | .windows(2)
156 | .map(|e| self.get_event(Some(&e[0]), &e[1])),
157 | );
158 |
159 | html_nodes.into()
160 | } else {
161 | html! {}
162 | };
163 |
164 | html! {
165 |
166 |
{ self.props.current_room.display_name() }
167 |
173 |
174 |
175 | }
176 | }
177 | }
178 |
179 | impl EventList {
180 | // Typeinspection of IDEA breaks with this :D
181 | //noinspection RsTypeCheck
182 | fn get_event(
183 | &self,
184 | prev_event: Option<&AnySyncMessageEvent>,
185 | event: &AnySyncMessageEvent,
186 | ) -> Html {
187 | // TODO make encryption supported
188 |
189 | match &event.content() {
190 | AnyMessageEventContent::RoomMessage(room_message) => match room_message {
191 | MessageEventContent::Text(text_event) => {
192 | html! {
193 |
199 | }
200 | }
201 | MessageEventContent::Notice(notice_event) => {
202 | html! {
203 |
209 | }
210 | }
211 | MessageEventContent::Image(image_event) => {
212 | html! {
213 |
219 | }
220 | }
221 | MessageEventContent::Video(video_event) => {
222 | html! {
223 |
229 | }
230 | }
231 | _ => html! {},
232 | },
233 | _ => html! {},
234 | }
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/src/app/components/events/image.rs:
--------------------------------------------------------------------------------
1 | use std::rc::Rc;
2 |
3 | use crate::app::components::events::{EventExt, RoomExt};
4 | use matrix_sdk::{
5 | events::{room::message::ImageMessageEventContent, AnySyncMessageEvent},
6 | Room,
7 | };
8 | use rand::random;
9 | use yew::prelude::*;
10 |
11 | pub(crate) struct Image {
12 | props: Props,
13 | }
14 |
15 | #[derive(Clone, Properties, Debug)]
16 | pub struct Props {
17 | #[prop_or_default]
18 | pub prev_event: Option,
19 | pub event: AnySyncMessageEvent,
20 | pub image_event: ImageMessageEventContent,
21 | pub room: Rc,
22 | }
23 |
24 | impl Component for Image {
25 | type Message = ();
26 | type Properties = Props;
27 |
28 | fn create(props: Self::Properties, _link: ComponentLink) -> Self {
29 | Image { props }
30 | }
31 |
32 | fn update(&mut self, _msg: Self::Message) -> bool {
33 | false
34 | }
35 |
36 | fn change(&mut self, _props: Self::Properties) -> bool {
37 | // TODO fix the PartialEq hack
38 | /*if format!("{:?}", self.props) != format!("{:?}", props) {
39 | self.props = props;
40 | true
41 | } else {
42 | false
43 | }*/
44 | true
45 | }
46 |
47 | //noinspection RsTypeCheck
48 | fn view(&self) -> Html {
49 | let new_user = self.props.event.is_new_user(self.props.prev_event.as_ref());
50 | let sender_displayname = if new_user {
51 | self.props.room.get_sender_displayname(&self.props.event)
52 | } else {
53 | ""
54 | };
55 |
56 | if let Some(image_url) = &self.props.image_event.url {
57 | let thumbnail = self
58 | .props
59 | .image_event
60 | .info
61 | .as_ref()
62 | .unwrap()
63 | .thumbnail_url
64 | .as_ref()
65 | .unwrap_or(image_url);
66 |
67 | let lightbox_id: u8 = random();
68 | let lightbox_id_full = format!("image_{}", lightbox_id);
69 | let lightbox_href_full = format!("#image_{}", lightbox_id);
70 | if new_user {
71 | html! {
72 |
73 |
{sender_displayname}{": "}
74 |
75 |
76 |

77 |
78 |
81 |
82 | }
83 | } else {
84 | html! {
85 |
86 |

87 |
88 |

89 |
90 |
93 |
94 | }
95 | }
96 | } else {
97 | html! {}
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/app/components/events/mod.rs:
--------------------------------------------------------------------------------
1 | use matrix_sdk::events::AnySyncMessageEvent;
2 | use matrix_sdk::Room;
3 | use url::Url;
4 |
5 | use crate::app::matrix::types::get_media_download_url;
6 |
7 | pub mod image;
8 | pub mod notice;
9 | pub mod text;
10 | pub mod video;
11 |
12 | pub trait EventExt {
13 | fn is_new_user(&self, prev_event: Option<&AnySyncMessageEvent>) -> bool;
14 | }
15 |
16 | impl EventExt for AnySyncMessageEvent {
17 | fn is_new_user(&self, prev_event: Option<&AnySyncMessageEvent>) -> bool {
18 | if let Some(prev_event) = prev_event {
19 | prev_event.sender() != self.sender()
20 | } else {
21 | true
22 | }
23 | }
24 | }
25 |
26 | pub trait RoomExt {
27 | fn get_sender_displayname<'a>(&'a self, event: &'a AnySyncMessageEvent) -> &'a str;
28 | fn get_sender_avatar<'a>(
29 | &'a self,
30 | homeserver_url: &'a Url,
31 | event: &'a AnySyncMessageEvent,
32 | ) -> Option;
33 | }
34 |
35 | impl RoomExt for Room {
36 | fn get_sender_displayname<'a>(&'a self, event: &'a AnySyncMessageEvent) -> &'a str {
37 | self.joined_members
38 | .get(&event.sender())
39 | .or_else(|| self.invited_members.get(&event.sender()))
40 | .and_then(|member| member.display_name.as_deref())
41 | .unwrap_or_else(|| event.sender().as_str())
42 | }
43 |
44 | fn get_sender_avatar<'a>(
45 | &self,
46 | homeserver_url: &'a Url,
47 | event: &'a AnySyncMessageEvent,
48 | ) -> Option {
49 | let member = self
50 | .joined_members
51 | .get(&event.sender())
52 | .or_else(|| self.invited_members.get(&event.sender()))?;
53 |
54 | Some(get_media_download_url(
55 | homeserver_url,
56 | member.avatar_url.as_deref()?,
57 | ))
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/app/components/events/notice.rs:
--------------------------------------------------------------------------------
1 | use std::rc::Rc;
2 |
3 | use linkify::LinkFinder;
4 | use matrix_sdk::{
5 | events::{room::message::NoticeMessageEventContent, AnySyncMessageEvent},
6 | Room,
7 | };
8 | use web_sys::Node;
9 | use yew::prelude::*;
10 | use yew::virtual_dom::VNode;
11 |
12 | use crate::app::components::events::{EventExt, RoomExt};
13 |
14 | pub(crate) struct Notice {
15 | props: Props,
16 | }
17 |
18 | #[derive(Clone, Properties, Debug)]
19 | pub struct Props {
20 | #[prop_or_default]
21 | pub prev_event: Option,
22 | pub event: AnySyncMessageEvent,
23 | pub notice_event: NoticeMessageEventContent,
24 | pub room: Rc,
25 | }
26 |
27 | impl Component for Notice {
28 | type Message = ();
29 | type Properties = Props;
30 |
31 | fn create(props: Self::Properties, _link: ComponentLink) -> Self {
32 | Notice { props }
33 | }
34 |
35 | fn update(&mut self, _msg: Self::Message) -> bool {
36 | false
37 | }
38 |
39 | fn change(&mut self, _props: Self::Properties) -> bool {
40 | // TODO fix the PartialEq hack
41 | /*if format!("{:?}", self.props) != format!("{:?}", props) {
42 | self.props = props;
43 | true
44 | } else {
45 | false
46 | }*/
47 | true
48 | }
49 |
50 | //noinspection RsTypeCheck
51 | fn view(&self) -> Html {
52 | let new_user = self.props.event.is_new_user(self.props.prev_event.as_ref());
53 | let sender_displayname = if new_user {
54 | self.props.room.get_sender_displayname(&self.props.event)
55 | } else {
56 | ""
57 | };
58 |
59 | let mut pure_content = self.props.notice_event.body.clone();
60 | let finder = LinkFinder::new();
61 | let pure_content_clone = pure_content.clone();
62 | let links: Vec<_> = finder.links(&pure_content_clone).collect();
63 |
64 | if !links.is_empty() {
65 | for link in links {
66 | let html_link = format!("{}", link.as_str(), link.as_str());
67 | pure_content.replace_range(link.start()..link.end(), &html_link);
68 | }
69 | }
70 |
71 | if new_user {
72 | let full_html = format!(
73 | "{}: {}
",
74 | sender_displayname, pure_content
75 | );
76 | let js_text_event = {
77 | let div = web_sys::window()
78 | .unwrap()
79 | .document()
80 | .unwrap()
81 | .create_element("p")
82 | .unwrap();
83 | div.set_inner_html(full_html.as_str());
84 | div
85 | };
86 | let node = Node::from(js_text_event);
87 | VNode::VRef(node)
88 | } else {
89 | let full_html = format!("{}
", pure_content);
90 | let js_text_event = {
91 | let div = web_sys::window()
92 | .unwrap()
93 | .document()
94 | .unwrap()
95 | .create_element("p")
96 | .unwrap();
97 | div.set_inner_html(full_html.as_str());
98 | div
99 | };
100 | let node = Node::from(js_text_event);
101 | VNode::VRef(node)
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/app/components/events/text.rs:
--------------------------------------------------------------------------------
1 | use std::rc::Rc;
2 |
3 | use crate::app::components::events::{EventExt, RoomExt};
4 | use linkify::LinkFinder;
5 | use matrix_sdk::{
6 | events::{room::message::TextMessageEventContent, AnySyncMessageEvent},
7 | Room,
8 | };
9 | use web_sys::Node;
10 | use yew::prelude::*;
11 | use yew::virtual_dom::VNode;
12 |
13 | pub struct Text {
14 | props: Props,
15 | }
16 |
17 | #[derive(Clone, Properties, Debug)]
18 | pub struct Props {
19 | #[prop_or_default]
20 | pub prev_event: Option,
21 | pub event: AnySyncMessageEvent,
22 | pub text_event: TextMessageEventContent,
23 | pub room: Rc,
24 | }
25 |
26 | impl Component for Text {
27 | type Message = ();
28 | type Properties = Props;
29 |
30 | fn create(props: Self::Properties, _link: ComponentLink) -> Self {
31 | Text { props }
32 | }
33 |
34 | fn update(&mut self, _msg: Self::Message) -> bool {
35 | false
36 | }
37 |
38 | fn change(&mut self, _props: Self::Properties) -> bool {
39 | // TODO fix the PartialEq hack
40 | /*if format!("{:?}", self.props) != format!("{:?}", props) {
41 | self.props = props;
42 | true
43 | } else {
44 | false
45 | }*/
46 | true
47 | }
48 |
49 | //noinspection RsTypeCheck
50 | fn view(&self) -> Html {
51 | let new_user = self.props.event.is_new_user(self.props.prev_event.as_ref());
52 | let sender_displayname = if new_user {
53 | self.props.room.get_sender_displayname(&self.props.event)
54 | } else {
55 | ""
56 | };
57 |
58 | let mut pure_content = self.props.text_event.body.clone();
59 | let finder = LinkFinder::new();
60 | let pure_content_clone = pure_content.clone();
61 | let links: Vec<_> = finder.links(&pure_content_clone).collect();
62 |
63 | let content = if !links.is_empty() {
64 | for link in links {
65 | let html_link = format!("{}", link.as_str(), link.as_str());
66 | pure_content.replace_range(link.start()..link.end(), &html_link);
67 | }
68 | pure_content
69 | } else {
70 | pure_content
71 | };
72 |
73 | if let Some(formatted) = &self.props.text_event.formatted {
74 | let format_slot;
75 | let message = if new_user {
76 | format_slot = format!(
77 | "{}: {}",
78 | sender_displayname, formatted.body
79 | );
80 | &format_slot
81 | } else {
82 | &formatted.body
83 | };
84 | let js_text_event = {
85 | let div = web_sys::window()
86 | .unwrap()
87 | .document()
88 | .unwrap()
89 | .create_element("p")
90 | .unwrap();
91 | div.set_inner_html(message.as_str());
92 | div
93 | };
94 | let node = Node::from(js_text_event);
95 | VNode::VRef(node)
96 | } else if new_user {
97 | let full_html = format!(
98 | "{}: {}
",
99 | sender_displayname, content
100 | );
101 | let js_text_event = {
102 | let div = web_sys::window()
103 | .unwrap()
104 | .document()
105 | .unwrap()
106 | .create_element("p")
107 | .unwrap();
108 | div.set_inner_html(full_html.as_str());
109 | div
110 | };
111 | let node = Node::from(js_text_event);
112 | VNode::VRef(node)
113 | } else {
114 | let full_html = format!("{}
", content);
115 | let js_text_event = {
116 | let div = web_sys::window()
117 | .unwrap()
118 | .document()
119 | .unwrap()
120 | .create_element("p")
121 | .unwrap();
122 | div.set_inner_html(full_html.as_str());
123 | div
124 | };
125 | let node = Node::from(js_text_event);
126 | VNode::VRef(node)
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/app/components/events/video.rs:
--------------------------------------------------------------------------------
1 | use std::rc::Rc;
2 |
3 | use crate::app::components::events::{EventExt, RoomExt};
4 | use matrix_sdk::{
5 | events::{room::message::VideoMessageEventContent, AnySyncMessageEvent},
6 | Room,
7 | };
8 | use rand::random;
9 | use yew::prelude::*;
10 |
11 | pub(crate) struct Video {
12 | props: Props,
13 | }
14 |
15 | #[derive(Clone, Properties, Debug)]
16 | pub struct Props {
17 | #[prop_or_default]
18 | pub prev_event: Option,
19 | pub event: AnySyncMessageEvent,
20 | pub video_event: VideoMessageEventContent,
21 | pub room: Rc,
22 | }
23 |
24 | impl Component for Video {
25 | type Message = ();
26 | type Properties = Props;
27 |
28 | fn create(props: Self::Properties, _link: ComponentLink) -> Self {
29 | Video { props }
30 | }
31 |
32 | fn update(&mut self, _msg: Self::Message) -> bool {
33 | false
34 | }
35 |
36 | fn change(&mut self, _props: Self::Properties) -> bool {
37 | // TODO fix the PartialEq hack
38 | /*if format!("{:?}", self.props) != format!("{:?}", props) {
39 | self.props = props;
40 | true
41 | } else {
42 | false
43 | }*/
44 | true
45 | }
46 |
47 | //noinspection RsTypeCheck
48 | fn view(&self) -> Html {
49 | let new_user = self.props.event.is_new_user(self.props.prev_event.as_ref());
50 | let sender_displayname = if new_user {
51 | self.props.room.get_sender_displayname(&self.props.event)
52 | } else {
53 | ""
54 | };
55 |
56 | let _caption = format!("{}: {}", sender_displayname, self.props.video_event.body);
57 |
58 | if let Some(video_url) = self.props.video_event.url.as_ref() {
59 | let thumbnail = self
60 | .props
61 | .video_event
62 | .info
63 | .as_ref()
64 | .unwrap()
65 | .thumbnail_url
66 | .as_ref()
67 | .unwrap_or(video_url);
68 |
69 | let lightbox_id: u8 = random();
70 | let lightbox_id_full = format!("video_{}", lightbox_id);
71 | let lightbox_href_full = format!("#video_{}", lightbox_id);
72 | if new_user {
73 | html! {
74 |
75 |
{sender_displayname}{": "}
76 |

77 |
78 |
82 |
83 |
86 |
87 | }
88 | } else {
89 | html! {
90 |
91 |

92 |
93 |
97 |
98 |
101 |
102 | }
103 | }
104 | } else {
105 | html! {}
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/app/components/input.rs:
--------------------------------------------------------------------------------
1 | use yew::prelude::*;
2 |
3 | #[derive(Debug, PartialEq, Clone, Properties)]
4 | pub struct InputProps {
5 | pub on_submit: Callback,
6 | }
7 |
8 | pub struct InputState {
9 | value: Option,
10 | }
11 |
12 | pub struct Input {
13 | on_input: Callback,
14 | on_submit: Callback,
15 | state: InputState,
16 | props: InputProps,
17 | }
18 |
19 | #[allow(clippy::large_enum_variant)]
20 | pub enum Msg {
21 | ValueChange(InputData),
22 | ValueSubmit(KeyboardEvent),
23 | }
24 |
25 | impl Component for Input {
26 | type Message = Msg;
27 | type Properties = InputProps;
28 |
29 | fn create(props: Self::Properties, link: ComponentLink) -> Self {
30 | let state = InputState { value: None };
31 | Self {
32 | props,
33 | on_input: link.callback(Msg::ValueChange),
34 | on_submit: link.callback(Msg::ValueSubmit),
35 | state,
36 | }
37 | }
38 |
39 | fn update(&mut self, msg: Self::Message) -> bool {
40 | match msg {
41 | Msg::ValueChange(data) => {
42 | self.state.value = Some(data.value);
43 | true
44 | }
45 | Msg::ValueSubmit(data) => {
46 | if data.key() == "Enter" {
47 | self.props
48 | .on_submit
49 | .emit(self.state.value.as_deref().unwrap_or("").to_owned());
50 | self.state.value = None;
51 | return true;
52 | }
53 | false
54 | }
55 | }
56 | }
57 |
58 | fn change(&mut self, _props: Self::Properties) -> bool {
59 | false
60 | }
61 |
62 | fn view(&self) -> Html {
63 | html! {
64 |
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/app/components/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod event_list;
2 | pub mod events;
3 | pub mod input;
4 | pub mod raw_html;
5 | pub mod room_list;
6 |
--------------------------------------------------------------------------------
/src/app/components/raw_html.rs:
--------------------------------------------------------------------------------
1 | use web_sys::Element;
2 | use yew::{html, Component, ComponentLink, Html, NodeRef, Properties, ShouldRender};
3 |
4 | #[derive(Debug, Clone, Eq, PartialEq, Properties)]
5 | pub struct RawHTMLProps {
6 | pub inner_html: String,
7 | }
8 |
9 | pub struct RawHTML {
10 | props: RawHTMLProps,
11 | node_ref: NodeRef,
12 | }
13 |
14 | impl Component for RawHTML {
15 | type Message = ();
16 | type Properties = RawHTMLProps;
17 |
18 | fn create(props: Self::Properties, _: ComponentLink) -> Self {
19 | Self {
20 | props,
21 | node_ref: NodeRef::default(),
22 | }
23 | }
24 |
25 | fn update(&mut self, _: Self::Message) -> ShouldRender {
26 | true
27 | }
28 |
29 | fn change(&mut self, props: Self::Properties) -> ShouldRender {
30 | if self.props != props {
31 | self.props = props;
32 | true
33 | } else {
34 | false
35 | }
36 | }
37 |
38 | fn view(&self) -> Html {
39 | // create the parent element and store a reference to it
40 | html! {
41 |
42 | }
43 | }
44 |
45 | fn rendered(&mut self, _first_render: bool) {
46 | let el = self.node_ref.cast::
().unwrap();
47 | el.set_inner_html(&self.props.inner_html);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/app/components/room_list/item.rs:
--------------------------------------------------------------------------------
1 | use std::rc::Rc;
2 |
3 | use matrix_sdk::{
4 | events::{
5 | room::message::MessageEventContent, AnyPossiblyRedactedSyncMessageEvent,
6 | AnySyncMessageEvent, SyncMessageEvent,
7 | },
8 | identifiers::RoomId,
9 | Room,
10 | };
11 | use yew::prelude::*;
12 | use yewtil::NeqAssign;
13 |
14 | pub(crate) struct RoomItem {
15 | props: Props,
16 | link: ComponentLink,
17 | }
18 |
19 | pub enum Msg {
20 | ChangeRoom(Rc),
21 | }
22 |
23 | #[derive(Clone, Properties, Debug, PartialEq)]
24 | pub struct Props {
25 | pub room: Rc,
26 |
27 | #[prop_or_default]
28 | pub change_room_callback: Callback,
29 | }
30 |
31 | impl Component for RoomItem {
32 | type Message = Msg;
33 | type Properties = Props;
34 |
35 | fn create(props: Self::Properties, link: ComponentLink) -> Self {
36 | RoomItem { props, link }
37 | }
38 |
39 | fn update(&mut self, msg: Self::Message) -> bool {
40 | match msg {
41 | Msg::ChangeRoom(room) => {
42 | self.props.change_room_callback.emit(room.room_id.clone());
43 | }
44 | }
45 | false
46 | }
47 |
48 | fn change(&mut self, props: Self::Properties) -> bool {
49 | self.props.neq_assign(props)
50 | }
51 |
52 | //noinspection RsTypeCheck
53 | fn view(&self) -> Html {
54 | let room = self.props.room.clone();
55 |
56 | // TODO placeholder for encrypted rooms
57 | let last_message = match room.messages.iter().last() {
58 | Some(AnyPossiblyRedactedSyncMessageEvent::Regular(
59 | AnySyncMessageEvent::RoomMessage(SyncMessageEvent {
60 | content: MessageEventContent::Text(text_event),
61 | ..
62 | }),
63 | )) => &text_event.body,
64 | _ => "",
65 | };
66 |
67 | let room = room.clone();
68 | let display_name = room.display_name();
69 |
70 | html! {
71 |
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/app/components/room_list/mod.rs:
--------------------------------------------------------------------------------
1 | use std::{collections::HashMap, rc::Rc};
2 |
3 | use log::*;
4 | use matrix_sdk::{identifiers::RoomId, Room};
5 | use serde::{Deserialize, Serialize};
6 | use wasm_bindgen::JsCast;
7 | use web_sys::HtmlElement;
8 | use yew::prelude::*;
9 | use yew::utils::document;
10 | use yew::{Bridge, Bridged, Component, ComponentLink, Html};
11 | use yewtil::NeqAssign;
12 |
13 | use tr::tr;
14 |
15 | use crate::app::components::raw_html::RawHTML;
16 | use crate::app::components::room_list::item::RoomItem;
17 | use crate::app::matrix::{MatrixAgent, Request, Response};
18 |
19 | mod item;
20 |
21 | pub struct RoomList {
22 | link: ComponentLink,
23 | state: State,
24 | matrix_agent: Box>,
25 | props: Props,
26 | }
27 |
28 | #[allow(clippy::large_enum_variant)]
29 | pub enum Msg {
30 | NewMessage(Response),
31 | ChangeRoom(RoomId),
32 | SetFilter(String),
33 | ToggleTheme,
34 | }
35 |
36 | #[derive(Serialize, Deserialize, Default)]
37 | pub struct State {
38 | rooms: HashMap>,
39 | current_room: Option,
40 | loading: bool,
41 | search_query: Option,
42 | dark_theme: bool,
43 | }
44 |
45 | #[derive(Clone, PartialEq, Properties)]
46 | pub struct Props {
47 | #[prop_or_default]
48 | pub change_room_callback: Callback>,
49 | }
50 |
51 | impl Component for RoomList {
52 | type Message = Msg;
53 | type Properties = Props;
54 |
55 | fn create(props: Self::Properties, link: ComponentLink) -> Self {
56 | let matrix_callback = link.callback(Msg::NewMessage);
57 | let matrix_agent = MatrixAgent::bridge(matrix_callback);
58 | let state = State {
59 | rooms: Default::default(),
60 | current_room: None,
61 | loading: true,
62 | search_query: None,
63 | dark_theme: false,
64 | };
65 |
66 | RoomList {
67 | props,
68 | link,
69 | matrix_agent,
70 | state,
71 | }
72 | }
73 |
74 | fn update(&mut self, msg: Self::Message) -> bool {
75 | match msg {
76 | Msg::NewMessage(response) => match response {
77 | // Handle new rooms from sync
78 | Response::JoinedRoomSync(room_id) => {
79 | info!("Got JoinedRoomSync");
80 | if !(self.state.rooms.contains_key(&room_id)) {
81 | self.matrix_agent.send(Request::GetJoinedRoom(room_id));
82 | }
83 | false
84 | }
85 | Response::JoinedRoom((room_id, room)) => {
86 | info!("Got JoinedRoom");
87 | self.state.rooms.insert(room_id, Rc::new(room));
88 | if self.state.loading {
89 | self.state.loading = false;
90 | }
91 | true
92 | }
93 | _ => false,
94 | },
95 | Msg::ChangeRoom(room_id) => {
96 | if self.state.current_room.is_some()
97 | && self.state.current_room.as_ref().unwrap() == &room_id
98 | {
99 | return false;
100 | }
101 |
102 | let room = self.state.rooms[&room_id].clone();
103 | self.props.change_room_callback.emit(room);
104 | self.state.current_room = Some(room_id);
105 | true
106 | }
107 | Msg::SetFilter(query) => {
108 | self.state.search_query = Some(query);
109 | true
110 | }
111 | Msg::ToggleTheme => {
112 | self.state.dark_theme = !self.state.dark_theme;
113 | let theme = if self.state.dark_theme {
114 | "dark"
115 | } else {
116 | "light"
117 | };
118 | document()
119 | .document_element()
120 | .unwrap()
121 | .dyn_into::()
122 | .unwrap()
123 | .dataset()
124 | .set("theme", theme)
125 | .unwrap();
126 | true
127 | }
128 | }
129 | }
130 |
131 | fn change(&mut self, props: Self::Properties) -> ShouldRender {
132 | self.props.neq_assign(props)
133 | }
134 |
135 | //noinspection RsTypeCheck
136 | fn view(&self) -> Html {
137 | if self.state.loading {
138 | html! {
139 |
144 | }
145 | } else {
146 | let rooms: Html = match self.state.search_query.as_deref() {
147 | None | Some("") => self
148 | .state
149 | .rooms
150 | .iter()
151 | .map(|(_, room)| self.get_room(room))
152 | .collect(),
153 | _ => self
154 | .state
155 | .rooms
156 | .iter()
157 | .filter(|(_, room)| {
158 | room.display_name()
159 | .to_lowercase()
160 | .contains(&self.state.search_query.as_ref().unwrap().to_lowercase())
161 | })
162 | .map(|(_, room)| self.get_room(room))
163 | .collect(),
164 | };
165 |
166 | html! {
167 |
168 |
169 |
170 |
171 |
172 |
173 | {"search"}
174 |
185 |
186 |
187 |
188 |
{rooms}
189 |
190 |
191 |
192 |
193 |
206 |
207 |
208 |
209 |
210 | }
211 | }
212 | }
213 | }
214 |
215 | impl RoomList {
216 | fn get_room(&self, matrix_room: &Rc) -> Html {
217 | let room = matrix_room.clone();
218 | html! {
219 |
220 | }
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/src/app/matrix/login.rs:
--------------------------------------------------------------------------------
1 | use crate::errors::{Field, MatrixError};
2 | use log::*;
3 | use matrix_sdk::{Client, ClientConfig, Session};
4 | use serde::{Deserialize, Serialize};
5 | use std::convert::TryFrom;
6 | use url::Url;
7 | use wasm_bindgen_futures::spawn_local;
8 |
9 | #[derive(Serialize, Deserialize, Default, Clone, Debug)]
10 | pub struct SessionStore {
11 | pub(crate) access_token: String,
12 | pub(crate) user_id: String,
13 | pub(crate) device_id: String,
14 | pub(crate) homeserver_url: String,
15 | }
16 |
17 | pub fn login(
18 | session: Option<&SessionStore>,
19 | homeserver: Option<&String>,
20 | ) -> Result {
21 | info!("preparing client");
22 | match session {
23 | Some(session) => restore_client(session),
24 | None => match homeserver {
25 | Some(homeserver) => {
26 | let homeserver = Url::parse(&homeserver);
27 | match homeserver {
28 | Ok(homeserver) => {
29 | let client_config = ClientConfig::new();
30 | let client = Client::new_with_config(homeserver, client_config).unwrap();
31 | Ok(client)
32 | }
33 | Err(e) => Err(MatrixError::UrlParseError(e.to_string())),
34 | }
35 | }
36 | None => Err(MatrixError::MissingFields(Field::Homeserver)),
37 | },
38 | }
39 | }
40 |
41 | fn restore_client(session: &SessionStore) -> Result {
42 | let homeserver = Url::parse(&session.homeserver_url);
43 | match homeserver {
44 | Ok(homeserver) => {
45 | let client_config = ClientConfig::new();
46 | let client = Client::new_with_config(homeserver, client_config);
47 | match client {
48 | Ok(client) => {
49 | info!("got client");
50 | // Also directly restore Login data
51 | let session = Session {
52 | access_token: session.access_token.clone(),
53 | user_id: matrix_sdk::identifiers::UserId::try_from(
54 | session.user_id.as_str(),
55 | )
56 | .unwrap(),
57 | device_id: session.device_id.clone().into(),
58 | };
59 | info!("before restore");
60 | let cloned_client = client.clone();
61 | spawn_local(async move {
62 | if let Err(e) = cloned_client.restore_login(session).await {
63 | error!("{}", e);
64 | // TODO find a way to get this back up in the function tree
65 | }
66 | });
67 | info!("after restore");
68 | Ok(client)
69 | }
70 | Err(e) => Err(MatrixError::SDKError(e.to_string())),
71 | }
72 | }
73 | Err(e) => Err(MatrixError::UrlParseError(e.to_string())),
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/app/matrix/mod.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashSet;
2 | use std::sync::Arc;
3 |
4 | use log::*;
5 | use matrix_sdk::{
6 | api::r0::{
7 | filter::RoomEventFilter,
8 | message::{
9 | get_message_events::Direction, get_message_events::Request as GetMessagesRequest,
10 | },
11 | },
12 | events::{
13 | room::message::{FormattedBody, MessageEventContent, TextMessageEventContent},
14 | AnyMessageEvent, AnyRoomEvent, AnySyncMessageEvent,
15 | },
16 | identifiers::RoomId,
17 | js_int::uint,
18 | locks::RwLock,
19 | Client, Raw, Room,
20 | };
21 | use pulldown_cmark::{html, Options, Parser};
22 | use serde::{Deserialize, Serialize};
23 | use wasm_bindgen_futures::spawn_local;
24 | use yew::worker::*;
25 |
26 | use crate::app::matrix::types::{get_media_download_url, get_video_media_download_url};
27 | use crate::errors::MatrixError;
28 | use login::{login, SessionStore};
29 |
30 | pub mod login;
31 | mod sync;
32 | pub mod types;
33 |
34 | #[derive(Default, Clone, Debug)]
35 | pub struct MatrixClient {
36 | pub(crate) homeserver: Option,
37 | pub(crate) username: Option,
38 | pub(crate) password: Option,
39 | }
40 |
41 | #[derive(Clone, Debug)]
42 | pub struct MatrixAgent {
43 | link: AgentLink,
44 | matrix_state: MatrixClient,
45 | matrix_client: Option,
46 | // TODO make arc mutex :(
47 | subscribers: HashSet,
48 | session: Option,
49 | }
50 |
51 | #[derive(Serialize, Deserialize, Debug)]
52 | pub enum Request {
53 | SetHomeserver(String),
54 | SetUsername(String),
55 | SetPassword(String),
56 | SetSession(SessionStore),
57 | Login,
58 | GetLoggedIn,
59 | GetOldMessages((RoomId, Option)),
60 | StartSync,
61 | GetJoinedRoom(RoomId),
62 | SendMessage((RoomId, String)),
63 | }
64 |
65 | #[allow(clippy::large_enum_variant)]
66 | #[derive(Serialize, Deserialize, Debug, Clone)]
67 | pub enum Response {
68 | Error(MatrixError),
69 | LoggedIn(bool),
70 | // TODO properly handle sync events
71 | Sync((RoomId, Raw)),
72 | JoinedRoomSync(RoomId),
73 | SyncPing,
74 | OldMessages((RoomId, Vec>)),
75 | JoinedRoom((RoomId, Room)),
76 | SaveSession(SessionStore),
77 | }
78 |
79 | #[derive(Debug, Clone)]
80 | pub enum Msg {
81 | OnSyncResponse(Response),
82 | }
83 |
84 | impl Agent for MatrixAgent {
85 | type Reach = Public;
86 | type Message = Msg;
87 | type Input = Request;
88 | type Output = Response;
89 |
90 | fn create(link: AgentLink) -> Self {
91 | MatrixAgent {
92 | link,
93 | matrix_state: Default::default(),
94 | matrix_client: None,
95 | subscribers: HashSet::new(),
96 | session: Default::default(),
97 | }
98 | }
99 |
100 | fn update(&mut self, msg: Self::Message) {
101 | match msg {
102 | Msg::OnSyncResponse(resp) => {
103 | for sub in self.subscribers.iter() {
104 | self.link.respond(*sub, resp.clone());
105 | }
106 | }
107 | }
108 | }
109 |
110 | fn connected(&mut self, id: HandlerId) {
111 | self.subscribers.insert(id);
112 | }
113 | fn handle_input(&mut self, msg: Self::Input, _: HandlerId) {
114 | match msg {
115 | Request::SetSession(session) => {
116 | self.session = Some(session);
117 | }
118 |
119 | Request::SetHomeserver(homeserver) => {
120 | self.matrix_state.homeserver = Some(homeserver);
121 | }
122 | Request::SetUsername(username) => {
123 | self.matrix_state.username = Some(username);
124 | }
125 | Request::SetPassword(password) => {
126 | self.matrix_state.password = Some(password);
127 | }
128 | Request::Login => {
129 | info!("Starting Login");
130 | let homeserver = self.matrix_state.homeserver.as_ref();
131 | let session = self.session.as_ref();
132 | let client = login(session, homeserver);
133 | match client {
134 | Ok(client) => {
135 | if let Some(_session) = session {
136 | for sub in self.subscribers.iter() {
137 | let resp = Response::LoggedIn(true);
138 | self.link.respond(*sub, resp);
139 | }
140 | }
141 | self.matrix_client = Some(client.clone());
142 | let username = self.matrix_state.username.clone().unwrap();
143 | let password = self.matrix_state.password.clone().unwrap();
144 | let agent = self.clone();
145 | spawn_local(async move {
146 | // FIXME gracefully handle login errors
147 | // TODO make the String to &str conversion smarter if possible
148 | let login_response = agent
149 | .matrix_client
150 | .as_ref()
151 | .unwrap()
152 | .login(&username, &password, None, Some("Daydream"))
153 | .await;
154 | match login_response {
155 | Ok(login_response) => {
156 | let session_store = SessionStore {
157 | access_token: login_response.access_token,
158 | user_id: login_response.user_id.to_string(),
159 | device_id: login_response.device_id.into(),
160 | homeserver_url: client.homeserver().to_string(),
161 | };
162 | for sub in agent.subscribers.iter() {
163 | let resp = Response::SaveSession(session_store.clone());
164 | agent.link.respond(*sub, resp);
165 | let resp = Response::LoggedIn(true);
166 | agent.link.respond(*sub, resp);
167 | }
168 | }
169 | Err(e) => {
170 | if let matrix_sdk::Error::Reqwest(e) = e {
171 | match e.status() {
172 | None => {
173 | for sub in agent.subscribers.iter() {
174 | let resp = Response::Error(
175 | MatrixError::SDKError(e.to_string()),
176 | );
177 | agent.link.respond(*sub, resp);
178 | }
179 | }
180 | Some(v) => {
181 | if v.is_server_error() {
182 | for sub in agent.subscribers.iter() {
183 | let resp = Response::Error(
184 | MatrixError::LoginTimeout,
185 | );
186 | agent.link.respond(*sub, resp);
187 | }
188 | } else {
189 | for sub in agent.subscribers.iter() {
190 | let resp = Response::Error(
191 | MatrixError::SDKError(e.to_string()),
192 | );
193 | agent.link.respond(*sub, resp);
194 | }
195 | }
196 | }
197 | }
198 | } else {
199 | for sub in agent.subscribers.iter() {
200 | let resp = Response::Error(MatrixError::SDKError(
201 | e.to_string(),
202 | ));
203 | agent.link.respond(*sub, resp);
204 | }
205 | }
206 | }
207 | }
208 | });
209 | }
210 | Err(e) => {
211 | for sub in self.subscribers.iter() {
212 | let resp = Response::Error(e.clone());
213 | self.link.respond(*sub, resp);
214 | }
215 | }
216 | }
217 | }
218 | Request::GetLoggedIn => {
219 | let homeserver = self.matrix_state.homeserver.as_ref();
220 | let session = self.session.clone();
221 | let client = login(session.as_ref(), homeserver);
222 | match client {
223 | Ok(client) => {
224 | info!("Got client");
225 | self.matrix_client = Some(client);
226 | info!("Client set");
227 | let agent = self.clone();
228 | spawn_local(async move {
229 | let logged_in = agent.get_logged_in().await;
230 |
231 | if !logged_in && session.is_some() {
232 | error!("Not logged in but got session");
233 | } else {
234 | for sub in agent.subscribers.iter() {
235 | let resp = Response::LoggedIn(logged_in);
236 | agent.link.respond(*sub, resp);
237 | }
238 | }
239 | });
240 | }
241 | Err(e) => {
242 | error!("Got no client: {:?}", e);
243 | for sub in self.subscribers.iter() {
244 | let resp = Response::Error(e.clone());
245 | self.link.respond(*sub, resp);
246 | }
247 | }
248 | }
249 | }
250 | Request::StartSync => {
251 | // Always clone agent after having tried to login!
252 | let agent = self.clone();
253 | spawn_local(async move {
254 | agent.start_sync().await;
255 | });
256 | }
257 | Request::GetOldMessages((room_id, from)) => {
258 | let agent = self.clone();
259 | spawn_local(async move {
260 | let sync_token = match from {
261 | Some(from) => from,
262 | None => agent
263 | .matrix_client
264 | .as_ref()
265 | .unwrap()
266 | .sync_token()
267 | .await
268 | .unwrap(),
269 | };
270 | let mut req =
271 | GetMessagesRequest::new(&room_id, &sync_token, Direction::Backward);
272 | let filter = RoomEventFilter {
273 | types: Some(vec!["m.room.message".to_string()]),
274 | ..Default::default()
275 | };
276 | // TODO find better way than cloning
277 | req.filter = Some(filter);
278 | req.limit = uint!(30);
279 |
280 | // TODO handle error gracefully
281 | let messsages = agent
282 | .matrix_client
283 | .clone()
284 | .unwrap()
285 | .room_messages(req)
286 | .await
287 | .unwrap();
288 | // TODO save end point for future loading
289 |
290 | let mut wrapped_messages: Vec> = Vec::new();
291 | let chunk_iter: Vec> = messsages.chunk;
292 | let (oks, _): (Vec<_>, Vec<_>) = chunk_iter
293 | .iter()
294 | .map(|event| event.deserialize())
295 | .partition(Result::is_ok);
296 |
297 | let deserialized_events: Vec =
298 | oks.into_iter().map(Result::unwrap).collect();
299 |
300 | for event in deserialized_events.into_iter().rev() {
301 | // TODO deduplicate betweeen this and sync
302 | if let AnyRoomEvent::Message(AnyMessageEvent::RoomMessage(mut event)) =
303 | event
304 | {
305 | if let MessageEventContent::Image(mut image_event) =
306 | event.clone().content
307 | {
308 | if let Some(image_event_url) = image_event.url {
309 | let new_url = get_media_download_url(
310 | agent.matrix_client.as_ref().unwrap().homeserver(),
311 | &image_event_url,
312 | );
313 | image_event.url = Some(new_url.to_string());
314 | }
315 | if let Some(mut info) = image_event.info {
316 | if let Some(thumbnail_url) = info.thumbnail_url.as_ref() {
317 | let new_url = get_media_download_url(
318 | agent.matrix_client.as_ref().unwrap().homeserver(),
319 | thumbnail_url,
320 | );
321 | info.thumbnail_url = Some(new_url.to_string());
322 | }
323 | image_event.info = Some(info);
324 | }
325 | event.content = MessageEventContent::Image(image_event);
326 | }
327 | if let MessageEventContent::Video(mut video_event) = event.content {
328 | if let Some(video_event_url) = video_event.url {
329 | let new_url = get_video_media_download_url(
330 | agent.matrix_client.as_ref().unwrap().homeserver(),
331 | video_event_url,
332 | );
333 | video_event.url = Some(new_url.to_string());
334 | }
335 | if let Some(mut info) = video_event.info {
336 | if let Some(thumbnail_url) = info.thumbnail_url {
337 | let new_url = Some(get_media_download_url(
338 | agent.matrix_client.as_ref().unwrap().homeserver(),
339 | &thumbnail_url,
340 | ))
341 | .unwrap();
342 | info.thumbnail_url = Some(new_url.to_string());
343 | }
344 | video_event.info = Some(info);
345 | }
346 | event.content = MessageEventContent::Video(video_event);
347 | }
348 |
349 | let serialized_event =
350 | Raw::from(AnyMessageEvent::RoomMessage(event.clone()));
351 | wrapped_messages.push(serialized_event);
352 | }
353 | }
354 |
355 | for sub in agent.subscribers.iter() {
356 | let resp =
357 | Response::OldMessages((room_id.clone(), wrapped_messages.clone()));
358 | agent.link.respond(*sub, resp);
359 | }
360 | });
361 | }
362 | Request::GetJoinedRoom(room_id) => {
363 | let agent = self.clone();
364 | spawn_local(async move {
365 | let room: Arc> = agent
366 | .matrix_client
367 | .unwrap()
368 | .get_joined_room(&room_id)
369 | .await
370 | .unwrap();
371 | let read_clone = room.read().await;
372 | let clean_room = (*read_clone).clone();
373 | for sub in agent.subscribers.iter() {
374 | let resp = Response::JoinedRoom((room_id.clone(), clean_room.clone()));
375 | agent.link.respond(*sub, resp);
376 | }
377 | });
378 | }
379 | Request::SendMessage((room_id, raw_message)) => {
380 | let client = self.matrix_client.clone().unwrap();
381 | spawn_local(async move {
382 | let replacer = gh_emoji::Replacer::new();
383 | let message = replacer.replace_all(raw_message.as_str());
384 |
385 | let mut options = Options::empty();
386 | options.insert(Options::ENABLE_STRIKETHROUGH);
387 | let parser = Parser::new_ext(message.as_ref(), options);
388 |
389 | let mut formatted_message: String =
390 | String::with_capacity(message.len() * 3 / 2);
391 | html::push_html(&mut formatted_message, parser);
392 | formatted_message = formatted_message.replace("", "").replace("
", "");
393 | formatted_message.pop();
394 |
395 | let content = if formatted_message == message {
396 | MessageEventContent::Text(TextMessageEventContent::plain(message))
397 | } else {
398 | MessageEventContent::Text(TextMessageEventContent {
399 | body: message.to_string(),
400 | relates_to: None,
401 | formatted: Some(FormattedBody::html(formatted_message)),
402 | })
403 | };
404 | if let Err(e) = client.room_send(&room_id, content, None).await {
405 | // TODO show error in UI or try again if possible
406 | error!("Error sending message: {}", e);
407 | }
408 | });
409 | }
410 | }
411 | }
412 |
413 | fn disconnected(&mut self, id: HandlerId) {
414 | self.subscribers.remove(&id);
415 | }
416 |
417 | fn name_of_resource() -> &'static str {
418 | "worker.js"
419 | }
420 | }
421 |
422 | unsafe impl Send for MatrixAgent {}
423 |
424 | unsafe impl std::marker::Sync for MatrixAgent {}
425 |
426 | impl MatrixAgent {
427 | async fn start_sync(&self) {
428 | let sync = sync::Sync {
429 | matrix_client: self.matrix_client.clone().unwrap(),
430 | callback: self.link.callback(Msg::OnSyncResponse),
431 | };
432 | sync.start_sync().await;
433 | }
434 |
435 | async fn get_logged_in(&self) -> bool {
436 | if self.matrix_client.is_none() {
437 | return false;
438 | }
439 | self.matrix_client.as_ref().unwrap().logged_in().await
440 | }
441 | }
442 |
--------------------------------------------------------------------------------
/src/app/matrix/sync.rs:
--------------------------------------------------------------------------------
1 | use std::mem;
2 | use std::sync::Arc;
3 | use std::sync::Mutex;
4 | use std::time::Duration;
5 |
6 | use log::*;
7 | use matrix_sdk::{
8 | api::r0::filter::{FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter},
9 | api::r0::sync::sync_events::Filter,
10 | api::r0::sync::sync_events::Response as SyncResponse,
11 | events::{
12 | room::message::MessageEventContent, AnySyncMessageEvent, AnySyncRoomEvent,
13 | AnySyncStateEvent,
14 | },
15 | identifiers::RoomId,
16 | locks::RwLock,
17 | Client, Raw, Room, SyncSettings,
18 | };
19 | use wasm_bindgen_futures::spawn_local;
20 | use yew::Callback;
21 |
22 | use lazy_static::lazy_static;
23 | use matrix_sdk::js_int::UInt;
24 |
25 | use crate::app::components::events::RoomExt;
26 | use crate::app::matrix::types::{get_media_download_url, get_video_media_download_url};
27 | use crate::app::matrix::Response;
28 | use crate::utils::notifications::Notifications;
29 |
30 | lazy_static! {
31 | static ref SYNC_NUMBER: Mutex = Mutex::new(0);
32 | }
33 |
34 | pub struct Sync {
35 | pub(crate) matrix_client: Client,
36 | pub(crate) callback: Callback,
37 | }
38 |
39 | impl Sync {
40 | pub async fn start_sync(&self) {
41 | debug!("start sync!");
42 | let client = self.matrix_client.clone();
43 | let settings = SyncSettings::default()
44 | .timeout(Duration::from_secs(30))
45 | .filter(Filter::FilterDefinition(FilterDefinition {
46 | room: Some(RoomFilter {
47 | timeline: Some(RoomEventFilter {
48 | limit: Some(UInt::new(20).unwrap()),
49 | lazy_load_options: LazyLoadOptions::Enabled {
50 | include_redundant_members: true,
51 | },
52 | ..Default::default()
53 | }),
54 | ..Default::default()
55 | }),
56 | ..Default::default()
57 | }));
58 | //.full_state(true);
59 |
60 | debug!("start sync_forever!");
61 | client
62 | .sync_forever(settings, |response| self.on_sync_response(response))
63 | .await;
64 | }
65 |
66 | async fn on_sync_response(&self, response: SyncResponse) {
67 | debug!("got sync!");
68 |
69 | // FIXME: Is there a smarter way?
70 | let resp = Response::SyncPing;
71 | self.callback.emit(resp);
72 | for (room_id, room) in response.rooms.join {
73 | for event in room.state.events {
74 | if let Ok(event) = event.deserialize() {
75 | self.on_state_event(&room_id, event).await
76 | }
77 | }
78 | for event in room.timeline.events {
79 | if let Ok(event) = event.deserialize() {
80 | self.on_room_message(&room_id, event).await
81 | }
82 | }
83 | }
84 | let mut sync_number = SYNC_NUMBER.lock().unwrap();
85 | if *sync_number == 0 {
86 | *sync_number = 1;
87 | }
88 | }
89 |
90 | async fn on_state_event(&self, room_id: &RoomId, event: AnySyncStateEvent) {
91 | if let AnySyncStateEvent::RoomCreate(_event) = event {
92 | info!("Sent JoinedRoomSync State");
93 | let resp = Response::JoinedRoomSync(room_id.clone());
94 | self.callback.emit(resp);
95 | }
96 | }
97 |
98 | async fn on_room_message(&self, room_id: &RoomId, event: AnySyncRoomEvent) {
99 | // TODO handle all messages...
100 |
101 | if let AnySyncRoomEvent::State(AnySyncStateEvent::RoomCreate(_create_event)) = event.clone()
102 | {
103 | info!("Sent JoinedRoomSync Timeline");
104 | let resp = Response::JoinedRoomSync(room_id.clone());
105 | self.callback.emit(resp);
106 | }
107 |
108 | if let AnySyncRoomEvent::Message(AnySyncMessageEvent::RoomMessage(mut event)) = event {
109 | if let MessageEventContent::Text(text_event) = event.content.clone() {
110 | let homeserver_url = self.matrix_client.clone().homeserver().clone();
111 |
112 | let cloned_event = event.clone();
113 | let client = self.matrix_client.clone();
114 | let local_room_id = room_id.clone();
115 | let sync_number = SYNC_NUMBER.lock().unwrap();
116 | if *sync_number == 1 {
117 | spawn_local(async move {
118 | let room: Arc> = client
119 | .clone()
120 | .get_joined_room(&local_room_id)
121 | .await
122 | .unwrap();
123 | if cloned_event.sender.clone() != client.user_id().await.unwrap() {
124 | let (avatar_url, room_name, displayname) = {
125 | let room = room.read().await;
126 | (
127 | room.get_sender_avatar(
128 | &homeserver_url,
129 | &AnySyncMessageEvent::RoomMessage(cloned_event.clone()),
130 | ),
131 | room.display_name(),
132 | room.get_sender_displayname(&AnySyncMessageEvent::RoomMessage(
133 | cloned_event,
134 | ))
135 | .to_string(),
136 | )
137 | };
138 |
139 | let title = if displayname == room_name {
140 | displayname
141 | } else {
142 | format!("{} ({})", displayname, room_name)
143 | };
144 |
145 | let notification =
146 | Notifications::new(avatar_url, title, text_event.body.clone());
147 | notification.show();
148 | }
149 | });
150 | }
151 | }
152 | if let MessageEventContent::Image(image_event) = &mut event.content {
153 | if let Some(image_url) = &mut image_event.url {
154 | let old_image_url = mem::take(image_url);
155 | *image_url = get_media_download_url(
156 | self.matrix_client.clone().homeserver(),
157 | &old_image_url,
158 | )
159 | .to_string();
160 | }
161 |
162 | if let Some(info) = &mut image_event.info {
163 | if let Some(thumbnail_url) = &mut info.thumbnail_url {
164 | let old_thumbnail_url = mem::take(thumbnail_url);
165 | *thumbnail_url = get_media_download_url(
166 | self.matrix_client.clone().homeserver(),
167 | &old_thumbnail_url,
168 | )
169 | .to_string();
170 | }
171 | }
172 | }
173 | if let MessageEventContent::Video(video_event) = &mut event.content {
174 | if let Some(video_url) = &mut video_event.url {
175 | let old_video_url = mem::take(video_url);
176 | *video_url = get_video_media_download_url(
177 | self.matrix_client.clone().homeserver(),
178 | old_video_url,
179 | )
180 | .to_string();
181 | }
182 |
183 | if let Some(info) = &mut video_event.info {
184 | if let Some(thumbnail_url) = &mut info.thumbnail_url {
185 | let old_thumbnail_url = mem::take(thumbnail_url);
186 | *thumbnail_url = get_media_download_url(
187 | self.matrix_client.clone().homeserver(),
188 | &old_thumbnail_url,
189 | )
190 | .to_string();
191 | }
192 | }
193 | }
194 |
195 | let serialized_event = Raw::from(AnySyncMessageEvent::RoomMessage(event));
196 | let resp = Response::Sync((room_id.clone(), serialized_event));
197 | self.callback.emit(resp);
198 | }
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/src/app/matrix/types.rs:
--------------------------------------------------------------------------------
1 | use url::Url;
2 |
3 | pub fn get_media_download_url(homeserver: &Url, mxc_url: &str) -> Url {
4 | let url_parts_raw = mxc_url.replace("mxc://", "");
5 | let url_parts: Vec<&str> = url_parts_raw.split('/').collect();
6 | let server_name = (*url_parts.first().unwrap()).to_string();
7 | let media_id = (*url_parts.last().unwrap()).to_string();
8 | let new_path = format!(
9 | "_matrix/media/r0/download/{}/{}/fix.jpg",
10 | server_name, media_id,
11 | );
12 | let mut new_url = homeserver.clone();
13 | new_url.set_path(new_path.as_str());
14 | new_url
15 | }
16 |
17 | pub fn get_video_media_download_url(homeserver: &Url, mxc_url: String) -> Url {
18 | let url_parts_raw = mxc_url.replace("mxc://", "");
19 | let url_parts: Vec<&str> = url_parts_raw.split('/').collect();
20 | let server_name = (*url_parts.first().unwrap()).to_string();
21 | let media_id = (*url_parts.last().unwrap()).to_string();
22 | let new_path = format!(
23 | "_matrix/media/r0/download/{}/{}/fix.mp4",
24 | server_name, media_id,
25 | );
26 | let mut new_url = homeserver.clone();
27 | new_url.set_path(new_path.as_str());
28 | new_url
29 | }
30 |
--------------------------------------------------------------------------------
/src/app/mod.rs:
--------------------------------------------------------------------------------
1 | use yew::{prelude::*, virtual_dom::VNode};
2 | use yew_router::agent::RouteRequest::ChangeRoute;
3 | use yew_router::{prelude::*, Switch};
4 |
5 | use crate::app::matrix::{login::SessionStore, MatrixAgent, Response};
6 | use crate::app::views::{login::Login, main_view::MainView};
7 | use crate::constants::AUTH_KEY;
8 | use log::*;
9 | use std::sync::{Arc, Mutex};
10 | use yew::format::Json;
11 | use yew::services::storage::Area;
12 | use yew::services::StorageService;
13 |
14 | pub mod components;
15 | pub mod matrix;
16 | mod views;
17 |
18 | #[derive(Switch, Clone)]
19 | pub enum AppRoute {
20 | #[to = "/login"]
21 | Login,
22 | #[to = "/"]
23 | MainView,
24 | }
25 |
26 | #[allow(clippy::large_enum_variant)]
27 | pub enum Msg {
28 | RouteChanged(Route<()>),
29 | ChangeRoute(AppRoute),
30 | NewMessage(Response),
31 | }
32 |
33 | pub struct App {
34 | // While unused this needs to stay :(
35 | _matrix_agent: Box>,
36 | route: Option>,
37 | route_agent: Box>>,
38 | storage: Arc>,
39 | }
40 |
41 | impl Component for App {
42 | type Message = Msg;
43 | type Properties = ();
44 |
45 | fn create(_: Self::Properties, link: ComponentLink) -> Self {
46 | let storage = Arc::new(Mutex::new(
47 | StorageService::new(Area::Local).expect("storage was disabled by the user"),
48 | ));
49 |
50 | let session: Option = {
51 | if let Json(Ok(restored_model)) = storage.lock().unwrap().restore(AUTH_KEY) {
52 | Some(restored_model)
53 | } else {
54 | None
55 | }
56 | };
57 | let route_agent = RouteAgent::bridge(link.callback(Msg::RouteChanged));
58 | let mut matrix_agent = MatrixAgent::bridge(link.callback(Msg::NewMessage));
59 | if let Some(session) = session {
60 | matrix_agent.send(matrix::Request::SetSession(session));
61 | }
62 | matrix_agent.send(matrix::Request::GetLoggedIn);
63 | App {
64 | _matrix_agent: matrix_agent,
65 | route_agent,
66 | route: None,
67 | storage,
68 | }
69 | }
70 |
71 | fn update(&mut self, msg: Self::Message) -> ShouldRender {
72 | match msg {
73 | Msg::RouteChanged(route) => {
74 | self.route = Some(route);
75 | }
76 | Msg::ChangeRoute(route) => {
77 | let route: Route = route.into();
78 | self.route = Some(route.clone());
79 | info!("{:?}", self.route);
80 | self.route_agent.send(ChangeRoute(route));
81 | }
82 | Msg::NewMessage(response) => {
83 | //info!("NewMessage: {:#?}", response);
84 | match response {
85 | Response::SaveSession(session) => {
86 | let mut storage = self.storage.lock().unwrap();
87 | storage.store(AUTH_KEY, Json(&session));
88 | }
89 | Response::Error(_) => {}
90 | Response::LoggedIn(logged_in) => {
91 | let route: Route = if logged_in {
92 | //self.state.logged_in = true;
93 |
94 | // replace with sync routeagent message once its possible
95 | // https://github.com/yewstack/yew/issues/1127
96 | //RouteService::new().get_route();
97 | AppRoute::MainView.into()
98 | } else {
99 | AppRoute::Login.into()
100 | };
101 |
102 | self.route = Some(route.clone());
103 | self.route_agent.send(ChangeRoute(route));
104 | }
105 | _ => {}
106 | }
107 | }
108 | }
109 | true
110 | }
111 |
112 | fn change(&mut self, _props: Self::Properties) -> ShouldRender {
113 | false
114 | }
115 |
116 | fn view(&self) -> Html {
117 | match &self.route {
118 | None => html! { },
119 | Some(route) => match AppRoute::switch(route.clone()) {
120 | Some(AppRoute::MainView) => html! { },
121 | Some(AppRoute::Login) => html! { },
122 | None => VNode::from("404"),
123 | },
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/app/svgs/DaydreamLogo_v0.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/svgs/DaydreamLogo_v0_light.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/svgs/loading_animation.svg:
--------------------------------------------------------------------------------
1 |
30 |
--------------------------------------------------------------------------------
/src/app/views/login.rs:
--------------------------------------------------------------------------------
1 | use std::include_str;
2 | use std::thread::sleep;
3 | use std::time::Duration;
4 |
5 | use log::*;
6 | use serde::{Deserialize, Serialize};
7 | use yew::prelude::*;
8 |
9 | use tr::tr;
10 |
11 | use crate::app::components::raw_html::RawHTML;
12 | use crate::app::matrix::{MatrixAgent, Request, Response};
13 | use crate::errors::{Field, MatrixError};
14 |
15 | pub struct Login {
16 | link: ComponentLink,
17 | state: State,
18 | matrix_agent: Box>,
19 | }
20 |
21 | pub enum Msg {
22 | NewMessage(Response),
23 | SetHomeserver(String),
24 | SetUsername(String),
25 | SetPassword(String),
26 | Login,
27 | }
28 |
29 | #[derive(Serialize, Deserialize, Default)]
30 | pub struct State {
31 | loading: bool,
32 | homeserver: String,
33 | username: String,
34 | password: String,
35 | error: Option,
36 | error_field: Option,
37 | retries: u64,
38 | }
39 |
40 | impl Component for Login {
41 | type Message = Msg;
42 | type Properties = ();
43 |
44 | fn create(_: Self::Properties, link: ComponentLink) -> Self {
45 | let matrix_callback = link.callback(Msg::NewMessage);
46 | let matrix_agent = MatrixAgent::bridge(matrix_callback);
47 | let state = State {
48 | loading: false,
49 | homeserver: "".to_string(),
50 | username: "".to_string(),
51 | password: "".to_string(),
52 | error: None,
53 | error_field: None,
54 | retries: 0,
55 | };
56 | Login {
57 | link,
58 | state,
59 | matrix_agent,
60 | }
61 | }
62 |
63 | fn update(&mut self, msg: Self::Message) -> ShouldRender {
64 | match msg {
65 | Msg::SetHomeserver(homeserver) => {
66 | self.state.homeserver = homeserver.clone();
67 | self.matrix_agent.send(Request::SetHomeserver(homeserver));
68 | true
69 | }
70 | Msg::SetUsername(username) => {
71 | self.state.username = username.clone();
72 | self.matrix_agent.send(Request::SetUsername(username));
73 | true
74 | }
75 | Msg::SetPassword(password) => {
76 | self.state.password = password.clone();
77 | self.matrix_agent.send(Request::SetPassword(password));
78 | true
79 | }
80 | Msg::Login => {
81 | // Reset Errors
82 | self.state.error = None;
83 | self.state.error_field = None;
84 |
85 | // Start loading
86 | self.matrix_agent.send(Request::Login);
87 | self.state.loading = true;
88 | true
89 | }
90 | Msg::NewMessage(Response::Error(error)) => {
91 | match &error {
92 | MatrixError::MissingFields(field) => {
93 | self.state.loading = false;
94 | self.state.error = Some(error.to_string());
95 | self.state.error_field = Some(field.clone());
96 | true
97 | }
98 | MatrixError::LoginTimeout => {
99 | // If we had less than 10 tries try again
100 | if self.state.retries < 10 {
101 | self.state.retries += 1;
102 | info!("Trying login again in 5 seconds");
103 | sleep(Duration::from_secs(5));
104 | self.link.send_message(Msg::Login);
105 | false
106 | } else {
107 | self.state.loading = false;
108 | self.state.error = Some("Login failed after 10 tries.".to_string());
109 | true
110 | }
111 | }
112 | MatrixError::SDKError(e) => {
113 | // TODO handle login error != timeout better
114 | error!("SDK Error: {}", e);
115 | false
116 | }
117 | _ => false,
118 | }
119 | }
120 | Msg::NewMessage(_) => false,
121 | }
122 | }
123 |
124 | fn change(&mut self, _props: Self::Properties) -> ShouldRender {
125 | false
126 | }
127 |
128 | //noinspection RsTypeCheck
129 | fn view(&self) -> Html {
130 | let mut homeserver_classes = "login-input";
131 | let mut mxid_classes = "login-input";
132 | let mut password_classes = "login-input";
133 | if let Some(v) = self.state.error_field.as_ref() {
134 | match v {
135 | Field::Homeserver => homeserver_classes = "login-input uk-form-danger",
136 | Field::MXID => mxid_classes = "login-input uk-form-danger",
137 | Field::Password => password_classes = "login-input uk-form-danger",
138 | }
139 | }
140 |
141 | if self.state.loading {
142 | html! {
143 |
148 | }
149 | } else {
150 | let error = match &self.state.error {
151 | Some(v) => html! {
152 |
153 | {
154 | tr!(
155 | // {0} is the Error that happened on login
156 | // The error message of the Login page
157 | "Error: {0}",
158 | v
159 | )
160 | }
161 |
162 | },
163 | None => html! {},
164 | };
165 |
166 | html! {
167 | <>
168 |
169 |
257 | >
258 | }
259 | }
260 | }
261 | }
262 |
--------------------------------------------------------------------------------
/src/app/views/main_view.rs:
--------------------------------------------------------------------------------
1 | use std::rc::Rc;
2 |
3 | use log::*;
4 | use matrix_sdk::Room;
5 | use serde::{Deserialize, Serialize};
6 | use yew::prelude::*;
7 | use yew::ComponentLink;
8 |
9 | use tr::tr;
10 |
11 | use crate::app::components::{event_list::EventList, room_list::RoomList};
12 | use crate::app::matrix::{MatrixAgent, Request};
13 |
14 | pub struct MainView {
15 | link: ComponentLink,
16 | state: State,
17 | }
18 |
19 | pub enum Msg {
20 | ChangeRoom(Rc),
21 | }
22 |
23 | #[derive(Serialize, Deserialize, Default)]
24 | pub struct State {
25 | pub current_room: Option>,
26 | pub current_room_displayname: String,
27 | }
28 |
29 | impl Component for MainView {
30 | type Message = Msg;
31 | type Properties = ();
32 |
33 | fn create(_: Self::Properties, link: ComponentLink) -> Self {
34 | let mut matrix_agent = MatrixAgent::dispatcher();
35 | matrix_agent.send(Request::StartSync);
36 | let state = State {
37 | current_room: None,
38 | current_room_displayname: Default::default(),
39 | };
40 |
41 | MainView { link, state }
42 | }
43 |
44 | fn update(&mut self, msg: Self::Message) -> bool {
45 | match msg {
46 | Msg::ChangeRoom(room) => {
47 | info!("Changing room to: {}", room.room_id);
48 | self.state.current_room = Some(room);
49 | }
50 | }
51 | true
52 | }
53 |
54 | fn change(&mut self, _props: Self::Properties) -> bool {
55 | false
56 | }
57 |
58 | //noinspection RsTypeCheck
59 | fn view(&self) -> Html {
60 | match &self.state.current_room {
61 | None => html! {
62 |
71 | },
72 | Some(room) if room.is_encrypted() => html! {
73 |
87 | },
88 | Some(room) => html! {
89 |
90 |
91 |
92 |
93 | },
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/app/views/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod login;
2 | pub mod main_view;
3 |
--------------------------------------------------------------------------------
/src/bin/app.rs:
--------------------------------------------------------------------------------
1 | #![recursion_limit = "1024"]
2 | extern crate console_error_panic_hook;
3 | //extern crate wee_alloc;
4 | use console_error_panic_hook::set_once as set_panic_hook;
5 | use i18n_embed::{language_loader, I18nEmbed, WebLanguageRequester};
6 | use rust_embed::RustEmbed;
7 |
8 | #[derive(RustEmbed, I18nEmbed)]
9 | #[folder = "i18n/mo"] // path to the compiled localization resources
10 | struct Translations;
11 |
12 | language_loader!(DaydreamLanguageLoader);
13 |
14 | //#[global_allocator]
15 | //static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
16 |
17 | fn main() {
18 | // If the `console_error_panic_hook` feature is enabled this will set a panic hook, otherwise
19 | // it will do nothing.
20 | set_panic_hook();
21 | wasm_logger::init(wasm_logger::Config::default());
22 |
23 | let translations = Translations {};
24 | let language_loader = DaydreamLanguageLoader::new();
25 | let requested_languages = WebLanguageRequester::requested_languages();
26 |
27 | i18n_embed::select(&language_loader, &translations, &requested_languages).unwrap();
28 |
29 | yew::start_app::();
30 | }
31 |
--------------------------------------------------------------------------------
/src/bin/native_worker.rs:
--------------------------------------------------------------------------------
1 | #![recursion_limit = "1024"]
2 | //====== Running the worker ======///
3 |
4 | // We need to import the Threaded trait to register the worker
5 | use log::*;
6 | use yew::agent::Threaded;
7 |
8 | /// This gets called by the worker.js entrypoint
9 | /// We need to wrap it in wasm_bindgen so the worker knows the spin the the yew worker instance
10 | fn main() {
11 | wasm_logger::init(wasm_logger::Config::default());
12 |
13 | // Spawning a yew component without StartApp requires initializing
14 | yew::initialize();
15 | info!("Worker");
16 |
17 | // ... registering the worker
18 | daydream::app::matrix::MatrixAgent::register();
19 | }
20 |
--------------------------------------------------------------------------------
/src/constants.rs:
--------------------------------------------------------------------------------
1 | pub const AUTH_KEY: &str = "nordgedanken.daydream.auth_data";
2 |
--------------------------------------------------------------------------------
/src/errors.rs:
--------------------------------------------------------------------------------
1 | use serde::{Deserialize, Serialize};
2 | use std::fmt;
3 | use thiserror::Error;
4 |
5 | #[derive(Debug, Clone, Serialize, Deserialize)]
6 | pub enum Field {
7 | Homeserver,
8 | MXID,
9 | Password,
10 | }
11 |
12 | impl fmt::Display for Field {
13 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
14 | match *self {
15 | Field::Homeserver => write!(f, "Homeserver Field"),
16 | Field::MXID => write!(f, "MXID Field"),
17 | Field::Password => write!(f, "Password Field"),
18 | }
19 | }
20 | }
21 |
22 | // TODO figure out a way to translate this
23 | #[derive(Error, Debug, Clone, Serialize, Deserialize)]
24 | pub enum MatrixError {
25 | #[error("No Matrix Client is available yet")]
26 | MissingClient,
27 | #[error("Missing required Data in {0}")]
28 | MissingFields(Field),
29 |
30 | /// An error occurred in the Matrix client library.
31 | /// This can't use transparent as we need Clone
32 | #[error("A Timeout happened on Login")]
33 | LoginTimeout,
34 |
35 | /// An error occurred in the Matrix client library.
36 | /// This can't use transparent as we need Clone
37 | #[error("An error occurred in the Matrix client library: `{0}`")]
38 | SDKError(String),
39 |
40 | /// An error occurred in the URL parse library.
41 | /// This can't use transparent as we need Serialize, Deserialize
42 | #[error("An error occurred in the URL parse library: `{0}`")]
43 | UrlParseError(String),
44 | }
45 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![recursion_limit = "1024"]
2 |
3 | pub mod app;
4 | mod constants;
5 | mod errors;
6 | pub mod utils;
7 |
--------------------------------------------------------------------------------
/src/utils/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod notifications;
2 | pub mod ruma;
3 | pub mod string_utils;
4 |
--------------------------------------------------------------------------------
/src/utils/notifications.rs:
--------------------------------------------------------------------------------
1 | use url::Url;
2 | use wasm_bindgen::prelude::*;
3 | use wasm_bindgen::JsCast;
4 | use web_sys::{Notification, NotificationOptions, NotificationPermission};
5 |
6 | #[derive(Clone)]
7 | pub(crate) struct Notifications {
8 | avatar: Option,
9 | displayname: String,
10 | content: String,
11 | }
12 |
13 | impl Notifications {
14 | pub fn new(avatar: Option, displayname: String, content: String) -> Self {
15 | Notifications {
16 | avatar,
17 | displayname,
18 | content,
19 | }
20 | }
21 |
22 | fn notifications_allowed(&self) -> bool {
23 | match Notification::permission() {
24 | NotificationPermission::Granted => true,
25 | _ => false,
26 | }
27 | }
28 |
29 | pub fn show(&self) {
30 | if !self.notifications_allowed() {
31 | let self_clone = self.clone();
32 | let cb = Closure::wrap(Box::new(move || {
33 | if self_clone.notifications_allowed() {
34 | self_clone.clone().show_actual();
35 | }
36 | }) as Box);
37 |
38 | if let Err(_e) = Notification::request_permission_with_permission_callback(
39 | cb.as_ref().unchecked_ref(),
40 | ) {
41 | // Noop to please clippy/rust compiler
42 | }
43 | cb.forget();
44 | } else {
45 | self.show_actual();
46 | }
47 | }
48 |
49 | fn show_actual(&self) {
50 | let mut options_0 = NotificationOptions::new() as NotificationOptions;
51 | let options_1 = options_0.body(&self.content).tag("daydream") as &mut NotificationOptions;
52 | let options = match self.clone().avatar {
53 | None => options_1,
54 | Some(avatar) => {
55 | let url = avatar.to_string();
56 | options_1.icon(&url)
57 | }
58 | };
59 | if let Err(_e) = Notification::new_with_options(&self.displayname, &options) {
60 | // Noop to please clippy/rust compiler
61 | // TODO check if we in this case should stop showing notifications
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/utils/ruma.rs:
--------------------------------------------------------------------------------
1 | use matrix_sdk::events::{
2 | room::redaction::SyncRedactionEvent, AnyMessageEvent, AnySyncMessageEvent, MessageEvent,
3 | MessageEventContent, SyncMessageEvent,
4 | };
5 |
6 | pub trait AnyMessageEventExt {
7 | fn without_room_id(self) -> AnySyncMessageEvent;
8 | }
9 |
10 | impl AnyMessageEventExt for AnyMessageEvent {
11 | fn without_room_id(self) -> AnySyncMessageEvent {
12 | fn without_room_id(ev: MessageEvent) -> SyncMessageEvent
13 | where
14 | C: MessageEventContent,
15 | {
16 | SyncMessageEvent {
17 | content: ev.content,
18 | event_id: ev.event_id,
19 | sender: ev.sender,
20 | origin_server_ts: ev.origin_server_ts,
21 | unsigned: ev.unsigned,
22 | }
23 | }
24 |
25 | use AnySyncMessageEvent::*;
26 |
27 | match self {
28 | Self::CallAnswer(ev) => CallAnswer(without_room_id(ev)),
29 | Self::CallInvite(ev) => CallInvite(without_room_id(ev)),
30 | Self::CallHangup(ev) => CallHangup(without_room_id(ev)),
31 | Self::CallCandidates(ev) => CallCandidates(without_room_id(ev)),
32 | Self::RoomEncrypted(ev) => RoomEncrypted(without_room_id(ev)),
33 | Self::RoomMessage(ev) => RoomMessage(without_room_id(ev)),
34 | Self::RoomMessageFeedback(ev) => RoomMessageFeedback(without_room_id(ev)),
35 | Self::Sticker(ev) => Sticker(without_room_id(ev)),
36 | Self::Custom(ev) => Custom(without_room_id(ev)),
37 | Self::RoomRedaction(ev) => RoomRedaction(SyncRedactionEvent {
38 | content: ev.content,
39 | event_id: ev.event_id,
40 | sender: ev.sender,
41 | origin_server_ts: ev.origin_server_ts,
42 | unsigned: ev.unsigned,
43 | redacts: ev.redacts,
44 | }),
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/utils/string_utils.rs:
--------------------------------------------------------------------------------
1 | use std::ops::{Bound, RangeBounds};
2 |
3 | pub trait StringUtils {
4 | fn substring(&self, start: usize, len: usize) -> &str;
5 | fn slice(&self, range: impl RangeBounds) -> &str;
6 | }
7 |
8 | impl StringUtils for str {
9 | fn substring(&self, start: usize, len: usize) -> &str {
10 | let mut char_pos = 0;
11 | let mut byte_start = 0;
12 | let mut it = self.chars();
13 | loop {
14 | if char_pos == start {
15 | break;
16 | }
17 | if let Some(c) = it.next() {
18 | char_pos += 1;
19 | byte_start += c.len_utf8();
20 | } else {
21 | break;
22 | }
23 | }
24 | char_pos = 0;
25 | let mut byte_end = byte_start;
26 | loop {
27 | if char_pos == len {
28 | break;
29 | }
30 | if let Some(c) = it.next() {
31 | char_pos += 1;
32 | byte_end += c.len_utf8();
33 | } else {
34 | break;
35 | }
36 | }
37 | &self[byte_start..byte_end]
38 | }
39 | fn slice(&self, range: impl RangeBounds) -> &str {
40 | let start = match range.start_bound() {
41 | Bound::Included(bound) | Bound::Excluded(bound) => *bound,
42 | Bound::Unbounded => 0,
43 | };
44 | let len = match range.end_bound() {
45 | Bound::Included(bound) => *bound + 1,
46 | Bound::Excluded(bound) => *bound,
47 | Bound::Unbounded => self.len(),
48 | } - start;
49 | self.substring(start, len)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/static/custom-css.scss:
--------------------------------------------------------------------------------
1 | .scrollable {
2 | overflow-y: auto !important;
3 | overflow-x: hidden !important;
4 | height: 100%;
5 | scrollbar-width: thin;
6 | scrollbar-color: $scrollbar-color $scrollbar-bg-color;
7 | }
8 |
9 | .scrollable * {
10 | /* don't allow the children of the scrollable element to be selected as an anchor node */
11 | overflow-anchor: none;
12 | }
13 |
14 | .scrollable::-webkit-scrollbar {
15 | width: 5px;
16 | background: $scrollbar-bg-color;
17 | }
18 |
19 | .scrollable::-webkit-scrollbar-thumb {
20 | background: $scrollbar-color;
21 | }
22 |
23 | #anchor {
24 | /* allow the final child to be selected as an anchor node */
25 | overflow-anchor: auto;
26 |
27 | /* anchor nodes are required to have non-zero area */
28 | height: 1px;
29 | }
30 |
31 | .non-scrollable-container {
32 | overflow: hidden;
33 | }
34 |
35 |
36 | .auto-scrollable-container {
37 | overflow: auto;
38 | }
39 |
40 | .uk-badge.red {
41 | background: #f0506e;
42 | }
43 |
44 | .h-100 {
45 | height: 100% !important;
46 | }
47 |
48 | .menu {
49 | width: 20rem;
50 | padding: 1.5rem;
51 | }
52 |
53 | blockquote {
54 | margin: 0 0 10px 0 !important;
55 | font-size: 16px !important;
56 | line-height: 1.5 !important;
57 | border-left: 4px solid #ddd !important;
58 | padding-left: 2px !important;
59 | font-style: italic !important;
60 | color: var(--global-emphasis-color) !important;
61 | }
62 |
63 | displayname {
64 | font-weight: bold !important;
65 | }
66 |
67 | .uk-active, .uk-nav-default > li > a:hover, .uk-nav-default > li > a:focus, .uk-nav-header, h1 {
68 | font-weight: bold !important;
69 | }
70 |
71 | em {
72 | color: black !important;
73 | }
74 |
75 | .uk-nav-default > li > a {
76 | color: $active-menu !important;
77 | }
78 |
79 | .error {
80 | color: #992E2E;
81 | font-family: 'Roboto', sans-serif;
82 | font-style: normal;
83 | font-weight: 500;
84 | font-size: 16px;
85 | line-height: 56px;
86 | display: flex;
87 | align-items: center;
88 | text-align: center;
89 | letter-spacing: 0.02em;
90 | font-feature-settings: 'ordn' on;
91 | margin: 0;
92 | }
93 |
94 | .uk-search, .uk-inline {
95 | position: relative;
96 | }
97 |
98 | .uk-search input, .uk-inline input {
99 | text-indent: 30px;
100 | }
101 |
102 | .uk-search > #ma-icon, .uk-inline > #ma-icon {
103 | position: absolute;
104 | top: 12px;
105 | left: 13px;
106 | font-size: 18px;
107 | }
108 |
109 | ul {
110 | list-style: none;
111 | }
112 |
113 | ul > li > a:hover {
114 | text-decoration: none !important;
115 | }
116 |
117 | ul > li > a {
118 | padding: 5px 0;
119 | }
120 |
121 | .uk-nav-header {
122 | padding: 5px 0;
123 | text-transform: uppercase;
124 | font-size: .875rem;
125 | }
126 |
127 | ul > li > a {
128 | display: block;
129 | text-decoration: none;
130 | }
131 |
132 | li {
133 | display: list-item;
134 | text-align: -webkit-match-parent;
135 | }
136 |
137 |
138 | @import "sun-loading-animation.scss";
139 | @import "lightbox.scss";
140 |
141 | .daydream-title {
142 | //position: relative;
143 | //margin-left: calc(50% - 295px / 2 - 0.5px);
144 | margin: 0 auto 1rem;
145 |
146 | width: 18.875rem;
147 | }
148 |
149 | .login-page-bg {
150 | transform: scale(1.03);
151 | position: absolute;
152 | top: calc(50% - 100vh / 2);
153 | left: calc(50% - 100vw / 2);
154 | width: 100vw;
155 | height: 100vh;
156 |
157 | background: url(images/login_bg.webp) no-repeat center center fixed;
158 | -webkit-background-size: cover;
159 | -moz-background-size: cover;
160 | -o-background-size: cover;
161 | background-size: cover;
162 | filter: blur(5px);
163 | }
164 |
165 | /* Safari 7.1+ */
166 |
167 | _::-webkit-full-page-media, _:future, :root .login-page-bg {
168 | background: url(images/login_bg.jpg) no-repeat center center fixed;
169 | }
170 |
171 | .login-bg {
172 | position: relative;
173 | width: 481px;
174 | margin: 48px auto 0;
175 | height: calc(100% - 48px);
176 | box-sizing: border-box;
177 |
178 | /* 4dp — Elevation */
179 | box-shadow: 0 1px 10px rgba(0, 0, 0, 0.2), 0 4px 5px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.14);
180 | border-radius: 25px;
181 |
182 | padding: 48px 56px 48px 56px;
183 | }
184 |
185 | .login-bg::before {
186 | content: "";
187 | display: block;
188 | height: 100%;
189 | position: absolute;
190 | top: 0;
191 | left: 0;
192 | width: 100%;
193 | background: #1A1A1A;
194 | opacity: 0.5;
195 | mix-blend-mode: multiply;
196 | border-radius: 25px;
197 | }
198 |
199 | .login-content {
200 | position: relative;
201 | }
202 |
203 | .login-title {
204 | font-family: 'Roboto', sans-serif;
205 | font-style: normal;
206 | font-weight: bold;
207 | font-size: 48px;
208 | line-height: 56px;
209 | display: flex;
210 | align-items: center;
211 | text-align: center;
212 | letter-spacing: 0.02em;
213 | font-feature-settings: 'ordn' on;
214 |
215 | color: #FFFFFF;
216 |
217 | /* 6dp — Elevation */
218 |
219 | text-shadow: 0 3px 5px rgba(0, 0, 0, 0.2), 0 1px 18px rgba(0, 0, 0, 0.12), 0 6px 10px rgba(0, 0, 0, 0.14);
220 | }
221 |
222 | .login-button {
223 | border: none;
224 | width: 100%;
225 | height: 61px;
226 | margin-top: 56px;
227 |
228 | background: #6E992E;
229 | /* 4dp — Elevation */
230 |
231 | box-shadow: 0 1px 10px rgba(0, 0, 0, 0.2), 0 4px 5px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.14);
232 | transition: all 0.3s ease 0s;
233 | border-radius: 45px;
234 |
235 | font-family: 'Roboto', sans-serif;
236 | font-style: normal;
237 | font-weight: normal;
238 | font-size: 24px;
239 | line-height: 24px;
240 |
241 | text-align: center;
242 | letter-spacing: 0.15px;
243 |
244 | /* Background */
245 |
246 | color: #FFFFFF;
247 | }
248 |
249 | .login-button:hover {
250 | box-shadow: 0 15px 20px rgba(0, 0, 0, 0.4);
251 | transform: translateY(-7px);
252 | }
253 |
254 | .login-input-first > input.login-input {
255 | margin-top: 0 !important;
256 | }
257 |
258 | .login-input-first > .login-icons {
259 | bottom: calc(50% - 6px * 2);
260 | }
261 |
262 | input.login-input {
263 | //height: 48px;
264 | width: 100%;
265 | background: rgba(0, 0, 0, 0.65);
266 | border-radius: 15px 15px 0 0;
267 | border: none;
268 | padding-left: 16px;
269 | padding-top: 12px;
270 | padding-bottom: 12px;
271 |
272 | border-bottom: 2px solid #FFFFFF;
273 | box-sizing: border-box;
274 | margin-top: 48px;
275 |
276 | /* Subtitle 1 / Roboto Regular */
277 |
278 | font-family: 'Roboto', sans-serif;
279 | font-style: normal;
280 | font-weight: normal;
281 | font-size: 16px;
282 | line-height: 24px;
283 |
284 | /* Gray 4 */
285 |
286 | color: #BDBDBD;
287 | }
288 |
289 | input:placeholder-shown {
290 | border: none;
291 | border-bottom: 2px solid #FFFFFF;
292 | }
293 |
294 | input.login-input:invalid {
295 | border-bottom: 2px solid #992E2E;
296 | }
297 |
298 | .login-icons {
299 | font-size: 24px !important;
300 | color: white;
301 | position: absolute;
302 | bottom: calc(50% - 19px * 2);
303 | left: 16px;
304 | }
305 |
306 | .login-inline {
307 | position: relative;
308 | }
309 |
310 | .login-inline > input {
311 | padding-left: 64px;
312 | }
313 |
314 | html {
315 | overflow: hidden;
316 | height: 100vh;
317 | width: 100vw;
318 | }
319 |
320 | body {
321 | overflow: hidden;
322 | height: 100vh;
323 | width: 100vw;
324 | }
325 |
326 | .thumbnail-container {
327 | height: 37.5rem;
328 |
329 | img {
330 | max-height: 100%;
331 | max-width: 100%;
332 | }
333 | }
334 |
335 | .room-title {
336 | width: 100%;
337 | overflow: hidden;
338 | //padding-bottom: 2.5rem;
339 | flex: 0 1 7.875rem;
340 | box-shadow: 0 0 0.0625rem rgba(0, 0, 0, 0.04), 0 0.125rem 0.375rem rgba(0, 0, 0, 0.04), 0 1rem 1.5rem rgba(0, 0, 0, 0.06);
341 | -moz-box-shadow: 0 0 0.0625rem rgba(0, 0, 0, 0.04), 0 0.125rem 0.375rem rgba(0, 0, 0, 0.04), 0 1rem 1.5rem rgba(0, 0, 0, 0.06);
342 | -webkit-box-shadow: 0 0 0.0625rem rgba(0, 0, 0, 0.04), 0 0.125rem 0.375rem rgba(0, 0, 0, 0.04), 0 1rem 1.5rem rgba(0, 0, 0, 0.06);
343 | margin-bottom: 0.3125rem;
344 |
345 | div {
346 | width: 100%;
347 | height: 100%;
348 | padding-left: 9.375rem;
349 | padding-top: 1.1875rem;
350 |
351 | h1 {
352 | font-family: Roboto, sans-serif;
353 | font-style: normal;
354 | font-weight: bold;
355 | font-size: 2.125rem;
356 | line-height: 2.5rem;
357 | letter-spacing: 0.015625rem;
358 | }
359 | }
360 | }
361 |
362 | .message-input {
363 | flex: 0 1 4.375rem;
364 | display: flex;
365 | flex-direction: row;
366 |
367 | border-top: 2px solid #4D4D4D;
368 |
369 | .encryption-bg {
370 | background: #EB9C00;
371 | width: 42px;
372 | height: 42px;
373 | margin: 14px 67px 14px 48px;
374 |
375 | box-shadow: 0 0 1px rgba(0, 0, 0, 0.04), 0 2px 6px rgba(0, 0, 0, 0.04), 0 10px 20px rgba(0, 0, 0, 0.04);
376 |
377 | border-radius: 50%;
378 |
379 | span {
380 | margin: 9px;
381 | }
382 | }
383 |
384 | textarea {
385 | flex-grow: 1;
386 | //background-color: #f5f5f5;
387 | // Reset
388 | border: 0;
389 |
390 | &:focus {
391 | outline: 0;
392 | }
393 |
394 |
395 | }
396 | }
397 |
398 | textarea::-webkit-input-placeholder {
399 | padding-top: 20px;
400 | }
401 |
402 | textarea::-moz-input-placeholder {
403 | padding-top: 20px;
404 | }
405 |
406 | textarea:-moz-input-placeholder {
407 | padding-top: 20px;
408 | }
409 |
410 | textarea:-ms-input-placeholder {
411 | padding-top: 20px;
412 | }
413 |
414 |
415 | .message-scrollarea {
416 | flex: 1 1 auto;
417 | }
418 |
419 | .event-list {
420 | height: 100%;
421 | display: flex;
422 | flex-direction: column;
423 | width: 100%;
424 | }
425 |
426 | .message-container {
427 | width: 100%;
428 | max-width: 67.0625rem;
429 |
430 | margin: 0 auto;
431 | }
432 |
433 |
434 | .roomlist {
435 | width: 100%;
436 | max-width: 31.5rem;
437 | display: flex;
438 | flex-direction: column;
439 | position: relative;
440 | z-index: 0;
441 |
442 | .list {
443 | width: 100%;
444 | flex-grow: 1;
445 | height: auto !important;
446 | z-index: -1;
447 | border-right: 1px solid #CCCCCC;
448 | }
449 |
450 | .top-bar {
451 | width: 100%;
452 |
453 | height: 138px;
454 |
455 | .userdata {
456 | width: 100%;
457 | height: 5.875rem;
458 |
459 | background-color: $accent-color;
460 |
461 | padding-left: 1.5625rem;
462 | padding-right: 1.1875rem;
463 | -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
464 | -moz-box-sizing: border-box; /* Firefox, other Gecko */
465 | box-sizing: border-box; /* Opera/IE 8+ */
466 | }
467 |
468 | .search {
469 | width: 100%;
470 |
471 | div {
472 | height: 2.75rem;
473 |
474 | display: flex;
475 | flex-direction: row;
476 | align-items: center;
477 |
478 | padding-left: 1.5625rem;
479 | padding-right: 1.1875rem;
480 | -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
481 | -moz-box-sizing: border-box; /* Firefox, other Gecko */
482 | box-sizing: border-box; /* Opera/IE 8+ */
483 | z-index: 1;
484 |
485 | width: 100%;
486 | box-shadow: 0 0 0.0625rem rgba(0, 0, 0, 0.04), 0 0.125rem 0.375rem rgba(0, 0, 0, 0.04), 0 1rem 1.5rem rgba(0, 0, 0, 0.06);
487 | -moz-box-shadow: 0 0 0.0625rem rgba(0, 0, 0, 0.04), 0 0.125rem 0.375rem rgba(0, 0, 0, 0.04), 0 1rem 1.5rem rgba(0, 0, 0, 0.06);
488 | -webkit-box-shadow: 0 0 0.0625rem rgba(0, 0, 0, 0.04), 0 0.125rem 0.375rem rgba(0, 0, 0, 0.04), 0 1rem 1.5rem rgba(0, 0, 0, 0.06);
489 |
490 | .material-icons {
491 | margin-top: 0.625rem;
492 | margin-bottom: 0.625rem;
493 | }
494 |
495 | .search-input {
496 | /*
497 | * 1. Define consistent box sizing.
498 | * 3. Remove `border-radius` in iOS.
499 | * 4. Change font properties to `inherit` in all browsers
500 | * 5. Show the overflow in Edge.
501 | * 6. Remove default style in iOS.
502 | * 7. Vertical alignment
503 | * 8. Take the full container width
504 | * 9. Style
505 | */
506 | /* 1 */
507 | box-sizing: border-box;
508 | /* 3 */
509 | border-radius: 0;
510 | /* 4 */
511 | font: inherit;
512 | /* 5 */
513 | overflow: visible;
514 | /* 6 */
515 | -webkit-appearance: none;
516 | /* 7 */
517 | vertical-align: middle;
518 | /* 8 */
519 | width: 100%;
520 | /* 9 */
521 | border: none;
522 |
523 |
524 | margin-top: 0.625rem;
525 | margin-bottom: 0.625rem;
526 | margin-left: 3.206875rem;
527 |
528 | /* H6 / Roboto Medium */
529 |
530 | font-family: 'Roboto', sans-serif;
531 | font-style: normal;
532 | font-weight: 500;
533 | font-size: 20px;
534 | line-height: 23px;
535 | display: flex;
536 | align-items: center;
537 | letter-spacing: 0.15px;
538 |
539 | color: $search-grey;
540 |
541 | &:focus {
542 | // Chrome override
543 | outline: none 0;
544 | }
545 |
546 |
547 | /*
548 | * Remove the inner padding and cancel buttons in Chrome on OS X and Safari on OS X.
549 | */
550 |
551 | &::-webkit-search-cancel-button,
552 | &::-webkit-search-decoration {
553 | -webkit-appearance: none;
554 | }
555 |
556 | /*
557 | * Removes placeholder transparency in Firefox.
558 | */
559 |
560 | &::-moz-placeholder {
561 | opacity: 1;
562 | }
563 |
564 | }
565 | }
566 | }
567 |
568 | }
569 |
570 | .bottom-bar {
571 | height: 4.375rem;
572 | width: 100%;
573 | position: relative;
574 | border-top: 2px solid #4D4D4D;
575 |
576 | .toggleWrapper {
577 | height: 100%;
578 | // Following later becomes button stuff
579 |
580 | div {
581 | position: relative;
582 | left: 40%;
583 | top: 5%;
584 | }
585 | }
586 | }
587 | }
588 |
589 | // TODO make room-list-item responsive
590 | .room-list-item {
591 | &:hover {
592 | background-color: #d9d9d9;
593 | }
594 |
595 | a {
596 | &:hover {
597 | text-decoration: none !important;
598 | }
599 |
600 | background-color: #F2F2F2;
601 | height: 5rem;
602 |
603 | .content {
604 | margin-left: 35px;
605 | height: 5rem;
606 | display: flex;
607 | flex-direction: row;
608 | width: 100%;
609 | max-width: 28.8125rem;
610 |
611 | img.avatar {
612 | border-radius: 50%;
613 | box-shadow: 0 0 1px rgba(0, 0, 0, 0.04), 0 2px 6px rgba(0, 0, 0, 0.04), 0 10px 20px rgba(0, 0, 0, 0.04);
614 | }
615 |
616 | .avatar {
617 | margin-right: 28px;
618 | width: 48px;
619 | height: 48px;
620 | box-sizing: border-box;
621 | margin-bottom: 16px;
622 | margin-top: 16px;
623 | }
624 |
625 | div {
626 | margin-bottom: 3px;
627 |
628 | .name {
629 | /* RoomList Names */
630 |
631 | font-family: Roboto, sans-serif;
632 | font-style: normal;
633 | font-weight: 500;
634 | font-size: 18px;
635 | line-height: 23px;
636 | margin: 0 !important;
637 | margin-top: 18px !important;
638 | text-overflow: ellipsis;
639 | overflow: hidden;
640 | white-space: nowrap;
641 |
642 | /* Black */
643 |
644 | color: #000000;
645 |
646 | width: 100%;
647 | max-width: 22rem;
648 | }
649 |
650 | p.latest-msg {
651 | font-family: Roboto, sans-serif;
652 | font-style: normal;
653 | font-weight: normal;
654 | font-size: 14px;
655 | line-height: 28px;
656 | text-overflow: ellipsis;
657 | overflow: hidden;
658 | white-space: nowrap;
659 |
660 | letter-spacing: 0.5px;
661 | margin: 0 !important;
662 |
663 | /* Search Grey */
664 | color: #737373;
665 |
666 | width: 100%;
667 | max-width: 22rem;
668 | display: inline-block;
669 | }
670 | }
671 | }
672 |
673 | }
674 | }
675 |
676 |
--------------------------------------------------------------------------------
/static/dark_theme/_globals.scss:
--------------------------------------------------------------------------------
1 | [data-theme="dark"] {
2 | --global-background: #1c1c21;
3 | --global-scrollbar-color: hsla(0,0%,100%,.2);
4 | --global-scrollbar-bg-color: transparent;
5 | --global-accent-color: #732222;
6 | --global-emphasis-color: #fff;
7 | --global-color: #fff;
8 | --icon-link-active-color: darken(#fff, 5%);
9 | --active-menu: #dddddd;
10 | --search-grey: #737373;
11 | }
12 |
--------------------------------------------------------------------------------
/static/dark_theme/_variables.scss:
--------------------------------------------------------------------------------
1 | @at-root {@import "./globals.scss";}
2 |
3 | .uk-input, .uk-search-input {
4 | border: 1px solid $global-emphasis-color;
5 | }
6 |
--------------------------------------------------------------------------------
/static/images/login_bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daydream-mx/Daydream/85c2cf395906e944174eb8e7acac1afe6426fefa/static/images/login_bg.jpg
--------------------------------------------------------------------------------
/static/images/login_bg.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daydream-mx/Daydream/85c2cf395906e944174eb8e7acac1afe6426fefa/static/images/login_bg.webp
--------------------------------------------------------------------------------
/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Daydream
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/static/index_rust_only.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Daydream
14 |
15 |
16 |
17 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/static/light_theme/_globals.scss:
--------------------------------------------------------------------------------
1 | [data-theme="light"] {
2 | --global-scrollbar-color: black;
3 | --global-scrollbar-bg-color: transparent;
4 | --global-accent-color: #732222;
5 | --global-emphasis-color: #333;
6 | --global-color: #333333;
7 | --icon-link-active-color: darken(#666, 5%);
8 | --active-menu: #666666;
9 | --search-grey: #737373;
10 | }
11 |
--------------------------------------------------------------------------------
/static/light_theme/_variables.scss:
--------------------------------------------------------------------------------
1 | @at-root {@import "./globals.scss";}
2 |
--------------------------------------------------------------------------------
/static/lightbox.scss:
--------------------------------------------------------------------------------
1 | .short-animate {
2 | -webkit-transition:.5s ease-in-out;
3 | -moz-transition:.5s ease-in-out;
4 | -ms-transition:.5s ease-in-out;
5 | -o-transition:.5s ease-in-out;
6 | transition:.5s ease-in-out;
7 | }
8 |
9 | .long-animate {
10 | -webkit-transition: .5s .5s ease-in-out;
11 | -moz-transition: .5s .5s ease-in-out;
12 | -ms-transition: .5s .5s ease-in-out;
13 | -o-transition:.5s .5s ease-in-out;
14 | transition:.5s .5s ease-in-out;
15 | }
16 |
17 | .lightbox {
18 | position:fixed;
19 | top:-100%;
20 | bottom:100%;
21 | left:0;
22 | right:0;
23 | background: rgba(161, 161, 161, 0.8);
24 | z-index:501;
25 | opacity:0;
26 | }
27 |
28 | .lightbox img {
29 | position:absolute;
30 | margin:auto;
31 | top:0;
32 | left:0;
33 | right:0;
34 | bottom:0;
35 | max-width:0%;
36 | max-height:0%;
37 | }
38 |
39 |
40 | .lightbox video {
41 | position:absolute;
42 | margin:auto;
43 | top:0;
44 | left:0;
45 | right:0;
46 | bottom:0;
47 | max-width:0%;
48 | max-height:0%;
49 | }
50 |
51 | #lightbox-controls {
52 | position:fixed;
53 | height:70px;
54 | width:70px;
55 | top:-70px;
56 | right:0;
57 | z-index:502;
58 | background:rgba(0,0,0,.1);
59 | }
60 |
61 | #close-lightbox {
62 | display:block;
63 | position:absolute;
64 | overflow:hidden;
65 | height:50px;
66 | width:50px;
67 | text-indent:-5000px;
68 | right:10px;
69 | top:10px;
70 | -webkit-transform:rotate(45deg);
71 | -moz-transform:rotate(45deg);
72 | -ms-transform:rotate(45deg);
73 | -o-transform:rotate(45deg);
74 | transform:rotate(45deg);
75 | }
76 |
77 | #close-lightbox:before {
78 | content:'';
79 | display:block;
80 | position:absolute;
81 | height:0px;
82 | width:3px;
83 | left:24px;
84 | top:0;
85 | background:white;
86 | border-radius:2px;
87 | -webkit-transition: .5s .5s ease-in-out;
88 | -moz-transition: .5s .5s ease-in-out;
89 | -ms-transition: .5s .5s ease-in-out;
90 | -o-transition:.5s .5s ease-in-out;
91 | transition:.5s .5s ease-in-out;
92 | }
93 |
94 | #close-lightbox:after {
95 | content:'';
96 | display:block;
97 | position:absolute;
98 | width:0px;
99 | height:3px;
100 | top:24px;
101 | left:0;
102 | background:white;
103 | border-radius:2px;
104 | -webkit-transition: .5s 1s ease-in-out;
105 | -moz-transition: .5s 1s ease-in-out;
106 | -ms-transition: .5s 1s ease-in-out;
107 | -o-transition:.5s 1s ease-in-out;
108 | transition:.5s 1s ease-in-out;
109 | }
110 |
111 | .lightbox:target {
112 | top:0%;
113 | bottom:0%;
114 | opacity:1;
115 | }
116 |
117 | .lightbox:target img {
118 | max-width:100%;
119 | max-height:100%;
120 | }
121 | .lightbox:target video {
122 | max-width:100%;
123 | max-height:100%;
124 | }
125 |
126 | .lightbox:target ~ #lightbox-controls {
127 | top:0px;
128 | }
129 |
130 | .lightbox:target ~ #lightbox-controls #close-lightbox:after {
131 | width:50px;
132 | }
133 |
134 | .lightbox:target ~ #lightbox-controls #close-lightbox:before {
135 | height:50px;
136 | }
137 |
--------------------------------------------------------------------------------
/static/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /* Document
4 | ========================================================================== */
5 |
6 | /**
7 | * 1. Correct the line height in all browsers.
8 | * 2. Prevent adjustments of font size after orientation changes in iOS.
9 | */
10 |
11 | html {
12 | line-height: 1.15; /* 1 */
13 | -webkit-text-size-adjust: 100%; /* 2 */
14 | }
15 |
16 | /* Sections
17 | ========================================================================== */
18 |
19 | /**
20 | * Remove the margin in all browsers.
21 | */
22 |
23 | body {
24 | margin: 0;
25 | }
26 |
27 | /**
28 | * Render the `main` element consistently in IE.
29 | */
30 |
31 | main {
32 | display: block;
33 | }
34 |
35 | /**
36 | * Correct the font size and margin on `h1` elements within `section` and
37 | * `article` contexts in Chrome, Firefox, and Safari.
38 | */
39 |
40 | h1 {
41 | font-size: 2em;
42 | margin: 0.67em 0;
43 | }
44 |
45 | /* Grouping content
46 | ========================================================================== */
47 |
48 | /**
49 | * 1. Add the correct box sizing in Firefox.
50 | * 2. Show the overflow in Edge and IE.
51 | */
52 |
53 | hr {
54 | box-sizing: content-box; /* 1 */
55 | height: 0; /* 1 */
56 | overflow: visible; /* 2 */
57 | }
58 |
59 | /**
60 | * 1. Correct the inheritance and scaling of font size in all browsers.
61 | * 2. Correct the odd `em` font sizing in all browsers.
62 | */
63 |
64 | pre {
65 | font-family: monospace, monospace; /* 1 */
66 | font-size: 1em; /* 2 */
67 | }
68 |
69 | /* Text-level semantics
70 | ========================================================================== */
71 |
72 | /**
73 | * Remove the gray background on active links in IE 10.
74 | */
75 |
76 | a {
77 | background-color: transparent;
78 | }
79 |
80 | /**
81 | * 1. Remove the bottom border in Chrome 57-
82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
83 | */
84 |
85 | abbr[title] {
86 | border-bottom: none; /* 1 */
87 | text-decoration: underline; /* 2 */
88 | text-decoration: underline dotted; /* 2 */
89 | }
90 |
91 | /**
92 | * Add the correct font weight in Chrome, Edge, and Safari.
93 | */
94 |
95 | b,
96 | strong {
97 | font-weight: bolder;
98 | }
99 |
100 | /**
101 | * 1. Correct the inheritance and scaling of font size in all browsers.
102 | * 2. Correct the odd `em` font sizing in all browsers.
103 | */
104 |
105 | code,
106 | kbd,
107 | samp {
108 | font-family: monospace, monospace; /* 1 */
109 | font-size: 1em; /* 2 */
110 | }
111 |
112 | /**
113 | * Add the correct font size in all browsers.
114 | */
115 |
116 | small {
117 | font-size: 80%;
118 | }
119 |
120 | /**
121 | * Prevent `sub` and `sup` elements from affecting the line height in
122 | * all browsers.
123 | */
124 |
125 | sub,
126 | sup {
127 | font-size: 75%;
128 | line-height: 0;
129 | position: relative;
130 | vertical-align: baseline;
131 | }
132 |
133 | sub {
134 | bottom: -0.25em;
135 | }
136 |
137 | sup {
138 | top: -0.5em;
139 | }
140 |
141 | /* Embedded content
142 | ========================================================================== */
143 |
144 | /**
145 | * Remove the border on images inside links in IE 10.
146 | */
147 |
148 | img {
149 | border-style: none;
150 | }
151 |
152 | /* Forms
153 | ========================================================================== */
154 |
155 | /**
156 | * 1. Change the font styles in all browsers.
157 | * 2. Remove the margin in Firefox and Safari.
158 | */
159 |
160 | button,
161 | input,
162 | optgroup,
163 | select,
164 | textarea {
165 | font-family: inherit; /* 1 */
166 | font-size: 100%; /* 1 */
167 | line-height: 1.15; /* 1 */
168 | margin: 0; /* 2 */
169 | }
170 |
171 | /**
172 | * Show the overflow in IE.
173 | * 1. Show the overflow in Edge.
174 | */
175 |
176 | button,
177 | input { /* 1 */
178 | overflow: visible;
179 | }
180 |
181 | /**
182 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
183 | * 1. Remove the inheritance of text transform in Firefox.
184 | */
185 |
186 | button,
187 | select { /* 1 */
188 | text-transform: none;
189 | }
190 |
191 | /**
192 | * Correct the inability to style clickable types in iOS and Safari.
193 | */
194 |
195 | button,
196 | [type="button"],
197 | [type="reset"],
198 | [type="submit"] {
199 | -webkit-appearance: button;
200 | }
201 |
202 | /**
203 | * Remove the inner border and padding in Firefox.
204 | */
205 |
206 | button::-moz-focus-inner,
207 | [type="button"]::-moz-focus-inner,
208 | [type="reset"]::-moz-focus-inner,
209 | [type="submit"]::-moz-focus-inner {
210 | border-style: none;
211 | padding: 0;
212 | }
213 |
214 | /**
215 | * Restore the focus styles unset by the previous rule.
216 | */
217 |
218 | button:-moz-focusring,
219 | [type="button"]:-moz-focusring,
220 | [type="reset"]:-moz-focusring,
221 | [type="submit"]:-moz-focusring {
222 | outline: 1px dotted ButtonText;
223 | }
224 |
225 | /**
226 | * Correct the padding in Firefox.
227 | */
228 |
229 | fieldset {
230 | padding: 0.35em 0.75em 0.625em;
231 | }
232 |
233 | /**
234 | * 1. Correct the text wrapping in Edge and IE.
235 | * 2. Correct the color inheritance from `fieldset` elements in IE.
236 | * 3. Remove the padding so developers are not caught out when they zero out
237 | * `fieldset` elements in all browsers.
238 | */
239 |
240 | legend {
241 | box-sizing: border-box; /* 1 */
242 | color: inherit; /* 2 */
243 | display: table; /* 1 */
244 | max-width: 100%; /* 1 */
245 | padding: 0; /* 3 */
246 | white-space: normal; /* 1 */
247 | }
248 |
249 | /**
250 | * Add the correct vertical alignment in Chrome, Firefox, and Opera.
251 | */
252 |
253 | progress {
254 | vertical-align: baseline;
255 | }
256 |
257 | /**
258 | * Remove the default vertical scrollbar in IE 10+.
259 | */
260 |
261 | textarea {
262 | overflow: auto;
263 | }
264 |
265 | /**
266 | * 1. Add the correct box sizing in IE 10.
267 | * 2. Remove the padding in IE 10.
268 | */
269 |
270 | [type="checkbox"],
271 | [type="radio"] {
272 | box-sizing: border-box; /* 1 */
273 | padding: 0; /* 2 */
274 | }
275 |
276 | /**
277 | * Correct the cursor style of increment and decrement buttons in Chrome.
278 | */
279 |
280 | [type="number"]::-webkit-inner-spin-button,
281 | [type="number"]::-webkit-outer-spin-button {
282 | height: auto;
283 | }
284 |
285 | /**
286 | * 1. Correct the odd appearance in Chrome and Safari.
287 | * 2. Correct the outline style in Safari.
288 | */
289 |
290 | [type="search"] {
291 | -webkit-appearance: textfield; /* 1 */
292 | outline-offset: -2px; /* 2 */
293 | }
294 |
295 | /**
296 | * Remove the inner padding in Chrome and Safari on macOS.
297 | */
298 |
299 | [type="search"]::-webkit-search-decoration {
300 | -webkit-appearance: none;
301 | }
302 |
303 | /**
304 | * 1. Correct the inability to style clickable types in iOS and Safari.
305 | * 2. Change font properties to `inherit` in Safari.
306 | */
307 |
308 | ::-webkit-file-upload-button {
309 | -webkit-appearance: button; /* 1 */
310 | font: inherit; /* 2 */
311 | }
312 |
313 | /* Interactive
314 | ========================================================================== */
315 |
316 | /*
317 | * Add the correct display in Edge, IE 10+, and Firefox.
318 | */
319 |
320 | details {
321 | display: block;
322 | }
323 |
324 | /*
325 | * Add the correct display in all browsers.
326 | */
327 |
328 | summary {
329 | display: list-item;
330 | }
331 |
332 | /* Misc
333 | ========================================================================== */
334 |
335 | /**
336 | * Add the correct display in IE 10+.
337 | */
338 |
339 | template {
340 | display: none;
341 | }
342 |
343 | /**
344 | * Add the correct display in IE 10.
345 | */
346 |
347 | [hidden] {
348 | display: none;
349 | }
350 |
--------------------------------------------------------------------------------
/static/style.scss:
--------------------------------------------------------------------------------
1 | @import "normalize.css";
2 | @import "theme_slider.scss";
3 |
4 |
5 | @import "./variable-ovewrites.scss";
6 | @import "./custom-css.scss";
7 | @import "../node_modules/uikit/src/scss/mixins-theme.scss";
8 | @import "../node_modules/uikit/src/scss/variables.scss";
9 | @import "../node_modules/uikit/src/scss/uikit-theme.scss";
10 |
11 | [data-theme="dark"] {
12 | @import "dark_theme/variables";
13 |
14 | [data-theme] {
15 | background-color: var(--global-background);
16 | color: white;
17 | }
18 |
19 |
20 | }
21 |
22 | [data-theme="light"] {
23 | @import "light_theme/variables";
24 |
25 | [data-theme] {
26 | background-color: var(--global-background);
27 | color: black;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/static/sun-loading-animation.scss:
--------------------------------------------------------------------------------
1 | .sun-animation {
2 | $c: #f4c042;
3 | $t: 5s;
4 |
5 | svg {
6 | width: 400px;
7 | height: 300px;
8 | }
9 |
10 | svg * {
11 | stroke: $c;
12 | stroke-linecap: round;
13 | vector-effect: non-scaling-stroke;
14 | }
15 |
16 | [id='line'] {
17 | stroke-width: 3;
18 | }
19 |
20 | svg text {
21 | font: .875em century gothic, verdana, sans-serif;
22 | }
23 |
24 | [id='mover'] {
25 | animation: sun-motion $t cubic-bezier(0.175, 0.885, 0.32, 1.275) infinite;
26 | }
27 |
28 | [id='main'] {
29 | fill: transparent;
30 | stroke-width: 7;
31 | }
32 |
33 | [id='eyes'] {
34 | animation: eye-motion $t ease-out infinite;
35 |
36 | circle {
37 | fill: $c;
38 | }
39 | }
40 |
41 | [id='ray'] {
42 | stroke-width: 4;
43 | }
44 |
45 | [id='rays'] {
46 | animation: rot $t linear infinite;
47 | }
48 |
49 | @keyframes rot {
50 | to {
51 | transform: rotate(.25turn);
52 | }
53 | }
54 |
55 | @keyframes eye-motion {
56 | 0%, 20%, 49%, 100% {
57 | transform: translate(-13px);
58 | }
59 | 21%, 25%, 29%, 47% {
60 | transform: translate(13px) scaleY(1);
61 | }
62 | 27% {
63 | transform: translate(13px) scaleY(0);
64 | }
65 | 48% {
66 | transform: translate(0);
67 | }
68 | }
69 |
70 | @keyframes sun-motion {
71 | 0%, 99%, 100% {
72 | transform: translateY(-16px);
73 | }
74 | 50% {
75 | transform: translateY(-29px);
76 | }
77 | 52%, 98% {
78 | transform: translate(4px) scaleY(1.25);
79 | }
80 | 53%, 97% {
81 | transform: translateY(23px);
82 | }
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/static/theme_slider.scss:
--------------------------------------------------------------------------------
1 | .toggleWrapper {
2 |
3 | div {
4 | z-index: -100;
5 | display: inline-block;
6 | }
7 | input {
8 | position: absolute;
9 | left: -99em;
10 | margin-bottom: 5px;
11 | }
12 | }
13 |
14 | .toggle {
15 | cursor: pointer;
16 | display: inline-block;
17 | position: relative;
18 | width: 90px;
19 | height: 40px;
20 | background-color: #83D8FF;
21 | border-radius: 90px - 6;
22 | transition: background-color 200ms cubic-bezier(0.445, 0.05, 0.55, 0.95);
23 |
24 | &:before {
25 | content: 'Light';
26 | position: absolute;
27 | left: -50px;
28 | top: 8px;
29 | font-size: 18px;
30 | color: #749ED7;
31 | }
32 |
33 | &:after {
34 | content: 'Dark';
35 | position: absolute;
36 | right: -48px;
37 | top: 8px;
38 | font-size: 18px;
39 | color: #434B59;
40 | }
41 | }
42 |
43 | .toggle__handler {
44 | display: inline-block;
45 | position: relative;
46 | z-index: 1;
47 | top: 3px;
48 | left: 3px;
49 | width: 40px - 6;
50 | height: 40px - 6;
51 | background-color: #FFCF96;
52 | border-radius: 40px;
53 | box-shadow: 0 2px 6px rgba(0,0,0,.3);
54 | transition: all 400ms cubic-bezier(0.68, -0.55, 0.265, 1.55);
55 | //transform: rotate(-45deg);
56 |
57 | .crater {
58 | position: absolute;
59 | background-color: #E8CDA5;
60 | opacity: 0;
61 | transition: opacity 200ms ease-in-out;
62 | border-radius: 100%;
63 | }
64 |
65 | .crater--1 {
66 | top: 18px;
67 | left: 10px;
68 | width: 4px;
69 | height: 4px;
70 | }
71 |
72 | .crater--2 {
73 | top: 23px;
74 | left: 17px;
75 | width: 6px;
76 | height: 6px;
77 | }
78 |
79 | .crater--3 {
80 | top: 10px;
81 | left: 22px;
82 | width: 8px;
83 | height: 8px;
84 | }
85 | }
86 |
87 | .star {
88 | position: absolute;
89 | background-color: #ffffff;
90 | transition: all 300ms cubic-bezier(0.445, 0.05, 0.55, 0.95);
91 | border-radius: 50%;
92 | }
93 |
94 | .star--1 {
95 | top: 10px;
96 | left: 20px;
97 | z-index: 0;
98 | width: 30px;
99 | height: 3px;
100 | }
101 |
102 | .star--2 {
103 | top: 18px;
104 | left: 21px;
105 | z-index: 1;
106 | width: 30px;
107 | height: 3px;
108 | }
109 |
110 | .star--3 {
111 | top: 27px;
112 | left: 25px;
113 | z-index: 0;
114 | width: 30px;
115 | height: 3px;
116 | }
117 |
118 | .star--4,
119 | .star--5,
120 | .star--6 {
121 | opacity: 0;
122 | transition: all 300ms 0 cubic-bezier(0.445, 0.05, 0.55, 0.95);
123 | }
124 |
125 | .star--4 {
126 | top: 16px;
127 | left: 11px;
128 | z-index: 0;
129 | width: 2px;
130 | height: 2px;
131 | transform: translate3d(3px,0,0);
132 | }
133 |
134 | .star--5 {
135 | top: 32px;
136 | left: 17px;
137 | z-index: 0;
138 | width: 3px;
139 | height: 3px;
140 | transform: translate3d(3px,0,0);
141 | }
142 |
143 | .star--6 {
144 | top: 36px;
145 | left: 28px;
146 | z-index: 0;
147 | width: 2px;
148 | height: 2px;
149 | transform: translate3d(3px,0,0);
150 | }
151 |
152 | input:checked {
153 | + .toggle {
154 | background-color: #749DD6;
155 |
156 | &:before {
157 | color: #626C80;
158 | }
159 |
160 | &:after {
161 | color: #749ED7;
162 | }
163 |
164 | .toggle__handler {
165 | background-color: #FFE5B5;
166 | transform: translate3d(50px, 0, 0) rotate(0);
167 |
168 | .crater { opacity: 1; }
169 | }
170 |
171 | .star--1 {
172 | width: 2px;
173 | height: 2px;
174 | left: 35px;
175 | }
176 |
177 | .star--2 {
178 | width: 4px;
179 | height: 4px;
180 | left: 28px;
181 | transform: translate3d(-5px, 0, 0);
182 | }
183 |
184 | .star--3 {
185 | width: 2px;
186 | height: 2px;
187 | left: 40px;
188 | transform: translate3d(-7px, 0, 0);
189 | }
190 |
191 | .star--4,
192 | .star--5,
193 | .star--6 {
194 | opacity: 1;
195 | transform: translate3d(0,0,0);
196 | }
197 | .star--4 {
198 | transition: all 300ms 200ms cubic-bezier(0.445, 0.05, 0.55, 0.95);
199 | }
200 | .star--5 {
201 | transition: all 300ms 300ms cubic-bezier(0.445, 0.05, 0.55, 0.95);
202 | }
203 | .star--6 {
204 | transition: all 300ms 400ms cubic-bezier(0.445, 0.05, 0.55, 0.95);
205 | }
206 | }
207 | }
208 |
209 |
--------------------------------------------------------------------------------
/static/variable-ovewrites.scss:
--------------------------------------------------------------------------------
1 | // Custom component colors
2 | $scrollbar-color: var(--global-scrollbar-color);
3 | $scrollbar-bg-color: var(--global-scrollbar-bg-color);
4 | $accent-color: var(--global-accent-color);
5 | $search-grey: var(--search-grey);
6 |
7 | // UIKit Colors
8 | $global-background: var(--global-background);
9 | $global-emphasis-color: var(--global-emphasis-color);
10 | $global-color: var(--global-color);
11 | $icon-link-active-color: var(--icon-link-active-color);
12 | $active-menu: var(--active-menu);
13 |
14 |
15 | @import "../node_modules/uikit/src/scss/variables-theme.scss";
16 |
--------------------------------------------------------------------------------
/webdriver.json:
--------------------------------------------------------------------------------
1 | {
2 | "moz:firefoxOptions": {
3 | "prefs": {
4 | "media.navigator.streams.fake": true,
5 | "media.navigator.permission.disabled": true
6 | },
7 | "args": []
8 | },
9 | "goog:chromeOptions": {
10 | "args": [
11 | "--use-fake-device-for-media-stream",
12 | "--use-fake-ui-for-media-stream"
13 | ]
14 | }
15 | }
16 |
--------------------------------------------------------------------------------