Turned the PaleteBlock into an IIFE

This commit is contained in:
unsettledgames 2021-12-06 11:26:42 +01:00
parent 71bfe543a5
commit d972f9c530
12 changed files with 339 additions and 330 deletions

View File

@ -17,7 +17,6 @@ The next version is mostly focused on adding missing essential features and port
Suggestions / Planned features: Suggestions / Planned features:
- Documentation - Documentation
- Possibility to hide and resize menus (layers, palette) - Possibility to hide and resize menus (layers, palette)
- Tiled mode - Tiled mode
- Load palette from LPE file - Load palette from LPE file
@ -26,8 +25,8 @@ Suggestions / Planned features:
- Possibly add collaborate function - Possibly add collaborate function
- Code refactoring - Code refactoring
- Find inefficient sections (nested loops, useless / inefficient parts) - Find and fix inefficient sections (nested loops, useless / inefficient parts)
- Create classes ResizableTool and SelectionTool. Make them inherit from Tool, avoid creating brush resizing functions each time for each tool that can be resized - Refactor the ColorPicker IIFE
- Mobile - Mobile
- Touch equivalent for mouse clicks - Touch equivalent for mouse clicks
@ -36,7 +35,7 @@ Suggestions / Planned features:
- Fix popups - Fix popups
- Polish: - Polish:
- ctrl a to select everything / selection -> all, same for deselection - CTRL+A to select everything / selection -> all, same for deselection
- Warning windows for wrong inputs - Warning windows for wrong inputs
- Palette option remove unused colors - Palette option remove unused colors
- Move selection with arrows - Move selection with arrows

View File

@ -165,4 +165,16 @@ class Color {
return {h: myH * 360, s: myS * 100, v: myV * 100}; return {h: myH * 360, s: myS * 100, v: myV * 100};
} }
/** Converts a CSS colour eg rgb(x,y,z) to a hex string
*
* @param {*} rgb
*/
static cssToHex(rgb) {
rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
function hex(x) {
return ("0" + parseInt(x).toString(16)).slice(-2);
}
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
}
} }

View File

@ -160,7 +160,7 @@ const ColorModule = (() => {
const col = coloursList.children[i].style.backgroundColor; const col = coloursList.children[i].style.backgroundColor;
if (col.includes("rgb")) { if (col.includes("rgb")) {
addColor(cssToHex(col)); addColor(Color.cssToHex(col));
} }
else { else {
addColor(col); addColor(col);

View File

@ -773,7 +773,11 @@ const ColorPicker = (() => {
} }
return { return {
init init,
getSelectedColours,
updatePickerByHex,
updateSlidersByHex,
updateMiniPickerColour
} }
})(); })();

View File

