Skip to content

Commit a352a32

Browse files
committed
WIP: Add an app switcher widget
1 parent 47da91b commit a352a32

File tree

17 files changed

+299
-22
lines changed

17 files changed

+299
-22
lines changed

src/plugins/chatboxviews/index.js

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,56 @@
22
* @copyright 2024, the Converse.js contributors
33
* @license Mozilla Public License (MPLv2)
44
*/
5+
import { html } from 'lit';
56
import { _converse, api, converse } from '@converse/headless';
67
import './view.js';
78
import ChatBoxViews from './container.js';
89
import { calculateViewportHeightUnit } from './utils.js';
10+
import '../rootview/index.js';
911

1012
import './styles/chats.scss';
1113

12-
1314
converse.plugins.add('converse-chatboxviews', {
14-
dependencies: ['converse-chatboxes', 'converse-vcard'],
15+
dependencies: ['converse-rootview', 'converse-chatboxes', 'converse-vcard'],
1516

16-
initialize () {
17+
initialize() {
1718
api.promises.add(['chatBoxViewsInitialized']);
1819

1920
// Configuration values for this plugin
2021
// ====================================
2122
// Refer to docs/source/configuration.rst for explanations of these
2223
// configuration settings.
23-
api.settings.extend({ 'animate': true });
24+
api.settings.extend({ animate: true });
25+
26+
api.apps.add({
27+
name: 'chat',
28+
render: () => {
29+
const extra_classes = api.settings.get('singleton') ? ['converse-singleton'] : [];
30+
extra_classes.push(`converse-${api.settings.get('view_mode')}`);
31+
return html`<converse-chats
32+
class="converse-chatboxes row justify-content-start g-0 ${extra_classes.join(' ')}"
33+
></converse-chats>`;
34+
},
35+
active: true,
36+
});
37+
38+
// TODO: move to own plugin
39+
api.apps.add({
40+
name: 'todo',
41+
render: () => {
42+
return html`<p>hello world: todo</p>`;
43+
},
44+
active: false,
45+
});
46+
47+
// TODO: move to own plugin
48+
api.apps.add({
49+
name: 'timetracker',
50+
render: () => {
51+
return html`<p>hello world: timetracker</p>`;
52+
},
53+
active: false,
54+
});
2455

2556
const chatboxviews = new ChatBoxViews();
2657
Object.assign(_converse, { chatboxviews }); // XXX DEPRECATED
@@ -53,14 +84,14 @@ converse.plugins.add('converse-chatboxviews', {
5384
* @example
5485
* converse.insertInto(document.querySelector('#converse-container'));
5586
*/
56-
insertInto (container) {
87+
insertInto(container) {
5788
const el = chatboxviews.el;
5889
if (el && !container.contains(el)) {
5990
container.insertAdjacentElement('afterbegin', el);
6091
} else if (!el) {
6192
throw new Error('Cannot insert non-existing #conversejs element into the DOM');
6293
}
63-
}
94+
},
6495
});
65-
}
96+
},
6697
});

src/plugins/chatboxviews/styles/chats.scss

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22
@import 'bootstrap/scss/variables';
33
@import 'bootstrap/scss/mixins';
44

