diff --git a/src/base/SlotDefinition.php b/src/base/SlotDefinition.php index ade1f90..7057c72 100644 --- a/src/base/SlotDefinition.php +++ b/src/base/SlotDefinition.php @@ -8,6 +8,8 @@ class SlotDefinition { + protected bool $collapsed = false; + public function __construct( protected ?Component $component = null, protected ?string $name = null, @@ -43,6 +45,18 @@ public function defaults(array $componentConfig): self return $this; } + public function collapsed(bool $collapsed=true): self + { + $this->collapsed = $collapsed; + + return $this; + } + + public function isCollapsed(): bool + { + return $this->collapsed; + } + public function allows(string $type): bool { if (! empty($this->whitelist)) { diff --git a/src/controllers/ComponentsController.php b/src/controllers/ComponentsController.php index 6e6a3de..437929f 100644 --- a/src/controllers/ComponentsController.php +++ b/src/controllers/ComponentsController.php @@ -105,4 +105,24 @@ public function actionMove() 'fieldHtml' => $data->getTargetElement()->getFieldHtml($data->getTargetField()), ]); } + + public function actionToggleDisclosure() + { + /** @var Component $component */ + $component = $this->request->getQueryParamObjectOrFail(Component::class); + $defns = $component->getType()->getSlotDefinitions(); + $defaultState = $defns->every(fn ($d) => $d->isCollapsed()) ? 'closed' : 'open'; + $state = $component->disclosure->state ?? $defaultState; + $newState = $state === 'open' ? 'closed' : 'open'; + + if ($newState === $defaultState) { + $component->disclosure->delete(); + } + else { + $component->disclosure->state = $newState; + $component->disclosure->save(); + } + + return $this->asSuccess('Saved'); + } } diff --git a/src/db/Table.php b/src/db/Table.php index b182da6..acb1d86 100644 --- a/src/db/Table.php +++ b/src/db/Table.php @@ -5,6 +5,6 @@ class Table { const COMPONENTS = '{{%keystone_components}}'; - const COMPONENT_DATA = '{{%keystone_component_data}}'; + const COMPONENT_DISCLOSURES = '{{%keystone_component_disclosure}}'; } diff --git a/src/fields/Keystone.php b/src/fields/Keystone.php index ce9a481..599c4c2 100644 --- a/src/fields/Keystone.php +++ b/src/fields/Keystone.php @@ -42,7 +42,7 @@ protected function inputHtml(mixed $value, ElementInterface $element = null): st return Craft::$app->getView()->renderTemplate('keystone/field', [ 'element' => $element, 'field' => $this, - 'component' => $this->getFragment($element), + 'component' => $this->getFragment($element)->withDisclosures(), 'getComponentTypes' => new GetComponentType, ]); } diff --git a/src/migrations/Install.php b/src/migrations/Install.php index fb4df24..d0f4d00 100644 --- a/src/migrations/Install.php +++ b/src/migrations/Install.php @@ -32,16 +32,29 @@ public function safeUp() 'uid' => $this->uid(), ]); + $this->createTable(Table::COMPONENT_DISCLOSURES, [ + 'id' => $this->primaryKey(), + 'userId' => $this->integer(), + 'componentId' => $this->integer(), + 'state' => $this->enum('state', ['open', 'closed']), + 'dateCreated' => $this->dateTime()->notNull(), + 'dateUpdated' => $this->dateTime()->notNull(), + 'uid' => $this->uid(), + ]); + $this->createIndex(null, Table::COMPONENTS, ['id', 'elementId']); $this->addForeignKey(null, Table::COMPONENTS, ['elementId'], \craft\db\Table::ELEMENTS, ['id'], 'CASCADE', null); $this->addForeignKey(null, Table::COMPONENTS, ['fieldId'], \craft\db\Table::FIELDS, ['id'], 'CASCADE', null); $this->addForeignKey(null, Table::COMPONENTS, ['dataId'], Table::COMPONENT_DATA, ['id'], 'CASCADE', null); + $this->addForeignKey(null, Table::COMPONENT_DISCLOSURES, ['userId'], \craft\db\Table::USERS, ['id'], 'CASCADE', null); + $this->addForeignKey(null, Table::COMPONENT_DISCLOSURES, ['componentId'], Table::COMPONENTS, ['id'], 'CASCADE', null); return true; } public function safeDown() { + $this->dropTableIfExists(Table::COMPONENT_DISCLOSURES); $this->dropTableIfExists(Table::COMPONENTS); $this->dropTableIfExists(Table::COMPONENT_DATA); diff --git a/src/models/Component.php b/src/models/Component.php index 3b70766..d9fb3f7 100644 --- a/src/models/Component.php +++ b/src/models/Component.php @@ -47,6 +47,8 @@ class Component extends ActiveRecord protected ?ComponentType $_type = null; + protected bool $withDisclosures = false; + public static function factory(): \markhuot\keystone\factories\Component { return new \markhuot\keystone\factories\Component; @@ -73,6 +75,18 @@ public function getData(): ActiveQuery return $this->hasOne(ComponentData::class, ['id' => 'dataId']); } + public function getDisclosure(): ActiveQuery + { + return $this->hasOne(ComponentDisclosure::class, ['componentId' => 'id']); + } + + public function withDisclosures(bool $withDisclosures=true): self + { + $this->withDisclosures = $withDisclosures; + + return $this; + } + /** * @return array */ @@ -124,6 +138,13 @@ public function __get($name) $value = $data; } + if ($name === 'disclosure' && $value === null) { + $this->populateRelation($name, $data = new ComponentDisclosure); + $data->userId = app()->getUser()->getIdentity()->id; + $data->componentId = $this->id; + $value = $data; + } + if ($name === 'data' && $value instanceof ComponentData) { $value->setNormalizer((new NormalizeFieldDataForComponent($this))->handle(...)); } @@ -286,20 +307,20 @@ public function getSlot(string $name = null): SlotCollection if ($this->slotted === null && $this->elementId && $this->fieldId) { $components = Component::find() - ->with('data') + ->with(array_filter(['data', $this->withDisclosures ? 'disclosure' : null])) ->where(['and', ['elementId' => $this->elementId], ['fieldId' => $this->fieldId], + new OrCondition(array_filter([ + ! $this->getChildPath() ? ['path' => null] : null, + ['like', 'path', $this->getChildPath().'%', false], + ])), // this is intentionally left out. We don't want to limit our query by slot name // because children of this component may not share the same name. We need to pull // all children out of the database and then the slot name filtering happens below // before being returned. // ['slot' => $name], - new OrCondition(array_filter([ - ! $this->getChildPath() ? ['path' => null] : null, - ['like', 'path', $this->getChildPath().'%', false], - ])), ]) ->orderBy('sortOrder') ->collect(); diff --git a/src/models/ComponentDisclosure.php b/src/models/ComponentDisclosure.php new file mode 100644 index 0000000..e76aa9f --- /dev/null +++ b/src/models/ComponentDisclosure.php @@ -0,0 +1,27 @@ +{% endexport %} {% if props.template %} {% set parts = props.template|split(':') %} {% if parts|length == 2 %} diff --git a/src/templates/field.twig b/src/templates/field.twig index a504121..43c28dc 100644 --- a/src/templates/field.twig +++ b/src/templates/field.twig @@ -15,7 +15,7 @@ {% endif %}