implement flow refinements as discussed with Nad,add text client support

This commit is contained in:
Bruno Windels 2020-12-01 19:01:15 +01:00
parent cf489284c9
commit 1caa9b1bf3
8 changed files with 130 additions and 86 deletions

View file

@ -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 {

View file

@ -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)))
]);
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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) {

View file

@ -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]),
])
]);
}

View file

@ -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();
}