chunked diff start, optimised blocks internal storage space

This commit is contained in:
Hugh Bord 2021-12-17 13:53:07 +10:00
parent 8bf4ecdccf
commit 044f92d104
4 changed files with 197 additions and 157 deletions

View File

@ -28,7 +28,7 @@ A most latest production build to use is available at https://asciibird.jewbird.
* Show and hide layers
* Change layer order
* Double click to rename layer
* Copy and paste between tabs
* Copy and paste ASCII blocks between tabs
* Remembers state on refresh and when the browser loads, can also export the state to a file and load elsewhere.
* So you never lose your ascii art!
* Saves layers, brushes data also to same file
@ -65,23 +65,20 @@ A most latest production build to use is available at https://asciibird.jewbird.
ASCIIBIRD is mostly usable. There are some bugs however to note at the moment. Refreshing the page seems to fix most strange things.
* Importer could be re-written with regex
* That inverted black / white bug when exporting, happens if fg or bg is null
* That inverted black / white bug when exporting, happens if fg or bg is null (this maybe fixed with the recent work)
* Having more than a few layers depending on ascii size will slow things down, until the `fillNullBlocks` is refactored.
* CHUNKED DIFF ENGINE
* Redo ctrl Y is COOKED (not working) - redo the undo and redo with this new system
## Focusing on Now / Roadmap
* We can store chunked editing differences! Just need to fix up undo and redo with this new system.
* Warning to the user for width and clipping, on export
* Optimise the export code to use less chars
* More Context Menus (right click menu)
* Brushes Canvas right click
* ASCII right click
* Review encodings check on file import - UTF8 vs Latin something
## AFTER the above stuff is done
* Warning to the user for width and clipping
* Optimise the export code to use less chars
# Keyboard Shortcuts
## ASCII Editing

View File

