move "looks like not installed" in client view, and open & install stage

This commit is contained in:
Bruno Windels 2020-12-01 15:29:12 +01:00
parent c1bc2546fd
commit 64657c196a
9 changed files with 141 additions and 53 deletions

View file

@ -144,19 +144,19 @@ button.text:hover {
padding: 0; padding: 0;
} }
.ClientView .header { .ListedClientView .header {
display: flex; display: flex;
} }
.ClientView .description { .ListedClientView .description {
flex: 1; flex: 1;
} }
.ClientView h3 { .ListedClientView h3 {
margin-top: 0; margin-top: 0;
} }
.ClientView .icon { .ListedClientView .icon {
border-radius: 8px; border-radius: 8px;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: cover; background-size: cover;
@ -165,23 +165,39 @@ button.text:hover {
} }
.ClientView .icon.element-io { .ListedClientView .icon.element-io {
background-image: url('../images/client-icons/element.svg'); background-image: url('../images/client-icons/element.svg');
} }
button.primary, a.primary, button.secondary, a.secondary {
text-decoration: none;
font-weight: bold;
text-align: center;
padding: 8px;
margin: 8px 0;
line-height: 24px;
}
button.secondary, a.secondary {
background: var(--background);
color: var(--link);
}
button.primary, a.primary { button.primary, a.primary {
background: var(--link); background: var(--link);
color: var(--background); color: var(--background);
border-radius: 32px; border-radius: 32px;
text-decoration: none;
font-weight: bold;
} }
.ClientView .actions a { button.primary, button.secondary {
border: none;
width: 100%;
font-size: inherit;
}
.PreviewView .primary, .PreviewView .secondary {
display: block; display: block;
text-align: center;
padding: 8px;
margin: 8px;
} }
.ClientView .actions a.badge { .ClientView .actions a.badge {

View file

@ -40,6 +40,15 @@ function asPrefix(identifierKind) {
} }
} }
export function getLabelForLinkKind(kind) {
switch (kind) {
case LinkKind.User: return "Start chat";
case LinkKind.Room: return "View room";
case LinkKind.Group: return "View community";
case LinkKind.Event: return "View message";
}
}
export const LinkKind = createEnum( export const LinkKind = createEnum(
"Room", "Room",
"User", "User",

View file

@ -15,11 +15,11 @@ limitations under the License.
*/ */
import {TemplateView} from "../utils/TemplateView.js"; import {TemplateView} from "../utils/TemplateView.js";
import {ClientView} from "./ClientView.js"; import {ListedClientView} from "./ClientView.js";
export class ClientListView extends TemplateView { export class ClientListView extends TemplateView {
render(t, vm) { render(t, vm) {
const clients = vm.clients.map(clientViewModel => t.view(new ClientView(clientViewModel))); const clients = vm.clients.map(clientViewModel => t.view(new ListedClientView(clientViewModel)));
return t.div({className: "ClientListView"}, [ return t.div({className: "ClientListView"}, [
t.h3("You need an app to continue"), t.h3("You need an app to continue"),
t.div({className: "ClientListView"}, clients) t.div({className: "ClientListView"}, clients)

View file

@ -16,9 +16,9 @@ limitations under the License.
import {TemplateView} from "../utils/TemplateView.js"; import {TemplateView} from "../utils/TemplateView.js";
export class ClientView extends TemplateView { export class ListedClientView extends TemplateView {
render(t, vm) { render(t, vm) {
return t.div({className: "ClientView"}, [ return t.div({className: "ListedClientView"}, [
t.div({className: "header"}, [ t.div({className: "header"}, [
t.div({className: "description"}, [ t.div({className: "description"}, [
t.h3(vm.name), t.h3(vm.name),
@ -26,6 +26,41 @@ export class ClientView extends TemplateView {
]), ]),
t.div({className: `icon ${vm.clientId}`}) t.div({className: `icon ${vm.clientId}`})
]), ]),
t.view(new ClientView(vm))
]);
}
}
export class ClientView extends TemplateView {
render(t, vm) {
return t.div({className: "ClientView"}, [
t.mapView(vm => vm.stage, stage => {
switch (stage) {
case "open": return new OpenClientView(vm);
case "install": return new InstallClientView(vm);
}
}),
]);
}
}
class OpenClientView extends TemplateView {
render(t, vm) {
return t.div({className: "OpenClientView"}, [
t.a({
className: "primary",
href: vm.deepLink,
rel: "noopener noreferrer",
onClick: () => vm.deepLinkActivated(),
}, vm.deepLinkLabel)
]);
}
}
class InstallClientView extends TemplateView {
render(t, vm) {
return t.div({className: "InstallClientView"}, [
t.h3(`Looks like you don't have ${vm.name} installed.`),
t.div({className: "actions"}, vm.actions.map(a => { t.div({className: "actions"}, vm.actions.map(a => {
let badgeUrl; let badgeUrl;
switch (a.kind) { switch (a.kind) {

View file

@ -16,20 +16,28 @@ limitations under the License.
import {isWebPlatform, Platform} from "./Platform.js"; import {isWebPlatform, Platform} from "./Platform.js";
import {ViewModel} from "../utils/ViewModel.js"; import {ViewModel} from "../utils/ViewModel.js";
import {getLabelForLinkKind} from "../Link.js";
export class ClientViewModel extends ViewModel { export class ClientViewModel extends ViewModel {
constructor(options) { constructor(options) {
super(options); super(options);
const {client, link} = options; const {client, link} = options;
this._client = client; this._client = client;
this._link = link;
const supportedPlatforms = client.platforms; const supportedPlatforms = client.platforms;
const matchingPlatforms = this.platforms.filter(p => { const matchingPlatforms = this.platforms.filter(p => {
return supportedPlatforms.includes(p); return supportedPlatforms.includes(p);
}); });
const nativePlatform = matchingPlatforms.find(p => !isWebPlatform(p)); const nativePlatform = matchingPlatforms.find(p => !isWebPlatform(p));
const webPlatform = this.platforms.find(p => isWebPlatform(p)); const webPlatform = this.platforms.find(p => isWebPlatform(p));
this._showOpen = nativePlatform && !client.canInterceptMatrixToLinks(nativePlatform);
this._proposedPlatform = this.preferences.platform || nativePlatform || webPlatform;
this.actions = this._createActions(client, link, nativePlatform, webPlatform); this.actions = this._createActions(client, link, nativePlatform, webPlatform);
this.name = this._client.getName(nativePlatform || webPlatform); this.name = this._client.getName(this._proposedPlatform);
this.deepLink = this._client.getDeepLink(this._proposedPlatform, this._link);
} }
_createActions(client, link, nativePlatform, webPlatform) { _createActions(client, link, nativePlatform, webPlatform) {
@ -64,4 +72,34 @@ export class ClientViewModel extends ViewModel {
get clientId() { get clientId() {
return this._client.id; return this._client.id;
} }
get stage() {
return this._showOpen ? "open" : "install";
} }
get deepLinkLabel() {
return getLabelForLinkKind(this._link.kind);
}
deepLinkActivated() {
this.preferences.setClient(this._client.id, this._proposedPlatform);
if (this._showOpen) {
this._showOpen = false;
this.emitChange();
}
}
}
/*
if (this._preferredClient.getLinkSupport(this.preferences.platform, this._link)) {
const deepLink = this._preferredClient.getDeepLink(this.preferences.platform, this._link);
this.openLink(deepLink);
const protocol = new URL(deepLink).protocol;
const isWebProtocol = protocol === "http:" || protocol === "https:";
if (!isWebProtocol) {
this.missingClientViewModel = new ClientViewModel(this.childOptions({client: this._preferredClient, link: this._link}));
}
} else {
this.acceptInstructions = this._preferredClient.getLinkInstructions(this.preferences.platform, this._link);
}
*/

View file

@ -29,6 +29,7 @@ export const Platform = createEnum(
export function guessApplicablePlatforms(userAgent) { export function guessApplicablePlatforms(userAgent) {
// use https://github.com/faisalman/ua-parser-js to guess, and pass as RootVM options // use https://github.com/faisalman/ua-parser-js to guess, and pass as RootVM options
return [Platform.DesktopWeb, Platform.Linux]; return [Platform.DesktopWeb, Platform.Linux];
//return [Platform.MobileWeb, Platform.iOS];
} }
export function isWebPlatform(p) { export function isWebPlatform(p) {

View file

@ -78,4 +78,8 @@ export class Element {
default: return [new WebsiteLink("https://element.io/get-started")]; default: return [new WebsiteLink("https://element.io/get-started")];
} }
} }
canInterceptMatrixToLinks(platform) {
return platform === Platform.iOS || platform === Platform.Android;
}
} }

View file

@ -31,19 +31,14 @@ export class PreviewView extends TemplateView {
t.p(["Preview from ", vm => vm.previewDomain]), t.p(["Preview from ", vm => vm.previewDomain]),
]), ]),
]), ]),
t.p({hidden: vm => !!vm.clientsViewModel}, t.button({onClick: () => vm.accept()}, vm => vm.acceptLabel)), t.p({className: {hidden: vm => !vm.canShowClients}}, t.button({
className: "primary",
onClick: () => vm.showClients()
}, vm => vm.showClientsLabel)),
t.mapView(vm => vm.clientsViewModel, childVM => childVM ? new ClientListView(childVM) : null), t.mapView(vm => vm.clientsViewModel, childVM => childVM ? new ClientListView(childVM) : null),
t.mapView(vm => vm.missingClientViewModel, childVM => childVM ? new MissingClientView(childVM) : null), t.mapView(vm => vm.preferredClientViewModel, childVM => childVM ? new ClientView(childVM) : null),
t.p({className: {hidden: vm => !vm.preferredClientViewModel}}, vm => `This will open in ${vm.preferredClientViewModel?.name}`),
]) ])
]); ]);
} }
} }
class MissingClientView extends TemplateView {
render(t, vm) {
return t.div({className: "MissingClientView"}, [
t.h3(`It looks like you don't have ${vm.name} installed.`),
t.view(new ClientView(vm)),
]);
}
}

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {LinkKind} from "../Link.js"; import {LinkKind, getLabelForLinkKind} from "../Link.js";
import {ViewModel} from "../utils/ViewModel.js"; import {ViewModel} from "../utils/ViewModel.js";
import {resolveServer} from "./HomeServer.js"; import {resolveServer} from "./HomeServer.js";
import {ClientListViewModel} from "../client/ClientListViewModel.js"; import {ClientListViewModel} from "../client/ClientListViewModel.js";
@ -35,7 +35,10 @@ export class PreviewViewModel extends ViewModel {
this.previewDomain = null; this.previewDomain = null;
this.clientsViewModel = null; this.clientsViewModel = null;
this.acceptInstructions = null; this.acceptInstructions = null;
this.missingClientViewModel = null; this.preferredClientViewModel = this._preferredClient ? new ClientViewModel(this.childOptions({
client: this._preferredClient,
link: this._link
})) : null;
} }
async load() { async load() {
@ -71,31 +74,18 @@ export class PreviewViewModel extends ViewModel {
return this._link.identifier; return this._link.identifier;
} }
get acceptLabel() { get canShowClients() {
if (this._preferredClient) { return !(this.preferredClientViewModel || this.clientsViewModel);
return `Open in ${this._preferredClient.getName(this.preferences.platform)}`;
} else {
return "Choose app";
}
} }
accept() { get showClientsLabel() {
if (this._preferredClient) { return getLabelForLinkKind(this._link.kind);
if (this._preferredClient.getLinkSupport(this.preferences.platform, this._link)) {
const deepLink = this._preferredClient.getDeepLink(this.preferences.platform, this._link);
this.openLink(deepLink);
const protocol = new URL(deepLink).protocol;
const isWebProtocol = protocol === "http:" || protocol === "https:";
if (!isWebProtocol) {
this.missingClientViewModel = new ClientViewModel(this.childOptions({client: this._preferredClient, link: this._link}));
} }
} else {
this.acceptInstructions = this._preferredClient.getLinkInstructions(this.preferences.platform, this._link); showClients() {
} if (!this._preferredClient) {
} else {
this.clientsViewModel = new ClientListViewModel(this.childOptions({clients: this._clients, link: this._link})); this.clientsViewModel = new ClientListViewModel(this.childOptions({clients: this._clients, link: this._link}));
// show client list
}
this.emitChange(); this.emitChange();
} }
} }
}