Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## v1.3.0 (2025-11-07)

- Added error view mode to display issues (like missing config file) with distinct red/orange styling
- Added optional Font Awesome auto-include from CDN via bundle configuration

## v1.2.0 (2025-11-07)

- Added `icon_type` parameter for widgets: `fa` (Font Awesome, default) or `text` (for emoji/plain text)
Expand Down
79 changes: 71 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,80 @@ widgets:

### Widget Properties

| Property | Type | Required | Description |
|----------|:--------:|:--------:|--------------------------------------------------------------------------------|
| `icon`* | `string` | | Optional Font Awesome icon class (e.g., `fa-bug`). |
| `text`* | `string` | | Optional widget label to display (e.g., `Mailpit`). |
| `url` | `string` | required | Link URL to redirect to once widget is clicked. |
| `target` | `string` | | Link target (e.g., `_blank`). Default: no target |
| `title` | `string` | | Tooltip text. If not given, `url` is shown. |
| `expand` | `bool` | | Set to `true` to make widget expand and fill available space. Default `false`. |
| Property | Type | Required | Description |
|-------------|:--------:|:--------:|--------------------------------------------------------------------------------|
| `icon`* | `string` | | Optional icon to display. Can be Font Awesome class or emoji/text. |
| `icon_type` | `string` | | Icon type: `fa` (Font Awesome, default) or `text` (emoji/plain text). |
| `text`* | `string` | | Optional widget label to display alongside icon. |
| `url` | `string` | required | Link URL to redirect to once widget is clicked. |
| `target` | `string` | | Link target (e.g., `_blank`). Default: no target |
| `title` | `string` | | Tooltip text. If not given, `url` is shown. |
| `expand` | `bool` | | Set to `true` to make widget expand and fill available space. Default `false`.|

*) Either `icon` or `text` must be provided or exception will be thrown.

### Font Awesome Icons

DiscoDevBar supports Font Awesome icons for widgets. You have two options for including Font Awesome:

#### Option 1: Automatic Inclusion (Recommended for Quick Setup)

Enable automatic Font Awesome inclusion from CDN in your `.disco-devbar.yaml` configuration file:

```yaml
font_awesome:
enabled: true # Enable auto-include from CDN (default: false)
version: '6.5.1' # Font Awesome version to use (optional, default: 6.5.1)

widgets:
left:
- icon: "fa-flag-checkered"
text: "1.0"
url: "https://github.com/user/repo"
```

**Benefits:**

- Works out of the box - no additional setup needed
- Icons display immediately
- Configuration kept in one place with your widgets

**Note:** Only enable this if your application doesn't already include Font Awesome. If you have Font Awesome in
your project, use Option 2 instead to avoid version conflicts.

#### Option 2: Manual Setup (Recommended if Font Awesome Already Installed)

If your application already includes Font Awesome (via NPM, CDN, or other means), simply use Font Awesome icon
classes in your widget configuration. DiscoDevBar will use your existing Font Awesome installation.

**Example:**

```yaml
widgets:
left:
- icon: "fa-database"
icon_type: "fa" # Use Font Awesome (default)
url: "http://localhost:8080"
```

If you don't have Font Awesome installed, you can include it manually in your base template:

```twig
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" />
```

#### Using Text/Emoji Instead

If you prefer not to use Font Awesome, you can use emoji or plain text:

```yaml
widgets:
left:
- icon: "🚀"
icon_type: "text" # Use plain text/emoji
url: "/admin"
```

## Usage

Include the devbar template in your base layout:
Expand Down
18 changes: 18 additions & 0 deletions Resources/public/devbar.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@
font-size: 15px;
}

/* DiscoDevBar error state - more red/orange to indicate problem */
.disco-devbar-error {
background: repeating-linear-gradient(
45deg,
#d32f2f,
#d32f2f 10px,
#ff5722 10px,
#ff5722 20px
);
}

.disco-devbar-content {
max-width: 1100px;
margin: 0 auto;
Expand Down Expand Up @@ -117,6 +128,13 @@
margin-right: 4px;
}

/* Plain text in devbar (non-link) */
.disco-devbar-text {
color: white;
font-weight: normal;
padding: 6px 8px;
}

