Merge pull request #83 from AlecM33/develop

make elements tabbable, fix fields exported with roles, styling changes
This commit is contained in:
Alec
2022-01-18 20:45:39 -05:00
committed by GitHub
14 changed files with 190 additions and 121 deletions

View File

@@ -0,0 +1 @@
<svg fill="#d7d7d7" width="512px" height="512px" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><title>ionicons-v5-b</title><path d="M321.94,98,158.82,237.78a24,24,0,0,0,0,36.44L321.94,414c15.57,13.34,39.62,2.28,39.62-18.22V116.18C361.56,95.68,337.51,84.62,321.94,98Z"/></svg>

After

Width:  |  Height:  |  Size: 287 B

View File

@@ -0,0 +1 @@
<svg fill="#d7d7d7" width="512px" height="512px" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><title>ionicons-v5-b</title><path d="M190.06,414,353.18,274.22a24,24,0,0,0,0-36.44L190.06,98c-15.57-13.34-39.62-2.28-39.62,18.22V395.82C150.44,416.32,174.49,427.38,190.06,414Z"/></svg>

After

Width:  |  Height:  |  Size: 291 B

View File

@@ -163,8 +163,8 @@ function renderModerationTypeStep (game, containerId, stepNumber) {
setAttributes(stepContainer, { id: 'step-' + stepNumber, class: 'flex-row-container step' });
stepContainer.innerHTML =
"<div id='moderation-dedicated'>I will be the <strong>dedicated mod.</strong> Don't deal me a card.</div>" +
"<div id='moderation-self'>The <strong>first person out</strong> will mod. Deal me into the game <span>(mod will be assigned automatically).</span></div>";
"<div tabindex=\"0\" id='moderation-dedicated'>I will be the <strong>dedicated mod.</strong> Don't deal me a card.</div>" +
"<div tabindex=\"0\" id='moderation-self'>The <strong>first person out</strong> will mod. Deal me into the game <span>(mod will be assigned automatically).</span></div>";
const dedicatedOption = stepContainer.querySelector('#moderation-dedicated');
if (game.hasDedicatedModerator) {
@@ -175,17 +175,27 @@ function renderModerationTypeStep (game, containerId, stepNumber) {
selfOption.classList.add('option-selected');
}
dedicatedOption.addEventListener('click', () => {
dedicatedOption.classList.add('option-selected');
selfOption.classList.remove('option-selected');
game.hasDedicatedModerator = true;
});
const dedicatedHandler = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
dedicatedOption.classList.add('option-selected');
selfOption.classList.remove('option-selected');
game.hasDedicatedModerator = true;
}
};
selfOption.addEventListener('click', () => {
selfOption.classList.add('option-selected');
dedicatedOption.classList.remove('option-selected');
game.hasDedicatedModerator = false;
});
const tempModHandler = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
selfOption.classList.add('option-selected');
dedicatedOption.classList.remove('option-selected');
game.hasDedicatedModerator = false;
}
};
dedicatedOption.addEventListener('click', dedicatedHandler);
dedicatedOption.addEventListener('keyup', dedicatedHandler);
selfOption.addEventListener('click', tempModHandler);
selfOption.addEventListener('keyup', tempModHandler);
document.getElementById(containerId).appendChild(stepContainer);
}
@@ -198,16 +208,29 @@ function renderRoleSelectionStep (game, containerId, step, deckManager) {
stepContainer.innerHTML += templates.CREATE_GAME_DECK_STATUS;
stepContainer.innerHTML += templates.CREATE_GAME_DECK;
const exportHandler = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
e.preventDefault();
deckManager.downloadCustomRoles('play-werewolf-custom-roles', JSON.stringify(
deckManager.getCurrentCustomRoleOptions()
.map((option) => (
{ role: option.role, team: option.team, description: option.description, custom: option.custom }
))
));
}
};
document.getElementById(containerId).appendChild(stepContainer);
document.querySelector('#custom-roles-export').addEventListener('click', (e) => {
e.preventDefault();
deckManager.downloadCustomRoles('play-werewolf-custom-roles', JSON.stringify(deckManager.getCurrentCustomRoleOptions()));
});
document.querySelector('#custom-roles-export').addEventListener('click', exportHandler);
document.querySelector('#custom-roles-export').addEventListener('keyup', exportHandler);
document.querySelector('#custom-roles-import').addEventListener('click', (e) => {
e.preventDefault();
ModalManager.displayModal('upload-custom-roles-modal', 'modal-background', 'close-upload-custom-roles-modal-button');
});
const importHandler = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
e.preventDefault();
ModalManager.displayModal('upload-custom-roles-modal', 'modal-background', 'close-upload-custom-roles-modal-button');
}
};
document.querySelector('#custom-roles-import').addEventListener('click', importHandler);
document.querySelector('#custom-roles-import').addEventListener('keyup', importHandler);
document.getElementById('upload-custom-roles-form').onsubmit = (e) => {
e.preventDefault();
@@ -351,7 +374,7 @@ function showButtons (back, forward, forwardHandler, backHandler, builtGame = nu
document.querySelector('#create-game')?.remove();
if (back) {
const backButton = document.createElement('button');
backButton.innerText = '\u25C0';
backButton.innerHTML = '<img alt="back" src="../images/caret-back.svg"/>';
backButton.addEventListener('click', backHandler);
backButton.setAttribute('id', 'step-back-button');
backButton.classList.add('cancel');
@@ -361,7 +384,7 @@ function showButtons (back, forward, forwardHandler, backHandler, builtGame = nu
if (forward && builtGame === null) {
const fwdButton = document.createElement('button');
fwdButton.innerHTML = '\u25b6';
fwdButton.innerHTML = '<img alt="next" src="../images/caret-forward.svg"/>';
fwdButton.addEventListener('click', forwardHandler);
fwdButton.setAttribute('id', 'step-forward-button');
fwdButton.classList.add('app-button');
@@ -428,14 +451,14 @@ function constructCompactDeckBuilderElement (card, deckManager) {
cardContainer.setAttribute('id', 'card-' + card.role.replaceAll(' ', '-'));
cardContainer.innerHTML =
"<div class='compact-card-left'>" +
"<div tabindex='0' class='compact-card-left'>" +
'<p>-</p>' +
'</div>' +
"<div class='compact-card-header'>" +
"<p class='card-role'></p>" +
"<div class='card-quantity'></div>" +
'</div>' +
"<div class='compact-card-right'>" +
"<div tabindex='0' class='compact-card-right'>" +
'<p>+</p>' +
'</div>';
@@ -447,22 +470,33 @@ function constructCompactDeckBuilderElement (card, deckManager) {
cardContainer.classList.add('selected-card');
}
cardContainer.querySelector('.compact-card-right').addEventListener('click', () => {
deckManager.addCopyOfCard(card.role);
updateDeckStatus(deckManager);
cardContainer.querySelector('.card-quantity').innerText = deckManager.getCard(card.role).quantity;
if (deckManager.getCard(card.role).quantity > 0) {
document.getElementById('card-' + card.role.replaceAll(' ', '-')).classList.add('selected-card');
const addHandler = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
deckManager.addCopyOfCard(card.role);
updateDeckStatus(deckManager);
cardContainer.querySelector('.card-quantity').innerText = deckManager.getCard(card.role).quantity;
if (deckManager.getCard(card.role).quantity > 0) {
document.getElementById('card-' + card.role.replaceAll(' ', '-')).classList.add('selected-card');
}
}
});
cardContainer.querySelector('.compact-card-left').addEventListener('click', () => {
deckManager.removeCopyOfCard(card.role);
updateDeckStatus(deckManager);
cardContainer.querySelector('.card-quantity').innerText = deckManager.getCard(card.role).quantity;
if (deckManager.getCard(card.role).quantity === 0) {
document.getElementById('card-' + card.role.replaceAll(' ', '-')).classList.remove('selected-card');
};
const removeHandler = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
deckManager.removeCopyOfCard(card.role);
updateDeckStatus(deckManager);
cardContainer.querySelector('.card-quantity').innerText = deckManager.getCard(card.role).quantity;
if (deckManager.getCard(card.role).quantity === 0) {
document.getElementById('card-' + card.role.replaceAll(' ', '-')).classList.remove('selected-card');
}
}
});
};
cardContainer.querySelector('.compact-card-right').addEventListener('click', addHandler);
cardContainer.querySelector('.compact-card-right').addEventListener('keyup', addHandler);
cardContainer.querySelector('.compact-card-left').addEventListener('click', removeHandler);
cardContainer.querySelector('.compact-card-left').addEventListener('keyup', removeHandler);
return cardContainer;
}
@@ -560,56 +594,72 @@ function addOptionsToList (deckManager, selectEl) {
function addCustomRoleEventListeners (deckManager, select) {
document.querySelectorAll('.deck-select-role').forEach((role) => {
const name = role.querySelector('.deck-select-role-name').innerText;
role.querySelector('.deck-select-include').addEventListener('click', (e) => {
e.preventDefault();
if (!deckManager.getCard(name)) {
deckManager.addToDeck(name);
const cardEl = constructCompactDeckBuilderElement(deckManager.getCard(name), deckManager);
toast('"' + name + '" made available below.', 'success', true, true, 4);
if (deckManager.getCard(name).team === globals.ALIGNMENT.GOOD) {
document.getElementById('deck-good').appendChild(cardEl);
} else {
document.getElementById('deck-evil').appendChild(cardEl);
}
updateCustomRoleOptionsList(deckManager, select);
} else {
toast('"' + select.value + '" already included.', 'error', true, true, 3);
}
});
role.querySelector('.deck-select-remove').addEventListener('click', (e) => {
if (confirm("Delete the role '" + name + "'?")) {
const includeHandler = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
e.preventDefault();
deckManager.removeFromCustomRoleOptions(name);
updateCustomRoleOptionsList(deckManager, select);
if (!deckManager.getCard(name)) {
deckManager.addToDeck(name);
const cardEl = constructCompactDeckBuilderElement(deckManager.getCard(name), deckManager);
toast('"' + name + '" made available below.', 'success', true, true, 4);
if (deckManager.getCard(name).team === globals.ALIGNMENT.GOOD) {
document.getElementById('deck-good').appendChild(cardEl);
} else {
document.getElementById('deck-evil').appendChild(cardEl);
}
updateCustomRoleOptionsList(deckManager, select);
} else {
toast('"' + select.value + '" already included.', 'error', true, true, 3);
}
}
});
};
role.querySelector('.deck-select-include').addEventListener('click', includeHandler);
role.querySelector('.deck-select-include').addEventListener('keyup', includeHandler);
role.querySelector('.deck-select-info').addEventListener('click', (e) => {
const alignmentEl = document.getElementById('custom-role-info-modal-alignment');
alignmentEl.classList.remove(globals.ALIGNMENT.GOOD);
alignmentEl.classList.remove(globals.ALIGNMENT.EVIL);
e.preventDefault();
const option = deckManager.getCustomRoleOption(name);
document.getElementById('custom-role-info-modal-name').innerText = name;
alignmentEl.classList.add(option.team);
document.getElementById('custom-role-info-modal-description').innerText = option.description;
alignmentEl.innerText = option.team;
ModalManager.displayModal('custom-role-info-modal', 'modal-background', 'close-custom-role-info-modal-button');
});
const removeHandler = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
if (confirm("Delete the role '" + name + "'?")) {
e.preventDefault();
deckManager.removeFromCustomRoleOptions(name);
updateCustomRoleOptionsList(deckManager, select);
}
}
};
role.querySelector('.deck-select-remove').addEventListener('click', removeHandler);
role.querySelector('.deck-select-remove').addEventListener('keyup', removeHandler);
role.querySelector('.deck-select-edit').addEventListener('click', (e) => {
e.preventDefault();
const option = deckManager.getCustomRoleOption(name);
document.getElementById('role-name').value = option.role;
document.getElementById('role-alignment').value = option.team;
document.getElementById('role-description').value = option.description;
deckManager.createMode = false;
deckManager.currentlyEditingRoleName = option.role;
const createBtn = document.getElementById('create-role-button');
createBtn.setAttribute('value', 'Update');
ModalManager.displayModal('role-modal', 'modal-background', 'close-modal-button');
});
const infoHandler = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
const alignmentEl = document.getElementById('custom-role-info-modal-alignment');
alignmentEl.classList.remove(globals.ALIGNMENT.GOOD);
alignmentEl.classList.remove(globals.ALIGNMENT.EVIL);
e.preventDefault();
const option = deckManager.getCustomRoleOption(name);
document.getElementById('custom-role-info-modal-name').innerText = name;
alignmentEl.classList.add(option.team);
document.getElementById('custom-role-info-modal-description').innerText = option.description;
alignmentEl.innerText = option.team;
ModalManager.displayModal('custom-role-info-modal', 'modal-background', 'close-custom-role-info-modal-button');
}
};
role.querySelector('.deck-select-info').addEventListener('click', infoHandler);
role.querySelector('.deck-select-info').addEventListener('keyup', infoHandler);
const editHandler = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
e.preventDefault();
const option = deckManager.getCustomRoleOption(name);
document.getElementById('role-name').value = option.role;
document.getElementById('role-alignment').value = option.team;
document.getElementById('role-description').value = option.description;
deckManager.createMode = false;
deckManager.currentlyEditingRoleName = option.role;
const createBtn = document.getElementById('create-role-button');
createBtn.setAttribute('value', 'Update');
ModalManager.displayModal('role-modal', 'modal-background', 'close-modal-button');
}
};
role.querySelector('.deck-select-edit').addEventListener('click', editHandler);
role.querySelector('.deck-select-edit').addEventListener('keyup', editHandler);
});
}

