implement flow refinements as discussed with Nad,add text client support
This commit is contained in:
parent
cf489284c9
commit
1caa9b1bf3
41
css/main.css
41
css/main.css
@ -70,19 +70,14 @@ textarea {
|
|||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.PreviewView .preview {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.PreviewView .avatar {
|
.PreviewView .avatar {
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.PreviewView .preview {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.PreviewView .profileInfo {
|
|
||||||
flex: 1;
|
|
||||||
margin-left: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hidden {
|
.hidden {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
@ -139,24 +134,30 @@ button.text:hover {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ClientListView ul {
|
.ClientListView .list {
|
||||||
list-style: none;
|
padding: 16px 0;
|
||||||
padding: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ListedClientView .header {
|
.ClientView {
|
||||||
|
border: 1px solid #E6E6E6;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 16px 0;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ClientView .header {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ListedClientView .description {
|
.ClientView .description {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ListedClientView h3 {
|
.ClientView h3 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ListedClientView .icon {
|
.ClientView .icon {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
@ -164,8 +165,7 @@ button.text:hover {
|
|||||||
height: 60px;
|
height: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ClientView .icon.element-io {
|
||||||
.ListedClientView .icon.element-io {
|
|
||||||
background-image: url('../images/client-icons/element.svg');
|
background-image: url('../images/client-icons/element.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,12 +195,13 @@ button.primary, a.primary {
|
|||||||
|
|
||||||
button.primary, button.secondary {
|
button.primary, button.secondary {
|
||||||
border: none;
|
border: none;
|
||||||
width: 100%;
|
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.PreviewView .primary, .PreviewView .secondary {
|
.fullwidth {
|
||||||
display: block;
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ClientView .actions a.badge {
|
.ClientView .actions a.badge {
|
||||||
|
@ -15,14 +15,38 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {TemplateView} from "../utils/TemplateView.js";
|
import {TemplateView} from "../utils/TemplateView.js";
|
||||||
import {ListedClientView} from "./ClientView.js";
|
import {ClientView} 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 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"}, [
|
return t.div({className: "ClientListView"}, [
|
||||||
t.h3("You need an app to continue"),
|
t.h2("Choose an app to continue"),
|
||||||
t.div({className: "ClientListView"}, clients)
|
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)))
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,25 @@ import {ViewModel} from "../utils/ViewModel.js";
|
|||||||
export class ClientListViewModel extends ViewModel {
|
export class ClientListViewModel extends ViewModel {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
const {clients, link} = options;
|
const {clients, client, link} = options;
|
||||||
this.clients = clients.map(client => new ClientViewModel(this.childOptions({client, link, showAcceptAnyway: true})));
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,9 @@ limitations under the License.
|
|||||||
|
|
||||||
import {TemplateView} from "../utils/TemplateView.js";
|
import {TemplateView} from "../utils/TemplateView.js";
|
||||||
|
|
||||||
export class ListedClientView extends TemplateView {
|
export class ClientView extends TemplateView {
|
||||||
render(t, vm) {
|
render(t, vm) {
|
||||||
return t.div({className: "ListedClientView"}, [
|
return t.div({className: "ClientView"}, [
|
||||||
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,14 +26,6 @@ export class ListedClientView 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 => {
|
t.mapView(vm => vm.stage, stage => {
|
||||||
switch (stage) {
|
switch (stage) {
|
||||||
case "open": return new OpenClientView(vm);
|
case "open": return new OpenClientView(vm);
|
||||||
@ -48,38 +40,51 @@ class OpenClientView extends TemplateView {
|
|||||||
render(t, vm) {
|
render(t, vm) {
|
||||||
return t.div({className: "OpenClientView"}, [
|
return t.div({className: "OpenClientView"}, [
|
||||||
t.a({
|
t.a({
|
||||||
className: "primary",
|
className: "primary fullwidth",
|
||||||
href: vm.deepLink,
|
href: vm.deepLink,
|
||||||
rel: "noopener noreferrer",
|
rel: "noopener noreferrer",
|
||||||
onClick: () => vm.deepLinkActivated(),
|
onClick: () => vm.deepLinkActivated(),
|
||||||
}, vm.deepLinkLabel)
|
}, "Continue")
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InstallClientView extends TemplateView {
|
class InstallClientView extends TemplateView {
|
||||||
|
|
||||||
|
copyToClipboard() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
render(t, vm) {
|
render(t, vm) {
|
||||||
return t.div({className: "InstallClientView"}, [
|
const children = [];
|
||||||
t.h3(`Looks like you don't have ${vm.name} installed.`),
|
|
||||||
t.div({className: "actions"}, vm.actions.map(a => {
|
if (vm.textInstructions) {
|
||||||
let badgeUrl;
|
const copy = t.button({className: "primary", onClick: evt => this.copyToClipboard(evt)}, "Copy");
|
||||||
switch (a.kind) {
|
children.push(t.p([vm.textInstructions, copy]));
|
||||||
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;
|
const actions = t.div({className: "actions"}, vm.actions.map(a => {
|
||||||
}
|
let badgeUrl;
|
||||||
return t.a({
|
switch (a.kind) {
|
||||||
href: a.url,
|
case "play-store": badgeUrl = "images/google-play-us.svg"; break;
|
||||||
className: {
|
case "fdroid": badgeUrl = "images/fdroid-badge.png"; break;
|
||||||
primary: a.primary && !badgeUrl,
|
case "apple-app-store": badgeUrl = "images/app-store-us-alt.svg"; break;
|
||||||
secondary: !a.primary && !badgeUrl,
|
}
|
||||||
badge: !!badgeUrl,
|
return t.a({
|
||||||
},
|
href: a.url,
|
||||||
rel: "noopener noreferrer",
|
className: {
|
||||||
["aria-label"]: a.label,
|
fullwidth: true,
|
||||||
onClick: () => a.activated()
|
primary: a.primary && !badgeUrl,
|
||||||
}, badgeUrl ? t.img({src: badgeUrl}) : a.label);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -16,28 +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, pickClient} = options;
|
||||||
this._client = client;
|
this._client = client;
|
||||||
this._link = link;
|
this._link = link;
|
||||||
|
this._pickClient = pickClient;
|
||||||
|
|
||||||
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 = matchingPlatforms.find(p => isWebPlatform(p));
|
||||||
|
|
||||||
this._showOpen = nativePlatform && !client.canInterceptMatrixToLinks(nativePlatform);
|
|
||||||
this._proposedPlatform = this.preferences.platform || nativePlatform || webPlatform;
|
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(this._proposedPlatform);
|
this.name = this._client.getName(this._proposedPlatform);
|
||||||
this.deepLink = this._client.getDeepLink(this._proposedPlatform, this._link);
|
this.deepLink = this._client.getDeepLink(this._proposedPlatform, this._link);
|
||||||
|
this._showOpen = this.deepLink && nativePlatform && !client.canInterceptMatrixToLinks(nativePlatform);
|
||||||
}
|
}
|
||||||
|
|
||||||
_createActions(client, link, nativePlatform, webPlatform) {
|
_createActions(client, link, nativePlatform, webPlatform) {
|
||||||
@ -65,6 +65,10 @@ export class ClientViewModel extends ViewModel {
|
|||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get identifier() {
|
||||||
|
return this._link.identifier;
|
||||||
|
}
|
||||||
|
|
||||||
get description() {
|
get description() {
|
||||||
return this._client.description;
|
return this._client.description;
|
||||||
}
|
}
|
||||||
@ -77,11 +81,12 @@ export class ClientViewModel extends ViewModel {
|
|||||||
return this._showOpen ? "open" : "install";
|
return this._showOpen ? "open" : "install";
|
||||||
}
|
}
|
||||||
|
|
||||||
get deepLinkLabel() {
|
get textInstructions() {
|
||||||
return getLabelForLinkKind(this._link.kind);
|
return this._client.getLinkInstructions(this._proposedPlatform, this._link);
|
||||||
}
|
}
|
||||||
|
|
||||||
deepLinkActivated() {
|
deepLinkActivated() {
|
||||||
|
this._pickClient(this._client);
|
||||||
this.preferences.setClient(this._client.id, this._proposedPlatform);
|
this.preferences.setClient(this._client.id, this._proposedPlatform);
|
||||||
if (this._showOpen) {
|
if (this._showOpen) {
|
||||||
this._showOpen = false;
|
this._showOpen = false;
|
||||||
|
@ -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; }
|
getMaturity(platform) { return Maturity.Stable; }
|
||||||
|
|
||||||
getLinkSupport(platform, link) { return true; }
|
|
||||||
|
|
||||||
getDeepLink(platform, link) {
|
getDeepLink(platform, link) {
|
||||||
let fragmentPath;
|
let fragmentPath;
|
||||||
switch (link.kind) {
|
switch (link.kind) {
|
||||||
|
@ -21,23 +21,19 @@ import {ClientView} from "../client/ClientView.js";
|
|||||||
export class PreviewView extends TemplateView {
|
export class PreviewView extends TemplateView {
|
||||||
render(t, vm) {
|
render(t, vm) {
|
||||||
return t.div({className: "PreviewView card"}, [
|
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: {hidden: vm => vm.loading}}, [
|
||||||
t.div({className: "preview"}, [
|
t.div({className: "preview"}, [
|
||||||
t.p(t.img({className: "avatar", src: vm => vm.avatarUrl})),
|
t.p(t.img({className: "avatar", src: vm => vm.avatarUrl})),
|
||||||
t.div({className: "profileInfo"}, [
|
t.h1(vm => vm.name),
|
||||||
t.h2(vm => vm.name),
|
t.p(vm => vm.identifier),
|
||||||
t.p(vm => vm.identifier),
|
|
||||||
t.p(["Preview from ", vm => vm.previewDomain]),
|
|
||||||
]),
|
|
||||||
]),
|
]),
|
||||||
t.p({className: {hidden: vm => !vm.canShowClients}}, t.button({
|
t.p({className: {hidden: vm => vm.clientsViewModel}}, t.button({
|
||||||
className: "primary",
|
className: "primary fullwidth",
|
||||||
onClick: () => vm.showClients()
|
onClick: () => vm.showClients()
|
||||||
}, vm => vm.showClientsLabel)),
|
}, 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.preferredClientViewModel, childVM => childVM ? new ClientView(childVM) : null),
|
t.p(["Preview provided by ", vm => vm.previewDomain]),
|
||||||
t.p({className: {hidden: vm => !vm.preferredClientViewModel}}, vm => `This will open in ${vm.preferredClientViewModel?.name}`),
|
|
||||||
])
|
])
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -35,9 +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.preferredClientViewModel = this._preferredClient ? new ClientViewModel(this.childOptions({
|
this.clientsViewModel = this._preferredClient ? new ClientListViewModel(this.childOptions({
|
||||||
|
clients: this._clients,
|
||||||
client: this._preferredClient,
|
client: this._preferredClient,
|
||||||
link: this._link
|
link: this._link,
|
||||||
})) : null;
|
})) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,16 +75,12 @@ export class PreviewViewModel extends ViewModel {
|
|||||||
return this._link.identifier;
|
return this._link.identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
get canShowClients() {
|
|
||||||
return !(this.preferredClientViewModel || this.clientsViewModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
get showClientsLabel() {
|
get showClientsLabel() {
|
||||||
return getLabelForLinkKind(this._link.kind);
|
return getLabelForLinkKind(this._link.kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
showClients() {
|
showClients() {
|
||||||
if (!this._preferredClient) {
|
if (!this.clientsViewModel) {
|
||||||
this.clientsViewModel = new ClientListViewModel(this.childOptions({clients: this._clients, link: this._link}));
|
this.clientsViewModel = new ClientListViewModel(this.childOptions({clients: this._clients, link: this._link}));
|
||||||
this.emitChange();
|
this.emitChange();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user