Readme update, removed font awesome (we now have material icons), front end css review
This commit is contained in:
parent
28479ccd80
commit
90010a8831
52
README.md
52
README.md
|
@ -28,29 +28,34 @@ 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 ASCII blocks between tabs
|
||||
* Context menu for layers
|
||||
* Copy and paste ASCII blocks between tabs with the select tool
|
||||
* 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
|
||||
* Can import from clipboard, load from irc.watch/ascii, load from file
|
||||
* Can export mirc ascii to clipboard, file or HTTP POST
|
||||
* 99 Colour support, swap colours with button click
|
||||
* 99 Colour support
|
||||
* Swap fg and bg colours with button click or alt + k
|
||||
* Mirror X and Y
|
||||
* Grid mode with alt + g
|
||||
* Undo and redo with ctrl + z and ctrl + y, undos are set to a limit of 50 at the moment.
|
||||
* Undo and redo with ctrl + z and ctrl + y, undos are set to a limit of 200 at the moment.
|
||||
* Fg, Bg and Char boxes to filter when using certain tools
|
||||
* For example filling with Char unchecked will ignore characters when filling
|
||||
* Image overlay to trace images
|
||||
* If you want to remove the background but keep the text, unchek FG and Char and eraser the bg only.
|
||||
* Image overlay to trace images
|
||||
* Accepts URLs only at the moment
|
||||
* Can adjust the size and properties
|
||||
* Toolbar containing
|
||||
* Select
|
||||
* Text mode
|
||||
* Select, to copy and paste blocks as brushes
|
||||
* Text mode, with arrow key support
|
||||
* Fill background blocks
|
||||
* Brush mode, can be controlled with keyboard and mouse
|
||||
* Block picker (grab fg, bg and char of a block)
|
||||
* Eraser - remove blocks, can be controlled with keyboard and mouse
|
||||
* Fill Eraser - Fill remove blocks by bg, fg or char
|
||||
* Brush Library and History
|
||||
* Make circle, square and cross brushes by sizes
|
||||
* Make circle, square, cross and other brushes by sizes
|
||||
* Brush history, can save or re-use old brushes
|
||||
* Library - Save most used brushes to library
|
||||
* Brush history is set to a limit of 50
|
||||
|
@ -59,24 +64,27 @@ A most latest production build to use is available at https://asciibird.jewbird.
|
|||
* Clicking updates block
|
||||
* Right clicking removes block
|
||||
* Hovering outside brush area will save brush to history
|
||||
* Context menu available on all brushes preview areas
|
||||
* Export any brush to PNG, mIRC clipboard or file by right clicking the brush preview
|
||||
# Roadmap and Bug To Fixes
|
||||
|
||||
## Noted Bugs to Fix
|
||||
|
||||
ASCIIBIRD is mostly usable. There are some bugs however to note at the moment. Refreshing the page seems to fix most strange things.
|
||||
|
||||
* Layers manipulation and undo / redo is buggy
|
||||
|
||||
## Focusing on Now
|
||||
|
||||
* Importer could be re-written with regex
|
||||
* More Context Menus (right click menu)
|
||||
* Brushes Canvas right click
|
||||
* ASCII right click
|
||||
|
||||
## Roadmap
|
||||
## Features to Add
|
||||
|
||||
* Layers undo and redo could be implemented, at the moment there isn't any.
|
||||
* Warning on mirc export if ascii exceeds IRCs 512 per chat line limit.
|
||||
* Review encodings check on file import - UTF8 vs Latin something
|
||||
* More Context Menus (right click menu)
|
||||
* ASCII right click
|
||||
|
||||
## Bugs to fix
|
||||
|
||||
* If you apply an empty block from a brush, it will remove the char when it is supposed to leave the block alone.
|
||||
* A bigger circle brush is a good example for this one.
|
||||
* Can't type in dialogs
|
||||
* Still cannot change width in edit ascii modal
|
||||
## Mobile / Touch Screen support
|
||||
|
||||
Doesn't exist at the moment. While the underlying functions and code is compatible with mobile browsers from *babel*, the touch canvas events and text will need to be reviewed to work better with touch screens. For example while you can brush once, you cannot move the brush around.
|
||||
|
||||
# Keyboard Shortcuts
|
||||
|
||||
|
@ -126,7 +134,7 @@ The toolbar keyboard shorts are used with the ALT key.
|
|||
### Eraser Mode Only
|
||||
|
||||
* Four arrow keys control text cursor
|
||||
* Space - apply brush
|
||||
* Space - apply eraser
|
||||
|
||||
### Brush Mode Only
|
||||
|
||||
|
|
|
@ -8,10 +8,6 @@
|
|||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
||||
"@fortawesome/free-regular-svg-icons": "^5.15.3",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.3",
|
||||
"@fortawesome/vue-fontawesome": "^2.0.2",
|
||||
"@tailwindcss/forms": "^0.3.3",
|
||||
"@tailwindcss/postcss7-compat": "^2.2.7",
|
||||
"autoprefixer": "^9",
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
|
||||
<KeyboardShortcuts
|
||||
:selected-blocks="selectedBlocks"
|
||||
:text-editing="textEditing"
|
||||
:selecting="selecting"
|
||||
:is-showing-dialog="isShowingDialog"
|
||||
@updatecanvas="updatecanvas"
|
||||
|
@ -33,7 +32,7 @@
|
|||
<t-input v-model="lastPostURL" />
|
||||
</t-dialog>
|
||||
|
||||
<context-menu :display="showContextMenu" ref="menu" class="z-50">
|
||||
<context-menu ref="menu" class="z-50">
|
||||
<ul>
|
||||
<li
|
||||
@click="$store.commit('openModal', 'new-ascii')"
|
||||
|
@ -147,6 +146,7 @@
|
|||
/>
|
||||
|
||||
<Toolbar :y-offset="scrollOffset" />
|
||||
|
||||
<DebugPanel
|
||||
:canvas-x="canvasX"
|
||||
:canvas-y="canvasY"
|
||||
|
@ -154,7 +154,7 @@
|
|||
:y-offset="scrollOffset"
|
||||
/>
|
||||
|
||||
<BrushLibrary v-if="brushLibraryState.visible" :y-offset="scrollOffset" />
|
||||
<BrushLibrary v-show="brushLibraryState.visible" :y-offset="scrollOffset" />
|
||||
|
||||
<BrushPreview @inputtingbrush="inputtingbrush" :y-offset="scrollOffset" />
|
||||
|
||||
|
@ -184,9 +184,6 @@
|
|||
@mouseup.right="openContextMenu"
|
||||
@contextmenu.prevent
|
||||
>
|
||||
<!-- <h1 class="text-4xl">ASCIIBIRD</h1>
|
||||
<h3>Right click to start</h3> -->
|
||||
|
||||
<BrushCanvas :blocks="splashAscii" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
310
src/ascii.js
310
src/ascii.js
|
@ -151,43 +151,35 @@ export const charCodes = [' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*'
|
|||
// Toolbar icons
|
||||
export const toolbarIcons = [{
|
||||
name: 'default',
|
||||
icon: 'mouse-pointer',
|
||||
fa: 'fas',
|
||||
icon: 'edit_off',
|
||||
},
|
||||
{
|
||||
name: 'select',
|
||||
icon: 'square',
|
||||
fa: 'far',
|
||||
icon: 'photo_size_select_small',
|
||||
},
|
||||
{
|
||||
name: 'text',
|
||||
icon: 'font',
|
||||
fa: 'fas',
|
||||
icon: 'text_rotation_none',
|
||||
},
|
||||
{
|
||||
name: 'fill',
|
||||
icon: 'fill-drip',
|
||||
fa: 'fas',
|
||||
icon: 'format_color_fill',
|
||||
},
|
||||
{
|
||||
name: 'brush',
|
||||
icon: 'paint-brush',
|
||||
fa: 'fas',
|
||||
icon: 'brush',
|
||||
},
|
||||
{
|
||||
name: 'dropper',
|
||||
icon: 'eye-dropper',
|
||||
fa: 'fas',
|
||||
icon: 'colorize',
|
||||
},
|
||||
{
|
||||
name: 'eraser',
|
||||
icon: 'eraser',
|
||||
fa: 'fas',
|
||||
icon: 'remove_circle_outline',
|
||||
},
|
||||
{
|
||||
name: 'fill-eraser',
|
||||
icon: 'fill',
|
||||
fa: 'fas',
|
||||
icon: 'auto_fix_off',
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -218,7 +210,6 @@ export const maxUndoHistory = 500;
|
|||
export const tabLimit = 20;
|
||||
|
||||
export const parseMircAscii = async (contents, filename) => {
|
||||
const MIRC_MAX_COLOURS = mircColours99.length;
|
||||
const mIrcColourRegex = new RegExp(/\u0003(\d{0,2})?[,]?(\d{0,2})?/, 'gu');
|
||||
|
||||
// The current state of the Colours
|
||||
|
@ -239,7 +230,6 @@ export const parseMircAscii = async (contents, filename) => {
|
|||
|
||||
const finalAscii = {
|
||||
title: filename,
|
||||
current: [],
|
||||
layers: [{
|
||||
label: filename,
|
||||
visible: true,
|
||||
|
@ -248,7 +238,6 @@ export const parseMircAscii = async (contents, filename) => {
|
|||
height: contents.split('\n').length,
|
||||
}],
|
||||
history: [],
|
||||
// redo: [],
|
||||
historyIndex: 0,
|
||||
imageOverlay: {
|
||||
url: null,
|
||||
|
@ -315,14 +304,6 @@ export const parseMircAscii = async (contents, filename) => {
|
|||
}
|
||||
}
|
||||
|
||||
if (Number.isNaN(curBlock.bg)) {
|
||||
delete curBlock['bg'];
|
||||
}
|
||||
|
||||
if (Number.isNaN(curBlock.fg)) {
|
||||
delete curBlock['fg'];
|
||||
}
|
||||
|
||||
colourData.push({
|
||||
code: codeData,
|
||||
b: {
|
||||
|
@ -331,19 +312,19 @@ export const parseMircAscii = async (contents, filename) => {
|
|||
});
|
||||
}
|
||||
|
||||
}
|
||||
// Readjust the indexes
|
||||
let indexAdjustment = 0;
|
||||
|
||||
// Readjust the indexes
|
||||
let buildString = "";
|
||||
for (let index in colourData) {
|
||||
if (index === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let index in colourData) {
|
||||
if (index === 0) {
|
||||
continue;
|
||||
colourData[index].code.index = colourData[index].code.index - indexAdjustment;
|
||||
indexAdjustment = indexAdjustment + colourData[index].code[0].length;
|
||||
newData[colourData[index].code.index] = colourData[index].b;
|
||||
}
|
||||
|
||||
colourData[index].code.index = colourData[index].code.index - buildString.length;
|
||||
buildString = `${buildString}${colourData[index].code[0]}`
|
||||
newData[colourData[index].code.index] = colourData[index].b;
|
||||
}
|
||||
|
||||
|
||||
|
@ -354,12 +335,11 @@ export const parseMircAscii = async (contents, filename) => {
|
|||
// If there is a colour change present at this index
|
||||
// we will keep track of it
|
||||
if (!isPlainText && newData[i]) {
|
||||
// console.log(newData[i]);
|
||||
if (newData[i].bg !== undefined && newData[i].bg !== NaN) {
|
||||
if (newData[i].bg !== undefined) {
|
||||
curBlock.bg = newData[i].bg;
|
||||
}
|
||||
|
||||
if (newData[i].fg !== undefined && newData[i].fg !== NaN) {
|
||||
if (newData[i].fg !== undefined) {
|
||||
curBlock.fg = newData[i].fg;
|
||||
}
|
||||
|
||||
|
@ -373,8 +353,6 @@ export const parseMircAscii = async (contents, filename) => {
|
|||
|
||||
curBlock.char = char;
|
||||
|
||||
|
||||
|
||||
finalAscii.layers[0].data[y][i] = {
|
||||
...curBlock
|
||||
};
|
||||
|
@ -390,244 +368,6 @@ export const parseMircAscii = async (contents, filename) => {
|
|||
JSON.stringify(finalAscii.layers),
|
||||
);
|
||||
|
||||
// What we will see on the canvas
|
||||
finalAscii.current = LZString.compressToUTF16(JSON.stringify(finalAscii.layers[0].data));
|
||||
|
||||
// Save ASCII to storage
|
||||
store.commit('newAsciibirdMeta', finalAscii);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
export const parseMircAsciiV2 = async (content, title) => {
|
||||
const MIRC_MAX_COLOURS = mircColours99.length;
|
||||
|
||||
// The current state of the Colours
|
||||
let curBlock = {
|
||||
...emptyBlock,
|
||||
};
|
||||
|
||||
const contents = content;
|
||||
const filename = title;
|
||||
|
||||
// set asciiImport as the entire file contents as a string
|
||||
const asciiImport = contents
|
||||
.split('\u0003\u0003')
|
||||
.join('\u0003')
|
||||
.split('\u000F').join('')
|
||||
.split('\u0003\n')
|
||||
.join('\n')
|
||||
.split('\u0002\u0003')
|
||||
.join('\u0003');
|
||||
|
||||
// This will end up in the asciibirdMeta
|
||||
const finalAscii = {
|
||||
title: filename,
|
||||
current: [],
|
||||
layers: [{
|
||||
label: filename,
|
||||
visible: true,
|
||||
data: create2DArray(asciiImport.split('\n').length),
|
||||
width: false, // defined in: switch (curChar) case "\n":
|
||||
height: asciiImport.split('\n').length,
|
||||
}],
|
||||
history: [],
|
||||
// redo: [],
|
||||
historyIndex: 0,
|
||||
imageOverlay: {
|
||||
url: null,
|
||||
opacity: 95,
|
||||
position: 'centered',
|
||||
size: 100,
|
||||
repeatx: true,
|
||||
repeaty: true,
|
||||
visible: false,
|
||||
stretched: false,
|
||||
},
|
||||
x: blockWidth * 35, // the dragable ascii canvas x
|
||||
y: blockHeight * 2, // the dragable ascii canvas y
|
||||
selectedLayer: 0,
|
||||
};
|
||||
|
||||
// Turn the entire ascii string into an array
|
||||
let asciiStringArray = asciiImport.split('');
|
||||
const linesArray = asciiImport.split('\n');
|
||||
|
||||
// The proper X and Y value of the block inside the ASCII
|
||||
let asciiX = 0;
|
||||
let asciiY = 0;
|
||||
|
||||
// used to determine colours
|
||||
let colourChar1 = null;
|
||||
let colourChar2 = null;
|
||||
let parsedColour = null;
|
||||
|
||||
// This variable just counts the amount of colour and char codes to minus
|
||||
// to get the real width
|
||||
let widthOfColCodes = 0;
|
||||
|
||||
// Better for colourful asciis
|
||||
let maxWidthLoop = 0;
|
||||
|
||||
// Used before the loop, better for plain text
|
||||
let maxWidthFound = 0;
|
||||
|
||||
for (let i = 0; i < linesArray.length; i++) {
|
||||
if (linesArray[i].length > maxWidthFound) {
|
||||
maxWidthFound = linesArray[i].length;
|
||||
}
|
||||
}
|
||||
|
||||
while (asciiStringArray.length) {
|
||||
const curChar = asciiStringArray[0];
|
||||
|
||||
// Defining a small finite state machine
|
||||
// to detect the colour code
|
||||
switch (curChar) {
|
||||
case '\n':
|
||||
// Reset the colours here on a new line
|
||||
curBlock = {
|
||||
...emptyBlock,
|
||||
};
|
||||
|
||||
if (linesArray[asciiY] && linesArray[asciiY].length > maxWidthLoop) {
|
||||
maxWidthLoop = linesArray[asciiY].length;
|
||||
}
|
||||
|
||||
// the Y value of the ascii
|
||||
asciiY++;
|
||||
|
||||
// Calculate widths mirc asciis vs plain text
|
||||
if (!finalAscii.layers[0].width && widthOfColCodes > 0) {
|
||||
finalAscii.layers[0].width = maxWidthLoop - widthOfColCodes;
|
||||
}
|
||||
|
||||
if (!finalAscii.layers[0].width && widthOfColCodes === 0) {
|
||||
// Plain text
|
||||
finalAscii.layers[0].width = maxWidthFound;
|
||||
}
|
||||
|
||||
// Resets the X value
|
||||
asciiX = 0;
|
||||
|
||||
asciiStringArray.shift();
|
||||
widthOfColCodes = 0;
|
||||
break;
|
||||
|
||||
case '\u0003':
|
||||
// Remove the colour char
|
||||
asciiStringArray.shift();
|
||||
widthOfColCodes++;
|
||||
|
||||
// Attempt to work out bg
|
||||
colourChar1 = `${asciiStringArray[0]}`;
|
||||
colourChar2 = `${asciiStringArray[1]}`;
|
||||
parsedColour = parseInt(`${colourChar1}${colourChar2}`);
|
||||
|
||||
// Work out the 01, 02 double digit codes
|
||||
if (parseInt(colourChar1) === 0 && parseInt(colourChar2) >= 0) {
|
||||
asciiStringArray.shift();
|
||||
}
|
||||
|
||||
if (Number.isNaN(parsedColour)) {
|
||||
curBlock.bg = parseInt(colourChar1);
|
||||
widthOfColCodes += 1;
|
||||
asciiStringArray.shift();
|
||||
} else if (parsedColour <= MIRC_MAX_COLOURS && parsedColour >= 0) {
|
||||
curBlock.fg = parseInt(parsedColour);
|
||||
widthOfColCodes += parsedColour.toString().length;
|
||||
|
||||
asciiStringArray = asciiStringArray.slice(
|
||||
parsedColour.toString().length,
|
||||
asciiStringArray.length,
|
||||
);
|
||||
}
|
||||
|
||||
// No background colour
|
||||
if (asciiStringArray[0] !== ',') {
|
||||
break;
|
||||
} else {
|
||||
// Remove , from array
|
||||
widthOfColCodes += 1;
|
||||
asciiStringArray.shift();
|
||||
}
|
||||
|
||||
// Attempt to work out bg
|
||||
colourChar1 = `${asciiStringArray[0]}`;
|
||||
colourChar2 = `${asciiStringArray[1]}`;
|
||||
parsedColour = parseInt(`${colourChar1}${colourChar2}`);
|
||||
|
||||
if (
|
||||
!Number.isNaN(colourChar1) &&
|
||||
!Number.isNaN(colourChar2) &&
|
||||
parseInt(colourChar2) > parseInt(colourChar1) &&
|
||||
!Number.isNaN(parsedColour) &&
|
||||
parseInt(parsedColour) < 10
|
||||
) {
|
||||
parsedColour = parseInt(colourChar2);
|
||||
widthOfColCodes += 1;
|
||||
asciiStringArray.shift();
|
||||
}
|
||||
|
||||
if (
|
||||
parseInt(colourChar2) === parseInt(colourChar1) &&
|
||||
parseInt(parsedColour) < 10
|
||||
) {
|
||||
parsedColour = parseInt(colourChar1);
|
||||
asciiStringArray.shift();
|
||||
asciiStringArray.shift();
|
||||
widthOfColCodes += 2;
|
||||
|
||||
curBlock.bg = parseInt(colourChar1);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (Number.isNaN(parsedColour)) {
|
||||
curBlock.bg = parseInt(colourChar1);
|
||||
widthOfColCodes += 1;
|
||||
asciiStringArray.shift();
|
||||
} else if (parsedColour <= MIRC_MAX_COLOURS && parsedColour >= 0) {
|
||||
curBlock.bg = parseInt(parsedColour);
|
||||
widthOfColCodes += parsedColour.toString().length;
|
||||
|
||||
asciiStringArray = asciiStringArray.slice(
|
||||
parsedColour.toString().length,
|
||||
asciiStringArray.length,
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
curBlock.char = curChar;
|
||||
asciiStringArray.shift();
|
||||
asciiX++;
|
||||
|
||||
finalAscii.layers[0].data[asciiY][asciiX - 1] = {
|
||||
...curBlock,
|
||||
};
|
||||
break;
|
||||
} // End Switch
|
||||
} // End loop charPos
|
||||
|
||||
// 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.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.layers);
|
||||
|
||||
// Save ASCII to storage
|
||||
store.commit('newAsciibirdMeta', finalAscii);
|
||||
|
||||
|
@ -639,11 +379,9 @@ export const createNewAscii = (forms) => {
|
|||
const newAscii = {
|
||||
title: forms.createAscii.title,
|
||||
history: [],
|
||||
// redo: [],
|
||||
historyIndex: 0,
|
||||
x: 247, // the dragable ascii canvas x
|
||||
y: 24, // the dragable ascii canvas y
|
||||
current: [],
|
||||
layers: [{
|
||||
label: forms.createAscii.title,
|
||||
visible: true,
|
||||
|
@ -674,8 +412,7 @@ export const createNewAscii = (forms) => {
|
|||
}
|
||||
|
||||
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');
|
||||
|
||||
|
@ -683,7 +420,7 @@ export const createNewAscii = (forms) => {
|
|||
};
|
||||
|
||||
// Converts ASCIIBIRD blocks to mIRC colours
|
||||
export const exportMirc = () => {
|
||||
export const exportMirc = (blocks = null) => {
|
||||
const {
|
||||
currentAscii
|
||||
} = store.getters;
|
||||
|
@ -692,7 +429,10 @@ export const exportMirc = () => {
|
|||
currentAsciiLayersWidthHeight
|
||||
} = store.getters;
|
||||
|
||||
const blocks = mergeLayers();
|
||||
if (blocks === null) {
|
||||
blocks = mergeLayers();
|
||||
}
|
||||
|
||||
const output = [];
|
||||
let curBlock = false;
|
||||
let pushString = '';
|
||||
|
|
|
@ -49,34 +49,26 @@
|
|||
|
||||
<t-button
|
||||
type="button"
|
||||
class="rounded-3xl h-7 ml-1 mt-1"
|
||||
@click="saveToLibrary(decompressBlock(brush.blocks))"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'save']"
|
||||
size="lg"
|
||||
class="p-1 mx-1"
|
||||
/>
|
||||
<span class="material-icons">save</span>
|
||||
|
||||
</t-button>
|
||||
<t-button
|
||||
type="button"
|
||||
@click="reuseBlocks(decompressBlock(brush.blocks))"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'paint-brush']"
|
||||
size="lg"
|
||||
class="p-1 mx-1"
|
||||
/>
|
||||
</t-button>
|
||||
class="rounded-3xl h-7 ml-1 mt-1"
|
||||
@click="reuseBlocks(decompressBlock(brush.blocks))">
|
||||
<span class="material-icons">brush</span>
|
||||
</t-button>
|
||||
|
||||
<t-button
|
||||
type="button"
|
||||
class="rounded-3xl h-7 ml-1 mt-1"
|
||||
@click="removeFromHistory(decompressBlock(brush.blocks))"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'trash']"
|
||||
size="lg"
|
||||
class="p-1 mx-1 right-auto"
|
||||
/>
|
||||
<span class="material-icons">delete</span>
|
||||
|
||||
</t-button>
|
||||
</t-card>
|
||||
</div>
|
||||
|
@ -91,23 +83,19 @@
|
|||
|
||||
<t-button
|
||||
type="button"
|
||||
class="rounded-3xl h-7 ml-1 mt-1"
|
||||
@click="removeFromLibrary(decompressBlock(brush.blocks))"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'trash']"
|
||||
size="lg"
|
||||
class="p-1 mx-1"
|
||||
/>
|
||||
<span class="material-icons">trash</span>
|
||||
|
||||
</t-button>
|
||||
<t-button
|
||||
type="button"
|
||||
class="rounded-3xl h-7 ml-1 mt-1"
|
||||
@click="reuseBlocks(decompressBlock(brush.blocks))"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'paint-brush']"
|
||||
size="lg"
|
||||
class="p-1 mx-1"
|
||||
/>
|
||||
<span class="material-icons">brush</span>
|
||||
|
||||
</t-button>
|
||||
</t-card>
|
||||
</div>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<t-button
|
||||
type="button"
|
||||
:style="`background-color: ${mircColours[currentBg]} !important;`"
|
||||
class="border-gray-200 w-12 h-12 text-2xl ml-2"
|
||||
class="border-gray-200 w-12 h-12 text-2xl ml-4"
|
||||
id="currentColourBg"
|
||||
@click="$store.commit('changeIsUpdatingBg', ! toolbarState.isChoosingBg )"
|
||||
>
|
||||
|
@ -22,18 +22,18 @@
|
|||
|
||||
<t-button
|
||||
type="button"
|
||||
class="bg-white absolute rounded-3xl"
|
||||
style="margin-left: -62px; margin-top: 12px"
|
||||
class="rounded-3xl"
|
||||
style="margin-left: -68px; margin-top: 12px;"
|
||||
id="swapColour"
|
||||
@click="swapColours()"
|
||||
>
|
||||
<font-awesome-icon :icon="['fas', 'sync']" />
|
||||
<span class="material-icons">swap_horiz</span>
|
||||
</t-button>
|
||||
|
||||
<t-button
|
||||
type="button"
|
||||
:style="`background-color: ${mircColours[currentBg]} !important;color: ${mircColours[currentFg]};`"
|
||||
class="border-gray-200 w-12 h-12 text-2xl ml-2"
|
||||
class="border-gray-200 w-12 h-12 text-2xl ml-12"
|
||||
id="currentChar"
|
||||
@click="$store.commit('changeIsUpdatingChar', ! toolbarState.isChoosingChar )"
|
||||
>
|
||||
|
|
|
@ -110,7 +110,9 @@ export default {
|
|||
asciiStats() {
|
||||
// const compressed = ( JSON.stringify(this.currentAscii) / 1024).toFixed(2);
|
||||
// const uncompressed = (JSON.stringify(this.currentAscii).length / 1024).toFixed(2);
|
||||
const stateSize = (JSON.stringify(this.state).length / 1024).toFixed(2);
|
||||
const byteSize = str => new Blob([str]).size;
|
||||
|
||||
const stateSize = (byteSize(JSON.stringify(this.state)) / 1024).toFixed(2);
|
||||
return {
|
||||
stateSize: `${stateSize}kb`,
|
||||
|
||||
|
|
|
@ -22,75 +22,77 @@
|
|||
<div class="flex">
|
||||
<label class="ml-1 w-1/3">
|
||||
<t-checkbox
|
||||
class="form-checkbox m-1"
|
||||
class="form-checkbox h-5 w-5 text-blue-600"
|
||||
name="targetingFg"
|
||||
v-model="toolbarState.targetingFg"
|
||||
:disabled="!canBg && !canText"
|
||||
/>
|
||||
<span class="text-sm">FG</span>
|
||||
<span class="ab-checkbox-label">FG</span>
|
||||
|
||||
|
||||
</label>
|
||||
|
||||
<label class="ml-1 w-1/3">
|
||||
<t-checkbox
|
||||
class="form-checkbox m-1"
|
||||
class="ab-checkbox"
|
||||
name="targetingBg"
|
||||
v-model="toolbarState.targetingBg"
|
||||
:disabled="!canFg && !canText"
|
||||
checked
|
||||
/>
|
||||
<span class="text-sm">BG</span>
|
||||
<span class="ab-checkbox-label">BG</span>
|
||||
</label>
|
||||
|
||||
<label class="ml-1 w-1/3">
|
||||
<t-checkbox
|
||||
class="form-checkbox m-1"
|
||||
class="ab-checkbox"
|
||||
name="targetingChar"
|
||||
v-model="toolbarState.targetingChar"
|
||||
:disabled="!canFg && !canBg"
|
||||
/>
|
||||
<span class="text-sm">Text</span>
|
||||
<span class="ab-checkbox-label">Text</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
<label class="ml-1 w-1/2">
|
||||
<t-checkbox
|
||||
class="form-checkbox m-1"
|
||||
class="ab-checkbox"
|
||||
name="mirror-x"
|
||||
v-model="mirror.x"
|
||||
@change="updateMirror()"
|
||||
/>
|
||||
<span class="text-sm">Mirror X</span>
|
||||
<span class="ab-checkbox-label">Mirror X</span>
|
||||
</label>
|
||||
<label class="ml-1 w-1/2">
|
||||
<t-checkbox
|
||||
class="form-checkbox m-1"
|
||||
class="ab-checkbox"
|
||||
name="mirror-y"
|
||||
v-model="mirror.y"
|
||||
@change="updateMirror()"
|
||||
/>
|
||||
<span class="text-sm">Mirror Y</span>
|
||||
<span class="ab-checkbox-label">Mirror Y</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
<label class="ml-1 w-1/2">
|
||||
<t-checkbox
|
||||
class="form-checkbox m-1"
|
||||
class="ab-checkbox"
|
||||
name="update-brush"
|
||||
v-model="toolbarState.updateBrush"
|
||||
@change="$store.commit('toggleUpdateBrush', updateBrush)"
|
||||
/>
|
||||
<span class="text-sm">Update Brush</span>
|
||||
<span class="ab-checkbox-label">Update Brush</span>
|
||||
</label>
|
||||
<label class="ml-1 w-1/2">
|
||||
<t-checkbox
|
||||
class="form-checkbox m-1"
|
||||
class="ab-checkbox"
|
||||
name="grid"
|
||||
v-model="toolbarState.gridView"
|
||||
@change="$store.commit('toggleGridView', gridView)"
|
||||
/>
|
||||
<span class="text-sm">Grid</span>
|
||||
<span class="ab-checkbox-label">Grid</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
@ -107,7 +109,7 @@
|
|||
}`"
|
||||
@click="$store.commit('changeTool', keyToolbar)"
|
||||
>
|
||||
<font-awesome-icon :icon="[value.fa, value.icon]" size="lg" />
|
||||
<span class="material-icons">{{ value.icon }}</span>
|
||||
</t-button>
|
||||
</t-card>
|
||||
</vue-draggable-resizable>
|
||||
|
|
|
@ -8,8 +8,33 @@
|
|||
class="previewcanvas"
|
||||
:width="blocksWidthHeight.w"
|
||||
:height="blocksWidthHeight.h"
|
||||
@mouseup.right="openContextMenu"
|
||||
@contextmenu.prevent
|
||||
/>
|
||||
</div>
|
||||
|
||||
<context-menu :ref="`block-menu-${hash}`" class="z-50" >
|
||||
<ul>
|
||||
<li
|
||||
@click="true"
|
||||
class="ml-1 text-sm hover:bg-gray-400"
|
||||
>
|
||||
Save as PNG
|
||||
</li>
|
||||
<li
|
||||
@click="true"
|
||||
class="ml-1 text-sm hover:bg-gray-400"
|
||||
>
|
||||
Export ASCII to mIRC Clipboard
|
||||
</li>
|
||||
<li
|
||||
@click="true"
|
||||
class="ml-1 text-sm hover:bg-gray-400"
|
||||
>
|
||||
Export ASCII to mIRC File
|
||||
</li>
|
||||
</ul>
|
||||
</context-menu>
|
||||
</t-card>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -17,7 +42,7 @@
|
|||
|
||||
<script>
|
||||
import { mircColours99, blockWidth, blockHeight, cyrb53, getBlocksWidth, filterNullBlocks } from "../../ascii";
|
||||
|
||||
import ContextMenu from "./ContextMenu.vue";
|
||||
export default {
|
||||
name: "BrushCanvas",
|
||||
created() {
|
||||
|
@ -37,6 +62,9 @@ export default {
|
|||
default: false,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
ContextMenu,
|
||||
},
|
||||
data: () => ({
|
||||
ctx: null,
|
||||
redraw: true,
|
||||
|
@ -87,9 +115,11 @@ export default {
|
|||
options() {
|
||||
return this.$store.getters.options;
|
||||
},
|
||||
hash() {
|
||||
return cyrb53(JSON.stringify(this.getBlocks));
|
||||
},
|
||||
canvasName() {
|
||||
let hash = cyrb53(JSON.stringify(this.getBlocks))
|
||||
return `${hash}-brush-canvas`;
|
||||
return `${this.hash}-brush-canvas`;
|
||||
},
|
||||
getBlocks() {
|
||||
return this.blocks === false
|
||||
|
@ -157,6 +187,14 @@ export default {
|
|||
filterNullBlocks(blocks) {
|
||||
return filterNullBlocks(blocks)
|
||||
},
|
||||
openContextMenu(e) {
|
||||
e.preventDefault();
|
||||
// These are the correct X and Y when inside the floating panel
|
||||
this.$refs[`block-menu-${this.hash}`].open({
|
||||
pageX: e.layerX,
|
||||
pageY: e.layerY,
|
||||
});
|
||||
},
|
||||
drawPreview() {
|
||||
this.ctx.clearRect(0, 0, this.canvasRef.width, this.canvasRef.height);
|
||||
this.ctx.fillStyle = this.mircColours[1];
|
||||
|
|
|
@ -22,43 +22,32 @@
|
|||
|
||||
<div class="w-full bg-white rounded-lg shadow">
|
||||
<ul class="divide-y-2 divide-gray-100 mb-2">
|
||||
<div class="flex p-1">
|
||||
|
||||
<t-button
|
||||
type="button"
|
||||
class="rounded-xl"
|
||||
@click="updateImageOverlay"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', imageOverlay.visible ? 'eye' : 'eye-slash']"
|
||||
/> </t-button
|
||||
>
|
||||
<div class="w-full p-1" @click="showOverlayModal">
|
||||
<div class="flex text-right" >
|
||||
<div class="w-full">
|
||||
<t-card class="w-full pl-2 hover:bg-gray-300 cursor-pointer">
|
||||
<span>{{
|
||||
imageOverlayUrl || 'Image Overlay'
|
||||
|
||||
}}</span>
|
||||
</t-card>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="flex p-1">
|
||||
<t-button
|
||||
type="button"
|
||||
class="rounded-xl"
|
||||
@click="updateImageOverlay"
|
||||
>
|
||||
<span class="material-icons">{{
|
||||
imageOverlay.visible ? "remove_red_eye" : "panorama_fish_eye"
|
||||
}}</span>
|
||||
</t-button>
|
||||
<div class="w-full p-1" @click="showOverlayModal">
|
||||
<div class="flex text-right">
|
||||
<div class="w-full">
|
||||
<t-card class="w-full pl-2 hover:bg-gray-300 cursor-pointer">
|
||||
<span>{{ imageOverlayUrl || "Image Overlay" }}</span>
|
||||
</t-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ul>
|
||||
|
||||
<ul class="mt-1 mb-2">
|
||||
<li></li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
<ul class="divide-y-2 divide-gray-100 reverseorder">
|
||||
<li
|
||||
:class="`p-1 ${selectedLayerClass(key)}`"
|
||||
|
@ -69,22 +58,22 @@
|
|||
<div class="w-12">
|
||||
<t-button
|
||||
type="button"
|
||||
class="rounded-xl"
|
||||
class="rounded-3xl h-7"
|
||||
@click="toggleLayer(key)"
|
||||
:disabled="!canToggleLayer"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', layer.visible ? 'eye' : 'eye-slash']"
|
||||
/> </t-button
|
||||
<span class="material-icons">{{ layer.visible ? "remove_red_eye" : "panorama_fish_eye" }}</span>
|
||||
|
||||
</t-button
|
||||
><br />
|
||||
|
||||
<t-button
|
||||
type="button"
|
||||
class="rounded-xl"
|
||||
class="rounded-3xl h-7"
|
||||
@click="removeLayer(key)"
|
||||
:disabled="!canToggleLayer"
|
||||
>
|
||||
<font-awesome-icon :icon="['fas', 'trash']" />
|
||||
<span class="material-icons">delete</span>
|
||||
</t-button>
|
||||
</div>
|
||||
|
||||
|
@ -101,22 +90,22 @@
|
|||
<div class="w-5">
|
||||
<t-button
|
||||
type="button"
|
||||
class="rounded-xl"
|
||||
class="rounded-3xl h-7"
|
||||
@click="downLayer(key)"
|
||||
:disabled="!canToggleLayer"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'chevron-circle-up']"
|
||||
/> </t-button
|
||||
|
||||
<span class="material-icons">arrow_upward</span>
|
||||
</t-button
|
||||
><br />
|
||||
|
||||
<t-button
|
||||
type="button"
|
||||
class="rounded-xl"
|
||||
class="rounded-3xl h-7"
|
||||
@click="upLayer(key)"
|
||||
:disabled="!canToggleLayer"
|
||||
>
|
||||
<font-awesome-icon :icon="['fas', 'chevron-circle-down']" />
|
||||
<span class="material-icons">arrow_downward</span>
|
||||
</t-button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -152,8 +141,10 @@ export default {
|
|||
return this.$store.getters.imageOverlay || false;
|
||||
},
|
||||
imageOverlayUrl() {
|
||||
return this.imageOverlay.url ? this.imageOverlay.url.split("/").pop() : '';
|
||||
}
|
||||
return this.imageOverlay.url
|
||||
? this.imageOverlay.url.split("/").pop()
|
||||
: "";
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selectedLayer() {
|
||||
|
@ -243,16 +234,14 @@ export default {
|
|||
this.$store.commit("removeLayer", key);
|
||||
},
|
||||
showOverlayModal() {
|
||||
this.$store.commit('openModal', 'overlay');
|
||||
this.$store.commit("openModal", "overlay");
|
||||
},
|
||||
// Image overlay
|
||||
updateImageOverlay() {
|
||||
let overlay = { ... this.imageOverlay }
|
||||
overlay.visible = ! overlay.visible
|
||||
updateImageOverlay() {
|
||||
let overlay = { ...this.imageOverlay };
|
||||
overlay.visible = !overlay.visible;
|
||||
this.$store.commit("updateImageOverlay", overlay);
|
||||
},
|
||||
|
||||
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,42 +1,84 @@
|
|||
<template>
|
||||
<div>
|
||||
<t-card class="overflow-x-scroll h-full">
|
||||
<div :style="`height: ${blocksWidthHeight.h}px;width: ${blocksWidthHeight.w}px;`">
|
||||
<t-card class="overflow-x-scroll overflow-y-scroll h-full" :h="blocksWidthHeight.h">
|
||||
|
||||
<div
|
||||
:style="`height: ${blocksWidthHeight.h}px;width: ${blocksWidthHeight.w}px;`"
|
||||
@mouseup.right="openContextMenu"
|
||||
>
|
||||
|
||||
<canvas
|
||||
ref="brushcanvas"
|
||||
id="brushcanvas"
|
||||
class="brushcanvas"
|
||||
@mousemove="canvasMouseMove"
|
||||
@mouseup="disable"
|
||||
@mousedown.left="addBlock"
|
||||
@mousedown.right="eraseBlock"
|
||||
@mousedown.left="processClick"
|
||||
|
||||
@mouseup.left="canTool = false"
|
||||
@contextmenu.prevent
|
||||
:width="blocksWidthHeight.w"
|
||||
:height="blocksWidthHeight.h"
|
||||
@mouseenter="disableToolbarMoving"
|
||||
@mouseleave="enableToolbarMoving"
|
||||
/>
|
||||
|
||||
<context-menu ref="main-brush-menu" class="z-50" @contextmenu.prevent>
|
||||
<ul>
|
||||
<li
|
||||
@click="true"
|
||||
class="ml-1 text-sm hover:bg-gray-400"
|
||||
>
|
||||
Save as PNG
|
||||
</li>
|
||||
<li
|
||||
@click="true"
|
||||
class="ml-1 text-sm hover:bg-gray-400"
|
||||
>
|
||||
Export Brush to mIRC Clipboard
|
||||
</li>
|
||||
<li
|
||||
@click="true"
|
||||
class="ml-1 text-sm hover:bg-gray-400"
|
||||
>
|
||||
Export Brush to mIRC File
|
||||
</li>
|
||||
<li
|
||||
@click="true"
|
||||
class="ml-1 text-sm hover:bg-gray-400"
|
||||
>
|
||||
Save to Library
|
||||
</li>
|
||||
</ul>
|
||||
</context-menu>
|
||||
</div>
|
||||
</t-card>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ContextMenu from "./ContextMenu.vue";
|
||||
import {
|
||||
mircColours99,
|
||||
blockWidth,
|
||||
blockHeight,
|
||||
getBlocksWidth,
|
||||
filterNullBlocks,
|
||||
toolbarIcons,
|
||||
emptyBlock,
|
||||
} from "../../ascii";
|
||||
|
||||
export default {
|
||||
name: "MainBrushCanvas",
|
||||
components: {
|
||||
ContextMenu,
|
||||
},
|
||||
created() {
|
||||
window.addEventListener('load', () => {
|
||||
// Fixes the font on load issue
|
||||
this.delayRedrawCanvas();
|
||||
})
|
||||
window.addEventListener("load", () => {
|
||||
// Fixes the font on load issue
|
||||
this.delayRedrawCanvas();
|
||||
});
|
||||
},
|
||||
mounted() {
|
||||
this.ctx = this.canvasRef.getContext("2d");
|
||||
|
@ -45,8 +87,8 @@ export default {
|
|||
data: () => ({
|
||||
ctx: null,
|
||||
redraw: true,
|
||||
erasing: false,
|
||||
drawing: false,
|
||||
canTool: false,
|
||||
showContextMenu: true,
|
||||
x: 0,
|
||||
y: 0,
|
||||
}),
|
||||
|
@ -58,7 +100,7 @@ export default {
|
|||
return blockHeight * this.blockSizeMultiplier;
|
||||
},
|
||||
blockSizeMultiplier() {
|
||||
return this.$store.getters.blockSizeMultiplier
|
||||
return this.$store.getters.blockSizeMultiplier;
|
||||
},
|
||||
currentAscii() {
|
||||
return this.$store.getters.currentAscii;
|
||||
|
@ -123,6 +165,18 @@ export default {
|
|||
gridView() {
|
||||
return this.toolbarState.gridView;
|
||||
},
|
||||
currentTool() {
|
||||
return toolbarIcons[this.$store.getters.currentTool] ?? null;
|
||||
},
|
||||
isDefault() {
|
||||
return this.currentTool.name === "default";
|
||||
},
|
||||
isBrushing() {
|
||||
return this.currentTool.name === "brush";
|
||||
},
|
||||
isErasing() {
|
||||
return this.currentTool.name === "eraser";
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
brushBlocks() {
|
||||
|
@ -165,6 +219,36 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
openContextMenu(e) {
|
||||
e.preventDefault();
|
||||
// These are the correct X and Y when inside the floating panel
|
||||
this.$refs['main-brush-menu'].open({
|
||||
pageX: e.layerX,
|
||||
pageY: e.layerY,
|
||||
});
|
||||
},
|
||||
processClick(e) {
|
||||
this.canTool = true;
|
||||
|
||||
if (e.offsetX >= 0) {
|
||||
this.x = e.offsetX;
|
||||
}
|
||||
|
||||
if (e.offsetY >= 0) {
|
||||
this.y = e.offsetY;
|
||||
}
|
||||
|
||||
this.x = Math.floor(this.x / blockWidth);
|
||||
this.y = Math.floor(this.y / blockHeight);
|
||||
|
||||
if (this.isErasing) {
|
||||
this.eraseBlock();
|
||||
}
|
||||
|
||||
if (this.isBrushing) {
|
||||
this.addBlock();
|
||||
}
|
||||
},
|
||||
getBlocksWidth(blocks) {
|
||||
return getBlocksWidth(blocks);
|
||||
},
|
||||
|
@ -182,19 +266,19 @@ export default {
|
|||
ctx.moveTo(x, 0);
|
||||
ctx.lineTo(x, h);
|
||||
}
|
||||
|
||||
|
||||
ctx.strokeStyle = "rgba(0, 0, 0, 1)";
|
||||
ctx.lineWidth = 1;
|
||||
ctx.setLineDash([1]);
|
||||
|
||||
|
||||
ctx.stroke();
|
||||
|
||||
|
||||
ctx.beginPath();
|
||||
for (var y = 0; y <= h; y += blockHeight) {
|
||||
ctx.moveTo(0, y);
|
||||
ctx.lineTo(w, y);
|
||||
}
|
||||
|
||||
|
||||
ctx.stroke();
|
||||
},
|
||||
|
||||
|
@ -216,7 +300,7 @@ export default {
|
|||
if (this.brushBlocks[y] && this.brushBlocks[y][x]) {
|
||||
const curBlock = this.brushBlocks[y][x];
|
||||
|
||||
if ((curBlock.bg !== undefined || curBlock.bg !== null) && this.isTargettingBg) {
|
||||
if (curBlock.bg !== undefined) { // we had used to hide or show depending on canFg, etc && this.isTargettingBg
|
||||
this.ctx.fillStyle = this.mircColours[curBlock.bg];
|
||||
|
||||
this.ctx.fillRect(
|
||||
|
@ -227,11 +311,11 @@ export default {
|
|||
);
|
||||
}
|
||||
|
||||
if (curBlock.fg !== undefined && this.isTargettingFg) {
|
||||
if (curBlock.fg !== undefined) { // we had used to hide or show depending on canFg, etc && this.isTargettingFg
|
||||
this.ctx.fillStyle = this.mircColours[curBlock.fg];
|
||||
}
|
||||
|
||||
if (curBlock.char !== undefined && this.isTargettingChar) {
|
||||
if (curBlock.char !== undefined) { // we had used to hide or show depending on canFg, etc && this.isTargettingChar
|
||||
this.ctx.fillStyle = this.mircColours[curBlock.fg];
|
||||
this.ctx.fillText(
|
||||
curBlock.char,
|
||||
|
@ -243,84 +327,68 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
if (this.gridView) {
|
||||
this.drawGrid();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
},
|
||||
delayRedrawCanvas() {
|
||||
if (this.redraw) {
|
||||
this.redraw = false;
|
||||
var _this = this;
|
||||
setTimeout(function(){
|
||||
setTimeout(function () {
|
||||
requestAnimationFrame(() => {
|
||||
_this.drawPreview();
|
||||
_this.redraw = true;
|
||||
});
|
||||
}, 1000/this.options.fps)
|
||||
}, 1000 / this.options.fps);
|
||||
}
|
||||
},
|
||||
// Basic block editing
|
||||
canvasMouseMove(e) {
|
||||
if (e.offsetX >= 0) {
|
||||
this.x = e.offsetX;
|
||||
if (this.canTool) {
|
||||
this.processClick(e);
|
||||
}
|
||||
},
|
||||
addBlock() {
|
||||
let block = { ...emptyBlock };
|
||||
|
||||
if (this.canBg) {
|
||||
block["bg"] = this.currentBg;
|
||||
}
|
||||
|
||||
if (e.offsetY >= 0) {
|
||||
this.y = e.offsetY;
|
||||
if (this.canFg) {
|
||||
block["fg"] = this.currentFg;
|
||||
}
|
||||
|
||||
this.x = Math.floor(this.x / blockWidth);
|
||||
this.y = Math.floor(this.y / blockHeight);
|
||||
|
||||
if (this.erasing) {
|
||||
this.brushBlocks[this.y][this.x] = {
|
||||
bg: this.canBg ? null : this.currentFg,
|
||||
fg: this.canFg ? null : this.currentBg,
|
||||
char: this.canText ? null : this.currentChar,
|
||||
};
|
||||
if (this.canText) {
|
||||
block["char"] = this.currentChar;
|
||||
}
|
||||
|
||||
if (this.drawing) {
|
||||
this.brushBlocks[this.y][this.x] = {
|
||||
bg: this.canBg ? this.currentBg : null,
|
||||
fg: this.canFg ? this.currentFg : null,
|
||||
char: this.canText ? this.currentChar : null,
|
||||
};
|
||||
this.brushBlocks[this.y][this.x] = block;
|
||||
this.delayRedrawCanvas();
|
||||
},
|
||||
eraseBlock() {
|
||||
if (this.canBg) {
|
||||
delete this.brushBlocks[this.y][this.x]["bg"];
|
||||
}
|
||||
|
||||
if (this.canFg) {
|
||||
delete this.brushBlocks[this.y][this.x]["fg"];
|
||||
}
|
||||
|
||||
if (this.canText) {
|
||||
delete this.brushBlocks[this.y][this.x]["char"];
|
||||
}
|
||||
|
||||
this.delayRedrawCanvas();
|
||||
},
|
||||
disable() {
|
||||
this.erasing = false;
|
||||
this.drawing = false;
|
||||
},
|
||||
addBlock() {
|
||||
this.drawing = true;
|
||||
this.brushBlocks[this.y][this.x] = {
|
||||
bg: this.canBg ? this.currentBg : null,
|
||||
fg: this.canFg ? this.currentFg : null,
|
||||
char: this.canText ? this.currentChar : null,
|
||||
};
|
||||
},
|
||||
eraseBlock(e) {
|
||||
this.erasing = true;
|
||||
this.brushBlocks[this.y][this.x] = {
|
||||
bg: this.canBg ? null : this.currentFg,
|
||||
fg: this.canFg ? null : this.currentBg,
|
||||
char: this.canText ? null : this.currentChar,
|
||||
};
|
||||
},
|
||||
disableToolbarMoving() {
|
||||
this.$store.commit("changeToolBarDraggable", false);
|
||||
},
|
||||
enableToolbarMoving() {
|
||||
// Save the blocks when the mouse leaves the canvas area
|
||||
// To avoid one block history changes
|
||||
this.disable()
|
||||
this.$store.commit("brushBlocks", this.brushBlocks);
|
||||
this.$store.commit("changeToolBarDraggable", true);
|
||||
},
|
||||
|
|
|
@ -10,14 +10,8 @@ import Dashboard from './Dashboard.vue';
|
|||
import Toasted from 'vue-toasted';
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
import {
|
||||
FontAwesomeIcon,
|
||||
} from '@fortawesome/vue-fontawesome';
|
||||
import './fontAwesome';
|
||||
|
||||
Vue.use(VueTailwind, tailwindCss);
|
||||
Vue.component('font-awesome-icon', FontAwesomeIcon);
|
||||
|
||||
Vue.component('vue-draggable-resizable', VueDraggableResizable);
|
||||
Vue.use(VueClipboard);
|
||||
Vue.use(Toasted, {
|
||||
|
|
|
@ -219,7 +219,7 @@ export default new Vuex.Store({
|
|||
state.asciibirdMeta[state.tab].layers = LZString.compressToUTF16(JSON.stringify(
|
||||
tempLayers));
|
||||
|
||||
state.asciibirdMeta[state.tab].current = LZString.compressToUTF16(JSON.stringify(mergeLayers()));
|
||||
// state.asciibirdMeta[state.tab].current = LZString.compressToUTF16(JSON.stringify(mergeLayers()));
|
||||
|
||||
let historyIndex = state.asciibirdMeta[state.tab].historyIndex;
|
||||
|
||||
|
@ -683,7 +683,6 @@ export default new Vuex.Store({
|
|||
selectedLayer: (state) => state.asciibirdMeta[state.tab].selectedLayer,
|
||||
imageOverlay: (state) => state.asciibirdMeta[state.tab].imageOverlay,
|
||||
asciibirdMeta: (state) => state.asciibirdMeta,
|
||||
nextTabValue: (state) => state.asciibirdMeta.length,
|
||||
brushSizeHeight: (state) => state.toolbarState.brushSizeHeight,
|
||||
brushSizeWidth: (state) => state.toolbarState.brushSizeWidth,
|
||||
brushSizeType: (state) => state.toolbarState.brushSizeType,
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
font-display: swap;
|
||||
}
|
||||
|
||||
/* @import url('https://use.fontawesome.com/releases/v5.0.9/css/all.css'); */
|
||||
|
||||
body {
|
||||
background: rgb(58, 58, 58);
|
||||
-ms-user-select: none;
|
||||
|
@ -25,7 +23,7 @@ body {
|
|||
-webkit-touch-callout: none;
|
||||
-khtml-user-select: none;
|
||||
user-select: none;
|
||||
overflow-y: hidden;
|
||||
/* overflow-y: hidden; */
|
||||
}
|
||||
|
||||
.canvas {
|
||||
|
@ -83,4 +81,19 @@ body {
|
|||
@apply bg-gray-800;
|
||||
}
|
||||
|
||||
.ab-toolbar-button {
|
||||
@apply w-10 h-10 mt-1 ml-1 border-gray-200 bg-gray-500;
|
||||
}
|
||||
|
||||
.ab-toolbar-button-active {
|
||||
@apply w-10 h-10 mt-1 ml-1 border-gray-900 bg-blue-500;
|
||||
}
|
||||
|
||||
.ab-checkbox {
|
||||
@apply h-4 w-4 text-blue-600;
|
||||
}
|
||||
|
||||
.ab-checkbox-label {
|
||||
@apply ml-2 text-gray-700;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,30 @@
|
|||
@mouseleave="isMouseOnCanvas = false"
|
||||
@mouseenter="isMouseOnCanvas = true"
|
||||
>
|
||||
|
||||
<context-menu ref="editor-menu" class="z-50" >
|
||||
<ul>
|
||||
<li
|
||||
@click="true"
|
||||
class="ml-1 text-sm hover:bg-gray-400"
|
||||
>
|
||||
Save as PNG
|
||||
</li>
|
||||
<li
|
||||
@click="true"
|
||||
class="ml-1 text-sm hover:bg-gray-400"
|
||||
>
|
||||
Export ASCII to mIRC Clipboard
|
||||
</li>
|
||||
<li
|
||||
@click="true"
|
||||
class="ml-1 text-sm hover:bg-gray-400"
|
||||
>
|
||||
Export ASCII to mIRC File
|
||||
</li>
|
||||
</ul>
|
||||
</context-menu>
|
||||
|
||||
<vue-draggable-resizable
|
||||
ref="canvasdrag"
|
||||
:grid="[blockWidth, blockHeight]"
|
||||
|
@ -41,6 +65,8 @@
|
|||
@mousemove.left="canvasMouseMove"
|
||||
@mousedown.left="canvasMouseDown"
|
||||
@mouseup.left="canvasMouseUp"
|
||||
@mouseup.right="openContextMenu"
|
||||
@contextmenu.prevent
|
||||
@touchmove="canvasMouseMove"
|
||||
@touchend="canvasMouseDown"
|
||||
@touchstart="canvasMouseUp"
|
||||
|
@ -51,6 +77,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import ContextMenu from "./../components/parts/ContextMenu.vue";
|
||||
import {
|
||||
toolbarIcons,
|
||||
mircColours99,
|
||||
|
@ -62,11 +89,13 @@ import {
|
|||
getBlocksWidth,
|
||||
checkVisible,
|
||||
mergeLayers,
|
||||
cyrb53,
|
||||
} from "../ascii";
|
||||
|
||||
export default {
|
||||
name: "Editor",
|
||||
components: {
|
||||
ContextMenu,
|
||||
},
|
||||
mounted() {
|
||||
this.ctx = this.canvasRef.getContext("2d");
|
||||
this.toolCtx = this.$refs.canvastools.getContext("2d");
|
||||
|
@ -297,7 +326,9 @@ export default {
|
|||
return this.currentSelectedLayer.width;
|
||||
},
|
||||
currentAsciiHeight() {
|
||||
return this.currentSelectedLayer.height;
|
||||
// Tested with wtf.txt and the max rows before the canvas
|
||||
// stops working seems to be 2184
|
||||
return (this.currentSelectedLayer.height > 2184 ? 2184 : this.currentSelectedLayer.height);
|
||||
},
|
||||
imageOverlay() {
|
||||
return this.$store.getters.imageOverlay;
|
||||
|
@ -429,6 +460,11 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
openContextMenu(e) {
|
||||
e.preventDefault();
|
||||
// These are the correct X and Y when inside the floating panel
|
||||
this.$refs['editor-menu'].open(e);
|
||||
},
|
||||
canvasKeyDown(char) {
|
||||
// if (this.isTextEditing) {
|
||||
console.log(char);
|
||||
|
@ -754,35 +790,38 @@ export default {
|
|||
return mergeLayers();
|
||||
},
|
||||
drawGrid() {
|
||||
let ctx = this.ctx;
|
||||
let w = this.canvas.width;
|
||||
let h = this.canvas.height;
|
||||
|
||||
ctx.beginPath();
|
||||
this.ctx.beginPath();
|
||||
|
||||
for (var x = 0; x <= w; x += blockWidth) {
|
||||
ctx.moveTo(x, 0);
|
||||
ctx.lineTo(x, h);
|
||||
this.ctx.moveTo(x, 0);
|
||||
this.ctx.lineTo(x, h);
|
||||
}
|
||||
|
||||
ctx.strokeStyle = "rgba(0, 0, 0, 1)";
|
||||
ctx.lineWidth = 1;
|
||||
ctx.setLineDash([1]);
|
||||
this.ctx.strokeStyle = "rgba(0, 0, 0, 1)";
|
||||
this.ctx.lineWidth = 1;
|
||||
this.ctx.setLineDash([1]);
|
||||
|
||||
ctx.stroke();
|
||||
this.ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
this.ctx.beginPath();
|
||||
for (var y = 0; y <= h; y += blockHeight) {
|
||||
ctx.moveTo(0, y);
|
||||
ctx.lineTo(w, y);
|
||||
this.ctx.moveTo(0, y);
|
||||
this.ctx.lineTo(w, y);
|
||||
}
|
||||
|
||||
ctx.stroke();
|
||||
this.ctx.stroke();
|
||||
},
|
||||
redrawCanvas() {
|
||||
if (this.currentAsciiLayers.length) {
|
||||
// https://stackoverflow.com/questions/28390358/high-cpu-usage-with-canvas-and-requestanimationframe
|
||||
this.ctx.save();
|
||||
this.canvasRef.width = this.canvasRef.width;
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
|
||||
// Position of the meta array
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
|
@ -838,6 +877,8 @@ export default {
|
|||
if (this.gridView) {
|
||||
this.drawGrid();
|
||||
}
|
||||
|
||||
this.ctx.restore();
|
||||
}
|
||||
},
|
||||
onCanvasResize(left, top, width, height) {
|
||||
|
@ -1066,6 +1107,8 @@ export default {
|
|||
clearToolCanvas() {
|
||||
if (this.toolCtx) {
|
||||
this.toolCtx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
this.toolCtx.save();
|
||||
this.$refs.canvastools.width = this.$refs.canvastools.width;
|
||||
}
|
||||
},
|
||||
delayRedrawCanvas() {
|
||||
|
@ -1335,8 +1378,8 @@ export default {
|
|||
}
|
||||
|
||||
// Apply text to ascii blocks
|
||||
if (this.canText && this.canTool && brushBlock.char !== null) {
|
||||
targetBlock.char = brushBlock.char;
|
||||
if (this.canText && this.canTool) {
|
||||
targetBlock['char'] = brushBlock['char'];
|
||||
|
||||
if (
|
||||
this.mirrorX &&
|
||||
|
@ -1433,6 +1476,7 @@ export default {
|
|||
// Apply the actual brush block to the ascii block
|
||||
if (this.canTool && brushBlock[target] !== undefined) {
|
||||
targetBlock[target] = brushBlock[target];
|
||||
// targetBlock['char'] = brushBlock['char'];
|
||||
|
||||
let theX = asciiWidth - arrayX;
|
||||
let theY = asciiHeight - arrayY;
|
||||
|
@ -1473,6 +1517,7 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
this.toolCtx.restore();
|
||||
return;
|
||||
},
|
||||
//
|
||||
|
@ -1483,12 +1528,12 @@ export default {
|
|||
drawBrush(plain = false) {
|
||||
this.clearToolCanvas();
|
||||
let brushDiffX = 0;
|
||||
let xLength = 0;
|
||||
let xLength = false;
|
||||
|
||||
// If the first row isn't selected then we cannot get the width
|
||||
// with the 0 index
|
||||
for (let i = 0; i <= this.brushBlocks.length; i++) {
|
||||
if (this.brushBlocks[i]) {
|
||||
if (this.brushBlocks[i] && xLength === false) {
|
||||
brushDiffX = Math.floor(this.brushBlocks[i].length / 2) * blockWidth;
|
||||
xLength = this.brushBlocks[i].length;
|
||||
break;
|
||||
|
@ -1507,6 +1552,7 @@ export default {
|
|||
continue;
|
||||
}
|
||||
|
||||
|
||||
// if (
|
||||
// this.top !== false &&
|
||||
// !this.checkVisible(this.top + (y * blockHeight) - this.yOffset)
|
||||
|
@ -1540,7 +1586,9 @@ export default {
|
|||
}
|
||||
|
||||
this.drawBrushBlocks(brushX, brushY, brushBlock, null);
|
||||
} else {
|
||||
|
||||
}
|
||||
else if (this.isErasing) {
|
||||
this.drawBrushBlocks(brushX, brushY, brushBlock, null, true);
|
||||
}
|
||||
|
||||
|
|
31
yarn.lock
31
yarn.lock
|
@ -903,37 +903,6 @@
|
|||
"@babel/helper-validator-identifier" "^7.14.9"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@fortawesome/fontawesome-common-types@^0.2.36":
|
||||
version "0.2.36"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz#b44e52db3b6b20523e0c57ef8c42d315532cb903"
|
||||
integrity sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==
|
||||
|
||||
"@fortawesome/fontawesome-svg-core@^1.2.35":
|
||||
version "1.2.36"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz#4f2ea6f778298e0c47c6524ce2e7fd58eb6930e3"
|
||||
integrity sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "^0.2.36"
|
||||
|
||||
"@fortawesome/free-regular-svg-icons@^5.15.3":
|
||||
version "5.15.4"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.4.tgz#b97edab436954333bbeac09cfc40c6a951081a02"
|
||||
integrity sha512-9VNNnU3CXHy9XednJ3wzQp6SwNwT3XaM26oS4Rp391GsxVYA+0oDR2J194YCIWf7jNRCYKjUCOduxdceLrx+xw==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "^0.2.36"
|
||||
|
||||
"@fortawesome/free-solid-svg-icons@^5.15.3":
|
||||
version "5.15.4"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz#2a68f3fc3ddda12e52645654142b9e4e8fbb6cc5"
|
||||
integrity sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "^0.2.36"
|
||||
|
||||
"@fortawesome/vue-fontawesome@^2.0.2":
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/vue-fontawesome/-/vue-fontawesome-2.0.2.tgz#5b86cd2fb7b4c17e5dede722c1c2855c97eceaea"
|
||||
integrity sha512-ecpKSBUWXsxRJVi/dbOds4tkKwEcBQ1JSDZFzE2jTFpF8xIh3OgTX8POIor6bOltjibr3cdEyvnDjecMwUmxhQ==
|
||||
|
||||
"@hapi/address@2.x.x":
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
|
||||
|
|
Loading…
Reference in New Issue