diff --git a/.eslintrc.js b/.eslintrc.js index 1d3cb3d..7410a96 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -13,7 +13,7 @@ module.exports = { ], parserOptions: { // parser: 'babel-eslint', - ecmaVersion: 2020, + ecmaVersion: 13, sourceType: 'module', }, rules: { diff --git a/app/Dockerfile b/app/Dockerfile index df72388..484da71 100644 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -2,10 +2,10 @@ FROM node:20-alpine as base WORKDIR /app COPY package.json yarn.lock ./ -COPY app/package.json ./app/package.json COPY packages/file-graph/package.json ./packages/file-graph/package.json COPY packages/utils/package.json ./packages/utils/package.json COPY packages/constants/package.json ./packages/constants/package.json +COPY app/package.json ./app/package.json RUN yarn install --frozen-lockfile # Dev stage diff --git a/app/package.json b/app/package.json index c09dc4e..4fbe9d2 100644 --- a/app/package.json +++ b/app/package.json @@ -20,6 +20,7 @@ "@linux-odyssey/file-graph": "*", "@linux-odyssey/utils": "*", "axios": "^1.6.0", + "echarts": "^5.4.3", "socket.io-client": "^4.7.1", "validator": "^13.11.0", "vue": "^3.3.4", diff --git a/app/src/components/QuestMap.vue b/app/src/components/QuestMap.vue new file mode 100644 index 0000000..18ed7e6 --- /dev/null +++ b/app/src/components/QuestMap.vue @@ -0,0 +1,158 @@ + + + diff --git a/app/src/img/catelogbg.svg b/app/src/img/catelogbg.svg new file mode 100644 index 0000000..892031b --- /dev/null +++ b/app/src/img/catelogbg.svg @@ -0,0 +1,1130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/img/rect.svg b/app/src/img/rect.svg new file mode 100644 index 0000000..15481fd --- /dev/null +++ b/app/src/img/rect.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/src/img/svg.js b/app/src/img/svg.js new file mode 100644 index 0000000..13f5e2c --- /dev/null +++ b/app/src/img/svg.js @@ -0,0 +1,2 @@ +export const NodeImage = + 'path://M0.640015 25.0339C0.640015 11.2268 11.8329 0.0339355 25.64 0.0339355H246.36C260.167 0.0339355 271.36 11.2268 271.36 25.0339V113.14C271.36 126.948 260.167 138.14 246.36 138.14H25.64C11.8329 138.14 0.640015 126.948 0.640015 113.14V25.0339Z' diff --git a/app/src/router.js b/app/src/router.js index 9df8a54..060af3f 100644 --- a/app/src/router.js +++ b/app/src/router.js @@ -3,6 +3,7 @@ import GamePage from './views/GamePage.vue' import LoginPage from './views/LoginPage.vue' import RegisterPage from './views/RegisterPage.vue' import ChooseUsernamePage from './views/ChooseUsernamePage.vue' +import QuestMapPage from './views/QuestMapPage.vue' import { isLoggedIn } from './utils/auth' const routes = [ @@ -40,6 +41,12 @@ const routes = [ component: ChooseUsernamePage, meta: { requiresGuest: true }, }, + { + path: '/map', + name: 'map', + component: QuestMapPage, + meta: { requiresAuth: true }, + }, ] const router = createRouter({ diff --git a/app/src/views/QuestMapPage.vue b/app/src/views/QuestMapPage.vue new file mode 100644 index 0000000..56a904a --- /dev/null +++ b/app/src/views/QuestMapPage.vue @@ -0,0 +1,19 @@ + + + diff --git a/docker-compose.yml b/docker-compose.yml index 3aeaf0b..38e399f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -78,15 +78,6 @@ services: volumes: - ./data:/data/db - swagger: - image: swaggerapi/swagger-editor:latest - ports: - - 8080:8080 - volumes: - - ./server/swagger.yaml:/tmp/swagger.yaml - environment: - - SWAGGER_FILE=/tmp/swagger.yaml - base: image: linuxodyssey/quest-base build: diff --git a/packages/utils/dag.js b/packages/utils/dag.js new file mode 100644 index 0000000..6a3a911 --- /dev/null +++ b/packages/utils/dag.js @@ -0,0 +1,73 @@ +export default class DAG { + #nodes = new Map() + + #edges = new Set() + + #layers = [] + + constructor(nodes) { + this.#nodes = new Map(nodes.map((node) => [node._id, node])) + this.#edges = new Set() + this.#nodes.forEach((_, id) => { + this.#setLayer(id) + this.#addEdges(id) + }) + } + + getNode(id) { + return this.#nodes.get(id) + } + + getNodes() { + return Array.from(this.#nodes.values()) + } + + getEdges() { + return this.#edges + } + + getEdgesArray() { + return Array.from(this.#edges.values()) + } + + getLayers() { + return this.#layers + } + + getLayer(id) { + const node = this.getNode(id) + if (!node) { + throw new Error(`Node ${id} not found`) + } + return this.#layers[node.layer - 1] + } + + #setLayer(id) { + const node = this.getNode(id) + if (!node) { + throw new Error(`Node ${id} not found`) + } + if (node.layer !== undefined) { + return node.layer + } + const previousLayer = node.requirements.map((rId) => this.#setLayer(rId)) + node.layer = Math.max(...previousLayer, 0) + 1 + if (this.#layers.length < node.layer) { + this.#layers.push(1) + } else { + this.#layers[node.layer - 1] += 1 + } + node.index = this.#layers[node.layer - 1] + return node.layer + } + + #addEdges(id) { + const node = this.getNode(id) + if (!node) { + throw new Error(`Node ${id} not found`) + } + node.requirements.forEach((rId) => { + this.#edges.add([rId, id]) + }) + } +} diff --git a/packages/utils/dag.test.js b/packages/utils/dag.test.js new file mode 100644 index 0000000..e296782 --- /dev/null +++ b/packages/utils/dag.test.js @@ -0,0 +1,165 @@ +import DAG from './dag.js' + +describe('dag layers', () => { + it('two layer', () => { + const data = [ + { + _id: 'helloworld', + requirements: [], + }, + { + _id: 'spell', + requirements: ['helloworld'], + }, + ] + const dag = new DAG(data) + expect(dag.getNode('helloworld').layer).toBe(1) + expect(dag.getNode('spell').layer).toBe(2) + }) + it('branch', () => { + const data = [ + { + _id: 'helloworld', + requirements: [], + }, + { + _id: 'spell', + requirements: ['helloworld'], + }, + { + _id: 'discover', + requirements: ['helloworld'], + }, + ] + const dag = new DAG(data) + expect(dag.getNode('helloworld').layer).toBe(1) + expect(dag.getNode('spell').layer).toBe(2) + expect(dag.getNode('discover').layer).toBe(2) + }) + it('merge', () => { + const data = [ + { + _id: 'A', + requirements: [], + }, + { + _id: 'B', + requirements: [], + }, + { + _id: 'C', + requirements: ['A', 'B'], + }, + ] + const dag = new DAG(data) + expect(dag.getNode('A').layer).toBe(1) + expect(dag.getNode('B').layer).toBe(1) + expect(dag.getNode('C').layer).toBe(2) + }) + it('skip', () => { + const data = [ + { + _id: 'A', + requirements: [], + }, + { + _id: 'B', + requirements: [], + }, + { + _id: 'C', + requirements: ['A'], + }, + { + _id: 'D', + requirements: ['B', 'C'], + }, + ] + const dag = new DAG(data) + expect(dag.getNode('A').layer).toBe(1) + expect(dag.getNode('B').layer).toBe(1) + expect(dag.getNode('C').layer).toBe(2) + expect(dag.getNode('D').layer).toBe(3) + }) +}) + +describe('dag edges', () => { + it('one edge', () => { + const data = [ + { + _id: 'helloworld', + requirements: [], + }, + { + _id: 'spell', + requirements: ['helloworld'], + }, + ] + + const dag = new DAG(data) + expect(dag.getEdges()).toEqual(new Set([['helloworld', 'spell']])) + }) + + it('branch edges', () => { + const data = [ + { + _id: 'helloworld', + requirements: [], + }, + { + _id: 'spell', + requirements: ['helloworld'], + }, + { + _id: 'discover', + requirements: ['helloworld'], + }, + ] + + const dag = new DAG(data) + expect(dag.getEdges()).toEqual( + new Set([ + ['helloworld', 'discover'], + ['helloworld', 'spell'], + ]) + ) + }) +}) + +describe('dag layers', () => { + it('two layer', () => { + const data = [ + { + _id: 'helloworld', + requirements: [], + }, + { + _id: 'spell', + requirements: ['helloworld'], + }, + ] + + const dag = new DAG(data) + expect(dag.getLayers()).toEqual([1, 1]) + }) + + it('branch layers', () => { + const data = [ + { + _id: 'helloworld', + requirements: [], + }, + { + _id: 'spell', + requirements: ['helloworld'], + }, + { + _id: 'discover', + requirements: ['helloworld'], + }, + ] + + const dag = new DAG(data) + expect(dag.getLayers()).toEqual([1, 2]) + }) +}) diff --git a/packages/utils/index.js b/packages/utils/index.js index 8d79493..83029e5 100644 --- a/packages/utils/index.js +++ b/packages/utils/index.js @@ -1,2 +1,3 @@ export { isValidEmail, isValidUsername } from './auth.js' export { buildFileCheckCmd } from './commands.js' +export { default as DAG } from './dag.js' diff --git a/yarn.lock b/yarn.lock index bd4e3ec..9f3d531 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2367,6 +2367,14 @@ ecdsa-sig-formatter@1.0.11: dependencies: safe-buffer "^5.0.1" +echarts@^5.4.3: + version "5.4.3" + resolved "https://registry.yarnpkg.com/echarts/-/echarts-5.4.3.tgz#f5522ef24419164903eedcfd2b506c6fc91fb20c" + integrity sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA== + dependencies: + tslib "2.3.0" + zrender "5.4.4" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -6238,6 +6246,11 @@ tsconfig-paths@^3.14.1: minimist "^1.2.6" strip-bom "^3.0.0" +tslib@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" + integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== + tslib@^2.1.0: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" @@ -6691,3 +6704,10 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zrender@5.4.4: + version "5.4.4" + resolved "https://registry.yarnpkg.com/zrender/-/zrender-5.4.4.tgz#8854f1d95ecc82cf8912f5a11f86657cb8c9e261" + integrity sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw== + dependencies: + tslib "2.3.0"