diff --git a/css/main.css b/css/main.css index 16048ce..668224a 100644 --- a/css/main.css +++ b/css/main.css @@ -144,19 +144,19 @@ button.text:hover { padding: 0; } -.ClientView .header { +.ListedClientView .header { display: flex; } -.ClientView .description { +.ListedClientView .description { flex: 1; } -.ClientView h3 { +.ListedClientView h3 { margin-top: 0; } -.ClientView .icon { +.ListedClientView .icon { border-radius: 8px; background-repeat: no-repeat; background-size: cover; @@ -165,27 +165,43 @@ button.text:hover { } -.ClientView .icon.element-io { +.ListedClientView .icon.element-io { 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 { background: var(--link); color: var(--background); 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; - text-align: center; - padding: 8px; - margin: 8px; } .ClientView .actions a.badge { - display: inline-block; + display: inline-block; height: 40px; margin: 8px 16px 8px 0; } diff --git a/src/Link.js b/src/Link.js index 91e4851..596e6eb 100644 --- a/src/Link.js +++ b/src/Link.js @@ -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( "Room", "User", diff --git a/src/client/ClientListView.js b/src/client/ClientListView.js index 8a62cea..33d8215 100644 --- a/src/client/ClientListView.js +++ b/src/client/ClientListView.js @@ -15,11 +15,11 @@ limitations under the License. */ import {TemplateView} from "../utils/TemplateView.js"; -import {ClientView} from "./ClientView.js"; +import {ListedClientView} from "./ClientView.js"; export class ClientListView extends TemplateView { 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"}, [ t.h3("You need an app to continue"), t.div({className: "ClientListView"}, clients) diff --git a/src/client/ClientView.js b/src/client/ClientView.js index 3bf3784..03d8a84 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 ClientView extends TemplateView { +export class ListedClientView extends TemplateView { render(t, vm) { - return t.div({className: "ClientView"}, [ + return t.div({className: "ListedClientView"}, [ t.div({className: "header"}, [ t.div({className: "description"}, [ t.h3(vm.name), @@ -26,6 +26,41 @@ export class ClientView 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); + 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 => { let badgeUrl; switch (a.kind) { diff --git a/src/client/ClientViewModel.js b/src/client/ClientViewModel.js index 683c6f4..895b0b4 100644 --- a/src/client/ClientViewModel.js +++ b/src/client/ClientViewModel.js @@ -16,20 +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; this._client = client; + this._link = link; + 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._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(nativePlatform || webPlatform); + this.name = this._client.getName(this._proposedPlatform); + this.deepLink = this._client.getDeepLink(this._proposedPlatform, this._link); } _createActions(client, link, nativePlatform, webPlatform) { @@ -64,4 +72,34 @@ export class ClientViewModel extends ViewModel { get clientId() { return this._client.id; } -} \ No newline at end of file + + 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); + } + */ \ No newline at end of file diff --git a/src/client/Platform.js b/src/client/Platform.js index a5b3c15..19899a1 100644 --- a/src/client/Platform.js +++ b/src/client/Platform.js @@ -29,6 +29,7 @@ export const Platform = createEnum( export function guessApplicablePlatforms(userAgent) { // use https://github.com/faisalman/ua-parser-js to guess, and pass as RootVM options return [Platform.DesktopWeb, Platform.Linux]; + //return [Platform.MobileWeb, Platform.iOS]; } export function isWebPlatform(p) { diff --git a/src/client/clients/Element.js b/src/client/clients/Element.js index 13f5db5..3db5fc2 100644 --- a/src/client/clients/Element.js +++ b/src/client/clients/Element.js @@ -78,4 +78,8 @@ export class Element { default: return [new WebsiteLink("https://element.io/get-started")]; } } + + canInterceptMatrixToLinks(platform) { + return platform === Platform.iOS || platform === Platform.Android; + } } \ No newline at end of file diff --git a/src/preview/PreviewView.js b/src/preview/PreviewView.js index ca76446..4698354 100644 --- a/src/preview/PreviewView.js +++ b/src/preview/PreviewView.js @@ -31,19 +31,14 @@ export class PreviewView extends TemplateView { 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.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)), - ]); - } -} \ No newline at end of file diff --git a/src/preview/PreviewViewModel.js b/src/preview/PreviewViewModel.js index 74aac1f..b015718 100644 --- a/src/preview/PreviewViewModel.js +++ b/src/preview/PreviewViewModel.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {LinkKind} from "../Link.js"; +import {LinkKind, getLabelForLinkKind} from "../Link.js"; import {ViewModel} from "../utils/ViewModel.js"; import {resolveServer} from "./HomeServer.js"; import {ClientListViewModel} from "../client/ClientListViewModel.js"; @@ -35,7 +35,10 @@ export class PreviewViewModel extends ViewModel { this.previewDomain = null; this.clientsViewModel = null; this.acceptInstructions = null; - this.missingClientViewModel = null; + this.preferredClientViewModel = this._preferredClient ? new ClientViewModel(this.childOptions({ + client: this._preferredClient, + link: this._link + })) : null; } async load() { @@ -71,31 +74,18 @@ export class PreviewViewModel extends ViewModel { return this._link.identifier; } - get acceptLabel() { - if (this._preferredClient) { - return `Open in ${this._preferredClient.getName(this.preferences.platform)}`; - } else { - return "Choose app"; - } + get canShowClients() { + return !(this.preferredClientViewModel || this.clientsViewModel); } - accept() { - if (this._preferredClient) { - 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); - } - } else { + get showClientsLabel() { + return getLabelForLinkKind(this._link.kind); + } + + showClients() { + if (!this._preferredClient) { this.clientsViewModel = new ClientListViewModel(this.childOptions({clients: this._clients, link: this._link})); - // show client list + this.emitChange(); } - this.emitChange(); } } \ No newline at end of file