Skip to content

Commit

Permalink
Merge pull request #22 from HerrZatacke/main
Browse files Browse the repository at this point in the history
UI improvements and additional GIF options
  • Loading branch information
untoxa authored Jan 29, 2025
2 parents 6186aa2 + 71037c6 commit 52e3644
Show file tree
Hide file tree
Showing 11 changed files with 345 additions and 71 deletions.
80 changes: 53 additions & 27 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<link rel="stylesheet" href="./src/style.css">
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#cc3366">
<link rel="apple-touch-startup-image" media="screen and (device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/iPhone_14_Pro_Max__iPhone_14_Max__iPhone_13_Pro_Max__iPhone_12_Pro_Max_portrait.png">
<link rel="apple-touch-startup-image" media="screen and (device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/iPhone_14_Pro__iPhone_14__iPhone_13_Pro__iPhone_13__iPhone_12_Pro__iPhone_12_portrait.png">
Expand All @@ -23,34 +22,61 @@
<header id="header">
<h1>Pico Game Boy Printer</h1>
<span class="indicator"></span>
<div class="buttons">
<button id="select_all_btn"><span>Select All</span></button>
<button id="delete_selected_btn" disabled><span>Delete</span></button>
<button id="average_selected_btn" disabled><span>Average</span></button>
<button id="gif_selected_btn" disabled><span>Animate</span></button>
<button id="rgb_selected_btn" disabled><span>RGB</span></button>
<label class="select">
<select id="download_size">
<option value="1">Scale: 1x</option>
<option value="2">Scale: 2x</option>
<option value="4">Scale: 4x</option>
<option value="8">Scale: 8x</option>
</select>
</label>
<label class="select">
<select id="download_fps">
<option value="1">FPS: 1x</option>
<option value="2">FPS: 2x</option>
<option value="4">FPS: 4x</option>
<option value="8">FPS: 8x</option>
<option value="12">FPS: 12x</option>
<option value="18">FPS: 18x</option>
<option value="24">FPS: 24x</option>
</select>
</label>
</div>
</header>
<div class="buttons">
<button title="Select All" id="select_all_btn"><span class="icon"><svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M268-240 42-466l57-56 170 170 56 56-57 56Zm226 0L268-466l56-57 170 170 368-368 56 57-424 424Zm0-226-57-56 198-198 57 56-198 198Z"/></svg></span><span class="title">Select All</span></button>
<button title="Delete" id="delete_selected_btn" disabled><span class="icon"><svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm80-160h80v-360h-80v360Zm160 0h80v-360h-80v360Z"/></svg></span><span class="title">Delete</span></button>
<button title="Average" id="average_selected_btn" disabled><span class="icon"><svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M480-400 40-640l440-240 440 240-440 240Zm0 160L63-467l84-46 333 182 333-182 84 46-417 227Zm0 160L63-307l84-46 333 182 333-182 84 46L480-80Z"/></svg></span><span class="title">Average</span></button>
<button title="Animate" id="gif_selected_btn" disabled><span class="icon"><svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M160-280q-33 0-56.5-23.5T80-360v-240q0-33 23.5-56.5T160-680h160q33 0 56.5 23.5T400-600H160v240h160v-80h-80v-80h160v160q0 33-23.5 56.5T320-280H160Zm320 0v-400h80v400h-80Zm160 0v-400h280v80H720v80h160v80H720v160h-80Z"/></svg></span><span class="title">Animate</span></button>
<button title="RGB" id="rgb_selected_btn" disabled><span class="icon"><svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 32.5-156t88-127Q256-817 330-848.5T488-880q80 0 151 27.5t124.5 76q53.5 48.5 85 115T880-518q0 115-70 176.5T640-280h-74q-9 0-12.5 5t-3.5 11q0 12 15 34.5t15 51.5q0 50-27.5 74T480-80ZM260-440q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17Zm120-160q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17Zm200 0q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17Zm120 160q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17Z"/></svg></span><span class="title">RGB</span></button>
<button title="Settings" id="open_settings" class="button"><span class="icon"><svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="m370-80-16-128q-13-5-24.5-12T307-235l-119 50L78-375l103-78q-1-7-1-13.5v-27q0-6.5 1-13.5L78-585l110-190 119 50q11-8 23-15t24-12l16-128h220l16 128q13 5 24.5 12t22.5 15l119-50 110 190-103 78q1 7 1 13.5v27q0 6.5-2 13.5l103 78-110 190-118-50q-11 8-23 15t-24 12L590-80H370Zm112-260q58 0 99-41t41-99q0-58-41-99t-99-41q-59 0-99.5 41T342-480q0 58 40.5 99t99.5 41Z"/></svg></span><span class="title">Settings</span></button>
</div>
<div id="settings">
<div class="box">
<button id="settings_close"><svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z"/></svg></button>
</div>
<div class="settings-options">
<div class="select">
<label for="download_size">Download size</label>
<div class="box">
<select id="download_size" class="setting-select">
<option value="1">Scale: 1x</option>
<option value="2">Scale: 2x</option>
<option value="4">Scale: 4x</option>
<option value="8">Scale: 8x</option>
</select>
</div>
</div>
<div class="select">
<label for="download_fps">Animation speed</label>
<div class="box">
<select id="download_fps" class="setting-select">
<option value="1">1 fps</option>
<option value="2">2 fps</option>
<option value="4">4 fps</option>
<option value="8">8 fps</option>
<option value="12">12 fps</option>
<option value="18">18 fps</option>
<option value="24">24 fps</option>
</select>
</div>
</div>
<div class="select">
<label for="gif_direction">Animation direction</label>
<div class="box">
<select id="gif_direction" class="setting-select">
<option value="fwd">Forward</option>
<option value="rev">Reverse</option>
<option value="yoyo">YoYo</option>
</select>
</div>
</div>
</div>
<a href="https://github.com/untoxa/pico-gb-printer/" class="about-link" target="_blank">this project on GitHub</a>
</div>
<button id="settings_backdrop"></button>
<div id="gallery" class="gallery"></div>
<div class="toast-target"></div>
<script type="module" src="./src/index.ts"></script>
</body>
</html>
13 changes: 10 additions & 3 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"chunk": "^0.0.3",
"ofetch": "^1.4.1",
"omggif": "^1.0.10",
"reset-css": "^5.0.2",
"typescript": "~5.6.2",
"vite": "^6.0.5"
"vite": "^6.0.11"
}
}
1 change: 1 addition & 0 deletions frontend/remote.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<body>
<header id="header">
<h1>You are connected to a remote application.</h1>
<br />
<p>Do not close this window during the process</p>
<span class="indicator"></span>
</header>
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,10 @@ export const BASIC_POLL_DELAY = 10;

