1// Copyright (C) 2019 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import * as MicroModal from 'micromodal';
16import * as m from 'mithril';
17
18// We need any here so we can accept vnodes with arbitrary attrs.
19// tslint:disable-next-line:no-any
20export type AnyAttrsVnode = m.Vnode<any, {}>;
21
22interface ModalDefinition {
23  title: string;
24  content: AnyAttrsVnode;
25  buttons: Button[];
26}
27
28export interface Button {
29  text: string;
30  primary: boolean;
31  id: string;
32  action: () => void;
33}
34
35// We need to create a div outside of the mithril's render root (<main>), that's
36// why the manual DOM manipulation.
37function getOrCreateDOM() {
38  let div = document.getElementById('main-modal') as HTMLElement;
39  if (div) return div;
40  div = document.createElement('div');
41  div.id = 'main-modal';
42  div.classList.add('modal');
43  div.classList.add('micromodal-slide');
44  (div as {} as {ariaHidden: boolean}).ariaHidden = true;
45  document.body.appendChild(div);
46  MicroModal.init();
47  return div;
48}
49
50export async function showModal(attrs: ModalDefinition): Promise<void> {
51  const modal = getOrCreateDOM();
52  m.render(
53      modal,
54      m('.modal-overlay[data-micromodal-close]',
55        {tabindex: -1},
56        m('.modal-container[aria-labelledby=mm-title][aria-model][role=dialog]',
57          m('header.modal-header',
58            m('h2.modal-title', {id: 'mm-title'}, attrs.title),
59            m('button.modal-close[aria-label=Close Modal]' +
60              '[data-micromodal-close]')),
61          m('main.modal-content', attrs.content),
62          m('footer.modal-footer', ...makeButtons(attrs.buttons)))));
63  return new Promise(resolve => {
64    MicroModal.show(
65        'main-modal', {onClose: () => resolve(), awaitCloseAnimation: true});
66  });
67}
68
69export function hideModel() {
70  MicroModal.close();
71}
72
73function makeButtons(buttonDefinition: Button[]): Array<m.Vnode<Button>> {
74  const buttons: Array<m.Vnode<Button>> = [];
75  buttonDefinition.forEach(button => {
76    buttons.push(
77        m('button[data-micromodal-close].modal-btn',
78          {
79            class: button.primary ? 'modal-btn-primary' : '',
80            id: button.id,
81            onclick: button.action
82          },
83          button.text));
84  });
85  return buttons;
86}
87