Initial commit
This commit is contained in:
32
.gitignore
vendored
Normal file
32
.gitignore
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
.env
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["Vue.volar"]
|
||||||
|
}
|
||||||
29
README.md
Normal file
29
README.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# alttp-randomizer-frontend
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 in Vite.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||||
|
|
||||||
|
## Customize configuration
|
||||||
|
|
||||||
|
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||||
|
|
||||||
|
## Project Setup
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Hot-Reload for Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Minify for Production
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
13
index.html
Normal file
13
index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Vite App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
8
jsconfig.json
Normal file
8
jsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
3072
package-lock.json
generated
Normal file
3072
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
27
package.json
Normal file
27
package.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "alttp-randomizer-frontend",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.8.1",
|
||||||
|
"bootstrap": "^5.3.3",
|
||||||
|
"bps": "^2.0.1",
|
||||||
|
"center-align": "^1.0.1",
|
||||||
|
"crc-32": "^1.2.2",
|
||||||
|
"js-base64": "^3.7.7",
|
||||||
|
"localforage": "^1.10.0",
|
||||||
|
"vue": "^3.5.13",
|
||||||
|
"vue-router": "^4.5.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
|
"vite": "^6.1.0",
|
||||||
|
"vite-plugin-vue-devtools": "^7.7.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
15
src/App.vue
Normal file
15
src/App.vue
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<script setup>
|
||||||
|
import { RouterLink, RouterView } from "vue-router";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<header>
|
||||||
|
<div class="wrapper">
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<RouterView />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
||||||
117
src/ZSPR.js
Normal file
117
src/ZSPR.js
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import center from "center-align";
|
||||||
|
|
||||||
|
export default class ZSPR {
|
||||||
|
constructor(buffer) {
|
||||||
|
this.patch = buffer;
|
||||||
|
const dec = new TextDecoder("utf-8");
|
||||||
|
const header = dec.decode(buffer.subarray(0, 4));
|
||||||
|
this.valid = (header == 'ZSPR');
|
||||||
|
if (this.valid) {
|
||||||
|
this._parse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_parse() {
|
||||||
|
this.gfxOffset = (this.patch[12] << 24) | (this.patch[11] << 16) | (this.patch[10] << 8) | this.patch[9];
|
||||||
|
this.palOffset = (this.patch[18] << 24) | (this.patch[17] << 16) | (this.patch[16] << 8) | this.patch[15];
|
||||||
|
const metadataOffset = 0x1D;
|
||||||
|
const dec8 = new TextDecoder("utf-8");
|
||||||
|
const dec16 = new TextDecoder("utf-16");
|
||||||
|
|
||||||
|
var start = metadataOffset;
|
||||||
|
var index = start;
|
||||||
|
while (index < this.gfxOffset) {
|
||||||
|
if (this.patch[index] == 0 && this.patch[index + 1] == 0) {
|
||||||
|
this.spriteName = dec16.decode(this.patch.subarray(start, index));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
index += 2;
|
||||||
|
}
|
||||||
|
index += 2;
|
||||||
|
|
||||||
|
start = index;
|
||||||
|
while (index < this.gfxOffset) {
|
||||||
|
if (this.patch[index] == 0 && this.patch[index + 1] == 0) {
|
||||||
|
this.spriteAuthor = dec16.decode(this.patch.subarray(start, index));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
index += 2;
|
||||||
|
}
|
||||||
|
index += 2;
|
||||||
|
|
||||||
|
start = index;
|
||||||
|
while (index < this.gfxOffset) {
|
||||||
|
if (this.patch[index] == 0) {
|
||||||
|
this.spriteAuthorShort = dec8.decode(this.patch.subarray(start, index));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply(rom) {
|
||||||
|
if (!this.valid) {
|
||||||
|
throw "Invalid Patch";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.gfxOffset != 0xFFFFFFFF) {
|
||||||
|
rom.set(this.patch.subarray(this.gfxOffset, this.gfxOffset + 0x7000), 0x080000);
|
||||||
|
}
|
||||||
|
|
||||||
|
rom.set(this.patch.subarray(this.palOffset, this.palOffset + 120), 0x0DD308);
|
||||||
|
rom.set(this.patch.subarray(this.palOffset + 120, this.palOffset + 124), 0x0DEDF5);
|
||||||
|
|
||||||
|
if (rom[0x118000] === 0x02 && rom[0x118001] === 0x37
|
||||||
|
&& rom[0x11801E] === 0x02 && rom[0x11801F] === 0x37) {
|
||||||
|
var author = center(this.spriteAuthorShort.substring(0, 28), 28);
|
||||||
|
if (author.length == 27) {
|
||||||
|
author += " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
const [tophalf, bottomhalf] = format_author(author);
|
||||||
|
|
||||||
|
rom.set(tophalf, 0x118002);
|
||||||
|
rom.set(bottomhalf, 0x11801F);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function format_author(name) {
|
||||||
|
const tophalf = [];
|
||||||
|
const bothalf = [];
|
||||||
|
|
||||||
|
for (var chr of name.split("")) {
|
||||||
|
if (chr >= "0" && chr <= "9") {
|
||||||
|
tophalf.push(chr.charCodeAt(0) - "0".charCodeAt(0) + 0x53);
|
||||||
|
bothalf.push(chr.charCodeAt(0) - "0".charCodeAt(0) + 0x79);
|
||||||
|
} else if (chr >= "A" && chr <= "Z") {
|
||||||
|
tophalf.push(chr.charCodeAt(0) - "A".charCodeAt(0) + 0x5D);
|
||||||
|
bothalf.push(chr.charCodeAt(0) - "A".charCodeAt(0) + 0x83);
|
||||||
|
} else if (chr >= "a" && chr <= "z") {
|
||||||
|
tophalf.push(chr.charCodeAt(0) - "a".charCodeAt(0) + 0x5D);
|
||||||
|
bothalf.push(chr.charCodeAt(0) - "a".charCodeAt(0) + 0x83);
|
||||||
|
} else if (chr == "'") {
|
||||||
|
tophalf.push(0x77);
|
||||||
|
bothalf.push(0x9D);
|
||||||
|
} else if (chr == ".") {
|
||||||
|
tophalf.push(0xA0);
|
||||||
|
bothalf.push(0xC0);
|
||||||
|
} else if (chr == "/") {
|
||||||
|
tophalf.push(0xA2);
|
||||||
|
bothalf.push(0xC2);
|
||||||
|
} else if (chr == ":") {
|
||||||
|
tophalf.push(0xA3);
|
||||||
|
bothalf.push(0xC3);
|
||||||
|
} else if (chr == "_") {
|
||||||
|
tophalf.push(0xA6);
|
||||||
|
bothalf.push(0xC6);
|
||||||
|
} else {
|
||||||
|
tophalf.push(0x9F);
|
||||||
|
bothalf.push(0x9F);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [new Uint8Array(tophalf), new Uint8Array(bothalf)];
|
||||||
|
}
|
||||||
1
src/assets/logo.svg
Normal file
1
src/assets/logo.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
||||||
|
After Width: | Height: | Size: 276 B |
9
src/assets/main.css
Normal file
9
src/assets/main.css
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#app {
|
||||||
|
max-width: 30rem;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invalid {
|
||||||
|
text-align: center;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
158
src/components/Seed.vue
Normal file
158
src/components/Seed.vue
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
<script>
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
|
import SpritePicker from "@/components/SpritePicker.vue";
|
||||||
|
|
||||||
|
import { Base64 } from "js-base64";
|
||||||
|
import * as bps from "bps";
|
||||||
|
import CRC32 from "crc-32";
|
||||||
|
import localforage from "localforage";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
SpritePicker,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
rom_checksum: "3322EFFC",
|
||||||
|
baserom: null,
|
||||||
|
baserom_error: null,
|
||||||
|
sprite: null,
|
||||||
|
patch: null,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
id: "",
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
document.title = `ALttPRandomizer: ${this.id}`;
|
||||||
|
const file = await localforage.getItem("baserom");
|
||||||
|
if (file) {
|
||||||
|
const dataTransfer = new DataTransfer();
|
||||||
|
dataTransfer.items.add(file);
|
||||||
|
document.getElementById("rom-input").files = dataTransfer.files;
|
||||||
|
this.uploadBaseRom(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await axios.get(`/seed/${this.id}`);
|
||||||
|
|
||||||
|
if (response && response.data && response.data["patch.bps"]) {
|
||||||
|
const seedData = response.data;
|
||||||
|
const patchArray = Base64.toUint8Array(seedData["patch.bps"]);
|
||||||
|
try {
|
||||||
|
const { instructions, _ } = bps.parse(patchArray);
|
||||||
|
const sourceChecksum = instructions.sourceChecksum.toString(16).toUpperCase();
|
||||||
|
if (sourceChecksum == this.rom_checksum) {
|
||||||
|
this.patch = instructions;
|
||||||
|
} else {
|
||||||
|
this.error = "Patch does not specify correct source checksum.";
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
this.error = "Error parsing patch.";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(response.data);
|
||||||
|
this.error = "Error loading seed.";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
permalink() {
|
||||||
|
return `/seed/${this.id}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
uploadBaseRom(file) {
|
||||||
|
if (!file) {
|
||||||
|
this.baserom_error = null;
|
||||||
|
this.baserom = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function() {
|
||||||
|
const buffer = new Uint8Array(reader.result);
|
||||||
|
const crc = (CRC32.buf(buffer, 0) >>> 0).toString(16).toUpperCase();
|
||||||
|
|
||||||
|
if (crc != this.rom_checksum) {
|
||||||
|
this.baserom_error = `Expected CRC ${this.rom_checksum}, but got ${crc}`;
|
||||||
|
this.baserom = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.baserom_error = null;
|
||||||
|
this.baserom = buffer;
|
||||||
|
localforage.setItem("baserom", file);
|
||||||
|
}.bind(this);
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
},
|
||||||
|
spriteUpdate(sprite) {
|
||||||
|
this.sprite = sprite;
|
||||||
|
},
|
||||||
|
async patchRom() {
|
||||||
|
var rom = bps.apply(this.patch, this.baserom);
|
||||||
|
|
||||||
|
if (this.sprite) {
|
||||||
|
this.sprite.apply(rom);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix Checksum
|
||||||
|
const sum = rom.reduce(function(sum, mbyte, i) {
|
||||||
|
if (i >= 0x7fdc && i < 0x7fe0) {
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
return sum + mbyte;
|
||||||
|
});
|
||||||
|
const checksum = (sum + 0x01FE) & 0xFFFF;
|
||||||
|
const inverse = checksum ^ 0xFFFF;
|
||||||
|
rom[0x7FDC] = inverse & 0xFF;
|
||||||
|
rom[0x7FDD] = inverse >> 8;
|
||||||
|
rom[0x7FDE] = checksum & 0xFF;
|
||||||
|
rom[0x7FDF] = checksum >> 8;
|
||||||
|
|
||||||
|
const blob = new Blob([rom], { type: 'octet/stream' });
|
||||||
|
const link = document.getElementById('downloader');
|
||||||
|
link.href = URL.createObjectURL(blob);
|
||||||
|
link.download = `GK_${this.id}.sfc`;
|
||||||
|
link.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="card content-div mt-3 mb-3">
|
||||||
|
<div class="card-header">
|
||||||
|
Permalink: <a :href="permalink">{{ permalink }}</a>
|
||||||
|
</div>
|
||||||
|
<div v-if="error" class="card-header">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
<li class="list-group-item">
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="rom-input" class="form-label">
|
||||||
|
The Legend of Zelda: A Link to the Past (JP 1.0) Rom:
|
||||||
|
</label>
|
||||||
|
<input id="rom-input" class="form-control" type="file" accept=".sfc,.smc" @change="uploadBaseRom($event.target.files[0])" />
|
||||||
|
<div v-if="baserom_error" class="invalid">
|
||||||
|
{{ baserom_error }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item">
|
||||||
|
<div class="mb-2">
|
||||||
|
<SpritePicker @spriteUpdate="spriteUpdate" />
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item">
|
||||||
|
<button type="submit" class="btn btn-primary submit-btn" :disabled="!baserom || !patch" @click="patchRom">
|
||||||
|
Download Seed!
|
||||||
|
</button>
|
||||||
|
<a id="downloader" style="display: none;" />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
62
src/components/SpritePicker.vue
Normal file
62
src/components/SpritePicker.vue
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<script>
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
import ZSPR from "@/ZSPR.js";
|
||||||
|
import localforage from "localforage";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
sprite: null,
|
||||||
|
sprite_error: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
uploadSprite(file) {
|
||||||
|
console.log(file);
|
||||||
|
if (!file) {
|
||||||
|
this.sprite_error = null;
|
||||||
|
this.sprite = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function() {
|
||||||
|
const buffer = new Uint8Array(reader.result);
|
||||||
|
const zspr = new ZSPR(buffer);
|
||||||
|
|
||||||
|
if (!zspr.valid) {
|
||||||
|
this.sprite_error = "Invalid sprite file";
|
||||||
|
this.sprite = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sprite_error = null;
|
||||||
|
this.sprite = zspr;
|
||||||
|
localforage.setItem("sprite", file);
|
||||||
|
this.$emit("spriteUpdate", this.sprite);
|
||||||
|
}.bind(this);
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<label for="sprite-input" class="form-label">
|
||||||
|
Custom Sprite:
|
||||||
|
<template v-if="sprite">
|
||||||
|
{{ sprite.spriteName }} by {{ sprite.spriteAuthor }}
|
||||||
|
</template>
|
||||||
|
</label>
|
||||||
|
<input id="sprite-input" class="form-control" type="file" accept=".zspr" @change="uploadSprite($event.target.files[0])" />
|
||||||
|
<div v-if="sprite_error" class="invalid">
|
||||||
|
{{ sprite_error }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
17
src/main.js
Normal file
17
src/main.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { createApp } from "vue";
|
||||||
|
import App from "./App.vue";
|
||||||
|
import router from "./router";
|
||||||
|
|
||||||
|
import "bootstrap/dist/css/bootstrap.min.css";
|
||||||
|
import "bootstrap";
|
||||||
|
|
||||||
|
import "./assets/main.css";
|
||||||
|
|
||||||
|
import axios from "axios";
|
||||||
|
axios.defaults.baseURL = import.meta.env.VITE_BACKEND_URL;
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
|
||||||
|
app.use(router)
|
||||||
|
|
||||||
|
app.mount('#app')
|
||||||
15
src/router/index.js
Normal file
15
src/router/index.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { createRouter, createWebHistory } from "vue-router";
|
||||||
|
import SeedView from "@/views/SeedView.vue";
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/seed/:id',
|
||||||
|
name: 'game',
|
||||||
|
component: SeedView,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
15
src/views/SeedView.vue
Normal file
15
src/views/SeedView.vue
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<script>
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
import Seed from '../components/Seed.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
Seed,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Seed :id="$route.params.id" />
|
||||||
|
</template>
|
||||||
18
vite.config.js
Normal file
18
vite.config.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
vueDevTools(),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user