export const LOCALSTORAGE_SCALE_KEY = 'pico-printer-save-scale';
export const LOCALSTORAGE_FPS_KEY = 'pico-printer-save-fps';
export const LOCALSTORAGE_GIF_DIR_KEY = 'pico-printer-gif-direction';

export enum Direction {
FORWARD = 'fwd',
REVERSE = 'rev',
YOYO = 'yoyo',
}
3 changes: 2 additions & 1 deletion frontend/src/functions/canvas/imageDatasToBlob.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import chunk from 'chunk';
import { GifWriter } from 'omggif';
import { showToast } from '../settings/toast.ts';

export interface GifFrameData {
palette: number[],
Expand Down Expand Up @@ -50,7 +51,7 @@ export const imageDatasToBlob = (frames: ImageData[], fps: number): Blob => {

if (frameCount !== frames.length) {
const msg = 'Some frames in your image contain more than 256 colors, which makes creating a GIF impossible';
alert(msg);
showToast(msg);
throw new Error(msg);
}

Expand Down
51 changes: 30 additions & 21 deletions frontend/src/functions/gallery/buttons.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import chunk from 'chunk';
import { LOCALSTORAGE_FPS_KEY, LOCALSTORAGE_SCALE_KEY } from '../../consts.ts';
import { Direction, LOCALSTORAGE_FPS_KEY, LOCALSTORAGE_GIF_DIR_KEY } from '../../consts.ts';
import { imageDatasToBlob } from '../canvas/imageDatasToBlob.ts';
import { showToast } from '../settings/toast.ts';
import { DataType, DbAccess } from '../storage/database.ts';
import { sortBySelectionOrder, updateSelectionOrder } from './selectionOrder.ts';

Expand All @@ -10,8 +11,7 @@ const selectAllBtn = document.getElementById("select_all_btn") as HTMLButtonElem
const averageSelectedBtn = document.getElementById("average_selected_btn") as HTMLButtonElement;
const gifSelectedBtn = document.getElementById("gif_selected_btn") as HTMLButtonElement;
const rgbSelectedBtn = document.getElementById("rgb_selected_btn") as HTMLButtonElement;
const scaleSelect = document.getElementById("download_size") as HTMLSelectElement;
const fpsSelect = document.getElementById("download_fps") as HTMLSelectElement;


export const updateButtons = () => {
const numSelectedItems = document.querySelectorAll('.marked-for-action').length;
Expand All @@ -21,9 +21,6 @@ export const updateButtons = () => {
averageSelectedBtn.disabled = numSelectedItems < 2 || numSelectedItemsFinal !== 0;
gifSelectedBtn.disabled = numSelectedItems < 2 || numSelectedItemsFinal !== 0;
rgbSelectedBtn.disabled = numSelectedItems !== 3 || numSelectedItemsFinal !== 0;

scaleSelect.value = localStorage.getItem(LOCALSTORAGE_SCALE_KEY) || '1';
fpsSelect.value = localStorage.getItem(LOCALSTORAGE_FPS_KEY) || '12';
}

interface Dimensions {
Expand Down Expand Up @@ -112,7 +109,7 @@ export const initButtons = (store: DbAccess) => {
const dimensions = getCommonSize(items);

if (!dimensions) {
alert("Image dimensions must be the same to create an average");
showToast('Image dimensions must be the same for all selected images to create an average');
return;
}

Expand Down Expand Up @@ -159,6 +156,7 @@ export const initButtons = (store: DbAccess) => {
gifSelectedBtn.addEventListener('click', async () => {
const items = [...gallery.querySelectorAll('.marked-for-action')] as HTMLDivElement[];
const fps = parseInt(localStorage.getItem(LOCALSTORAGE_FPS_KEY) || '12', 10);
const dir = localStorage.getItem(LOCALSTORAGE_GIF_DIR_KEY) as Direction || Direction.FORWARD;

if (items.length < 2) {
return;
Expand All @@ -171,7 +169,7 @@ export const initButtons = (store: DbAccess) => {
const dimensions = getCommonSize(images);

if (!dimensions) {
alert("Image dimensions must be the same to create an animation");
showToast('Image dimensions must be the same for all selected images to create an animation');
return;
}

Expand All @@ -184,14 +182,36 @@ export const initButtons = (store: DbAccess) => {
return ctx.getImageData(0, 0, canvas.width, canvas.height);
});

unselectAll();
switch (dir) {
case Direction.FORWARD:
break;

case Direction.REVERSE:
frames.reverse();
break;

case Direction.YOYO: {
console.log(frames);
if (frames.length > 2) {
frames.push(...frames.slice(1, -1).reverse());
}
console.log(frames);
break;
}

default:
break;
}


const timestamp = Date.now();
store.add({
type: DataType.BLOB,
timestamp,
data: imageDatasToBlob(frames, fps),
});

unselectAll();
});

rgbSelectedBtn.addEventListener('click', async () => {
Expand All @@ -208,7 +228,7 @@ export const initButtons = (store: DbAccess) => {
const dimensions = getCommonSize(images);

if (!dimensions) {
alert("Image dimensions must be the same to create a RGB image");
showToast('Image dimensions must be the same for all selected images to create a RGB image');
return;
}

Expand Down Expand Up @@ -248,16 +268,5 @@ export const initButtons = (store: DbAccess) => {
unselectAll();
});


scaleSelect.addEventListener('change', () => {
const scale = parseInt(scaleSelect.value || '0', 10) || 1;
localStorage.setItem(LOCALSTORAGE_SCALE_KEY, scale.toString(10));
});

fpsSelect.addEventListener('change', () => {
const fps = parseInt(fpsSelect.value || '0', 10) || 12;
localStorage.setItem(LOCALSTORAGE_FPS_KEY, fps.toString(10));
});

updateButtons();
}
53 changes: 53 additions & 0 deletions frontend/src/functions/settings/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { LOCALSTORAGE_GIF_DIR_KEY, LOCALSTORAGE_FPS_KEY, LOCALSTORAGE_SCALE_KEY, Direction } from '../../consts.ts';

const settingsMenu = document.getElementById('settings') as HTMLDivElement;
const settingsBackdrop = document.getElementById('settings_backdrop') as HTMLButtonElement;
const settingsCloseBtn = document.getElementById('settings_close') as HTMLButtonElement;
const scaleSelect = document.getElementById('download_size') as HTMLSelectElement;
const fpsSelect = document.getElementById('download_fps') as HTMLSelectElement;
const gifDirection = document.getElementById('gif_direction') as HTMLSelectElement;
const settingsBtn = document.getElementById('open_settings') as HTMLButtonElement;

const updateSettings = () => {
scaleSelect.value = localStorage.getItem(LOCALSTORAGE_SCALE_KEY) || '1';
fpsSelect.value = localStorage.getItem(LOCALSTORAGE_FPS_KEY) || '12';
gifDirection.value = localStorage.getItem(LOCALSTORAGE_GIF_DIR_KEY) || Direction.FORWARD;
}

export const initSettings = () => {
updateSettings();

scaleSelect.addEventListener('change', () => {
const scale = parseInt(scaleSelect.value || '0', 10) || 1;
localStorage.setItem(LOCALSTORAGE_SCALE_KEY, scale.toString(10));
});

fpsSelect.addEventListener('change', () => {
const fps = parseInt(fpsSelect.value || '0', 10) || 12;
localStorage.setItem(LOCALSTORAGE_FPS_KEY, fps.toString(10));
});

gifDirection.addEventListener('change', () => {
const dir = gifDirection.value || Direction.FORWARD;
localStorage.setItem(LOCALSTORAGE_GIF_DIR_KEY, dir);
});


settingsBtn.addEventListener('click', () => {
document.body.classList.add('fixed');
settingsMenu.classList.add('visible');
settingsBackdrop.classList.add('visible');
});

settingsBackdrop.addEventListener('click', () => {
document.body.classList.remove('fixed');
settingsMenu.classList.remove('visible');
settingsBackdrop.classList.remove('visible');
});

settingsCloseBtn.addEventListener('click', () => {
document.body.classList.remove('fixed');
settingsMenu.classList.remove('visible');
settingsBackdrop.classList.remove('visible');
});
}
17 changes: 17 additions & 0 deletions frontend/src/functions/settings/toast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const toastTarget = document.querySelector('.toast-target') as HTMLDivElement;

export const showToast = (message: string) => {
const toast = document.createElement('div');
toast.classList.add('toast');
toast.innerText = message;
toastTarget.appendChild(toast);

const closeTimeout = setTimeout(() => {
toastTarget.removeChild(toast);
}, 10000);

toast.addEventListener('click', () => {
clearTimeout(closeTimeout);
toastTarget.removeChild(toast);
})
}
5 changes: 5 additions & 0 deletions frontend/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { initGallery } from './functions/gallery';
import { initSettings } from './functions/settings';
import { initDb } from './functions/storage/database.ts';
import { startPolling } from './functions/pollLoop.ts';
import { webappConnect } from './functions/remote/webappConnect.ts';

import 'reset-css/reset.css';
import './style.css';

(async () => {
const store = await initDb();

Expand All @@ -11,6 +15,7 @@ import { webappConnect } from './functions/remote/webappConnect.ts';
await webappConnect(store, window.opener);
}
} else {
await initSettings();
await initGallery(store);
}

Expand Down
Loading

0 comments on commit 52e3644

Please sign in to comment.