UNPKG

saepenatus

Version:

Web3-Onboard makes it simple to connect Ethereum hardware and software wallets to your dapp. Features standardised spec compliant web3 providers for all supported wallets, framework agnostic modern javascript UI with code splitting, CSS customization, mul

329 lines (301 loc) 7.91 kB
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' ) => ` <button class="pin-button" style="width: ${width}; height: ${height};" type="button" onclick="window.${HANDLE_PIN_PRESS}(${value})"> ${ slot || `<svg class="pin-button-dot" viewBox="0 0 18 18" width="18" height="18"> <circle cx="9" cy="9" r="9" ></circle> </svg>` } <div class="pin-button-bg"> </button> ` const pinButtons = ` <div class="pin-pad-buttons"> ${[7, 8, 9, 4, 5, 6, 1, 2, 3].map(val => pinButton(val)).join('')} </div> ` const delButtonIcon = `<svg class="del-button-icon" viewBox="0 0 24 24" focusable="false" class="chakra-icon css-onkibi" aria-hidden="true"><path fill="currentColor" d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"></path></svg>` const pinPhraseInput = (modalType: string) => ` <form id="pin-phrase-form" class="pin-phrase-input-container"> <input id="pin-phrase-input" placeholder="${modalType === 'pin' ? 'PIN' : ''}" type="password" autocomplete="current-password" /> ${ modalType === 'pin' ? ` <div class="del-button-wrapper"> ${pinButton(-1, delButtonIcon, '38px', '38px')} </div>` : '' } </form> ` // 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 = ` <style>${baseStyles}${pinModalStyles}</style> <h2>Enter Your Pin</h2> <p> Use PIN layout shown on your device to find the location to press on this pin pad. </p> <div id="entry" class="bn-onboard-custom"> ${pinButtons} ${pinPhraseInput('pin')} </div> ` const passphraseHTML = ` <style>${baseStyles}${passphraseModalStyles}</style> <h2 style="margin-bottom: 35px">Enter Your Passphrase</h2> <div id="entry" class="bn-onboard-custom"> ${pinPhraseInput('passphrase')} </div> ` 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 } } ] } }