Re-enabled stubs for PNG analyser and ICO maker, Implemented Data utils, file utils and PNG parser, Still testing
Update png-analyser.yml, png-chunk-analyser.yml, and 17 more files...
This commit is contained in:
7
data/applets/png-analyser.yml
Normal file
7
data/applets/png-analyser.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
applets:
|
||||||
|
- id: "png-analyser"
|
||||||
|
resources:
|
||||||
|
scripts:
|
||||||
|
- "applet://png-analyser.mjs"
|
||||||
|
stylesheets:
|
||||||
|
- "applet://png-analyser.css"
|
@@ -1,7 +0,0 @@
|
|||||||
applets:
|
|
||||||
- id: "png-chunk-analyser"
|
|
||||||
resources:
|
|
||||||
scripts:
|
|
||||||
- "applet://png-chunk-analyser.mjs"
|
|
||||||
stylesheets:
|
|
||||||
- "applet://png-chunk-analyser.css"
|
|
@@ -4,7 +4,8 @@ meta.title: "Icon Maker"
|
|||||||
meta.description: "..."
|
meta.description: "..."
|
||||||
|
|
||||||
|
|
||||||
|
file.selection.title: "File Selection"
|
||||||
|
file.selection.1: "Drop your file(s) here or click on the buttons."
|
||||||
|
|
||||||
enable.expert.mode: "Enable expert mode"
|
enable.expert.mode: "Enable expert mode"
|
||||||
enable.binary.blobs: "Allow binary blobs"
|
enable.binary.blobs: "Allow binary blobs"
|
||||||
|
@@ -4,7 +4,8 @@ meta.title: "Fabricateur d'icônes"
|
|||||||
meta.description: "..."
|
meta.description: "..."
|
||||||
|
|
||||||
|
|
||||||
|
file.selection.title : "Sélection de fichier(s)"
|
||||||
|
file.selection.1 : "Déposez vos fichiers ici ou cliquez sur les boutons."
|
||||||
|
|
||||||
enable.expert.mode: "Activer le mode expert"
|
enable.expert.mode: "Activer le mode expert"
|
||||||
enable.binary.blobs: "Autoriser les blobs binaires"
|
enable.binary.blobs: "Autoriser les blobs binaires"
|
||||||
|
@@ -16,7 +16,7 @@ tools:
|
|||||||
title_key: "meta.title"
|
title_key: "meta.title"
|
||||||
description_key: "meta.description"
|
description_key: "meta.description"
|
||||||
index:
|
index:
|
||||||
priority: 100
|
priority: 1500
|
||||||
enable: true
|
enable: true
|
||||||
title_key: "meta.title"
|
title_key: "meta.title"
|
||||||
preamble_key: "meta.description"
|
preamble_key: "meta.description"
|
||||||
@@ -27,4 +27,5 @@ tools:
|
|||||||
title_key: "meta.title"
|
title_key: "meta.title"
|
||||||
subtitle_key: "article.subtitle"
|
subtitle_key: "article.subtitle"
|
||||||
tags:
|
tags:
|
||||||
- "undefined"
|
- "utility"
|
||||||
|
- "graphics"
|
@@ -1,6 +1,6 @@
|
|||||||
tools:
|
tools:
|
||||||
- id: "png-chunk-analyser"
|
- id: "png-analyser"
|
||||||
applet_id: "png-chunk-analyser"
|
applet_id: "png-analyser"
|
||||||
metadata:
|
metadata:
|
||||||
head:
|
head:
|
||||||
title_key: "meta.title"
|
title_key: "meta.title"
|
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
.ico-maker-advanced {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"]#ico-maker-enable-expert-mode:checked ~ .ico-maker-advanced {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
@@ -0,0 +1,27 @@
|
|||||||
|
|
||||||
|
import {initCore} from "../../js/nibblepoker-core.mjs"
|
||||||
|
import {parsePngFile} from "../../libs/png-utils.mjs";
|
||||||
|
|
||||||
|
{
|
||||||
|
initCore();
|
||||||
|
|
||||||
|
const toolId = "png-analyser";
|
||||||
|
|
||||||
|
const eFileInput = document.getElementById(`${toolId}-test-input`);
|
||||||
|
|
||||||
|
window.onload = function () {
|
||||||
|
|
||||||
|
eFileInput.addEventListener('change', function(e) {
|
||||||
|
let files = e.target.files;
|
||||||
|
|
||||||
|
console.log(files);
|
||||||
|
|
||||||
|
parsePngFile(files[0]).then(pngFile => {
|
||||||
|
console.log(pngFile);
|
||||||
|
|
||||||
|
console.log(pngFile.getImageHeaderChunk().getWidth());
|
||||||
|
console.log(pngFile.getImageHeaderChunk().getHeight());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
52
static/resources/NibblePoker/libs/data-utils.mjs
Normal file
52
static/resources/NibblePoker/libs/data-utils.mjs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two UintXArray and checks if their content is the same.
|
||||||
|
* @param array1 {Uint8Array|Uint16Array|Uint32Array}
|
||||||
|
* @param array2 {Uint8Array|Uint16Array|Uint32Array}
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
export function areUintArraysEqual(array1, array2) {
|
||||||
|
if ((typeof array1) !== (typeof array2)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array1.length !== array2.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < array1.length; i++) {
|
||||||
|
if (array1[i] !== array2[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Peeks a UInt32 from the given data at the given offset in Big-Endian.
|
||||||
|
* @param data {Uint8Array} - Data to read from.
|
||||||
|
* @param offset {number} - Offset to read from in the given `data`.
|
||||||
|
* @return {number} The peeked number.
|
||||||
|
* @throws RangeError If the given offset is too close or over the end of the data.
|
||||||
|
*/
|
||||||
|
export function peekUInt32BE(data, offset = 0) {
|
||||||
|
if(offset + 4 > data.length) {
|
||||||
|
throw new RangeError(`Offset is too far into the given data ! (${offset} & ${data.length})`);
|
||||||
|
}
|
||||||
|
return new DataView(data.buffer, offset, 4).getUint32(0, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Peeks a UInt32 from the given data at the given offset in Little-Endian.
|
||||||
|
* @param data {Uint8Array} - Data to read from.
|
||||||
|
* @param offset {number} - Offset to read from in the given `data`.
|
||||||
|
* @return {number} The peeked number.
|
||||||
|
* @throws RangeError If the given offset is too close or over the end of the data.
|
||||||
|
*/
|
||||||
|
export function peekUInt32LE(data, offset = 0) {
|
||||||
|
if(offset + 4 > data.length) {
|
||||||
|
throw new RangeError(`Offset is too far into the given data ! (${offset} & ${data.length})`);
|
||||||
|
}
|
||||||
|
return new DataView(data.buffer, offset, 4).getUint32(0, true);
|
||||||
|
}
|
28
static/resources/NibblePoker/libs/file-utils.mjs
Normal file
28
static/resources/NibblePoker/libs/file-utils.mjs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a file and returns its content as a string.
|
||||||
|
* @param {File} file - The file to read.
|
||||||
|
* @returns {Promise<string>} A promise that resolves with the file content as a string.
|
||||||
|
*/
|
||||||
|
export function loadFileAsText(file) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => resolve(reader.result);
|
||||||
|
reader.onerror = reject;
|
||||||
|
reader.readAsText(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a file and returns its content as a `Uint8Array`.
|
||||||
|
* @param {File} file - The file to read.
|
||||||
|
* @returns {Promise<Uint8Array>} A promise that resolves with the file content as a byte buffer.
|
||||||
|
*/
|
||||||
|
export function loadFileAsUint8Array(file) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => resolve(new Uint8Array(reader.result));
|
||||||
|
reader.onerror = reject;
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
});
|
||||||
|
}
|
233
static/resources/NibblePoker/libs/png-utils.mjs
Normal file
233
static/resources/NibblePoker/libs/png-utils.mjs
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
|
||||||
|
//import {__crc32, crc32b, _crc32b, decimalToHexString} from "./crc32.mjs";
|
||||||
|
import {areUintArraysEqual, peekUInt32BE, peekUInt32LE} from "./data-utils.mjs"
|
||||||
|
import {loadFileAsUint8Array} from "./file-utils.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parent class extended by all PNG-related errors.
|
||||||
|
*/
|
||||||
|
export class PngError extends Error {}
|
||||||
|
|
||||||
|
export class PngInvalidFileHeaderError extends PngError {}
|
||||||
|
|
||||||
|
export class PngInvalidStructureError extends PngError {}
|
||||||
|
|
||||||
|
export class PngInvalidChunkNameError extends PngError {}
|
||||||
|
|
||||||
|
export class PngInvalidImageHeaderError extends PngError {}
|
||||||
|
|
||||||
|
export const PngFileHeader = new Uint8Array([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
|
||||||
|
|
||||||
|
export class PngChunk {
|
||||||
|
/** @type {string} */
|
||||||
|
type;
|
||||||
|
|
||||||
|
/** @type {Uint8Array} */
|
||||||
|
data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param type {string}
|
||||||
|
* @param data {Uint8Array}
|
||||||
|
* @param expectedChecksum {Uint8Array|null}
|
||||||
|
* @throws PngInvalidChunkNameError If the given chunk name isn't a valid chunk name.
|
||||||
|
*/
|
||||||
|
constructor(type, data, expectedChecksum) {
|
||||||
|
this.type = type;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
getChecksum() {
|
||||||
|
throw new Error("This function isn't implemented yet !");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PngImageHeaderChunk extends PngChunk {
|
||||||
|
/**
|
||||||
|
* @param type {string}
|
||||||
|
* @param data {Uint8Array}
|
||||||
|
* @param expectedChecksum {Uint8Array|null}
|
||||||
|
* @throws PngInvalidChunkNameError If the given chunk name isn't a valid chunk name.
|
||||||
|
* @throws PngInvalidImageHeaderError If the given chunk's size isn't exactly 13 bytes.
|
||||||
|
*/
|
||||||
|
constructor(type, data, expectedChecksum) {
|
||||||
|
super(type, data, expectedChecksum);
|
||||||
|
|
||||||
|
if(this.data.length !== 13) {
|
||||||
|
throw new PngInvalidImageHeaderError(`Invalid IHDR chunk size, got ${this.data.length} instead of 13 !`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getWidth() {
|
||||||
|
return peekUInt32BE(this.data, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
getHeight() {
|
||||||
|
return peekUInt32BE(this.data, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
getBitDepth() {
|
||||||
|
return this.data[8];
|
||||||
|
}
|
||||||
|
|
||||||
|
getColorType() {
|
||||||
|
return this.data[9];
|
||||||
|
}
|
||||||
|
|
||||||
|
getCompressionMethod() {
|
||||||
|
return this.data[10];
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilterMethod() {
|
||||||
|
return this.data[11];
|
||||||
|
}
|
||||||
|
|
||||||
|
getInterlaceMethod() {
|
||||||
|
return this.data[12];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a `PngChunk` to a `PngImageHeaderChunk`.
|
||||||
|
* @param pngChunk {PngChunk}
|
||||||
|
* @return {PngImageHeaderChunk}
|
||||||
|
*/
|
||||||
|
static fromPngChunk(pngChunk) {
|
||||||
|
return new PngImageHeaderChunk(pngChunk.type, pngChunk.data, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PngFile {
|
||||||
|
/** @type {File|null} */
|
||||||
|
originalFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional trailing data located after the 'IEND' chunk.
|
||||||
|
* @type {Uint8Array|null}
|
||||||
|
*/
|
||||||
|
trailingData;
|
||||||
|
|
||||||
|
/** @type {PngChunk[]} */
|
||||||
|
chunks;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param file {File|null}
|
||||||
|
* @param fileData {Uint8Array|null}
|
||||||
|
* @throws PngInvalidFileHeaderError If the `fileData` is provided and doesn't contain a valid PNG file header.
|
||||||
|
*/
|
||||||
|
constructor(file = null, fileData = null) {
|
||||||
|
this.originalFile = file;
|
||||||
|
|
||||||
|
this.chunks = [];
|
||||||
|
|
||||||
|
// Parsing the data
|
||||||
|
if(fileData !== null) {
|
||||||
|
this.#validateFileHeader(fileData);
|
||||||
|
this.#parseChunks(fileData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param originalFileData {Uint8Array}
|
||||||
|
*/
|
||||||
|
#validateFileHeader = (originalFileData) => {
|
||||||
|
if(originalFileData.length < 8) {
|
||||||
|
throw new PngInvalidFileHeaderError(
|
||||||
|
`The file header's length is smaller than the required 8 ! (Got: ${originalFileData.length})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!areUintArraysEqual(originalFileData.slice(0, 8), PngFileHeader)) {
|
||||||
|
throw new PngInvalidFileHeaderError(
|
||||||
|
"The file header didn't have the expected data !\n" +
|
||||||
|
`Expected: ${PngFileHeader}\\n` +
|
||||||
|
`Got: ${originalFileData.slice(0, 8)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param originalFileData {Uint8Array}
|
||||||
|
* @throws TypeError If a chunk's type couldn't be parsed.
|
||||||
|
*/
|
||||||
|
#parseChunks = (originalFileData) => {
|
||||||
|
let currentOffset = PngFileHeader.length;
|
||||||
|
|
||||||
|
while(currentOffset < originalFileData.length) {
|
||||||
|
// Checking if we haven't encountered an IEND, and we encountered a truncated file or trash data.
|
||||||
|
if(currentOffset + 12 > originalFileData.length) {
|
||||||
|
throw new PngInvalidStructureError("Unable to parse more chunks and no 'IEND' was encountered !");
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunkLength = peekUInt32BE(originalFileData.slice(currentOffset, currentOffset + 4));
|
||||||
|
const chunkType = new TextDecoder().decode(originalFileData.slice(currentOffset + 4, currentOffset + 8));
|
||||||
|
|
||||||
|
// Checking if we have enough data left to read for the chunk's data.
|
||||||
|
if(currentOffset + 12 + chunkLength > originalFileData.length) {
|
||||||
|
throw new PngInvalidStructureError("Not enough data left to read the chunk !")
|
||||||
|
}
|
||||||
|
|
||||||
|
let chunkChecksum = originalFileData.slice(
|
||||||
|
currentOffset + 8 + chunkLength, currentOffset + 8 + chunkLength + 4);
|
||||||
|
|
||||||
|
this.chunks.push(
|
||||||
|
new PngChunk(
|
||||||
|
chunkType,
|
||||||
|
(chunkLength === 0) ?
|
||||||
|
new Uint8Array(0) :
|
||||||
|
originalFileData.slice(currentOffset + 8, currentOffset + 8 + chunkLength),
|
||||||
|
chunkChecksum,
|
||||||
|
//originalFileData.slice(currentOffset + 4, currentOffset + 8)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if(chunkType === "IEND") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentOffset += 12 + chunkLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handling trailing data
|
||||||
|
if(currentOffset !== originalFileData.length) {
|
||||||
|
this.trailingData = originalFileData.slice(currentOffset, originalFileData.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to retrieve a chunk via its type.
|
||||||
|
* @param chunkName {string} The desired chunk's type.
|
||||||
|
* @return {PngChunk|null}
|
||||||
|
*/
|
||||||
|
getChunkByType(chunkName) {
|
||||||
|
for(let iChunk in this.chunks) {
|
||||||
|
if(this.chunks[iChunk].type === chunkName) {
|
||||||
|
return this.chunks[iChunk];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasEndChunk() {
|
||||||
|
return this.getChunkByType("IEND") !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {PngImageHeaderChunk|null}
|
||||||
|
*/
|
||||||
|
getImageHeaderChunk() {
|
||||||
|
let desiredChunk = this.getChunkByType("IHDR");
|
||||||
|
if(desiredChunk !== null) {
|
||||||
|
desiredChunk = PngImageHeaderChunk.fromPngChunk(desiredChunk);
|
||||||
|
}
|
||||||
|
return desiredChunk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads and parses a given PNG file.
|
||||||
|
* @param {File} file - The PNG file to process.
|
||||||
|
* @returns {Promise<PngFile>} A promise that resolves with parsed PNG file.
|
||||||
|
*/
|
||||||
|
export function parsePngFile(file) {
|
||||||
|
return loadFileAsUint8Array(file).then(byteBuffer => {
|
||||||
|
return new PngFile(file, byteBuffer);
|
||||||
|
});
|
||||||
|
}
|
@@ -1,26 +1,14 @@
|
|||||||
|
|
||||||
<section id="{{ applet_data.id }}-introduction">
|
{{ render_file_input(applet_data.id + "-image-input-file", true, ".png, .bmp, .ico", true, false, user_lang) }}
|
||||||
{{ render_h2(l10n("introduction.title", "commons", user_lang)) }}
|
|
||||||
{{ render_paragraph(l10n("introduction.1", applet_data.id, user_lang)) }}
|
|
||||||
{{ render_paragraph(l10n("introduction.2", applet_data.id, user_lang)) }}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section id="{{ applet_data.id }}-input">
|
<hr class="subtle">
|
||||||
{{ render_h2(l10n("input.title", "commons", user_lang)) }}
|
|
||||||
{{ render_paragraph(l10n("input.1", applet_data.id, user_lang)) }}
|
|
||||||
|
|
||||||
{{ render_file_input(applet_data.id + "-image-input-file", true, ".png, .bmp, .ico", true, false, user_lang) }}
|
<label for="{{ applet_data.id }}-enable-expert-mode" class="mr-xxs">
|
||||||
|
|
||||||
<hr class="subtle">
|
|
||||||
|
|
||||||
<label for="{{ applet_data.id }}-enable-expert-mode" class="mr-xxs">
|
|
||||||
{{ l10n("enable.expert.mode", applet_data.id, user_lang) }}:
|
{{ l10n("enable.expert.mode", applet_data.id, user_lang) }}:
|
||||||
</label>
|
</label>
|
||||||
<input id="{{ applet_data.id }}-enable-expert-mode" class="r-m border" type="checkbox">
|
<input id="{{ applet_data.id }}-enable-expert-mode" class="r-m border" type="checkbox">
|
||||||
<!--<span class="ml-s t-italic t-muted">Toggles some uncommon features.</span>-->
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
|
<div class="ico-maker-advanced">
|
||||||
<label for="{{ applet_data.id }}-enable-binary-blobs" class="mr-xxs">
|
<label for="{{ applet_data.id }}-enable-binary-blobs" class="mr-xxs">
|
||||||
{{ l10n("enable.binary.blobs", applet_data.id, user_lang) }}:
|
{{ l10n("enable.binary.blobs", applet_data.id, user_lang) }}:
|
||||||
</label>
|
</label>
|
||||||
@@ -35,17 +23,16 @@
|
|||||||
|
|
||||||
<span class="f-right">
|
<span class="f-right">
|
||||||
{{ render_button("<i class=\"fa-duotone fa-solid fa-remove\"></i>Remove Binary Blobs", False, None, "bkgd-orange") }}
|
{{ render_button("<i class=\"fa-duotone fa-solid fa-remove\"></i>Remove Binary Blobs", False, None, "bkgd-orange") }}
|
||||||
{{ render_button("<i class=\"fa-duotone fa-solid fa-remove\"></i>Remove Everything", False, None, "bkgd-red") }}
|
<!--{{ render_button("<i class=\"fa-duotone fa-solid fa-remove\"></i>Remove Everything", False, None, "bkgd-red") }}-->
|
||||||
</span>
|
</span>
|
||||||
</section>
|
</div>
|
||||||
|
|
||||||
<section id="{{ applet_data.id }}-icon-parts">
|
<hr class="subtle">
|
||||||
{{ render_h2(l10n("idk01.title", "commons", user_lang)) }}
|
|
||||||
{{ render_paragraph(l10n("idk01.1", applet_data.id, user_lang)) }}
|
|
||||||
|
|
||||||
{{ render_button("<i class=\"fa-duotone fa-solid fa-download\"></i>Download <span class=\"t-monospace\">.ico</span>", False, None, "bkgd-green") }}
|
|
||||||
|
|
||||||
<div class="border r-m">
|
{{ render_button("<i class=\"fa-duotone fa-solid fa-download\"></i>Download <span class=\"t-monospace\">.ico</span>", False, None, "bkgd-green") }}
|
||||||
|
|
||||||
|
<div class="border r-m ico-maker-advanced">
|
||||||
<table class="table-p-xxs ">
|
<table class="table-p-xxs ">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="bkgd-grid40 rl-m br t-size-14 t-noselect" rowspan="2">
|
<td class="bkgd-grid40 rl-m br t-size-14 t-noselect" rowspan="2">
|
||||||
@@ -67,9 +54,9 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="{{ applet_data.id }}-icon-parts-list">
|
<div id="{{ applet_data.id }}-icon-parts-list">
|
||||||
|
|
||||||
<div class="border r-m">
|
<div class="border r-m">
|
||||||
<table class="table-p-xxs table-v-center">
|
<table class="table-p-xxs table-v-center">
|
||||||
@@ -198,13 +185,12 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</section>
|
|
||||||
|
|
||||||
|
|
||||||
<section id="{{ applet_data.id }}-licenses">
|
|
||||||
{{ render_h2(l10n("licenses.title", "commons", user_lang)) }}
|
{% if is_standalone %}
|
||||||
|
<hr class="subtle">
|
||||||
{{ render_paragraph(l10n("licenses.1", applet_data.id, user_lang)) }}
|
{{ render_paragraph(l10n("licenses.1", applet_data.id, user_lang)) }}
|
||||||
{{ render_paragraph(l10n("licenses.2", applet_data.id, user_lang)) }}
|
{{ render_paragraph(l10n("licenses.2", applet_data.id, user_lang)) }}
|
||||||
</section>
|
{% endif %}
|
||||||
|
3
templates/applets/png-analyser.jinja
Normal file
3
templates/applets/png-analyser.jinja
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
{{ render_file_input(tool_id + "-test-input", true, ".png", true, true) }}
|
||||||
|
|
@@ -1,5 +0,0 @@
|
|||||||
|
|
||||||
{{ render_h2(l10n("upload.title", "commons", user_lang)) }}
|
|
||||||
|
|
||||||
{{ render_file_input("test-input", true, None, true, true) }}
|
|
||||||
|
|
Reference in New Issue
Block a user