View File

@@ -46,11 +46,15 @@ export class GameStateRenderer {
const linkDiv = document.createElement('div');
linkDiv.innerText = window.location;
gameLinkContainer.prepend(linkDiv);
gameLinkContainer.addEventListener('click', () => {
navigator.clipboard.writeText(gameLinkContainer.innerText).then(() => {
toast('Link copied!', 'success', true);
});
});
const linkCopyHandler = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
navigator.clipboard.writeText(gameLinkContainer.innerText).then(() => {
toast('Link copied!', 'success', true);
});
}
};
gameLinkContainer.addEventListener('click', linkCopyHandler);
gameLinkContainer.addEventListener('keyup', linkCopyHandler);
const copyImg = document.createElement('img');
copyImg.setAttribute('src', '../images/copy.svg');
gameLinkContainer.appendChild(copyImg);
@@ -250,15 +254,19 @@ function renderPotentialMods (gameState, group, transferModHandlers, socket) {
if ((member.out || member.userType === globals.USER_TYPES.SPECTATOR) && !(member.id === gameState.client.id)) {
const container = document.createElement('div');
container.classList.add('potential-moderator');
container.setAttribute("tabindex", "0");
container.dataset.pointer = member.id;
container.innerText = member.name;
transferModHandlers[member.id] = () => {
if (confirm('Transfer moderator powers to ' + member.name + '?')) {
socket.emit(globals.COMMANDS.TRANSFER_MODERATOR, gameState.accessCode, member.id);
transferModHandlers[member.id] = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
if (confirm('Transfer moderator powers to ' + member.name + '?')) {
socket.emit(globals.COMMANDS.TRANSFER_MODERATOR, gameState.accessCode, member.id);
}
}
};
container.addEventListener('click', transferModHandlers[member.id]);
container.addEventListener('keyup', transferModHandlers[member.id]);
modalContent.appendChild(container);
}
}

