123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471 |
- import React, {useState, useEffect} from 'react';
- import logo from './logo.svg';
- import './App.css';
- import { GraphQLClient } from 'graphql-request';
- let gql = new GraphQLClient("/graphql", {headers: localStorage.authToken ? {Authorization: 'Bearer '+localStorage.authToken} : {}})
- //TODO: use graphql-tag to get types, or, at least detect query
- //for proper binding between model objects and result of gql query
- async function createModels(gql, config={create: 'Upsert', update: 'Upsert', delete: "Delete", findOne: "FindOne", find: 'Find', count: 'Count'}){
- const getQuery = name =>
- `query mutations{
- __type(name: "${name}") {
- name,
- kind,
- fields {
- name,
- type {
- kind,
- ofType{
- name,
- fields{
- name,
- type{
- name,
- kind
- }
- }
- }
- name,
- fields{
- name,
- type{
- name,
- kind
- }
- }
- }
- args{
- name,
- type{
- name,
- inputFields{
- name,
- type{
- name,
- kind,
- ofType{
- name
- }
- }
- }
- }
- }
- }
- }
- }`
- let { __type: { fields: mutations }} = await gql.request(getQuery('Mutation'))
- let { __type: { fields: queries }} = await gql.request(getQuery('Query'))
- console.log(mutations)
- console.log(queries)
- let classes = {}
- const createClass = (name, fields) => {
- if (!(name in classes)) {
- classes[name] = class {
- constructor(data){
- Object.assign(this, data)
- }
- static get fields(){
- return fields
- }
- }
- Object.defineProperty(classes[name], 'name', {value: name})
- }
- return classes[name]
- }
- for (let query of queries){
- let className = query.type.name || query.type.ofType.name
- if (className === 'Int'){ //TODO: fuck this
- className = query.name.match(new RegExp(`([A-Za-z]+)(${Object.values(config).join('|')})`))[1]
- }
- let classFields = query.type.fields || query.type.ofType && query.type.ofType.fields
- console.log(classFields)
- let _class = createClass(className, classFields)
- for (let [method, methodName] of Object.entries(config)){
- if (!_class[method] && query.name.includes(methodName)){
- let methods = {
- find(q, cursorCalls={}){
- let queryText = `query FMFind($q:${query.args[0].type.name}){
- ${query.name}(${query.args[0].name}: $q)
- {${classFields.filter(x => x.type.kind === 'SCALAR').map(x => x.name).join(',')}}
- }
- `
- console.log(queryText, {q: JSON.stringify([q, cursorCalls])})
- return gql.request(queryText, {q: JSON.stringify([q, cursorCalls])}).then(data =>{
- return data[query.name].map(record => new _class(record))
- })
- },
- findOne(query){
- },
- count(q, cursorCalls={}){
- let queryText = `query FMFind($q:${query.args[0].type.name}){
- ${query.name}(${query.args[0].name}: $q)
- }
- `
- return gql.request(queryText, {q: JSON.stringify([q, cursorCalls])}).then(data =>{
- return data[query.name]
- })
- }
- }
- _class[method] = methods[method]
- }
- }
- }
- return classes
- }
- async function createModels2(gql, config={create: 'Upsert', update: 'Upsert', delete: "Delete", findOne: "FindOne", find: 'Find', count: 'Count'}){
- const universeQuery = `query universe{
- __schema{
- types {
- name,
- kind,
- inputFields{
- name,
- type {
- kind,
- ofType{
- name,
- fields{
- name,
- type{
- name,
- kind
- }
- }
- }
- name,
- fields{
- name,
- type{
- name,
- kind
- }
- }
- }
-
-
- }
- fields {
- name,
- type {
- kind,
- ofType{
- name,
- fields{
- name,
- type{
- name,
- kind
- }
- }
- }
- name,
- fields{
- name,
- type{
- name,
- kind
- }
- }
- }
- args{
- name,
- type{
- name,
- inputFields{
- name,
- type{
- name,
- kind,
- ofType{
- name
- }
- }
- }
- }
- }
- }
- }
- }}
- `
- const universe = await gql.request(universeQuery)
- console.log(universe)
- const types = []
- const inputs = []
- for (let type of universe.__schema.types){
- if (!["Query", "Mutation"].includes(type.name) && type.kind === "OBJECT" && !type.name.startsWith('__')) types.push(type)
- if (!["Query", "Mutation"].includes(type.name) && type.kind === "INPUT_OBJECT" && !type.name.startsWith('__')) inputs.push(type)
- }
- console.log(types, inputs)
- let classes = {}
- const inTypes = name => types.find(type => type.name === name)
- const inInputs = name => inputs.find(type => type.name === name)
- const projectionBuilder = (type, allFields=true) => {
- if (!allFields && type.fields[0].name !== '_id') allFields = true
- if (allFields)
- return '{' +
- type.fields.map(field => {
- return field.name + ((field.type.kind === 'OBJECT' && (inTypes(field.type.name)) && projectionBuilder(inTypes(field.type.name), false)) ||
- (field.type.kind === 'LIST' && (inTypes(field.type.ofType.name)) && projectionBuilder(inTypes(field.type.ofType.name), false)) || '')
- })
- .join(',')
- + '}'
- else return `{_id}`
- }
- const identityMap = {}
- const createClass = (name, type, input) => {
- if (!(name in classes)) {
- classes[name] = class {
- constructor(data={}, empty = false){
- if (data._id && data._id in identityMap)
- return identityMap[data._id]
- this.populate(data)
- this.empty = empty
- if (this._id) identityMap[this._id] = this
- }
- populate(data){
- type.fields.forEach(({name, type:{ ofType, kind, name:otherName}}) => {
- ({SCALAR(){
- if (data && typeof data === 'object' && name in data) this[name] = data[name]
- },
- LIST(){
- const otherType = inTypes(ofType.name)
- if (otherType && data[name])
- this[name] = data[name].map(otherEntity => new classes[otherType.name](otherEntity, otherEntity._id && Object.keys(otherEntity).length === 1))
- else if (data && typeof data === 'object' && name in data) this[name] = data[name]
- },
- OBJECT(){
- const otherType = inTypes(otherName)
- if (otherType && data[name])
- this[name] = new classes[otherType.name](data[name], data[name]._id && Object.keys(data[name]).length === 1)
- else if (data && typeof data === 'object' && name in data) this[name] = data[name]
- }})[kind].call(this)
- })
- }
- get empty(){
- return this.then
- }
- set empty(value){
- if (value){
- this.then = async (onFullfilled, onRejected) => {
- const gqlQuery = `
- query ${name}FindOne($query: String){
- ${name}FindOne(query: $query)
- ${projectionBuilder(type)}
-
- }
- `
- const data = await gql.request(gqlQuery, {query: JSON.stringify([{_id: this._id}])})
- this.populate(data[name + 'FindOne'])
- this.empty = false
- const thenResult = onFullfilled(this)
- if (thenResult && typeof thenResult === 'object' && typeof thenResult.then === 'function'){
- return await thenResult
- }
- return thenResult
- }
- }
- else delete this.then
- }
- static get type(){
- return type
- }
- static get input(){
- return input
- }
- async save(){
- if (this.empty) throw new ReferenceError('Cannot save empty object')
- const data = {}
- input.inputFields.forEach(({name, type:{ ofType, kind, name:otherName}}) => {
- ({SCALAR(){
- if (this[name]) data[name] = this[name]
- },
- LIST(){
- const otherType = inInputs(ofType.name)
- if (otherType && this[name] && this[name] instanceof Array)
- data[name] = this[name].map(otherEntity => (otherEntity._id ? {_id: otherEntity._id} : otherEntity))
- },
- INPUT_OBJECT(){
- const otherType = inInputs(otherName)
- if (otherType && this[name] && typeof this[name] === 'object')
- data[name] = (this[name]._id ? {_id: this[name]._id} : this[name])
- }})[kind].call(this)
- })
- const gqlQuery = `
- mutation ${name}Upsert($data: ${input.name}){
- ${name}Upsert(${name.toLowerCase()}: $data)
- ${projectionBuilder(type)}
- }
- `
- let result = await gql.request(gqlQuery, {data})
- this.populate(result)
- }
- static async find(query){
- const gqlQuery = `
- query ${name}Find($query: String){
- ${name}Find(query: $query)
- ${projectionBuilder(type)}
- }
- `
- let result = await gql.request(gqlQuery, {query: JSON.stringify(query)})
- return result[name + 'Find'].map(entity => new classes[name](entity))
- }
- }
- Object.defineProperty(classes[name], 'name', {value: name})
- }
- return classes[name]
- }
- types.forEach((type) => createClass(type.name, type, inputs.find(input => input.name === `${type.name}Input`)))
- console.log(classes)
- classes.Good.find([{}]).then(goods => console.log(goods))
- }
- const dataReader = async () => {
- const { PriceItem } = await createModels(gql)
- let data = await PriceItem.find({}, {limit: [10]})
- return {data, PriceItem }
- }
- const Th = p =>
- <div className='Th' {...p} />
- const Row = ({top, children}) =>
- <div className='Row' style={{position: 'relative',top}}>
- {children}
- </div>
- const Cell = ({children, ...props}) =>
- <div className='Cell' {...props}>
- {children}
- </div>
- function ModelGrid({model, rowHeight, gridHeight, overload, query, cellDoubleClick}){
- const onScreenRowCount = gridHeight/rowHeight
- const overloadedRowCount = overload * onScreenRowCount
- const [oldQuery, setOldQuery] = useState()
- const jsonQuery = JSON.stringify(query)
- const [totalCount, setTotalCount] = useState()
- if (!totalCount || oldQuery !== jsonQuery) {
- model && model.count(query).then(count => { setTotalCount(count); setOldQuery(jsonQuery)})
- }
- const [gridContentRef, setGridContentRef] = useState()
- const [scroll, setScroll] = useState(0)
- const onScreenFirstRowIndex = Math.floor(scroll/rowHeight)
- let skip = onScreenFirstRowIndex - (overloadedRowCount - onScreenRowCount)/2;
- if (skip < 0) skip = 0
- const [sort, setSort] = useState([])
- const [records, setRecords] = useState()
- const cursorCalls = {skip: [skip], limit: [overloadedRowCount]}
- if (!model) return <>No model</>;
- if (sort.length) cursorCalls.sort = [sort]
- if (!records || oldQuery !== jsonQuery) {
- model.find(query, cursorCalls)
- .then(records => (setRecords(records), setOldQuery(jsonQuery)))
- }
- return(
- <>
- <div className='GridHeader'>
- {model.fields.map(field => <Th onClick={e => (setSort(field.name === sort[0] ? [sort[0], -sort[1]] : [field.name, 1]), setRecords(null))}>{field.name}</Th>)}
- </div>
- <div className='GridViewport'
- onScroll={e => {
- if (Math.abs(e.target.scrollTop - scroll) > gridHeight*overload/3){
- setScroll(e.target.scrollTop);
- setRecords(null)
- } }}
- style={{maxHeight: gridHeight, height: gridHeight}}>
- <div className='GridContent'
- style={{height: totalCount*rowHeight}}
- ref={e => setGridContentRef(e)}>
- {records && records.map((record,i) =>
- <Row top={(skip)*rowHeight}>
- {model.fields.map(field =>
- <Cell onDoubleClick={e => cellDoubleClick(field.name, record[field.name], record._id)}>
- {record[field.name]}
- </Cell>)}
- </Row>)}
- </div>
- </div>
- </>
- );
- }
- 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)
- console.log('MODELS', models)
- return (
- <div className="App">
- {models && Object.entries(models).map(([key, model]) => <div>{key}</div>)}
- {models && <ModelGrid model={models.jk} 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>
- );
- }
- export default App;
|