Skip to content

Commit c6ca64a

Browse files
committed
feat: Implement grouped collage layout selection
Key changes include: - **new CollageLayoutScanner:** new scanner to dynamically scan all available json-designs. - **Nested Grouping:** Collage layouts grouped into two levels (e.g., "Standard Layouts" > "Portrait Layouts", "Custom Layouts" > "Community Layouts") for better organization and clarity. - **Dynamic Directory Creation:** The scanner automatically creates missing base (`private/collage`) and subdirectories (`private/collage/portrait`, `private/collage/community`) if they don't exist. - **Robust Layout Naming:** Each layout is guaranteed to have a display name, using the `name` field from its JSON configuration or falling back to the filename (`layoutId`) if `name` is missing. - **changes Collage template:** added `name` field
1 parent 1c50da2 commit c6ca64a

File tree

4 files changed

+222
-36
lines changed

4 files changed

+222
-36
lines changed

admin/collage-designer/components/design-selector.php

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,53 @@
11
<?php
2-
// admin/collage-designer/components/design-selector.php
32

43
use Photobooth\Utility\AdminInput;
54
use Photobooth\Service\LanguageService;
5+
use Photobooth\Enum\CollageLayoutEnum;
6+
use Photobooth\Utility\CollageLayoutScanner;
67

78
$languageService = LanguageService::getInstance();
89

9-
// --- PLACEHOLDER: Fetching data for the Collage Designer ---
10-
// This will be filled with actual logic later.
11-
// For now, we simulate empty values or a few mock options.
1210
$currentDesign = '';
11+
12+
$designes = CollageLayoutScanner::scanLayouts();
13+
1314
$optionsHtml = '
14-
<option value="">
15-
' . $languageService->translate('collage_choose_new_design') . '
16-
</option>
17-
<option value="mock_design_1">Mock Design 1</option>
18-
<option value="mock_design_2" selected>Mock Design 2</option> <!-- Example: one option pre-selected -->
19-
';
15+
<option value="">
16+
' . $languageService->translate('collage_choose_new_design') . '
17+
</option>';
18+
19+
foreach ($designes as $mainGroupTitle => $subGroups) { // Iteriere über Hauptgruppen (z.B. "Standard Layouts", "Custom Layouts")
20+
$optionsHtml .= '<optgroup label="' . htmlspecialchars($mainGroupTitle, ENT_QUOTES) . '">';
21+
22+
// Sortiere die Untergruppen nach ihren übersetzten Titeln, um eine konsistente Reihenfolge zu gewährleisten
23+
// Hier ist eine einfache Sortierung nach Key (dem übersetzten Namen)
24+
ksort($subGroups);
25+
26+
foreach ($subGroups as $subGroupTitle => $layouts) { // Iteriere über Untergruppen (z.B. "Portrait Layouts", "Community Layouts")
27+
// Füge eine (deaktivierte) Option als Überschrift für die Untergruppe hinzu
28+
if (!empty($subGroupTitle)) {
29+
$optionsHtml .= '<option disabled>' . str_repeat('&nbsp;', 4) . '--- ' . htmlspecialchars($subGroupTitle, ENT_QUOTES) . ' ---</option>';
30+
}
31+
32+
// Sortiere die Layouts innerhalb der Untergruppe nach ihrem Namen
33+
uasort($layouts, function($a, $b) {
34+
return strcmp($a['name'] ?? $a['id'], $b['name'] ?? $b['id']);
35+
});
36+
37+
foreach ($layouts as $layoutId => $layoutData) { // Jetzt sind hier die tatsächlichen Layout-Daten
38+
$selected = ($layoutId === $currentDesign) ? ' selected="selected"' : '';
39+
40+
// Verwende den Null-Coalescing-Operator für "name", um Deprecated-Warnungen zu vermeiden
41+
// und den "id" als Fallback, falls "name" fehlen sollte (was der Scanner bereits beheben sollte)
42+
$displayName = htmlspecialchars($layoutData['name'] ?? $layoutData['id'] ?? '', ENT_QUOTES);
43+
44+
$optionsHtml .= '<option value="' . htmlspecialchars($layoutId, ENT_QUOTES) . '"' . $selected . '>';
45+
$optionsHtml .= str_repeat('&nbsp;', 8); // Zusätzliche Einrückung für Layout-Elemente
46+
$optionsHtml .= $displayName . '</option>';
47+
}
48+
}
49+
$optionsHtml .= '</optgroup>';
50+
}
2051

