Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1d03412
task: collage layout selection
andi34 Jan 13, 2025
1e66508
task: allow to disable layouts from selection
andi34 Jan 14, 2025
a19690c
task: make collage selection optional available
andi34 Jan 14, 2025
7bd397f
task: use collage frame & background depending on layout
andi34 Jan 19, 2025
bdd541e
collage: switch logic, select available layouts
andi34 Dec 30, 2025
e7db4ad
logger use correct interface
reloxx13 Dec 30, 2025
523b329
add collage limit calculator
reloxx13 Dec 30, 2025
a47aee8
Add touch-friendly collage layout modal and unify selection flow
reloxx13 Dec 30, 2025
86a0dcd
feat: Add visual SVG previews to collage layout selection modal
flacoonb Jan 1, 2026
b1b266a
refactor: Generate collage layout previews dynamically from JSON
flacoonb Jan 1, 2026
7671c3d
fix: Correct photo numbering in photostrip layouts (2x4, 2x3)
flacoonb Jan 1, 2026
a67e134
fix: Respect collage orientation setting in layout previews
flacoonb Jan 1, 2026
900c8aa
capture: respect collage limit on collage selection
andi34 Jan 2, 2026
98966d1
Implement zone-based text positioning with auto-fit for collages
flacoonb Jan 3, 2026
939f67b
test: add button for preview collage layouts with text zones
andi34 Jan 3, 2026
c06a1ae
test: refactor text-positions.php with admin UI components
flacoonb Jan 3, 2026
0adfdb3
docs: layout specifix collage frames & backgrounds
andi34 Jan 4, 2026
c5b4353
api(aaplyEffects): always collage-layout specific frame & background
andi34 Jan 4, 2026
aeff96e
src: add function to get prefixed file name
andi34 Jan 4, 2026
71e56ae
docs: formatting
andi34 Jan 6, 2026
c293f78
remove logs
reloxx13 Jan 6, 2026
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
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
template/collage/**/*.json
56 changes: 4 additions & 52 deletions api/admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
$defaultConfig = $configurationService->getDefaultConfiguration();

$data = ArrayUtility::replaceBooleanValues($_POST);
$action = isset($data['type']) ? $data['type'] : null;
$action = $data['type'] ?? null;

// Reset
if ($action === 'reset') {
Expand Down Expand Up @@ -257,59 +257,11 @@
// Collage json config
$newConfig['collage']['limit'] = $newConfig['collage']['limit'] ?? $defaultConfig['collage']['limit'];
if ($newConfig['collage']['enabled']) {
$collageConfigFilePath = Collage::getCollageConfigPath($newConfig['collage']['layout'], $newConfig['collage']['orientation']);

if ($collageConfigFilePath !== null) {
$collageJson = json_decode((string)file_get_contents($collageConfigFilePath), true);

if (is_array($collageJson)) {
if (isset($collageJson['layout']) && !empty($collageJson['layout'])) {
$layoutConfigArray = $collageJson['layout'];

if (array_key_exists('placeholder', $collageJson)) {
$newConfig['collage']['placeholder'] = $collageJson['placeholder'];
}
if (array_key_exists('placeholderposition', $collageJson)) {
$newConfig['collage']['placeholderposition'] = $collageJson['placeholderposition'];
}
if (array_key_exists('placeholderpath', $collageJson)) {
$newConfig['collage']['placeholderpath'] = $collageJson['placeholderpath'];
}
} else {
$layoutConfigArray = $collageJson;
}

// Calculate collage limit
if (str_starts_with($newConfig['collage']['layout'], '2x')) {
$newConfig['collage']['limit'] = (int) ceil(count($layoutConfigArray) / 2);
} else {
$newConfig['collage']['limit'] = count($layoutConfigArray);
}

// If there is a collage placeholder whithin the correct range (0 < placeholderposition <= collage limit), we need to decrease the collage limit by 1
if ($newConfig['collage']['placeholder']) {
$collagePlaceholderPosition = (int) $newConfig['collage']['placeholderposition'];
if ($collagePlaceholderPosition > 0 && $collagePlaceholderPosition <= $newConfig['collage']['limit']) {
$newConfig['collage']['limit'] = $newConfig['collage']['limit'] - 1;
} else {
$newConfig['collage']['placeholder'] = false;
$logger->debug('Placeholder position not in range. Placeholder disabled.');
}

if ($newConfig['collage']['placeholderpath'] === '') {
$newConfig['collage']['placeholder'] = false;
$logger->debug('Collage Placeholder is empty. Collage Placeholder disabled.');
}
}
} else {
$newConfig['collage']['enabled'] = false;
$logger->debug('No valid collage json found. Collage disabled.');
}
}
$limitData = Collage::calculateLimit($newConfig['collage'], $logger);
$newConfig['collage']['limit'] = $limitData['limit'];
$newConfig['collage']['placeholder'] = $limitData['placeholderEnabled'];
if ($newConfig['collage']['limit'] < 1) {
$newConfig['collage']['enabled'] = false;
$newConfig['collage']['limit'] = $defaultConfig['collage']['limit'];
$logger->debug('Invalid collage limit, must be 1 or greater. Collage disabled.');
}
}

Expand Down
9 changes: 9 additions & 0 deletions api/applyEffects.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@
throw new \Exception('Invalid or missing style parameter');
}