@ -44,7 +44,7 @@ const Dialogue = (() => {
// If I'm opening the palette window, I initialize the colour picker // If I'm opening the palette window, I initialize the colour picker
if (dialogueName == 'palette-block' && Startup.documentCreated()) { if (dialogueName == 'palette-block' && Startup.documentCreated()) {
ColorPicker.init(); ColorPicker.init();
pbInit(); PaletteBlock.init();
} }
//track google event //track google event

300
js/PaletteBlock.js Normal file
View File

@ -0,0 +1,300 @@
const PaletteBlock = (() => {
// HTML elements
let coloursList = document.getElementById("palette-list");
let rampMenu = document.getElementById("pb-ramp-options");
let pbRampDialogue = document.getElementById("pb-ramp-dialogue");
// PaletteBlock-specific data
let currentSquareSize = coloursList.children[0].clientWidth;
let blockData = {blockWidth: 300, blockHeight: 320, squareSize: 40};
let isRampSelecting = false;
let ramps = [];
let currentSelection = {startIndex:0, endIndex:0, startCoords:[], endCoords: [], name: "", colour: "", label: null};
// Making the palette list sortable
new Sortable(document.getElementById("palette-list"), {
animation: 100,
onEnd: updateRampSelection
});
// Listening for the palette block resize
new ResizeObserver(updateSizeData).observe(coloursList.parentElement);
Events.on("click", "pb-addcolours", addColours);
Events.on("click", "pb-removecolours", removeColours);
/** Listens for the mouse wheel, used to change the size of the squares in the palette list
*
*/
coloursList.parentElement.addEventListener("wheel", function (mouseEvent) {
// Only resize when pressing alt, used to distinguish between scrolling through the palette and
// resizing it
if (mouseEvent.altKey) {
resizeSquares(mouseEvent);
}
});
// Initializes the palette block
function init() {
let simplePalette = document.getElementById("colors-menu");
let childCount = coloursList.childElementCount;
currentSquareSize = coloursList.children[0].clientWidth;
coloursList = document.getElementById("palette-list");
// Remove all the colours
for (let i=0; i<childCount; i++) {
coloursList.children[0].remove();
}
// Add all the colours from the simplepalette
for (let i=0; i<simplePalette.childElementCount-1; i++) {
addSingleColour(Color.cssToHex(simplePalette.children[i].children[0].style.backgroundColor));
}
}
/** Tells whether a colour is in the palette or not
*
* @param {*} colour The colour to add
*/
function hasColour(colour) {
for (let i=0; i<coloursList.childElementCount; i++) {
let currentCol = coloursList.children[i].style.backgroundColor;
let currentHex = Color.cssToHex(currentCol);
if (currentHex == colour) {
return true;
}
}
return false;
}
/** Adds a single colour to the palette
*
* @param {*} colour The colour to add
*/
function addSingleColour(colour) {
if (!hasColour(colour)) {
let li = document.createElement("li");
li.style.width = currentSquareSize + "px";
li.style.height = currentSquareSize + "px";
li.style.backgroundColor = colour;
li.addEventListener("mousedown", startRampSelection.bind(this));
li.addEventListener("mouseup", endRampSelection.bind(this));
li.addEventListener("mousemove", updateRampSelection.bind(this));
li.addEventListener("onclick", endRampSelection.bind(this));
coloursList.appendChild(li);
}
}
/** Adds all the colours currently selected in the colour picker
*
*/
function addColours() {
let colours = ColorPicker.getSelectedColours();
for (let i=0; i<colours.length; i++) {
addSingleColour(colours[i]);
}
}
/** Removes all the currently selected colours from the palette
*
*/
function removeColours() {
let startIndex = currentSelection.startIndex;
let endIndex = currentSelection.endIndex;
if (startIndex > endIndex) {
let tmp = startIndex;
startIndex = endIndex;
endIndex = tmp;
}
for (let i=startIndex; i<=endIndex; i++) {
coloursList.removeChild(coloursList.children[startIndex]);
}
clearBorders();
}
/** Starts selecting a ramp. Saves the data needed to draw the outline.
*
* @param {*} mouseEvent
*/
function startRampSelection(mouseEvent) {
if (mouseEvent.which == 3) {
let index = getElementIndex(mouseEvent.target);
isRampSelecting = true;
currentSelection.startIndex = index;
currentSelection.endIndex = index;
currentSelection.startCoords = getColourCoordinates(index);
currentSelection.endCoords = getColourCoordinates(index);
}
}
/** Updates the outline for the current selection.
*
* @param {*} mouseEvent
*/
function updateRampSelection(mouseEvent) {
if (mouseEvent != null && mouseEvent.which == 3) {
currentSelection.endIndex = getElementIndex(mouseEvent.target);
}
if (mouseEvent == null || mouseEvent.which == 3) {
let startCoords = getColourCoordinates(currentSelection.startIndex);
let endCoords = getColourCoordinates(currentSelection.endIndex);
let startIndex = currentSelection.startIndex;
let endIndex = currentSelection.endIndex;
if (currentSelection.startIndex > endIndex) {
let tmp = startIndex;
startIndex = endIndex;
endIndex = tmp;
tmp = startCoords;
startCoords = endCoords;
endCoords = tmp;
}
clearBorders();
for (let i=startIndex; i<=endIndex; i++) {
let currentSquare = coloursList.children[i];
let currentCoords = getColourCoordinates(i);
let borderStyle = "3px solid white";
let bordersToSet = [];
// Deciding which borders to use to make the outline
if (i == 0 || i == startIndex) {
bordersToSet.push("border-left");
}
if (currentCoords[1] == startCoords[1] || ((currentCoords[1] == startCoords[1] + 1)) && currentCoords[0] < startCoords[0]) {
bordersToSet.push("border-top");
}
if (currentCoords[1] == endCoords[1] || ((currentCoords[1] == endCoords[1] - 1)) && currentCoords[0] > endCoords[0]) {
bordersToSet.push("border-bottom");
}
if ((i == coloursList.childElementCount - 1) || (currentCoords[0] == Math.floor(blockData.blockWidth / blockData.squareSize) - 1)
|| i == endIndex) {
bordersToSet.push("border-right");
}
if (bordersToSet != []) {
currentSquare.style["box-sizing"] = "border-box";
for (let i=0; i<bordersToSet.length; i++) {
currentSquare.style[bordersToSet[i]] = borderStyle;
}
}
}
}
}
/** Removes all the borders from all the squares. The borders are cleared only for the
* current selection, so every border that is not white is kept.
*
*/
function clearBorders() {
for (let i=0; i<coloursList.childElementCount; i++) {
coloursList.children[i].style["border-top"] = "none";
coloursList.children[i].style["border-left"] = "none";
coloursList.children[i].style["border-right"] = "none";
coloursList.children[i].style["border-bottom"] = "none";
}
}
/** Ends the current selection, opens the ramp menu
*
* @param {*} mouseEvent
*/
function endRampSelection(mouseEvent) {
let col;
if (currentSelection.startCoords.length == 0) {
currentSelection.endIndex = getElementIndex(mouseEvent.target);
currentSelection.startIndex = currentSelection.endIndex;
currentSelection.startCoords = getColourCoordinates(currentSelection.startIndex);
}
// I'm not selecting a ramp anymore
isRampSelecting = false;
// Setting the end coordinates
currentSelection.endCoords = getColourCoordinates(getElementIndex(mouseEvent.target));
// Setting the colour in the colour picker
col = Color.cssToHex(coloursList.children[currentSelection.startIndex].style.backgroundColor);
ColorPicker.updatePickerByHex(col);
ColorPicker.updateSlidersByHex(col);
ColorPicker.updateMiniPickerColour();
updateRampSelection();
currentSelection.startCoords = [];
}
/** Updates the current data about the size of the palette list (height, width and square size).
* It also updates the outline after doing so.
*
*/
function updateSizeData() {
blockData.blockHeight = coloursList.parentElement.clientHeight;
blockData.blockWidth = coloursList.parentElement.clientWidth;
blockData.squareSize = coloursList.children[0].clientWidth;
updateRampSelection();
}
/** Gets the colour coordinates relative to the colour list seen as a matrix. Coordinates
* start from the top left angle.
*
* @param {*} index The index of the colour in the list seen as a linear array
*/
function getColourCoordinates(index) {
let yIndex = Math.floor(index / Math.floor(blockData.blockWidth / blockData.squareSize));
let xIndex = Math.floor(index % Math.floor(blockData.blockWidth / blockData.squareSize));
return [xIndex, yIndex];
}
/** Returns the index of the element in the colour list
*
* @param {*} element The element of which we need to get the index
*/
function getElementIndex(element) {
for (let i=0; i<coloursList.childElementCount; i++) {
if (element == coloursList.children[i]) {
return i;
}
}
}
/** Resizes the squares depending on the scroll amount (only resizes if the user is
* also holding alt)
*
* @param {*} mouseEvent
*/
function resizeSquares(mouseEvent) {
let amount = mouseEvent.deltaY > 0 ? -5 : 5;
currentSquareSize += amount;
for (let i=0; i<coloursList.childElementCount; i++) {
let currLi = coloursList.children[i];
currLi.style["box-sizing"] = "content-box";
currLi.style.width = currLi.clientWidth + amount + "px";
currLi.style.height = currLi.clientHeight + amount + "px";
}
updateSizeData();
}
return {
init
}
})();

View File

@ -30,16 +30,21 @@ const ToolManager = (() => {
Events.onCustom("tool-shortcut", onShortcut); Events.onCustom("tool-shortcut", onShortcut);
function onShortcut(tool) { function onShortcut(tool) {
if (!Startup.documentCreated || Dialogue.isOpen())
return;
switchTool(tools[tool]); switchTool(tools[tool]);
} }
function onMouseWheel(mouseEvent) { function onMouseWheel(mouseEvent) {
if (!Startup.documentCreated || Dialogue.isOpen())
return;
let mousePos = Input.getCursorPosition(mouseEvent); let mousePos = Input.getCursorPosition(mouseEvent);
tools["zoom"].onMouseWheel(mousePos, mouseEvent.deltaY < 0 ? 'in' : 'out'); tools["zoom"].onMouseWheel(mousePos, mouseEvent.deltaY < 0 ? 'in' : 'out');
} }
function onMouseDown(mouseEvent) { function onMouseDown(mouseEvent) {
if (!Startup.documentCreated()) if (!Startup.documentCreated() || Dialogue.isOpen())
return; return;
let mousePos = Input.getCursorPosition(mouseEvent); let mousePos = Input.getCursorPosition(mouseEvent);
@ -70,7 +75,7 @@ const ToolManager = (() => {
} }
function onMouseMove(mouseEvent) { function onMouseMove(mouseEvent) {
if (!Startup.documentCreated()) if (!Startup.documentCreated() || Dialogue.isOpen())
return; return;
let mousePos = Input.getCursorPosition(mouseEvent); let mousePos = Input.getCursorPosition(mouseEvent);
// Call the hover event // Call the hover event

View File

@ -1,311 +0,0 @@
/** INIT is called when it shouldn't **/
let coloursList = document.getElementById("palette-list");
let rampMenu = document.getElementById("pb-ramp-options");
let pbRampDialogue = document.getElementById("pb-ramp-dialogue");
let currentSquareSize = coloursList.children[0].clientWidth;
let blockData = {blockWidth: 300, blockHeight: 320, squareSize: 40};
let isRampSelecting = false;
let ramps = [];
let currentSelection = {startIndex:0, endIndex:0, startCoords:[], endCoords: [], name: "", colour: "", label: null};
// Making the palette list sortable
new Sortable(document.getElementById("palette-list"), {
animation: 100,
onEnd: updateRampSelection
});
// Listening for the palette block resize
new ResizeObserver(updateSizeData).observe(coloursList.parentElement);
// Initializes the palette block
function pbInit() {
let simplePalette = document.getElementById("colors-menu");
let childCount = coloursList.childElementCount;
currentSquareSize = coloursList.children[0].clientWidth;
coloursList = document.getElementById("palette-list");
// Remove all the colours
for (let i=0; i<childCount; i++) {
coloursList.children[0].remove();
}
// Add all the colours from the simplepalette
for (let i=0; i<simplePalette.childElementCount-1; i++) {
addSingleColour(cssToHex(simplePalette.children[i].children[0].style.backgroundColor));
}
}
/** Listens for the mouse wheel, used to change the size of the squares in the palette list
*
*/
coloursList.parentElement.addEventListener("wheel", function (mouseEvent) {
// Only resize when pressing alt, used to distinguish between scrolling through the palette and
// resizing it
if (mouseEvent.altKey) {
resizeSquares(mouseEvent);
}
});
/** Tells whether a colour is in the palette or not
*
* @param {*} colour The colour to add
*/
function hasColour(colour) {
for (let i=0; i<coloursList.childElementCount; i++) {
let currentCol = coloursList.children[i].style.backgroundColor;
let currentHex = cssToHex(currentCol);
if (currentHex == colour) {
return true;
}
}
return false;
}
/** Adds a single colour to the palette
*
* @param {*} colour The colour to add
*/
function addSingleColour(colour) {
if (!hasColour(colour)) {
let li = document.createElement("li");
li.style.width = currentSquareSize + "px";
li.style.height = currentSquareSize + "px";
li.style.backgroundColor = colour;
li.addEventListener("mousedown", startRampSelection);
li.addEventListener("mouseup", endRampSelection);
li.addEventListener("mousemove", updateRampSelection);
li.addEventListener("onclick", endRampSelection);
coloursList.appendChild(li);
}
}
/** Adds all the colours currently selected in the colour picker
*
*/
function pbAddColours() {
let colours = getSelectedColours();
for (let i=0; i<colours.length; i++) {
addSingleColour(colours[i]);
}
}
/** Removes all the currently selected colours from the palette
*
*/
function pbRemoveColours() {
let startIndex = currentSelection.startIndex;
let endIndex = currentSelection.endIndex;
if (startIndex > endIndex) {
let tmp = startIndex;
startIndex = endIndex;
endIndex = tmp;
}
for (let i=startIndex; i<=endIndex; i++) {
coloursList.removeChild(coloursList.children[startIndex]);
}
clearBorders();
}
/** Starts selecting a ramp. Saves the data needed to draw the outline.
*
* @param {*} mouseEvent
*/
function startRampSelection(mouseEvent) {
if (mouseEvent.which == 3) {
let index = getElementIndex(mouseEvent.target);
isRampSelecting = true;
currentSelection.startIndex = index;
currentSelection.endIndex = index;
currentSelection.startCoords = getColourCoordinates(index);
currentSelection.endCoords = getColourCoordinates(index);
}
}
/** Updates the outline for the current selection.
*
* @param {*} mouseEvent
*/
function updateRampSelection(mouseEvent) {
if (mouseEvent != null && mouseEvent.which == 3) {
currentSelection.endIndex = getElementIndex(mouseEvent.target);
}
if (mouseEvent == null || mouseEvent.which == 3) {
let startCoords = getColourCoordinates(currentSelection.startIndex);
let endCoords = getColourCoordinates(currentSelection.endIndex);
let startIndex = currentSelection.startIndex;
let endIndex = currentSelection.endIndex;
if (currentSelection.startIndex > endIndex) {
let tmp = startIndex;
startIndex = endIndex;
endIndex = tmp;
tmp = startCoords;
startCoords = endCoords;
endCoords = tmp;
}
clearBorders();
for (let i=startIndex; i<=endIndex; i++) {
let currentSquare = coloursList.children[i];
let currentCoords = getColourCoordinates(i);
let borderStyle = "3px solid white";
let bordersToSet = [];
// Deciding which borders to use to make the outline
if (i == 0 || i == startIndex) {
bordersToSet.push("border-left");
}
if (currentCoords[1] == startCoords[1] || ((currentCoords[1] == startCoords[1] + 1)) && currentCoords[0] < startCoords[0]) {
bordersToSet.push("border-top");
}
if (currentCoords[1] == endCoords[1] || ((currentCoords[1] == endCoords[1] - 1)) && currentCoords[0] > endCoords[0]) {
bordersToSet.push("border-bottom");
}
if ((i == coloursList.childElementCount - 1) || (currentCoords[0] == Math.floor(blockData.blockWidth / blockData.squareSize) - 1)
|| i == endIndex) {
bordersToSet.push("border-right");
}
if (bordersToSet != []) {
currentSquare.style["box-sizing"] = "border-box";
for (let i=0; i<bordersToSet.length; i++) {
currentSquare.style[bordersToSet[i]] = borderStyle;
}
}
}
}
}
/** Removes all the borders from all the squares. The borders are cleared only for the
* current selection, so every border that is not white is kept.
*
*/
function clearBorders() {
for (let i=0; i<coloursList.childElementCount; i++) {
coloursList.children[i].style["border-top"] = "none";
coloursList.children[i].style["border-left"] = "none";
coloursList.children[i].style["border-right"] = "none";
coloursList.children[i].style["border-bottom"] = "none";
}
}
/** Ends the current selection, opens the ramp menu
*
* @param {*} mouseEvent
*/
function endRampSelection(mouseEvent) {
let col;
if (currentSelection.startCoords.length == 0) {
currentSelection.endIndex = getElementIndex(mouseEvent.target);
currentSelection.startIndex = currentSelection.endIndex;
currentSelection.startCoords = getColourCoordinates(currentSelection.startIndex);
}
// I'm not selecting a ramp anymore
isRampSelecting = false;
// Setting the end coordinates
currentSelection.endCoords = getColourCoordinates(getElementIndex(mouseEvent.target));
// Setting the colour in the colour picker
col = cssToHex(coloursList.children[currentSelection.startIndex].style.backgroundColor);
updatePickerByHex(col);
updateSlidersByHex(col);
updateMiniPickerColour();
updateRampSelection();
currentSelection.startCoords = [];
}
function closeAllSubmenus() {
let menus = document.getElementsByClassName("pb-submenu");
for (let i=0; i<menus.length; i++) {
menus[i].style.display = "none";
}
}
/** Updates the current data about the size of the palette list (height, width and square size).
* It also updates the outline after doing so.
*
*/
function updateSizeData() {
blockData.blockHeight = coloursList.parentElement.clientHeight;
blockData.blockWidth = coloursList.parentElement.clientWidth;
blockData.squareSize = coloursList.children[0].clientWidth;
updateRampSelection();
}
/** Gets the colour coordinates relative to the colour list seen as a matrix. Coordinates
* start from the top left angle.
*
* @param {*} index The index of the colour in the list seen as a linear array
*/
function getColourCoordinates(index) {
let yIndex = Math.floor(index / Math.floor(blockData.blockWidth / blockData.squareSize));
let xIndex = Math.floor(index % Math.floor(blockData.blockWidth / blockData.squareSize));
return [xIndex, yIndex];
}
/** Returns the index of the element in the colour list
*
* @param {*} element The element of which we need to get the index
*/
function getElementIndex(element) {
for (let i=0; i<coloursList.childElementCount; i++) {
if (element == coloursList.children[i]) {
return i;
}
}
}
/** Resizes the squares depending on the scroll amount (only resizes if the user is
* also holding alt)
*
* @param {*} mouseEvent
*/
function resizeSquares(mouseEvent) {
let amount = mouseEvent.deltaY > 0 ? -5 : 5;
currentSquareSize += amount;
for (let i=0; i<coloursList.childElementCount; i++) {
let currLi = coloursList.children[i];
currLi.style["box-sizing"] = "content-box";
currLi.style.width = currLi.clientWidth + amount + "px";
currLi.style.height = currLi.clientHeight + amount + "px";
}
updateSizeData();
}
/** Converts a CSS colour eg rgb(x,y,z) to a hex string
*
* @param {*} rgb
*/
function cssToHex(rgb) {
rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
function hex(x) {
return ("0" + parseInt(x).toString(16)).slice(-2);
}
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
}

View File

@ -1,6 +1,6 @@
/**utilities**/ /**utilities**/
//=include lib/cookies.js //=include lib/cookies.js
//=include _jscolor.js //=include lib/jscolor.js
//=include data/variables.js //=include data/variables.js
//=include lib/sortable.js //=include lib/sortable.js
//=include Util.js //=include Util.js
@ -46,8 +46,8 @@
/**functions**/ /**functions**/
//=include _resizeCanvas.js //=include _resizeCanvas.js
//=include _resizeSprite.js //=include _resizeSprite.js
//=include _colorPicker.js //=include ColorPicker.js
//=include _paletteBlock.js //=include PaletteBlock.js
//=include SplashPage.js //=include SplashPage.js
/**buttons**/ /**buttons**/
@ -59,7 +59,7 @@
//=include Input.js //=include Input.js
/**feature toggles**/ /**feature toggles**/
//=include _featureToggles.js //=include FeatureToggles.js
// Controls execution of this preset module // Controls execution of this preset module
PresetModule.instrumentPresetMenu(); PresetModule.instrumentPresetMenu();

View File

@ -60,15 +60,15 @@
<div id = "palette-container"> <div id = "palette-container">
<ul id = "palette-list"> <ul id = "palette-list">
<li style = "background-color:rgb(255,0,0);width:40px;height:40px;" onmousedown="startRampSelection(event)" <li style = "background-color:rgb(255,0,0);width:40px;height:40px;" onmousedown="PaletteBlock.startRampSelection(event)"
onmousemove="updateRampSelection(event)" onmouseup="endRampSelection(event)"></li> onmousemove="PaletteBlock.updateRampSelection(event)" onmouseup="PaletteBlock.endRampSelection(event)"></li>
<li style = "background-color:rgb(0,255,0);width:40px;height:40px;"onmousedown="startRampSelection(event)" <li style = "background-color:rgb(0,255,0);width:40px;height:40px;"onmousedown="PaletteBlock.startRampSelection(event)"
onmousemove="updateRampSelection(event)" onmouseup="endRampSelection(event)"></li> onmousemove="PaletteBlock.updateRampSelection(event)" onmouseup="PaletteBlock.endRampSelection(event)"></li>
</ul> </ul>
</div> </div>
<div id="pb-options"> <div id="pb-options">
<button title="Add colours to palette" onclick="pbAddColours()">Add colours</button> <button title="Add colours to palette" id="pb-addcolours">Add colours</button>
<button title="Remove colours from palette" onclick="pbRemoveColours()">Remove colours</button> <button title="Remove colours from palette" id="pb-removecolours">Remove colours</button>
</div> </div>
</div> </div>