@ -192,9 +192,9 @@ export const toolbarIcons = [{
];
export const emptyBlock = {
bg: null,
fg: null,
char: null,
// bg: null,
// fg: null,
// char: null,
};
export const create2DArray = (rows) => {
@ -241,10 +241,8 @@ export const parseMircAscii = async (content, title) => {
// This will end up in the asciibirdMeta
const finalAscii = {
title: filename,
// key: store.getters.nextTabValue,
// blockWidth: blockWidth * store.getters.blockSizeMultiplier,
// blockHeight: blockHeight * store.getters.blockSizeMultiplier,
blocks: [{
current: [],
layers: [{
label: filename,
visible: true,
data: create2DArray(asciiImport.split('\n').length),
@ -252,7 +250,8 @@ export const parseMircAscii = async (content, title) => {
height: asciiImport.split('\n').length,
}],
history: [],
redo: [],
// redo: [],
historyIndex: 0,
imageOverlay: {
url: null,
opacity: 95,
@ -317,13 +316,13 @@ export const parseMircAscii = async (content, title) => {
asciiY++;
// Calculate widths mirc asciis vs plain text
if (!finalAscii.blocks[0].width && widthOfColCodes > 0) {
finalAscii.blocks[0].width = maxWidthLoop - widthOfColCodes;
if (!finalAscii.layers[0].width && widthOfColCodes > 0) {
finalAscii.layers[0].width = maxWidthLoop - widthOfColCodes;
}
if (!finalAscii.blocks[0].width && widthOfColCodes === 0) {
if (!finalAscii.layers[0].width && widthOfColCodes === 0) {
// Plain text
finalAscii.blocks[0].width = maxWidthFound;
finalAscii.layers[0].width = maxWidthFound;
}
// Resets the X value
@ -425,22 +424,26 @@ export const parseMircAscii = async (content, title) => {
asciiStringArray.shift();
asciiX++;
finalAscii.blocks[0].data[asciiY][asciiX - 1] = {
finalAscii.layers[0].data[asciiY][asciiX - 1] = {
...curBlock,
};
break;
} // End Switch
} // End loop charPos
finalAscii.blocks = [...fillNullBlocks(finalAscii.blocks[0].height, finalAscii.blocks[0]
.width, finalAscii.blocks)];
// First layer data generation
finalAscii.layers = [...fillNullBlocks(finalAscii.layers[0].height, finalAscii.layers[0]
.width, finalAscii.layers)];
// Store the ASCII and ensure we have no null blocks
finalAscii.blocks = LZString.compressToUTF16(
JSON.stringify(finalAscii.blocks),
finalAscii.layers = LZString.compressToUTF16(
JSON.stringify(finalAscii.layers),
);
// What we will see on the canvas
finalAscii.current = LZString.compressToUTF16(JSON.stringify(finalAscii.layers[0].data));
// We need to also store in the first undo history the original state
finalAscii.history.push(finalAscii.blocks);
// finalAscii.history.push(finalAscii.layers);
// Save ASCII to storage
store.commit('newAsciibirdMeta', finalAscii);
@ -453,10 +456,12 @@ export const createNewAscii = (forms) => {
const newAscii = {
title: forms.createAscii.title,
history: [],
redo: [],
// redo: [],
historyIndex: 0,
x: 247, // the dragable ascii canvas x
y: 24, // the dragable ascii canvas y
blocks: [{
current: [],
layers: [{
label: forms.createAscii.title,
visible: true,
data: create2DArray(forms.createAscii.height),
@ -477,16 +482,17 @@ export const createNewAscii = (forms) => {
};
// Push all the default ASCII blocks
for (let x = 0; x < newAscii.blocks[0].width; x++) {
for (let y = 0; y < newAscii.blocks[0].height; y++) {
newAscii.blocks[0].data[y].push({
for (let x = 0; x < newAscii.layers[0].width; x++) {
for (let y = 0; y < newAscii.layers[0].height; y++) {
newAscii.layers[0].data[y].push({
...emptyBlock,
});
}
}
newAscii.blocks = LZString.compressToUTF16(JSON.stringify(newAscii.blocks));
newAscii.history.push(newAscii.blocks);
newAscii.layers = LZString.compressToUTF16(JSON.stringify(newAscii.layers));
newAscii.current = LZString.compressToUTF16(JSON.stringify(newAscii.layers[0].data));
// newAscii.history.push(newAscii.layers);
store.commit('newAsciibirdMeta', newAscii);
store.commit('closeModal', 'new-ascii');
@ -528,19 +534,19 @@ export const exportMirc = () => {
};
const zeroPad = (num, places) => String(num).padStart(places, '0');
if (curBlock.fg === null && curBlock.bg === null) {
if (curBlock.fg === undefined && curBlock.bg === undefined) {
output.push('\u0003');
} else {
if (curBlock.bg === null && curBlock.fg !== null) {
if (curBlock.bg === undefined && curBlock.fg !== undefined) {
pushString = `\u0003\u0003${zeroPad(curBlock.fg, 2)}`;
}
if (curBlock.bg !== null && curBlock.fg !== null) {
if (curBlock.bg !== undefined && curBlock.fg !== undefined) {
pushString = `\u0003${zeroPad(curBlock.fg, 2)},${zeroPad(curBlock.bg, 2)}`;
}
if (curBlock.bg !== null && curBlock.fg === null) {
if (curBlock.bg !== undefined && curBlock.fg === undefined) {
pushString = `\u000300,${zeroPad(curBlock.bg, 2)}`;
}
@ -733,6 +739,7 @@ export const filterNullBlocks = function (blocks) {
newBlocks[y] = (blocks[y].filter(function (item) {
return item !== null
}))
}
return newBlocks
@ -786,18 +793,18 @@ export const mergeLayers = function (blocks = null) {
store.getters.currentAsciiLayers[z] &&
store.getters.currentAsciiLayers[z].data &&
store.getters.currentAsciiLayers[z].data[y] &&
store.getters.currentAsciiLayers[z].data[y][x]
store.getters.currentAsciiLayers[z].data[y][x]
) {
if (curBlock.char === null) {
if (curBlock.char === undefined) {
curBlock.char = store.getters.currentAsciiLayers[z].data[y][x].char;
}
if (curBlock.fg === null) {
if (curBlock.fg === undefined) {
curBlock.fg = store.getters.currentAsciiLayers[z].data[y][x].fg;
}
if (curBlock.bg === null) {
if (curBlock.bg === undefined) {
curBlock.bg = store.getters.currentAsciiLayers[z].data[y][x].bg;
}

View File

@ -9,7 +9,8 @@ import {
getBlocksWidth,
create2DArray,
emptyBlock,
mergeLayers
mergeLayers,
exportMirc
} from "../ascii";
Vue.use(Vuex);
@ -20,6 +21,7 @@ const vuexLocal = new VuexPersistence({
export default new Vuex.Store({
state: {
ver: 1,
modalState: {
newAscii: false,
editAscii: false,
@ -111,7 +113,9 @@ export default new Vuex.Store({
Object.assign(state, payload);
},
updateOptions(state, payload) {
state.options = { ... payload};
state.options = {
...payload
};
},
changeTab(state, payload) {
state.tab = payload;
@ -148,7 +152,7 @@ export default new Vuex.Store({
state.layersLibraryState = payload;
},
changeAsciiWidthHeight(state, payload) {
state.asciibirdMeta[state.tab].blocks = LZString.compressToUTF16(JSON.stringify(
state.asciibirdMeta[state.tab].layers = LZString.compressToUTF16(JSON.stringify(
payload.layers));
},
changeAsciiCanvasState(state, payload) {
@ -208,17 +212,23 @@ export default new Vuex.Store({
state.asciibirdMeta[state.tab].history.shift()
}
if (!skipUndo) {
state.asciibirdMeta[state.tab].history.push(state.asciibirdMeta[state.tab].blocks);
// if (!skipUndo) {
// state.asciibirdMeta[state.tab].history.push(state.asciibirdMeta[state.tab].layers);
// }
let tempLayers = JSON.parse(LZString.decompressFromUTF16(state.asciibirdMeta[state.tab].layers))
tempLayers[state.asciibirdMeta[state.tab].selectedLayer].data = payload.blocks
state.asciibirdMeta[state.tab].layers = LZString.compressToUTF16(JSON.stringify(
tempLayers));
state.asciibirdMeta[state.tab].current = LZString.compressToUTF16(JSON.stringify(mergeLayers()));
if (payload.diff && payload.diff.new && payload.diff.new.length) {
state.asciibirdMeta[state.tab].history.push(LZString.compressToUTF16(JSON.stringify(payload.diff)))
}
let tempLayers = JSON.parse(LZString.decompressFromUTF16(state.asciibirdMeta[state.tab]
.blocks))
tempLayers[state.asciibirdMeta[state.tab].selectedLayer].data = payload
state.asciibirdMeta[state.tab].blocks = LZString.compressToUTF16(JSON.stringify(
tempLayers));
state.asciibirdMeta[state.tab].redo = [];
},
//
@ -226,7 +236,7 @@ export default new Vuex.Store({
//
addLayer(state) {
let tempLayers = JSON.parse(LZString.decompressFromUTF16(state.asciibirdMeta[state.tab]
.blocks))
.layers))
let width = tempLayers[0].width;
let height = tempLayers[0].height;
@ -250,12 +260,12 @@ export default new Vuex.Store({
height: height
})
state.asciibirdMeta[state.tab].blocks = LZString.compressToUTF16(JSON.stringify(
state.asciibirdMeta[state.tab].layers = LZString.compressToUTF16(JSON.stringify(
tempLayers));
},
mergeAllLayers(state) {
let tempLayers = JSON.parse(LZString.decompressFromUTF16(state.asciibirdMeta[state.tab]
.blocks))
.layers))
let width = tempLayers[0].width;
let height = tempLayers[0].height;
@ -270,7 +280,7 @@ export default new Vuex.Store({
}];
state.asciibirdMeta[state.tab].selectedLayer = 0;
state.asciibirdMeta[state.tab].blocks = LZString.compressToUTF16(JSON.stringify(
state.asciibirdMeta[state.tab].layers = LZString.compressToUTF16(JSON.stringify(
mergedLayers));
},
changeLayer(state, payload) {
@ -278,16 +288,16 @@ export default new Vuex.Store({
},
toggleLayer(state, payload) {
let tempLayers = JSON.parse(LZString.decompressFromUTF16(state.asciibirdMeta[state.tab]
.blocks))
.layers))
tempLayers[payload].visible = !tempLayers[payload].visible
state.asciibirdMeta[state.tab].blocks = LZString.compressToUTF16(JSON.stringify(
state.asciibirdMeta[state.tab].layers = LZString.compressToUTF16(JSON.stringify(
tempLayers));
},
removeLayer(state, payload) {
let tempLayers = JSON.parse(LZString.decompressFromUTF16(state.asciibirdMeta[state.tab]
.blocks))
.layers))
if (tempLayers.length > 1) {
tempLayers.splice(payload, 1)
@ -301,14 +311,14 @@ export default new Vuex.Store({
state.asciibirdMeta[state.tab].selectedLayer = 0
}
state.asciibirdMeta[state.tab].blocks = LZString.compressToUTF16(JSON.stringify(
state.asciibirdMeta[state.tab].layers = LZString.compressToUTF16(JSON.stringify(
tempLayers));
}
},
downLayer(state, payload) {
let tempLayers = JSON.parse(LZString.decompressFromUTF16(state.asciibirdMeta[state.tab]
.blocks))
.layers))
if (tempLayers[payload + 1]) {
let swap1 = tempLayers[payload + 1];
@ -317,14 +327,14 @@ export default new Vuex.Store({
tempLayers[payload + 1] = swap
tempLayers[payload] = swap1
state.asciibirdMeta[state.tab].blocks = LZString.compressToUTF16(JSON.stringify(
state.asciibirdMeta[state.tab].layers = LZString.compressToUTF16(JSON.stringify(
tempLayers));
}
},
upLayer(state, payload) {
let tempLayers = JSON.parse(LZString.decompressFromUTF16(state.asciibirdMeta[state.tab]
.blocks))
.layers))
if (tempLayers[payload - 1]) {
let swap1 = tempLayers[payload - 1];
@ -333,18 +343,18 @@ export default new Vuex.Store({
tempLayers[payload - 1] = swap
tempLayers[payload] = swap1
state.asciibirdMeta[state.tab].blocks = LZString.compressToUTF16(JSON.stringify(
state.asciibirdMeta[state.tab].layers = LZString.compressToUTF16(JSON.stringify(
tempLayers));
}
},
updateLayerName(state, payload) {
let tempLayers = JSON.parse(LZString.decompressFromUTF16(state.asciibirdMeta[state.tab]
.blocks))
.layers))
if (tempLayers[payload.key]) {
tempLayers[payload.key].label = payload.label;
state.asciibirdMeta[state.tab].blocks = LZString.compressToUTF16(JSON.stringify(
state.asciibirdMeta[state.tab].layers = LZString.compressToUTF16(JSON.stringify(
tempLayers));
}
@ -357,19 +367,19 @@ export default new Vuex.Store({
// BLOCKS
undoBlocks(state) {
if (state.asciibirdMeta[state.tab].history.length > 1) {
// if (state.asciibirdMeta[state.tab].history.length > 1) {
state.asciibirdMeta[state.tab].blocks = state.asciibirdMeta[state.tab].history.pop();
state.asciibirdMeta[state.tab].redo.push(state.asciibirdMeta[state.tab].blocks);
// state.asciibirdMeta[state.tab].layers = state.asciibirdMeta[state.tab].history.pop();
// state.asciibirdMeta[state.tab].redo.push(state.asciibirdMeta[state.tab].layers);
}
// }
},
redoBlocks(state) {
if (state.asciibirdMeta[state.tab].redo.length) {
const next = state.asciibirdMeta[state.tab].redo.pop();
state.asciibirdMeta[state.tab].blocks = next;
state.asciibirdMeta[state.tab].history.push(next);
}
// if (state.asciibirdMeta[state.tab].redo.length) {
// const next = state.asciibirdMeta[state.tab].redo.pop();
// state.asciibirdMeta[state.tab].layers = next;
// state.asciibirdMeta[state.tab].history.push(next);
// }
},
//
@ -471,7 +481,7 @@ export default new Vuex.Store({
},
// Modals / Tabs
openModal(state, type) {
switch (type) {
case 'new-ascii':
state.modalState.newAscii = true;
@ -500,7 +510,7 @@ export default new Vuex.Store({
}
},
closeModal(state, type) {
switch (type) {
case 'new-ascii':
state.modalState.newAscii = false;
@ -560,20 +570,19 @@ export default new Vuex.Store({
currentTab: (state) => state.tab,
currentAscii: (state) => state.asciibirdMeta[state.tab] ?? false,
currentAsciiBlocks: (state) => {
let blocks = JSON.parse(LZString.decompressFromUTF16(state.asciibirdMeta[
state.tab].blocks)) || [];
let blocks = JSON.parse(LZString.decompressFromUTF16(state.asciibirdMeta[state.tab].current)) || [];
return blocks[state.asciibirdMeta[state.tab].selectedLayer].data || []
return blocks;
},
currentAsciiLayers: (state) => {
let blocks = JSON.parse(LZString.decompressFromUTF16(state.asciibirdMeta[
state.tab].blocks));
let layers = JSON.parse(LZString.decompressFromUTF16(state.asciibirdMeta[
state.tab].layers));
return blocks
return layers
},
currentAsciiLayersWidthHeight: (state) => {
let blocks = JSON.parse(LZString.decompressFromUTF16(state.asciibirdMeta[
state.tab].blocks));
state.tab].layers));
return {
width: blocks[0].width,

View File

@ -62,6 +62,7 @@ import {
getBlocksWidth,
checkVisible,
mergeLayers,
cyrb53,
} from "../ascii";
export default {
@ -79,11 +80,9 @@ export default {
var _this = this;
hotkeys("*", "editor", function (event, handler) {
if (_this.isBrushing || _this.isErasing) {
event.preventDefault();
switch (event.key) {
case "ArrowUp":
_this.y--;
@ -113,10 +112,10 @@ export default {
_this.canTool = true;
_this.isBrushing ? _this.drawBrush() : _this.eraser();
_this.canTool = false;
_this.$store.commit(
"updateAsciiBlocks",
_this.currentAsciiLayerBlocks
);
_this.$store.commit("updateAsciiBlocks", {
current: _this.currentAsciiLayerBlocks,
diff: {},
});
break;
}
@ -158,8 +157,8 @@ export default {
selectedBlocks: [],
tempBlocks: {
new: [],
old: [],
new: [],
},
isUsingKeyboard: false,
@ -332,6 +331,9 @@ export default {
canvasTransparent() {
return this.imageOverlay.visible ? "opacity: 0.6;" : "opacity: 1;";
},
currentAsciiBlocks() {
return this.$store.getters.currentAsciiBlocks;
},
},
watch: {
currentAscii(val, old) {
@ -394,7 +396,10 @@ export default {
// Save text to store when finished
isTextEditing(val, old) {
if (val !== old && val === false) {
this.$store.commit("updateAsciiBlocks", this.currentAsciiLayerBlocks);
this.$store.commit("updateAsciiBlocks", {
blocks: this.currentAsciiLayerBlocks,
diff: {},
});
}
},
textEditing(val, old) {
@ -416,48 +421,6 @@ export default {
yOffset() {
this.delayRedrawCanvas();
},
// asciiBlockAtXy(val, old) {
// Keep track of what blocks have changed
// if (this.canTool) {
// this.tempBlocks.new = this.tempBlocks.new.filter(obj => obj.x !== this.x);
// this.tempBlocks.new = this.tempBlocks.new.filter(obj => obj.y !== this.y);
// this.tempBlocks.new.push({
// x: this.x,
// y: this.y,
// block: {... val } })
// this.tempBlocks.old = this.tempBlocks.old.filter(obj => obj.x !== this.x);
// this.tempBlocks.old = this.tempBlocks.old.filter(obj => obj.y !== this.y);
// this.tempBlocks.old.push({
// x: this.x,
// y: this.y,
// block: {... old } })
// }
// },
// canvasxy(val,old) {
// if ((this.isBrushing || this.isErasing) && isUsingKeyboard) {
// this.x = val.x;
// this.y = val.y;
// let e = {offsetX: (this.x), offsetY: (this.y)}
// this.canvasMouseMove(e)
// }
// },
// brush(val, old) {
// console.log({triggerbrush: val})
// if (val !== old) {
// this.canvasMouseDown()
// this.canvasMouseUp()
// }
// }
},
methods: {
warnInvisibleLayer() {
@ -573,13 +536,13 @@ export default {
curBlock = { ...mergeLayers[y][x] };
if (curBlock.bg !== null) {
if (curBlock.bg !== undefined) {
this.ctx.fillStyle = this.mircColours[curBlock.bg];
this.ctx.fillRect(canvasX, canvasY, blockWidth, blockHeight);
}
if (curBlock.char) {
if (curBlock.fg !== null) {
if (curBlock.char !== undefined) {
if (curBlock.fg !== undefined) {
this.ctx.fillStyle = this.mircColours[curBlock.fg];
} else {
this.ctx.fillStyle = "#000000";
@ -635,13 +598,29 @@ export default {
case "brush":
this.canTool = false;
this.$store.commit("updateAsciiBlocks", this.currentAsciiLayerBlocks);
this.tempBlocks.old = this.tempBlocks.old.flat();
this.tempBlocks.new = this.tempBlocks.new.flat();
console.log(JSON.stringify(this.tempBlocks));
this.$store.commit("updateAsciiBlocks", {
blocks: this.currentAsciiLayerBlocks,
diff: { ...this.tempBlocks },
});
this.tempBlocks = {
new: [],
old: [],
};
break;
case "eraser":
this.canTool = false;
this.$store.commit("updateAsciiBlocks", this.currentAsciiLayerBlocks);
this.$store.commit("updateAsciiBlocks", {
blocks: this.currentAsciiLayerBlocks,
diff: {},
});
break;
case "fill-eraser":
@ -659,15 +638,23 @@ export default {
this.textEditing.startY = this.y;
break;
}
// this.tempBlocks = {
// new: [],
// old: [],
// }
},
canvasMouseDown() {
if (this.isDefault) return;
if (
cyrb53(JSON.stringify(this.asciiBlockAtXy)) ===
cyrb53(
JSON.stringify({
bg: this.currentBg,
fg: this.currentFg,
char: this.currentChar,
})
)
) {
return;
}
if (this.asciiBlockAtXy && this.currentTool) {
const targetBlock = this.asciiBlockAtXy;
@ -681,19 +668,19 @@ export default {
case "fill":
this.fill();
this.$store.commit(
"updateAsciiBlocks",
this.currentAsciiLayerBlocks
);
this.$store.commit("updateAsciiBlocks", {
blocks: this.currentAsciiLayerBlocks,
diff: {},
});
this.canTool = false;
break;
case "fill-eraser":
this.fill(true);
this.$store.commit(
"updateAsciiBlocks",
this.currentAsciiLayerBlocks
);
this.$store.commit("updateAsciiBlocks", {
blocks: this.currentAsciiLayerBlocks,
diff: {},
});
this.canTool = false;
break;
@ -1214,6 +1201,8 @@ export default {
][target] = brushBlock[target];
}
}
return brushBlock;
},
//
// drawBrush
@ -1266,6 +1255,25 @@ export default {
this.currentAsciiLayerBlocks[arrayY] &&
this.currentAsciiLayerBlocks[arrayY][arrayX]
) {
if (this.canTool) {
if (!this.tempBlocks.old[arrayY]) {
this.tempBlocks.old[arrayY] = [];
}
if (!this.tempBlocks.old[arrayY][arrayX]) {
this.tempBlocks.old[arrayY][arrayX] = [];
}
this.tempBlocks.old[arrayY][arrayX] = {
l: this.selectedLayerIndex,
d: {
x: arrayX,
y: arrayY,
b: { ...this.currentAsciiLayerBlocks[arrayY][arrayX] },
},
};
}
if (!plain) {
if (this.canBg) {
this.drawBrushBlocks(brushX, brushY, brushBlock, "bg");
@ -1279,6 +1287,25 @@ export default {
} else {
this.drawBrushBlocks(brushX, brushY, brushBlock, null, true);
}
if (this.canTool) {
if (!this.tempBlocks.new[arrayY]) {
this.tempBlocks.new[arrayY] = [];
}
if (!this.tempBlocks.new[arrayY][arrayX]) {
this.tempBlocks.new[arrayY][arrayX] = [];
}
this.tempBlocks.new[arrayY][arrayX] = {
l: this.selectedLayerIndex,
d: {
x: arrayX,
y: arrayY,
b: { ... this.currentAsciiLayerBlocks[arrayY][arrayX] },
},
};
}
}
}
}
@ -1410,7 +1437,7 @@ export default {
newColor.bg = this.currentBg;
current.bg = this.asciiBlockAtXy.bg;
}
//
//
if (this.canFg) {
newColor.fg = this.currentFg;
current.fg = this.asciiBlockAtXy.fg;