From 7dea95554af46386453748c103bbf4bbe42751af Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Tue, 23 May 2023 15:24:21 -0700 Subject: [PATCH 1/8] Keep track of the last-selected source path Resolves #13147 --- src/base/Element.php | 8 ++ src/base/ElementInterface.php | 11 +++ src/controllers/ElementIndexesController.php | 18 ++++ src/elements/Asset.php | 27 ++++++ src/fields/Assets.php | 1 + src/models/VolumeFolder.php | 2 + src/web/assets/cp/dist/cp.js | 2 +- src/web/assets/cp/dist/cp.js.map | 2 +- src/web/assets/cp/src/js/BaseElementIndex.js | 94 +++++++++++++++++--- 9 files changed, 153 insertions(+), 12 deletions(-) diff --git a/src/base/Element.php b/src/base/Element.php index 9d5ebf1e6f5..104172fb182 100644 --- a/src/base/Element.php +++ b/src/base/Element.php @@ -684,6 +684,14 @@ public static function findSource(string $sourceKey, ?string $context = null): ? return null; } + /** + * @inheritdoc + */ + public static function sourcePath(string $sourceKey, string $stepKey, ?string $context): ?array + { + return null; + } + /** * @inheritdoc * @since 3.5.0 diff --git a/src/base/ElementInterface.php b/src/base/ElementInterface.php index 0f453217c84..f99113b6a95 100644 --- a/src/base/ElementInterface.php +++ b/src/base/ElementInterface.php @@ -293,6 +293,17 @@ public static function sources(string $context = null): array; */ public static function findSource(string $sourceKey, ?string $context = null): ?array; + /** + * Returns the source path for a given source key, step key, and context. + * + * @param string $sourceKey + * @param string $stepKey + * @param string|null $context + * @return array[]|null + * @since 3.8.12 + */ + public static function sourcePath(string $sourceKey, string $stepKey, ?string $context): ?array; + /** * Returns all of the field layouts associated with elements from the given source. * diff --git a/src/controllers/ElementIndexesController.php b/src/controllers/ElementIndexesController.php index df9524ffa08..ba19e1e3c90 100644 --- a/src/controllers/ElementIndexesController.php +++ b/src/controllers/ElementIndexesController.php @@ -128,6 +128,24 @@ public function getElementQuery(): ElementQueryInterface return $this->elementQuery; } + /** + * Returns the source path for the given source key, step key, and context. + * + * @since 3.8.12 + */ + public function actionSourcePath(): Response + { + /** @var string|ElementInterface $elementType */ + $elementType = $this->elementType; + $stepKey = $this->request->getRequiredBodyParam('stepKey'); + $sourcePath = $elementType::sourcePath($this->sourceKey, $stepKey, $this->context); + + return $this->asJson([ + 'sourcePath' => $sourcePath, + ]); + } + + /** * Renders and returns an element index container, plus its first batch of elements. * diff --git a/src/elements/Asset.php b/src/elements/Asset.php index b4d51aa8b02..7599022ddde 100644 --- a/src/elements/Asset.php +++ b/src/elements/Asset.php @@ -335,6 +335,33 @@ public static function findSource(string $sourceKey, ?string $context = null): ? return null; } + public static function sourcePath(string $sourceKey, string $stepKey, ?string $context): ?array + { + if (!preg_match('/^folder:(\d+)$/', $stepKey, $match)) { + return null; + } + + $folder = Craft::$app->getAssets()->getFolderById((int)$match[1]); + + if (!$folder) { + return null; + } + + $path = [$folder->getSourcePathInfo()]; + + while ($parent = $folder->getParent()) { + array_unshift($path, $parent->getSourcePathInfo()); + $folder = $parent; + } + + // Make sure the root folder lives in the same volume as $sourceKey + if ($sourceKey !== "folder:$folder->uid") { + return null; + } + + return $path; + } + /** * @inheritdoc * @since 3.5.0 diff --git a/src/fields/Assets.php b/src/fields/Assets.php index 82bbb26c5da..168e2202bd8 100644 --- a/src/fields/Assets.php +++ b/src/fields/Assets.php @@ -716,6 +716,7 @@ protected function inputTemplateVariables($value = null, ElementInterface $eleme $variables['defaultSourcePath'] = array_map(function(VolumeFolder $folder) { return $folder->getSourcePathInfo(); }, $folders); + $variables['preferStoredSource'] = true; } } diff --git a/src/models/VolumeFolder.php b/src/models/VolumeFolder.php index 32a65b2174c..74586590371 100644 --- a/src/models/VolumeFolder.php +++ b/src/models/VolumeFolder.php @@ -130,6 +130,7 @@ public function getSourcePathInfo(): ?array // Is this a root folder? if (!$this->parentId) { $info += [ + 'key' => "folder:$this->uid", 'icon' => 'home', 'label' => Craft::t('app', '{volume} root', [ 'volume' => Html::encode(Craft::t('site', $volume->name)), @@ -140,6 +141,7 @@ public function getSourcePathInfo(): ?array $canRename = $canCreate & $userSession->checkPermission("deleteFilesAndFoldersInVolume:$volume->uid"); $info += [ + 'key' => "folder:$this->id", 'label' => Html::encode($this->name), 'criteria' => [ 'folderId' => $this->id, diff --git a/src/web/assets/cp/dist/cp.js b/src/web/assets/cp/dist/cp.js index 14c4f27f166..0019c8644c5 100644 --- a/src/web/assets/cp/dist/cp.js +++ b/src/web/assets/cp/dist/cp.js @@ -1,2 +1,2 @@ -!function(){var t={7452:function(){Craft.AdminTable=Garnish.Base.extend({settings:null,totalItems:null,sorter:null,$noItems:null,$table:null,$tbody:null,$deleteBtns:null,init:function(t){this.setSettings(t,Craft.AdminTable.defaults),this.settings.allowDeleteAll||(this.settings.minItems=1),this.$noItems=$(this.settings.noItemsSelector),this.$table=$(this.settings.tableSelector),this.$tbody=this.$table.children("tbody"),this.totalItems=this.$tbody.children().length,this.settings.sortable&&(this.sorter=new Craft.DataTableSorter(this.$table,{onSortChange:this.reorderItems.bind(this)})),this.$deleteBtns=this.$table.find(".delete:not(.disabled)"),this.addListener(this.$deleteBtns,"click","handleDeleteBtnClick"),this.updateUI()},addRow:function(t){if(!(this.settings.maxItems&&this.totalItems>=this.settings.maxItems)){var e=$(t).appendTo(this.$tbody),i=e.find(".delete");this.settings.sortable&&this.sorter.addItems(e),this.$deleteBtns=this.$deleteBtns.add(i),this.addListener(i,"click","handleDeleteBtnClick"),this.totalItems++,this.updateUI()}},reorderItems:function(){var t=this;if(this.settings.sortable){for(var e=[],i=0;i=this.settings.maxItems?$(this.settings.newItemBtnSelector).addClass("hidden"):$(this.settings.newItemBtnSelector).removeClass("hidden"))}},{defaults:{tableSelector:null,noItemsSelector:null,newItemBtnSelector:null,idAttribute:"data-id",nameAttribute:"data-name",sortable:!1,allowDeleteAll:!0,minItems:0,maxItems:null,reorderAction:null,deleteAction:null,reorderSuccessMessage:Craft.t("app","New order saved."),reorderFailMessage:Craft.t("app","Couldn’t save new order."),confirmDeleteMessage:Craft.t("app","Are you sure you want to delete “{name}”?"),deleteSuccessMessage:Craft.t("app","“{name}” deleted."),deleteFailMessage:Craft.t("app","Couldn’t delete “{name}”."),onReorderItems:$.noop,onDeleteItem:$.noop}})},76:function(){Craft.AssetEditor=Craft.BaseElementEditor.extend({$filenameInput:null,originalBasename:null,originalExtension:null,reloadIndex:!1,init:function(t,e){var i=this;this.on("updateForm",(function(){i.addListener(i.$sidebar.find(".preview-thumb-container .edit-btn"),"click","showImageEditor"),i.addListener(i.$sidebar.find(".preview-thumb-container .preview-btn"),"click","showImagePreview"),i.$filenameInput=i.$sidebar.find(".filename"),i.addListener(i.$filenameInput,"focus","selectFilename")})),this.on("closeSlideout",(function(){i.reloadIndex&&(i.settings.elementIndex?i.settings.elementIndex.updateElements():i.settings.input&&i.settings.input.refreshThumbnail(i.$element.data("id")))})),this.base(t,e),this.settings.validators.push((function(){return i.validateExtension()}))},showImageEditor:function(){var t=this;new Craft.AssetImageEditor(this.$element.data("id"),{onSave:function(){t.reloadIndex=!0,t.load()}})},showImagePreview:function(){var t={};this.$element.data("image-width")&&(t.startingWidth=this.$element.data("image-width"),t.startingHeight=this.$element.data("image-height")),new Craft.PreviewFileModal(this.$element.data("id"),null,t)},selectFilename:function(){var t=this;if(void 0!==this.$filenameInput[0].selectionStart){var e=this._parseFilename(),i=e.basename,s=e.extension;null===this.originalBasename&&(this.originalBasename=i,this.originalExtension=s),this.$filenameInput[0].selectionStart=0,this.$filenameInput[0].selectionEnd=i.length,this.$filenameInput.one("mouseup.keepselection",(function(t){t.preventDefault()})),setTimeout((function(){t.$filenameInput.off("mouseup.keepselection")}),500)}},validateExtension:function(){if(null===this.originalBasename)return!0;var t=this._parseFilename(),e=t.basename,i=t.extension;return i===this.originalExtension||(i?confirm(Craft.t("app","Are you sure you want to change the extension from “.{oldExt}” to “.{newExt}”?",{oldExt:this.originalExtension,newExt:i})):this.originalFilename!==e?(this.$filenameInput.val("".concat(Craft.rtrim(e,"."),".").concat(this.originalExtension)),!0):confirm(Craft.t("app","Are you sure you want to remove the extension “.{ext}”?",{ext:this.originalExtension})))},_parseFilename:function(){var t=this.$filenameInput.val().split("."),e=t.length>1?t.pop():null;return{basename:t.join("."),extension:e}}}),Craft.registerElementEditorClass("craft\\elements\\Asset",Craft.AssetEditor)},6872:function(){Craft.AssetImageEditor=Garnish.Modal.extend({$body:null,$footer:null,$imageTools:null,$buttons:null,$cancelBtn:null,$replaceBtn:null,$saveBtn:null,$editorContainer:null,$straighten:null,$croppingCanvas:null,$spinnerCanvas:null,canvas:null,image:null,viewport:null,focalPoint:null,grid:null,croppingCanvas:null,clipper:null,croppingRectangle:null,cropperHandles:null,cropperGrid:null,croppingShade:null,imageStraightenAngle:0,viewportRotation:0,originalWidth:0,originalHeight:0,imageVerticeCoords:null,zoomRatio:1,animationInProgress:!1,currentView:"",assetId:null,cacheBust:null,draggingCropper:!1,scalingCropper:!1,draggingFocal:!1,previousMouseX:0,previousMouseY:0,shiftKeyHeld:!1,editorHeight:0,editorWidth:0,cropperState:!1,scaleFactor:1,flipData:{},focalPointState:!1,spinnerInterval:null,maxImageSize:null,lastLoadedDimensions:null,imageIsLoading:!1,mouseMoveEvent:null,croppingConstraint:!1,constraintOrientation:"landscape",showingCustomConstraint:!1,renderImage:null,renderCropper:null,init:function(t,e){this.cacheBust=Date.now(),this.setSettings(e,Craft.AssetImageEditor.defaults),null===this.settings.allowDegreeFractions&&(this.settings.allowDegreeFractions=Craft.isImagick),this.assetId=t,this.flipData={x:0,y:0},this.$container=$('').appendTo(Garnish.$bod),this.$body=$('
').appendTo(this.$container),this.$footer=$('