if (isset($_POST['collageLayout'])) {
$config['collage']['layout'] = $_POST['collageLayout'];

}

$limitData = Collage::calculateLimit($config['collage'], $logger);
$config['collage']['limit'] = $limitData['limit'];
$config['collage']['placeholder'] = $limitData['placeholderEnabled'];

$vars['style'] = $_POST['style'];

$vars['imageFilter'] = null;
Expand Down
4 changes: 4 additions & 0 deletions api/capture.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
throw new \Exception('No style provided');
}

if (isset($_POST['collageLimit'])) {
$config['collage']['limit'] = $_POST['collageLimit'];
}

if (!empty($_POST['file']) && (preg_match('/^[a-z0-9_]+\.jpg$/', $_POST['file']) || preg_match('/^[a-z0-9_]+\.(mp4)$/', $_POST['file']))) {
$file = $_POST['file'];
} else {
Expand Down
59 changes: 52 additions & 7 deletions assets/js/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ const photoBooth = (function () {
api.chromaimage = '';
api.filename = '';
api.photoStyle = '';
api.collageLayout = config.collage.layout;
api.collageLimit = config.collage.limit;

api.isTimeOutPending = function () {
return typeof timeOut !== 'undefined';
Expand Down Expand Up @@ -122,7 +124,6 @@ const photoBooth = (function () {

api.init = function () {
api.reset();

startPage.addClass('stage--active');
if (usesBackgroundPreview) {
photoboothPreview.startVideo(CameraDisplayMode.BACKGROUND);
Expand Down Expand Up @@ -284,7 +285,7 @@ const photoBooth = (function () {
'<br>' +
(api.nextCollageNumber + 1) +
' / ' +
config.collage.limit;
api.collageLimit;
labelElement.style.textAlign = 'center';
element.appendChild(labelElement);
} else {
Expand Down Expand Up @@ -561,6 +562,7 @@ const photoBooth = (function () {
if (api.photoStyle === PhotoStyle.COLLAGE) {
data.file = currentCollageFile;
data.collageNumber = api.nextCollageNumber;
data.collageLimit = api.collageLimit;
}

if (api.photoStyle === PhotoStyle.CHROMA) {
Expand Down Expand Up @@ -636,10 +638,10 @@ const photoBooth = (function () {
loaderImage.show();

photoboothTools.console.logDev(
'Taken collage photo number: ' + (result.current + 1) + ' / ' + result.limit
'Taken collage photo number: ' + (result.current + 1) + ' / ' + api.collageLimit
);

if (result.current + 1 < result.limit) {
if (result.current + 1 < api.collageLimit) {
photoboothTools.console.logDev('core: initialize Media.');
photoboothPreview.initializeMedia();
api.takingPic = false;
Expand All @@ -650,7 +652,7 @@ const photoBooth = (function () {
setTimeout(() => {
api.clearLoaderImage();
imageUrl = '';
if (result.current + 1 < result.limit) {
if (result.current + 1 < api.collageLimit) {
api.thrill(PhotoStyle.COLLAGE);
} else {
currentCollageFile = '';
Expand All @@ -660,7 +662,7 @@ const photoBooth = (function () {
}, continuousCollageTime);
} else {
// collage with interruption
if (result.current + 1 < result.limit) {
if (result.current + 1 < api.collageLimit) {
const takePictureButton = $(
'<button type="button" class="button collageNext rotaryfocus" id="btnCollageNext">'
);
Expand Down Expand Up @@ -890,7 +892,9 @@ const photoBooth = (function () {
data: {
file: result.file,
filter: imgFilter,
style: api.photoStyle
style: api.photoStyle,
collageLayout: api.collageLayout,
collageLimit: api.collageLimit
},
success: (data) => {
setFiltersEnabled(true);
Expand Down Expand Up @@ -1401,6 +1405,12 @@ const photoBooth = (function () {

$('.takeCollage, .newcollage').on('click', function (e) {
e.preventDefault();
if (config.collage.enabled && config.collage.allow_selection && $('#collageSelectorModal').length) {
$('#collageSelectorModal').data('pending-start', true);
$('#collageSelectorModal').removeClass('hidden').attr('aria-hidden', 'false');
$(this).trigger('blur');
return;
}
api.thrill(PhotoStyle.COLLAGE);
$(this).trigger('blur');
});
Expand Down Expand Up @@ -1601,6 +1611,41 @@ const photoBooth = (function () {
});
}

if (
typeof onStandaloneGalleryView === 'undefined' &&
typeof onCaptureChromaView === 'undefined' &&
config.collage.enabled &&
config.collage.allow_selection
) {
const collageModal = $('#collageSelectorModal');
const closeBtn = $('#collageSelectorClose');
const optionButtons = $('.collageSelector__option');

// Move modal to body so it isn't hidden by stage visibility
if (collageModal.length) {
$('body').append(collageModal.detach());
}

const setSelection = (layout, limit) => {
api.collageLayout = layout;
api.collageLimit = parseInt(limit, 10);
};

const closeModal = () => {
collageModal.addClass('hidden');
collageModal.attr('aria-hidden', 'true');
};

closeBtn.on('click', closeModal);

optionButtons.on('click', function () {
const button = $(this);
setSelection(button.data('layout'), button.data('limit'));
closeModal();
api.thrill(PhotoStyle.COLLAGE);
});
}

previewVideo.on('loadedmetadata', function (ev) {
const videoEl = ev.target;
let newWidth = videoEl.offsetWidth;
Expand Down
85 changes: 85 additions & 0 deletions assets/sass/components/_button.scss
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,67 @@
}
}

/**
* Select menu
*/
#collageSelector {
width: 100%;
margin-top: 0.75rem;
}

.collageSelector__trigger {
justify-content: flex-start;
gap: 0.5rem;
width: 100%;
}

.collageSelector__current {
display: block;
font-size: 0.9rem;
opacity: 0.85;
margin-top: 0.25rem;
}

.collageSelector__options {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 0.75rem;
width: 100%;
}

.collageSelector__option {
width: 100%;
border: 1px solid var(--border-color);
border-radius: 0.5rem;
padding: 1rem;
font-size: 1.05rem;
text-align: left;
background: #fff;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
}

.collageSelector__option:focus,
.collageSelector__option:hover,
.collageSelector__option.is-selected {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-color), #fff 70%);
}

.collageSelector__limit {
display: block;
font-size: 0.85rem;
opacity: 0.8;
margin-top: 0.35rem;
}

[data-ui-button='classic_rounded'],
[data-ui-button='modern'],
[data-ui-button='modern_squared'] {
.collageSelector__option {
border-radius: 0.75rem;
}
}

/**
* Buttonbar
*/
Expand Down Expand Up @@ -173,3 +234,27 @@
gap: 0.5rem;
padding: 0.5rem 1rem;
}

.collageSelector__preview-container {
margin-bottom: 0.75rem;
border-radius: 0.25rem;
overflow: hidden;
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
padding: 0.5rem;
}

.collageSelector__preview {
width: 100%;
max-width: 120px;
height: auto;
display: block;
}

.collageSelector__label {
font-weight: 600;
margin-bottom: 0.25rem;
color: var(--text-color);
}
8 changes: 6 additions & 2 deletions assets/sass/components/_modal.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
--modal-font-size: 1rem;
--modal-line-height: 1.2;
--modal-padding: 2rem;
--modal-spacing: 2rem;
--modal-spacing: 1.5rem;
--modal-max-width: 960px;

--modal-button-color: var(--button-font-color);
--modal-button-background: var(--primary-color);
Expand Down Expand Up @@ -45,11 +46,14 @@
position: relative;
color: var(--modal-color);
background: var(--modal-background);
max-width: calc(100dvw - var(--modal-spacing) * 2);
width: min(var(--modal-max-width), calc(100dvw - var(--modal-spacing) * 2));
max-width: min(var(--modal-max-width), calc(100dvw - var(--modal-spacing) * 2));
min-width: min(640px, calc(100dvw - var(--modal-spacing) * 2));
max-height: calc(100dvh - var(--modal-spacing) * 2);
}

&-body {
width: 100%;
text-align: center;
overflow-y: scroll;
font-size: var(--modal-font-size);
Expand Down
8 changes: 8 additions & 0 deletions assets/sass/components/_stage.scss
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@
&--start {
--stage-color: var(--stage-start-color);
--stage-background: var(--stage-start-background);
#collageDiv {
font-size: var(--button-font-size);
font-weight: bold;
color: var(--button-color);
padding-left: 2rem;
padding-right: 2rem;
padding-bottom: 1rem;
}
}

&--chroma {
Expand Down
3 changes: 3 additions & 0 deletions assets/sass/themes/_classic.scss
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,8 @@
.stage-inner:before {
border-radius: 1rem;
}
#collageDiv {
border-radius: 1rem;
}
}
}
5 changes: 5 additions & 0 deletions assets/sass/themes/_modern.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
.stage--start {
.stage-inner {
justify-content: flex-end;
#collageDiv {
background: color-mix(in srgb, var(--primary-color), transparent 75%);
backdrop-filter: blur(10px);
border-radius: 1rem;
}
}

button[data-command='cups-button'] {
Expand Down
Loading
Loading