diff --git a/css/main.css b/css/main.css index e9e4d2c..79ecd0f 100644 --- a/css/main.css +++ b/css/main.css @@ -70,19 +70,14 @@ textarea { padding: 2rem; } +.PreviewView .preview { + text-align: center; +} + .PreviewView .avatar { border-radius: 100%; } -.PreviewView .preview { - display: flex; -} - -.PreviewView .profileInfo { - flex: 1; - margin-left: 12px; -} - .hidden { display: none !important; } @@ -139,24 +134,30 @@ button.text:hover { cursor: pointer; } -.ClientListView ul { - list-style: none; - padding: 0; +.ClientListView .list { + padding: 16px 0; } -.ListedClientView .header { +.ClientView { + border: 1px solid #E6E6E6; + border-radius: 8px; + margin: 16px 0; + padding: 16px; +} + +.ClientView .header { display: flex; } -.ListedClientView .description { +.ClientView .description { flex: 1; } -.ListedClientView h3 { +.ClientView h3 { margin-top: 0; } -.ListedClientView .icon { +.ClientView .icon { border-radius: 8px; background-repeat: no-repeat; background-size: cover; @@ -164,8 +165,7 @@ button.text:hover { height: 60px; } - -.ListedClientView .icon.element-io { +.ClientView .icon.element-io { background-image: url('../images/client-icons/element.svg'); } @@ -195,12 +195,13 @@ button.primary, a.primary { button.primary, button.secondary { border: none; - width: 100%; font-size: inherit; } -.PreviewView .primary, .PreviewView .secondary { +.fullwidth { display: block; + width: 100%; + box-sizing: border-box; } .ClientView .actions a.badge { diff --git a/src/client/ClientListView.js b/src/client/ClientListView.js index 33d8215..162c298 100644 --- a/src/client/ClientListView.js +++ b/src/client/ClientListView.js @@ -15,14 +15,38 @@ limitations under the License. */ import {TemplateView} from "../utils/TemplateView.js"; -import {ListedClientView} from "./ClientView.js"; +import {ClientView} from "./ClientView.js"; export class ClientListView extends TemplateView { render(t, vm) { - const clients = vm.clients.map(clientViewModel => t.view(new ListedClientView(clientViewModel))); + return t.mapView(vm => vm.clientViewModel, () => { + if (vm.clientViewModel) { + return new ContinueWithClientView(vm); + } else { + return new AllClientsView(vm); + } + }); + } +} + +class AllClientsView extends TemplateView { + render(t, vm) { + const clients = vm.clientList.map(clientViewModel => t.view(new ClientView(clientViewModel))); return t.div({className: "ClientListView"}, [ - t.h3("You need an app to continue"), - t.div({className: "ClientListView"}, clients) + t.h2("Choose an app to continue"), + t.div({className: "list"}, clients) + ]); + } +} + +class ContinueWithClientView extends TemplateView { + render(t, vm) { + return t.div({className: "ClientListView"}, [ + t.h2([ + `Continue with ${vm.clientViewModel.name} `, + t.button({onClick: () => vm.showAll()}, "Back") + ]), + t.div({className: "list"}, t.view(new ClientView(vm.clientViewModel))) ]); } } diff --git a/src/client/ClientListViewModel.js b/src/client/ClientListViewModel.js index 6cc1d47..713ee48 100644 --- a/src/client/ClientListViewModel.js +++ b/src/client/ClientListViewModel.js @@ -21,7 +21,25 @@ 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, showAcceptAnyway: true}))); + const {clients, client, link} = options; + this.clientList = clients.map(client => new ClientViewModel(this.childOptions({ + client, + link, + pickClient: client => this._pickClient(client) + }))); + this.clientViewModel = null; + if (client) { + this._pickClient(client); + } + } + + _pickClient(client) { + this.clientViewModel = this.clientList.find(vm => vm.clientId === client.id); + this.emitChange(); + } + + showAll() { + this.clientViewModel = null; + this.emitChange(); } } diff --git a/src/client/ClientView.js b/src/client/ClientView.js index 03d8a84..cdee999 100644 --- a/src/client/ClientView.js +++ b/src/client/ClientView.js @@ -16,9 +16,9 @@ limitations under the License. import {TemplateView} from "../utils/TemplateView.js"; -export class ListedClientView extends TemplateView { +export class ClientView extends TemplateView { render(t, vm) { - return t.div({className: "ListedClientView"}, [ + return t.div({className: "ClientView"}, [ t.div({className: "header"}, [ t.div({className: "description"}, [ t.h3(vm.name), @@ -26,14 +26,6 @@ export class ListedClientView extends TemplateView { ]), 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); @@ -48,38 +40,51 @@ class OpenClientView extends TemplateView { render(t, vm) { return t.div({className: "OpenClientView"}, [ t.a({ - className: "primary", + className: "primary fullwidth", href: vm.deepLink, rel: "noopener noreferrer", onClick: () => vm.deepLinkActivated(), - }, vm.deepLinkLabel) + }, "Continue") ]); } } class InstallClientView extends TemplateView { + + copyToClipboard() { + + } + 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 => { - let badgeUrl; - switch (a.kind) { - case "play-store": badgeUrl = "images/google-play-us.svg"; break; - case "fdroid": badgeUrl = "images/fdroid-badge.png"; break; - case "apple-app-store": badgeUrl = "images/app-store-us-alt.svg"; break; - } - return t.a({ - href: a.url, - className: { - primary: a.primary && !badgeUrl, - secondary: !a.primary && !badgeUrl, - badge: !!badgeUrl, - }, - rel: "noopener noreferrer", - ["aria-label"]: a.label, - onClick: () => a.activated() - }, badgeUrl ? t.img({src: badgeUrl}) : a.label); - })) - ]); + const children = []; + + if (vm.textInstructions) { + const copy = t.button({className: "primary", onClick: evt => this.copyToClipboard(evt)}, "Copy"); + children.push(t.p([vm.textInstructions, copy])); + } + + const actions = t.div({className: "actions"}, vm.actions.map(a => { + let badgeUrl; + switch (a.kind) { + case "play-store": badgeUrl = "images/google-play-us.svg"; break; + case "fdroid": badgeUrl = "images/fdroid-badge.png"; break; + case "apple-app-store": badgeUrl = "images/app-store-us-alt.svg"; break; + } + return t.a({ + href: a.url, + className: { + fullwidth: true, + primary: a.primary && !badgeUrl, + secondary: !a.primary && !badgeUrl, + badge: !!badgeUrl, + }, + rel: "noopener noreferrer", + ["aria-label"]: a.label, + onClick: () => a.activated() + }, badgeUrl ? t.img({src: badgeUrl}) : a.label); + })); + children.push(actions); + + return t.div({className: "InstallClientView"}, children); } } \ No newline at end of file diff --git a/src/client/ClientViewModel.js b/src/client/ClientViewModel.js index 895b0b4..5ee587d 100644 --- a/src/client/ClientViewModel.js +++ b/src/client/ClientViewModel.js @@ -16,28 +16,28 @@ limitations under the License. import {isWebPlatform, Platform} from "./Platform.js"; import {ViewModel} from "../utils/ViewModel.js"; -import {getLabelForLinkKind} from "../Link.js"; export class ClientViewModel extends ViewModel { constructor(options) { super(options); - const {client, link} = options; + const {client, link, pickClient} = options; this._client = client; this._link = link; + this._pickClient = pickClient; 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)); + const webPlatform = matchingPlatforms.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.name = this._client.getName(this._proposedPlatform); this.deepLink = this._client.getDeepLink(this._proposedPlatform, this._link); + this._showOpen = this.deepLink && nativePlatform && !client.canInterceptMatrixToLinks(nativePlatform); } _createActions(client, link, nativePlatform, webPlatform) { @@ -65,6 +65,10 @@ export class ClientViewModel extends ViewModel { return actions; } + get identifier() { + return this._link.identifier; + } + get description() { return this._client.description; } @@ -77,11 +81,12 @@ export class ClientViewModel extends ViewModel { return this._showOpen ? "open" : "install"; } - get deepLinkLabel() { - return getLabelForLinkKind(this._link.kind); + get textInstructions() { + return this._client.getLinkInstructions(this._proposedPlatform, this._link); } deepLinkActivated() { + this._pickClient(this._client); this.preferences.setClient(this._client.id, this._proposedPlatform); if (this._showOpen) { this._showOpen = false; diff --git a/src/client/clients/Element.js b/src/client/clients/Element.js index 3db5fc2..152ee23 100644 --- a/src/client/clients/Element.js +++ b/src/client/clients/Element.js @@ -32,12 +32,10 @@ export class Element { ]; } - get description() { return 'Fully-featured Matrix client'; } + get description() { return 'Fully-featured Matrix client, used by millions.'; } getMaturity(platform) { return Maturity.Stable; } - getLinkSupport(platform, link) { return true; } - getDeepLink(platform, link) { let fragmentPath; switch (link.kind) { diff --git a/src/preview/PreviewView.js b/src/preview/PreviewView.js index 4698354..ffa0996 100644 --- a/src/preview/PreviewView.js +++ b/src/preview/PreviewView.js @@ -21,23 +21,19 @@ import {ClientView} from "../client/ClientView.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.h1({className: {hidden: vm => !vm.loading}}, "Loading preview…"), 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.h1(vm => vm.name), + t.p(vm => vm.identifier), ]), - t.p({className: {hidden: vm => !vm.canShowClients}}, t.button({ - className: "primary", + t.p({className: {hidden: vm => vm.clientsViewModel}}, t.button({ + className: "primary fullwidth", onClick: () => vm.showClients() }, vm => vm.showClientsLabel)), t.mapView(vm => vm.clientsViewModel, childVM => childVM ? new ClientListView(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}`), + t.p(["Preview provided by ", vm => vm.previewDomain]), ]) ]); } diff --git a/src/preview/PreviewViewModel.js b/src/preview/PreviewViewModel.js index b015718..eb1103b 100644 --- a/src/preview/PreviewViewModel.js +++ b/src/preview/PreviewViewModel.js @@ -35,9 +35,10 @@ export class PreviewViewModel extends ViewModel { this.previewDomain = null; this.clientsViewModel = null; this.acceptInstructions = null; - this.preferredClientViewModel = this._preferredClient ? new ClientViewModel(this.childOptions({ + this.clientsViewModel = this._preferredClient ? new ClientListViewModel(this.childOptions({ + clients: this._clients, client: this._preferredClient, - link: this._link + link: this._link, })) : null; } @@ -74,16 +75,12 @@ export class PreviewViewModel extends ViewModel { return this._link.identifier; } - get canShowClients() { - return !(this.preferredClientViewModel || this.clientsViewModel); - } - get showClientsLabel() { return getLabelForLinkKind(this._link.kind); } showClients() { - if (!this._preferredClient) { + if (!this.clientsViewModel) { this.clientsViewModel = new ClientListViewModel(this.childOptions({clients: this._clients, link: this._link})); this.emitChange(); }