2152
// --- Preparing the $configManagerSetting array (structure only, with real values later) ---
2253
$configManagerSetting = [
@@ -31,17 +62,14 @@
3162
'save_btn_id' => 'collage-save-btn',
3263
'save_btn_title_label_key' => 'collage_save', // Language key for the title
3364
'save_btn_onclick' => 'adminCollageSave();', // JavaScript function for saving (to be implemented later)
34-
'save_btn_icon_class' => 'fa fa-save',
3565

3666
'load_btn_id' => 'collage-load-btn',
3767
'load_btn_title_label_key' => 'collage_load',
3868
'load_btn_onclick' => 'adminCollageLoad();', // JavaScript function for loading (to be implemented later)
39-
'load_btn_icon_class' => 'fa fa-download',
4069

4170
'delete_btn_id' => 'collage-delete-btn',
4271
'delete_btn_title_label_key' => 'collage_delete',
4372
'delete_btn_onclick' => 'adminCollageDelete();', // JavaScript function for deleting (to be implemented later)
44-
'delete_btn_icon_class' => 'fa fa-trash',
4573
];
4674
?>
4775

resources/lang/en.json

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -66,27 +66,6 @@
6666
"collage:collage_placeholderpath": "Placeholder image path",
6767
"collage:collage_placeholderposition": "Collage image number to replace",
6868
"collage:collage_take_frame": "Take collage with frame",
69-
"collage:generator:add_image": "Add image",
70-
"collage:generator:configuration_saved": "Configuration saved",
71-
"collage:generator:configuration_saving_error": "Error during configuration saving",
72-
"collage:generator:final_height": "Collage height",
73-
"collage:generator:final_width": "Collage width",
74-
"collage:generator:general_settings": "General settings",
75-
"collage:generator:image_height": "Image height",
76-
"collage:generator:image_rotation": "Image rotation",
77-
"collage:generator:image_width": "Image width",
78-
"collage:generator:load_current_configuration": "Load current configuration",
79-
"collage:generator:placeholder_settings": "Placeholder settings",
80-
"collage:generator:please_enable_write": "In order to save the collage.json file automatically you need to enable the write on that file.",
81-
"collage:generator:portrait": "Portrait",
82-
"collage:generator:rotate_after_creation": "Rotate after creation",
83-
"collage:generator:save_config_manually": "The json is saved but please go to the admin panel and save the configuration",
84-
"collage:generator:show_background": "Show background",
85-
"collage:generator:show_frame": "Show frame",
86-
"collage:generator:show_single_frame": "Toggle frame",
87-
"collage:generator:text_settings": "Text settings",
88-
"collage:generator:x_position": "X position",
89-
"collage:generator:y_position": "Y position",
9069
"collage:layout_generator": "Collage layout generator",
9170
"collage:textoncollage_enabled": "Text on collage",
9271
"collage:textoncollage_font": "Font",
@@ -1034,5 +1013,16 @@
10341013
"video:video_enabled": "Allow taking short videos",
10351014
"video:video_gif": "Video as GIF",
10361015
"video:video_qr": "Show video QR code",
1037-
"wait_message": "Please wait..."
1016+
"wait_message": "Please wait...",
1017+
1018+
"standard_layouts": "Standard Layouts",
1019+
"custom_layouts": "Custom Layouts",
1020+
"portrait": "Portrait Layouts",
1021+
"landscape": "Landscape Layouts",
1022+
"square": "Square Layouts",
1023+
"community_layouts": "Community Layouts",
1024+
"collage_choose_new_design": "Choose a new design",
1025+
"collage_name_placeholder": "Design Name",
1026+
"manage_collage_designs": "Manage Collage Designs",
1027+
"load": "Load"
10381028
}

