405 lines
11 KiB
Vue
405 lines
11 KiB
Vue
<template>
|
|
<div id="app">
|
|
<NewAscii />
|
|
<EditAscii />
|
|
<PasteAscii />
|
|
|
|
<context-menu :display="showContextMenu" ref="menu">
|
|
<ul>
|
|
<li
|
|
@click="$store.commit('openModal', 'new-ascii')"
|
|
class="ml-1"
|
|
@contextmenu.prevent
|
|
>
|
|
New ASCII
|
|
</li>
|
|
<li @click="clearCache()" class="ml-1">Clear and Refresh</li>
|
|
<li @click="startImport('mirc')" class="ml-1">Import mIRC</li>
|
|
<li
|
|
@click="exportMirc('file')"
|
|
class="ml-1"
|
|
v-if="asciibirdMeta.length"
|
|
>
|
|
Export mIRC to File
|
|
</li>
|
|
<li
|
|
class="ml-1"
|
|
@click="$store.commit('openModal', 'paste-modal')"
|
|
>
|
|
Import mIRC from Clipboard
|
|
</li>
|
|
<li
|
|
class="ml-1"
|
|
@click="exportMirc('clipboard')"
|
|
v-if="asciibirdMeta.length"
|
|
>
|
|
Export mIRC to Clipboard
|
|
</li>
|
|
<li
|
|
@click="exportAsciibirdState()"
|
|
class="ml-1"
|
|
v-if="asciibirdMeta.length"
|
|
>
|
|
Save Asciibird State
|
|
</li>
|
|
<li @click="startImport('asb')" class="ml-1">Load Asciibird State</li>
|
|
<li @click="$store.commit('openModal', 'edit-ascii')" class="ml-1" v-if="asciibirdMeta.length">Edit Current Ascii</li>
|
|
</ul>
|
|
</context-menu>
|
|
|
|
<span
|
|
@mouseup.right="openContextMenu"
|
|
@contextmenu.prevent
|
|
style="width: 100%; height: 100%; position: absolute; z-index: -1"
|
|
></span>
|
|
|
|
<input
|
|
type="file"
|
|
style="display: none"
|
|
ref="asciiInput"
|
|
@change="onImport()"
|
|
/>
|
|
|
|
<template v-if="asciibirdMeta.length">
|
|
<t-button
|
|
v-for="(value, key) in asciibirdMeta"
|
|
v-bind:key="key"
|
|
class="ml-1"
|
|
@click="changeTab(key, value)"
|
|
:disabled="false"
|
|
>
|
|
{{ value.title }}
|
|
</t-button>
|
|
|
|
<Toolbar :canvas-x="canvasX" :canvas-y="canvasY" />
|
|
<DebugPanel :canvas-x="canvasX" :canvas-y="canvasY" v-if="debugPanelState.visible" />
|
|
<Editor @coordsupdate="updateCoords" />
|
|
|
|
<CharPicker v-if="toolbarState.isChoosingChar" />
|
|
<ColourPicker
|
|
v-if="
|
|
toolbarState.isChoosingFg ||
|
|
toolbarState.isChoosingBg
|
|
"
|
|
/>
|
|
</template>
|
|
<template v-else>
|
|
<div style="left: 35%; top: 15%; position: absolute; z-index: -2">
|
|
<h1 style="font-size: 72px; text-align: center">ASCIIBIRD</h1>
|
|
<h1 style="font-size: 13px; text-align: center">
|
|
Right click to start
|
|
</h1>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import Toolbar from "./components/Toolbar.vue";
|
|
import DebugPanel from "./components/DebugPanel.vue";
|
|
import Editor from "./views/Editor.vue";
|
|
|
|
import CharPicker from "./components/parts/CharPicker.vue";
|
|
import ColourPicker from "./components/parts/ColourPicker.vue";
|
|
import ContextMenu from "./components/parts/ContextMenu.vue";
|
|
|
|
import NewAscii from "./components/modals/NewAscii.vue";
|
|
import EditAscii from "./components/modals/EditAscii.vue";
|
|
import PasteAscii from "./components/modals/PasteAscii.vue";
|
|
import LZString from "lz-string";
|
|
|
|
import { parseMircAscii } from "./ascii.js"
|
|
|
|
export default {
|
|
async created() {
|
|
// Load from irc watch if present in the URL bar
|
|
const asciiUrlCdn = new URL(location.href).searchParams.get("ascii");
|
|
if (asciiUrlCdn) {
|
|
const res = await fetch(`https://ascii.jewbird.live/${asciiUrlCdn}`, {
|
|
method: "GET",
|
|
headers: {
|
|
Accept: "text/plain",
|
|
},
|
|
});
|
|
|
|
const asciiData = await res.text();
|
|
this.mircAsciiImport(asciiData, asciiUrlCdn);
|
|
}
|
|
|
|
const asciiUrl = new URL(location.href).searchParams.get("ircwatch");
|
|
if (asciiUrl) {
|
|
const res = await fetch(`https://irc.watch/ascii/txt/${asciiUrl}`, {
|
|
method: "GET",
|
|
headers: {
|
|
Accept: "text/plain",
|
|
},
|
|
});
|
|
|
|
const asciiData = await res.text();
|
|
this.mircAsciiImport(asciiData, asciiUrl);
|
|
}
|
|
|
|
const haxAscii = new URL(location.href).searchParams.get("haxAscii");
|
|
if (haxAscii) {
|
|
const res = await fetch(`https://art.h4x.life/${haxAscii}`, {
|
|
method: "GET",
|
|
headers: {
|
|
Accept: "text/plain",
|
|
},
|
|
});
|
|
|
|
// Considers paths
|
|
const asciiName = haxAscii.split('/').pop();
|
|
const asciiData = await res.text();
|
|
this.mircAsciiImport(asciiData, asciiName);
|
|
}
|
|
},
|
|
components: {
|
|
Toolbar,
|
|
DebugPanel,
|
|
Editor,
|
|
CharPicker,
|
|
ColourPicker,
|
|
ContextMenu,
|
|
NewAscii,
|
|
EditAscii,
|
|
PasteAscii
|
|
},
|
|
name: "Dashboard",
|
|
data: () => ({
|
|
showNewAsciiModal: false,
|
|
currentTab: 1,
|
|
canvasX: null,
|
|
canvasY: null,
|
|
dashboardX: 0,
|
|
dashboardY: 0,
|
|
importType: null,
|
|
showContextMenu: false,
|
|
}),
|
|
computed: {
|
|
currentTool() {
|
|
return (
|
|
this.$store.getters.toolbarIcons[
|
|
this.$store.getters.currentTool
|
|
] ?? null
|
|
);
|
|
},
|
|
icon() {
|
|
return [
|
|
this.currentTool.fa ?? "fas",
|
|
this.currentTool.icon ?? "mouse-pointer",
|
|
];
|
|
},
|
|
options() {
|
|
return this.$store.getters.options;
|
|
},
|
|
canFg() {
|
|
return this.$store.getters.isTargettingFg;
|
|
},
|
|
canBg() {
|
|
return this.$store.getters.isTargettingBg;
|
|
},
|
|
canText() {
|
|
return this.$store.getters.isTargettingChar;
|
|
},
|
|
currentFg() {
|
|
return this.$store.getters.currentFg;
|
|
},
|
|
currentBg() {
|
|
return this.$store.getters.currentBg;
|
|
},
|
|
currentChar() {
|
|
return this.$store.getters.getChar;
|
|
},
|
|
toolbarState() {
|
|
return this.$store.getters.toolbarState;
|
|
},
|
|
asciibirdMeta() {
|
|
return this.$store.getters.asciibirdMeta;
|
|
},
|
|
debugPanelState() {
|
|
return this.$store.getters.debugPanel
|
|
},
|
|
},
|
|
methods: {
|
|
openContextMenu(e) {
|
|
e.preventDefault();
|
|
this.$refs.menu.open(e);
|
|
},
|
|
updateCoords(value) {
|
|
this.canvasX = value.x;
|
|
this.canvasY = value.y;
|
|
},
|
|
async onImport() {
|
|
const { files } = this.$refs.asciiInput;
|
|
const filename = files[0].name;
|
|
const fileReader = new FileReader();
|
|
|
|
const _importType = this.importType;
|
|
fileReader.addEventListener("load", () => {
|
|
switch (_importType) {
|
|
case "asb":
|
|
this.importAsciibirdState(fileReader.result, filename);
|
|
break;
|
|
|
|
case "mirc":
|
|
this.mircAsciiImport(fileReader.result, filename);
|
|
break;
|
|
}
|
|
});
|
|
|
|
// This will fire the file reader 'load' event
|
|
const asciiImport = fileReader.readAsText(files[0]);
|
|
},
|
|
startImport(type) {
|
|
// For ANSI we'll need to add back in the
|
|
// type cariable here
|
|
this.importType = type;
|
|
// console.log(this.importType);
|
|
this.$refs.asciiInput.click();
|
|
},
|
|
mircAsciiImport(contents, filename) {
|
|
parseMircAscii(contents, filename)
|
|
},
|
|
importAsciibirdState(fileContents) {
|
|
let contents = JSON.parse(
|
|
LZString.decompressFromEncodedURIComponent(fileContents)
|
|
);
|
|
this.$store.commit("changeState", { ...contents });
|
|
},
|
|
exportAsciibirdState() {
|
|
let output;
|
|
|
|
try {
|
|
output = LZString.compressToEncodedURIComponent(
|
|
JSON.stringify(this.$store.getters.state)
|
|
);
|
|
|
|
// Default timestamp for filename
|
|
const today = new Date();
|
|
const y = today.getFullYear();
|
|
const m = today.getMonth() + 1; // JavaScript months are 0-based.
|
|
const d = today.getDate();
|
|
const h = today.getHours();
|
|
const mi = today.getMinutes();
|
|
const s = today.getSeconds();
|
|
|
|
this.downloadToFile(
|
|
output,
|
|
`asciibird-${y}-${m}-${d}-${h}-${mi}-${s}.asb`,
|
|
"application/gzip"
|
|
);
|
|
} catch (err) {
|
|
console.log(err);
|
|
}
|
|
},
|
|
exportMirc(type) {
|
|
const { currentAscii } = this.$store.getters;
|
|
let blocks = this.$store.getters.currentAsciiBlocks;
|
|
const output = [];
|
|
let curBlock = null;
|
|
let prevBlock = { bg: -1, fg: -1 };
|
|
|
|
for (let y = 0; y <= blocks.length - 1; y++) {
|
|
|
|
if (y >= currentAscii.height) {
|
|
continue;
|
|
}
|
|
|
|
for (let x = 0; x <= blocks[y].length - 1; x++) {
|
|
|
|
if (x >= currentAscii.width) {
|
|
continue;
|
|
}
|
|
|
|
curBlock = blocks[y][x];
|
|
|
|
// If we have a difference between our previous block
|
|
// we'll put a colour codes and continue as normal
|
|
if (curBlock.bg !== prevBlock.bg || curBlock.fg !== prevBlock.fg) {
|
|
Object.assign(curBlock, blocks[y][x]);
|
|
const zeroPad = (num, places) => String(num).padStart(places, "0");
|
|
output.push(
|
|
`\u0003${zeroPad(
|
|
curBlock.fg ?? this.options.defaultFg,
|
|
2
|
|
)},${zeroPad(
|
|
curBlock.bg ?? this.options.defaultBg,
|
|
2
|
|
)}`
|
|
);
|
|
}
|
|
|
|
// null .chars will end up as space
|
|
output.push(curBlock.char ?? " ");
|
|
prevBlock = blocks[y][x];
|
|
}
|
|
|
|
// We can never have a -1 colour code so we'll always
|
|
// write one at the start of each line
|
|
prevBlock = { bg: -1, fg: -1 };
|
|
|
|
// New line except for the very last line
|
|
if (y < blocks.length - 1) {
|
|
output.push("\n");
|
|
}
|
|
}
|
|
|
|
// Download to a txt file
|
|
// Check if txt already exists and append it
|
|
const filename =
|
|
currentAscii.title.slice(currentAscii.title.length - 3) === "txt"
|
|
? currentAscii.title
|
|
: `${currentAscii.title}.txt`;
|
|
|
|
switch (type) {
|
|
case "clipboard":
|
|
this.$copyText(output.join("")).then(
|
|
function (e) {
|
|
alert("Copied");
|
|
console.log(e);
|
|
},
|
|
function (e) {
|
|
alert("Can not copy");
|
|
console.log(e);
|
|
}
|
|
);
|
|
break;
|
|
|
|
default:
|
|
case "file":
|
|
this.downloadToFile(output.join(""), filename, "text/plain");
|
|
break;
|
|
}
|
|
},
|
|
downloadToFile(content, filename, contentType) {
|
|
const downloadToFile = (content, filename, contentType) => {
|
|
const a = document.createElement("a");
|
|
const file = new Blob([content], { type: contentType });
|
|
|
|
a.href = URL.createObjectURL(file);
|
|
a.download = filename;
|
|
a.click();
|
|
|
|
URL.revokeObjectURL(a.href);
|
|
};
|
|
|
|
return downloadToFile(content, filename, contentType);
|
|
},
|
|
changeTab(key, value) {
|
|
// Update the tab index in vuex store
|
|
this.currentTab = key;
|
|
this.$store.commit("changeTab", key);
|
|
},
|
|
clearCache() {
|
|
localStorage.clear();
|
|
window.location.href = "/";
|
|
},
|
|
captureMouse(event) {
|
|
this.dashboardX = event.pageX;
|
|
this.dashboardY = event.pageY;
|
|
},
|
|
},
|
|
};
|
|
</script>
|