First attempt at login/register
This commit is contained in:
parent
6028779b45
commit
76f151a64c
26
index.html
26
index.html
@ -115,6 +115,32 @@
|
||||
layersTitle: 'Layers',
|
||||
copyToClipboardSuccess: 'Copied to clipboard',
|
||||
copyToClipboardError: 'Unable to copy to clipboard',
|
||||
|
||||
loginTitle: 'Login/Register',
|
||||
loginHeading: 'Existing User',
|
||||
loginUsernameLabel: 'Username',
|
||||
loginPasswordLabel: 'Password',
|
||||
loginSubmit: 'Login',
|
||||
loginErrorUnknown: 'Unexpected error while logging in',
|
||||
loginErrorDisabled: 'Logging in is disabled on this server',
|
||||
loginErrorIncorrect: 'Incorrect username or password',
|
||||
loginSuccess: 'Logged in successfully',
|
||||
|
||||
registerHeading: 'New User',
|
||||
registerDescription: `Enter your username and password, along with your registration code.
|
||||
|
||||
You can get a registration code by running /dynmap webregister in-game.`,
|
||||
registerConfirmPasswordLabel: 'Confirm Password',
|
||||
registerCodeLabel: 'Registration Code',
|
||||
registerSubmit: 'Register',
|
||||
registerErrorUnknown: 'Unexpected error during registration',
|
||||
registerErrorDisabled: 'Registration is disabled on this server',
|
||||
registerErrorVerifyFailed: 'The entered passwords do not match',
|
||||
registerErrorIncorrect: 'Registration failed, please check the entered details are correct',
|
||||
|
||||
logoutTitle: 'Logout',
|
||||
logoutErrorUnknown: 'Unexpected error while logging out',
|
||||
logoutSuccess: 'Logged out successfully',
|
||||
},
|
||||
|
||||
ui: {
|
||||
|
22
src/App.vue
22
src/App.vue
@ -17,6 +17,7 @@
|
||||
<template>
|
||||
<Map></Map>
|
||||
<ChatBox v-if="chatBoxEnabled" v-show="chatBoxEnabled && chatVisible"></ChatBox>
|
||||
<LoginModal v-if="loginEnabled"></LoginModal>
|
||||
<Sidebar></Sidebar>
|
||||
<notifications position="bottom center" :speed="250" :max="3" :ignoreDuplicates="true" classes="notification" />
|
||||
</template>
|
||||
@ -32,13 +33,16 @@ import {parseUrl} from '@/util';
|
||||
import {hideSplash, showSplash, showSplashError} from '@/util/splash';
|
||||
import {MutationTypes} from "@/store/mutation-types";
|
||||
import {LiveAtlasServerDefinition, LiveAtlasUIElement} from "@/index";
|
||||
import LoginModal from "@/components/login/LoginModal.vue";
|
||||
import {notify} from "@kyvg/vue3-notification";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'App',
|
||||
components: {
|
||||
Map,
|
||||
Sidebar,
|
||||
ChatBox
|
||||
ChatBox,
|
||||
LoginModal
|
||||
},
|
||||
|
||||
setup() {
|
||||
@ -48,6 +52,7 @@ export default defineComponent({
|
||||
currentServer = computed(() => store.state.currentServer),
|
||||
configurationHash = computed(() => store.state.configurationHash),
|
||||
chatBoxEnabled = computed(() => store.state.components.chatBox),
|
||||
loginEnabled = computed(() => store.state.components.login),
|
||||
chatVisible = computed(() => store.state.ui.visibleElements.has('chat')),
|
||||
loggedIn = computed(() => store.state.loggedIn),
|
||||
|
||||
@ -79,6 +84,14 @@ export default defineComponent({
|
||||
return;
|
||||
}
|
||||
|
||||
//Show login screen if required
|
||||
if(e.message === 'login-required') {
|
||||
hideSplash();
|
||||
store.commit(MutationTypes.SHOW_UI_MODAL, 'login');
|
||||
notify('Login required');
|
||||
return;
|
||||
}
|
||||
|
||||
const error = `Failed to load server configuration for '${store.state.currentServer!.id}'`;
|
||||
console.error(`${error}:`, e);
|
||||
showSplashError(`${error}\n${e}`, false, ++loadingAttempts.value);
|
||||
@ -163,6 +176,12 @@ export default defineComponent({
|
||||
await loadConfiguration();
|
||||
}
|
||||
});
|
||||
watch(loggedIn, async () => {
|
||||
if(!loading.value) {
|
||||
console.log('Login state changed. Reloading configuration');
|
||||
await loadConfiguration();
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => loadConfiguration());
|
||||
onBeforeUnmount(() => store.dispatch(ActionTypes.STOP_UPDATES, undefined));
|
||||
@ -184,6 +203,7 @@ export default defineComponent({
|
||||
return {
|
||||
chatBoxEnabled,
|
||||
chatVisible,
|
||||
loginEnabled,
|
||||
}
|
||||
},
|
||||
});
|
||||
|
1
src/assets/icons/login.svg
Normal file
1
src/assets/icons/login.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="375.12" height="369.507" version="1.0" viewBox="0 0 281.34 277.13" xmlns="http://www.w3.org/2000/svg"><g stroke-linecap="round"><path d="M117.345 0c-15.437 0-28.178 12.74-28.178 28.176v87.008h20V28.176c0-4.702 3.476-8.175 8.178-8.175h135.817c4.703 0 8.177 3.473 8.177 8.175v220.778c0 4.702-3.474 8.176-8.176 8.176H117.344c-4.702 0-8.178-3.474-8.178-8.176V167.24h-20v81.712c0 15.437 12.742 28.178 28.178 28.178h135.817c15.437 0 28.176-12.741 28.176-28.178V28.176C281.339 12.74 268.6 0 253.163 0H117.345z"/><path d="M148.458 77.933a12.5 12.5 0 0 0-8.848 3.643 12.5 12.5 0 0 0-.04 17.678l29.329 29.457H12.508a12.5 12.5 0 0 0-12.5 12.501 12.5 12.5 0 0 0 12.5 12.5h156.39l-29.328 29.457a12.5 12.5 0 0 0 .04 17.678 12.5 12.5 0 0 0 17.677-.038l50.552-50.777a12.501 12.501 0 0 0 0-17.64l-50.551-50.774a12.5 12.5 0 0 0-8.83-3.683z"/></g></svg>
|
After Width: | Height: | Size: 846 B |
1
src/assets/icons/logout.svg
Normal file
1
src/assets/icons/logout.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="375.12" height="369.507" version="1.0" viewBox="0 0 281.34 277.13" xmlns="http://www.w3.org/2000/svg"><g stroke-linecap="round"><path d="M117.345 0c-15.437 0-28.178 12.74-28.178 28.176v87.008h20V28.176c0-4.702 3.476-8.175 8.178-8.175h135.817c4.703 0 8.177 3.473 8.177 8.175v220.778c0 4.702-3.474 8.176-8.176 8.176H117.344c-4.702 0-8.178-3.474-8.178-8.176V167.24h-20v81.712c0 15.437 12.742 28.178 28.178 28.178h135.817c15.437 0 28.176-12.741 28.176-28.178V28.176C281.339 12.74 268.6 0 253.163 0z"/><path d="M63.024 77.933a12.5 12.5 0 0 1 8.848 3.643 12.5 12.5 0 0 1 .04 17.678l-29.329 29.457h156.39a12.5 12.5 0 0 1 12.5 12.501 12.5 12.5 0 0 1-12.5 12.5H42.583l29.328 29.457a12.5 12.5 0 0 1-.04 17.678 12.5 12.5 0 0 1-17.677-.038L3.642 150.032a12.501 12.501 0 0 1 0-17.64l50.552-50.774a12.5 12.5 0 0 1 8.83-3.683z"/></g></svg>
|
After Width: | Height: | Size: 836 B |
@ -25,9 +25,12 @@
|
||||
<CoordinatesControl v-if="coordinatesControlEnabled" :leaflet="leaflet"></CoordinatesControl>
|
||||
<LinkControl v-if="linkControlEnabled" :leaflet="leaflet"></LinkControl>
|
||||
<ClockControl v-if="clockControlEnabled" :leaflet="leaflet"></ClockControl>
|
||||
|
||||
<LoginControl v-if="loginEnabled" :leaflet="leaflet"></LoginControl>
|
||||
<ChatControl v-if="chatBoxEnabled" :leaflet="leaflet"></ChatControl>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<MapContextMenu :leaflet="leaflet" v-if="leaflet"></MapContextMenu>
|
||||
</template>
|
||||
|
||||
@ -48,6 +51,7 @@ import LiveAtlasLeafletMap from "@/leaflet/LiveAtlasLeafletMap";
|
||||
import {LoadingControl} from "@/leaflet/control/LoadingControl";
|
||||
import MapContextMenu from "@/components/map/MapContextMenu.vue";
|
||||
import {Coordinate, LiveAtlasPlayer} from "@/index";
|
||||
import LoginControl from "@/components/map/control/LoginControl.vue";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@ -59,7 +63,8 @@ export default defineComponent({
|
||||
ClockControl,
|
||||
LinkControl,
|
||||
ChatControl,
|
||||
LogoControl
|
||||
LogoControl,
|
||||
LoginControl
|
||||
},
|
||||
|
||||
setup() {
|
||||
@ -75,6 +80,7 @@ export default defineComponent({
|
||||
clockControlEnabled = computed(() => store.getters.clockControlEnabled),
|
||||
linkControlEnabled = computed(() => store.state.components.linkControl),
|
||||
chatBoxEnabled = computed(() => store.state.components.chatBox),
|
||||
loginEnabled = computed(() => store.state.components.login),
|
||||
logoControls = computed(() => store.state.components.logoControls),
|
||||
|
||||
currentWorld = computed(() => store.state.currentWorld),
|
||||
@ -102,6 +108,7 @@ export default defineComponent({
|
||||
clockControlEnabled,
|
||||
linkControlEnabled,
|
||||
chatBoxEnabled,
|
||||
loginEnabled,
|
||||
|
||||
logoControls,
|
||||
followTarget,
|
||||
|
101
src/components/Modal.vue
Normal file
101
src/components/Modal.vue
Normal file
@ -0,0 +1,101 @@
|
||||
<!--
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div :class="{'modal': true, 'modal--visible': visible}" role="dialog" :id="`modal--${id}`"
|
||||
:aria-labelledby="`${id}__heading`" aria-modal="true" @click="onClick" ref="modal">
|
||||
<div class="modal__content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {defineComponent, onMounted, onUnmounted} from "@vue/runtime-core";
|
||||
import {useStore} from "@/store";
|
||||
import {MutationTypes} from "@/store/mutation-types";
|
||||
import {LiveAtlasUIModal} from "@/index";
|
||||
import {computed, ref} from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
id: {
|
||||
required: true,
|
||||
type: String,
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore(),
|
||||
modal = ref<HTMLElement | null>(null),
|
||||
visible = computed(() => store.state.ui.visibleModal === props.id);
|
||||
|
||||
const onKeydown = (e: KeyboardEvent) => {
|
||||
if(visible.value && e.key === 'Escape') {
|
||||
store.commit(MutationTypes.HIDE_UI_MODAL, props.id as LiveAtlasUIModal);
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
const onClick = (e: MouseEvent) => {
|
||||
if(e.target === modal.value) {
|
||||
store.commit(MutationTypes.HIDE_UI_MODAL, props.id as LiveAtlasUIModal);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('keydown', onKeydown);
|
||||
});
|
||||
onUnmounted(() => {
|
||||
window.addEventListener('keydown', onKeydown);
|
||||
});
|
||||
|
||||
return {
|
||||
visible,
|
||||
modal,
|
||||
onClick,
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../scss/placeholders';
|
||||
|
||||
.modal {
|
||||
position: fixed;
|
||||
z-index: 120;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: none;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
padding: 10vh 1rem;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
overflow: auto;
|
||||
|
||||
&.modal--visible {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.modal__content {
|
||||
@extend %panel;
|
||||
max-width: 80rem;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
125
src/components/login/LoginForm.vue
Normal file
125
src/components/login/LoginForm.vue
Normal file
@ -0,0 +1,125 @@
|
||||
<!--
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<form :class="{'form': true, 'form--invalid': invalid}" @submit.prevent="login" ref="form" novalidate>
|
||||
<h3>{{ loginHeading }}</h3>
|
||||
|
||||
<div class="form__group">
|
||||
<label for="login-username" class="form__label" >{{ usernameLabel }}</label>
|
||||
<input id="login-username" type="text" name="username" autocomplete="username"
|
||||
v-model="loginUsername" required ref="usernameField" />
|
||||
</div>
|
||||
|
||||
<div class="form__group">
|
||||
<label for="login-password" class="form__label" >{{ passwordLabel }}</label>
|
||||
<input id="login-password" type="password" name="password" autocomplete="current-password"
|
||||
v-model="loginPassword" required />
|
||||
</div>
|
||||
|
||||
<div role="alert" v-if="error" class="form__group alert">{{ error }}</div>
|
||||
|
||||
<div class="form__group">
|
||||
<button type="submit" :disabled="submitting">{{ loginSubmit }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {defineComponent} from "@vue/runtime-core";
|
||||
import {useStore} from "@/store";
|
||||
import {computed, ref, watch} from "vue";
|
||||
import {ActionTypes} from "@/store/action-types";
|
||||
import {MutationTypes} from "@/store/mutation-types";
|
||||
import {notify} from "@kyvg/vue3-notification";
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const store = useStore(),
|
||||
form = ref<HTMLFormElement | null>(null),
|
||||
usernameField = ref<HTMLFormElement | null>(null),
|
||||
|
||||
loginModalVisible = computed(() => store.state.ui.visibleModal === 'login'),
|
||||
|
||||
heading = computed(() => store.state.messages.loginHeading),
|
||||
loginHeading = computed(() => store.state.messages.loginHeading),
|
||||
usernameLabel = computed(() => store.state.messages.loginUsernameLabel),
|
||||
passwordLabel = computed(() => store.state.messages.loginPasswordLabel),
|
||||
loginSubmit = computed(() => store.state.messages.loginSubmit),
|
||||
loginSuccess = computed(() => store.state.messages.loginSuccess),
|
||||
|
||||
loginUsername = ref(''),
|
||||
loginPassword = ref(''),
|
||||
|
||||
submitting = ref(false),
|
||||
invalid = ref(false),
|
||||
error = ref(null);
|
||||
|
||||
watch(loginModalVisible, (newValue) => {
|
||||
if(newValue) {
|
||||
requestAnimationFrame(() => usernameField.value!.focus());
|
||||
} else {
|
||||
loginUsername.value = '';
|
||||
loginPassword.value = '';
|
||||
}
|
||||
});
|
||||
|
||||
const login = async () => {
|
||||
error.value = null;
|
||||
invalid.value = !form.value!.reportValidity();
|
||||
|
||||
if(invalid.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
submitting.value = true;
|
||||
|
||||
await store.dispatch(ActionTypes.LOGIN, {
|
||||
username: loginUsername.value,
|
||||
password: loginPassword.value,
|
||||
});
|
||||
|
||||
store.commit(MutationTypes.HIDE_UI_MODAL, 'login');
|
||||
notify(loginSuccess.value);
|
||||
} catch(e: any) {
|
||||
error.value = e;
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
};
|
||||
return {
|
||||
form,
|
||||
usernameField,
|
||||
|
||||
heading,
|
||||
loginHeading,
|
||||
usernameLabel,
|
||||
passwordLabel,
|
||||
loginSubmit,
|
||||
|
||||
loginUsername,
|
||||
loginPassword,
|
||||
|
||||
submitting,
|
||||
invalid,
|
||||
error,
|
||||
|
||||
login,
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
70
src/components/login/LoginModal.vue
Normal file
70
src/components/login/LoginModal.vue
Normal file
@ -0,0 +1,70 @@
|
||||
<!--
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<Modal id="login">
|
||||
<h2 id="login__heading">{{ heading }}</h2>
|
||||
|
||||
<LoginForm></LoginForm>
|
||||
<RegisterForm></RegisterForm>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {defineComponent} from "@vue/runtime-core";
|
||||
import {useStore} from "@/store";
|
||||
import {computed} from "vue";
|
||||
import LoginForm from "@/components/login/LoginForm.vue";
|
||||
import RegisterForm from "@/components/login/RegisterForm.vue";
|
||||
import Modal from "@/components/Modal.vue";
|
||||
|
||||
export default defineComponent({
|
||||
components: {Modal, RegisterForm, LoginForm},
|
||||
setup() {
|
||||
const store = useStore(),
|
||||
heading = computed(() => store.state.messages.loginTitle);
|
||||
|
||||
return {
|
||||
heading
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#modal--login {
|
||||
::v-deep(.modal__content) {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#login__heading,
|
||||
#login__error {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.form {
|
||||
width: 50%;
|
||||
padding: 1rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
</style>
|
163
src/components/login/RegisterForm.vue
Normal file
163
src/components/login/RegisterForm.vue
Normal file
@ -0,0 +1,163 @@
|
||||
<!--
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<form :class="{'form': true, 'form--invalid': invalid}" @submit.prevent="register" ref="form" novalidate>
|
||||
<h3>{{ messageHeading }}</h3>
|
||||
<p>{{ messageDescription }}</p>
|
||||
|
||||
<div class="form__group">
|
||||
<label for="register-username" class="form__label" >{{ messageUsernameLabel }}</label>
|
||||
<input id="register-username" type="text" name="username" autocomplete="username"
|
||||
v-model="valueUsername" required />
|
||||
</div>
|
||||
|
||||
<div class="form__group">
|
||||
<label for="register-password" class="form__label" >{{ messagePasswordLabel }}</label>
|
||||
<input id="register-password" type="password" name="password" autocomplete="new-password"
|
||||
v-model="valuePassword" required />
|
||||
</div>
|
||||
|
||||
<div class="form__group">
|
||||
<label for="register-confirm-password" class="form__label">{{ messageConfirmPasswordLabel }}</label>
|
||||
<input id="register-confirm-password" type="password" name="confirm_password"
|
||||
autocomplete="new-password" v-model="valuePassword2" required ref="confirmPasswordField"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form__group">
|
||||
<label for="register-code" class="form__label">{{ messageRegisterCodeLabel }}</label>
|
||||
<input id="register-code" type="text" name="code" v-model="valueCode" required />
|
||||
</div>
|
||||
|
||||
<div role="alert" v-if="error" class="form__group alert">{{ error }}</div>
|
||||
|
||||
<div class="form__group">
|
||||
<button type="submit" :disabled="submitting">{{ messageSubmit }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {defineComponent, watch} from "@vue/runtime-core";
|
||||
import {useStore} from "@/store";
|
||||
import {computed, ref} from "vue";
|
||||
import {ActionTypes} from "@/store/action-types";
|
||||
import {MutationTypes} from "@/store/mutation-types";
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const store = useStore(),
|
||||
form = ref<HTMLFormElement | null>(null),
|
||||
confirmPasswordField = ref<HTMLInputElement | null>(null),
|
||||
|
||||
loginModalVisible = computed(() => store.state.ui.visibleModal === 'login'),
|
||||
|
||||
messageUsernameLabel = computed(() => store.state.messages.loginUsernameLabel),
|
||||
messagePasswordLabel = computed(() => store.state.messages.loginPasswordLabel),
|
||||
messageConfirmPasswordLabel = computed(() => store.state.messages.registerConfirmPasswordLabel),
|
||||
messageRegisterCodeLabel = computed(() => store.state.messages.registerCodeLabel),
|
||||
|
||||
messageHeading = computed(() => store.state.messages.registerHeading),
|
||||
messageDescription = computed(() => store.state.messages.registerDescription),
|
||||
messageSubmit = computed(() => store.state.messages.registerSubmit),
|
||||
messagePasswordMismatch = computed(() => store.state.messages.registerErrorVerifyFailed),
|
||||
|
||||
valueUsername = ref(''),
|
||||
valuePassword = ref(''),
|
||||
valuePassword2 = ref(''),
|
||||
valueCode = ref(''),
|
||||
|
||||
submitting = ref(false),
|
||||
invalid = ref(false),
|
||||
error = ref(null);
|
||||
|
||||
watch(loginModalVisible, (newValue) => {
|
||||
if(!newValue) {
|
||||
valueUsername.value = '';
|
||||
valuePassword.value = '';
|
||||
valuePassword2.value = '';
|
||||
valueCode.value = '';
|
||||
}
|
||||
});
|
||||
|
||||
const checkPasswords = () => {
|
||||
if(valuePassword.value !== valuePassword2.value) {
|
||||
console.log(messagePasswordMismatch.value);
|
||||
confirmPasswordField.value!.setCustomValidity(messagePasswordMismatch.value)
|
||||
} else {
|
||||
confirmPasswordField.value!.setCustomValidity('');
|
||||
}
|
||||
}
|
||||
|
||||
const register = async () => {
|
||||
error.value = null;
|
||||
checkPasswords();
|
||||
invalid.value = !form.value!.reportValidity();
|
||||
|
||||
if(invalid.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
submitting.value = true;
|
||||
|
||||
await store.dispatch(ActionTypes.REGISTER, {
|
||||
username: valueUsername.value,
|
||||
password: valuePassword.value,
|
||||
code: valueCode.value,
|
||||
});
|
||||
|
||||
store.commit(MutationTypes.HIDE_UI_MODAL, 'login');
|
||||
} catch(e: any) {
|
||||
error.value = e;
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
form,
|
||||
confirmPasswordField,
|
||||
|
||||
messageHeading,
|
||||
messageDescription,
|
||||
messageUsernameLabel,
|
||||
messagePasswordLabel,
|
||||
messageConfirmPasswordLabel,
|
||||
messageRegisterCodeLabel,
|
||||
messageSubmit,
|
||||
|
||||
valueUsername,
|
||||
valuePassword,
|
||||
valuePassword2,
|
||||
valueCode,
|
||||
|
||||
submitting,
|
||||
invalid,
|
||||
error,
|
||||
|
||||
register
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
p {
|
||||
white-space: pre-line;
|
||||
}
|
||||
</style>
|
43
src/components/map/control/LoginControl.vue
Normal file
43
src/components/map/control/LoginControl.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<!--
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import {defineComponent, onMounted, onUnmounted} from "@vue/runtime-core";
|
||||
import LiveAtlasLeafletMap from "@/leaflet/LiveAtlasLeafletMap";
|
||||
import {LoginControl} from "@/leaflet/control/LoginControl";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
leaflet: {
|
||||
type: Object as () => LiveAtlasLeafletMap,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const control = new LoginControl({
|
||||
position: 'topleft',
|
||||
});
|
||||
|
||||
onMounted(() => props.leaflet.addControl(control));
|
||||
onUnmounted(() => props.leaflet.removeControl(control));
|
||||
},
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
</script>
|
27
src/index.d.ts
vendored
27
src/index.d.ts
vendored
@ -100,6 +100,27 @@ interface LiveAtlasGlobalMessageConfig {
|
||||
layersTitle: string;
|
||||
copyToClipboardSuccess: string;
|
||||
copyToClipboardError: string;
|
||||
loginTitle: string;
|
||||
loginHeading: string;
|
||||
loginUsernameLabel: string;
|
||||
loginPasswordLabel: string;
|
||||
loginSubmit: string;
|
||||
loginErrorUnknown: string;
|
||||
loginErrorDisabled: string;
|
||||
loginErrorIncorrect: string;
|
||||
loginSuccess: string;
|
||||
registerHeading: string;
|
||||
registerDescription: string;
|
||||
registerConfirmPasswordLabel: string;
|
||||
registerCodeLabel: string;
|
||||
registerSubmit: string;
|
||||
registerErrorUnknown: string;
|
||||
registerErrorDisabled: string;
|
||||
registerErrorVerifyFailed: string;
|
||||
registerErrorIncorrect: string;
|
||||
logoutTitle: string;
|
||||
logoutErrorUnknown: string;
|
||||
logoutSuccess: string;
|
||||
}
|
||||
|
||||
// Messages defined by dynmap configuration responses and can vary per server
|
||||
@ -122,7 +143,8 @@ interface LiveAtlasUIConfig {
|
||||
playersSearch: boolean;
|
||||
}
|
||||
|
||||
export type LiveAtlasUIElement = 'layers' | 'chat' | 'players' | 'maps' | 'settings';
|
||||
export type LiveAtlasUIElement = 'layers' | 'chat' | 'players' | 'maps';
|
||||
export type LiveAtlasUIModal = 'login' | 'settings';
|
||||
export type LiveAtlasSidebarSection = 'servers' | 'players' | 'maps';
|
||||
export type LiveAtlasDimension = 'overworld' | 'nether' | 'end';
|
||||
|
||||
@ -172,6 +194,9 @@ interface LiveAtlasMapProvider {
|
||||
startUpdates(): void;
|
||||
stopUpdates(): void;
|
||||
sendChatMessage(message: string): void;
|
||||
login(formData: FormData): void;
|
||||
logout(): void;
|
||||
register(formData: FormData): void;
|
||||
destroy(): void;
|
||||
|
||||
getPlayerHeadUrl(entry: HeadQueueEntry): string;
|
||||
|
100
src/leaflet/control/LoginControl.ts
Normal file
100
src/leaflet/control/LoginControl.ts
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {Control, ControlOptions, DomEvent, DomUtil} from 'leaflet';
|
||||
import {useStore} from "@/store";
|
||||
import {MutationTypes} from "@/store/mutation-types";
|
||||
import {watch} from "@vue/runtime-core";
|
||||
|
||||
import "@/assets/icons/login.svg";
|
||||
import "@/assets/icons/logout.svg";
|
||||
import {computed} from "vue";
|
||||
import {ActionTypes} from "@/store/action-types";
|
||||
import {notify} from "@kyvg/vue3-notification";
|
||||
|
||||
export class LoginControl extends Control {
|
||||
declare options: ControlOptions
|
||||
private readonly store = useStore();
|
||||
private readonly loggedIn = computed(() => this.store.state.loggedIn);
|
||||
private readonly _button: HTMLButtonElement;
|
||||
|
||||
constructor(options: ControlOptions) {
|
||||
super(options);
|
||||
|
||||
this._button = DomUtil.create('button',
|
||||
'leaflet-control-bottom leaflet-control-button leaflet-control-login') as HTMLButtonElement;
|
||||
|
||||
this._button.type = 'button';
|
||||
|
||||
this._button.addEventListener('click', async e => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
await this.handleClick();
|
||||
});
|
||||
|
||||
//Open login on ArrowRight from button
|
||||
DomEvent.on(this._button,'keydown', async (e: Event) => {
|
||||
if ((e as KeyboardEvent).key === 'ArrowRight') {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
await this.handleClick();
|
||||
}
|
||||
});
|
||||
|
||||
watch(this.loggedIn, () => {
|
||||
this.update();
|
||||
});
|
||||
|
||||
watch(this.store.state.ui, newValue => {
|
||||
this._button.setAttribute('aria-expanded', (newValue.visibleModal === 'login').toString());
|
||||
}, {
|
||||
deep: true,
|
||||
});
|
||||
|
||||
this.update();
|
||||
}
|
||||
|
||||
onAdd() {
|
||||
return this._button;
|
||||
}
|
||||
|
||||
private update() {
|
||||
this._button.title = this.loggedIn.value
|
||||
? this.store.state.messages.logoutTitle : this.store.state.messages.loginTitle;
|
||||
this._button.innerHTML = `
|
||||
<svg class="svg-icon">
|
||||
<use xlink:href="#icon--${this.loggedIn.value ? 'logout' : 'login'}" />
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
private async handleClick() {
|
||||
const logoutSuccess = computed(() => this.store.state.messages.logoutSuccess),
|
||||
logoutError = computed(() => this.store.state.messages.logoutErrorUnknown);
|
||||
|
||||
if (this.loggedIn.value) {
|
||||
try {
|
||||
await this.store.dispatch(ActionTypes.LOGOUT, undefined);
|
||||
notify(logoutSuccess.value);
|
||||
} catch(e) {
|
||||
notify(logoutError.value);
|
||||
}
|
||||
} else {
|
||||
this.store.commit(MutationTypes.SHOW_UI_MODAL, 'login');
|
||||
}
|
||||
}
|
||||
}
|
@ -629,8 +629,11 @@ export default class DynmapMapProvider extends MapProvider {
|
||||
const response = await DynmapMapProvider.getJSON(this.config.dynmap!.configuration, this.configurationAbort.signal);
|
||||
|
||||
if(response.error === 'login-required') {
|
||||
throw new Error("Login required");
|
||||
} else if (response.error) {
|
||||
this.store.commit(MutationTypes.SET_LOGGED_IN, false);
|
||||
this.store.commit(MutationTypes.SET_COMPONENTS, {login: true});
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
@ -807,6 +810,106 @@ export default class DynmapMapProvider extends MapProvider {
|
||||
return `${this.config.dynmap!.markers}_markers_/${icon}.png`;
|
||||
}
|
||||
|
||||
async login(data: any) {
|
||||
const store = useStore();
|
||||
|
||||
if (!store.state.components.login) {
|
||||
return Promise.reject(store.state.messages.loginErrorDisabled);
|
||||
}
|
||||
|
||||
store.commit(MutationTypes.SET_LOGGED_IN, false);
|
||||
|
||||
try {
|
||||
const body = new URLSearchParams();
|
||||
|
||||
body.append('j_username', data.username || '');
|
||||
body.append('j_password', data.password || '');
|
||||
|
||||
|
||||
const response = await DynmapMapProvider.fetchJSON(this.config.dynmap!.login, {
|
||||
method: 'POST',
|
||||
body,
|
||||
});
|
||||
|
||||
switch(response.result) {
|
||||
case 'success':
|
||||
store.commit(MutationTypes.SET_LOGGED_IN, true);
|
||||
return;
|
||||
|
||||
case 'loginfailed':
|
||||
return Promise.reject(store.state.messages.loginErrorIncorrect);
|
||||
|
||||
default:
|
||||
return Promise.reject(store.state.messages.loginErrorUnknown);
|
||||
}
|
||||
} catch(e) {
|
||||
console.error(store.state.messages.loginErrorUnknown);
|
||||
console.trace(e);
|
||||
return Promise.reject(store.state.messages.loginErrorUnknown);
|
||||
}
|
||||
}
|
||||
|
||||
async logout() {
|
||||
const store = useStore();
|
||||
|
||||
if (!store.state.components.login) {
|
||||
return Promise.reject(store.state.messages.loginErrorDisabled);
|
||||
}
|
||||
|
||||
try {
|
||||
await DynmapMapProvider.fetchJSON(this.config.dynmap!.login, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
store.commit(MutationTypes.SET_LOGGED_IN, false);
|
||||
} catch(e) {
|
||||
return Promise.reject(store.state.messages.logoutErrorUnknown);
|
||||
}
|
||||
}
|
||||
|
||||
async register(data: any) {
|
||||
const store = useStore();
|
||||
|
||||
if (!store.state.components.login) {
|
||||
return Promise.reject(store.state.messages.loginErrorDisabled);
|
||||
}
|
||||
|
||||
store.commit(MutationTypes.SET_LOGGED_IN, false);
|
||||
|
||||
try {
|
||||
const body = new URLSearchParams();
|
||||
|
||||
body.append('j_username', data.username || '');
|
||||
body.append('j_password', data.password || '');
|
||||
body.append('j_verify_password', data.password || '');
|
||||
body.append('j_passcode', data.code || '');
|
||||
|
||||
const response = await DynmapMapProvider.fetchJSON(this.config.dynmap!.register, {
|
||||
method: 'POST',
|
||||
body,
|
||||
});
|
||||
|
||||
switch(response.result) {
|
||||
case 'success':
|
||||
store.commit(MutationTypes.SET_LOGGED_IN, true);
|
||||
return;
|
||||
|
||||
case 'verifyfailed':
|
||||
return Promise.reject(store.state.messages.registerErrorVerifyFailed);
|
||||
|
||||
case 'registerfailed':
|
||||
return Promise.reject(store.state.messages.registerErrorIncorrect);
|
||||
|
||||
default:
|
||||
return Promise.reject(store.state.messages.registerErrorUnknown);
|
||||
}
|
||||
} catch(e) {
|
||||
console.error(store.state.messages.registerErrorUnknown);
|
||||
console.trace(e);
|
||||
return Promise.reject(store.state.messages.registerErrorUnknown);
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
|
||||
|
@ -54,6 +54,18 @@ export default abstract class MapProvider implements LiveAtlasMapProvider {
|
||||
throw new Error('Provider does not support chat');
|
||||
}
|
||||
|
||||
async login(data: any) {
|
||||
throw new Error('Provider does not support logging in');
|
||||
}
|
||||
|
||||
async logout() {
|
||||
throw new Error('Provider does not support logging out');
|
||||
}
|
||||
|
||||
async register(data: any) {
|
||||
throw new Error('Provider does not support registration');
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.currentWorldUnwatch();
|
||||
}
|
||||
|
@ -22,6 +22,7 @@
|
||||
box-sizing: border-box;
|
||||
overflow: visible;
|
||||
font-size: 1.5rem;
|
||||
flex-shrink: 0;
|
||||
|
||||
a, button {
|
||||
@extend %button;
|
||||
|
@ -343,6 +343,33 @@ img {
|
||||
}
|
||||
}
|
||||
|
||||
.form {
|
||||
.form__group {
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.form__label {
|
||||
font-size: 1.6rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--text-emphasis);
|
||||
}
|
||||
|
||||
&.form--invalid input:invalid {
|
||||
border-color: var(--background-error);
|
||||
outline-color: var(--background-error);
|
||||
}
|
||||
}
|
||||
|
||||
.alert {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
background-color: var(--background-error);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
@media print {
|
||||
@page {
|
||||
size: 297mm 210mm;
|
||||
|
@ -25,4 +25,7 @@ export enum ActionTypes {
|
||||
POP_LINE_UPDATES = "popLineUpdates",
|
||||
POP_TILE_UPDATES = "popTileUpdates",
|
||||
SEND_CHAT_MESSAGE = "sendChatMessage",
|
||||
LOGIN = "login",
|
||||
LOGOUT = "logout",
|
||||
REGISTER = "register",
|
||||
}
|
||||
|
@ -71,6 +71,17 @@ export interface Actions {
|
||||
{commit}: AugmentedActionContext,
|
||||
payload: string
|
||||
): Promise<void>
|
||||
[ActionTypes.LOGIN](
|
||||
{commit}: AugmentedActionContext,
|
||||
payload: any
|
||||
): Promise<void>
|
||||
[ActionTypes.LOGOUT](
|
||||
{commit}: AugmentedActionContext
|
||||
): Promise<void>
|
||||
[ActionTypes.REGISTER](
|
||||
{commit}: AugmentedActionContext,
|
||||
payload: any
|
||||
): Promise<void>
|
||||
}
|
||||
|
||||
export const actions: ActionTree<State, State> & Actions = {
|
||||
@ -240,4 +251,16 @@ export const actions: ActionTree<State, State> & Actions = {
|
||||
async [ActionTypes.SEND_CHAT_MESSAGE]({commit, state}, message: string): Promise<void> {
|
||||
await state.currentMapProvider!.sendChatMessage(message);
|
||||
},
|
||||
|
||||
async [ActionTypes.LOGIN]({state, commit}, data: any): Promise<void> {
|
||||
await state.currentMapProvider!.login(data);
|
||||
},
|
||||
|
||||
async [ActionTypes.LOGOUT]({state}): Promise<void> {
|
||||
await state.currentMapProvider!.logout();
|
||||
},
|
||||
|
||||
async [ActionTypes.REGISTER]({state}, data: any): Promise<void> {
|
||||
await state.currentMapProvider!.register(data);
|
||||
},
|
||||
}
|
||||
|
@ -55,6 +55,8 @@ export enum MutationTypes {
|
||||
SET_SMALL_SCREEN = 'setSmallScreen',
|
||||
TOGGLE_UI_ELEMENT_VISIBILITY = 'toggleUIElementVisibility',
|
||||
SET_UI_ELEMENT_VISIBILITY = 'setUIElementVisibility',
|
||||
SHOW_UI_MODAL = 'showUIModal',
|
||||
HIDE_UI_MODAL = 'hideUIModal',
|
||||
|
||||
TOGGLE_SIDEBAR_SECTION_COLLAPSED_STATE = 'toggleSidebarSectionCollapsedState',
|
||||
SET_SIDEBAR_SECTION_COLLAPSED_STATE = 'setSidebarSectionCollapsedState',
|
||||
|
@ -39,7 +39,7 @@ import {
|
||||
LiveAtlasMarker,
|
||||
LiveAtlasMarkerSet,
|
||||
LiveAtlasServerDefinition,
|
||||
LiveAtlasServerConfig, LiveAtlasChat, LiveAtlasPartialComponentConfig, LiveAtlasComponentConfig
|
||||
LiveAtlasServerConfig, LiveAtlasChat, LiveAtlasPartialComponentConfig, LiveAtlasComponentConfig, LiveAtlasUIModal
|
||||
} from "@/index";
|
||||
import DynmapMapProvider from "@/providers/DynmapMapProvider";
|
||||
import Pl3xmapMapProvider from "@/providers/Pl3xmapMapProvider";
|
||||
@ -88,6 +88,8 @@ export type Mutations<S = State> = {
|
||||
[MutationTypes.SET_SMALL_SCREEN](state: S, payload: boolean): void
|
||||
[MutationTypes.TOGGLE_UI_ELEMENT_VISIBILITY](state: S, payload: LiveAtlasUIElement): void
|
||||
[MutationTypes.SET_UI_ELEMENT_VISIBILITY](state: S, payload: {element: LiveAtlasUIElement, state: boolean}): void
|
||||
[MutationTypes.SHOW_UI_MODAL](state: S, payload: LiveAtlasUIModal): void
|
||||
[MutationTypes.HIDE_UI_MODAL](state: S, payload: LiveAtlasUIModal): void
|
||||
|
||||
[MutationTypes.TOGGLE_SIDEBAR_SECTION_COLLAPSED_STATE](state: S, section: LiveAtlasSidebarSection): void
|
||||
[MutationTypes.SET_SIDEBAR_SECTION_COLLAPSED_STATE](state: S, payload: {section: LiveAtlasSidebarSection, state: boolean}): void
|
||||
@ -138,6 +140,27 @@ export const mutations: MutationTree<State> & Mutations = {
|
||||
layersTitle: messageConfig.layersTitle || '',
|
||||
copyToClipboardSuccess: messageConfig.copyToClipboardSuccess || '',
|
||||
copyToClipboardError: messageConfig.copyToClipboardError || '',
|
||||
loginTitle: messageConfig.loginTitle || '',
|
||||
loginHeading: messageConfig.loginHeading || '',
|
||||
loginUsernameLabel: messageConfig.loginUsernameLabel || '',
|
||||
loginPasswordLabel: messageConfig.loginPasswordLabel || '',
|
||||
loginSubmit: messageConfig.loginSubmit || '',
|
||||
loginErrorUnknown: messageConfig.loginErrorUnknown || '',
|
||||
loginErrorDisabled: messageConfig.loginErrorDisabled || '',
|
||||
loginErrorIncorrect: messageConfig.loginErrorIncorrect || '',
|
||||
loginSuccess: messageConfig.loginSuccess || '',
|
||||
registerHeading: messageConfig.registerHeading || '',
|
||||
registerDescription: messageConfig.registerDescription || '',
|
||||
registerConfirmPasswordLabel: messageConfig.registerConfirmPasswordLabel || '',
|
||||
registerCodeLabel: messageConfig.registerCodeLabel || '',
|
||||
registerSubmit: messageConfig.registerSubmit || '',
|
||||
registerErrorUnknown: messageConfig.registerErrorUnknown || '',
|
||||
registerErrorDisabled: messageConfig.registerErrorDisabled || '',
|
||||
registerErrorVerifyFailed: messageConfig.registerErrorVerifyFailed || '',
|
||||
registerErrorIncorrect: messageConfig.registerErrorIncorrect || '',
|
||||
logoutTitle: messageConfig.logoutTitle || '',
|
||||
logoutErrorUnknown: messageConfig.logoutErrorUnknown || '',
|
||||
logoutSuccess: messageConfig.logoutSuccess || '',
|
||||
}
|
||||
|
||||
state.messages = Object.assign(state.messages, messages);
|
||||
@ -586,6 +609,16 @@ export const mutations: MutationTree<State> & Mutations = {
|
||||
payload.state ? state.ui.visibleElements.add(payload.element) : state.ui.visibleElements.delete(payload.element);
|
||||
},
|
||||
|
||||
[MutationTypes.SHOW_UI_MODAL](state: State, modal: LiveAtlasUIModal): void {
|
||||
state.ui.visibleModal = modal;
|
||||
},
|
||||
|
||||
[MutationTypes.HIDE_UI_MODAL](state: State, modal: LiveAtlasUIModal): void {
|
||||
if(state.ui.visibleModal === modal) {
|
||||
state.ui.visibleModal = undefined;
|
||||
}
|
||||
},
|
||||
|
||||
[MutationTypes.TOGGLE_SIDEBAR_SECTION_COLLAPSED_STATE](state: State, section: LiveAtlasSidebarSection): void {
|
||||
if(state.ui.sidebar.collapsedSections.has(section)) {
|
||||
state.ui.sidebar.collapsedSections.delete(section);
|
||||
@ -642,5 +675,7 @@ export const mutations: MutationTree<State> & Mutations = {
|
||||
state.components.chatBox = undefined;
|
||||
state.components.chatBalloons = false;
|
||||
state.components.login = false;
|
||||
|
||||
state.ui.visibleModal = undefined;
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ import {
|
||||
LiveAtlasPlayer,
|
||||
LiveAtlasMarkerSet,
|
||||
LiveAtlasComponentConfig,
|
||||
LiveAtlasServerConfig, LiveAtlasChat
|
||||
LiveAtlasServerConfig, LiveAtlasChat, LiveAtlasUIModal
|
||||
} from "@/index";
|
||||
import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition";
|
||||
|
||||
@ -78,6 +78,7 @@ export type State = {
|
||||
|
||||
smallScreen: boolean;
|
||||
visibleElements: Set<LiveAtlasUIElement>;
|
||||
visibleModal?: LiveAtlasUIModal;
|
||||
previouslyVisibleElements: Set<LiveAtlasUIElement>;
|
||||
|
||||
sidebar: {
|
||||
@ -144,6 +145,27 @@ export const state: State = {
|
||||
layersTitle: '',
|
||||
copyToClipboardSuccess: '',
|
||||
copyToClipboardError: '',
|
||||
loginTitle: '',
|
||||
loginHeading: '',
|
||||
loginUsernameLabel: '',
|
||||
loginPasswordLabel: '',
|
||||
loginSubmit: '',
|
||||
loginErrorUnknown: '',
|
||||
loginErrorDisabled: '',
|
||||
loginErrorIncorrect: '',
|
||||
loginSuccess: '',
|
||||
registerHeading: '',
|
||||
registerDescription: '',
|
||||
registerConfirmPasswordLabel: '',
|
||||
registerCodeLabel: '',
|
||||
registerSubmit: '',
|
||||
registerErrorUnknown: '',
|
||||
registerErrorDisabled: '',
|
||||
registerErrorVerifyFailed: '',
|
||||
registerErrorIncorrect: '',
|
||||
logoutTitle: '',
|
||||
logoutErrorUnknown: '',
|
||||
logoutSuccess: '',
|
||||
},
|
||||
|
||||
loggedIn: false,
|
||||
@ -227,6 +249,7 @@ export const state: State = {
|
||||
|
||||
smallScreen: false,
|
||||
visibleElements: new Set(),
|
||||
visibleModal: undefined,
|
||||
previouslyVisibleElements: new Set(),
|
||||
|
||||
sidebar: {
|
||||
|
Loading…
Reference in New Issue
Block a user