import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import collect from 'collect.js';
import Field from './Field';
import qs from 'qs';
// import PrimordialModel from './PrimordialModel';
// Import traits
import Queryable from './traits/Queryable';
import HasReflection from './traits/HasReflection';
import HasRelations from './traits/HasRelations';
import RenderableAsBasicForm from './traits/RenderableAsBasicForm';
import RenderableAsDiagram from './traits/RenderableAsDiagram';
import RenderableDefault from './traits/RenderableDefault';
import HasKeyProp from './traits/HasKeyProp';
import Shell from './Shell';
import { pluralToClassName, classNameToPlural, isModel, studly_case, app_put, app_get, def } from '../helpers';
const pluralize = require('pluralize');
import { filter, flatten, isEmpty, toPairs, pick, kebabCase, snakeCase, difference, intersection } from 'lodash';
/**
@extends Component
@mixes Queryable
@mixes HasReflection
@mixes HasKeyProp
@mixes HasRelations
@mixes RenderableAsBasicForm
@mixes RenderableAsDiagram
*/
class Model extends Component{
static get defaultProps(){
let defaultProps = {
id: null,
created_at: null,
updated_at: null,
}
return defaultProps;
}
/**
@constructor
@argument {Object} props An object representing a single model
*/
constructor(props){
super(props);
Model.addModel(this.constructor);
if(!app_get(this.plural)){
app_put(this.plural, {});
}
const res = app_put(`${this.plural}.${this.id}`, this);
this._calculatedProperties = {
endpoint: `${window.location.protocol}//${window.location.hostname}/${this.constructor.plural}`,
api_url: `${window.location.protocol}//${window.location.hostname}/api/${this.singular}/${this.props.id}`,
url: `${window.location.protocol}//${window.location.hostname}/${this.constructor.singular}/${this.props.id}`,
};
this.state = this.mutableState;
let relations = filter(toPairs(this.schema), (v) => v[1].type == "relation");
this.relations = {};
relations.forEach((v_array) => {
let [ relationName, relationDefinition ] = v_array;
const { relation_type, definition } = relationDefinition;
if(relationName in this.props){
const relationValue = this.props[relationName];
const ThisModel = Model.getModel(pluralToClassName(relationName));
let relationValueModels = null;
if(relationValue && ('map' in relationValue)) {
if(definition.withDefault && isEmpty(relationValue)){
relationValue.push({});
}
relationValueModels = relationValue.map((i, index) => {
return (<ThisModel key={index} {...i} />);
});
}
else {
if(definition.withDefault && isEmpty(relationValue)){
relationValueModels = (<ThisModel {...{}} />);
}
else{
relationValueModels = (<ThisModel {...relationValue} />);
}
}
if(relationValueModels){
this[relationName] = relationValueModels;
}
// this.relations[relationName] = this[relationName];
}
else {
if(typeof this[relation_type] !== 'function'){
console.error(relation_type, 'this[relation_type]');
}
else{
def(this, relationName, () => this[relation_type](definition));
def(this.relations, relationName, () => this[relation_type](definition));
/*
const resolved = this[relation_type](definition);
this[relationName] = resolved;
this.relations[relationName] = resolved;
*/
}
}
});
this.constructor.boot(this);
}
/** */
hydrate(){
return new Promise((resolve, reject) => {
if(this.props.id && Object.keys(this.props).length === 1){
axios.get(this.api_url).then(response => {
return resolve(new this.constructor(response.data));
});
}
else{
return resolve(this);
}
});
}
/** */
set_render(render_name){
const fn = this.props.set_render;
}
/** */
static getPrimaryKey(){
return collect(this.getSchema()).firstWhere('primaryKey')['name'];
}
/** */
get primaryKey(){
return this.constructor.getPrimaryKey();
}
/** */
get id(){
return '' + this.props[this.primaryKey];
}
/** */
static get editable_props(){
return [];
}
/** */
get calculatedProperties(){
return this._calculatedProperties;
}
/** */
static getSchema(){
return window[window.ReactSyncGlobal].schemas[this.name];
}
/** */
get schema(){
return this.constructor.getSchema();
}
/** */
get url(){
return this._calculatedProperties.url;
}
/** */
get api_url(){
return this._calculatedProperties.api_url;
}
/** */
get breadcrumb(){
let a = document.createElement('a');
a.href = this._calculatedProperties.url;
let segments = filter(a.pathname.split('/'));
let joined = [];
const reducer = (accumulator, currentValue) => {
accumulator.push(flatten([...accumulator, currentValue]));
return accumulator;
}
return segments.reduce(reducer, [[]])
.map(s => `${window.location.protocol}//${window.location.hostname}/${s.join('/')}`);
}
/**
@todo Implement this as a way to add instantiation items
*/
static boot(instance){
//
}
/** */
filterMutable(objectToFilter){
// if(!this.constructor.editable_props.length) return objectToFilter;
return pick(objectToFilter, this.constructor.editable_props);
}
/** */
get mutableState(){
return this.filterMutable(this.props);
}
/** */
get plural(){
return this.constructor.plural;
}
/** */
static get plural(){
return pluralize(kebabCase(this.name));
}
/** */
get singular(){
return this.constructor.singular;
}
/** */
static get singular(){
return pluralize.singular(this.plural);
}
/** */
static query_props = ['find', 'where', 'all', 'first'];
/** */
static pagination_props = ['per_page'];
/** */
static get reserved_props(){
return [...this.query_props, ...this.pagination_props, 'order_by', 'sort_by'];
}
/** */
static get_non_reserved_props(props){
const ret = difference(Object.keys(props), this.reserved_props);
// debugger
const val = {};
for(const i in ret){
val[ret[i]] = props[ret[i]];
}
return val;
}
/** */
get_non_reserved_props(){
return this.constructor.get_non_reserved_props(this.props);
}
/** */
get non_reserved_props(){
return this.get_non_reserved_props();
}
/** */
get_query_prop(){
const i = intersection(this.constructor.query_props, Object.keys(this.props));
return i.length ? i[0] : null;
}
/** */
get query_prop(){
return this.get_query_prop();
}
/** */
get is_query(){
return !!this.get_query_prop();
}
/** */
get_query_endpoint(){
const q = this.get_query_prop();
let where_prop;
if(q == 'where'){
if(Array.isArray(this.props.where)){
where_prop = this.props.where.join('|');
}
else where_prop = this.props.where;
}
const map = {
find: `/api/${this.singular}/${this.props.find}`,
where: `/api/${this.plural}/where/${where_prop}`,
all: `/api/${this.plural}`,
first: `/api/${this.singular}`,
}
const qs_object = {};
if(this.props.per_page) qs_object.per_page = this.props.per_page;
const qs_string = qs.stringify(qs_object);
return map[q] + `?${qs_string}`;
}
/** */
queryRender(){
const q = this.get_query_endpoint();
return <Shell url={q} Model={this.constructor} {...this.non_reserved_props} />
}
/** */
render(){
if(this.is_query){
return this.queryRender();
}
let renderAs = this.props.renderAs || this.props.render || 'Default';
let renderName = `render${studly_case(renderAs)}`;
if(typeof renderAs === 'function'){
return renderAs(this.props);
}
if(typeof this[renderName] === 'function'){
return this[renderName]();
}
// return the default render method.
return this.renderDefault();
}
/** */
renderDebug(){
return (<div><code>{this.constructor.name} | {this.props.id}</code></div>);
}
}
new RenderableAsBasicForm(Model);
new RenderableAsDiagram(Model);
new RenderableDefault(Model);
new HasReflection(Model);
new HasRelations(Model);
new HasKeyProp(Model);
new Queryable(Model);
(function(){
/** */
Model.addModel = function(M){
Model.models = Model.models || {};
if(!(M.name in Model.models))
Model.models[M.name] = M;
}
/** */
Model.getModel = function(M){
Model.models = Model.models || {};
const Mname = typeof M === 'string' ? M : M.name;
return Model.models[Mname];
}
/** */
Model.allModels = function(){
Model.models = Model.models || {};
return Model.models;
}
/** */
Model.getRegex = function(){
const models = collect(Model.allModels());
if(!models.count()){
return false;
}
const slugs = models.map((v, k) => {
return [
k,
kebabCase(k),
snakeCase(k),
pluralize(k),
v.plural,
pluralize(kebabCase(k)),
pluralize(snakeCase(k)),
];
})
.values()
.collapse()
.unique()
.implode('|');
const regex_string = `/(${slugs})/?([0-9]*?)$`;
return new RegExp(regex_string, 'i')
};
Model.getRegex();
/** */
Model.matchUrlPath = function(){
const regex = Model.getRegex();
if(!regex){
return false;
}
let path = window.location.pathname;
if(path.slice(-1) === '/'){
path = path.slice(0, -1);
}
const match_array = window.location.pathname.match(regex);
if(!match_array){
return false;
}
return Array.from(match_array);
}
collect().macro('hydrate', function(){
return new Promise((resolve, reject) => {
const needs_hydration = this.filter(i => typeof i !== 'object');
if(needs_hydration.isEmpty()){
return resolve(this);
}
const _promises = this.items.map(i => {
if(typeof i !== 'object'){
return (new this.model({id: i})).hydrate();
}
return i;
});
Promise.all(_promises)
.then(x => {
this.items = x;
return resolve(this);
});
});
});
collect().macro('promise', function(){
return new Promise((resolve, reject) => {
Promise.all(Object.values(this.items))
.then(values => {
this.items = values;
resolve(this);
});
});
});
/** */
Model.extractInstancesFromUrl = function(){
let matches = Model.matchUrlPath();
const return_val = {
models: [],
instances: {},
}
if(!matches){
return_val.models = collect(return_val.models);
return_val.instances = collect(return_val.instances);
return return_val;
}
matches = collect(matches).filter().all();
const model_name = pluralToClassName(matches[1]);
const M = Model.getModel(model_name);
if(!M){
return resolve(return_val);
}
const plural = M.plural;
return_val.models.push(M);
return_val.instances[plural] = return_val.instances[plural] || [];
if(matches.length === 3){
return_val.instances[plural].push(matches[2]);
}
return_val.models = collect(return_val.models);
return_val.instances = collect(return_val.instances);
return_val.instances = return_val.instances.map((v, k) => {
const M = Model.getModel(pluralToClassName(k));
const coll = collect(v);
coll.model = M;
return coll.hydrate();
});
return return_val;
}
/** */
Model.resolveInstancesFromUrl = function(return_val){
return new Promise((resolve, reject) => {
return_val.instances.promise().then((r) => {
return_val.instances = return_val.instances.keyBy('model.plural').all();
return resolve(return_val);
});
});
}
/*
Model.modifyInstance = function(instance, newProps){
const M = instance.type;
const resolved_props = {...instance.props, ...newProps};
return <M {...resolved_props} />;
}
*/
})();
export default Model;