import { detach, insert, noop, SvelteComponentDev } from 'svelte/internal' import Modal from './elements/Modal.svelte' import Button from './elements/Button.svelte' const HANDLE_PIN_PRESS = 'handlePinPress' const BUTTON_COLOR = `#EBEBED` const BUTTON_DOT_COLOR = `#33394B` interface Slot { (): { c: () => void m: (target: any, anchor: any) => void d: (detaching: any) => void l: () => void } } interface Slots { default: Slot[] } const pinButton = ( value: number, slot?: string, width = '64px', height = '64px' ) => ` ` const pinButtons = `
${[7, 8, 9, 4, 5, 6, 1, 2, 3].map(val => pinButton(val)).join('')}
` const delButtonIcon = `` const pinPhraseInput = (modalType: string) => `
${ modalType === 'pin' ? `
${pinButton(-1, delButtonIcon, '38px', '38px')}
` : '' }
` // Contains styles used by both the pin // entry modal and the passphrase entry modal const baseStyles = ` .keepkey-modal { max-width: 22rem; padding: 20px 10px; } .pin-phrase-input-container { display: flex; position: relative; align-items: center; margin: 20px 0; width: 100%; } #pin-phrase-input { background: inherit; font-size: 0.889em; font-family: inherit; border-width: 1px; border-style: solid; border-color: #242835; border-radius: 4px; padding-left: 0.5rem; padding-right: 4.1rem; transition: opacity 150ms ease-in-out; height: 42px; width: 100%; opacity: 0.6; outline: none; } #pin-phrase-input:hover, #pin-phrase-input:focus { opacity: 1; } .unlock-button { height: 26px; display: flex; align-items: center; width: 100%; justify-content: center; } /* Overrides the branding on the modal*/ .keepkey-modal + .bn-branding { visibility: hidden !important; } .keepkey-modal .bn-onboard-prepare-button { width: 100%; } ` const pinModalStyles = ` #entry { align-items: center; display: flex; flex-flow: column; padding: 20px; } .pin-pad-buttons { display: grid; grid-template-columns: repeat(3, 75px); grid-template-rows: repeat(3, 75px); align-items: center; justify-items: center; margin-bottom: 15px; } .pin-button { align-items: center; border-radius: 6px; border: 1px solid ${BUTTON_COLOR}; cursor: pointer; display: flex; justify-content: center; font-size: 18px; overflow: hidden; padding: 0; background-color: unset; overflow: hidden; } .pin-button-bg { width: 100%; height: 100%; display: flex; overflow: hidden; background-color: ${BUTTON_COLOR}; transition: opacity 100ms ease-in; } .pin-button-bg:hover { opacity: .2; } .pin-button-dot { fill: ${BUTTON_DOT_COLOR}; position: absolute; pointer-events: none; z-index: 2; } .del-button-wrapper { position: absolute; height: 42px; width: 42px; right: 2px; display: flex; align-items: center; justify-content: center; } .del-button-wrapper > .pin-button { border: none; } .del-button-icon { position: absolute; width: 20px; z-index: 2; pointer-events: none; } .del-button-icon + div { opacity: .5; } .del-button-icon + div:hover { opacity: 1; } ` const passphraseModalStyles = ` .keepkey-modal { padding: 40px 30px; } ` const pinHTML = `

Enter Your Pin

Use PIN layout shown on your device to find the location to press on this pin pad.

${pinButtons} ${pinPhraseInput('pin')}
` const passphraseHTML = `

Enter Your Passphrase

${pinPhraseInput('passphrase')}
` export const entryModal = ( modalType: string, submit: (value: string) => void, cancel: () => void, containerElement?: string ): void => { const modalHtml = modalType === 'pin' ? pinHTML : passphraseHTML const getInput = () => document.getElementById('pin-phrase-input') as HTMLInputElement const deleteWindowProperties = () => { delete (window as any)[HANDLE_PIN_PRESS] } if (modalType === 'pin') { ;(window as any)[HANDLE_PIN_PRESS] = (value: number) => { const input = getInput() // A value of -1 signals a backspace // e.g. we delete the last char from the input input.value = value === -1 ? input.value.slice(0, -1) : input.value + value } } const containerElementQuery = containerElement || 'body' const containerEl = document.querySelector(containerElementQuery) if (!containerEl) { throw new Error( `Element with query ${containerElementQuery} does not exist.` ) } // Creates a modal component which gets // mounted to the body and is passed the pin html into it's slot const div = document.createElement('div') div.innerHTML = modalHtml div.className = 'keepkey-modal' const pinModal = new Modal({ target: containerEl, props: { closeModal: () => { // Cancels any action that the keepkey wallet may be doing cancel() deleteWindowProperties() pinModal.$destroy() }, $$slots: createSlot(div), $$scope: {} } } as SvelteComponentDev['new']) // Submits the pin or passphrase to the Keepkey device const submitValue = async () => { const value = getInput().value submit(value) pinModal.$destroy() } const pinPhraseForm = document.getElementById('pin-phrase-form') pinPhraseForm && pinPhraseForm.addEventListener('submit', e => { e.preventDefault() submitValue() }) // Creates a new Button component used to trigger sending the pin to Keepkey const entryEl = document.getElementById('entry') if (entryEl) { const span = document.createElement('span') span.innerHTML = `Unlock` span.className = `unlock-button` new Button({ target: entryEl, props: { onclick: async () => { submitValue() deleteWindowProperties() }, $$slots: createSlot(span), $$scope: {} } } as SvelteComponentDev['new']) } } /** * createSlot - creates the necessary object needed to pass * arbitrary html into a component's default slot * @param element The html element which is inserted into the components slot */ function createSlot(element: HTMLElement): Slots { return { default: [ function () { return { c: noop, m: function mount(target: any, anchor: any) { insert(target, element, anchor) }, d: function destroy(detaching: any) { if (detaching) { detach(element) } }, l: noop } } ] } }