diff --git a/.gitignore b/.gitignore index 297959a..b817577 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,10 @@ -/vendor/ -node_modules/ -npm-debug.log -yarn-error.log - -# Laravel 4 specific -bootstrap/compiled.php -app/storage/ - -# Laravel 5 & Lumen specific -public/storage -public/hot - -# Laravel 5 & Lumen specific with changed public path -public_html/storage -public_html/hot - -storage/*.key -.env -Homestead.yaml -Homestead.json -/.vagrant +/.idea +/vendor +/node_modules +package-lock.json +composer.phar +composer.lock +phpunit.xml .phpunit.result.cache +.DS_Store +Thumbs.db diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..bed4704 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +version 0.1.0 (2020-02-04) +----------------------------- +### INITED \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..e968ac8 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 armincms + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index fbfd431..92a7b41 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,116 @@ -# belongs-to-many -The inline `BelongsToMany` and `MorphedByMany` field for the Laravel Nova +# BelongsToMany +A Laravel Nova field for `simple` and `polymorphic` `ManyToMany` relationships. + +##### Table of Contents +* [Features](#features) +* [Install](#install) +* [Simple Usage](#simple-usage) +* [Pivots](#pivots) +* [Duplicate Attachment](#duplicate-attachment) +* [Polymorphic Relation](#polymorphic-relation) + + +## Features + - Attach polymorphic and non-polymorphic `ManyToMany` relationships in + the creation and update page + - Edit pivot columns when attaching relation + - Attach a source to another resource many times + +## Install +```bash +composer require armincms/belongs-to-many +``` + +## Simple Usage + +``` + use Armincms\Fields\BelongsToMany; + + + + /** + * Get the fields displayed by the resource. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + public function fields(Request $request) + { + return [ + BelongsToMany::make(__("Label"), 'relationName', RelatedResource::class) + ->fields(function() { + return [ + Text::make('Price') + ->rules('required', 'numeric'), + ]; + }) + ->pivots(), + ]; + } + +``` + +## Pivots +For customizing the pivot columns when attaching a resource you can use the `pivots` method of the field. then define your custom pivot fields with the `fields` method. now, when attaching a resource; a Modal that contains the pivot fields will be displayed to you. + + +``` + use Armincms\Fields\BelongsToMany; + + + + /** + * Get the fields displayed by the resource. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + public function fields(Request $request) + { + return [ + BelongsToMany::make(__("Label"), 'relationName', RelatedResource::class) + ->fields(function() { + return [ + Text::make('Price') + ->rules('required', 'numeric'), + ]; + }) + ->pivots(), + ]; + } + +``` + +## Duplicate Attachment +You can use the `duplicate` feature for repetitively attach a resource to another resource. follow the example: + + + +``` + use Armincms\Fields\BelongsToMany; + + + + /** + * Get the fields displayed by the resource. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + public function fields(Request $request) + { + return [ + BelongsToMany::make(__("Label"), 'relationName', RelatedResource::class) + ->fields(function() { + return [ + Text::make('Price') + ->rules('required', 'numeric'), + ]; + }) + ->duplicate(), + ]; + } + +``` +## Polymorphic Relation +Using for the polymorphic relationships is like non-polymorphic. \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..fe98bf6 --- /dev/null +++ b/composer.json @@ -0,0 +1,29 @@ +{ + "name": "armincms/belongs-to-many", + "description": "A Laravel Nova field.", + "keywords": [ + "laravel", + "nova" + ], + "license": "MIT", + "require": { + "php": ">=7.1.0" + }, + "autoload": { + "psr-4": { + "Armincms\\Fields\\": "src/" + } + }, + "extra": { + "laravel": { + "providers": [ + "Armincms\\Fields\\FieldServiceProvider" + ] + } + }, + "config": { + "sort-packages": true + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/dist/css/field.css b/dist/css/field.css new file mode 100644 index 0000000..e69de29 diff --git a/dist/js/field.js b/dist/js/field.js new file mode 100644 index 0000000..d2ffde1 --- /dev/null +++ b/dist/js/field.js @@ -0,0 +1 @@ +!function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:r})},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=2)}([function(t,e){t.exports=function(t,e,n,r,o,i){var a,s=t=t||{},u=typeof t.default;"object"!==u&&"function"!==u||(a=t,s=t.default);var c,A="function"==typeof s?s.options:s;if(e&&(A.render=e.render,A.staticRenderFns=e.staticRenderFns,A._compiled=!0),n&&(A.functional=!0),o&&(A._scopeId=o),i?(c=function(t){(t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),r&&r.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(i)},A._ssrRegister=c):r&&(c=r),c){var l=A.functional,f=l?A.render:A.beforeCreate;l?(A._injectStyles=c,A.render=function(t,e){return c.call(e),f(t,e)}):A.beforeCreate=f?[].concat(f,c):[c]}return{esModule:a,exports:s,options:A}}},function(t,e,n){t.exports=n(9)},function(t,e,n){n(3),t.exports=n(22)},function(t,e,n){Nova.booting(function(t,e,r){t.component("index-armincms-belongs-to-many",n(4)),t.component("detail-armincms-belongs-to-many",n(7)),t.component("form-armincms-belongs-to-many",n(12))})},function(t,e,n){var r=n(0)(n(5),n(6),!1,null,null,null);t.exports=r.exports},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default={props:["resourceName","field"]}},function(t,e){t.exports={render:function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("span",[t.field.value&&t.field.value.length?t._l(t.field.value,function(e,r){return n("router-link",{key:r,staticClass:"no-underline font-bold dim text-primary",attrs:{to:{name:"detail",params:{resourceName:t.field.resourceName,resourceId:e.id}}}},[t._v("\n "+t._s(e.text)+" "+t._s(t.field.value.length-r-1?" , ":"")+"\n ")])}):n("p",{attrs:{slote:"value"}},[t._v("—")])],2)},staticRenderFns:[]}},function(t,e,n){var r=n(0)(n(8),n(11),!1,null,null,null);t.exports=r.exports},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(1),o=n.n(r);e.default={props:["resource","resourceName","resourceId","field"],data:function(){return{display:!1,fields:[]}},methods:{displayPivots:function(t){this.display=t,this.loading=!1,this.getPivotFields()},getPivotFields:function(){var t,e=(t=o.a.mark(function t(e){var n=this;return o.a.wrap(function(t){for(;;)switch(t.prev=t.next){case 0:return t.next=2,Nova.request().get("/nova-api/armincms/"+this.resourceName+"/pivot-fields/"+this.field.resourceName,{params:{resourceId:this.resourceId,relatedId:this.display.id,pivotId:this.display.pivotId}}).then(function(t){var e=t.data;n.fields=e,_.each(n.fields,function(t){t.fill=function(){return""}})});case 2:case"end":return t.stop()}},t,this)}),function(){var e=t.apply(this,arguments);return new Promise(function(t,n){return function r(o,i){try{var a=e[o](i),s=a.value}catch(t){return void n(t)}if(!a.done)return Promise.resolve(s).then(function(t){r("next",t)},function(t){r("throw",t)});t(s)}("next")})});return function(t){return e.apply(this,arguments)}}(),resolveComponentName:function(t){return t.prefixComponent?"detail-"+t.component:t.component},handleClose:function(){this.display=null,this.$emit("close")}},computed:{}}},function(t,e,n){var r=function(){return this}()||Function("return this")(),o=r.regeneratorRuntime&&Object.getOwnPropertyNames(r).indexOf("regeneratorRuntime")>=0,i=o&&r.regeneratorRuntime;if(r.regeneratorRuntime=void 0,t.exports=n(10),o)r.regeneratorRuntime=i;else try{delete r.regeneratorRuntime}catch(t){r.regeneratorRuntime=void 0}},function(t,e){!function(e){"use strict";var n,r=Object.prototype,o=r.hasOwnProperty,i="function"==typeof Symbol?Symbol:{},a=i.iterator||"@@iterator",s=i.asyncIterator||"@@asyncIterator",u=i.toStringTag||"@@toStringTag",c="object"==typeof t,A=e.regeneratorRuntime;if(A)c&&(t.exports=A);else{(A=e.regeneratorRuntime=c?t.exports:{}).wrap=x;var l="suspendedStart",f="suspendedYield",d="executing",p="completed",h={},v={};v[a]=function(){return this};var g=Object.getPrototypeOf,m=g&&g(g(S([])));m&&m!==r&&o.call(m,a)&&(v=m);var y=_.prototype=w.prototype=Object.create(v);E.prototype=y.constructor=_,_.constructor=E,_[u]=E.displayName="GeneratorFunction",A.isGeneratorFunction=function(t){var e="function"==typeof t&&t.constructor;return!!e&&(e===E||"GeneratorFunction"===(e.displayName||e.name))},A.mark=function(t){return Object.setPrototypeOf?Object.setPrototypeOf(t,_):(t.__proto__=_,u in t||(t[u]="GeneratorFunction")),t.prototype=Object.create(y),t},A.awrap=function(t){return{__await:t}},C(B.prototype),B.prototype[s]=function(){return this},A.AsyncIterator=B,A.async=function(t,e,n,r){var o=new B(x(t,e,n,r));return A.isGeneratorFunction(e)?o:o.next().then(function(t){return t.done?t.value:o.next()})},C(y),y[u]="Generator",y[a]=function(){return this},y.toString=function(){return"[object Generator]"},A.keys=function(t){var e=[];for(var n in t)e.push(n);return e.reverse(),function n(){for(;e.length;){var r=e.pop();if(r in t)return n.value=r,n.done=!1,n}return n.done=!0,n}},A.values=S,T.prototype={constructor:T,reset:function(t){if(this.prev=0,this.next=0,this.sent=this._sent=n,this.done=!1,this.delegate=null,this.method="next",this.arg=n,this.tryEntries.forEach(j),!t)for(var e in this)"t"===e.charAt(0)&&o.call(this,e)&&!isNaN(+e.slice(1))&&(this[e]=n)},stop:function(){this.done=!0;var t=this.tryEntries[0].completion;if("throw"===t.type)throw t.arg;return this.rval},dispatchException:function(t){if(this.done)throw t;var e=this;function r(r,o){return s.type="throw",s.arg=t,e.next=r,o&&(e.method="next",e.arg=n),!!o}for(var i=this.tryEntries.length-1;i>=0;--i){var a=this.tryEntries[i],s=a.completion;if("root"===a.tryLoc)return r("end");if(a.tryLoc<=this.prev){var u=o.call(a,"catchLoc"),c=o.call(a,"finallyLoc");if(u&&c){if(this.prev=0;--n){var r=this.tryEntries[n];if(r.tryLoc<=this.prev&&o.call(r,"finallyLoc")&&this.prev=0;--e){var n=this.tryEntries[e];if(n.finallyLoc===t)return this.complete(n.completion,n.afterLoc),j(n),h}},catch:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var n=this.tryEntries[e];if(n.tryLoc===t){var r=n.completion;if("throw"===r.type){var o=r.arg;j(n)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(t,e,r){return this.delegate={iterator:S(t),resultName:e,nextLoc:r},"next"===this.method&&(this.arg=n),h}}}function x(t,e,n,r){var o=e&&e.prototype instanceof w?e:w,i=Object.create(o.prototype),a=new T(r||[]);return i._invoke=function(t,e,n){var r=l;return function(o,i){if(r===d)throw new Error("Generator is already running");if(r===p){if("throw"===o)throw i;return k()}for(n.method=o,n.arg=i;;){var a=n.delegate;if(a){var s=R(a,n);if(s){if(s===h)continue;return s}}if("next"===n.method)n.sent=n._sent=n.arg;else if("throw"===n.method){if(r===l)throw r=p,n.arg;n.dispatchException(n.arg)}else"return"===n.method&&n.abrupt("return",n.arg);r=d;var u=b(t,e,n);if("normal"===u.type){if(r=n.done?p:f,u.arg===h)continue;return{value:u.arg,done:n.done}}"throw"===u.type&&(r=p,n.method="throw",n.arg=u.arg)}}}(t,n,a),i}function b(t,e,n){try{return{type:"normal",arg:t.call(e,n)}}catch(t){return{type:"throw",arg:t}}}function w(){}function E(){}function _(){}function C(t){["next","throw","return"].forEach(function(e){t[e]=function(t){return this._invoke(e,t)}})}function B(t){var e;this._invoke=function(n,r){function i(){return new Promise(function(e,i){!function e(n,r,i,a){var s=b(t[n],t,r);if("throw"!==s.type){var u=s.arg,c=u.value;return c&&"object"==typeof c&&o.call(c,"__await")?Promise.resolve(c.__await).then(function(t){e("next",t,i,a)},function(t){e("throw",t,i,a)}):Promise.resolve(c).then(function(t){u.value=t,i(u)},a)}a(s.arg)}(n,r,e,i)})}return e=e?e.then(i,i):i()}}function R(t,e){var r=t.iterator[e.method];if(r===n){if(e.delegate=null,"throw"===e.method){if(t.iterator.return&&(e.method="return",e.arg=n,R(t,e),"throw"===e.method))return h;e.method="throw",e.arg=new TypeError("The iterator does not provide a 'throw' method")}return h}var o=b(r,t.iterator,e.arg);if("throw"===o.type)return e.method="throw",e.arg=o.arg,e.delegate=null,h;var i=o.arg;return i?i.done?(e[t.resultName]=i.value,e.next=t.nextLoc,"return"!==e.method&&(e.method="next",e.arg=n),e.delegate=null,h):i:(e.method="throw",e.arg=new TypeError("iterator result is not an object"),e.delegate=null,h)}function O(t){var e={tryLoc:t[0]};1 in t&&(e.catchLoc=t[1]),2 in t&&(e.finallyLoc=t[2],e.afterLoc=t[3]),this.tryEntries.push(e)}function j(t){var e=t.completion||{};e.type="normal",delete e.arg,t.completion=e}function T(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(O,this),this.reset(!0)}function S(t){if(t){var e=t[a];if(e)return e.call(t);if("function"==typeof t.next)return t;if(!isNaN(t.length)){var r=-1,i=function e(){for(;++rn.parts.length&&(r.parts.length=n.parts.length)}else{var a=[];for(o=0;o=0},performEditTag:function(t){var e=this;this.field.pivots&&(this.cancelCallback=function(){t.performCancelEdit(t.index),e.attachCallback=e.cancelCallback=function(){}},this.attachCallback=function(){return t.performSaveEdit(t.index),e.attachCallback=e.cancelCallback=function(){},t.index},t.performOpenEdit(t.index))},attachTheResource:function(){var t=s(o.a.mark(function t(){var e;return o.a.wrap(function(t){for(;;)switch(t.prev=t.next){case 0:return t.next=2,this.validatePivotFields(this.processingResource);case 2:if(!t.sent){t.next=14;break}return t.next=5,this.resourceProcessor();case 5:return console.log("attached the resource:",this.processingResource.text),t.next=8,this.attachCallback();case 8:return e=t.sent,t.next=11,this.resetCallbak();case 11:e="number"==typeof e?e:this.attachedResources.length-1,this.$set(this.attachedResources,e,this.attachPivots(this.attachedResources[e])),this.processingModal=!1;case 14:case"end":return t.stop()}},t,this)}));return function(){return t.apply(this,arguments)}}(),validatePivotFields:function(){var t=s(o.a.mark(function t(e){return o.a.wrap(function(t){for(;;)switch(t.prev=t.next){case 0:if(!(this.fields.length>0)){t.next=10;break}return t.prev=1,t.next=4,this.validateRequest(e);case 4:t.next=10;break;case 6:return t.prev=6,t.t0=t.catch(1),422==t.t0.response.status&&(this.validationErrors=new i.Errors(t.t0.response.data.errors),Nova.error(this.__("There was a problem submitting the form."))),t.abrupt("return",!1);case 10:return t.abrupt("return",!0);case 11:case"end":return t.stop()}},t,this,[[1,6]])}));return function(e){return t.apply(this,arguments)}}(),validateRequest:function(t){return Nova.request().post("/nova-api/armincms/"+this.resourceName+"/pivots-validate/"+this.field.resourceName,this.attachmentFormData,{params:{resourceId:this.resourceId,relatedId:this.processingResource.id,editing:!0,editMode:t.attached?"update-attached":"attach"}})},processTheResource:function(){var t=s(o.a.mark(function t(e,n){return o.a.wrap(function(t){for(;;)switch(t.prev=t.next){case 0:if(this.validationErrors=new i.Errors,this.loading=!0,this.processingResource=e,this.resourceProcessor=n,t.t0=this.field.pivots,!t.t0){t.next=8;break}return t.next=8,this.getPivotFields(e);case 8:this.processingModal=this.fields.length>0||this.attachTheResource(),this.loading=!1,console.log("processing resource:",this.processingResource.text);case 11:case"end":return t.stop()}},t,this)}));return function(e,n){return t.apply(this,arguments)}}(),triggerLoading:function(){this.loading=!this.loading},cancelProcessing:function(){this.processingModal=!1,console.log("canceled attachment:",this.processingResource.text),this.cancelCallback(),this.$emit("close"),this.resetCallbak()},attachPivots:function(t){var e=!0,n=!1,r=void 0;try{for(var o,i=this.attachmentFormData.entries()[Symbol.iterator]();!(e=(o=i.next()).done);e=!0){var a=o.value;t[a[0]]=a[1]}}catch(t){n=!0,r=t}finally{try{!e&&i.return&&i.return()}finally{if(n)throw r}}return t.attached=!1,t},getAvailableResources:function(){var t=this,e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"";Nova.request().get("/nova-api/armincms/"+this.resourceName+"/attachable/"+this.field.resourceName,{params:{search:e,resourceId:this.resourceId,withTrashed:this.field.withTrashed}}).then(function(e){var n=e.data;t.availableResources=n})},getAttachedResources:function(){var t=this,e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"";Nova.request().get("/nova-api/armincms/"+this.resourceName+"/attached/"+this.field.resourceName,{params:{search:e,resourceId:this.resourceId,withTrashed:this.field.withTrashed}}).then(function(e){var n=e.data;t.attachedResources=n})},createTag:function(t){return _.tap(Object(a.createTag)(t.text,this.attachedResources),function(e){e.attached=t.attached,e.id=t.id})},getPivotFields:function(){var t=s(o.a.mark(function t(e){var n=this;return o.a.wrap(function(t){for(;;)switch(t.prev=t.next){case 0:return t.next=2,Nova.request().get("/nova-api/armincms/"+this.resourceName+"/pivot-fields/"+this.field.resourceName,{params:{resourceId:this.resourceId,relatedId:this.processingResource.id,pivotId:this.processingResource.pivotId,editing:!0,editMode:e.attached?"update-attached":"attach"}}).then(function(t){var r=t.data;n.fields=r,_.each(n.fields,function(t){t.fill=function(){return""},e.hasOwnProperty(t.attribute)&&(t.value=e[t.attribute])})});case 2:case"end":return t.stop()}},t,this)}));return function(e){return t.apply(this,arguments)}}()},computed:{filteredResources:function(){var t=this;return this.availableResources.filter(function(e){return 0===t.tag.length||e.text.match(t.tag)})},attachmentFormData:function(){var t=this;return _.tap(new FormData,function(e){_.each(t.fields,function(t){t.fill(e)})})},placeholder:function(){return this.field.placeholder||this.__("Choose an option")},fillResources:function(){return this.attachedResources.map(function(t){return delete t.text,delete t.tiClasses,t})}}}},function(t,e,n){var r;r=function(){return function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=t,n.c=e,n.i=function(t){return t},n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:r})},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=47)}([function(t,e,n){"use strict";var r=n(46),o=n(156),i=Object.prototype.toString;function a(t){return"[object Array]"===i.call(t)}function s(t){return null!==t&&"object"==typeof t}function u(t){return"[object Function]"===i.call(t)}function c(t,e){if(null!==t&&void 0!==t)if("object"!=typeof t&&(t=[t]),a(t))for(var n=0,r=t.length;n=200&&t<300}};u.headers={common:{Accept:"application/json, text/plain, */*"}},r.forEach(["delete","get","head"],function(t){u.headers[t]={}}),r.forEach(["post","put","patch"],function(t){u.headers[t]=r.merge(i)}),t.exports=u}).call(e,n(75))},function(t,e,n){"use strict";e.__esModule=!0;var r,o=n(113),i=(r=o)&&r.__esModule?r:{default:r};e.default=function(t,e,n){return e in t?(0,i.default)(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}},function(t,e){t.exports=function(t){if(void 0==t)throw TypeError("Can't call method on "+t);return t}},function(t,e,n){var r=n(9),o=n(1).document,i=r(o)&&r(o.createElement);t.exports=function(t){return i?o.createElement(t):{}}},function(t,e){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,e){t.exports=!0},function(t,e,n){"use strict";var r=n(14);t.exports.f=function(t){return new function(t){var e,n;this.promise=new t(function(t,r){if(void 0!==e||void 0!==n)throw TypeError("Bad Promise constructor");e=t,n=r}),this.resolve=r(e),this.reject=r(n)}(t)}},function(t,e,n){var r=n(11).f,o=n(17),i=n(2)("toStringTag");t.exports=function(t,e,n){t&&!o(t=n?t:t.prototype,i)&&r(t,i,{configurable:!0,value:e})}},function(t,e,n){var r=n(60)("keys"),o=n(65);t.exports=function(t){return r[t]||(r[t]=o(t))}},function(t,e){var n=Math.ceil,r=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?r:n)(t)}},function(t,e,n){var r=n(54),o=n(27);t.exports=function(t){return r(o(t))}},function(t,e,n){var r=n(12).Symbol;t.exports=r},function(t,e,n){var r=n(170),o=n(189);t.exports=function(t,e){var n=o(t,e);return r(n)?n:void 0}},function(t,e){t.exports=function(t,e){return t===e||t!=t&&e!=e}},function(t,e){t.exports=function(t){return t}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=["1/2","1/3","2/3","1/4","3/4","1/5","2/5","3/5","4/5","1/6","5/6"]},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(154);Object.defineProperty(e,"default",{enumerable:!0,get:function(){return i(r).default}}),Object.defineProperty(e,"Form",{enumerable:!0,get:function(){return i(r).default}});var o=n(66);function i(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"Errors",{enumerable:!0,get:function(){return i(o).default}})},function(t,e,n){"use strict";(function(e){var r=n(0),o=n(101),i=n(104),a=n(110),s=n(108),u=n(45),c="undefined"!=typeof window&&window.btoa&&window.btoa.bind(window)||n(103);t.exports=function(t){return new Promise(function(A,l){var f=t.data,d=t.headers;r.isFormData(f)&&delete d["Content-Type"];var p=new XMLHttpRequest,h="onreadystatechange",v=!1;if("test"===e.env.NODE_ENV||"undefined"==typeof window||!window.XDomainRequest||"withCredentials"in p||s(t.url)||(p=new window.XDomainRequest,h="onload",v=!0,p.onprogress=function(){},p.ontimeout=function(){}),t.auth){var g=t.auth.username||"",m=t.auth.password||"";d.Authorization="Basic "+c(g+":"+m)}if(p.open(t.method.toUpperCase(),i(t.url,t.params,t.paramsSerializer),!0),p.timeout=t.timeout,p[h]=function(){if(p&&(4===p.readyState||v)&&(0!==p.status||p.responseURL&&0===p.responseURL.indexOf("file:"))){var e="getAllResponseHeaders"in p?a(p.getAllResponseHeaders()):null,n={data:t.responseType&&"text"!==t.responseType?p.response:p.responseText,status:1223===p.status?204:p.status,statusText:1223===p.status?"No Content":p.statusText,headers:e,config:t,request:p};o(A,l,n),p=null}},p.onerror=function(){l(u("Network Error",t,null,p)),p=null},p.ontimeout=function(){l(u("timeout of "+t.timeout+"ms exceeded",t,"ECONNABORTED",p)),p=null},r.isStandardBrowserEnv()){var y=n(106),x=(t.withCredentials||s(t.url))&&t.xsrfCookieName?y.read(t.xsrfCookieName):void 0;x&&(d[t.xsrfHeaderName]=x)}if("setRequestHeader"in p&&r.forEach(d,function(t,e){void 0===f&&"content-type"===e.toLowerCase()?delete d[e]:p.setRequestHeader(e,t)}),t.withCredentials&&(p.withCredentials=!0),t.responseType)try{p.responseType=t.responseType}catch(e){if("json"!==t.responseType)throw e}"function"==typeof t.onDownloadProgress&&p.addEventListener("progress",t.onDownloadProgress),"function"==typeof t.onUploadProgress&&p.upload&&p.upload.addEventListener("progress",t.onUploadProgress),t.cancelToken&&t.cancelToken.promise.then(function(t){p&&(p.abort(),l(t),p=null)}),void 0===f&&(f=null),p.send(f)})}}).call(e,n(75))},function(t,e,n){"use strict";function r(t){this.message=t}r.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},r.prototype.__CANCEL__=!0,t.exports=r},function(t,e,n){"use strict";t.exports=function(t){return!(!t||!t.__CANCEL__)}},function(t,e,n){"use strict";var r=n(100);t.exports=function(t,e,n,o,i){var a=new Error(t);return r(a,e,n,o,i)}},function(t,e,n){"use strict";t.exports=function(t,e){return function(){for(var n=new Array(arguments.length),r=0;rn;)e.push(arguments[n++]);return g[++v]=function(){s("function"==typeof t?t:Function(t),e)},r(v),v},d=function(t){delete g[t]},"process"==n(15)(l)?r=function(t){l.nextTick(a(m,t,1))}:h&&h.now?r=function(t){h.now(a(m,t,1))}:p?(i=(o=new p).port2,o.port1.onmessage=y,r=a(i.postMessage,i,1)):A.addEventListener&&"function"==typeof postMessage&&!A.importScripts?(r=function(t){A.postMessage(t+"","*")},A.addEventListener("message",y,!1)):r="onreadystatechange"in c("script")?function(t){u.appendChild(c("script")).onreadystatechange=function(){u.removeChild(this),m.call(t)}}:function(t){setTimeout(a(m,t,1),0)}),t.exports={set:f,clear:d}},function(t,e,n){var r=n(34),o=Math.min;t.exports=function(t){return t>0?o(r(t),9007199254740991):0}},function(t,e,n){var r=n(27);t.exports=function(t){return Object(r(t))}},function(t,e){var n=0,r=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++n+r).toString(36))}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=function(){function t(t,e){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{};!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.record(e)}return r(t,[{key:"all",value:function(){return this.errors}},{key:"has",value:function(t){var e=this.errors.hasOwnProperty(t);e||(e=Object.keys(this.errors).filter(function(e){return e.startsWith(t+".")||e.startsWith(t+"[")}).length>0);return e}},{key:"first",value:function(t){return this.get(t)[0]}},{key:"get",value:function(t){return this.errors[t]||[]}},{key:"any",value:function(){return Object.keys(this.errors).length>0}},{key:"record",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.errors=t}},{key:"clear",value:function(t){if(t){var e=Object.assign({},this.errors);Object.keys(e).filter(function(e){return e===t||e.startsWith(t+".")||e.startsWith(t+"[")}).forEach(function(t){return delete e[t]}),this.errors=e}else this.errors={}}}]),t}();e.default=o},function(t,e,n){var r=n(177),o=n(229),i=n(13),a=n(230),s=n(70),u=n(231),c=Object.prototype.hasOwnProperty;t.exports=function(t,e){var n=i(t),A=!n&&o(t),l=!n&&!A&&a(t),f=!n&&!A&&!l&&u(t),d=n||A||l||f,p=d?r(t.length,String):[],h=p.length;for(var v in t)!e&&!c.call(t,v)||d&&("length"==v||l&&("offset"==v||"parent"==v)||f&&("buffer"==v||"byteLength"==v||"byteOffset"==v)||s(v,h))||p.push(v);return p}},function(t,e,n){(function(e){var n="object"==typeof e&&e&&e.Object===Object&&e;t.exports=n}).call(e,n(241))},function(t,e){var n=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\ufe0e\\ufe0f]");t.exports=function(t){return n.test(t)}},function(t,e){var n=9007199254740991,r=/^(?:0|[1-9]\d*)$/;t.exports=function(t,e){var o=typeof t;return!!(e=null==e?n:e)&&("number"==o||"symbol"!=o&&r.test(t))&&t>-1&&t%1==0&&t-1&&t%1==0&&t<=n}},function(t,e,n){var r=n(178);t.exports=function(t){return null==t?"":r(t)}},function(t,e){var n,r,o=t.exports={};function i(){throw new Error("setTimeout has not been defined")}function a(){throw new Error("clearTimeout has not been defined")}function s(t){if(n===setTimeout)return setTimeout(t,0);if((n===i||!n)&&setTimeout)return n=setTimeout,setTimeout(t,0);try{return n(t,0)}catch(e){try{return n.call(null,t,0)}catch(e){return n.call(this,t,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:i}catch(t){n=i}try{r="function"==typeof clearTimeout?clearTimeout:a}catch(t){r=a}}();var u,c=[],A=!1,l=-1;function f(){A&&u&&(A=!1,u.length?c=u.concat(c):l=-1,c.length&&d())}function d(){if(!A){var t=s(f);A=!0;for(var e=c.length;e;){for(u=c,c=[];++l1)for(var n=1;n1&&void 0!==arguments[1]?arguments[1]:null;return this.viaManyToMany?this.detachResources(t):Nova.request({url:"/nova-api/"+this.resourceName,method:"delete",params:(0,i.default)({},this.queryString,{resources:a(t)})}).then(n||function(){e.deleteModalOpen=!1,e.getResources()})},deleteSelectedResources:function(){this.deleteResources(this.selectedResources)},deleteAllMatchingResources:function(){var t=this;return this.viaManyToMany?this.detachAllMatchingResources():Nova.request({url:this.deleteAllMatchingResourcesEndpoint,method:"delete",params:(0,i.default)({},this.queryString,{resources:"all"})}).then(function(){t.deleteModalOpen=!1,t.getResources()})},detachResources:function(t){var e=this;return Nova.request({url:"/nova-api/"+this.resourceName+"/detach",method:"delete",params:(0,i.default)({},this.queryString,{resources:a(t)})}).then(function(){e.deleteModalOpen=!1,e.getResources()})},detachAllMatchingResources:function(){var t=this;return Nova.request({url:"/nova-api/"+this.resourceName+"/detach",method:"delete",params:(0,i.default)({},this.queryString,{resources:"all"})}).then(function(){t.deleteModalOpen=!1,t.getResources()})},forceDeleteResources:function(t){var e=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;return Nova.request({url:"/nova-api/"+this.resourceName+"/force",method:"delete",params:(0,i.default)({},this.queryString,{resources:a(t)})}).then(n||function(){e.deleteModalOpen=!1,e.getResources()})},forceDeleteSelectedResources:function(){this.forceDeleteResources(this.selectedResources)},forceDeleteAllMatchingResources:function(){var t=this;return Nova.request({url:this.forceDeleteSelectedResourcesEndpoint,method:"delete",params:(0,i.default)({},this.queryString,{resources:"all"})}).then(function(){t.deleteModalOpen=!1,t.getResources()})},restoreResources:function(t){var e=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;return Nova.request({url:"/nova-api/"+this.resourceName+"/restore",method:"put",params:(0,i.default)({},this.queryString,{resources:a(t)})}).then(n||function(){e.restoreModalOpen=!1,e.getResources()})},restoreSelectedResources:function(){this.restoreResources(this.selectedResources)},restoreAllMatchingResources:function(){var t=this;return Nova.request({url:this.restoreAllMatchingResourcesEndpoint,method:"put",params:(0,i.default)({},this.queryString,{resources:"all"})}).then(function(){t.restoreModalOpen=!1,t.getResources()})}},computed:{deleteAllMatchingResourcesEndpoint:function(){return this.lens?"/nova-api/"+this.resourceName+"/lens/"+this.lens:"/nova-api/"+this.resourceName},forceDeleteSelectedResourcesEndpoint:function(){return this.lens?"/nova-api/"+this.resourceName+"/lens/"+this.lens+"/force":"/nova-api/"+this.resourceName+"/force"},restoreAllMatchingResourcesEndpoint:function(){return this.lens?"/nova-api/"+this.resourceName+"/lens/"+this.lens+"/restore":"/nova-api/"+this.resourceName+"/restore"},queryString:function(){return{search:this.currentSearch,filters:this.encodedFilters,trashed:this.currentTrashed,viaResource:this.viaResource,viaResourceId:this.viaResourceId,viaRelationship:this.viaRelationship}}}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=a(n(50)),o=a(n(26)),i=a(n(49));a(n(226)),a(n(228));function a(t){return t&&t.__esModule?t:{default:t}}e.default={methods:{clearSelectedFilters:function(){var t=(0,i.default)(r.default.mark(function t(e){var n;return r.default.wrap(function(t){for(;;)switch(t.prev=t.next){case 0:if(!e){t.next=5;break}return t.next=3,this.$store.dispatch(this.resourceName+"/resetFilterState",{resourceName:this.resourceName,lens:e});case 3:t.next=7;break;case 5:return t.next=7,this.$store.dispatch(this.resourceName+"/resetFilterState",{resourceName:this.resourceName});case 7:this.updateQueryString((n={},(0,o.default)(n,this.pageParameter,1),(0,o.default)(n,this.filterParameter,""),n));case 8:case"end":return t.stop()}},t,this)}));return function(e){return t.apply(this,arguments)}}(),filterChanged:function(){var t;this.updateQueryString((t={},(0,o.default)(t,this.pageParameter,1),(0,o.default)(t,this.filterParameter,this.$store.getters[this.resourceName+"/currentEncodedFilters"]),t))},initializeFilters:function(){var t=(0,i.default)(r.default.mark(function t(e){return r.default.wrap(function(t){for(;;)switch(t.prev=t.next){case 0:return this.$store.commit(this.resourceName+"/clearFilters"),t.next=3,this.$store.dispatch(this.resourceName+"/fetchFilters",{resourceName:this.resourceName,viaResource:this.viaResource,viaResourceId:this.viaResourceId,viaRelationship:this.viaRelationship,lens:e});case 3:return t.next=5,this.initializeState(e);case 5:case"end":return t.stop()}},t,this)}));return function(e){return t.apply(this,arguments)}}(),initializeState:function(){var t=(0,i.default)(r.default.mark(function t(e){return r.default.wrap(function(t){for(;;)switch(t.prev=t.next){case 0:if(!this.initialEncodedFilters){t.next=5;break}return t.next=3,this.$store.dispatch(this.resourceName+"/initializeCurrentFilterValuesFromQueryString",this.initialEncodedFilters);case 3:t.next=7;break;case 5:return t.next=7,this.$store.dispatch(this.resourceName+"/resetFilterState",{resourceName:this.resourceName,lens:e});case 7:case"end":return t.stop()}},t,this)}));return function(e){return t.apply(this,arguments)}}()},computed:{filterParameter:function(){return this.resourceName+"_filter"}}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default={props:{resourceName:{},field:{}},data:function(){return{value:""}},mounted:function(){var t=this;this.setInitialValue(),this.field.fill=this.fill,Nova.$on(this.field.attribute+"-value",function(e){t.value=e})},destroyed:function(){Nova.$off(this.field.attribute+"-value")},methods:{setInitialValue:function(){this.value=void 0!==this.field.value&&null!==this.field.value?this.field.value:""},fill:function(t){t.append(this.field.attribute,String(this.value))},handleChange:function(t){this.value=t}},computed:{isReadonly:function(){return this.field.readonly||_.get(this.field,"extraAttributes.readonly")}}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(41);e.default={props:{errors:{default:function(){return new r.Errors}}},data:function(){return{errorClass:"border-danger"}},computed:{errorClasses:function(){return this.hasError?[this.errorClass]:[]},fieldAttribute:function(){return this.field.attribute},hasError:function(){return this.errors.has(this.fieldAttribute)},firstError:function(){if(this.hasError)return this.errors.first(this.fieldAttribute)}}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=a(n(50)),o=a(n(49)),i=a(n(40));function a(t){return t&&t.__esModule?t:{default:t}}e.default={props:{loadCards:{type:Boolean,default:!0}},data:function(){return{cards:[]}},created:function(){this.fetchCards()},watch:{cardsEndpoint:function(){this.fetchCards()}},methods:{fetchCards:function(){var t=(0,o.default)(r.default.mark(function t(){var e,n;return r.default.wrap(function(t){for(;;)switch(t.prev=t.next){case 0:if(!this.loadCards){t.next=6;break}return t.next=3,Nova.request().get(this.cardsEndpoint,{params:this.extraCardParams});case 3:e=t.sent,n=e.data,this.cards=n;case 6:case"end":return t.stop()}},t,this)}));return function(){return t.apply(this,arguments)}}()},computed:{shouldShowCards:function(){return this.cards.length>0},smallCards:function(){return _.filter(this.cards,function(t){return-1!==i.default.indexOf(t.width)})},largeCards:function(){return _.filter(this.cards,function(t){return"full"==t.width})},extraCardParams:function(){return null}}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default={methods:{toAppTimezone:function(t){return t?moment.tz(t,this.userTimezone).clone().tz(Nova.config.timezone).format("YYYY-MM-DD HH:mm:ss"):t},fromAppTimezone:function(t){return t?moment.tz(t,Nova.config.timezone).clone().tz(this.userTimezone).format("YYYY-MM-DD HH:mm:ss"):t},localizeDateTimeField:function(t){if(!t.value)return t.value;var e=moment.tz(t.value,Nova.config.timezone).clone().tz(this.userTimezone);return t.format?e.format(t.format):this.usesTwelveHourTime?e.format("YYYY-MM-DD h:mm:ss A"):e.format("YYYY-MM-DD HH:mm:ss")},localizeDateField:function(t){if(!t.value)return t.value;var e=moment.tz(t.value,Nova.config.timezone).clone().tz(this.userTimezone);return t.format?e.format(t.format):e.format("YYYY-MM-DD")}},computed:{userTimezone:function(){return Nova.config.userTimezone?Nova.config.userTimezone:moment.tz.guess()},usesTwelveHourTime:function(){return _.endsWith((new Date).toLocaleString(),"AM")||_.endsWith((new Date).toLocaleString(),"PM")}}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r,o=n(225),i=(r=o)&&r.__esModule?r:{default:r};e.default={methods:{updateQueryString:function(t){this.$router.push({query:(0,i.default)(t,this.$route.query)})}}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default={computed:{resourceInformation:function(){var t=this;return _.find(Nova.config.resources,function(e){return e.uriKey==t.resourceName})},viaResourceInformation:function(){var t=this;if(this.viaResource)return _.find(Nova.config.resources,function(e){return e.uriKey==t.viaResource})},authorizedToCreate:function(){return this.resourceInformation.authorizedToCreate}}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r,o=n(26),i=(r=o)&&r.__esModule?r:{default:r};e.default={methods:{selectPreviousPage:function(){this.updateQueryString((0,i.default)({},this.pageParameter,this.currentPage-1))},selectNextPage:function(){this.updateQueryString((0,i.default)({},this.pageParameter,this.currentPage+1))}},computed:{currentPage:function(){return parseInt(this.$route.query[this.pageParameter]||1)}}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r,o=n(26),i=(r=o)&&r.__esModule?r:{default:r};e.default={data:function(){return{perPage:25}},methods:{initializePerPageFromQueryString:function(){this.perPage=this.currentPerPage},perPageChanged:function(){this.updateQueryString((0,i.default)({},this.perPageParameter,this.perPage))}},computed:{currentPerPage:function(){return this.$route.query[this.perPageParameter]||25}}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r,o=n(224),i=(r=o)&&r.__esModule?r:{default:r};e.default={data:function(){return{search:"",selectedResource:"",availableResources:[]}},methods:{selectResource:function(t){this.selectedResource=t},handleSearchCleared:function(){this.availableResources=[]},clearSelection:function(){this.selectedResource="",this.availableResources=[]},performSearch:function(t){var e=this;this.search=t;var n=t.trim();""!=n&&this.debouncer(function(){e.getAvailableResources(n)},500)},debouncer:(0,i.default)(function(t){return t()},500)}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default={data:function(){return{withTrashed:!1}},methods:{toggleWithTrashed:function(){this.withTrashed=!this.withTrashed},enableWithTrashed:function(){this.withTrashed=!0},disableWithTrashed:function(){this.withTrashed=!1}}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(t){return(0,i.default)(t)};var r,o=n(238),i=(r=o)&&r.__esModule?r:{default:r}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r,o=n(48),i=(r=o)&&r.__esModule?r:{default:r};e.default=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:100;return i.default.all([t,new i.default(function(t){setTimeout(function(){return t()},e)})]).then(function(t){return t[0]})}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(t,e){return t>1||0==t?r.Inflector.pluralize(e):r.Inflector.singularize(e)};var r=n(47)},function(t,e,n){"use strict";var r={uncountableWords:["equipment","information","rice","money","species","series","fish","sheep","moose","deer","news"],pluralRules:[[new RegExp("(m)an$","gi"),"$1en"],[new RegExp("(pe)rson$","gi"),"$1ople"],[new RegExp("(child)$","gi"),"$1ren"],[new RegExp("^(ox)$","gi"),"$1en"],[new RegExp("(ax|test)is$","gi"),"$1es"],[new RegExp("(octop|vir)us$","gi"),"$1i"],[new RegExp("(alias|status)$","gi"),"$1es"],[new RegExp("(bu)s$","gi"),"$1ses"],[new RegExp("(buffal|tomat|potat)o$","gi"),"$1oes"],[new RegExp("([ti])um$","gi"),"$1a"],[new RegExp("sis$","gi"),"ses"],[new RegExp("(?:([^f])fe|([lr])f)$","gi"),"$1$2ves"],[new RegExp("(hive)$","gi"),"$1s"],[new RegExp("([^aeiouy]|qu)y$","gi"),"$1ies"],[new RegExp("(x|ch|ss|sh)$","gi"),"$1es"],[new RegExp("(matr|vert|ind)ix|ex$","gi"),"$1ices"],[new RegExp("([m|l])ouse$","gi"),"$1ice"],[new RegExp("(quiz)$","gi"),"$1zes"],[new RegExp("s$","gi"),"s"],[new RegExp("$","gi"),"s"]],singularRules:[[new RegExp("(m)en$","gi"),"$1an"],[new RegExp("(pe)ople$","gi"),"$1rson"],[new RegExp("(child)ren$","gi"),"$1"],[new RegExp("([ti])a$","gi"),"$1um"],[new RegExp("((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$","gi"),"$1$2sis"],[new RegExp("(hive)s$","gi"),"$1"],[new RegExp("(tive)s$","gi"),"$1"],[new RegExp("(curve)s$","gi"),"$1"],[new RegExp("([lr])ves$","gi"),"$1f"],[new RegExp("([^fo])ves$","gi"),"$1fe"],[new RegExp("([^aeiouy]|qu)ies$","gi"),"$1y"],[new RegExp("(s)eries$","gi"),"$1eries"],[new RegExp("(m)ovies$","gi"),"$1ovie"],[new RegExp("(x|ch|ss|sh)es$","gi"),"$1"],[new RegExp("([m|l])ice$","gi"),"$1ouse"],[new RegExp("(bus)es$","gi"),"$1"],[new RegExp("(o)es$","gi"),"$1"],[new RegExp("(shoe)s$","gi"),"$1"],[new RegExp("(cris|ax|test)es$","gi"),"$1is"],[new RegExp("(octop|vir)i$","gi"),"$1us"],[new RegExp("(alias|status)es$","gi"),"$1"],[new RegExp("^(ox)en","gi"),"$1"],[new RegExp("(vert|ind)ices$","gi"),"$1ex"],[new RegExp("(matr)ices$","gi"),"$1ix"],[new RegExp("(quiz)zes$","gi"),"$1"],[new RegExp("s$","gi"),""]],nonTitlecasedWords:["and","or","nor","a","an","the","so","but","to","of","at","by","from","into","on","onto","off","out","in","over","with","for"],idSuffix:new RegExp("(_ids|_id)$","g"),underbar:new RegExp("_","g"),spaceOrUnderbar:new RegExp("[ _]","g"),uppercase:new RegExp("([A-Z])","g"),underbarPrefix:new RegExp("^_"),applyRules:function(t,e,n,r){if(r)t=r;else if(!(n.indexOf(t.toLowerCase())>-1))for(var o=0;o>8-s%1*8)){if((n=i.charCodeAt(s+=.75))>255)throw new o;e=e<<8|n}return a}},function(t,e,n){"use strict";var r=n(0);function o(t){return encodeURIComponent(t).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}t.exports=function(t,e,n){if(!e)return t;var i;if(n)i=n(e);else if(r.isURLSearchParams(e))i=e.toString();else{var a=[];r.forEach(e,function(t,e){null!==t&&void 0!==t&&(r.isArray(t)?e+="[]":t=[t],r.forEach(t,function(t){r.isDate(t)?t=t.toISOString():r.isObject(t)&&(t=JSON.stringify(t)),a.push(o(e)+"="+o(t))}))}),i=a.join("&")}return i&&(t+=(-1===t.indexOf("?")?"?":"&")+i),t}},function(t,e,n){"use strict";t.exports=function(t,e){return e?t.replace(/\/+$/,"")+"/"+e.replace(/^\/+/,""):t}},function(t,e,n){"use strict";var r=n(0);t.exports=r.isStandardBrowserEnv()?{write:function(t,e,n,o,i,a){var s=[];s.push(t+"="+encodeURIComponent(e)),r.isNumber(n)&&s.push("expires="+new Date(n).toGMTString()),r.isString(o)&&s.push("path="+o),r.isString(i)&&s.push("domain="+i),!0===a&&s.push("secure"),document.cookie=s.join("; ")},read:function(t){var e=document.cookie.match(new RegExp("(^|;\\s*)("+t+")=([^;]*)"));return e?decodeURIComponent(e[3]):null},remove:function(t){this.write(t,"",Date.now()-864e5)}}:{write:function(){},read:function(){return null},remove:function(){}}},function(t,e,n){"use strict";t.exports=function(t){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(t)}},function(t,e,n){"use strict";var r=n(0);t.exports=r.isStandardBrowserEnv()?function(){var t,e=/(msie|trident)/i.test(navigator.userAgent),n=document.createElement("a");function o(t){var r=t;return e&&(n.setAttribute("href",r),r=n.href),n.setAttribute("href",r),{href:n.href,protocol:n.protocol?n.protocol.replace(/:$/,""):"",host:n.host,search:n.search?n.search.replace(/^\?/,""):"",hash:n.hash?n.hash.replace(/^#/,""):"",hostname:n.hostname,port:n.port,pathname:"/"===n.pathname.charAt(0)?n.pathname:"/"+n.pathname}}return t=o(window.location.href),function(e){var n=r.isString(e)?o(e):e;return n.protocol===t.protocol&&n.host===t.host}}():function(){return!0}},function(t,e,n){"use strict";var r=n(0);t.exports=function(t,e){r.forEach(t,function(n,r){r!==e&&r.toUpperCase()===e.toUpperCase()&&(t[e]=n,delete t[r])})}},function(t,e,n){"use strict";var r=n(0),o=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];t.exports=function(t){var e,n,i,a={};return t?(r.forEach(t.split("\n"),function(t){if(i=t.indexOf(":"),e=r.trim(t.substr(0,i)).toLowerCase(),n=r.trim(t.substr(i+1)),e){if(a[e]&&o.indexOf(e)>=0)return;a[e]="set-cookie"===e?(a[e]?a[e]:[]).concat([n]):a[e]?a[e]+", "+n:n}}),a):a}},function(t,e,n){"use strict";t.exports=function(t){return function(e){return t.apply(null,e)}}},function(t,e,n){t.exports={default:n(115),__esModule:!0}},function(t,e,n){t.exports={default:n(116),__esModule:!0}},function(t,e,n){"use strict";e.__esModule=!0;var r,o=n(112),i=(r=o)&&r.__esModule?r:{default:r};e.default=i.default||function(t){for(var e=1;eA;)if((s=u[A++])!=s)return!0}else for(;c>A;A++)if((t||A in u)&&u[A]===n)return t||A||0;return!t&&-1}}},function(t,e,n){var r=n(16),o=n(125),i=n(124),a=n(4),s=n(63),u=n(144),c={},A={};(e=t.exports=function(t,e,n,l,f){var d,p,h,v,g=f?function(){return t}:u(t),m=r(n,l,e?2:1),y=0;if("function"!=typeof g)throw TypeError(t+" is not iterable!");if(i(g)){for(d=s(t.length);d>y;y++)if((v=e?m(a(p=t[y])[0],p[1]):m(t[y]))===c||v===A)return v}else for(h=g.call(t);!(p=h.next()).done;)if((v=o(h,m,p.value,e))===c||v===A)return v}).BREAK=c,e.RETURN=A},function(t,e,n){t.exports=!n(5)&&!n(29)(function(){return 7!=Object.defineProperty(n(28)("div"),"a",{get:function(){return 7}}).a})},function(t,e){t.exports=function(t,e,n){var r=void 0===n;switch(e.length){case 0:return r?t():t.call(n);case 1:return r?t(e[0]):t.call(n,e[0]);case 2:return r?t(e[0],e[1]):t.call(n,e[0],e[1]);case 3:return r?t(e[0],e[1],e[2]):t.call(n,e[0],e[1],e[2]);case 4:return r?t(e[0],e[1],e[2],e[3]):t.call(n,e[0],e[1],e[2],e[3])}return t.apply(n,e)}},function(t,e,n){var r=n(10),o=n(2)("iterator"),i=Array.prototype;t.exports=function(t){return void 0!==t&&(r.Array===t||i[o]===t)}},function(t,e,n){var r=n(4);t.exports=function(t,e,n,o){try{return o?e(r(n)[0],n[1]):e(n)}catch(e){var i=t.return;throw void 0!==i&&r(i.call(t)),e}}},function(t,e,n){"use strict";var r=n(131),o=n(59),i=n(32),a={};n(7)(a,n(2)("iterator"),function(){return this}),t.exports=function(t,e,n){t.prototype=r(a,{next:o(1,n)}),i(t,e+" Iterator")}},function(t,e,n){var r=n(2)("iterator"),o=!1;try{var i=[7][r]();i.return=function(){o=!0},Array.from(i,function(){throw 2})}catch(t){}t.exports=function(t,e){if(!e&&!o)return!1;var n=!1;try{var i=[7],a=i[r]();a.next=function(){return{done:n=!0}},i[r]=function(){return a},t(i)}catch(t){}return n}},function(t,e){t.exports=function(t,e){return{value:e,done:!!t}}},function(t,e,n){var r=n(1),o=n(62).set,i=r.MutationObserver||r.WebKitMutationObserver,a=r.process,s=r.Promise,u="process"==n(15)(a);t.exports=function(){var t,e,n,c=function(){var r,o;for(u&&(r=a.domain)&&r.exit();t;){o=t.fn,t=t.next;try{o()}catch(r){throw t?n():e=void 0,r}}e=void 0,r&&r.enter()};if(u)n=function(){a.nextTick(c)};else if(!i||r.navigator&&r.navigator.standalone)if(s&&s.resolve){var A=s.resolve(void 0);n=function(){A.then(c)}}else n=function(){o.call(r,c)};else{var l=!0,f=document.createTextNode("");new i(c).observe(f,{characterData:!0}),n=function(){f.data=l=!l}}return function(r){var o={fn:r,next:void 0};e&&(e.next=o),t||(t=o,n()),e=o}}},function(t,e,n){"use strict";var r=n(56),o=n(133),i=n(136),a=n(64),s=n(54),u=Object.assign;t.exports=!u||n(29)(function(){var t={},e={},n=Symbol(),r="abcdefghijklmnopqrst";return t[n]=7,r.split("").forEach(function(t){e[t]=t}),7!=u({},t)[n]||Object.keys(u({},e)).join("")!=r})?function(t,e){for(var n=a(t),u=arguments.length,c=1,A=o.f,l=i.f;u>c;)for(var f,d=s(arguments[c++]),p=A?r(d).concat(A(d)):r(d),h=p.length,v=0;h>v;)l.call(d,f=p[v++])&&(n[f]=d[f]);return n}:u},function(t,e,n){var r=n(4),o=n(132),i=n(52),a=n(33)("IE_PROTO"),s=function(){},u=function(){var t,e=n(28)("iframe"),r=i.length;for(e.style.display="none",n(53).appendChild(e),e.src="javascript:",(t=e.contentWindow.document).open(),t.write(" diff --git a/resources/js/components/FormField.vue b/resources/js/components/FormField.vue new file mode 100644 index 0000000..754a70f --- /dev/null +++ b/resources/js/components/FormField.vue @@ -0,0 +1,460 @@ + + + + + + \ No newline at end of file diff --git a/resources/js/components/IndexField.vue b/resources/js/components/IndexField.vue new file mode 100644 index 0000000..9fd44cc --- /dev/null +++ b/resources/js/components/IndexField.vue @@ -0,0 +1,27 @@ + + + diff --git a/resources/js/field.js b/resources/js/field.js new file mode 100644 index 0000000..4f21926 --- /dev/null +++ b/resources/js/field.js @@ -0,0 +1,5 @@ +Nova.booting((Vue, router, store) => { + Vue.component('index-armincms-belongs-to-many', require('./components/IndexField')) + Vue.component('detail-armincms-belongs-to-many', require('./components/DetailField')) + Vue.component('form-armincms-belongs-to-many', require('./components/FormField')) +}) diff --git a/resources/sass/field.scss b/resources/sass/field.scss new file mode 100644 index 0000000..f85ad40 --- /dev/null +++ b/resources/sass/field.scss @@ -0,0 +1 @@ +// Nova Tool CSS diff --git a/routes/api.php b/routes/api.php new file mode 100644 index 0000000..3737126 --- /dev/null +++ b/routes/api.php @@ -0,0 +1,6 @@ +resourceClass = $resource; + $this->resourceName = $resource::uriKey(); + $this->manyToManyRelationship = $this->attribute; + $this->deleteCallback = $this->detachmentCallback(); + + $this->fieldsCallback = function () { + return []; + }; + } + + /** + * Determine if the field should be displayed for the given request. + * + * @param \Illuminate\Http\Request $request + * @return bool + */ + public function authorize(Request $request) + { + return call_user_func( + [$this->resourceClass, 'authorizedToViewAny'], $request + ) && parent::authorize($request); + } + + /** + * Resolve the field's value. + * + * @param mixed $resource + * @param string|null $attribute + * @return void + */ + public function resolve($resource, $attribute = null) + { + $value = null; + + if ($resource->relationLoaded($this->manyToManyRelationship)) { + $value = $resource->getRelation($this->manyToManyRelationship); + } + + if (! $value) { + $value = $resource->{$this->manyToManyRelationship}() + ->withoutGlobalScopes() + ->getResults(); + } + + $this->value = collect($value)->map(function($resource) { + $display = $this->formatAttachableResource( + app(NovaRequest::class), new $this->resourceClass($resource) + ); + + return array_merge(['pivotId' => $resource->pivot->id], $display); + }); + } + + /** + * Hydrate the given attribute on the model based on the incoming request. + * + * @param \Laravel\Nova\Http\Requests\NovaRequest $request + * @param string $requestAttribute + * @param object $model + * @param string $attribute + * @return mixed + */ + protected function fillAttributeFromRequest(NovaRequest $request, $requestAttribute, $model, $attribute) + { + if ($request->exists($requestAttribute)) { + $value = json_decode($request[$requestAttribute], true); + + $model::saved(function($model) use ($value, $request) { + $authorized = $this->removeNonAuthorizedAttachments($request, $value, $model); + + $relationship = $model->{$this->manyToManyRelationship}()->withPivot('id'); + + $attaching = $authorized->reject->attached; + + $detaching = $this->mergeDetachments($model, $authorized); + + $relationship->wherePivotIn('id', $detaching->pluck('pivotId')->all()) + ->detach($detaching->pluck('id')->all()); + + if(! $this->duplicate) { + $attaching = $this->removeDuplicateAttachments($model, $attaching) + ->keyBy('id') + ->map(function($attachment) { + return $this->removeHelperData($attachment); + })->all(); + + $relationship->syncWithoutDetaching($attaching); + } else { + $attaching->each(function($attachment) use ($relationship) { + $relationship->attach( + $attachment['id'], $this->removeHelperData($attachment) + ); + }); + } + }); + } + } + + /** + * Remove non authorized attachments. + * + * @param \Laravel\Nova\Http\Requests\NovaRequest $request + * @param array $attachments + * @param integer $model + * @return array + */ + protected function removeNonAuthorizedAttachments(NovaRequest $request, $attachments, $model) + { + return collect($attachments)->filter(function($attachment) use ($request, $model) { + return $this->authorizedToAttach($request, $attachment['id']); + }); + } + + /** + * Detect if user can attach related resource + * @param NovaRequest $request + * @param array $attachment + * @return boolean + */ + protected function authorizedToAttach(NovaRequest $request, $attachment) + { + $parentModel = $request->resourceId + ? $request->findModelOrFail() : $request->model(); + + $parentResource = Nova::resourceForModel($parentModel); + + + return (new $parentResource($parentModel))->authorizedToAttachAny( + $request, $attachment + ) || (new $parentResource($parentModel))->authorizedToAttach( + $request, $attachment + ); + } + + /** + * Append database detachemnts into detachments. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @param array $authorized + * @return array + */ + protected function mergeDetachments($model, $authorized) + { + $pivotKeys = $authorized->filter->attached->pluck('pivotId')->all(); + + $shouldDetach = $model->{$this->manyToManyRelationship}() + ->withPivot('id') + ->wherePivotNotIn('id', $pivotKeys)->get() + ->map(function($related) { + return [ + 'id' => $related->id, + 'pivotId' => $related->pivot->id, + ]; + }); + + return $authorized->reject->attached->merge($shouldDetach); + } + + /** + * Remove related that before is attached + * + * @param \Illuminate\Database\Eloquent\Model $model + * @param array $attaching + * @return array + */ + public function removeDuplicateAttachments($model, $attaching) + { + $attachments = $model->{$this->manyToManyRelationship}()->get()->pluck('id'); + + return $attaching->reject(function($attachment) use ($attachments) { + return $attachments->contains($attachment['id']); + }); + } + + /** + * Remove data that append for vuejs + * + * @param array $attachment + * @return array + */ + protected function removeHelperData($attachment) + { + return collect($attachment)->reject(function($value, $key) { + return in_array($key, ['id', 'attached', 'pivotId']); + })->all(); + } + + /** + * Build an attachable query for the field. + * + * @param \Laravel\Nova\Http\Requests\NovaRequest $request + * @param bool $withTrashed + * @return \Illuminate\Database\Eloquent\Builder + */ + public function buildAttachableQuery(NovaRequest $request, $withTrashed = false) + { + $model = forward_static_call([$resourceClass = $this->resourceClass, 'newModel']); + + $query = $resourceClass::buildIndexQuery( + $request, $model->newQuery(), $request->search, [], [], + TrashedStatus::fromBoolean($withTrashed) + ); + + return $query->tap(function ($query) use ($request, $model) { + forward_static_call($this->attachableQueryCallable($request, $model), $request, $query); + }); + } + + /** + * Get the attachable query method name. + * + * @param \Laravel\Nova\Http\Requests\NovaRequest $request + * @param \Illuminate\Database\Eloquent\Model $model + * @return array + */ + protected function attachableQueryCallable(NovaRequest $request, $model) + { + return ($method = $this->attachableQueryMethod($request, $model)) + ? [$request->resource(), $method] + : [$this->resourceClass, 'relatableQuery']; + } + + /** + * Get the attachable query method name. + * + * @param \Laravel\Nova\Http\Requests\NovaRequest $request + * @param \Illuminate\Database\Eloquent\Model $model + * @return string + */ + protected function attachableQueryMethod(NovaRequest $request, $model) + { + $method = 'relatable'.Str::plural(class_basename($model)); + + if (method_exists($request->resource(), $method)) { + return $method; + } + } + + /** + * Format the given attachable resource. + * + * @param \Laravel\Nova\Http\Requests\NovaRequest $request + * @param mixed $resource + * @return array + */ + public function formatAttachableResource(NovaRequest $request, $resource, $attached = false) + { + return array_filter([ + 'avatar' => $resource->resolveAvatarUrl($request), + 'text' => $this->formatDisplayValue($resource), + 'id' => $resource->getKey(), + 'attached' => $attached, + ]); + } + + /** + * Specify the callback to be executed to retrieve the pivot fields. + * + * @param callable $callback + * @return $this + */ + public function fields(callable $callback) + { + $this->fieldsCallback = $callback; + + return $this; + } + + /** + * Set the label of the resource selection. + * + * @return string + */ + public function placeholder($placeholder) + { + $this->placeholder = $placeholder; + + return $this; + } + + /** + * Set the duplicate attachment status. + * + * @return string + */ + public function duplicate(bool $duplicate = true) + { + $this->duplicate = $duplicate; + + return $this; + } + + /** + * Set the pivots attachment status. + * + * @return string + */ + public function pivots(bool $pivots = true) + { + $this->pivots = $pivots; + + return $this; + } + + /** + * hides the "With Trashed" option. + * + * @return $this + */ + public function withoutTrashed() + { + $this->displaysWithTrashed = false; + + return $this; + } + + /** + * Prepare the field for JSON serialization. + * + * @return array + */ + public function jsonSerialize() + { + return array_merge([ + 'belongsToManyRelationship' => $this->manyToManyRelationship, + 'resourceName' => $this->resourceName, + 'placeholder' => $this->placeholder, + 'duplicate' => $this->duplicate, + 'pivots' => $this->pivots, + 'withTrashed' => $this->displaysWithTrashed, + ], parent::jsonSerialize()); + } +} diff --git a/src/FieldServiceProvider.php b/src/FieldServiceProvider.php new file mode 100644 index 0000000..2261931 --- /dev/null +++ b/src/FieldServiceProvider.php @@ -0,0 +1,54 @@ +app->booted(function() { + $this->routes(); + }); + } + + /** + * Register the tool's routes. + * + * @return void + */ + protected function routes() + { + if ($this->app->routesAreCached()) { + return; + } + + \Route::middleware(['nova']) + ->prefix('nova-api/armincms') + ->namespace(__NAMESPACE__.'\\Http\\Controllers') + ->group(__DIR__.'/../routes/api.php'); + } + + /** + * Register any application services. + * + * @return void + */ + public function register() + { + // + } +} diff --git a/src/Http/Controllers/AttachableController.php b/src/Http/Controllers/AttachableController.php new file mode 100644 index 0000000..f94aa44 --- /dev/null +++ b/src/Http/Controllers/AttachableController.php @@ -0,0 +1,67 @@ +relatedFieldFor( + $request, $resource = $this->resource($request), $request->field + ); + + $attachedResources = collect($resource->{$field->manyToManyRelationship})->map->getKey(); + + $withTrashed = $this->shouldIncludeTrashed( + $request, $associatedResource = $field->resourceClass + ); + + return $field->buildAttachableQuery($request, $withTrashed)->get() + ->mapInto($associatedResource) + ->map(function ($resource) use ($request, $field, $attachedResources) { + $key = $resource->resource->getKey(); + + return array_merge([ + 'text' => $key, + 'attached' => $attachedResources->contains($key), + ], $field->formatAttachableResource($request, $resource)); + })->sortBy('display', SORT_NATURAL | SORT_FLAG_CASE)->values(); + } + + /** + * Determine if the query should include trashed models. + * + * @param \Laravel\Nova\Http\Requests\NovaRequest $request + * @param string $associatedResource + * @return bool + */ + protected function shouldIncludeTrashed(NovaRequest $request, $associatedResource) + { + if ($request->withTrashed === 'true') { + return true; + } + + $associatedModel = $associatedResource::newModel(); + + if ($request->current && $associatedResource::softDeletes()) { + $associatedModel = $associatedModel->newQueryWithoutScopes()->find($request->current); + + return $associatedModel ? $associatedModel->trashed() : false; + } + + return false; + } +} diff --git a/src/Http/Controllers/AttachedController.php b/src/Http/Controllers/AttachedController.php new file mode 100644 index 0000000..33e7d9b --- /dev/null +++ b/src/Http/Controllers/AttachedController.php @@ -0,0 +1,39 @@ +relatedFieldFor( + $request, $resource = $this->resource($request), $request->field + ); + + $relationship = $resource->{$field->manyToManyRelationship}(); + $accessor = $relationship->getPivotAccessor(); + + + return $relationship->withPivot('id')->get() + ->mapInto($field->resourceClass) + ->map(function($relatedResource) use ($field, $request, $accessor) { + return array_merge([ + 'pivotId' => $relatedResource->{$accessor}->id, + 'attached' => true, + ], $field->formatAttachableResource($request, $relatedResource)); + })->values(); + } +} diff --git a/src/Http/Controllers/InteractsWithResourceRequest.php b/src/Http/Controllers/InteractsWithResourceRequest.php new file mode 100644 index 0000000..572adc7 --- /dev/null +++ b/src/Http/Controllers/InteractsWithResourceRequest.php @@ -0,0 +1,15 @@ +newResourceWith( + $request->findModelQuery($resourceId ?? $request->resourceId)->first() ?? $request->model() + ); + } +} diff --git a/src/Http/Controllers/PivotFieldController.php b/src/Http/Controllers/PivotFieldController.php new file mode 100644 index 0000000..894b027 --- /dev/null +++ b/src/Http/Controllers/PivotFieldController.php @@ -0,0 +1,23 @@ +json($this->fields($request, $this->resource($request))->all()); + } +} diff --git a/src/Http/Controllers/PivotValidateController.php b/src/Http/Controllers/PivotValidateController.php new file mode 100644 index 0000000..6624801 --- /dev/null +++ b/src/Http/Controllers/PivotValidateController.php @@ -0,0 +1,41 @@ +isCreateOrAttachRequest() + ? $field->getCreationRules($request) + : $field->getUpdateRules($request) + ); + + }; + + $this->fields($request, $this->resource($request))->each($ruleCallback); + + Validator::make($request->all(), $rules)->validate(); + + return response()->json(); + } +} diff --git a/src/Http/Controllers/ResolvesFields.php b/src/Http/Controllers/ResolvesFields.php new file mode 100644 index 0000000..5b20c5d --- /dev/null +++ b/src/Http/Controllers/ResolvesFields.php @@ -0,0 +1,200 @@ +resolvePivotFields( + $request, $resource, $request->relatedResource + ); + + if($request->isUpdateOrUpdateAttachedRequest()) { + return $this->removeNonUpdateFields($request, $fields, $resource); + } + + if($request->isCreateOrAttachRequest()) { + return $this->removeNonCreationFields($request, $fields); + } + + return $this->removeNonDetailFields($request, $fields, $resource) + ->each(function($field) use ($request, $resource) { + $relatedClass = Nova::resourceForKey($request->relatedResource); + + $relatedResource = new $relatedClass( + $this->pivotModel($request, $resource, $request->relatedResource) + ); + + $field->resolveForDisplay($relatedResource); + }); + } + + /** + * Resolve the pivot fields for the requested resource. + * + * @param \Laravel\Nova\Http\Requests\NovaRequest $request + * @param \Laravel\Nova\Resource $resource + * @param string $relatedResource + * @return \Laravel\Nova\Fields\FieldCollection + */ + public function resolvePivotFields(NovaRequest $request, $resource, $relatedResource) + { + $pivotModel = $this->pivotModel($request, $resource, $relatedResource); + + $fields = $this->pivotFieldsFor($request, $resource, $relatedResource); + + $resolveCallback = function ($field) use ($pivotModel) { + if ($field instanceof Resolvable) { + $field->resolve($pivotModel); + } + }; + + return FieldCollection::make($this->filter($fields->each($resolveCallback)->filter->authorize($request)->values()->all()))->values(); + } + + /** + * Resolve the pivot model for the requested resource and requested related id. + * + * @param \Laravel\Nova\Http\Requests\NovaRequest $request + * @param \Laravel\Nova\Resource $resource + * @param string $relatedResource + * @return \Illuminate\Database\Eloquent\Model + */ + public function pivotModel(NovaRequest $request, $resource, $relatedResource) + { + $relatedField = $this->relatedFieldFor($request, $resource, $relatedResource); + + $accessor = $this->pivotAccessorFor($request, $resource, $relatedField); + + $query = $resource->{$relatedField->manyToManyRelationship}(); + + if($related = $query->wherePivot('id', $request->pivotId)->first()) { + return $related->{$accessor}; + } + + return new Pivot; + } + + /** + * Get the pivot fields for the resource and relation. + * + * @param \Laravel\Nova\Http\Requests\NovaRequest $request + * @param string $resource + * @return \Laravel\Nova\Fields\FieldCollection + */ + public function pivotFieldsFor($request, $resource) + { + $field = $this->relatedFieldFor($request, $resource, $request->relatedResource); + + if($field && isset($field->fieldsCallback)) { + return FieldCollection::make(array_values( + $this->filter(call_user_func($field->fieldsCallback, $request, $resource)) + ))->each(function ($field) { + $field->pivot = true; + }); + } + + return new FieldCollection; + } + + /** + * Get the pivot fields for the resource and relation. + * + * @param \Laravel\Nova\Http\Requests\NovaRequest $request + * @param string $relatedResource + * @return \Laravel\Nova\Fields\FieldCollection + */ + public function relatedFieldFor(NovaRequest $request, $resource, $relatedResource) + { + return $resource->availableFields($request) + ->whereInstanceOf(BelongsToMany::class) + ->where('resourceName', $relatedResource) + ->first(); + } + + /** + * Get the name of the pivot accessor for the requested relationship. + * + * @param \Laravel\Nova\Http\Requests\NovaRequest $request + * @param \Laravel\Nova\Resource $resource + * @param \Laravel\Nova\Field $field + * @return string + */ + public function pivotAccessorFor(NovaRequest $request, $resource, $field) + { + return $resource->{$field->manyToManyRelationship}()->getPivotAccessor(); + } + + /** + * Remove non-update fields from the given collection. + * + * @param \Laravel\Nova\Http\Requests\NovaRequest $request + * @param \Laravel\Nova\Fields\FieldCollection $fields + * @param \Laravel\Nova\Resource $resource + * @return \Laravel\Nova\Fields\FieldCollection + */ + protected function removeNonUpdateFields(NovaRequest $request, FieldCollection $fields, $resource) + { + return $fields->reject(function($field) use ($request, $resource) { + return $this->isNonEditableField($request, $field) || + ! $field->isShownOnUpdate($request, $resource); + }); + } + + /** + * Remove non-creation fields from the given collection. + * + * @param \Laravel\Nova\Http\Requests\NovaRequest $request + * @param \Laravel\Nova\Fields\FieldCollection $fields + * @return \Laravel\Nova\Fields\FieldCollection + */ + protected function removeNonCreationFields(NovaRequest $request, FieldCollection $fields) + { + return $fields->reject(function($field) use ($request) { + return $this->isNonEditableField($request, $field) || + ! $field->isShownOnCreation($request); + }); + } + + /** + * Remove non-creation fields from the given collection. + * + * @param \Laravel\Nova\Http\Requests\NovaRequest $request + * @param \Laravel\Nova\Fields\FieldCollection $fields + * @return \Laravel\Nova\Fields\FieldCollection + */ + protected function removeNonDetailFields(NovaRequest $request, FieldCollection $fields, $resource) + { + return $fields->reject(function($field) use ($request, $resource) { + return ! $field->isShownOnDetail($request, $resource); + }); + } + + /** + * Detect if the field cannot be edit. + * + * @param \Laravel\Nova\Http\Requests\NovaRequest $request + * @param \Laravel\Nova\Fields\Field $field + * @return boolean + */ + protected function isNonEditableField(NovaRequest $request, $field) + { + $resource = $request->newResource(); + + return $field instanceof ListableField || + $field instanceof ResourceToolElement || + $field->attribute === 'ComputedField' || + ($field instanceof ID && $field->attribute === $resource->getKeyName()); + } +} diff --git a/src/MorphedByMany.php b/src/MorphedByMany.php new file mode 100644 index 0000000..6646c21 --- /dev/null +++ b/src/MorphedByMany.php @@ -0,0 +1,74 @@ +pivots(); + $this->fields(function() {}); + } + + /** + * Specify the callback to be executed to retrieve the pivot fields. + * + * @param callable $callback + * @return $this + */ + public function fields(callable $callback) + { + $this->fieldsCallback = function($request, $resource) use ($callback) { + $relation = $this->getMorphToMany($resource); + + return collect(call_user_func($callback, $request, $resource))->prepend( + Select::make(__("Resource"), $relation->getMorphType($resource)) + ->options([ + $relation->getMorphClass() => $resource::label(), + ]) + ->onlyOnForms() + ->required() + ->rules('required') + ->withMeta(['value' => $relation->getMorphClass()]) + )->all(); + }; + + return $this; + } + + /** + * Indicated the MorphToMany relationship. + * + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany + */ + protected function getMorphToMany($resource) + { + $model = $resource::newModel(); + + return $model->{$this->manyToManyRelationship}(); + } + + /** + * Set the pivots attachment status. + * + * @return string + */ + public function pivots(bool $pivots = true) + { + $this->pivots = true; + + return $this; + } +} diff --git a/src/RelatableAttachment.php b/src/RelatableAttachment.php new file mode 100644 index 0000000..570fc14 --- /dev/null +++ b/src/RelatableAttachment.php @@ -0,0 +1,30 @@ +request->resourceId + ? $this->request->findModelOrFail() : $this->request->model(); + + $parentResource = Nova::resourceForModel($parentModel); + + return (new $parentResource($parentModel))->authorizedToAttachAny( + $this->request, $model + ) || (new $parentResource($parentModel))->authorizedToAttach( + $this->request, $model + ); + } +} diff --git a/webpack.mix.js b/webpack.mix.js new file mode 100644 index 0000000..e847ed2 --- /dev/null +++ b/webpack.mix.js @@ -0,0 +1,6 @@ +let mix = require('laravel-mix') + +mix + .setPublicPath('dist') + .js('resources/js/field.js', 'js') + .sass('resources/sass/field.scss', 'css')