Skip to content

Commit

Permalink
Merge pull request #6969 from getkirby/v5/refactor/panel-php-part-2
Browse files Browse the repository at this point in the history
New Area and MenuItem classes
  • Loading branch information
bastianallgeier authored Feb 4, 2025
2 parents e6989e3 + 0f58771 commit f473fb8
Show file tree
Hide file tree
Showing 19 changed files with 1,589 additions and 498 deletions.
2 changes: 1 addition & 1 deletion config/areas/logout.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

return function ($kirby) {
return [
'icon' => 'user',
'icon' => 'logout',
'label' => I18n::translate('logout'),
'views' => [
'logout' => [
Expand Down
11 changes: 10 additions & 1 deletion panel/src/components/View/Inside.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
<template>
<k-panel class="k-panel-inside">
<k-panel-menu />
<k-panel-menu
v-bind="$panel.menu.props"
:hover="$panel.menu.hover"
:is-open="$panel.menu.isOpen"
:license="$panel.license"
:searches="$panel.searches"
@hover="$panel.menu.hover = $event"
@search="$panel.search()"
@toggle="$panel.menu.toggle()"
/>
<main class="k-panel-main">
<k-topbar :breadcrumb="$panel.view.breadcrumb" :view="$panel.view">
<!-- @slot Additional content for the Topbar -->
Expand Down
53 changes: 34 additions & 19 deletions panel/src/components/View/Menu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
<nav
class="k-panel-menu"
:aria-label="$t('menu')"
:data-hover="$panel.menu.hover"
@mouseenter="$panel.menu.hover = true"
@mouseleave="$panel.menu.hover = false"
:data-hover="hover"
@mouseenter="$emit('hover', true)"
@mouseleave="$emit('hover', false)"
>
<div class="k-panel-menu-body">
<!-- Search button -->
Expand All @@ -13,7 +13,7 @@
:text="$t('search')"
icon="search"
class="k-panel-menu-search k-panel-menu-button"
@click="$panel.search()"
@click="$emit('search')"
/>

<!-- Menus -->
Expand All @@ -23,13 +23,14 @@
:data-second-last="menuIndex === menus.length - 2"
class="k-panel-menu-buttons"
>
<k-button
v-for="entry in menu"
:key="entry.id"
v-bind="entry"
:title="entry.title ?? entry.text"
class="k-panel-menu-button"
/>
<template v-for="entry in menu">
<component
:is="entry.component"
:key="entry.key"
v-bind="entry.props"
class="k-panel-menu-button"
/>
</template>
</menu>

<menu v-if="activationButton">
Expand All @@ -40,17 +41,17 @@
theme="love"
variant="filled"
/>
<k-activation :status="$panel.license" />
<k-activation :status="license" />
</menu>
</div>

<!-- Collapse/expand toggle -->
<k-button
:icon="$panel.menu.isOpen ? 'angle-left' : 'angle-right'"
:title="$panel.menu.isOpen ? $t('collapse') : $t('expand')"
:icon="isOpen ? 'angle-left' : 'angle-right'"
:title="isOpen ? $t('collapse') : $t('expand')"
size="xs"
class="k-panel-menu-toggle"
@click="$panel.menu.toggle()"
@click="$emit('toggle')"
/>
</nav>
</template>
Expand All @@ -61,21 +62,35 @@
* @internal
*/
export default {
props: {
hover: Boolean,
isOpen: Boolean,
items: {
type: Array,
default: () => []
},
license: String,
searches: {
type: Object,
default: () => ({})
}
},
emits: ["search", "toggle"],
data() {
return {
over: false
};
},
computed: {
activationButton() {
if (this.$panel.license === "missing") {
if (this.license === "missing") {
return {
click: () => this.$dialog("registration"),
text: this.$t("activate")
};
}
if (this.$panel.license === "legacy") {
if (this.license === "legacy") {
return {
click: () => this.$dialog("license"),
text: this.$t("renew")
Expand All @@ -85,10 +100,10 @@ export default {
return false;
},
hasSearch() {
return this.$helper.object.length(this.$panel.searches) > 0;
return this.$helper.object.length(this.searches) > 0;
},
menus() {
return this.$helper.array.split(this.$panel.menu.entries, "-");
return this.$helper.array.split(this.items, "-");
}
}
};
Expand Down
6 changes: 3 additions & 3 deletions panel/src/panel/menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import State from "./state.js";

export const defaults = () => {
return {
entries: [],
props: {},
hover: false,
isOpen: false
};
Expand Down Expand Up @@ -100,8 +100,8 @@ export default (panel) => {
*
* @param {Array} entries
*/
set(entries) {
this.entries = entries;
set(menu) {
this.props = menu.props;
this.resize();
return this.state();
},
Expand Down
197 changes: 197 additions & 0 deletions src/Panel/Area.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
<?php

namespace Kirby\Panel;

use Closure;
use Kirby\Panel\Ui\MenuItem;
use Kirby\Toolkit\I18n;

/**
* @package Kirby Panel
* @author Nico Hoffmann <[email protected]>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.0.0
*/
class Area
{
public function __construct(
protected string $id,
protected array $breadcrumb = [],
protected Closure|array|string|null $breadcrumbLabel = null,
protected array $buttons = [],
protected Closure|bool|null $current = null,
protected string|null $dialog = null,
protected array $dialogs = [],
protected string|null $drawer = null,
protected array $drawers = [],
protected array $dropdowns = [],
protected string|null $icon = null,
protected Closure|array|string|null $label = null,
protected string|null $link = null,
protected Closure|array|bool|string $menu = false,
protected string|null $search = null,
protected array $searches = [],
protected array $requests = [],
protected Closure|array|string|null $title = null,
protected array $views = [],
) {
}

public function __call(string $name, array $args = [])
{
return $this->{$name};
}

/**
* A custom breadcrumb label that will be used for the
* breadcrumb instead of the default label
*/
public function breadcrumbLabel(): string
{
return $this->i18n($this->breadcrumbLabel ?? $this->label());
}

/**
* Translator for breadcrumbLabel, label & title
*/
protected function i18n(Closure|array|string|null $value): string|null
{
if ($value instanceof Closure) {
$value = $value();
}

return I18n::translate($value, $value);
}

/**
* Checks for access permissions for this area
*/
public function isAccessible(array $permissions): bool
{
return ($permissions['access'][$this->id] ?? true) === true;
}

/**
* Checks if the area is currently active
*/
public function isCurrent(string|null $current = null): bool
{
if ($this->current === null) {
return $this->id === $current;
}

if ($this->current instanceof Closure) {
return ($this->current)($current);
}

return $this->current;
}

/**
* The label is used for the menu item and the breadcrumb
* unless a custom breadcrumb label is defined
*/
public function label(): string
{
return $this->i18n($this->label ?? $this->id);
}

/**
* Link for the menu item
*/
public function link(): string
{
return $this->link ?? $this->id;
}

/**
* Set or overwrite additional props via array
*/
public function merge(array $props): static
{
foreach ($props as $key => $value) {
$this->{$key} = $value;
}

return $this;
}

/**
* Evaluate the menu settings to determine
* how the menu item for the area should be rendered
* and if it should be rendered at all
*/
public function menuItem(
array $areas = [],
array $permissions = [],
string|null $current = null
): MenuItem|null {
// areas without access permissions get skipped entirely
if ($this->isAccessible($permissions) === false) {
return null;
}

$menu = $this->menu;

// menu setting can be a callback
// that returns true, false or 'disabled'
if ($menu instanceof Closure) {
$menu = $menu($areas, $permissions, $current);
}

// false will remove the area/entry entirely
// just like with disabled permissions
if ($menu === false) {
return null;
}

// create a new menu item instance for the area
$item = new MenuItem(
current: $this->isCurrent($current),
icon: $this->icon() ?? $this->id(),
text: $this->label(),
dialog: $this->dialog(),
drawer: $this->drawer(),
link: $this->link(),
);

// add the custom menu settings
$item->merge(match ($menu) {
'disabled' => ['disabled' => true],
true => [],
default => $menu
});

return $item;
}

/**
* The title is used for the browser title. It will fall back
* to the label if no custom title is defined.
*/
public function title(): string
{
return $this->i18n($this->title ?? $this->label());
}

/**
* Returns parameters that will be added to the
* view response (one level above the props) to
* render the view component properly
*/
public function toView(): array
{
return [
'breadcrumb' => $this->breadcrumb(),
'breadcrumbLabel' => $this->breadcrumbLabel(),
'icon' => $this->icon(),
'id' => $this->id(),
'label' => $this->label(),
'link' => $this->link(),
'search' => $this->search(),
'title' => $this->title(),
];
}
}
Loading

0 comments on commit f473fb8

Please sign in to comment.