src/Utility/AdminInput.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,8 @@ public static function renderTheme(array $setting, string $label): string
500500
return self::renderConfigManager($configManagerSetting);
501501
}
502502

503-
/* Renders a generic configuration management UI component.
503+
/**
504+
* Renders a generic configuration management UI component.
504505
* This includes a dropdown for selection, an input for naming, and buttons for save, load, and delete.
505506
* The structure and button styling are derived from the theme management UI.
506507
*
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
<?php
2+
3+
namespace Photobooth\Utility;
4+
5+
use Photobooth\Utility\PathUtility;
6+
use Photobooth\Service\LanguageService;
7+
8+
class CollageLayoutScanner
9+
{
10+
/**
11+
* Scans predefined directories for collage layout JSON files and groups them.
12+
*
13+
* @return array An associative array of grouped collage layouts.
14+
* Example: ['Standard-Layouts' => ['Portrait-Layouts' => [...]], 'Eigene Layouts' => ['Community-Layouts' => [...]]]
15+
*/
16+
public static function scanLayouts(): array
17+
{
18+
$layoutFiles = [];
19+
20+
// Define the main base directories for grouping (e.g., 'template', 'private')
21+
// Use simple keys ('template', 'private') for logical grouping, map to actual paths.
22+
$mainBaseDirs = [
23+
'template' => 'template/collage', // Standard layouts path
24+
'private' => 'private/collage', // User-defined/community layouts path
25+
];
26+
27+
foreach ($mainBaseDirs as $mainGroupKey => $baseDirRelativePath) {
28+
$absoluteBaseDir = PathUtility::getAbsolutePath($baseDirRelativePath);
29+
30+
// Initialize the main group key in $layoutFiles early
31+
$layoutFiles[$mainGroupKey] = [];
32+
33+
// Ensure the base directory exists, create if it's a 'private' one and missing
34+
if (!is_dir($absoluteBaseDir)) {
35+
if ($mainGroupKey === 'private') {
36+
try {
37+
mkdir($absoluteBaseDir, 0777, true);
38+
} catch (\Exception $e) {
39+
error_log('CollageLayoutScanner: Failed to create base directory: ' . $absoluteBaseDir . ' - ' . $e->getMessage());
40+
continue;
41+
}
42+
} else {
43+
continue; // Skip if 'template' base dir doesn't exist (expected to be present)
44+
}
45+
}
46+
47+
// --- Scan subdirectories for specific groups (e.g., 'portrait', 'landscape', 'community') ---
48+
$subDirNames = ['portrait', 'landscape', 'community']; // Extend as needed
49+
50+
foreach ($subDirNames as $subGroupName) {
51+
$subDirPath = $absoluteBaseDir . DIRECTORY_SEPARATOR . $subGroupName;
52+
53+
// Ensure the subdirectory exists, create if it's in a 'private' context and missing
54+
if (!is_dir($subDirPath)) {
55+
if ($mainGroupKey === 'private') {
56+
try {
57+
mkdir($subDirPath, 0777, true);
58+
} catch (\Exception $e) {
59+
error_log('CollageLayoutScanner: Failed to create subdirectory: ' . $subDirPath . ' - ' . $e->getMessage());
60+
continue;
61+
}
62+
} else {
63+
continue; // Skip if 'template' subdir doesn't exist (expected to be present)
64+
}
65+
}
66+
67+
// If directory exists (or was created), scan it
68+
// Pass the mainGroupKey AND the subGroupName to build the nested structure
69+
self::scanDirectory($subDirPath, $layoutFiles[$mainGroupKey], $subGroupName);
70+
}
71+
}
72+
73+
return self::groupAndTranslateLayouts($layoutFiles);
74+
}
75+
76+
/**
77+
* Scans a given directory for JSON files and extracts relevant layout data.
78+
*
79+
* @param string $directory The absolute path to the directory.
80+
* @param array $layoutFiles Reference to the array to store found layouts for the current main group.
81+
* @param string $subGroupKey The key for the subgroup (e.g., 'landscape', 'community', 'square').
82+
*/
83+
private static function scanDirectory(string $directory, array &$layoutFiles, string $subGroupKey): void
84+
{
85+
$files = glob($directory . DIRECTORY_SEPARATOR . '*.json');
86+
foreach ($files as $filePath) {
87+
$fileContent = file_get_contents($filePath);
88+
if ($fileContent === false) {
89+
error_log('CollageLayoutScanner: Could not read file: ' . $filePath);
90+
continue;
91+
}
92+
93+
$layoutConfig = json_decode($fileContent, true);
94+
if (json_last_error() !== JSON_ERROR_NONE || !is_array($layoutConfig)) {
95+
error_log('CollageLayoutScanner: Malformed JSON in file: ' . $filePath);
96+
continue;
97+
}
98+
99+
$layoutId = basename($filePath, '.json');
100+
101+
$layoutName = $layoutConfig['name'] ?? $layoutId;
102+
103+
// Group by the provided $subGroupKey within the main group
104+
// $layoutFiles is passed by reference and already represents $layoutFiles[$mainGroupKey] from scanLayouts
105+
$layoutFiles[$subGroupKey][$layoutId] = [
106+
'id' => $layoutId,
107+
'name' => $layoutName,
108+
'description' => $layoutConfig['description'] ?? '',
109+
'file_path' => $filePath,
110+
'author' => $layoutConfig['author'] ?? 'Unknown',
111+
'aspect_ratio' => $layoutConfig['aspect_ratio'] ?? '',
112+
'width' => $layoutConfig['width'] ?? '',
113+
'height' => $layoutConfig['height'] ?? '',
114+
];
115+
}
116+
}
117+
118+
/**
119+
* Groups and translates the found layouts for display without explicit sorting.
120+
*
121+
* @param array $rawLayoutFiles The raw array of found layouts, grouped by main group and subgroup key.
122+
* @return array The grouped layouts, with translated group titles.
123+
*/
124+
private static function groupAndTranslateLayouts(array $rawLayoutFiles): array
125+
{
126+
$groupedLayouts = [];
127+
$languageService = LanguageService::getInstance();
128+
129+
// Define a desired order and translation keys for the main groups (template, private)
130+
$mainGroupTranslationKeys = [
131+
'template' => 'standard_layouts', // e.g., "Standard Layouts"
132+
'private' => 'custom_layouts', // e.g., "Eigene Layouts"
133+
];
134+
135+
// Define a desired order and translation keys for the subgroups (portrait, landscape, community)
136+
$subGroupTranslationKeys = [
137+
'portrait' => 'portrait',
138+
'landscape' => 'landscape',
139+
'community' => 'community_layouts',
140+
// Add other subdir names here
141+
];
142+
143+
foreach ($mainGroupTranslationKeys as $mainGroupKey => $mainTransKey) {
144+
$translatedMainGroupTitle = $languageService->translate($mainTransKey);
145+
$groupedLayouts[$translatedMainGroupTitle] = []; // Initialize main group
146+
147+
if (isset($rawLayoutFiles[$mainGroupKey])) {
148+
foreach ($subGroupTranslationKeys as $subGroupKey => $subTransKey) {
149+
if (isset($rawLayoutFiles[$mainGroupKey][$subGroupKey])) {
150+
$translatedSubGroupTitle = $languageService->translate($subTransKey);
151+
// Add directly, no sorting
152+
$groupedLayouts[$translatedMainGroupTitle][$translatedSubGroupTitle] = $rawLayoutFiles[$mainGroupKey][$subGroupKey];
153+
}
154+
}
155+
// Handle any subgroups not explicitly defined in $subGroupTranslationKeys (e.g., new custom folder)
156+
foreach ($rawLayoutFiles[$mainGroupKey] as $subGroupKey => $layouts) {
157+
if (!array_key_exists($subGroupKey, $subGroupTranslationKeys)) {
158+
$translatedSubGroupTitle = $languageService->translate($subGroupKey); // Try to translate, fallback to key
159+
$groupedLayouts[$translatedMainGroupTitle][$translatedSubGroupTitle] = $layouts;
160+
}
161+
}
162+
}
163+
}
164+
165+
return $groupedLayouts;
166+
}
167+
}

0 commit comments

Comments
 (0)