5+
.conversejs {
6+
&.converse-fullscreen {
7+
converse-app-container {
8+
display: flex;
9+
justify-content: flex-start;
10+
height: 100%;
11+
position: fixed;
12+
bottom: 0;
13+
right: 0;
14+
left: 0;
15+
}
16+
}
17+
}
18+
19+
520
.conversejs {
621
converse-chats {
722
&.converse-overlayed {
@@ -18,14 +33,9 @@
1833

1934
&.converse-fullscreen {
2035
flex-wrap: nowrap;
21-
}
22-
23-
&.converse-fullscreen {
2436
&.converse-chatboxes {
25-
position: fixed;
26-
bottom: 0;
27-
right: 0;
28-
left: 0;
37+
width: 100%;
38+
position: sticky;
2939
}
3040
}
3141

src/plugins/rootview/api.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { api } from '@converse/headless';
2+
3+
const apps = new Map();
4+
5+
function setCurrentAppInactive() {
6+
const currentApp = apps_api.apps.getActive();
7+
if (currentApp) {
8+
apps.set(currentApp.name, {
9+
...currentApp,
10+
active: false,
11+
});
12+
}
13+
}
14+
15+
const apps_api = {
16+
apps: {
17+
/**
18+
* @param {import('./types').App} app
19+
*/
20+
add(app) {
21+
if (!app.name) throw new Error("Can't add app without a name");
22+
if (app.active) setCurrentAppInactive();
23+
apps.set(app.name, app);
24+
},
25+
26+
/**
27+
* @returns {import('./types').App}
28+
*/
29+
getActive() {
30+
return Array.from(apps.values()).find((app) => app.active);
31+
},
32+
33+
/**
34+
* @param {string} name
35+
*/
36+
switch(name) {
37+
if (apps.has(name)) {
38+
setCurrentAppInactive();
39+
apps.set(name, {
40+
...apps.get(name),
41+
active: true,
42+
});
43+
const app = apps.get(name);
44+
/**
45+
* Triggered when switching to a different app
46+
* @event _converse#appSwitch
47+
* @type {import('./types').App}
48+
* @example _converse.api.listen.on('appSwitch', (app) => { ... });
49+
*/
50+
api.trigger('appSwitch', app);
51+
return app;
52+
}
53+
return null;
54+
},
55+
},
56+
};
57+
58+
export default apps_api;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { api } from '@converse/headless';
2+
import { CustomElement } from 'shared/components/element.js';
3+
import tplAppContainer from './templates/app-container.js';
4+
import './app-switcher.js';
5+
6+
export default class AppContainer extends CustomElement {
7+
initialize() {
8+
api.listen.on('appSwitch', () => this.requestUpdate());
9+
}
10+
11+
render() {
12+
return tplAppContainer();
13+
}
14+
}
15+
16+
api.elements.define('converse-app-container', AppContainer);
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { api } from '@converse/headless';
2+
import { CustomElement } from 'shared/components/element.js';
3+
import tplAppSwitcher from './templates/app-switcher.js';
4+
5+
import './styles/app-switcher.scss';
6+
7+
export default class AppSwitcher extends CustomElement {
8+
static get properties() {
9+
return {
10+
_activeApp: { type: String },
11+
};
12+
}
13+
14+
constructor() {
15+
super();
16+
this._activeApp = 'chat';
17+
}
18+
19+
initialize() {
20+
api.listen.on('appSwitch', () => this.requestUpdate());
21+
}
22+
23+
render() {
24+
return tplAppSwitcher(this);
25+
}
26+
27+
/**
28+
* @param {MouseEvent} ev
29+
*/
30+
switchApp(ev) {
31+
ev.preventDefault();
32+
const a = /** @type {HTMLElement} */ (ev.target).closest('.nav-link');
33+
const name = a.getAttribute('data-app-name');
34+
api.apps.switch(name);
35+
this.requestUpdate();
36+
}
37+
}
38+
39+
api.elements.define('converse-app-switcher', AppSwitcher);

src/plugins/rootview/index.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
import { api, converse } from '@converse/headless';
1+
import { _converse, api, converse } from '@converse/headless';
2+
import app_api from './api.js';
23
import ConverseRoot from './root.js';
34
import { ensureElement } from './utils.js';
45
import './background.js';
5-
6+
import './app-container.js';
67

78
converse.plugins.add('converse-rootview', {
9+
initialize() {
10+
Object.assign(_converse.api, app_api);
811

9-
initialize () {
1012
// Configuration values for this plugin
1113
// ====================================
1214
// Refer to docs/source/configuration.rst for explanations of these
@@ -15,7 +17,7 @@ converse.plugins.add('converse-rootview', {
1517
auto_insert: true,
1618
dark_theme: 'dracula',
1719
// Languages for which the UI is right-to-left
18-
rtl_langs: ["ar", "fa", "he", "ug"],
20+
rtl_langs: ['ar', 'fa', 'he', 'ug'],
1921
show_background: false,
2022
theme: 'classic',
2123
});
@@ -26,5 +28,5 @@ converse.plugins.add('converse-rootview', {
2628
// before `converse.initialized` has been called it will render too
2729
// early.
2830
api.elements.define('converse-root', ConverseRoot);
29-
}
31+
},
3032
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
converse-app-switcher {
2+
box-shadow: 0 0 0.3em gray;
3+
margin-right: 0.4em;
4+
padding: 0.5em;
5+
li {
6+
margin-bottom: 1em;
7+
}
8+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { html } from 'lit';
2+
import { api } from '@converse/headless';
3+
4+
export default () => {
5+
return html`
6+
<converse-app-switcher></converse-app-switcher>
7+
${api.apps.getActive().render()}
8+
`;
9+
};
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { api } from '@converse/headless';
2+
import { html } from 'lit';
3+
4+
/**
5+
* @param {import('../app-switcher').default} el
6+
*/
7+
export default (el) => {
8+
const { name: active_app } = api.apps.getActive();
9+
return html`
10+
<ul class="nav nav-pills nav-flush flex-column mb-auto text-center">
11+
<li class="nav-item" title="Chat" aria-label="Chat">
12+
<a
13+
href="#"
14+
class="${active_app === 'chat' ? 'active' : ''} nav-link py-3 border-bottom"
15+
data-app-name="chat"
16+
aria-current="page"
17+
@click="${el.switchApp}"
18+
>
19+
<converse-icon size="1em" class="fa fa-users"></converse-icon>
20+
</a>
21+
</li>
22+
<li title="TODO" aria-label="TODO">
23+
<a
24+
href="#"
25+
class="${active_app === 'todo' ? 'active' : ''} nav-link py-3 border-bottom"
26+
data-app-name="todo"
27+
@click="${el.switchApp}"
28+
>
29+
<converse-icon size="1em" class="fa fa-list-ul"></converse-icon>
30+
</a>
31+
</li>
32+
<li title="Time Tracker" aria-label="Time Tracker">
33+
<a
34+
href="#"
35+
class="${active_app === 'timetracker' ? 'active' : ''} nav-link py-3 border-bottom"
36+
data-app-name="timetracker"
37+
@click="${el.switchApp}"
38+
>
39+
<converse-icon size="1em" class="fa fa-lock"></converse-icon>
40+
</a>
41+
</li>
42+
</ul>
43+
`;
44+
};

src/plugins/rootview/templates/root.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@ import { api } from '@converse/headless';
33
import 'shared/components/font-awesome.js';
44

55
export default () => {
6-
const extra_classes = api.settings.get('singleton') ? ['converse-singleton'] : [];
7-
extra_classes.push(`converse-${api.settings.get('view_mode')}`);
86
return html`
97
${api.settings.get('show_background') ? html`<converse-bg logo></converse-bg>` : ''}
10-
<converse-chats class="converse-chatboxes row justify-content-start g-0 ${extra_classes.join(' ')}"></converse-chats>
8+
<converse-app-container></converse-app-container>
119
<converse-modals id="converse-modals" class="modals"></converse-modals>
1210
<converse-toasts></converse-toasts>
1311
<converse-fontawesome></converse-fontawesome>

0 commit comments

Comments
 (0)