├── .buildkite
└── pipeline.yml
├── .eslintrc.yml
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── LICENSE
├── README.md
├── babel.config.js
├── config.sample.json
├── docs
└── slide-theory.md
├── package.json
├── public
└── index.html
├── src
├── App.vue
├── assets
│ └── logo.png
├── components
│ ├── ActionButton.vue
│ ├── Editor
│ │ └── SlideRoomEditor.vue
│ ├── Forms
│ │ └── InputGroup.vue
│ ├── Login.vue
│ ├── Modal.vue
│ ├── Nav.vue
│ ├── QRCode.vue
│ ├── SettingsModal.vue
│ ├── SettingsModalTabs
│ │ ├── GeneralTab.vue
│ │ └── KeymappingTab.vue
│ ├── Slide.vue
│ ├── SlideCard.vue
│ ├── SlideFragment.vue
│ ├── SlideList.vue
│ ├── SlideRoom.vue
│ ├── Slides
│ │ ├── ReactionButton.vue
│ │ ├── ReactionViewer.vue
│ │ └── SlideTools.vue
│ ├── SubscribeModal.vue
│ ├── Sync.vue
│ └── TableTennis.vue
├── main.scss
├── main.ts
├── matrix-js-sdk.d.ts
├── models
│ ├── PositionEvent.ts
│ └── SlidesEvent.ts
├── pages
│ ├── CreateSlideshow.vue
│ ├── Home.vue
│ ├── Login.vue
│ └── Slides.vue
├── router
│ └── index.ts
├── shims-tsx.d.ts
├── shims-vue.d.ts
├── util
│ ├── eventStore.ts
│ ├── matrix.ts
│ └── store.ts
└── vue-feather-icons.d.ts
├── tsconfig.json
├── tslint.json
├── vue.config.js
└── yarn.lock
/.buildkite/pipeline.yml:
--------------------------------------------------------------------------------
1 | steps:
2 | - label: ':hammer: Build'
3 | command:
4 | - "yarn install"
5 | # Fix permission issues
6 | - "chmod -R a+rwx node_modules"
7 | - "yarn build"
8 | plugins:
9 | - docker#v3.5.0:
10 | image: "node:12"
11 |
12 | - label: ':eslint: Linting'
13 | command:
14 | - "yarn lint"
15 | plugins:
16 | - docker#v3.5.0:
17 | image: "node:12"
18 |
19 |
--------------------------------------------------------------------------------
/.eslintrc.yml:
--------------------------------------------------------------------------------
1 |
2 | root: true
3 |
4 | parser: 'vue-eslint-parser'
5 |
6 | env:
7 | es6: true
8 | node: true
9 |
10 | extends:
11 | - plugin:vue/essential
12 | - plugin:@typescript-eslint/eslint-recommended
13 | - plugin:@typescript-eslint/recommended
14 |
15 | parserOptions:
16 | parser: '@typescript-eslint/parser'
17 | sourceType: module
18 | extraFileExtensions: ['.vue', '.ts']
19 | ecmaFeatures:
20 | jsx: true
21 |
22 | plugins:
23 | # Local version of @typescript-eslint/eslint-plugin
24 | - '@typescript-eslint'
25 |
26 | rules:
27 | '@typescript-eslint/no-explicit-any': 'error'
28 | '@typescript-eslint/member-delimiter-style': 'off'
29 | '@typescript-eslint/explicit-function-return-type': 'off'
30 | '@typescript-eslint/camelcase':
31 | - "error"
32 | - properties: "never"
33 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v2
10 | - name: 🖥️ Setup Node
11 | uses: actions/setup-node@v1
12 | with:
13 | node-version: 12.x
14 | - name: 🧶 Install
15 | run: yarn install
16 | - name: Copy config
17 | run: cp config.sample.json config.json
18 | - name: 🔨 Build
19 | run: yarn build
20 | lint:
21 | runs-on: ubuntu-latest
22 | steps:
23 | - uses: actions/checkout@v2
24 | - name: 🖥️ Setup Node
25 | uses: actions/setup-node@v1
26 | with:
27 | node-version: 12.x
28 | - name: 🧶 Install
29 | run: yarn install
30 | - name: 📇 Lint
31 | run: yarn lint
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
63 | /dist
64 |
65 | config.json
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Will Hunt
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # matrix-presents
2 |
3 | A presentation client that reads from a Matrix room and displays it as pretty slides!
4 |
5 | For help and support, visit [#presents:half-shot.uk](https://matrix.to/#/#presents:half-shot.uk)
6 |
7 | ## Project setup
8 |
9 | To run the project:
10 |
11 | ```
12 | git clone https://github.com/Half-Shot/matrix-presents
13 | cd matrix-presents
14 | yarn
15 | ```
16 | Create `config.json` (e.g. based on `config.sample.json`) then
17 | ```
18 | yarn serve
19 | ```
20 |
21 | ## Docs
22 |
23 | - [Slide Theory](docs/slide-theory.md) contains some information about how Presents uses Matrix events and rooms.
24 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ],
5 | plugins: [
6 | '@babel/plugin-proposal-optional-chaining',
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/config.sample.json:
--------------------------------------------------------------------------------
1 | {
2 | "guest_homeserver": "https://reckless.half-shot.uk",
3 | "base_uri": "https://presents.half-shot.uk"
4 | }
--------------------------------------------------------------------------------
/docs/slide-theory.md:
--------------------------------------------------------------------------------
1 | Slide Theory
2 | ------------
3 |
4 | This document describes how `matrix-presents` uses rooms and events to store slides.
5 |
6 | ## The Room
7 |
8 | One presentation is stored inside one room.
9 |
10 | A room will have a `uk.half-shot.presents.slides` state event with an empty `state_key`.
11 | The event will contain a number of event_ids which correspond to each slide.
12 |
13 | ```
14 | {
15 | "slides": [
16 | "$jBKbAKRMgJ-SO37RfAOM6UtCwibPzn8zzMPxv85928k",
17 | "$j9jua8Rsd7TV4mkhvBA8jQIz0OLQoX75pd6vJqn1AO4",
18 | "$bgYjfoZsCjG9e3-OlRUWeluiLdCtLnvUt-Uis4l8Jms"
19 | ]
20 | }
21 | ```
22 | *Example event content for `uk.half-shot.presents.slides`*
23 |
24 | This allows a client to index all the slides, as well as store the ordering of the presentation.
25 | Currently the ordering of slides is linear, where the index of the event_id denotes the ordering of
26 | the slides.
27 |
28 | Any `uk.half-shot.presents.slide` event can be added into this index, from any `sender`.
29 |
30 | The powerlevel required to send `uk.half-shot.presents.slides` decides who is considered an **Editor** in
31 | a room. In Matrix, you require only PL50 to send a state event into the room, so by default all
32 | "Moderators" in a room are also Editors. Clients should ensure that the PL required to send this event is
33 | sufficently high for the user's needs.
34 |
35 | ## Slides
36 |
37 | Slides are stored inside the same room, and can be created by anyone. They should be normal events.
38 |
39 | ### Fragment Events
40 |
41 | Fragment events are ANY event that can be rendered inside a slide. Clients may decide for themselves which
42 | events they wish to render in a slide (and show an appropriate fallback if not), but allclients should
43 | display:
44 |
45 | - `m.room.message`
46 | - `msgtype`:
47 | - `m.text` (both HTML and plain)
48 | - `m.image`
49 |
50 | Anybody may create fragment, and again care should be taken to ensure that the PLs for the room have been
51 | set correctly. Anyone able to send any event is considered a **Contributor**.
52 |
53 | ### Slide Events
54 |
55 | Slide events will contain references to the child events.
56 |
57 | An example `uk.half-shot.presents.slide` event:
58 | ```json
59 | {
60 | "title": "Foobar",
61 | "subtitle": "The need for foo",
62 | "columns": [
63 | ["!foobar:example.com"],
64 | ["!bar:baz.com"]
65 | ]
66 | }
67 | ```
68 | *Example event content for `uk.half-shot.presents.slide`*
69 |
70 | A slide may contain a `title` and a `subtitle`. These are plaintext that may be rendered
71 | inside a heading. If `columns` is not defined or is empty (but NOT if it contains an empty
72 | array), then the title and subtitle should be rendered as a "title card". That is, they should be rendered
73 | centered. Optionally, if the event is also the first slide, the author of the slide may be present.
74 | Otherwise, the `title` and `subtitle` are optional and may be rendered above the rest of the slide.
75 |
76 | `columns` defines the fragments to be rendered inside a slide, seperated into columns in the show. A
77 | simple slide may only have one column with one item (and therefore should be rendered centered). Two or
78 | more columns should be equally spaced out along the slide. Columns may not yet have sub-columns, due to
79 | the difficulties in rendering subcolumns without impairing readability. Fragments should be rendered on
80 | top of each other per column, and width/height should be dynamically adjusted depending on the type of
81 | fragment.
82 |
83 | Fragments can be lazyloaded into a slide, or a client may wait for all the fragments to be loaded before
84 | displaying the slide. Smarter clients may attempt to "buffer" the next few slides while rendering the
85 | current one to avoid any slowdowns during a presentation.
86 |
87 | As with the parent `uk.half-shot.presents.slides` event, PLs should be chosen with care. By default,
88 | anyone can send a slide event into a room although only slides inside the parent will be rendered to \
89 | viewers.
90 |
91 | ### Edits
92 |
93 | Edits are currently an anomoly in that the previous event cannot point to the edited event, so it is hard to notify clients of an edit unless they see it come down sync. For the time being, editing a fragment will require editing the parent slide event.
94 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "matrix-presents",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "dev": "vue-cli-service serve --mode development",
8 | "build": "vue-cli-service build",
9 | "lint": "eslint src/**/*.vue src/**/*.ts"
10 | },
11 | "dependencies": {
12 | "animate.css": "^3.7.2",
13 | "core-js": "^3.4.4",
14 | "highlight.js": "^9.17.1",
15 | "matrix-js-sdk": "^3.0.0",
16 | "qrcode": "^1.4.4",
17 | "twemoji": "^12.1.5",
18 | "v-emoji-picker": "^2.0.3",
19 | "vue": "^2.6.10",
20 | "vue-class-component": "^7.0.2",
21 | "vue-feather-icons": "^5.0.0",
22 | "vue-property-decorator": "^8.3.0",
23 | "vue-router": "^3.1.3"
24 | },
25 | "devDependencies": {
26 | "@babel/plugin-proposal-optional-chaining": "^7.8.3",
27 | "@types/highlight.js": "^9.12.3",
28 | "@types/qrcode": "^1.3.4",
29 | "@types/twemoji": "^12.1.0",
30 | "@types/vue-router": "^2.0.0",
31 | "@typescript-eslint/eslint-plugin": "^2.16.0",
32 | "@typescript-eslint/parser": "^2.16.0",
33 | "@vue/cli-plugin-babel": "^4.1.0",
34 | "@vue/cli-plugin-router": "^4.1.0",
35 | "@vue/cli-plugin-typescript": "^4.1.0",
36 | "@vue/cli-service": "^4.1.0",
37 | "eslint": "^6.8.0",
38 | "eslint-plugin-vue": "^6.1.2",
39 | "sass": "^1.23.7",
40 | "sass-loader": "^8.0.0",
41 | "typescript": "~3.7.5",
42 | "vue-eslint-parser": "^7.0.0",
43 | "vue-template-compiler": "^2.6.10"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |