This commit is contained in:
CDeenen
2020-11-19 05:26:07 +01:00
parent 5c2357edd6
commit 0a4d32aaac
12 changed files with 516 additions and 180 deletions

View File

@@ -20,17 +20,17 @@ export const moduleName = "MaterialDeck";
export var selectedTokenId; export var selectedTokenId;
let ready = false; let ready = false;
let activeSounds = [];
//CONFIG.debug.hooks = true; //CONFIG.debug.hooks = true;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// Global variables // Global variables
// //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
var enableModule; export var enableModule;
//Websocket variables //Websocket variables
let ip = "localhost"; //Ip address of the websocket server
let port = "3001"; //Port of the websocket server
var ws; //Websocket variable var ws; //Websocket variable
let wsOpen = false; //Bool for checking if websocket has ever been opened => changes the warning message if there's no connection let wsOpen = false; //Bool for checking if websocket has ever been opened => changes the warning message if there's no connection
let wsInterval; //Interval timer to detect disconnections let wsInterval; //Interval timer to detect disconnections
@@ -101,17 +101,16 @@ async function analyzeWSmessage(msg){
combatTracker.keyPress(settings,context); combatTracker.keyPress(settings,context);
else if (action == 'playlist') else if (action == 'playlist')
playlistControl.keyPress(settings,context); playlistControl.keyPress(settings,context);
else if (action == 'soundboard'){ else if (action == 'soundboard')
soundboard.keyPressDown(settings); soundboard.keyPressDown(settings);
} else if (action == 'other')
otherControls.keyPress(settings);
} }
else if (event == 'keyUp'){ else if (event == 'keyUp'){
if (action == 'soundboard'){ if (action == 'soundboard'){
soundboard.keyPressUp(settings); soundboard.keyPressUp(settings);
} }
else if (action == 'other')
otherControls.keyPress(settings);
} }
}; };
@@ -122,8 +121,8 @@ async function analyzeWSmessage(msg){
* If message is received, reset the interval, and send the message to analyzeWSmessage() * If message is received, reset the interval, and send the message to analyzeWSmessage()
*/ */
function startWebsocket() { function startWebsocket() {
//ip = localhost; const address = game.settings.get(moduleName,'address');
ws = new WebSocket('ws://'+ip+':'+port); ws = new WebSocket('ws://'+address+'/');
ws.onmessage = function(msg){ ws.onmessage = function(msg){
//console.log(msg); //console.log(msg);
@@ -134,7 +133,7 @@ function startWebsocket() {
ws.onopen = function() { ws.onopen = function() {
WSconnected = true; WSconnected = true;
ui.notifications.info("Material Deck "+game.i18n.localize("MaterialDeck.Notifications.Connected") +": "+ip+':'+port); ui.notifications.info("Material Deck "+game.i18n.localize("MaterialDeck.Notifications.Connected") +": "+address);
wsOpen = true; wsOpen = true;
const msg = { const msg = {
target: "server", target: "server",
@@ -181,28 +180,31 @@ export function sendWS(txt){
*/ */
Hooks.once('ready', ()=>{ Hooks.once('ready', ()=>{
enableModule = (game.settings.get(moduleName,'Enable')) ? true : false; enableModule = (game.settings.get(moduleName,'Enable')) ? true : false;
if (enableModule == false) return;
game.socket.on(`module.MaterialDeck`, (payload) =>{ game.socket.on(`module.MaterialDeck`, (payload) =>{
//console.log(payload); //console.log(payload);
if (payload.msgType != "playSound") return; if (payload.msgType != "playSound") return;
playTrack(payload.trackNr,payload.play,payload.repeat,payload.volume); playTrack(payload.trackNr,payload.src,payload.play,payload.repeat,payload.volume);
}); });
for (let i=0; i<64; i++)
activeSounds[i] = false;
if (enableModule == false) return;
if (game.user.isGM == false) { if (game.user.isGM == false) {
ready = true; ready = true;
return; return;
} }
startWebsocket(); startWebsocket();
soundboard = new SoundboardControl();
streamDeck = new StreamDeck(); streamDeck = new StreamDeck();
tokenControl = new TokenControl(); tokenControl = new TokenControl();
move = new Move(); move = new Move();
macroControl = new MacroControl(); macroControl = new MacroControl();
combatTracker = new CombatTracker(); combatTracker = new CombatTracker();
playlistControl = new PlaylistControl(); playlistControl = new PlaylistControl();
soundboard = new SoundboardControl();
otherControls = new OtherControls(); otherControls = new OtherControls();
@@ -235,18 +237,9 @@ Hooks.once('ready', ()=>{
}); });
export function playTrack(soundNr,play,repeat,volume){ export function playTrack(soundNr,src,play,repeat,volume){
if (play){ if (play){
let trackId = game.settings.get(moduleName,'soundboardSettings').sounds[soundNr];
let playlistId = game.settings.get(moduleName,'soundboardSettings').playlist;
let sounds = game.playlists.entities.find(p => p._id == playlistId).data.sounds;
let sound = sounds.find(p => p._id == trackId);
if (sound == undefined){
activeSounds[soundNr] = false;
return;
}
volume *= game.settings.get("core", "globalInterfaceVolume"); volume *= game.settings.get("core", "globalInterfaceVolume");
let src = sound.path;
let howl = new Howl({src, volume, loop: repeat, onend: (id)=>{ let howl = new Howl({src, volume, loop: repeat, onend: (id)=>{
if (repeat == false){ if (repeat == false){
@@ -261,6 +254,7 @@ export function playTrack(soundNr,play,repeat,volume){
} }
else { else {
activeSounds[soundNr].stop(); activeSounds[soundNr].stop();
activeSounds[soundNr] = false;
} }
} }
@@ -329,7 +323,32 @@ Hooks.on('targetToken',(user,token,targeted)=>{
if (token.id == selectedTokenId) tokenControl.update(selectedTokenId); if (token.id == selectedTokenId) tokenControl.update(selectedTokenId);
}); });
Hooks.once('init', ()=>{ Hooks.on('sidebarCollapse',()=>{
if (enableModule == false || ready == false) return;
otherControls.updateAll();
});
Hooks.on('renderCompendium',()=>{
if (enableModule == false || ready == false) return;
otherControls.updateAll();
});
Hooks.on('closeCompendium',()=>{
if (enableModule == false || ready == false) return;
otherControls.updateAll();
});
Hooks.on('renderJournalSheet',()=>{
if (enableModule == false || ready == false) return;
otherControls.updateAll();
});
Hooks.on('closeJournalSheet',()=>{
if (enableModule == false || ready == false) return;
otherControls.updateAll();
});
Hooks.once('init', ()=>{
//CONFIG.debug.hooks = true; //CONFIG.debug.hooks = true;
registerSettings(); //in ./src/settings.js registerSettings(); //in ./src/settings.js
}); });
@@ -350,6 +369,21 @@ export function getFromJSONArray(data,i){
else if (i == 6) val = data.g; else if (i == 6) val = data.g;
else if (i == 7) val = data.h; else if (i == 7) val = data.h;
else if (i == 8) val = data.i; else if (i == 8) val = data.i;
else if (i == 9) val = data.j;
else if (i == 10) val = data.k;
else if (i == 11) val = data.l;
else if (i == 12) val = data.m;
else if (i == 13) val = data.n;
else if (i == 14) val = data.o;
else if (i == 15) val = data.p;
else if (i == 16) val = data.q;
else if (i == 17) val = data.r;
else if (i == 18) val = data.s;
else if (i == 19) val = data.t;
else if (i == 20) val = data.u;
else if (i == 21) val = data.v;
else if (i == 22) val = data.w;
else if (i == 23) val = data.x;
return val; return val;
} }
@@ -364,4 +398,19 @@ export function setToJSONArray(data,i,val){
else if (i == 6) data.g = val; else if (i == 6) data.g = val;
else if (i == 7) data.h = val; else if (i == 7) data.h = val;
else if (i == 8) data.i = val; else if (i == 8) data.i = val;
else if (i == 9) data.j = val;
else if (i == 10) data.k = val;
else if (i == 11) data.l = val;
else if (i == 12) data.m = val;
else if (i == 13) data.n = val;
else if (i == 14) data.o = val;
else if (i == 15) data.p = val;
else if (i == 16) data.q = val;
else if (i == 17) data.r = val;
else if (i == 18) data.s = val;
else if (i == 19) data.t = val;
else if (i == 20) data.u = val;
else if (i == 21) data.v = val;
else if (i == 22) data.w = val;
else if (i == 23) data.x = val;
} }

View File

@@ -1,4 +1,18 @@
# Changelog Material Deck Module # Changelog Material Deck Module
### v0.8.7 - 19-11-2020
<ul>
<li>Added support for more playlists</li>
<li>Added playmode setting for each playlist, which overrides the default playmode</li>
<li>Added option to use the file picker to select Soundboard sounds, including support for wildcard names</li>
<li>Fixed issue where macro config screen would not close if the module was disabled</li>
<li>Fixed issue where the layout of the configuration screens would be messed up depending on browser/screen size</li>
<li>Added option to open compendia and journal entries</li>
</ul>
<b>Compatible server app and SD plugin:</b><br>
Material Server v1.0.1: https://github.com/CDeenen/MaterialServer/releases <br>
SD plugin v0.9.0: https://github.com/CDeenen/MaterialDeck_SD/releases<br>
### v0.8.6 - 18-11-2020 ### v0.8.6 - 18-11-2020
<ul> <ul>
<li>Added support for the new Material Server app</li> <li>Added support for the new Material Server app</li>

View File

@@ -12,15 +12,20 @@
"MaterialDeck.Sett.PlaylistConfig": "Playlist Configuration", "MaterialDeck.Sett.PlaylistConfig": "Playlist Configuration",
"MaterialDeck.Sett.MacroConfig": "Macro Configuration", "MaterialDeck.Sett.MacroConfig": "Macro Configuration",
"MaterialDeck.Sett.SoundboardConfig": "Soundboard Configuration", "MaterialDeck.Sett.SoundboardConfig": "Soundboard Configuration",
"MaterialDeck.Sett.ServerAddr": "Material Server Address",
"MaterialDeck.Sett.ServerAddrHint": "Fill in the IP address and port of the Material Server. Must follow the format [ip_address]:[port], for example: 'localhost:3001' or '192.168.1.1:4000'.",
"MaterialDeck.PL.Unrestricted": "Unrestricted", "MaterialDeck.PL.Unrestricted": "Unrestricted",
"MaterialDeck.PL.OneTrackPlaylist": "One track per playlist", "MaterialDeck.PL.OneTrackPlaylist": "One track per playlist",
"MaterialDeck.PL.OneTrackTotal": "One track in total", "MaterialDeck.PL.OneTrackTotal": "One track in total",
"MaterialDeck.PL.Header": "Play Method", "MaterialDeck.PL.OneTrack": "One track",
"MaterialDeck.PL.Label": "Method", "MaterialDeck.PL.Settings": "Settings",
"MaterialDeck.PL.Mode": "Default Playmode",
"MaterialDeck.PL.Nr": "Number of playlists",
"MaterialDeck.Playlists": "Playlists", "MaterialDeck.Playlists": "Playlists",
"MaterialDeck.Playlist": "Playlist", "MaterialDeck.Playlist": "Playlist",
"MaterialDeck.FilePicker": "File Picker",
"MaterialDeck.FurnaceArgs": "Furnace arguments", "MaterialDeck.FurnaceArgs": "Furnace arguments",
"MaterialDeck.Background": "Background", "MaterialDeck.Background": "Background",
"MaterialDeck.Macro": "Macro", "MaterialDeck.Macro": "Macro",

View File

@@ -2,7 +2,7 @@
"name": "MaterialDeck", "name": "MaterialDeck",
"title": "Material Deck", "title": "Material Deck",
"description": "", "description": "",
"version": "0.8.6", "version": "0.9.0",
"author": "CDeenen", "author": "CDeenen",
"esmodules": [ "esmodules": [
"./MaterialDeck.js" "./MaterialDeck.js"

View File

@@ -5,6 +5,8 @@ export class playlistConfigForm extends FormApplication {
constructor(data, options) { constructor(data, options) {
super(data, options); super(data, options);
this.data = data; this.data = data;
this.playlistNr;
this.updatePlaylistNr = false;
} }
/** /**
@@ -24,26 +26,31 @@ export class playlistConfigForm extends FormApplication {
* Provide data to the template * Provide data to the template
*/ */
getData() { getData() {
const selectedPlaylists = game.settings.get(MODULE.moduleName,'selectedPlaylists'); let selectedPlaylists = game.settings.get(MODULE.moduleName,'selectedPlaylists');
let playlistData = {}; if (selectedPlaylists == undefined) selectedPlaylists = [];
let selectedPlaylistMethod = game.settings.get(MODULE.moduleName, 'selectedPlaylistMethod');
for (let i=0; i<9; i++){ if (selectedPlaylistMethod == undefined) selectedPlaylistMethod = [];
let playlist; let playlistData = [];
playlist = MODULE.getFromJSONArray(selectedPlaylists,i); let numberOfPlaylists = game.settings.get(MODULE.moduleName,'numberOfPlaylists');
if (this.updatePlaylistNr) numberOfPlaylists = this.playlistNr;
this.updatePlaylistNr = false;
for (let i=0; i<numberOfPlaylists; i++){
if (selectedPlaylists[i] == undefined) selectedPlaylists[i] = 'none';
if (selectedPlaylistMethod[i] == undefined) selectedPlaylistMethod[i] = 0;
let dataThis = { let dataThis = {
iteration: i+1, iteration: i+1,
playlist: selectedPlaylists[i], playlist: selectedPlaylists[i],
playlistMethod: selectedPlaylistMethod[i],
playlists: game.playlists.entities playlists: game.playlists.entities
} }
MODULE.setToJSONArray(playlistData,i,dataThis); playlistData.push(dataThis);
} }
if (!this.data && selectedPlaylists) { if (!this.data && selectedPlaylists) {
this.data = selectedPlaylists; this.data = selectedPlaylists;
} }
return { return {
playlists: game.playlists.entities, playlists: game.playlists.entities,
numberOfPlaylists: numberOfPlaylists,
playlistData: playlistData, playlistData: playlistData,
playMethod: game.settings.get(MODULE.moduleName,'playlistMethod') playMethod: game.settings.get(MODULE.moduleName,'playlistMethod')
} }
@@ -57,14 +64,19 @@ export class playlistConfigForm extends FormApplication {
async _updateObject(event, formData) { async _updateObject(event, formData) {
await game.settings.set(MODULE.moduleName,'selectedPlaylists', formData["selectedPlaylist"]); await game.settings.set(MODULE.moduleName,'selectedPlaylists', formData["selectedPlaylist"]);
await game.settings.set(MODULE.moduleName,'playlistMethod',formData["playMethod"]); await game.settings.set(MODULE.moduleName,'playlistMethod',formData["playMethod"]);
await game.settings.set(MODULE.moduleName,'numberOfPlaylists',formData["plNum"]);
await game.settings.set(MODULE.moduleName,'selectedPlaylistMethod',formData["playlistMethod"]);
} }
activateListeners(html) { activateListeners(html) {
super.activateListeners(html); super.activateListeners(html);
const numberOfPlaylists = html.find("input[name='plNum']");
numberOfPlaylists.on("change", event => {
this.playlistNr = event.target.value;
this.updatePlaylistNr = true;
this.render();
});
} }
} }
@@ -80,21 +92,21 @@ export class macroConfigForm extends FormApplication {
* Default Options for this FormApplication * Default Options for this FormApplication
*/ */
static get defaultOptions() { static get defaultOptions() {
/*
let streamDeckModel = game.settings.get(MODULE.moduleName,'streamDeckModel'); let streamDeckModel = game.settings.get(MODULE.moduleName,'streamDeckModel');
let width; let width;
if (streamDeckModel == 0) if (streamDeckModel == 0)
width = 500; width = 550;
else if (streamDeckModel == 1) else if (streamDeckModel == 1)
width= 800; width= 885;
else else
width = 1400; width = 1400;
*/
return mergeObject(super.defaultOptions, { return mergeObject(super.defaultOptions, {
id: "macro-config", id: "macro-config",
title: "Material Deck: "+game.i18n.localize("MaterialDeck.Sett.MacroConfig"), title: "Material Deck: "+game.i18n.localize("MaterialDeck.Sett.MacroConfig"),
template: "./modules/MaterialDeck/templates/macroConfig.html", template: "./modules/MaterialDeck/templates/macroConfig.html",
classes: ["sheet"], classes: ["sheet"]
width: width
}); });
} }
@@ -108,7 +120,7 @@ export class macroConfigForm extends FormApplication {
if (selectedMacros == undefined) selectedMacros = []; if (selectedMacros == undefined) selectedMacros = [];
if (color == undefined) color = []; if (color == undefined) color = [];
if (args == undefined) args = []; if (args == undefined) args = [];
let macroData = {}; let macroData = [];
let furnaceEnabled = false; let furnaceEnabled = false;
let furnace = game.modules.get("furnace"); let furnace = game.modules.get("furnace");
if (furnace != undefined && furnace.active) furnaceEnabled = true; if (furnace != undefined && furnace.active) furnaceEnabled = true;
@@ -132,7 +144,7 @@ export class macroConfigForm extends FormApplication {
let iteration = 0; let iteration = 0;
for (let j=0; j<jMax; j++){ for (let j=0; j<jMax; j++){
let macroThis = {}; let macroThis = [];
for (let i=0; i<iMax; i++){ for (let i=0; i<iMax; i++){
let colorThis = color[iteration]; let colorThis = color[iteration];
@@ -156,13 +168,13 @@ export class macroConfigForm extends FormApplication {
args: args[iteration], args: args[iteration],
furnace: furnaceEnabled furnace: furnaceEnabled
} }
MODULE.setToJSONArray(macroThis,i,dataThis); macroThis.push(dataThis);
iteration++; iteration++;
} }
let data = { let data = {
dataThis: macroThis, dataThis: macroThis,
}; };
MODULE.setToJSONArray(macroData,j,data); macroData.push(data);
} }
return { return {
@@ -187,6 +199,7 @@ export class macroConfigForm extends FormApplication {
let furnace = game.modules.get("furnace"); let furnace = game.modules.get("furnace");
if (furnace != undefined && furnace.active) if (furnace != undefined && furnace.active)
await game.settings.set(MODULE.moduleName,'macroArgs', formData["args"]); await game.settings.set(MODULE.moduleName,'macroArgs', formData["args"]);
if (MODULE.enableModule)
macroControl.updateAll(); macroControl.updateAll();
} }
@@ -202,7 +215,7 @@ export class soundboardConfigForm extends FormApplication {
super(data, options); super(data, options);
this.data = data; this.data = data;
//this.soundData = {}; //this.soundData = {};
this.playlist; this.playlists = [];
this.updatePlaylist = false; this.updatePlaylist = false;
} }
@@ -210,21 +223,21 @@ export class soundboardConfigForm extends FormApplication {
* Default Options for this FormApplication * Default Options for this FormApplication
*/ */
static get defaultOptions() { static get defaultOptions() {
/*
let streamDeckModel = game.settings.get(MODULE.moduleName,'streamDeckModel'); let streamDeckModel = game.settings.get(MODULE.moduleName,'streamDeckModel');
let width; let width;
if (streamDeckModel == 0) if (streamDeckModel == 0)
width = 500; width = 550;
else if (streamDeckModel == 1) else if (streamDeckModel == 1)
width= 800; width= 885;
else else
width = 1400; width = 1400;
*/
return mergeObject(super.defaultOptions, { return mergeObject(super.defaultOptions, {
id: "soundboard-config", id: "soundboard-config",
title: "Material Deck: "+game.i18n.localize("MaterialDeck.Sett.SoundboardConfig"), title: "Material Deck: "+game.i18n.localize("MaterialDeck.Sett.SoundboardConfig"),
template: "./modules/MaterialDeck/templates/soundboardConfig.html", template: "./modules/MaterialDeck/templates/soundboardConfig.html",
classes: ["sheet"], classes: ["sheet"],
width: width,
height: 720 height: 720
}); });
} }
@@ -238,16 +251,6 @@ export class soundboardConfigForm extends FormApplication {
* Provide data to the template * Provide data to the template
*/ */
getData() { getData() {
let playlistId = game.settings.get(MODULE.moduleName,'soundboardSettings').playlist;
if (this.updatePlaylist) playlistId = this.playlist;
this.updatePlaylist = false;
let playlist = 'none';
let sounds = [];
if (playlistId != undefined){
playlist = game.playlists.entities.find(p => p._id == playlistId);
if (playlist != undefined) sounds = playlist.sounds;
else playlist = 'none';
}
let selectedSounds = game.settings.get(MODULE.moduleName,'soundboardSettings').sounds; let selectedSounds = game.settings.get(MODULE.moduleName,'soundboardSettings').sounds;
let colorOn = game.settings.get(MODULE.moduleName,'soundboardSettings').colorOn; let colorOn = game.settings.get(MODULE.moduleName,'soundboardSettings').colorOn;
let colorOff = game.settings.get(MODULE.moduleName,'soundboardSettings').colorOff; let colorOff = game.settings.get(MODULE.moduleName,'soundboardSettings').colorOff;
@@ -255,6 +258,15 @@ export class soundboardConfigForm extends FormApplication {
let volume = game.settings.get(MODULE.moduleName,'soundboardSettings').volume; let volume = game.settings.get(MODULE.moduleName,'soundboardSettings').volume;
let img = game.settings.get(MODULE.moduleName,'soundboardSettings').img; let img = game.settings.get(MODULE.moduleName,'soundboardSettings').img;
let name = game.settings.get(MODULE.moduleName,'soundboardSettings').name; let name = game.settings.get(MODULE.moduleName,'soundboardSettings').name;
let selectedPlaylists = game.settings.get(MODULE.moduleName,'soundboardSettings').selectedPlaylists;
let src = game.settings.get(MODULE.moduleName,'soundboardSettings').src;
let playlists = [];
playlists.push({id:"none",name:game.i18n.localize("MaterialDeck.None")});
playlists.push({id:"FP",name:game.i18n.localize("MaterialDeck.FilePicker")})
for (let i=0; i<game.playlists.entities.length; i++){
playlists.push({id:game.playlists.entities[i]._id,name:game.playlists.entities[i].name});
}
if (selectedSounds == undefined) selectedSounds = []; if (selectedSounds == undefined) selectedSounds = [];
if (colorOn == undefined) colorOn = []; if (colorOn == undefined) colorOn = [];
@@ -262,7 +274,9 @@ export class soundboardConfigForm extends FormApplication {
if (mode == undefined) mode = []; if (mode == undefined) mode = [];
if (img == undefined) img = []; if (img == undefined) img = [];
if (name == undefined) name = []; if (name == undefined) name = [];
let soundData = {}; if (selectedPlaylists == undefined) selectedPlaylists = [];
if (src == undefined) src = [];
let soundData = [];
let streamDeckModel = game.settings.get(MODULE.moduleName,'streamDeckModel'); let streamDeckModel = game.settings.get(MODULE.moduleName,'streamDeckModel');
let iMax,jMax; let iMax,jMax;
@@ -279,40 +293,58 @@ export class soundboardConfigForm extends FormApplication {
iMax = 8; iMax = 8;
} }
if (this.updatePlaylist) selectedPlaylists = this.playlists;
else this.playlists = selectedPlaylists;
this.updatePlaylist = false;
let iteration = 0; let iteration = 0;
for (let j=0; j<jMax; j++){ for (let j=0; j<jMax; j++){
let soundsThis = {}; let soundsThis = [];
for (let i=0; i<iMax; i++){ for (let i=0; i<iMax; i++){
let selectedPlaylist;
let sounds = [];
if (volume == undefined) volume = 50; if (volume == undefined) volume = 50;
if (selectedPlaylists[iteration]==undefined) selectedPlaylist = 'none';
else if (selectedPlaylists[iteration] == 'none') selectedPlaylist = 'none';
else if (selectedPlaylists[iteration] == 'FP') selectedPlaylist = 'FP';
else {
const pl = game.playlists.entities.find(p => p._id == selectedPlaylists[iteration]);
selectedPlaylist = pl._id;
sounds = pl.sounds;
}
let styleSS = "";
let styleFP ="display:none";
if (selectedPlaylist == 'FP') {
styleSS = 'display:none';
styleFP = ''
}
let dataThis = { let dataThis = {
iteration: iteration+1, iteration: iteration+1,
playlists: playlists,
selectedPlaylist: selectedPlaylist,
sound: selectedSounds[iteration], sound: selectedSounds[iteration],
sounds: sounds, sounds: sounds,
srcPath: src[iteration],
colorOn: colorOn[iteration], colorOn: colorOn[iteration],
colorOff: colorOff[iteration], colorOff: colorOff[iteration],
mode: mode[iteration], mode: mode[iteration],
volume: volume[iteration], volume: volume[iteration],
imgPath: img[iteration], imgPath: img[iteration],
name: name[iteration] name: name[iteration],
styleSS: styleSS,
styleFP: styleFP
} }
MODULE.setToJSONArray(soundsThis,i,dataThis); soundsThis.push(dataThis);
iteration++; iteration++;
} }
let data = { let data = {
dataThis: soundsThis, dataThis: soundsThis,
}; };
MODULE.setToJSONArray(soundData,j,data); soundData.push(data);
} }
return { return {
playlists: game.playlists.entities, soundData: soundData
playlist: playlistId,
sounds: sounds,
selectedSound81: selectedSounds.a,
soundData: soundData,
} }
} }
@@ -324,86 +356,42 @@ export class soundboardConfigForm extends FormApplication {
async _updateObject(event, formData) { async _updateObject(event, formData) {
let length = formData["sounds"].length; let length = formData["sounds"].length;
let img = []; let img = [];
let soundSrc = []
for (let i=0; i<length; i++){ for (let i=0; i<length; i++){
let name = "img"+(i+1); let name = "img"+(i+1);
let src = formData[name]; let src = formData[name];
img[i] = src; img[i] = src;
name = "src"+(i+1);
src = formData[name];
soundSrc[i] = src;
} }
await game.settings.set(MODULE.moduleName,'soundboardSettings',{ await game.settings.set(MODULE.moduleName,'soundboardSettings',{
playlist: formData["playlist"], selectedPlaylists: formData["playlist"],
sounds: formData["sounds"], sounds: formData["sounds"],
colorOn: formData["colorOn"], colorOn: formData["colorOn"],
colorOff: formData["colorOff"], colorOff: formData["colorOff"],
mode: formData["mode"], mode: formData["mode"],
img: img, img: img,
volume: formData["volume"], volume: formData["volume"],
name: formData["name"] name: formData["name"],
src: soundSrc
}); });
//MODULE.launchpad.audioSoundboardUpdate();
} }
async activateListeners(html) { async activateListeners(html) {
super.activateListeners(html); super.activateListeners(html);
const colorPickerOn = html.find("button[name='colorPickerOn']");
const colorPickerOff = html.find("button[name='colorPickerOff']");
const playlistSelect = html.find("select[name='playlist']"); const playlistSelect = html.find("select[name='playlist']");
const volumeSlider = html.find("input[name='volume']");
const soundSelect = html.find("select[name='sounds']");
/*
colorPickerOn.on('click',(event) => {
let target = event.currentTarget.value;
let color = document.getElementById("colorOn"+target).value;
if ((color < 0 && color > 127) || color == "") color = 0;
MODULE.launchpad.colorPicker(target,1,color);
});
colorPickerOff.on('click',(event) => {
let target = event.currentTarget.value;
let color = document.getElementById("colorOff"+target).value;
if ((color < 0 && color > 127) || color == "") color = 0;
MODULE.launchpad.colorPicker(target,0,color);
});
if (playlistSelect.length > 0) { if (playlistSelect.length > 0) {
playlistSelect.on("change", event => { playlistSelect.on("change", event => {
this.playlist = event.target.value; let id = event.target.id.replace('playlists','');
this.playlists[id-1] = event.target.value;
this.updatePlaylist = true; this.updatePlaylist = true;
this.render(); this.render();
}); });
} }
volumeSlider.on('change', event => {
let id = event.target.id.replace('volume','');
let column = id%10-1;
let row = 8-Math.floor(id/10);
id = row*8+column;
let settings = game.settings.get(MODULE.moduleName,'soundboardSettings');
settings.volume[id] = event.target.value;
game.settings.set(MODULE.moduleName,'soundboardSettings',settings);
if (MODULE.launchpad.activeSounds[id] != false){
let volume = AudioHelper.inputToVolume(event.target.value/100) * game.settings.get("core", "globalInterfaceVolume");
MODULE.launchpad.activeSounds[id].volume(volume);
}
});
if (soundSelect.length > 0) {
soundSelect.on("change",event => {
let id = event.target.id.replace('soundSelect','');
let column = id%10-1;
let row = 8-Math.floor(id/10);
id = row*8+column;
let settings = game.settings.get(MODULE.moduleName,'soundboardSettings');
settings.sounds[id] = event.target.value;
game.settings.set(MODULE.moduleName,'soundboardSettings',settings);
if (MODULE.launchpad.activeSounds[id] != false){
let mode = settings.mode[id];
let repeat = false;
if (mode == 1) repeat = true;
MODULE.launchpad.playSound(id,repeat,false);
}
});
}
*/
} }
} }

View File

@@ -36,6 +36,15 @@ export class OtherControls{
else if (mode == 4){ //roll tables else if (mode == 4){ //roll tables
this.updateRollTable(settings,context); this.updateRollTable(settings,context);
} }
else if (mode == 5) { //open sidebar tab
this.updateSidebar(settings,context);
}
else if (mode == 6) { //open compendium
this.updateCompendium(settings,context);
}
else if (mode == 7) { //open journal
this.updateJournal(settings,context);
}
} }
keyPress(settings){ keyPress(settings){
@@ -57,6 +66,15 @@ export class OtherControls{
else if (mode == 4) { //roll tables else if (mode == 4) { //roll tables
this.keyPressRollTable(settings); this.keyPressRollTable(settings);
} }
else if (mode == 5) { //sidebar
this.keyPressSidebar(settings);
}
else if (mode == 6) { //open compendium
this.keyPressCompendium(settings);
}
else if (mode == 7) { //open journal
this.keyPressJournal(settings);
}
} }
////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////
@@ -528,4 +546,175 @@ export class OtherControls{
} }
} }
} }
//////////////////////////////////////////////////////////////////////////////////////////
getSidebarId(nr){
let id;
if (nr == 0) id = 'chat';
else if (nr == 1) id = 'combat';
else if (nr == 2) id = 'scenes';
else if (nr == 3) id = 'actors';
else if (nr == 4) id = 'items';
else if (nr == 5) id = 'journal';
else if (nr == 6) id = 'tables';
else if (nr == 7) id = 'playlists';
else if (nr == 8) id = 'compendium';
else if (nr == 9) id = 'settings';
else id = '';
return id;
}
getSidebarName(nr){
let name;
if (nr == 0) name = game.i18n.localize("SIDEBAR.TabChat");
else if (nr == 1) name = game.i18n.localize("SIDEBAR.TabCombat");
else if (nr == 2) name = game.i18n.localize("SIDEBAR.TabScenes");
else if (nr == 3) name = game.i18n.localize("SIDEBAR.TabActors");
else if (nr == 4) name = game.i18n.localize("SIDEBAR.TabItems");
else if (nr == 5) name = game.i18n.localize("SIDEBAR.TabJournal");
else if (nr == 6) name = game.i18n.localize("SIDEBAR.TabTables");
else if (nr == 7) name = game.i18n.localize("SIDEBAR.TabPlaylists");
else if (nr == 8) name = game.i18n.localize("SIDEBAR.TabCompendium");
else if (nr == 9) name = game.i18n.localize("SIDEBAR.TabSettings");
else if (nr == 10) name = game.i18n.localize("SIDEBAR.CollapseToggle");
return name;
}
getSidebarIcon(nr){
let icon;
if (nr == 0) icon = window.CONFIG.ChatMessage.sidebarIcon;
else if (nr == 1) icon = window.CONFIG.Combat.sidebarIcon;
else if (nr == 2) icon = window.CONFIG.Scene.sidebarIcon;
else if (nr == 3) icon = window.CONFIG.Actor.sidebarIcon;
else if (nr == 4) icon = window.CONFIG.Item.sidebarIcon;
else if (nr == 5) icon = window.CONFIG.JournalEntry.sidebarIcon;
else if (nr == 6) icon = window.CONFIG.RollTable.sidebarIcon;
else if (nr == 7) icon = window.CONFIG.Playlist.sidebarIcon;
else if (nr == 8) icon = "fas fa-atlas";
else if (nr == 9) icon = "fas fa-cogs";
else if (nr == 10) icon = "fas fa-caret-right";
return icon;
}
updateSidebar(settings,context){
let sidebarTab = settings.sidebarTab;
if (sidebarTab == undefined) sidebarTab = 0;
let activeTab = ui.sidebar.activeTab;
let collapsed = ui.sidebar._collapsed;
let name = "";
let icon = "";
let background = settings.background;
if(background == undefined) background = '#000000';
let ringColor = "#000000";
let ringOffColor = settings.offRing;
if (ringOffColor == undefined) ringOffColor = '#000000';
let ringOnColor = settings.onRing;
if (ringOnColor == undefined) ringOnColor = '#00FF00';
if (settings.displaySidebarName) name = this.getSidebarName(sidebarTab);
if (settings.displaySidebarIcon) icon = this.getSidebarIcon(sidebarTab);
if ((sidebarTab == 10 && collapsed))
ringColor = ringOnColor;
else
ringColor = ringOffColor;
streamDeck.setTitle(name,context);
streamDeck.setIcon(1,context,icon,background,2,ringColor);
}
keyPressSidebar(settings){
let sidebarTab = settings.sidebarTab;
if (sidebarTab == undefined) sidebarTab = 0;
let collapsed = ui.sidebar._collapsed;
if (sidebarTab < 10) ui.sidebar.activateTab(this.getSidebarId(sidebarTab));
else if (collapsed) ui.sidebar.expand();
else if (collapsed == false) ui.sidebar.collapse();
}
//////////////////////////////////////////////////////////////////////////////////////////
updateCompendium(settings,context){
let background = settings.background;
if(background == undefined) background = '#000000';
let name = settings.compendiumName;
if (name == undefined) return;
const compendium = game.packs.entries.find(p=>p.metadata.label == name);
if (compendium == undefined) return;
let ringColor = "#000000";
let ringOffColor = settings.offRing;
if (ringOffColor == undefined) ringOffColor = '#000000';
let ringOnColor = settings.onRing;
if (ringOnColor == undefined) ringOnColor = '#00FF00';
if (compendium.rendered) ringColor = ringOnColor;
else ringColor = ringOffColor;
if (settings.displayCompendiumName) streamDeck.setTitle(name,context);
streamDeck.setIcon(0,context,"",background,2,ringColor);
}
keyPressCompendium(settings){
let name = settings.compendiumName;
if (name == undefined) return;
const compendium = game.packs.entries.find(p=>p.metadata.label == name);
if (compendium == undefined) return;
if (compendium.rendered) compendium.close();
else compendium.render(true);
}
//////////////////////////////////////////////////////////////////////////////////////////
//Journals
//game.journal.entries[0].render(true)
updateJournal(settings,context){
let background = settings.background;
if(background == undefined) background = '#000000';
let name = settings.compendiumName;
if (name == undefined) return;
const journal = game.journal.entries.find(p=>p.name == name);
if (journal == undefined) return;
let ringColor = "#000000";
let ringOffColor = settings.offRing;
if (ringOffColor == undefined) ringOffColor = '#000000';
let ringOnColor = settings.onRing;
if (ringOnColor == undefined) ringOnColor = '#00FF00';
if (journal.sheet.rendered) ringColor = ringOnColor;
else ringColor = ringOffColor;
if (settings.displayCompendiumName) streamDeck.setTitle(name,context);
streamDeck.setIcon(0,context,"",background,2,ringColor);
}
keyPressJournal(settings){
let name = settings.compendiumName;
if (name == undefined) return;
const journal = game.journal.entries.find(p=>p.name == name);
if (journal == undefined) return;
//if (journal.sheet.rendered) journal.close();
journal.render(true);
}
} }

View File

@@ -127,12 +127,27 @@ export class PlaylistControl{
streamDeck.setTitle(name,context); streamDeck.setTitle(name,context);
} }
stopAll(){ stopAll(force=false){
if (force){
let playing = game.playlists.playing; let playing = game.playlists.playing;
for (let i=0; i<playing.length; i++){ for (let i=0; i<playing.length; i++){
playing[i].stopAll(); playing[i].stopAll();
} }
} }
else {
let playing = game.playlists.playing;
console.log(playing);
let selectedPlaylists = game.settings.get(MODULE.moduleName,'selectedPlaylists');
console.log(selectedPlaylists);
for (let i=0; i<playing.length; i++){
const playlistNr = selectedPlaylists.findIndex(p => p == playing[i]._id);
const mode = game.settings.get(MODULE.moduleName,'selectedPlaylistMethod')[playlistNr];
if (mode == 0) playing[i].stopAll();
console.log(playlistNr,mode);
}
}
}
getPlaylist(num){ getPlaylist(num){
let playlistId = game.settings.get(MODULE.moduleName,'selectedPlaylists')[num]; let playlistId = game.settings.get(MODULE.moduleName,'selectedPlaylists')[num];
@@ -156,11 +171,11 @@ export class PlaylistControl{
let playlist = this.getPlaylist(playlistNr); let playlist = this.getPlaylist(playlistNr);
if (playlist != undefined){ if (playlist != undefined){
if (settings.playlistMode == 0) if (settings.playlistMode == 0)
this.playPlaylist(playlist); this.playPlaylist(playlist,playlistNr);
else { else {
let track = playlist.data.sounds[trackNr]; let track = playlist.data.sounds[trackNr];
if (track != undefined){ if (track != undefined){
this.playTrack(track,playlist); this.playTrack(track,playlist,playlistNr);
} }
} }
} }
@@ -178,30 +193,38 @@ export class PlaylistControl{
} }
} }
else { else {
this.stopAll(); this.stopAll(true);
} }
} }
async playPlaylist(playlist){ async playPlaylist(playlist,playlistNr){
if (playlist.playing) { if (playlist.playing) {
playlist.stopAll(); playlist.stopAll();
return; return;
} }
let mode = game.settings.get(MODULE.moduleName,'playlistMethod'); let mode = game.settings.get(MODULE.moduleName,'selectedPlaylistMethod')[playlistNr];
if (mode == 0) {
mode = game.settings.get(MODULE.moduleName,'playlistMethod');
if (mode == 2) await this.stopAll(); if (mode == 2) await this.stopAll();
}
playlist.playAll(); playlist.playAll();
} }
async playTrack(track,playlist){ async playTrack(track,playlist,playlistNr){
let play; let play;
if (track.playing) if (track.playing)
play = false; play = false;
else { else {
play = true; play = true;
let mode = game.settings.get(MODULE.moduleName,'playlistMethod'); let mode = game.settings.get(MODULE.moduleName,'selectedPlaylistMethod')[playlistNr];
console.log('mode',mode);
if (mode == 0) {
mode = game.settings.get(MODULE.moduleName,'playlistMethod');
if (mode == 1) await playlist.stopAll(); if (mode == 1) await playlist.stopAll();
else if (mode == 2) await this.stopAll(); else if (mode == 2) await this.stopAll();
} }
else if (mode == 2) await playlist.stopAll();
}
await playlist.updateEmbeddedEntity("PlaylistSound", {_id: track._id, playing: play}); await playlist.updateEmbeddedEntity("PlaylistSound", {_id: track._id, playing: play});
playlist.update({playing: play}); playlist.update({playing: play});
} }

View File

@@ -9,9 +9,9 @@ export const registerSettings = function() {
//Enabled the module //Enabled the module
game.settings.register(MODULE.moduleName,'Enable', { game.settings.register(MODULE.moduleName,'Enable', {
name: "MaterialDeck.Sett.Enable", name: "MaterialDeck.Sett.Enable",
scope: "world", scope: "global",
config: true, config: true,
default: true, default: false,
type: Boolean, type: Boolean,
onChange: x => window.location.reload() onChange: x => window.location.reload()
}); });
@@ -26,6 +26,19 @@ export const registerSettings = function() {
choices:["MaterialDeck.Sett.Model_Mini","MaterialDeck.Sett.Model_Normal","MaterialDeck.Sett.Model_XL"], choices:["MaterialDeck.Sett.Model_Mini","MaterialDeck.Sett.Model_Normal","MaterialDeck.Sett.Model_XL"],
}); });
/**
* Sets the ip address of the server
*/
game.settings.register(MODULE.moduleName,'address', {
name: "MaterialDeck.Sett.ServerAddr",
hint: "MaterialDeck.Sett.ServerAddrHint",
scope: "world",
config: true,
default: "localhost:3001",
type: String,
onChange: x => window.location.reload()
});
/** /**
* Playlist soundboard * Playlist soundboard
*/ */
@@ -49,7 +62,23 @@ export const registerSettings = function() {
name: "selectedPlaylists", name: "selectedPlaylists",
scope: "world", scope: "world",
type: Object, type: Object,
default: {a: "None",b: "None",c: "none",d: "none",e: "none",f: "none",g: "none",h: "none",i: "none"}, default: {},
config: false
});
game.settings.register(MODULE.moduleName, 'selectedPlaylistMethod', {
name: "selectedPlaylistMethod",
scope: "world",
type: Object,
default: {},
config: false
});
game.settings.register(MODULE.moduleName, 'numberOfPlaylists', {
name: "numberOfPlaylists",
scope: "world",
type: Number,
default: 9,
config: false config: false
}); });

View File

@@ -117,30 +117,46 @@ export class SoundboardControl{
this.playSound(soundNr,false,false); this.playSound(soundNr,false,false);
} }
playSound(soundNr,repeat,play){ async playSound(soundNr,repeat,play){
let trackId = game.settings.get(MODULE.moduleName,'soundboardSettings').sounds[soundNr]; const soundBoardSettings = game.settings.get(MODULE.moduleName,'soundboardSettings');
const playlistId = soundBoardSettings.selectedPlaylists[soundNr];
let src;
if (playlistId == "" || playlistId == undefined) return;
if (playlistId == 'none') return;
else if (playlistId == 'FP') {
src = soundBoardSettings.src[soundNr];
const ret = await FilePicker.browse("data", src, {wildcard:true});
const files = ret.files;
if (files.length == 1) src = files;
else {
let value = Math.floor(Math.random() * Math.floor(files.length));
src = files[value];
}
}
else {
const soundId = soundBoardSettings.sounds[soundNr];
const sounds = game.playlists.entities.find(p => p._id == playlistId).data.sounds;
if (sounds == undefined) return;
const sound = sounds.find(p => p._id == soundId);
if (sound == undefined) return;
src = sound.path;
}
let volume = game.settings.get(MODULE.moduleName,'soundboardSettings').volume[soundNr]/100; let volume = game.settings.get(MODULE.moduleName,'soundboardSettings').volume[soundNr]/100;
volume = AudioHelper.inputToVolume(volume); volume = AudioHelper.inputToVolume(volume);
if (trackId == "" || trackId == undefined) return;
let payload = { let payload = {
"msgType": "playSound", "msgType": "playSound",
"trackNr": soundNr, "trackNr": soundNr,
"src": src,
"repeat": repeat, "repeat": repeat,
"play": play, "play": play,
"volume": volume "volume": volume
}; };
game.socket.emit(`module.MaterialDeck`, payload); game.socket.emit(`module.MaterialDeck`, payload);
if (play){ if (play){
let trackId = game.settings.get(MODULE.moduleName,'soundboardSettings').sounds[soundNr];
let playlistId = game.settings.get(MODULE.moduleName,'soundboardSettings').playlist;
let sounds = game.playlists.entities.find(p => p._id == playlistId).data.sounds;
let sound = sounds.find(p => p._id == trackId);
if (sound == undefined){
this.activeSounds[soundNr] = false;
return;
}
volume *= game.settings.get("core", "globalInterfaceVolume"); volume *= game.settings.get("core", "globalInterfaceVolume");
let src = sound.path;
let howl = new Howl({src, volume, loop: repeat, onend: (id)=>{ let howl = new Howl({src, volume, loop: repeat, onend: (id)=>{
if (repeat == false){ if (repeat == false){
@@ -160,5 +176,6 @@ export class SoundboardControl{
this.activeSounds[soundNr] = false; this.activeSounds[soundNr] = false;
} }
this.updateAll(); this.updateAll();
} }
} }

View File

@@ -3,7 +3,7 @@
.boxed { .boxed {
border: 1px solid black ; border: 1px solid black ;
border-radius: 5px ; border-radius: 5px ;
width: 100px; max-width: 166px;
height: {{height}}px; height: {{height}}px;
} }
</style> </style>

View File

@@ -1,9 +1,9 @@
<form autocomplete="off" onsubmit="event.preventDefault()"> <form autocomplete="off" onsubmit="event.preventDefault()">
<div > <div >
<h2>{{localize "MaterialDeck.PL.Header"}}</h2> <h2>{{localize "MaterialDeck.PL.Settings"}}</h2>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>{{localize "MaterialDeck.PL.Label"}}</label> <label>{{localize "MaterialDeck.PL.Mode"}}</label>
<select name="playMethod" class="playMethod" default=""> <select name="playMethod" class="playMethod" default="">
{{#select playMethod}} {{#select playMethod}}
<option value="0">{{localize "MaterialDeck.PL.Unrestricted"}}</option> <option value="0">{{localize "MaterialDeck.PL.Unrestricted"}}</option>
@@ -12,6 +12,10 @@
{{/select}} {{/select}}
</select> </select>
</div> </div>
<div class="form-group">
<label>{{localize "MaterialDeck.PL.Nr"}}</label>
<input type="number" id="numberOfPlaylists" name="plNum" min="0" max="127" value={{numberOfPlaylists}}>
</div>
<div> <div>
<h2>{{localize "MaterialDeck.Playlists"}}</h2> <h2>{{localize "MaterialDeck.Playlists"}}</h2>
</div> </div>
@@ -26,6 +30,14 @@
{{/each}} {{/each}}
{{/select}} {{/select}}
</select> </select>
&nbsp;
<select name="playlistMethod" class="playlistMethod" default="">
{{#select this.playlistMethod}}
<option value="0">{{localize "MaterialDeck.PL.Mode"}}</option>
<option value="1">{{localize "MaterialDeck.PL.Unrestricted"}}</option>
<option value="2">{{localize "MaterialDeck.PL.OneTrack"}}</option>
{{/select}}
</select>
</div> </div>
{{/each}} {{/each}}

View File

@@ -3,21 +3,11 @@
.boxed { .boxed {
border: 1px solid black ; border: 1px solid black ;
border-radius: 5px ; border-radius: 5px ;
width: 100px; max-width: 166px;
height: 255px; height: 300px;
} }
</style> </style>
<div class="form-group">
<label>{{localize "MaterialDeck.Playlist"}}&nbsp;&nbsp;&nbsp;</label>
<select name="playlist" class="playlist-select" default="" style="max-width:200px;">
{{#select playlist}}
<option value="">{{localize "MaterialDeck.None"}}</option>
{{#each playlists}}
<option value="{{this._id}}">{{this.name}}</option>
{{/each}}
{{/select}}
</select>
</div>
{{#each soundData}} {{#each soundData}}
<div class="form-group"> <div class="form-group">
{{#each this.dataThis}} {{#each this.dataThis}}
@@ -29,10 +19,24 @@
{{localize "MaterialDeck.Name"}} {{localize "MaterialDeck.Name"}}
</div> </div>
<input type="text" name="name" value="{{this.name}}"> <input type="text" name="name" value="{{this.name}}">
<div style="text-align:center;">
{{localize "MaterialDeck.Playlist"}}
</div>
<div>
<select name="playlist" class="playlist-select" default="" style="width:132px;" id="playlists{{this.iteration}}">
{{#select this.selectedPlaylist}}
{{#each playlists}}
<option value="{{this.id}}">{{this.name}}</option>
{{/each}}
{{/select}}
</select>
</div>
<div style="text-align:center;"> <div style="text-align:center;">
{{localize "MaterialDeck.Sound"}} {{localize "MaterialDeck.Sound"}}
</div> </div>
<div> <div class="form-fields" style={{this.styleSS}}>
<select name="sounds" class="sounds-select" default="" style="width:132px;" id="soundSelect{{this.iteration}}"> <select name="sounds" class="sounds-select" default="" style="width:132px;" id="soundSelect{{this.iteration}}">
{{#select this.sound}} {{#select this.sound}}
<option value="">{{localize "MaterialDeck.None"}}</option> <option value="">{{localize "MaterialDeck.None"}}</option>
@@ -42,6 +46,12 @@
{{/select}} {{/select}}
</select> </select>
</div> </div>
<div class="form-fields" style={{this.styleFP}}>
<button type="button" class="file-picker" data-type="audio" data-target="src{{this.iteration}}" title="Browse Files" tabindex="-1">
<i class="fas fa-file-import fa-fw"></i>
</button>
<input class="image" type="text" name="src{{this.iteration}}" id="srcPath{{this.iteration}}" placeholder="path/audio.mp3" value={{this.srcPath}}>
</div>
<div style="text-align:center;"> <div style="text-align:center;">
{{localize "MaterialDeck.Icon"}} {{localize "MaterialDeck.Icon"}}
</div> </div>