very basic client list code
This commit is contained in:
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
import {Link} from "./Link.js";
|
||||
import {ViewModel} from "./utils/ViewModel.js";
|
||||
import {PreviewViewModel} from "./preview/PreviewViewModel.js";
|
||||
import {Element} from "./client/clients/Element.js";
|
||||
|
||||
export class RootViewModel extends ViewModel {
|
||||
constructor(options) {
|
||||
@@ -28,9 +29,13 @@ export class RootViewModel extends ViewModel {
|
||||
_updateChildVMs(oldLink) {
|
||||
if (this.link) {
|
||||
if (!oldLink || !oldLink.equals(this.link)) {
|
||||
const element = new Element();
|
||||
this.previewViewModel = new PreviewViewModel(this.childOptions({
|
||||
link: this.link,
|
||||
consentedServers: this.link.servers
|
||||
consentedServers: this.link.servers,
|
||||
// preferredClient: element,
|
||||
// preferredPlatform: this.platforms[0],
|
||||
clients: [element]
|
||||
}));
|
||||
this.previewViewModel.load();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {TemplateView} from "../utils/TemplateView.js";
|
||||
import {ClientView} from "./ClientView.js";
|
||||
|
||||
export class ClientListView extends TemplateView {
|
||||
render(t, vm) {
|
||||
const clients = vm.clients.map(clientViewModel => t.view(new ClientView(clientViewModel)));
|
||||
return t.div({className: "ClientListView"}, [
|
||||
t.h3("You need an app to continue"),
|
||||
t.ul({className: "ClientListView"}, clients)
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {isWebPlatform, Platform} from "./Platform.js";
|
||||
import {ClientViewModel} from "./ClientViewModel.js";
|
||||
import {ViewModel} from "../utils/ViewModel.js";
|
||||
|
||||
export class ClientListViewModel extends ViewModel {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
const {clients, link} = options;
|
||||
this.clients = clients.map(client => new ClientViewModel(this.childOptions({client, link})));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {TemplateView} from "../utils/TemplateView.js";
|
||||
|
||||
export class ClientView extends TemplateView {
|
||||
render(t, vm) {
|
||||
return t.li({className: "ClientView"}, [
|
||||
t.div({className: "header"}, [
|
||||
t.div({className: "description"}, [
|
||||
t.h3(vm.name),
|
||||
t.p(vm.description),
|
||||
]),
|
||||
t.div({className: `icon ${vm.clientId}`})
|
||||
]),
|
||||
t.div({className: "actions"}, vm.actions.map(a => {
|
||||
return t.a({href: a.url, className: a.kind, rel: "noopener noreferrer", onClick: () => a.activated()}, a.label);
|
||||
}))
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {isWebPlatform, Platform} from "./Platform.js";
|
||||
import {ViewModel} from "../utils/ViewModel.js";
|
||||
|
||||
export class ClientViewModel extends ViewModel {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
const {client, link} = options;
|
||||
this._client = client;
|
||||
const supportedPlatforms = client.platforms;
|
||||
const matchingPlatforms = this.platforms.filter(p => {
|
||||
return supportedPlatforms.includes(p);
|
||||
});
|
||||
const nativePlatform = matchingPlatforms.find(p => !isWebPlatform(p));
|
||||
const webPlatform = this.platforms.find(p => isWebPlatform(p));
|
||||
this.actions = this._createActions(client, link, nativePlatform, webPlatform);
|
||||
this.name = this._client.getName(nativePlatform || webPlatform);
|
||||
}
|
||||
|
||||
_createActions(client, link, nativePlatform, webPlatform) {
|
||||
let actions = [];
|
||||
if (nativePlatform) {
|
||||
const nativeActions = client.getInstallLinks(nativePlatform).map(installLink => {
|
||||
return {
|
||||
label: installLink.description,
|
||||
url: installLink.createInstallURL(link),
|
||||
kind: installLink.channelId,
|
||||
activated() {},
|
||||
};
|
||||
});
|
||||
actions.push(...nativeActions);
|
||||
}
|
||||
if (webPlatform) {
|
||||
actions.push({
|
||||
label: `Or open in ${client.getName(webPlatform)}`,
|
||||
url: client.getDeepLink(webPlatform, link),
|
||||
kind: "open-in-web",
|
||||
activated() {},
|
||||
});
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
get description() {
|
||||
return this._client.description;
|
||||
}
|
||||
|
||||
get clientId() {
|
||||
return this._client.id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {createEnum} from "../utils/enum.js";
|
||||
|
||||
export const Platform = createEnum(
|
||||
"DesktopWeb",
|
||||
"MobileWeb",
|
||||
"Android",
|
||||
"iOS",
|
||||
"Windows",
|
||||
"macOS",
|
||||
"Linux"
|
||||
);
|
||||
|
||||
export function guessApplicablePlatforms(userAgent) {
|
||||
// use https://github.com/faisalman/ua-parser-js to guess, and pass as RootVM options
|
||||
return [Platform.DesktopWeb, Platform.Linux];
|
||||
}
|
||||
|
||||
export function isWebPlatform(p) {
|
||||
return p === Platform.DesktopWeb || p === Platform.MobileWeb;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {Maturity, Platform, LinkKind,
|
||||
FDroidLink, AppleStoreLink, PlayStoreLink, WebsiteLink} from "../types.js";
|
||||
|
||||
/**
|
||||
* Information on how to deep link to a given matrix client.
|
||||
*/
|
||||
export class Element {
|
||||
/* should only contain alphanumerical and -_, no dots (needs to be usable as css class) */
|
||||
get id() { return "element-io"; }
|
||||
|
||||
get platforms() {
|
||||
return [
|
||||
Platform.Android, Platform.iOS,
|
||||
Platform.Windows, Platform.macOS, Platform.Linux,
|
||||
Platform.DesktopWeb, Platform.MobileWeb
|
||||
];
|
||||
}
|
||||
|
||||
get description() { return 'Fully-featured Matrix client'; }
|
||||
|
||||
getMaturity(platform) { return Maturity.Stable; }
|
||||
|
||||
getLinkSupport(platform, link) { return true; }
|
||||
|
||||
getDeepLink(platform, link) {
|
||||
let fragmentPath;
|
||||
switch (link.kind) {
|
||||
case LinkKind.User:
|
||||
fragmentPath = `user/${link.identifier}`;
|
||||
break;
|
||||
case LinkKind.Room:
|
||||
fragmentPath = `room/${link.identifier}`;
|
||||
break;
|
||||
case LinkKind.Group:
|
||||
fragmentPath = `group/${link.identifier}`;
|
||||
break;
|
||||
case LinkKind.Event:
|
||||
fragmentPath = `room/${link.identifier}/${link.eventId}`;
|
||||
break;
|
||||
}
|
||||
if (platform === Platform.DesktopWeb || platform === Platform.MobileWeb || platform === Platform.iOS) {
|
||||
return `https://app.element.io/#/${fragmentPath}`;
|
||||
} else {
|
||||
return `element://${fragmentPath}`;
|
||||
}
|
||||
}
|
||||
|
||||
getLinkInstructions(platform, link) {}
|
||||
|
||||
getName(platform) {
|
||||
if (platform === Platform.DesktopWeb || platform === Platform.MobileWeb) {
|
||||
return "Element Web";
|
||||
} else {
|
||||
return "Element";
|
||||
}
|
||||
}
|
||||
|
||||
getInstallLinks(platform) {
|
||||
switch (platform) {
|
||||
case Platform.iOS: return [new AppleStoreLink('vector', 'id1083446067')];
|
||||
case Platform.Android: return [new PlayStoreLink('im.vector.app'), new FDroidLink('im.vector.app')];
|
||||
default: return [new WebsiteLink("https://element.io/get-started")];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {createEnum} from "../utils/enum.js";
|
||||
export const Maturity = createEnum("Alpha", "Beta", "Stable");
|
||||
export {LinkKind} from "../Link.js";
|
||||
export {Platform} from "./Platform.js";
|
||||
|
||||
export class AppleStoreLink {
|
||||
constructor(org, appId) {
|
||||
this._org = org;
|
||||
this._appId = appId;
|
||||
}
|
||||
|
||||
createInstallURL(link) {
|
||||
return `https://apps.apple.com/app/${encodeURIComponent(this._org)}/${encodeURIComponent(this._appId)}`;
|
||||
}
|
||||
|
||||
get channelId() {
|
||||
return "apple-app-store";
|
||||
}
|
||||
|
||||
get description() {
|
||||
return "Download on the App Store";
|
||||
}
|
||||
}
|
||||
|
||||
export class PlayStoreLink {
|
||||
constructor(appId) {
|
||||
this._appId = appId;
|
||||
}
|
||||
|
||||
createInstallURL(link) {
|
||||
return `https://play.google.com/store/apps/details?id=${encodeURIComponent(this._appId)}&referrer=${encodeURIComponent(link.identifier)}`;
|
||||
}
|
||||
|
||||
get channelId() {
|
||||
return "play-store";
|
||||
}
|
||||
|
||||
get description() {
|
||||
return "Get it on Google Play";
|
||||
}
|
||||
}
|
||||
|
||||
export class FDroidLink {
|
||||
constructor(appId) {
|
||||
this._appId = appId;
|
||||
}
|
||||
|
||||
createInstallURL(link) {
|
||||
return `https://f-droid.org/packages/${encodeURIComponent(this._appId)}`;
|
||||
}
|
||||
|
||||
get channelId() {
|
||||
return "fdroid";
|
||||
}
|
||||
|
||||
get description() {
|
||||
return "Get it on F-Droid";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class WebsiteLink {
|
||||
constructor(url) {
|
||||
this._url = url;
|
||||
}
|
||||
|
||||
createInstallURL(link) {
|
||||
return this._url;
|
||||
}
|
||||
|
||||
get channelId() {
|
||||
return "website";
|
||||
}
|
||||
|
||||
get description() {
|
||||
return `Download from ${new URL(this._url).hostname}`;
|
||||
}
|
||||
}
|
||||
+6
-1
@@ -1,9 +1,14 @@
|
||||
import {xhrRequest} from "./utils/xhr.js";
|
||||
import {RootViewModel} from "./RootViewModel.js";
|
||||
import {RootView} from "./RootView.js";
|
||||
import {guessApplicablePlatforms} from "./client/Platform.js";
|
||||
|
||||
export async function main(container) {
|
||||
const vm = new RootViewModel({request: xhrRequest});
|
||||
const vm = new RootViewModel({
|
||||
request: xhrRequest,
|
||||
openLink: url => location.href = url,
|
||||
platforms: guessApplicablePlatforms(navigator.userAgent),
|
||||
});
|
||||
vm.updateHash(location.hash);
|
||||
window.__rootvm = vm;
|
||||
const view = new RootView(vm);
|
||||
|
||||
@@ -15,18 +15,23 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import {TemplateView} from "../utils/TemplateView.js";
|
||||
import {ClientListView} from "../client/ClientListView.js";
|
||||
|
||||
export class PreviewView extends TemplateView {
|
||||
render(t, vm) {
|
||||
return t.div({className: "PreviewView card"}, [
|
||||
t.h2({className: {hidden: vm => !vm.loading}}, "Loading preview…"),
|
||||
t.div({className: {preview: true, hidden: vm => vm.loading}}, [
|
||||
t.p(t.img({className: "avatar", src: vm => vm.avatarUrl})),
|
||||
t.div({className: "profileInfo"}, [
|
||||
t.h2(vm => vm.name),
|
||||
t.p(vm => vm.identifier),
|
||||
t.p(["Preview from ", vm => vm.previewDomain]),
|
||||
])
|
||||
t.div({className: {hidden: vm => vm.loading}}, [
|
||||
t.div({className: "preview"}, [
|
||||
t.p(t.img({className: "avatar", src: vm => vm.avatarUrl})),
|
||||
t.div({className: "profileInfo"}, [
|
||||
t.h2(vm => vm.name),
|
||||
t.p(vm => vm.identifier),
|
||||
t.p(["Preview from ", vm => vm.previewDomain]),
|
||||
]),
|
||||
]),
|
||||
t.p({hidden: vm => !!vm.clientsViewModel}, t.button({onClick: () => vm.accept()}, vm => vm.acceptLabel)),
|
||||
t.mapView(vm => vm.clientsViewModel, vm => vm ? new ClientListView(vm) : null)
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -17,17 +17,28 @@ limitations under the License.
|
||||
import {LinkKind} from "../Link.js";
|
||||
import {ViewModel} from "../utils/ViewModel.js";
|
||||
import {resolveServer} from "./HomeServer.js";
|
||||
import {ClientListViewModel} from "../client/ClientListViewModel.js";
|
||||
|
||||
export class PreviewViewModel extends ViewModel {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
const {link, consentedServers} = options;
|
||||
const {
|
||||
link, consentedServers,
|
||||
preferredClient, preferredPlatform, clients
|
||||
} = options;
|
||||
this._link = link;
|
||||
this._consentedServers = consentedServers;
|
||||
this._preferredClient = preferredClient;
|
||||
// used to differentiate web from native if a client supports both
|
||||
this._preferredPlatform = preferredPlatform;
|
||||
this._clients = clients;
|
||||
|
||||
this.loading = false;
|
||||
this.name = null;
|
||||
this.avatarUrl = null;
|
||||
this.previewDomain = null;
|
||||
this.clientsViewModel = null;
|
||||
this.acceptInstructions = null;
|
||||
}
|
||||
|
||||
async load() {
|
||||
@@ -62,4 +73,28 @@ export class PreviewViewModel extends ViewModel {
|
||||
get identifier() {
|
||||
return this._link.identifier;
|
||||
}
|
||||
|
||||
get acceptLabel() {
|
||||
if (this._preferredClient) {
|
||||
return `Open in ${this._preferredClient.getName(this._preferredPlatform)}`;
|
||||
} else {
|
||||
return "Choose app";
|
||||
}
|
||||
}
|
||||
|
||||
accept() {
|
||||
if (this._preferredClient) {
|
||||
if (this._preferredClient.getLinkSupport(this._preferredPlatform, this._link)) {
|
||||
const deepLink = this._preferredClient.getDeepLink(this._preferredPlatform, this._link);
|
||||
this.openLink(deepLink);
|
||||
// show "looks like you don't have the native app installed"
|
||||
} else {
|
||||
this.acceptInstructions = this._preferredClient.getLinkInstructions(this._preferredPlatform, this._link);
|
||||
}
|
||||
} else {
|
||||
this.clientsViewModel = new ClientListViewModel(this.childOptions({clients: this._clients, link: this._link}));
|
||||
// show client list
|
||||
}
|
||||
this.emitChange();
|
||||
}
|
||||
}
|
||||
@@ -60,11 +60,15 @@ export class ViewModel extends EventEmitter {
|
||||
this.emit("change");
|
||||
}
|
||||
|
||||
get request() {
|
||||
return this._options.request;
|
||||
}
|
||||
get request() { return this._options.request; }
|
||||
get openLink() { return this._options.openLink; }
|
||||
get platforms() { return this._options.platforms; }
|
||||
|
||||
childOptions(options = {}) {
|
||||
return Object.assign({request: this.request}, options);
|
||||
return Object.assign({
|
||||
request: this.request,
|
||||
openLink: this.openLink,
|
||||
platforms: this.platforms,
|
||||
}, options);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user