|
@@ -24,15 +24,38 @@ const Th = p =>
|
|
|
<div className='Th' {...p} />
|
|
|
|
|
|
//<div className='Row' style={{position: 'relative',top}}>
|
|
|
-const Row = ({top, children}) =>
|
|
|
-<div className='Row' >
|
|
|
- {children}
|
|
|
-</div>
|
|
|
+const Row = ({top, selected, children}) => {
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ <div className={`Row ${selected ? 'selected' : ''}` } >
|
|
|
+ {children}
|
|
|
+ </div>
|
|
|
+ </>
|
|
|
+ )
|
|
|
+}
|
|
|
|
|
|
-const Cell = ({children, ...props}) =>
|
|
|
-<div className='Cell' {...props}>
|
|
|
- {children && children.toString()}
|
|
|
-</div>
|
|
|
+const Cell = ({children, options, record, field, selected, ...props}) => {
|
|
|
+ let Formatter = React.Fragment
|
|
|
+
|
|
|
+ const viewOrEdit = (!record.constructor.inputs || record.constructor.inputs.some(input => input.name === field.name)) && selected ? 'edit' : 'view'
|
|
|
+ if (field.type.name in options[viewOrEdit].formatters){
|
|
|
+ Formatter = options[viewOrEdit].formatters[field.type.name]
|
|
|
+ }
|
|
|
+ if (field.name in options[viewOrEdit].fields){
|
|
|
+ Formatter = options[viewOrEdit].fields[field.name]
|
|
|
+ }
|
|
|
+ if (children && typeof children === 'object'){
|
|
|
+ if (children.constructor.name in options[viewOrEdit].formatters)
|
|
|
+ Formatter = options[viewOrEdit].formatters[children.constructor.name]
|
|
|
+ else
|
|
|
+ Formatter = options[viewOrEdit].formatters.Object
|
|
|
+ }
|
|
|
+ return(
|
|
|
+ <div className='Cell' {...props}>
|
|
|
+ <Formatter {...props} field={field}>{Formatter === React.Fragment ? (children && children.toString()) : children}</Formatter>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+}
|
|
|
|
|
|
|
|
|
const ModelListItem = "div"
|
|
@@ -79,38 +102,43 @@ const GridHeader = ({fields, sort, onSort}) =>
|
|
|
{fields.map(field => <GridHeaderItem field={field} sort={sort} onClick={() => onSort(field.name)}/>)}
|
|
|
</div>
|
|
|
|
|
|
-const VirtualScroll = ({gridHeight, count, rowHeight, row=Row, onScroll, records, skip}) => {
|
|
|
+const VirtualScroll = ({options, gridHeight, count, rowHeight, components:Components={Row, Cell}, onScroll, records, skip}) => {
|
|
|
//const [records, setRecords] = useState([])
|
|
|
const limit = gridHeight/rowHeight
|
|
|
- const Row = row
|
|
|
+ const {Row, Cell} = Components
|
|
|
|
|
|
+ const [edit, setEdit] = useState({field: null, record: null})
|
|
|
|
|
|
const timeout = useRef(0)
|
|
|
|
|
|
return (
|
|
|
<div className='GridViewport'
|
|
|
onScroll={e => {
|
|
|
- let skip = Math.ceil(e.target.scrollTop/rowHeight)
|
|
|
- if (skip < 0) skip = 0
|
|
|
- if (skip > count - limit) skip = count - limit
|
|
|
-
|
|
|
- clearInterval(timeout.current)
|
|
|
- timeout.current = setTimeout(() => {
|
|
|
- console.log(skip, limit)
|
|
|
- onScroll(skip,limit)
|
|
|
- }, 1000)
|
|
|
+ //let skip = Math.ceil(e.target.scrollTop/rowHeight)
|
|
|
+ //if (skip < 0) skip = 0
|
|
|
+ //if (skip > count - limit) skip = count - limit
|
|
|
+
|
|
|
+ //clearInterval(timeout.current)
|
|
|
+ //timeout.current = setTimeout(() => {
|
|
|
+ //console.log(skip, limit)
|
|
|
+ //onScroll(skip,limit)
|
|
|
+ //}, 1000)
|
|
|
}}
|
|
|
style={{maxHeight: gridHeight, height: gridHeight}} >
|
|
|
<div className='GridContent'
|
|
|
style={{height: count*rowHeight, minHeight: count*rowHeight, maxHeight: count*rowHeight}} >
|
|
|
{ /* <div style={{height: skip*rowHeight}} /> */ }
|
|
|
- {records && records.map((record,i) =>
|
|
|
- <Row key={i}>
|
|
|
+ {records && records.map((record,i) =><React.Fragment key={i}>
|
|
|
+ <Row options={options} selected={edit.record === record}>
|
|
|
{record.constructor.fields.map(field =>
|
|
|
- <Cell>
|
|
|
- {record[field.name] === 'object' ? record[field.name].toString() : record[field.name]}
|
|
|
+ <Cell record={record} key={field.name} field={field} options={options} onClick={() => setEdit({record, field})}
|
|
|
+ selected={edit.record === record} >
|
|
|
+ {record[field.name]}
|
|
|
</Cell>)}
|
|
|
- </Row>)}
|
|
|
+ </Row>
|
|
|
+ {edit.record === record && <Row selected>additional inputs and save/cancel</Row>}
|
|
|
+ </React.Fragment>
|
|
|
+ )}
|
|
|
{/* <div style={{height: (count - skip - records.length)*rowHeight}} /> */ }
|
|
|
</div>
|
|
|
</div>
|
|
@@ -120,7 +148,7 @@ const VirtualScroll = ({gridHeight, count, rowHeight, row=Row, onScroll, records
|
|
|
|
|
|
|
|
|
|
|
|
-const ModelView = ({model, components:Components={Search, Count, GridHeader, Grid:VirtualScroll}, rowHeight=50, gridHeight=500, overload=2}) => {
|
|
|
+const ModelView = ({model, options, components:Components={Search, Count, GridHeader, Grid:VirtualScroll}, rowHeight=150, gridHeight=500, overload=2}) => {
|
|
|
|
|
|
|
|
|
|
|
@@ -141,25 +169,28 @@ const ModelView = ({model, components:Components={Search, Count, GridHeader, Gri
|
|
|
//setCursorCalls({sort:cursorCalls.sort, skip: skip ? [skip] : undefined, limit: overloadedRowCount ? [overloadedRowCount] : undefined})
|
|
|
//}, [scroll])
|
|
|
|
|
|
- const timeout = useRef(0)
|
|
|
- useEffect(() => {
|
|
|
- clearInterval(timeout.current)
|
|
|
- timeout.current = setTimeout(() => {
|
|
|
- setQuery(searchQueryBuilder(search, model))
|
|
|
- }, 1000)
|
|
|
- },[search, model])
|
|
|
|
|
|
useEffect(() => {
|
|
|
model.count(query, cursorCalls).then(count => setCount(count))
|
|
|
model.find(query, cursorCalls).then(records => Promise.all(records)).then(records => setRecords(records))
|
|
|
}, [query, model, cursorCalls])
|
|
|
|
|
|
- //console.log(records)
|
|
|
+ const timeout = useRef(0)
|
|
|
+ useEffect(() => {
|
|
|
+ clearInterval(timeout.current)
|
|
|
+ if (!search)
|
|
|
+ setQuery(searchQueryBuilder(search, model))
|
|
|
+ else {
|
|
|
+ timeout.current = setTimeout(() => {
|
|
|
+ setQuery(searchQueryBuilder(search, model))
|
|
|
+ },1000)
|
|
|
+ }
|
|
|
+ },[search, model])
|
|
|
|
|
|
return (
|
|
|
<>
|
|
|
- <Components.Search value={search} onChange={({target: {value}}) => setSearch(value)}/>
|
|
|
- <Components.Count>
|
|
|
+ <Components.Search options={options} value={search} onChange={({target: {value}}) => setSearch(value)}/>
|
|
|
+ <Components.Count options={options}>
|
|
|
{count}
|
|
|
</Components.Count>
|
|
|
<Components.GridHeader fields={model.fields}
|
|
@@ -169,6 +200,7 @@ const ModelView = ({model, components:Components={Search, Count, GridHeader, Gri
|
|
|
})}/>
|
|
|
|
|
|
{records && <Components.Grid
|
|
|
+ options={options}
|
|
|
skip={skip}
|
|
|
count={count}
|
|
|
records={records}
|
|
@@ -183,16 +215,80 @@ const ModelView = ({model, components:Components={Search, Count, GridHeader, Gri
|
|
|
)
|
|
|
}
|
|
|
|
|
|
-const Admin = ({models, components:Components={ModelList, Search}}) => {
|
|
|
+const ObjectShortView = ({children}) => {
|
|
|
+ const [record, setRecord] = useState(children)
|
|
|
+ if ('then' in children){
|
|
|
+ console.log('load')
|
|
|
+ children.then(child => setRecord({...child}))
|
|
|
+ }
|
|
|
+ if (children._id){
|
|
|
+ return (
|
|
|
+ <div className="ObjectShortView">
|
|
|
+ {record.name || record.key || record._id}
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return (
|
|
|
+ <pre>
|
|
|
+ {JSON.stringify(record, null, 4)}
|
|
|
+ </pre>
|
|
|
+ )
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+const defaultAdminOptions =
|
|
|
+ {
|
|
|
+ view: {
|
|
|
+ formatters:{
|
|
|
+ ID: ({children}) => <b>{children && children.slice(-6).toUpperCase()}</b>,
|
|
|
+ String: ({children}) => <>{children && children.length > 100 ? children.slice(0,100) + '...' : children}</>,
|
|
|
+ Object: ObjectShortView,
|
|
|
+ Array: ({children}) => <>{children.map(child => <ObjectShortView children={child} />)}</>
|
|
|
+ },
|
|
|
+ fields:{
|
|
|
+ createdAt: ({children}) => <>{new Date(+children).toISOString()}</> ,
|
|
|
+ url: ({children}) => <a href={children}>{children}</a>
|
|
|
+ },
|
|
|
+ models: {
|
|
|
+
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ edit: {
|
|
|
+ formatters:{
|
|
|
+ ID: ({children}) => <b>{children && children.slice(-6).toUpperCase()}</b>,
|
|
|
+ String: ({children, ...props}) => <textarea value={children} {...props}/>,
|
|
|
+ Int: ({children, ...props}) => <input type='number' value={children} {...props}/>,
|
|
|
+ Float: ({children, ...props}) => <input type='number' value={children} {...props}/>,
|
|
|
+ Object: ObjectShortView,
|
|
|
+ Array: ({children}) => <>{children.map(child => <ObjectShortView children={child} />)}</>
|
|
|
+ },
|
|
|
+ fields:{
|
|
|
+ createdAt: ({children}) => <input value={new Date(+children).toISOString()} />,
|
|
|
+ url: ({children}) => <a href={children}>{children}</a>
|
|
|
+ },
|
|
|
+ models: {
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+const Admin = ({models, components:{ModelList:ML=ModelList, Search:S=Search, ModelListItem:MLI=ModelListItem, ModelView:MV=ModelView, Count:C=Count, GridHeader:GH=GridHeader, Grid:G=VirtualScroll}={},
|
|
|
+ options=defaultAdminOptions
|
|
|
+ }) => {
|
|
|
const [selected, setSelected] = useState()
|
|
|
|
|
|
+ const mergedOptions = {...defaultAdminOptions, ...options}
|
|
|
+
|
|
|
|
|
|
return (
|
|
|
<>
|
|
|
- <Components.ModelList models={models} onChange={(name) => setSelected(name)} selected={selected}/>
|
|
|
+ <ML Item={MLI} models={models} onChange={(name) => setSelected(name)} selected={selected}/>
|
|
|
<content>
|
|
|
-
|
|
|
- {selected && <ModelView model={models[selected]} /> }
|
|
|
+ {selected && <MV options={mergedOptions} model={models[selected]} components={{Search: S, Count: C, GridHeader:GH, Grid:G}} /> }
|
|
|
</content>
|
|
|
</>
|
|
|
)
|
|
@@ -201,11 +297,8 @@ const Admin = ({models, components:Components={ModelList, Search}}) => {
|
|
|
|
|
|
function App() {
|
|
|
let [models, setModels] = useState()
|
|
|
- let [queryText, setQueryText] = useState('{manufacturerName: "TOYOTA"}')
|
|
|
- let [query, setQuery] = useState({})
|
|
|
models || createModels2(gql).then(models => setModels(models))
|
|
|
|
|
|
- console.log(query)
|
|
|
const classes = models
|
|
|
console.log(classes)
|
|
|
//if (classes && Object.keys(classes).length){
|
|
@@ -218,8 +311,6 @@ function App() {
|
|
|
{models && <Admin models={models} />}
|
|
|
{/*models && <ModelGrid model={models.Image} rowHeight={50} gridHeight={700} overload={5} query={query}
|
|
|
cellDoubleClick={(name, text, _id) => setQueryText(`{${name}:\`${text}\`}`)}/> */}
|
|
|
- <textarea onChange={e => setQueryText(e.target.value)} value={queryText} />
|
|
|
- <button onClick={() => setQuery(eval(`(${queryText})`))}>Run</button>
|
|
|
</div>
|
|
|
);
|
|
|
}
|