UNPKG

react-native-photos-framework

Version:

Use Apples Photos Framework with react-native to fetch media from CameraRoll and iCloud

467 lines (443 loc) 13.1 kB
/** * Copyright (c) 2016-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ 'use strict'; /*eslint no-console-disallow: "off"*/ /*global React:true*/ // TODO: // selection and arrow keys for navigating const rowHeight = 20; const treeIndent = 16; class Draggable extends React.Component { // eslint-disable-line no-unused-vars constructor(props) { super(props); } render() { const id = this.props.id; function dragStart(e) { e.dataTransfer.setData('text/plain', id); } return React.cloneElement( this.props.children, { draggable: 'true', onDragStart: dragStart } ); } } Draggable.propTypes = { children: React.PropTypes.element.isRequired, id: React.PropTypes.string.isRequired, }; class DropTarget extends React.Component { // eslint-disable-line no-unused-vars constructor(props) { super(props); } render() { const thisId = this.props.id; const dropFilter = this.props.dropFilter; const dropAction = this.props.dropAction; return React.cloneElement( this.props.children, { onDragOver: (e) => { const sourceId = e.dataTransfer.getData('text/plain'); if (dropFilter(sourceId)) { e.preventDefault(); } }, onDrop: (e) => { const sourceId = e.dataTransfer.getData('text/plain'); if (dropFilter(sourceId)) { e.preventDefault(); dropAction(sourceId, thisId); } }, } ); } } DropTarget.propTypes = { children: React.PropTypes.element.isRequired, id: React.PropTypes.string.isRequired, dropFilter: React.PropTypes.func.isRequired, dropAction: React.PropTypes.func.isRequired, }; class TableHeader extends React.Component { constructor(props) { super(props); } render() { const aggrow = this.props.aggrow; const aggregators = aggrow.getActiveAggregators(); const expanders = aggrow.getActiveExpanders(); const headers = []; for (let i = 0; i < aggregators.length; i++) { const name = aggrow.getAggregatorName(aggregators[i]); headers.push(( <DropTarget id={'aggregate:insert:' + i.toString()} dropFilter={(s) => s.startsWith('aggregate')} dropAction={this.props.dropAction} > <div style={{ width: '16px', height: 'inherit', backgroundColor: 'darkGray', flexShrink: '0' }} ></div> </DropTarget>)); headers.push((<Draggable id={'aggregate:active:' + i.toString()}> <div style={{ width: '128px', textAlign: 'center', flexShrink: '0' }}>{name}</div> </Draggable>)); } headers.push(( <DropTarget id="divider:insert" dropFilter={(s) => s.startsWith('aggregate') || s.startsWith('expander')} dropAction={this.props.dropAction} > <div style={{ width: '16px', height: 'inherit', backgroundColor: 'gold', flexShrink: '0' }}></div> </DropTarget>)); for (let i = 0; i < expanders.length; i++) { const name = aggrow.getExpanderName(expanders[i]); const bg = (i % 2 === 0) ? 'white' : 'lightGray'; headers.push((<Draggable id={'expander:active:' + i.toString()}> <div style={{ width: '128px', textAlign: 'center', backgroundColor: bg, flexShrink: '0' }}> {name} </div> </Draggable>)); const sep = i + 1 < expanders.length ? '->' : '...'; headers.push(( <DropTarget id={'expander:insert:' + (i + 1).toString()} dropFilter={()=>{return true; }} dropAction={this.props.dropAction} > <div style={{ height: 'inherit', backgroundColor: 'darkGray', flexShrink: '0' }}> {sep} </div> </DropTarget>) ); } return ( <div style={{ width: '100%', height: '26px', display: 'flex', flexDirection: 'row', alignItems: 'center', borderBottom: '2px solid black', }}> {headers} </div> ); } } TableHeader.propTypes = { aggrow: React.PropTypes.object.isRequired, dropAction: React.PropTypes.func.isRequired, }; class Table extends React.Component { // eslint-disable-line no-unused-vars constructor(props) { super(props); this.state = { aggrow: props.aggrow, viewport: { top: 0, height: 100 }, cursor: 0, }; } scroll(e) { const viewport = e.target; const top = Math.floor((viewport.scrollTop - viewport.clientHeight * 1.0) / rowHeight); const height = Math.ceil(viewport.clientHeight * 3.0 / rowHeight); if (top !== this.state.viewport.top || height !== this.state.viewport.height) { this.setState({viewport: {top, height}}); } } _contractRow(row) { let newCursor = this.state.cursor; if (newCursor > row.top && newCursor < row.top + row.height) { // in contracted section newCursor = row.top; } else if (newCursor >= row.top + row.height) { // below contracted section newCursor -= row.height - 1; } this.state.aggrow.contract(row); this.setState({cursor: newCursor}); console.log('-' + row.top); } _expandRow(row) { let newCursor = this.state.cursor; this.state.aggrow.expand(row); if (newCursor > row.top) { // below expanded section newCursor += row.height - 1; } this.setState({cursor: newCursor}); console.log('+' + row.top); } _scrollDiv: null; _keepCursorInViewport() { if (this._scrollDiv) { const cursor = this.state.cursor; const scrollDiv = this._scrollDiv; if (cursor * rowHeight < scrollDiv.scrollTop + scrollDiv.clientHeight * 0.1) { scrollDiv.scrollTop = cursor * rowHeight - scrollDiv.clientHeight * 0.1; } else if ((cursor + 1) * rowHeight > scrollDiv.scrollTop + scrollDiv.clientHeight * 0.9) { scrollDiv.scrollTop = (cursor + 1) * rowHeight - scrollDiv.clientHeight * 0.9; } } } keydown(e) { const aggrow = this.state.aggrow; let cursor = this.state.cursor; let row = aggrow.getRows(cursor, 1)[0]; switch (e.keyCode) { case 38: // up if (cursor > 0) { this.setState({cursor: cursor - 1}); this._keepCursorInViewport(); } e.preventDefault(); break; case 40: // down if (cursor < aggrow.getHeight() - 1) { this.setState({cursor: cursor + 1}); this._keepCursorInViewport(); } e.preventDefault(); break; case 37: // left if (aggrow.canContract(row)) { this._contractRow(row); } else if (aggrow.getRowIndent(row) > 0) { const indent = aggrow.getRowIndent(row) - 1; while (aggrow.getRowIndent(row) > indent) { cursor--; row = aggrow.getRows(cursor, 1)[0]; } this.setState({cursor: cursor}); this._keepCursorInViewport(); } e.preventDefault(); break; case 39: // right if (aggrow.canExpand(row)) { this._expandRow(row); } else if (cursor < aggrow.getHeight() - 1) { this.setState({cursor: cursor + 1}); this._keepCursorInViewport(); } e.preventDefault(); break; } } dropAction(s, d) { const aggrow = this.state.aggrow; console.log('dropped ' + s + ' to ' + d); if (s.startsWith('aggregate:active:')) { const sIndex = parseInt(s.substr(17), 10); let dIndex = -1; const active = aggrow.getActiveAggregators(); const dragged = active[sIndex]; if (d.startsWith('aggregate:insert:')) { dIndex = parseInt(d.substr(17), 10); } else if (d === 'divider:insert') { dIndex = active.length; } else { throw 'not allowed to drag ' + s + ' to ' + d; } if (dIndex > sIndex) { dIndex--; } active.splice(sIndex, 1); active.splice(dIndex, 0, dragged); aggrow.setActiveAggregators(active); this.setState({cursor:0}); } else if (s.startsWith('expander:active:')) { const sIndex = parseInt(s.substr(16), 10); let dIndex = -1; const active = aggrow.getActiveExpanders(); const dragged = active[sIndex]; if (d.startsWith('expander:insert:')) { dIndex = parseInt(d.substr(16), 10); } else if (d === 'divider:insert') { dIndex = 0; } else { throw 'not allowed to drag ' + s + ' to ' + d; } if (dIndex > sIndex) { dIndex--; } active.splice(sIndex, 1); active.splice(dIndex, 0, dragged); aggrow.setActiveExpanders(active); this.setState({cursor:0}); } } render() { return ( <div style={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column' }}> <TableHeader aggrow={this.state.aggrow} dropAction={(s, d) => this.dropAction(s, d)} /> <div style={{ width: '100%', flexGrow: '1', overflow: 'scroll' }} onScroll={ (e) => this.scroll(e) } ref={(div) => { this._scrollDiv = div; } }> <div style={{ position: 'relative' }}> { this.renderVirtualizedRows() } </div> </div> </div> ); } renderVirtualizedRows() { const aggrow = this.state.aggrow; const viewport = this.state.viewport; const rows = aggrow.getRows(viewport.top, viewport.height); return ( <div style={{ position: 'absolute', width: '100%', height: (rowHeight * (aggrow.getHeight() + 20)).toString() + 'px' }}> { rows.map(child => this.renderRow(child)) } </div> ); } renderRow(row) { if (row === null) { return null; } let bg = 'lightGray'; const aggrow = this.state.aggrow; const columns = []; let rowText = ''; const indent = 4 + aggrow.getRowIndent(row) * treeIndent; const aggregates = aggrow.getActiveAggregators(); if (row.parent !== null && (row.parent.expander % 2 === 0)) { bg = 'white'; } if (row.top === this.state.cursor) { bg = 'lightblue'; } for (let i = 0; i < aggregates.length; i++) { var aggregate = aggrow.getRowAggregate(row, i); columns.push(( <div style={{ width: '16px', height: 'inherit', backgroundColor: 'darkGray', flexShrink: '0' }}></div> )); columns.push(( <div style={{ width: '128px', textAlign: 'right', flexShrink: '0' }}> {aggregate} </div> )); } columns.push(( <div style={{ width: '16px', height: 'inherit', backgroundColor: 'gold', flexShrink: '0' }}></div> )); if (aggrow.canExpand(row)) { columns.push(( <div style={{ marginLeft: indent.toString() + 'px', flexShrink: '0', width: '12px', textAlign: 'center', border: '1px solid gray', }} onClick={ () => this._expandRow(row) } >+</div> )); } else if (aggrow.canContract(row)) { columns.push(( <div style={{ marginLeft: indent.toString() + 'px', flexShrink: '0', width: '12px', textAlign: 'center', border: '1px solid gray', }} onClick={ () => this._contractRow(row) } >-</div> )); } else { columns.push(( <div style={{ marginLeft: indent.toString() + 'px', }} ></div> )); } rowText += aggrow.getRowLabel(row); columns.push(( <div style={{ flexShrink: '0', whiteSpace: 'nowrap', marginRight: '20px' }}> {rowText} </div> )); return ( <div key={row.top} style={{ position: 'absolute', height: (rowHeight - 1).toString() + 'px', top: (rowHeight * row.top).toString() + 'px', display: 'flex', flexDirection: 'row', alignItems: 'center', backgroundColor: bg, borderBottom: '1px solid gray', }} onClick={ () => { this.setState({cursor: row.top}); }}> {columns} </div> ); } componentDidMount() { this.keydown = this.keydown.bind(this); document.body.addEventListener('keydown', this.keydown); } componentWillUnmount() { document.body.removeEventListener('keydown', this.keydown); } }