This commit is contained in:
BinarySandia04 2024-09-15 17:25:50 +02:00
parent 5a5fa05667
commit 679cc8d7f4
26 changed files with 602 additions and 359 deletions

188
client/package-lock.json generated
View File

@ -17,7 +17,7 @@
"jquery": "^3.7.1",
"jquery-ui": "^1.13.3",
"jquery-ui-dist": "^1.13.3",
"marked": "^9.1.0",
"marked": "^9.1.6",
"marked-katex-extension": "^4.0.1",
"mitt": "^3.0.1",
"prismjs": "^1.29.0",
@ -25,8 +25,7 @@
"three": "^0.164.1",
"vue": "^3.3.4",
"vue-draggable": "^2.0.6",
"vue-router": "^4.2.4",
"vue-simplemde": "^2.1.1"
"vue-router": "^4.2.4"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.3.4",
@ -271,82 +270,6 @@
"integrity": "sha512-GcIY79elgB+azP74j8vqkiXz8xLFfIzbQJdlwOPisgbKT00tviJQuEghOXSMVxJ00HoYJbGswr4kcllUc4xCcg==",
"deprecated": "Potential XSS vulnerability patched in v6.0.0."
},
"node_modules/@codemirror/autocomplete": {
"version": "6.9.2",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.9.2.tgz",
"integrity": "sha512-suItGf7PhtfgQMCd8ofYzycdsAHDBB8BkNrmyxeLvptW7yNT6zGT6ZzwhAfmB94TUyAAStrHjaDGC4/foenF2A==",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.17.0",
"@lezer/common": "^1.0.0"
},
"peerDependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"@lezer/common": "^1.0.0"
}
},
"node_modules/@codemirror/commands": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.3.0.tgz",
"integrity": "sha512-tFfcxRIlOWiQDFhjBSWJ10MxcvbCIsRr6V64SgrcaY0MwNk32cUOcCuNlWo8VjV4qRQCgNgUAnIeo0svkk4R5Q==",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.2.0",
"@codemirror/view": "^6.0.0",
"@lezer/common": "^1.1.0"
}
},
"node_modules/@codemirror/language": {
"version": "6.9.1",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.9.1.tgz",
"integrity": "sha512-lWRP3Y9IUdOms6DXuBpoWwjkR7yRmnS0hKYCbSfPz9v6Em1A1UCRujAkDiCrdYfs1Z0Eu4dGtwovNPStIfkgNA==",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"@lezer/common": "^1.1.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0",
"style-mod": "^4.0.0"
}
},
"node_modules/@codemirror/lint": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.4.2.tgz",
"integrity": "sha512-wzRkluWb1ptPKdzlsrbwwjYCPLgzU6N88YBAmlZi8WFyuiEduSd05MnJYNogzyc8rPK7pj6m95ptUApc8sHKVA==",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/search": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.4.tgz",
"integrity": "sha512-YoTrvjv9e8EbPs58opjZKyJ3ewFrVSUzQ/4WXlULQLSDDr1nGPJ67mMXFNNVYwdFhybzhrzrtqgHmtpJwIF+8g==",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/state": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.1.tgz",
"integrity": "sha512-RupHSZ8+OjNT38zU9fKH2sv+Dnlr8Eb8sl4NOnnqz95mCFTZUaiRP8Xv5MeeaG0px2b8Bnfe7YGwCV3nsBhbuw=="
},
"node_modules/@codemirror/view": {
"version": "6.21.3",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.21.3.tgz",
"integrity": "sha512-8l1aSQ6MygzL4Nx7GVYhucSXvW4jQd0F6Zm3v9Dg+6nZEfwzJVqi4C2zHfDljID+73gsQrWp9TgHc81xU15O4A==",
"dependencies": {
"@codemirror/state": "^6.1.4",
"style-mod": "^4.1.0",
"w3c-keyname": "^2.2.4"
}
},
"node_modules/@discoveryjs/json-ext": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
@ -915,27 +838,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@lezer/common": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.1.0.tgz",
"integrity": "sha512-XPIN3cYDXsoJI/oDWoR2tD++juVrhgIago9xyKhZ7IhGlzdDM9QgC8D8saKNCz5pindGcznFr2HBSsEQSWnSjw=="
},
"node_modules/@lezer/highlight": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.6.tgz",
"integrity": "sha512-cmSJYa2us+r3SePpRCjN5ymCqCPv+zyXmDl0ciWtVaNiORT/MxM7ZgOMQZADD0o51qOaOg24qc/zBViOIwAjJg==",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@lezer/lr": {
"version": "1.3.13",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.13.tgz",
"integrity": "sha512-RLAbau/4uSzKgIKj96mI5WUtG1qtiR0Frn0Ei9zhPj8YOkHM+1Bb8SgdVvmR/aWJCFIzjo2KFnDiRZ75Xf5NdQ==",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@mrmlnc/readdir-enhanced": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
@ -3407,28 +3309,6 @@
"node": ">=6"
}
},
"node_modules/codemirror": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
"integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/commands": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/lint": "^6.0.0",
"@codemirror/search": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0"
}
},
"node_modules/codemirror-spell-checker": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/codemirror-spell-checker/-/codemirror-spell-checker-1.1.2.tgz",
"integrity": "sha512-2Tl6n0v+GJRsC9K3MLCdLaMOmvWL0uukajNJseorZJsslaxZyZMgENocPU8R0DyoTAiKsyqiemSOZo7kjGV0LQ==",
"dependencies": {
"typo-js": "*"
}
},
"node_modules/collection-visit": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
@ -3854,11 +3734,6 @@
"node": ">=8"
}
},
"node_modules/crelt": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -8055,9 +7930,10 @@
}
},
"node_modules/marked": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/marked/-/marked-9.1.0.tgz",
"integrity": "sha512-VZjm0PM5DMv7WodqOUps3g6Q7dmxs9YGiFUZ7a2majzQTTCgX+6S6NAJHPvOhgFBzYz8s4QZKWWMfZKFmsfOgA==",
"version": "9.1.6",
"resolved": "https://registry.npmjs.org/marked/-/marked-9.1.6.tgz",
"integrity": "sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
},
@ -10860,16 +10736,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/simplemde": {
"version": "1.11.2",
"resolved": "https://registry.npmjs.org/simplemde/-/simplemde-1.11.2.tgz",
"integrity": "sha512-AUXuHJ/tEEDEcN/MTitHIw3AuBcheizJt7SVwtyn00B0UK5RKZ3GB+JndmRcJ5wfYGCIL0O2yJm/uz0sJOFSLg==",
"dependencies": {
"codemirror": "*",
"codemirror-spell-checker": "*",
"marked": "*"
}
},
"node_modules/sirv": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz",
@ -11449,11 +11315,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/style-mod": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.0.tgz",
"integrity": "sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA=="
},
"node_modules/stylehacks": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz",
@ -11828,11 +11689,6 @@
"node": ">= 0.6"
}
},
"node_modules/typo-js": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/typo-js/-/typo-js-1.2.3.tgz",
"integrity": "sha512-67Hyl94beZX8gmTap7IDPrG5hy2cHftgsCAcGvE1tzuxGT+kRB+zSBin0wIMwysYw8RUCBCvv9UfQl8TNM75dA=="
},
"node_modules/uc.micro": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
@ -12207,26 +12063,6 @@
"vue": "^3.2.0"
}
},
"node_modules/vue-simplemde": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/vue-simplemde/-/vue-simplemde-2.1.1.tgz",
"integrity": "sha512-UBjjdcTVlq7MrmeeKyzGVFEdsqarsqVxL/QGuCCfV1jGs7ve2wJ3pTENM6FEvaSURFnDJhIR6uUkPrxHg6S7HQ==",
"dependencies": {
"marked": "^3.0.8",
"simplemde": "*"
}
},
"node_modules/vue-simplemde/node_modules/marked": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/marked/-/marked-3.0.8.tgz",
"integrity": "sha512-0gVrAjo5m0VZSJb4rpL59K1unJAMb/hm8HRXqasD8VeC8m91ytDPMritgFSlKonfdt+rRYYpP/JfLxgIX8yoSw==",
"bin": {
"marked": "bin/marked"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/vue-style-loader": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
@ -12245,11 +12081,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/w3c-keyname": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
},
"node_modules/watchpack": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz",
@ -12803,9 +12634,10 @@
}
},
"node_modules/xss": {
"version": "1.0.14",
"resolved": "https://registry.npmjs.org/xss/-/xss-1.0.14.tgz",
"integrity": "sha512-og7TEJhXvn1a7kzZGQ7ETjdQVS2UfZyTlsEdDOqvQF7GoxNfY+0YLCzBy1kPdsDDx4QuNAonQPddpsn6Xl/7sw==",
"version": "1.0.15",
"resolved": "https://registry.npmjs.org/xss/-/xss-1.0.15.tgz",
"integrity": "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==",
"license": "MIT",
"dependencies": {
"commander": "^2.20.3",
"cssfilter": "0.0.10"

View File

@ -18,7 +18,7 @@
"jquery": "^3.7.1",
"jquery-ui": "^1.13.3",
"jquery-ui-dist": "^1.13.3",
"marked": "^9.1.0",
"marked": "^9.1.6",
"marked-katex-extension": "^4.0.1",
"mitt": "^3.0.1",
"prismjs": "^1.29.0",
@ -26,8 +26,7 @@
"three": "^0.164.1",
"vue": "^3.3.4",
"vue-draggable": "^2.0.6",
"vue-router": "^4.2.4",
"vue-simplemde": "^2.1.1"
"vue-router": "^4.2.4"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.3.4",

View File

@ -118,11 +118,23 @@ input[type=text], input[type=password], input[type=email] {
border: solid 1px var(--color-border);
}
textarea {
background-color: var(--color-background-softer);
padding: 12px;
color: var(--color-text);
border: none;
}
input[type=text]:focus, input[type=password]:focus, input[type=email]:focus {
outline: none;
background-color: var(--color-background-softest);
}
textarea:focus {
outline: none;
}
button {
margin-top: 5px;
margin-bottom: 5px;
@ -178,11 +190,16 @@ button:active {
margin-left: auto;
}
.centered {
text-align: center;
}
.window-wrapper {
display: flex;
align-items: center;
border: solid 1px var(--color-border);
/* opacity: 0; */
user-select: none;
-webkit-box-shadow: 0px 0px 10px -2px rgba(0,0,0,0.75);
@ -199,6 +216,10 @@ button:active {
font-size: 32px;
}
.document b {
font-weight: bold;
}
.text-icon {
height: 18px;
width: 18px;

View File

@ -1,35 +1,14 @@
import './assets/main.css'
import './assets/prism.css'
import { createApp, reactive } from 'vue'
import { createApp, defineComponent, reactive } from 'vue'
import App from './App.vue'
import router from './router'
import mitt from 'mitt';
const emitter = mitt();
import VueMarkdownEditor from '@kangc/v-md-editor';
import '@kangc/v-md-editor/lib/style/base-editor.css';
import vuepressTheme from '@kangc/v-md-editor/lib/theme/vuepress.js';
import '@kangc/v-md-editor/lib/theme/style/vuepress.css';
import esEs from '@kangc/v-md-editor/lib/lang/es-ES'
import Prism from 'prismjs';
import 'prismjs/components/prism-python';
import 'prismjs/components/prism-javascript';
import 'prismjs/components/prism-java';
import 'prismjs/components/prism-c';
import 'prismjs/components/prism-cpp';
import 'prismjs/components/prism-csharp';
import 'prismjs/components/prism-ruby';
import 'prismjs/components/prism-bash';
VueMarkdownEditor.lang.use('es-Es', esEs);
VueMarkdownEditor.use(vuepressTheme, { Prism });
const app = createApp(App).use(VueMarkdownEditor);
const app = createApp(App);
app.config.globalProperties.emitter = emitter
app.config.globalProperties.rollWindows = {
login: reactive([]),

View File

@ -0,0 +1,52 @@
import { ref } from 'vue';
import { ExitGame } from "./Game";
import { socket } from "./Socket";
import { GetUser } from "./User";
import { chat } from './Chat';
import { ClearAll, CreateWindow } from './Windows';
let _currentCampaign = null;
let _currentPlayer = null;
const _players = ref([]);
function ConnectToCampaign(campaign){
_currentCampaign = campaign;
chat.value = [];
socket.emit('enter', GetUser(), _currentCampaign._id);
}
function Disconnect(){
socket.emit('exit');
ExitGame();
_currentCampaign = null;
_currentPlayer = null;
chat.value = [];
}
function DisplayCampaign(data = currentCampaign){
ClearAll();
CreateWindow('campaign_preview', {campaign: data});
}
socket.on('update-players', data => {
_players.value = [];
Object.keys(data).forEach((key) => {
_players.value.push(data[key]);
if(GetUser()._id == data[key].user._id) _currentPlayer = data[key];
});
})
export {
_currentCampaign,
_currentPlayer,
_players,
ConnectToCampaign,
DisplayCampaign,
Disconnect
}

View File

@ -0,0 +1,36 @@
import { ref } from 'vue';
import { socket } from "./Socket";
let chatMessageId = 0;
const chat = ref([]);
socket.on('message', (data) => {
// Add new chat message, ?
if(chat.value.length > 0) if(chat.value[chat.value.length - 1].author == data.author){
chat.value[chat.value.length - 1].chunkSize += 1;
chat.value[chat.value.length - 1].chunks.push({
id: chat.value[chat.value.length - 1].chunkSize,
type: data.type ? data.type : 'text',
content: data.content
});
return;
}
chatMessageId += 1;
chat.value.push({
id: chatMessageId,
author: data.author,
chunkSize: 1,
chunks: [
{
id: 1,
type: data.type ? data.type : 'text',
content: data.content
}
]
});
});
export {
chat
}

View File

@ -32,6 +32,12 @@ function PopulateContext(val){
spanInfo.innerHTML = element.name;
contextMenuElement.appendChild(spanInfo);
if(element.icon){
let iconContextElement = document.createElement('img');
iconContextElement.src = element.icon;
contextMenuElement.appendChild(iconContextElement);
}
if(element.context){
let iconContextElement = document.createElement('img');
iconContextElement.src = arrowIcon;

View File

@ -0,0 +1,41 @@
import Api from '@/services/Api'
import { GetCampaign } from "./Dragonroll";
import { socket } from './Socket';
import { reactive } from 'vue';
let data = reactive({});
function InitData(){
data.value = {
concepts: [],
};
}
function FetchConcepts(){
Api().get('/concept/list?campaign=' + GetCampaign()._id).then(response => {
data.value.concepts = response.data.data;
}).catch((err) => console.log(err));
}
function FetchData(){
FetchConcepts();
}
socket.on('update-concepts', () => {
FetchConcepts();
});
let GetConcepts = () => data.value.concepts;
let GetConcept = (id) => Api().get('/concept/get?campaign=' + GetCampaign()._id + "&id=" + id)
export {
InitData,
FetchData,
FetchConcepts,
GetConcepts,
GetConcept,
}

View File

@ -1,12 +1,7 @@
import { ref } from 'vue';
import { ClearAll, ClearWindow, CreateWindow } from './Windows';
import { io } from "socket.io-client";
import Api from '@/services/Api'
import { backendUrl } from './BackendURL';
import { GetUser } from './User';
import { ExitGame } from './Game';
import { GetModule } from './Modules';
import { GetMap, LoadMap, UpdateMapList } from './Map';
import { socket } from './Socket';
import { _currentCampaign, _currentPlayer, _players } from './Campaign';
import { chat } from './Chat';
let emitter;
@ -20,62 +15,12 @@ function DisplayToast(color, text, duration = 1000){
emitter.emit("toast", {color, text, duration});
}
const socket = io(backendUrl)
let GetPlayerList = () => _players;
let GetCampaign = () => _currentCampaign;
let GetClient = () => _currentPlayer;
let currentCampaign = null;
let currentPlayer = null;
const players = ref([]);
let GetPlayerList = () => { return players; };
let GetCampaign = () => { return currentCampaign; };
let GetClient = () => { return currentPlayer; };
let chatMessageId = 0;
const chat = ref([]);
let GetChatRef = () => chat;
socket.on('change_map', data => {
console.log("ChangeMap")
UpdateMapList().then(() => {
LoadMap(GetMap(data.id));
});
})
socket.on('update-players', data => {
players.value = [];
Object.keys(data).forEach((key) => {
players.value.push(data[key]);
if(GetUser()._id == data[key].user._id) currentPlayer = data[key];
});
})
socket.on('message', (data) => {
// Add new chat message, ?
if(chat.value.length > 0) if(chat.value[chat.value.length - 1].author == data.author){
chat.value[chat.value.length - 1].chunkSize += 1;
chat.value[chat.value.length - 1].chunks.push({
id: chat.value[chat.value.length - 1].chunkSize,
type: data.type ? data.type : 'text',
content: data.content
});
return;
}
chatMessageId += 1;
chat.value.push({
id: chatMessageId,
author: data.author,
chunkSize: 1,
chunks: [
{
id: 1,
type: data.type ? data.type : 'text',
content: data.content
}
]
});
});
function _SendMap(id){
socket.emit('send_map', {id});
}
@ -84,30 +29,10 @@ function SendMessage(data){
socket.emit('message', data);
}
function DisplayCampaign(data = currentCampaign){
ClearAll();
CreateWindow('campaign_preview', {campaign: data});
}
function ConnectToCampaign(campaign){
currentCampaign = campaign;
chat.value = [];
socket.emit('enter', GetUser(), currentCampaign._id);
}
function Disconnect(){
socket.emit('exit');
ExitGame();
currentCampaign = null;
currentPlayer = null;
chat.value = [];
}
function GetPlayer(player_campaign){
let index = players.value.findIndex((p) => {return p._id == player_campaign});
if(index != -1) return players.value[index];
let index = _players.value.findIndex((p) => {return p._id == player_campaign});
if(index != -1) return _players.value[index];
}
function GetSystem(){
@ -115,16 +40,11 @@ function GetSystem(){
}
export {
socket,
SetEmitter,
GetEmitter,
DisplayToast,
DisplayCampaign,
ConnectToCampaign,
Disconnect,
GetCampaign,
GetClient,
GetPlayerList,

View File

@ -1,10 +1,13 @@
import { ref } from "vue";
import { FetchData, InitData } from "./Data";
const inGameRef = ref(false);
let InGameRef = () => inGameRef;
function LaunchGame(){
inGameRef.value = true;
InitData();
FetchData();
}
function ExitGame(){

View File

@ -3,6 +3,7 @@ import { initCustomFormatter, ref, toRaw } from 'vue';
import Api from '@/services/Api'
import { _SendMap, GetCampaign } from './Dragonroll';
import { backendUrl } from './BackendURL';
import { socket } from './Socket';
function dataURLtoFile(dataurl, filename) {
var arr = dataurl.split(","),
@ -292,6 +293,13 @@ function ChangeBackgroundColor(color){
SaveMap(currentMapId.value);
}
socket.on('change_map', data => {
UpdateMapList().then(() => {
LoadMap(GetMap(data.id));
});
})
export {
toMapX,
toMapY,

View File

@ -0,0 +1,8 @@
import { io } from "socket.io-client";
import { backendUrl } from './BackendURL';
const socket = io(backendUrl);
export {
socket
}

View File

@ -1,5 +1,5 @@
import { reactive, ref } from 'vue'
import { Disconnect } from './Dragonroll';
import { Disconnect } from './Campaign';
const windows = ref([])

View File

@ -4,7 +4,7 @@ import { InGameRef } from '../../services/Game';
import IconButton from '../partials/game/IconButton.vue';
import { AddSound } from '../../services/Sound';
import TileMap from './TileMap.vue';
import { DisplayCampaign, GetCampaign, GetClient } from '../../services/Dragonroll';
import { GetCampaign, GetClient } from '../../services/Dragonroll';
import { ClearAll, ClearWindow, CreateWindow } from '../../services/Windows';
const game = ref(null);

View File

@ -1,5 +1,5 @@
<script setup>
import { ref, reactive, defineComponent } from 'vue'
import { ref, reactive, defineComponent, TransitionGroup } from 'vue'
import { RouterLink, RouterView } from 'vue-router'
import { defineAsyncComponent } from 'vue'
@ -79,12 +79,23 @@ InjectSystemWindows('dnd-5e')
<template>
<div class="window-container" :key="reload">
<component v-for="win in windows" :is="WindowMap[win.type]" :key="win.id" :data="win"></component>
<TransitionGroup name="window">
<component v-for="win in windows" :is="WindowMap[win.type]" :key="win.id" :data="win"></component>
</TransitionGroup>
</div>
</template>
<style>
.window-enter-active,
.window-leave-active {
transition: all 0.15s ease;
}
.window-enter-from,
.window-leave-to {
opacity: 0;
transform: translateY(15px);
}
.window-wrapper {
background-color: var(--window-background);
@ -95,7 +106,6 @@ InjectSystemWindows('dnd-5e')
display: flex;
flex-direction: column;
text-align: center;
}
</style>

View File

@ -3,9 +3,8 @@
import { onMounted, ref } from 'vue';
import Api from '@/services/Api'
import { DisplayCampaign } from '@/services/Dragonroll'
import { AddSound } from '../../services/Sound';
import { ConnectToCampaign } from '../../services/Dragonroll';
import { ConnectToCampaign, DisplayCampaign } from '../../services/Campaign';
const props = defineProps(['data']);
const data = props.data;

View File

@ -41,7 +41,6 @@ watch(chat, () => {
if(chat.value.length > 0) if(chat.value[chat.value.length - 1].author == GetClient()._id){
setTimeout(() => {
messageContainer.value.scrollTop = messageContainer.value.scrollHeight;
console.log(messageContainer.value.scrollHeight)
}, 0);
}
}, {deep: true})

View File

@ -27,11 +27,12 @@ function OpenConcept(element){
<template>
<div class="list-container" ref="listContainer">
<div class="list-element" v-for="element in elements" :key="element._id" v-on:click.prevent="OpenConcept(element)">
<img :src="element.info ? element.info.icon : 'icons/game-icons/ffffff/lorc/crossed-swords.svg'" class="concept-icon">
<span class="title">{{ element.name }}</span>
</div>
<TransitionGroup name="list-element">
<div class="list-element" v-for="element in elements" :key="element._id" v-on:click.prevent="OpenConcept(element)">
<img :src="element.info ? element.info.icon : 'icons/game-icons/ffffff/lorc/crossed-swords.svg'" class="concept-icon">
<span class="title">{{ element.name }}</span>
</div>
</TransitionGroup>
</div>
</template>
@ -57,10 +58,29 @@ function OpenConcept(element){
}
}
.list-element-move, /* apply transition to moving elements */
.list-element-enter-active,
.list-element-leave-active {
transition: all 0.5s ease;
}
.list-element-enter-from,
.list-element-leave-to {
opacity: 0;
transform: translateX(30px);
}
/* ensure leaving items are taken out of layout flow so that moving
animations can be calculated correctly. */
.list-element-leave-active {
position: absolute;
}
.list-container {
display: flex;
flex-direction: column;
width: 100%;
overflow-y: auto;
overflow-x: hidden;
}
</style>

View File

@ -1,6 +1,6 @@
<script setup>
import { onMounted, ref, getCurrentInstance, defineExpose } from 'vue';
import { onMounted, ref, getCurrentInstance } from 'vue';
import { ClearWindow, CreateChildWindow } from '../../services/Windows';
const image = ref(null);

View File

@ -0,0 +1,94 @@
<script setup>
import { onMounted, ref, watch } from 'vue';
import { marked } from "https://cdn.jsdelivr.net/npm/marked/lib/marked.esm.js";
const props = defineProps(['done']);
import IconButton from '../partials/game/IconButton.vue';
const editing = ref(false);
const editor = ref(null);
const preview = ref(null);
const text = ref("");
function EditContent(){
editing.value = true;
}
function PreviewContent(){
editing.value = false;
preview.value.innerHTML = marked.parse(editor.value.value);
}
onMounted(() => {
watch(text, () => {
editor.value.value = text.value;
preview.value.innerHTML = marked.parse(editor.value.value);
});
editor.value.addEventListener("change", () => {
props.done(editor.value.value);
});
editor.value.value = props.text ?? '';
preview.value.innerHTML = marked.parse(editor.value.value);
})
defineExpose({
text,
})
</script>
<template>
<div class="markdown-editor">
<div class="editor-content">
<div v-show="!editing" class="preview">
<div class="document preview" ref="preview">
</div>
<div class="fixed-bottom-buttons">
<IconButton icon="icons/iconoir/regular/edit-pencil.svg" :action="EditContent"></IconButton>
</div>
</div>
<div v-show="editing" class="editor">
<textarea class="editing" ref="editor"></textarea>
<div class="fixed-bottom-buttons">
<IconButton icon="icons/iconoir/solid/eye.svg" :action="PreviewContent"></IconButton>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.editing, .editor, .editor-content {
width: 100%;
height: 100%;
}
.editing {
background-color: var(--color-background);
}
.markdown-editor {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background-color: var(--color-background);
}
.fixed-bottom-buttons {
position: absolute;
bottom: 10px;
right: 10px;
z-index: 2;
display: flex;
}
.preview {
padding: 8px 0 8px 8px;
overflow-y: auto;
height: 100%;
}
</style>

View File

@ -0,0 +1,101 @@
<script setup>
import { ref } from 'vue';
const props = defineProps(['rows']);
const rows = ref(props.rows);
const rowDict = {}
for(let i = 0; i < props.rows.length; i++) rowDict[props.rows[i].replace(/\s+/g, '-').toLowerCase()] = i;
let selectedTab = ref(props.rows[0].replace(/\s+/g, '-').toLowerCase());
function SelectTab(row){
selectedTab.value = row;
console.log(selectedTab.value);
}
</script>
<template>
<div class="tab-container">
<div class="row">
<div class="toggler" :class="{ selected: row.replace(/\s+/g, '-').toLowerCase() == selectedTab }" v-for="row in rows" v-on:click.prevent="SelectTab(row.replace(/\s+/g, '-').toLowerCase())">
{{ row }}
</div>
</div>
<div class="tab-container-outer">
<div v-for="row in rows" class="tab-content">
<TransitionGroup name="tab">
<div class="tab-content-inner" v-show="row.replace(/\s+/g, '-').toLowerCase() == selectedTab" :key="row">
<slot :name="row.replace(/\s+/g, '-').toLowerCase()" />
</div>
</TransitionGroup>
</div>
</div>
</div>
</template>
<style lang="scss">
.toggler.selected {
color: var(--color-text);
background-color: var(--color-background-softer);
}
</style>
<style scoped lang="scss">
.tab-container {
display: flex;
flex-direction: column;
height: 100%;
}
.tab-container-outer {
flex-grow: 1;
display: flex;
flex-direction: column;
position: relative;
pointer-events: none;
}
.tab-enter-active,
.tab-leave-active {
transition: all 0.15s ease;
}
.tab-enter-from,
.tab-leave-to {
opacity: 0;
transform: translateY(15px);
}
.tab-content {
width: 100%;
height: 100%;
position: absolute;
}
.tab-content-inner {
width: 100%;
height: 100%;
pointer-events: auto;
}
.toggler {
flex-grow: 1;
flex-basis: 0;
font-weight: bold;
padding: 3px 3px 3px 12px;
font-size: 16px;
color: #9c9c9c;
border-left: 1px solid var(--color-border);
border-bottom: 1px solid var(--color-border);
border-top: 1px solid var(--color-border);
transition: color 0.2s, background-color 0.2s;
&:first-child {
border-left: none;
}
&.active {
color: var(--color-text);
}
}
</style>

View File

@ -0,0 +1,87 @@
<script setup>
import { ref } from 'vue';
import IconButton from './game/IconButton.vue';
import { HideContextMenu, ShowContextMenu } from '../../services/ContextMenu';
const props = defineProps(['items', 'done']);
const items = ref(props.items);
const done = props.done;
const itemDict = {}
function lower(text) {
return text.replace(/\s+/g, '-').toLowerCase();
}
for(let i = 0; i < props.items.length; i++) itemDict[lower(items.value[i])] = i;
let selectedTags = ref([]);
function SelectTab(tag){
if(!selectedTags.value.includes(tag)) selectedTags.value.push(tag);
done(selectedTags.value);
}
function RemoveTag(tag){
selectedTags.value = selectedTags.value.filter((item) => item !== tag);
done(selectedTags.value);
}
function OpenDropdown(){
let context = [];
items.value.forEach(name => {
context.push({
icon: selectedTags.value.includes(name) ? 'icons/iconoir/regular/check.svg' : false,
name,
action: () => {
HideContextMenu();
if(!selectedTags.value.includes(name)){
SelectTab(name);
} else { RemoveTag(name) }
}
});
});
ShowContextMenu(context);
}
</script>
<template>
<div class="tags-container">
<div class="tag" v-for="tag in selectedTags" :key="tag">
<span>{{ tag }}</span>
<img class="close-tag" src="icons/iconoir/regular/xmark.svg" v-on:click.prevent="RemoveTag(tag)">
</div>
<IconButton class="small-icon" icon="icons/iconoir/regular/plus.svg" :action="OpenDropdown"></IconButton>
</div>
</template>
<style scoped lang="scss">
.tags-container {
display: flex;
flex-wrap: wrap;
padding-left: 4px;
padding-right: 4px;
}
.small-icon {
height: 24px;
width: 24px;
float: left;
transform: translateY(2px);
}
.tag {
width: auto;
float: left;
line-height: 20px;
padding: 2px;
border-radius: 4px;
margin: 4px 2px 0 2px;
display: flex;
align-items: center;
background-color: #303030;
.close-tag {
width: 20px;
height: 20px;
filter: invert(0.9);
}
}
</style>

View File

@ -59,7 +59,7 @@ function RefreshCampaigns(){
<!-- Body -->
<div class="campaign-list-container">
<div class="window-second-header">
<h2>Your campaigns</h2>
<h2 class="centered">Your campaigns</h2>
<div class="campaign-list">
<CampaignEntry v-for="camp in myCampaigns" :key="camp._id" :data="camp"></CampaignEntry>
@ -67,7 +67,7 @@ function RefreshCampaigns(){
</div>
<div class="window-second-header">
<h2>Other campaigns</h2>
<h2 class="centered">Other campaigns</h2>
<div class="campaign-list">
<CampaignEntry v-for="camp in otherCampaigns" :key="camp._id" :data="camp"></CampaignEntry>
</div>

View File

@ -4,7 +4,7 @@ import { SetupHandle, SetSize, SetPosition, ResetPosition } from '@/services/Win
import WindowHandle from '@/views/partials/WindowHandle.vue';
import PlayerList from '../../partials/PlayerList.vue';
import { Disconnect, DisplayToast, GetCampaign, GetClient } from '../../../services/Dragonroll';
import { DisplayToast, GetClient } from '../../../services/Dragonroll';
import CampaignBookList from '../../partials/books/CampaignBookList.vue';
import { ClearAll, ClearWindow, CreateWindow, SetMinSize, SetResizable } from '../../../services/Windows';
import { LaunchGame } from '../../../services/Game';
@ -13,6 +13,7 @@ import ChatComponent from '../../partials/ChatComponent.vue';
import GameSystem from '@/views/partials/GameSystem.vue'
import { GetModule } from '../../../services/Modules';
import { AddTooltip } from '../../../services/Tooltip';
import { Disconnect } from '../../../services/Campaign';
const handle = ref(null);
@ -75,7 +76,7 @@ function Exit(){
<div class="campaign-preview-container" :class="hide_chat ? 'campaign-preview-compact' : ''" ref="container">
<div class="campaign-preview-column left">
<h2>Players</h2>
<h2 class="centered">Players</h2>
<PlayerList :campaign="data.campaign"></PlayerList>
<div class="buttons-row">
<button class="btn-primary button-row sound-click" v-on:click.prevent="CopyCode" ref="copy_code_button">Copy invite code</button>

View File

@ -1,12 +1,12 @@
<script setup>
import WindowHandle from '@/views/partials/WindowHandle.vue';
import Api from '@/services/Api'
import { onMounted, ref, shallowRef } from 'vue';
import { onMounted, ref, shallowRef, watch } from 'vue';
import { ClearWindow, CreateWindow, ResetPosition, SetMinSize, SetResizable, SetSize, SetupHandle } from '../../../services/Windows';
import IconButton from '@/views/partials/game/IconButton.vue'
import ConceptList from '../../partials/ConceptList.vue';
import { GetCampaign, socket } from '../../../services/Dragonroll';
import { FetchConcepts, GetConcepts } from '../../../services/Data';
import Tabs from '../../partials/Tabs.vue';
const handle = ref(null);
@ -26,23 +26,14 @@ onMounted(() => {
SetResizable(id, true);
SetMinSize(id, {width: 350, height: 300});
FetchConcepts();
socket.on('update-concepts', () => {
console.log("!!!");
FetchConcepts();
});
});
function FetchConcepts(){
Api().get('/concept/list?campaign=' + GetCampaign()._id).then(response => {
// console.log(response.data);
elements.value = response.data.data;
console.log(elements);
watch(GetConcepts, () => {
console.log("Updated???")
}).catch((err) => console.log(err));
}
elements.value = GetConcepts();
console.log(elements);
});
FetchConcepts();
});
function OpenCreateItemPrompt(){
CreateWindow('create_item_prompt', {id: 'create_item_prompt', title: 'Create Item', close: () => ClearWindow('create_item_prompt')})
}
@ -54,12 +45,11 @@ function OpenCreateItemPrompt(){
<WindowHandle :window="id" ref="handle"></WindowHandle>
<div class="main-container">
<div class="row">
<div class="toggler">Items</div>
<div class="toggler">Spells</div>
<div class="toggler">Features</div>
</div>
<ConceptList :elements="elements"></ConceptList>
<Tabs :rows="['Items', 'Spells', 'Features']">
<template #items>
<ConceptList :elements="elements"></ConceptList>
</template>
</Tabs>
</div>
@ -75,27 +65,6 @@ function OpenCreateItemPrompt(){
height: calc(100% - 24px);
}
.toggler {
flex-grow: 1;
font-weight: bold;
padding: 10px;
font-size: 16px;
color: #9c9c9c;
border-left: 1px solid var(--color-border);
border-bottom: 1px solid var(--color-border);
border-top: 1px solid var(--color-border);
transition: color 1s;
&:first-child {
border-left: none;
}
&.active {
color: var(--color-text);
}
}
.fixed-bottom-buttons {
position: absolute;
bottom: 10px;

View File

@ -4,10 +4,14 @@ import Api from '@/services/Api'
import { onMounted, ref, shallowRef } from 'vue';
import { SetupHandle, SetSize, ResetPosition } from '@/services/Windows';
import { CreateWindow } from '../../../../services/Windows';
import { CreateWindow, SetMinSize, SetResizable } from '../../../../services/Windows';
import IconSelector from '../../../partials/IconSelector.vue';
import { AddContextMenu, HideContextMenu, ShowContextMenu } from '../../../../services/ContextMenu';
import { GetCampaign } from '../../../../services/Dragonroll';
import { GetConcept } from '../../../../services/Data';
import Tabs from '../../../partials/Tabs.vue';
import MarkdownEditor from '../../../partials/MarkdownEditor.vue';
import Tags from '../../../partials/Tags.vue';
const props = defineProps(['data']);
const data = props.data;
@ -17,6 +21,7 @@ const rarity = ref(null);
const weaponType = ref(null);
const item_name = ref(null);
const icon_selector = ref(null);
const description = ref(null);
function GenRarities(){
let rarities = [];
@ -76,6 +81,17 @@ function IconSelected(val){
Upload();
}
function DescriptionChanged(text){
SetParam('description', text);
Upload();
}
function PropertiesChanged(properties){
console.log(properties);
SetParam('properties', properties);
Upload();
}
function InitValues(){
let rarities = GenRarities();
let weapon_types = GenTypes(["", "Melee", "Ranged", "Martial Melee", "Martial Ranged", "Natural", "Improvised", "Siege Weapon"]);
@ -86,6 +102,7 @@ function InitValues(){
if(concept.value.info.icon) icon_selector.value.icon = concept.value.info.icon;
if(concept.value.info.rarity) rarity.value.innerHTML = `<span class='important ${concept.value.info.rarity.replace(/\s+/g, '-').toLowerCase()}'>${concept.value.info.rarity}</span>`;
if(concept.value.info.weapon_type) weaponType.value.innerHTML = `<span class='important'>${concept.value.info.weapon_type}</span>`;
if(concept.value.info.description) description.value.text = concept.value.info.description;
rarity.value.addEventListener("click", () => {
ShowContextMenu(rarities)
@ -106,7 +123,9 @@ function InitValues(){
onMounted(() => {
SetupHandle(id, handle);
SetSize(id, {width: 500, height: 400});
SetSize(id, {width: 600, height: 700});
SetResizable(id, true);
SetMinSize(id, {width: 400, height: 300});
ResetPosition(id, "center");
item_type.value = data.item_type;
@ -123,7 +142,7 @@ onMounted(() => {
}).catch(err => console.log(err));
} else {
// Get concept
Api().get('/concept/get?campaign=' + GetCampaign()._id + "&id=" + data.item_id).then(response => {
GetConcept(data.item_id).then(response => {
concept.value = response.data.concept;
InitValues();
}).catch(err => console.log(err));
@ -148,6 +167,23 @@ onMounted(() => {
</div>
</div>
</div>
<Tabs :rows="['Description', 'Details']">
<template #description>
<div class="description-container">
<div class="description-sidebar">
<p>Hola</p>
</div>
<div class="description">
<MarkdownEditor ref="description" :done="DescriptionChanged"></MarkdownEditor>
</div>
</div>
</template>
<template #details>
<h2 class="section">Properties</h2>
<Tags :items="['Amunnition','Finesse','Heavy','Light','Loading','Range','Reach','Special','Thrown','Two-Handed','Versatile']" :done="PropertiesChanged"></Tags>
<h2 class="section">Damage</h2>
</template>
</Tabs>
</div>
</div>
</template>
@ -165,6 +201,28 @@ onMounted(() => {
border: 1px solid var(--color-border);
}
h2.section {
margin-left: 12px;
font-family: NodestoCapsCondensed;
font-size: 32px;
line-height: 48px;
text-align: left;
}
.description-container {
display: flex;
height: 100%;
width: 100%;
.description-sidebar {
min-width: 200px;
}
.description {
flex-grow: 1;
height: 100%;
}
}
.item-header {
display: flex;