/* Adjust top navigation when DiscoDevBar is present (public pages only) */
body:has(.disco-devbar):not(:has(.wrapper)) .top-nav {
top: 40px;
Expand Down
114 changes: 67 additions & 47 deletions Resources/views/devbar.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -2,56 +2,76 @@
{# This template displays DiscoDevBar with links to various tools and information #}
{# Show with `{% if app.environment == 'dev' %}` condition #}

<div class="disco-devbar">
{% set banner_data = debug_banner_data() %}

{# Auto-include Font Awesome if enabled in configuration #}
{% if banner_data.fontAwesomeEnabled %}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/{{ banner_data.fontAwesomeVersion }}/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
{% endif %}

<div class="disco-devbar{% if banner_data.hasError %} disco-devbar-error{% endif %}">
<div class="disco-devbar-content">
{% set banner_data = debug_banner_data() %}
<div class="disco-devbar-container-left {% if banner_data.leftExpand %}disco-devbar-container-expand{% endif %}">
{% for widget in banner_data.left %}
{% if not loop.first %}
<span class="disco-devbar-separator">&middot;</span>
{% endif %}
<span class="disco-devbar-widget {% if widget.expand %}disco-devbar-widget-expand{% endif %}">
<a href="{{ widget.url }}"
class="disco-devbar-link"
{% if widget.target %}target="{{ widget.target }}"{% endif %}
{% if widget.title %}title="{{ widget.title }}"{% endif %}>
<span class="disco-devbar-link-content">
{% if widget.icon %}
{% if widget.iconType.value == 'fa' %}
<i class="fa-solid {{ widget.icon }}"></i>
{% else %}
{{ widget.icon }}
{% endif %}
{% endif %}
{% if widget.text %}{{ widget.text }}{% endif %}
</span>
{% if banner_data.hasError %}
<div class="disco-devbar-container-left">
<span class="disco-devbar-widget">
<a href="https://github.com/MarcinOrlowski/php-symfony-discodevbar" class="disco-devbar-link" target="_blank" title="Visit DiscoDevBar on GitHub">
<span class="disco-devbar-link-content">Disco DevBar</span>
</a>
</span>
{% endfor %}
</div>
<div class="disco-devbar-container-right {% if banner_data.rightExpand %}disco-devbar-container-expand{% endif %}">
{% for widget in banner_data.right %}
{% if not loop.first %}
<span class="disco-devbar-separator">&middot;</span>
{% endif %}
<span class="disco-devbar-widget {% if widget.expand %}disco-devbar-widget-expand{% endif %}">
<a href="{{ widget.url }}"
class="disco-devbar-link"
{% if widget.target %}target="{{ widget.target }}"{% endif %}
{% if widget.title %}title="{{ widget.title }}"{% endif %}>
<span class="disco-devbar-link-content">
{% if widget.icon %}
{% if widget.iconType.value == 'fa' %}
<i class="fa-solid {{ widget.icon }}"></i>
{% else %}
{{ widget.icon }}
{% endif %}
{% endif %}
{% if widget.text %}{{ widget.text }}{% endif %}
</span>
</a>
<span class="disco-devbar-separator">&middot;</span>
<span class="disco-devbar-widget">
<span class="disco-devbar-text">v{{ banner_data.version ?? 'N/A' }} * {{ banner_data.errorMessage }}</span>
</span>
{% endfor %}
</div>
</div>
{% else %}
<div class="disco-devbar-container-left {% if banner_data.leftExpand %}disco-devbar-container-expand{% endif %}">
{% for widget in banner_data.left %}
{% if not loop.first %}
<span class="disco-devbar-separator">&middot;</span>
{% endif %}
<span class="disco-devbar-widget {% if widget.expand %}disco-devbar-widget-expand{% endif %}">
<a href="{{ widget.url }}"
class="disco-devbar-link"
{% if widget.target %}target="{{ widget.target }}"{% endif %}
{% if widget.title %}title="{{ widget.title }}"{% endif %}>
<span class="disco-devbar-link-content">
{% if widget.icon %}
{% if widget.iconType.value == 'fa' %}
<i class="fa-solid {{ widget.icon }}"></i>
{% else %}
{{ widget.icon }}
{% endif %}
{% endif %}
{% if widget.text %}{{ widget.text }}{% endif %}
</span>
</a>
</span>
{% endfor %}
</div>
<div class="disco-devbar-container-right {% if banner_data.rightExpand %}disco-devbar-container-expand{% endif %}">
{% for widget in banner_data.right %}
{% if not loop.first %}
<span class="disco-devbar-separator">&middot;</span>
{% endif %}
<span class="disco-devbar-widget {% if widget.expand %}disco-devbar-widget-expand{% endif %}">
<a href="{{ widget.url }}"
class="disco-devbar-link"
{% if widget.target %}target="{{ widget.target }}"{% endif %}
{% if widget.title %}title="{{ widget.title }}"{% endif %}>
<span class="disco-devbar-link-content">
{% if widget.icon %}
{% if widget.iconType.value == 'fa' %}
<i class="fa-solid {{ widget.icon }}"></i>
{% else %}
{{ widget.icon }}
{% endif %}
{% endif %}
{% if widget.text %}{{ widget.text }}{% endif %}
</span>
</a>
</span>
{% endfor %}
</div>
{% endif %}
</div>
</div>
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "marcin-orlowski/symfony-discodevbar",
"description": "Development toolbar/banner for Symfony project",
"type": "symfony-bundle",
"version": "1.2.0",
"version": "1.3.0",
"license": "MIT",
"keywords": [
"symfony",
Expand Down
16 changes: 13 additions & 3 deletions src/Dto/DiscoDevBarData.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,24 @@ class DiscoDevBarData
/**
* @param array<Widget> $left
* @param array<Widget> $right
* @param bool $leftExpand Whether left container should expand
* @param bool $rightExpand Whether right container should expand
* @param bool $leftExpand Whether left container should expand
* @param bool $rightExpand Whether right container should expand
* @param bool $hasError Whether to show error/alternative view
* @param string $errorMessage Error message to display (when hasError is true)
* @param string|null $version Bundle version (null shows as N/A)
* @param bool $fontAwesomeEnabled Whether to auto-include Font Awesome
* @param string $fontAwesomeVersion Font Awesome version to use
*/
public function __construct(
public readonly array $left,
public readonly array $right,
public readonly bool $leftExpand,
public readonly bool $rightExpand
public readonly bool $rightExpand,
public readonly bool $hasError = false,
public readonly string $errorMessage = '',
public readonly ?string $version = null,
public readonly bool $fontAwesomeEnabled = false,
public readonly string $fontAwesomeVersion = '6.5.1'
) {
}
}
63 changes: 54 additions & 9 deletions src/Service/DiscoDevBarService.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

namespace MarcinOrlowski\DiscoDevBar\Service;

use Composer\InstalledVersions;
use MarcinOrlowski\DiscoDevBar\Dto\DiscoDevBarData;
use MarcinOrlowski\DiscoDevBar\Dto\Widget;
use Symfony\Component\Yaml\Yaml;
Expand All @@ -35,6 +36,11 @@ class DiscoDevBarService
'.debug-banner.yaml', // Legacy, kept for backward compatibility
];

/**
* Default Font Awesome version
*/
private const DEFAULT_FONT_AWESOME_VERSION = '6.5.1';

public function __construct(
private readonly string $projectDir
) {
Expand All @@ -43,14 +49,21 @@ public function __construct(
public function getDiscoDevBarData(): DiscoDevBarData
{
$configPath = $this->findConfigFile();
$version = $this->getVersion();

// Default: empty widgets
// Default: show error message when no config found
if ($configPath === null) {
$errorMessage = 'Config file not found: ' . self::CONFIG_FILES[0];
return new DiscoDevBarData(
left: [],
right: [],
leftExpand: false,
rightExpand: false
left: [],
right: [],
leftExpand: false,
rightExpand: false,
hasError: true,
errorMessage: $errorMessage,
version: $version,
fontAwesomeEnabled: false,
fontAwesomeVersion: self::DEFAULT_FONT_AWESOME_VERSION
);
}

Expand All @@ -61,6 +74,20 @@ public function getDiscoDevBarData(): DiscoDevBarData
$config = [];
}

// Extract Font Awesome configuration from YAML
$fontAwesomeConfig = $config['font_awesome'] ?? [];
$fontAwesomeEnabled = false;
$fontAwesomeVersion = self::DEFAULT_FONT_AWESOME_VERSION;

if (\is_array($fontAwesomeConfig)) {
$fontAwesomeEnabled = $fontAwesomeConfig['enabled'] ?? false;
// Use user's version if provided and not null, otherwise use default
$userVersion = $fontAwesomeConfig['version'] ?? null;
if ($userVersion !== null && \is_string($userVersion)) {
$fontAwesomeVersion = $userVersion;
}
}

$widgets = $config['widgets'] ?? [];
if (!\is_array($widgets)) {
$widgets = [];
Expand All @@ -73,10 +100,15 @@ public function getDiscoDevBarData(): DiscoDevBarData
$rightWidgets = $this->loadWidgets(\is_array($right) ? $right : []);

return new DiscoDevBarData(
left: $leftWidgets,
right: $rightWidgets,
leftExpand: $this->hasExpandingWidget($leftWidgets),
rightExpand: $this->hasExpandingWidget($rightWidgets)
left: $leftWidgets,
right: $rightWidgets,
leftExpand: $this->hasExpandingWidget($leftWidgets),
rightExpand: $this->hasExpandingWidget($rightWidgets),
hasError: false,
errorMessage: '',
version: $version,
fontAwesomeEnabled: \is_bool($fontAwesomeEnabled) ? $fontAwesomeEnabled : false,
fontAwesomeVersion: $fontAwesomeVersion
);
}

Expand Down Expand Up @@ -131,4 +163,17 @@ function ($widgetData): Widget {
$widgetsData
);
}

/**
* Get bundle version from Composer
*/
private function getVersion(): string
{
try {
$version = InstalledVersions::getVersion('marcin-orlowski/symfony-discodevbar');
return $version ?? 'dev';
} catch (\Exception $e) {
return 'dev';
}
}
}