First Commit
This commit is contained in:
commit
d3d51152cf
15
.eslintrc.cjs
Normal file
15
.eslintrc.cjs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/* eslint-env node */
|
||||||
|
require('@rushstack/eslint-patch/modern-module-resolution')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
'extends': [
|
||||||
|
'plugin:vue/vue3-essential',
|
||||||
|
'eslint:recommended',
|
||||||
|
'@vue/eslint-config-typescript',
|
||||||
|
'@vue/eslint-config-prettier/skip-formatting'
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest'
|
||||||
|
}
|
||||||
|
}
|
30
.gitignore
vendored
Normal file
30
.gitignore
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# 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
|
8
.prettierrc.json
Normal file
8
.prettierrc.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/prettierrc",
|
||||||
|
"semi": false,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 100,
|
||||||
|
"trailingComma": "none"
|
||||||
|
}
|
9
Dockerfile
Normal file
9
Dockerfile
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
FROM node:lts-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . ./
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
EXPOSE 5173
|
||||||
|
|
||||||
|
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
|
13
index.html
Normal file
13
index.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<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.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
4493
package-lock.json
generated
Normal file
4493
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
44
package.json
Normal file
44
package.json
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"name": "client",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "run-p type-check \"build-only {@}\" --",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"build-only": "vite build",
|
||||||
|
"type-check": "vue-tsc --build --force",
|
||||||
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||||
|
"format": "prettier --write src/"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@vueuse/core": "^10.9.0",
|
||||||
|
"axios": "^1.6.7",
|
||||||
|
"pinia": "^2.1.7",
|
||||||
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
|
"tailwind-merge": "^2.2.1",
|
||||||
|
"vue": "^3.4.15",
|
||||||
|
"vue-router": "^4.2.5",
|
||||||
|
"vue3-dnd": "^2.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@rushstack/eslint-patch": "^1.3.3",
|
||||||
|
"@tsconfig/node20": "^20.1.2",
|
||||||
|
"@types/node": "^20.11.10",
|
||||||
|
"@vitejs/plugin-vue": "^5.0.3",
|
||||||
|
"@vue/eslint-config-prettier": "^8.0.0",
|
||||||
|
"@vue/eslint-config-typescript": "^12.0.0",
|
||||||
|
"@vue/tsconfig": "^0.5.1",
|
||||||
|
"autoprefixer": "^10.4.18",
|
||||||
|
"eslint": "^8.49.0",
|
||||||
|
"eslint-plugin-vue": "^9.17.0",
|
||||||
|
"npm-run-all2": "^6.1.1",
|
||||||
|
"postcss": "^8.4.35",
|
||||||
|
"prettier": "^3.0.3",
|
||||||
|
"tailwindcss": "^3.4.1",
|
||||||
|
"typescript": "~5.3.0",
|
||||||
|
"vite": "^5.0.11",
|
||||||
|
"vue-tsc": "^1.8.27"
|
||||||
|
}
|
||||||
|
}
|
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
16
src/App.vue
Normal file
16
src/App.vue
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {RouterLink, RouterView} from 'vue-router'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="bg-gray-50 min-h-screen pt-24 px-4">
|
||||||
|
<div class="">
|
||||||
|
<RouterView/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
3
src/assets/main.css
Normal file
3
src/assets/main.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
26
src/components/AvailableResources.vue
Normal file
26
src/components/AvailableResources.vue
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Resource from "@/components/Resource.vue";
|
||||||
|
import {useResourcesStore} from "@/stores/useResourcesStore";
|
||||||
|
import {storeToRefs} from "pinia";
|
||||||
|
import {computed, ref} from "vue";
|
||||||
|
const store = useResourcesStore()
|
||||||
|
const {resources} = storeToRefs(store)
|
||||||
|
const searchTerm = ref('')
|
||||||
|
|
||||||
|
const filteredResources = computed(() => {
|
||||||
|
return resources.value.filter((resource) => {
|
||||||
|
return resource.title.toLowerCase().includes(searchTerm.value.toLowerCase())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex gap-3 flex-wrap pt-3">
|
||||||
|
<input v-model="searchTerm" type="text" class="w-full border border-gray-300 rounded-lg px-3 py-2" placeholder="단어 검색하기">
|
||||||
|
<Resource v-for="resource in filteredResources" :key="resource.title" :title="resource.title" :emoji="resource.emoji"></Resource>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
52
src/components/Box.vue
Normal file
52
src/components/Box.vue
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import {useDrag} from 'vue3-dnd'
|
||||||
|
import {ItemTypes} from './ItemTypes'
|
||||||
|
import {toRefs} from '@vueuse/core'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
id: any
|
||||||
|
left: number
|
||||||
|
top: number
|
||||||
|
hideSourceOnDrag?: boolean
|
||||||
|
loading?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const [collect, drag] = useDrag(() => ({
|
||||||
|
type: ItemTypes.BOX,
|
||||||
|
item: {id: props.id, left: props.left, top: props.top},
|
||||||
|
collect: monitor => ({
|
||||||
|
isDragging: monitor.isDragging(),
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
const {isDragging} = toRefs(collect)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="isDragging && hideSourceOnDrag" :ref="drag"/>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
:ref="drag"
|
||||||
|
class="absolute"
|
||||||
|
:style="{ left: `${left}px`, top: `${top}px` }"
|
||||||
|
role="Box"
|
||||||
|
data-testid="box"
|
||||||
|
>
|
||||||
|
<div v-if="loading">
|
||||||
|
<div
|
||||||
|
class="border-gray-200 shadow hover:bg-gray-100 cursor-pointer transition inline-flex items-center text-2xl space-x-2.5 py-2.5 px-4 font-medium border rounded-lg ">
|
||||||
|
<svg class="animate-spin -ml-1 mr-2 h-6 w-6 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
|
</svg>
|
||||||
|
<span>
|
||||||
|
생성중
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<slot v-else></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
87
src/components/Container.vue
Normal file
87
src/components/Container.vue
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import {useDrop, XYCoord} from 'vue3-dnd'
|
||||||
|
import {ItemTypes} from './ItemTypes'
|
||||||
|
import Box from './Box.vue'
|
||||||
|
import type {DragItem} from './interfaces'
|
||||||
|
import {reactive, ref} from 'vue'
|
||||||
|
import ItemCard from "@/components/ItemCard.vue";
|
||||||
|
import AvailableResources from "@/components/AvailableResources.vue";
|
||||||
|
import {useBoxesStore} from "@/stores/useBoxesStore";
|
||||||
|
import {storeToRefs} from "pinia";
|
||||||
|
|
||||||
|
const store = useBoxesStore()
|
||||||
|
const { boxes } = store
|
||||||
|
const moveBox = (id: string, left: number, top: number, title?: string, emoji?: string) => {
|
||||||
|
if (id) {
|
||||||
|
Object.assign(boxes[id], {left, top})
|
||||||
|
} else {
|
||||||
|
const key = Math.random().toString(36).substring(7);
|
||||||
|
boxes[key] = {top, left, title, emoji}
|
||||||
|
console.log(boxes)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const containerElement = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
|
const [, drop] = useDrop(() => ({
|
||||||
|
accept: ItemTypes.BOX,
|
||||||
|
drop(item: DragItem, monitor) {
|
||||||
|
if (item.id && item.left !== null && item.top !== null) {
|
||||||
|
const delta = monitor.getDifferenceFromInitialOffset() as XYCoord
|
||||||
|
if(delta && delta.x && delta.y){
|
||||||
|
const left = Math.round((item.left) + delta.x)
|
||||||
|
const top = Math.round((item.top) + delta.y )
|
||||||
|
moveBox(item.id, left, top)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const delta = monitor.getClientOffset() as XYCoord
|
||||||
|
// current mouse position relative to drop
|
||||||
|
const containerCoords = containerElement.value.getBoundingClientRect()
|
||||||
|
if(delta && delta.x && delta.y){
|
||||||
|
const left = Math.round(delta.x - containerCoords.left - 40)
|
||||||
|
const top = Math.round(delta.y - containerCoords.top - 15)
|
||||||
|
moveBox(null, left, top, item.title, item.emoji)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="containerElement">
|
||||||
|
|
||||||
|
<main class="flex gap-x-3">
|
||||||
|
<div class="w-3/4">
|
||||||
|
<div :ref="drop" class="container">
|
||||||
|
<Box
|
||||||
|
v-for="(value, key) in boxes"
|
||||||
|
:id="key"
|
||||||
|
:key="key"
|
||||||
|
:left="value.left"
|
||||||
|
:top="value.top"
|
||||||
|
:loading="value.loading"
|
||||||
|
>
|
||||||
|
<ItemCard size="large" :id="key" :title="value.title" :emoji="value.emoji"/>
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-1/4 bg-white shadow px-4 py-3 border-gray-200 border rounded-lg overflow-y-scroll max-h-[80vh]">
|
||||||
|
<h2 class="font-semibold">단어</h2>
|
||||||
|
<AvailableResources></AvailableResources>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 90vh;
|
||||||
|
}
|
||||||
|
</style>
|
15
src/components/Example.vue
Normal file
15
src/components/Example.vue
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Container from './Container.vue'
|
||||||
|
|
||||||
|
import { DndProvider } from 'vue3-dnd'
|
||||||
|
import { HTML5Backend } from 'react-dnd-html5-backend'
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<DndProvider :backend="HTML5Backend">
|
||||||
|
<Container />
|
||||||
|
</DndProvider>
|
||||||
|
</div>
|
||||||
|
</template>
|
77
src/components/ItemCard.vue
Normal file
77
src/components/ItemCard.vue
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {useDrop} from "vue3-dnd";
|
||||||
|
import {ItemTypes} from "@/components/ItemTypes";
|
||||||
|
import {DragItem} from "@/components/interfaces";
|
||||||
|
import {useBoxesStore} from "@/stores/useBoxesStore";
|
||||||
|
import axios from "axios";
|
||||||
|
import {useResourcesStore} from "@/stores/useResourcesStore";
|
||||||
|
import {storeToRefs} from "pinia";
|
||||||
|
import {twMerge} from "tailwind-merge";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
title: string;
|
||||||
|
emoji: string;
|
||||||
|
id: string;
|
||||||
|
size: 'small' | 'large';
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const store = useBoxesStore()
|
||||||
|
const {removeBox, addBox} = store
|
||||||
|
const {resources} = storeToRefs(useResourcesStore())
|
||||||
|
const {addResource} =useResourcesStore()
|
||||||
|
|
||||||
|
const [, drop] = useDrop(() => ({
|
||||||
|
accept: ItemTypes.BOX,
|
||||||
|
async drop(item: DragItem, monitor) {
|
||||||
|
if (item.id !== props.id) {
|
||||||
|
const droppedId = item?.id;
|
||||||
|
const secondTitle = store.boxes[droppedId]?.title ?? item?.title
|
||||||
|
const secondEmoji = store.boxes[droppedId]?.emoji ?? item?.emoji
|
||||||
|
if(droppedId){
|
||||||
|
removeBox(droppedId);
|
||||||
|
}
|
||||||
|
const second = `${secondEmoji} ${secondTitle}`
|
||||||
|
|
||||||
|
store.boxes[props.id].loading = true
|
||||||
|
const response = await axios.post('https://craft.runa.pw/v1/merge', {
|
||||||
|
first_word: `${store.boxes[props.id].emoji} ${store.boxes[props.id].title}`,
|
||||||
|
second_word: second
|
||||||
|
})
|
||||||
|
|
||||||
|
const resultAnswer = response.data.response.word !== '' ? response.data.response.word : store.boxes[props.id].title
|
||||||
|
const resultEmoji = response.data.response.emoji !== '' ? response.data.response.emoji : store.boxes[props.id].emoji
|
||||||
|
|
||||||
|
addBox({
|
||||||
|
title: resultAnswer,
|
||||||
|
emoji: resultEmoji,
|
||||||
|
left: store.boxes[props.id].left,
|
||||||
|
top: store.boxes[props.id].top
|
||||||
|
})
|
||||||
|
if(!resources.value.find((resource: { title: string; }) => resource.title === resultAnswer)){
|
||||||
|
addResource({
|
||||||
|
title: resultAnswer,
|
||||||
|
emoji: resultEmoji
|
||||||
|
})
|
||||||
|
}
|
||||||
|
removeBox(props.id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div :ref="drop"
|
||||||
|
:class="twMerge(props.size === 'large' ? 'text-2xl space-x-2.5 py-2.5 px-4' : 'space-x-1.5 px-3 py-1','border-gray-200 bg-white shadow hover:bg-gray-100 cursor-pointer transition inline-block font-medium border rounded-lg ')">
|
||||||
|
<span>
|
||||||
|
{{ emoji }}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{{ title }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
3
src/components/ItemTypes.ts
Normal file
3
src/components/ItemTypes.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const ItemTypes = {
|
||||||
|
BOX: 'box',
|
||||||
|
}
|
34
src/components/Resource.vue
Normal file
34
src/components/Resource.vue
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useDrag } from 'vue3-dnd'
|
||||||
|
import { ItemTypes } from './ItemTypes'
|
||||||
|
import { toRefs } from '@vueuse/core'
|
||||||
|
import ItemCard from "@/components/ItemCard.vue";
|
||||||
|
const props = defineProps<{
|
||||||
|
emoji: string
|
||||||
|
title: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const [collect, drag] = useDrag(() => ({
|
||||||
|
type: ItemTypes.BOX,
|
||||||
|
item: { title: props.title, emoji: props.emoji },
|
||||||
|
collect: monitor => ({
|
||||||
|
isDragging: monitor.isDragging(),
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
const { isDragging } = toRefs(collect)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="inline-block"
|
||||||
|
:ref="drag"
|
||||||
|
role="Box"
|
||||||
|
data-testid="box"
|
||||||
|
>
|
||||||
|
<ItemCard :title="title" :emoji="emoji"></ItemCard>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
3
src/components/index.ts
Normal file
3
src/components/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import Example from './Example.vue'
|
||||||
|
|
||||||
|
export default Example
|
8
src/components/interfaces.ts
Normal file
8
src/components/interfaces.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export interface DragItem {
|
||||||
|
type: string
|
||||||
|
id: string
|
||||||
|
top: number|null
|
||||||
|
left: number|null
|
||||||
|
emoji: string
|
||||||
|
title: string
|
||||||
|
}
|
14
src/main.ts
Normal file
14
src/main.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import './assets/main.css'
|
||||||
|
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
|
||||||
|
app.use(createPinia())
|
||||||
|
app.use(router)
|
||||||
|
|
||||||
|
app.mount('#app')
|
23
src/router/index.ts
Normal file
23
src/router/index.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import HomeView from '../views/HomeView.vue'
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'home',
|
||||||
|
component: HomeView
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/about',
|
||||||
|
name: 'about',
|
||||||
|
// route level code-splitting
|
||||||
|
// this generates a separate chunk (About.[hash].js) for this route
|
||||||
|
// which is lazy-loaded when the route is visited.
|
||||||
|
component: () => import('../views/AboutView.vue')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
30
src/stores/useBoxesStore.ts
Normal file
30
src/stores/useBoxesStore.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import {reactive} from "vue";
|
||||||
|
|
||||||
|
export interface BoxStoreEntry {
|
||||||
|
top: number
|
||||||
|
left: number
|
||||||
|
title: string
|
||||||
|
emoji: string
|
||||||
|
loading?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useBoxesStore = defineStore('counter', () => {
|
||||||
|
const boxes = reactive<{
|
||||||
|
[key: string]: BoxStoreEntry
|
||||||
|
}>({
|
||||||
|
a: {top: 20, left: 80, title: '불', emoji: '🔥'},
|
||||||
|
})
|
||||||
|
|
||||||
|
function addBox(box: BoxStoreEntry) {
|
||||||
|
const randomId = Math.random().toString(36).substr(2, 5)
|
||||||
|
boxes[randomId] = box
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeBox(id: string) {
|
||||||
|
delete boxes[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
return { boxes , removeBox, addBox}
|
||||||
|
})
|
28
src/stores/useResourcesStore.ts
Normal file
28
src/stores/useResourcesStore.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import {ref} from 'vue'
|
||||||
|
import {defineStore} from 'pinia'
|
||||||
|
import {useLocalStorage} from "@vueuse/core";
|
||||||
|
|
||||||
|
export interface ResourceStoreEntry {
|
||||||
|
title: string
|
||||||
|
emoji: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useResourcesStore = defineStore('resources', () => {
|
||||||
|
const resources =
|
||||||
|
useLocalStorage<ResourceStoreEntry[]>('opencraft/resources', [
|
||||||
|
{title: '불', emoji: '🔥'},
|
||||||
|
{title: '물', emoji: '💧'},
|
||||||
|
{title: '지구', emoji: '🌍'},
|
||||||
|
{title: '공기', emoji: '💨'},
|
||||||
|
{title: '빛', emoji: '💡'},
|
||||||
|
{title: '어둠', emoji: '🌑'},
|
||||||
|
{title: '생명', emoji: '🌱'},
|
||||||
|
{title: '시간', emoji: '⏳'},
|
||||||
|
{title: '사람', emoji: '👤'},
|
||||||
|
]);
|
||||||
|
function addResource(box: ResourceStoreEntry) {
|
||||||
|
resources.value.push(box)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { resources, addResource}
|
||||||
|
})
|
15
src/views/AboutView.vue
Normal file
15
src/views/AboutView.vue
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<div class="about">
|
||||||
|
<h1>This is an about page</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.about {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
11
src/views/HomeView.vue
Normal file
11
src/views/HomeView.vue
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Example from "@/components/Example.vue";
|
||||||
|
import ItemCard from "@/components/ItemCard.vue";
|
||||||
|
import Resource from "@/components/Resource.vue";
|
||||||
|
import AvaliableResources from "@/components/AvailableResources.vue";
|
||||||
|
import Container from "@/components/Container.vue";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Example></Example>
|
||||||
|
</template>
|
12
tailwind.config.js
Normal file
12
tailwind.config.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: [
|
||||||
|
"./index.html",
|
||||||
|
"./src/**/*.{vue,js,ts,jsx,tsx}",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
||||||
|
|
14
tsconfig.app.json
Normal file
14
tsconfig.app.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
|
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||||
|
"exclude": ["src/**/__tests__/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
tsconfig.json
Normal file
11
tsconfig.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.app.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
19
tsconfig.node.json
Normal file
19
tsconfig.node.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"extends": "@tsconfig/node20/tsconfig.json",
|
||||||
|
"include": [
|
||||||
|
"vite.config.*",
|
||||||
|
"vitest.config.*",
|
||||||
|
"cypress.config.*",
|
||||||
|
"nightwatch.conf.*",
|
||||||
|
"playwright.config.*"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"types": ["node"]
|
||||||
|
}
|
||||||
|
}
|
16
vite.config.ts
Normal file
16
vite.config.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user