View File

@@ -21,6 +21,7 @@ function displayModal (modalId, backgroundId, closeButtonId) {
} else {
throw new Error('One or more of the ids supplied to ModalManager.displayModal is invalid.');
}
modal.focus();
}
function dispelModal (modalId, backgroundId) {

View File

@@ -3,7 +3,7 @@ export const templates = {
"<div id='lobby-header'>" +
'<div>' +
"<label for='game-link'>Share Link</label>" +
"<div id='game-link'></div>" +
"<div tabindex='0' id='game-link'></div>" +
'</div>' +
"<div id='game-parameters'>" +
'<div>' +
@@ -75,7 +75,7 @@ export const templates = {
'</div>',
MODERATOR_GAME_VIEW:
"<div id='transfer-mod-modal-background' class='modal-background' style='display: none'></div>" +
"<div id='transfer-mod-modal' class='modal' style='display: none'>" +
"<div tabindex='-1' id='transfer-mod-modal' class='modal' style='display: none'>" +
'<h3>Transfer Mod Powers &#128081;</h3>' +
"<div id='transfer-mod-modal-content'></div>" +
"<div class='modal-button-container'>" +
@@ -188,7 +188,7 @@ export const templates = {
'</div>',
NAME_CHANGE_MODAL:
"<div id='change-name-modal-background' class='modal-background'></div>" +
"<div id='change-name-modal' class='modal'>" +
"<div tabindex='-1' id='change-name-modal' class='modal'>" +
"<form id='change-name-form'>" +
"<div id='transfer-mod-form-content'>" +
"<label for='player-new-name'>Your name:</label>" +
@@ -201,7 +201,7 @@ export const templates = {
'</div>',
ROLE_INFO_MODAL:
"<div id='role-info-modal-background' class='modal-background'></div>" +
"<div id='role-info-modal' class='modal'>" +
"<div tabindex='-1' id='role-info-modal' class='modal'>" +
'<h2>Roles in this game:</h2>' +
"<div id='game-role-info-container'></div>" +
"<div class='modal-button-container'>" +
@@ -243,8 +243,8 @@ export const templates = {
'</span>' +
'</button>' +
'<div id="custom-role-actions" style="display:none">' +
'<div class="custom-role-action" id="custom-roles-export">Export</div>' +
'<div class="custom-role-action" id="custom-roles-import">Import</div>' +
'<div tabindex="0" class="custom-role-action" id="custom-roles-export">Export</div>' +
'<div tabindex="0" class="custom-role-action" id="custom-roles-import">Import</div>' +
'</div>' +
'<label for="add-card-to-deck-form">Custom Role Box</label>' +
'<div id="deck-select"></div>' +
@@ -258,9 +258,9 @@ export const templates = {
DECK_SELECT_ROLE:
'<div class="deck-select-role-name"></div>' +
'<div class="deck-select-role-options">' +
'<img class="deck-select-include" src="images/add.svg" title="make available" alt="include"/>' +
'<img class="deck-select-info" src="images/info.svg" title="info" alt="info"/>' +
'<img class="deck-select-edit" src="images/pencil.svg" title="edit" alt="edit"/>' +
'<img class="deck-select-remove" src="images/delete.svg" title="remove" alt="remove"/>' +
'<img tabindex="0" class="deck-select-include" src="images/add.svg" title="make available" alt="include"/>' +
'<img tabindex="0" class="deck-select-info" src="images/info.svg" title="info" alt="info"/>' +
'<img tabindex="0" class="deck-select-edit" src="images/pencil.svg" title="edit" alt="edit"/>' +
'<img tabindex="0" class="deck-select-remove" src="images/delete.svg" title="remove" alt="remove"/>' +
'</div>'
};

View File

@@ -17,7 +17,7 @@ const game = () => {
toast('Disconnected. Attempting reconnect...', 'error', true, false);
});
socket.on('connect', () => {
console.log("connect event fired");
console.log('connect event fired');
socket.emit(globals.COMMANDS.GET_ENVIRONMENT, function (returnedEnvironment) {
prepareGamePage(returnedEnvironment, socket, timerWorker);
});

View File

@@ -12,7 +12,7 @@ th, thead, tr, tt, u, ul, var {
background: transparent;
}
@font-face {
font-family: 'diavlo';
font-family: 'signika-negative';
src: url("../webfonts/Diavlo_LIGHT_II_37.woff2") format("woff2");
}
@@ -49,7 +49,7 @@ body {
}
h1 {
font-family: 'diavlo', sans-serif;
font-family: 'signika-negative', sans-serif;
color: #b1afcd;
filter: drop-shadow(2px 2px 4px black);
margin: 0.5em 0;
@@ -90,7 +90,7 @@ h3 {
color: #f7f7f7;
text-decoration: none;
cursor: pointer;
font-family: 'diavlo', sans-serif;
font-family: 'signika-negative', sans-serif;
margin: 0 0.25em;
}
@@ -236,7 +236,6 @@ input {
padding: 5px 0;
width: 100%;
background-color: #333243;
border-bottom: 2px solid #57566a;
height: 51px;
z-index: 53000;
}
@@ -281,10 +280,10 @@ input {
color: #f7f7f7;
text-decoration: none;
cursor: pointer;
font-family: 'diavlo', sans-serif;
font-family: 'signika-negative', sans-serif;
border-radius: 5px;
padding: 2px 5px;
font-size: 20px;
font-size: 18px;
margin: 0 0.75em;
width: fit-content;
}

View File

@@ -461,8 +461,15 @@ input[type="number"] {
padding: 10px 20px;
}
#step-forward-button img, #step-back-button img {
height: 40px;
filter: drop-shadow(-2px 3px 2px rgb(0 0 0 / 60%));
}
#step-forward-button, #step-back-button {
background-color: #66666657 !important;
padding: 0 !important;
width: 50px;
}
#step-forward-button, #create-game {

View File

@@ -49,7 +49,9 @@
}
#game-state-container h2 {
margin: 0.5em 0;
margin: 1em 0;
text-align: center;
max-width: 17em;
}
#lobby-header {
@@ -82,6 +84,7 @@ h1 {
#end-of-game-header {
display: flex;
flex-wrap: wrap;
margin: 0 !important;
align-items: center;
}
@@ -188,7 +191,7 @@ h1 {
#role-info-modal h2 {
color: #d7d7d7;
font-family: diavlo, sans-serif;
font-family: signika-negative, sans-serif;
font-weight: normal;
}
@@ -302,7 +305,7 @@ h1 {
left: 50%;
transform: translate(-50%, -50%);
font-size: 20px;
font-family: 'diavlo', sans-serif;
font-family: 'signika-negative', sans-serif;
width: 95%;
text-align: center;
white-space: nowrap;
@@ -360,7 +363,7 @@ h1 {
#client-name {
color: whitesmoke;
font-family: 'diavlo', sans-serif;
font-family: 'signika-negative', sans-serif;
font-size: 30px;
margin: 0.25em 2em 0.25em 0;
}
@@ -377,7 +380,7 @@ h1 {
}
label[for='moderator'] {
font-family: 'diavlo', sans-serif;
font-family: 'signika-negative', sans-serif;
color: lightgray;
filter: drop-shadow(2px 2px 4px black);
font-size: 30px;

View File

@@ -58,7 +58,7 @@ h3 {
margin-bottom: 1em;
padding: 1em;
max-width: 23em;
font-family: 'diavlo', sans-serif;
font-family: 'signika-negative', sans-serif;
}
img[src='../images/logo_cropped.gif'] {
@@ -78,7 +78,7 @@ form > div {
#join-container > label {
font-size: 35px;
font-family: 'diavlo', sans-serif;
font-family: 'signika-negative', sans-serif;
color: #b1afcd;
filter: drop-shadow(2px 2px 4px black);
}

View File

@@ -63,7 +63,7 @@
}
#custom-role-info-modal-name {
font-family: 'diavlo', sans-serif;
font-family: 'signika-negative', sans-serif;
font-size: 23px;
}

View File

@@ -23,7 +23,7 @@
<div id="navbar"></div>
<div id="game-creation-container" class="container">
<div id="modal-background" class="modal-background" style="display: none"></div>
<div id="role-modal" class="modal" style="display: none">
<div tabindex="-1" id="role-modal" class="modal" style="display: none">
<form id="role-form">
<div>
<label for="role-name">Role Name</label>
@@ -46,7 +46,7 @@
</div>
</form>
</div>
<div id="upload-custom-roles-modal" class="modal" style="display:none">
<div tabindex="-1" id="upload-custom-roles-modal" class="modal" style="display:none">
<h3>Import Custom Roles</h3>
<form id="upload-custom-roles-form">
<input type="file" id="upload-custom-roles" name="Upload Custom Roles" accept="text/plain"/>
@@ -56,7 +56,7 @@
</div>
</form>
</div>
<div id="custom-role-info-modal" class="modal" style="display:none">
<div tabindex="-1" id="custom-role-info-modal" class="modal" style="display:none">
<h3 id="custom-role-info-modal-name"></h3>
<div id="custom-role-info-modal-alignment"></div>
<div id="custom-role-info-modal-description"></div>