Skip to content

Commit

Permalink
disclosures persist to the database
Browse files Browse the repository at this point in the history
  • Loading branch information
markhuot committed Nov 29, 2023
1 parent 923fbec commit 203f9bc
Show file tree
Hide file tree
Showing 13 changed files with 119 additions and 16 deletions.
14 changes: 14 additions & 0 deletions src/base/SlotDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

class SlotDefinition
{
protected bool $collapsed = false;

public function __construct(
protected ?Component $component = null,
protected ?string $name = null,
Expand Down Expand Up @@ -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)) {
Expand Down
20 changes: 20 additions & 0 deletions src/controllers/ComponentsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
}
2 changes: 1 addition & 1 deletion src/db/Table.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
class Table
{
const COMPONENTS = '{{%keystone_components}}';

const COMPONENT_DATA = '{{%keystone_component_data}}';
const COMPONENT_DISCLOSURES = '{{%keystone_component_disclosure}}';
}
2 changes: 1 addition & 1 deletion src/fields/Keystone.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
]);
}
Expand Down
13 changes: 13 additions & 0 deletions src/migrations/Install.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
31 changes: 26 additions & 5 deletions src/models/Component.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<string>
*/
Expand Down Expand Up @@ -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(...));
}
Expand Down Expand Up @@ -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();
Expand Down
27 changes: 27 additions & 0 deletions src/models/ComponentDisclosure.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace markhuot\keystone\models;

use ArrayAccess;
use Closure;
use craft\helpers\DateTimeHelper;
use craft\helpers\Db;
use craft\helpers\StringHelper;
use Illuminate\Support\Collection;
use markhuot\keystone\base\Attribute;
use markhuot\keystone\base\FieldDefinition;
use markhuot\keystone\db\ActiveRecord;
use markhuot\keystone\db\Table;

use function markhuot\keystone\helpers\base\throw_if;
use function markhuot\keystone\helpers\data\data_forget;

