diff --git a/css/style.css b/css/style.css index a58a307..e48bc12 100644 --- a/css/style.css +++ b/css/style.css @@ -420,6 +420,119 @@ a.underline { min-height: 100%; } +/* Share */ + +.modal__background { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + z-index: 9; + background: rgba(0, 0, 0, 0.3); +} + +.modal { + width: 100%; + position: relative; + background-color: #fff; + border-radius: 3px; + box-shadow: 0 0 0.5px rgba(0, 0, 0, 0.2), 0 2px 14px rgba(0, 0, 0, 0.15); +} + +.modal__header { + height: 36px; + background: #222; + color: white; + display: flex; + align-items: center; + justify-content: space-between; + padding-left: 12px; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} + +.share-modal { + max-width: 460px; + min-width: 300px; +} + +.share-modal .modal__body { + padding: 20px 30px 24px; + +} + +.modal__body h2 { + font-weight: normal; + text-transform: uppercase; + font-size: 11px; + letter-spacing: 1px; + margin-bottom: 14px; +} + +.form__input-wrapper { + display: flex; + background: #fff; + border: 1px solid #ddd; + border-radius: 3px; + padding-right: 6px; + margin-bottom: 16px; +} + +.form__input-wrapper.focus { + border: 1px solid #30c2ff !important; +} + +.form__input-wrapper.error { + border: 1px solid #f24822; +} + +.form__input { + border: 1px solid transparent; + flex-grow: 1; + width: 90px; + padding: 0 6px; + margin: 2px; + height: 24px; + outline: none; + font: inherit; + font-size: 11px; +} + +.form__input-error { + height: 24px; + line-height: 24px; + color: #f24822; + font-size: 11px; + text-align: right; + margin: 2px; +} + +.form__select-wrapper { + margin-bottom: 16px; +} + +.form__select { + -webkit-appearance: none; + -moz-appearance: none; + background: transparent; + background-image: url('data:image/svg+xml;utf8,'); + background-position: 100%; + background-repeat: no-repeat; + border: none; + outline: none; + padding-right: 12px; +} + +.form__select-label { + font-size: 11px; + margin-right: 14px; +} + + /* Docs */ .docs { diff --git a/js/editor.js b/js/editor.js index b18848a..3829994 100644 --- a/js/editor.js +++ b/js/editor.js @@ -403,10 +403,82 @@ class Editor extends Component { Editor.prototype.onInput = debounce(Editor.prototype.onInput, 200); +class FormInput extends Component { + constructor(props) { + super(props); + this.state = { focus: false }; + } + + onFocus() { + this.setState({ focus: true }); + } + + onBlur() { + this.setState({ focus: false }); + } + + render(props, state) { + let errorDiv; + if (props.error) { + errorDiv = h('div', {class: 'form__input-error'}, props.error); + + } + return h('div', {class: 'form__input-wrapper' + (props.error ? ' error' : '') + (state.focus ? ' focus' : '')}, + h('input', {class: 'form__input' , placeholder: props.placeholder, value: props.value, onFocus: this.onFocus.bind(this), onBlur: this.onBlur.bind(this)}), + errorDiv + ); + } +} + +class FormSelect extends Component { + constructor(props) { + super(props); + } + + render(props, state) { + let options = []; + console.assert(props.values.length === props.labels.length); + for (let i = 0; i < props.values.length; i++) { + options.push(h('option', {value: props.values[i]}, props.labels[i])); + } + return h('div', {class: 'form__select-wrapper' + (props.error ? ' error' : '')}, + h('span', {class: 'form__select-label'}, props.label), + h('select', {class: 'form__select'}, options) + ); + } +} + +class ShareModal extends Component { + constructor(props) { + super(props); + this.state = { }; + } + + onPreventBubble(e) { + e.stopPropagation(); + } + + render(props, state) { + return h('div', {class: 'modal__background', onClick: this.props.onCancel}, + h('div', {class: 'modal share-modal', onClick: this.onPreventBubble.bind(this)}, + h('div', {class: 'modal__header'}, 'Share'), + h('div', {class: 'modal__body'}, + h('h2', {}, 'Twitter Bot'), + h('form', {class: 'modal__form'}, + h(FormSelect, {label: 'Frequency:', values: [60, 120, 240, 360, 720], labels: ['every hour', 'every 2 hours', 'every 4 hours', 'every 6 hours', 'every 12 hours']}), + h(FormInput, {class: 'error', placeholder: 'OAuth Key', error: 'Invalid key'}), + h(FormInput, {placeholder: 'OAuth Secret'}) + ) + ) + ) + ); + } +} + class Sketch extends Component { constructor(props) { super(props); - this.state = { saving: false, unsaved: false, source: undefined, seed: undefined }; + this.state = { saving: false, unsaved: false, source: undefined, seed: undefined, showShareModal: false }; } onImportError() { @@ -452,9 +524,9 @@ class Sketch extends Component { sketch.source = this.state.source; sketch.seed = this.state.seed; if (this.props.id) sketch.parent = this.props.id; - const res = await fetch(new Request(`${SKETCH_REST_URL}.json`), { + const res = await fetch(new Request(`${SKETCH_REST_URL}.json`), { method: 'POST', - body: JSON.stringify(sketch), + body: JSON.stringify(sketch), headers: new Headers({'Content-Type': 'application/json'}) }); const json = await res.json(); @@ -470,8 +542,16 @@ class Sketch extends Component { } } + onCancelPopup() { + this.setState({ showShareModal: false }); + } + render(props, state) { let saveLabel = state.saving ? 'Saving...' : 'Save'; + let shareModal; + if (state.showShareModal) { + shareModal = h(ShareModal, { onCancel: this.onCancelPopup.bind(this) }); + } return h('div', {class: 'app'}, h(Header, { unsaved: !!state.unsaved }, h('button', {class: 'button save-button' + (state.unsaved ? ' unsaved' : '') + (state.importError ? ' disabled': ''), onClick: this.onSave.bind(this), disabled: state.saving}, saveLabel) @@ -485,7 +565,8 @@ class Sketch extends Component { onSourceChanged: this.onSourceChanged.bind(this), onSeedChanged: this.onSeedChanged.bind(this), headerRight: h('a', { href:'/docs', target: '_blank' }, 'Documentation') - }) + }), + shareModal ); } }