/**
*/
class ComponentDisclosure extends ActiveRecord
{
public static function tableName()
{
return Table::COMPONENT_DISCLOSURES;
}
}
6 changes: 5 additions & 1 deletion src/resources/components/alpine.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ window.post = function (action, config={}) {
}
const headers = {
'X-CSRF-Token': Craft.csrfTokenValue,
'X-Craft-Namespace': form ? $(form).data('cpScreen').namespace : null,
'X-Craft-Namespace': form && $(form).data('cpScreen') ? $(form).data('cpScreen').namespace : null,
};
const response = await axios({
method: 'post',
Expand All @@ -19,6 +19,10 @@ window.post = function (action, config={}) {
data
});

if (response.data.message) {
Craft.cp.displayNotice(response.data.message);
}

for (const then of thens) {
then(response);
}
Expand Down
4 changes: 3 additions & 1 deletion src/templates/components/asset.twig
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
} %}
{% export summary = props.asset.one().title|default %}
{% export category = "Data" %}
{% set slot = component.getType.defineSlot().defaults([{type: 'keystone/template', data: {template: 'cp:keystone/asset/embed'}}]) %}
{% set slot = component.getType.defineSlot()
.defaults([{type: 'keystone/template', data: {template: 'cp:keystone/asset/embed'}}])
.collapsed() %}
{% for asset in props.asset %}
{% do component.mergeContext({asset: asset, transform: props.transform.value|default(null)}) %}
{{ slot }}
Expand Down
4 changes: 3 additions & 1 deletion src/templates/components/entry.twig
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
} %}
{% export summary = props.entry.one().title|default %}
{% export category = "Data" %}
{% set slot = component.getType().defineSlot().defaults([{type: 'keystone/template', data: {template: 'cp:keystone/entry/link'}}]) %}
{% set slot = component.getType().defineSlot()
.defaults([{type: 'keystone/template', data: {template: 'cp:keystone/entry/link'}}])
.collapsed() %}
{% for entry in props.entry %}
{% do component.mergeContext({entry: entry}) %}
{{ slot }}
Expand Down
2 changes: 1 addition & 1 deletion src/templates/components/entryquery.twig
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
search: field('\\markhuot\\keystone\\fields\\Condition'),
limit: field('\\craft\\fields\\Number'),
} %}
{% set defaultSlot = component.getType().defineSlot() %}
{% set defaultSlot = component.getType().defineSlot().collapsed() %}
{% set elements = props.search.all()|default([]) %}
{% for element in elements %}
{{ defaultSlot.render({entry: element}) }}
Expand Down
1 change: 1 addition & 0 deletions src/templates/components/template.twig
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{% export summary = props.template %}
{% export icon %}<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path d="M58.34,101.66l-32-32a8,8,0,0,1,0-11.32l32-32A8,8,0,0,1,69.66,37.66L43.31,64,69.66,90.34a8,8,0,0,1-11.32,11.32Zm40,0a8,8,0,0,0,11.32,0l32-32a8,8,0,0,0,0-11.32l-32-32A8,8,0,0,0,98.34,37.66L124.69,64,98.34,90.34A8,8,0,0,0,98.34,101.66ZM200,40H176a8,8,0,0,0,0,16h24V200H56V136a8,8,0,0,0-16,0v64a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V56A16,16,0,0,0,200,40Z"></path></svg>{% endexport %}
{% if props.template %}
{% set parts = props.template|split(':') %}
{% if parts|length == 2 %}
Expand Down
9 changes: 4 additions & 5 deletions src/templates/field.twig
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
{% endif %}
<ul style="margin-left: 2rem;">
{% for child in component.getSlot(slot.name) %}
<li class="k-relative"
<li class="k-relative {{ child.getType().getSlotDefinitions().first().isCollapsed()|default(false) and child.disclosure.state != 'open' ? 'k-collapsed' }}"
draggable="true"
data-draggable="{{ child.id }}"
data-draggable-type="{{ child.getType().getHandle() }}"
Expand All @@ -25,19 +25,18 @@
data-dragtarget-whitelist="{{ slot.whitelist|join(',') }}"
data-dragtarget-blacklist="{{ slot.blacklist|join(',') }}"
>
<a class="k-absolute baz [&:hover>div]:k-bg-blue-600 k-top-[1.3rem] k-left-0 k-bottom-[1rem] k-w-[24px] hover:k-z-10" href="#" @click="$event.target.closest('li').classList.toggle('k-collapsed')">
<a class="k-absolute baz [&:hover>div]:k-bg-blue-600 k-top-[1.3rem] k-left-0 k-bottom-[1rem] k-w-[24px] hover:k-z-10" href="#" @click="post('{{ actionUrl('keystone/components/toggle-disclosure', child.getQueryCondition()) }}').then(() => $event.target.closest('li').classList.toggle('k-collapsed'))">
<div class="k-w-[1px] k-bg-gray-300 k-absolute k-h-full k-left-1/2 -k-translate-x-1/2"></div>
</a>
<div class="k-flex k-justify-between" data-draggable-row>
<div class="k-relative">
<div class="foo k-hidden k-pointer-events-none" style="height:10px; width: 2rem; position: absolute; top: 0.4em; left: -20px; border-radius: 0 0 0 10px; border-width: 0 0 1px 1px; border-color: rgba(96,125,159,.25); box-shadow: -2px 2px 0 white;"></div>
{% set params = {id: child.id, elementId: child.elementId, fieldId: field.id} %}
<a class="k-text-link" href="{{ cpUrl('keystone/components/edit', params) }}" data-open-keystone-component-editor='{{ params|json_encode }}'>
<a class="k-text-link" href="{{ cpUrl('keystone/components/edit', child.getQueryCondition()) }}" data-open-keystone-component-editor='{{ child.getQueryCondition()|json_encode }}'>
<div style="background: white; display: inline-block; position: relative; padding: 4px;">{{ child.getType().getIcon({class: 'k-w-4 k-inline'})|raw }}</div>
{{ child.getType().getName() }}
<span class="k-text-gray-400">{{ child.getSummary() }}</span>
</a>
<a class="k-expander k-hidden k-ml-1 [.k-collapsed_&]:k-inline-flex k-bg-gray-200 k-rounded k-w-5 k-h-3 k-text-gray-800 k-justify-center k-items-center hover:k-no-underline hover:k-bg-blue-500 hover:k-text-white" href="#" @click="$event.target.closest('li').classList.toggle('k-collapsed')">⋯</a>
<a class="k-expander k-hidden k-ml-1 [.k-collapsed_&]:k-inline-flex k-bg-gray-200 k-rounded k-w-5 k-h-3 k-text-gray-800 k-justify-center k-items-center hover:k-no-underline hover:k-bg-blue-500 hover:k-text-white" href="#" @click="post('{{ actionUrl('keystone/components/toggle-disclosure', child.getQueryCondition()) }}').then(() => $event.target.closest('li').classList.toggle('k-collapsed'))">⋯</a>
{{ hiddenInput('nodes[]', child.id ~ '@' ~ child.dateUpdated) }}
</div>
</div>
Expand Down

0 comments on commit 203f9bc

Please sign in to comment.