<template> <div> <CustomCodeModal v-if="customCodeInEditMode" :onCancel="closeCustomClodeModal" :onSave="saveCustomCode" :identifier="customCodeInEditMode.identifier" :initialValue="customCodeInEditMode.value" :initialTitle="customCodeInEditMode.title" /> <div class="preset-select"> <SelectComponent :options="getPresetOptions()" :onChange="(v) => loadPreset(v)" :placeholder="getPresetPlaceholder()" :key="generation" /> </div> <div v-for="category in codeCategories" v-bind:key="category.identifier" class="code-group"> <div class="category-title"> <span>{{ getCategoryTitle(category) }}</span> <ButtonComponent :small="true" v-if="category.identifier === 'custom'" className="btn-add-custom-code" label="+" :onClick="initCustomCodeModal" /> </div> <ul> <li v-for="(code, idx) in availableCodes.filter((c) => c.category === category.identifier)" v-bind:key="code.identifier ? code.identifier : idx" :class="code.selected ? 'checked' : code.disabled ? 'disabled' : ''" @click="toggle(code)" @mouseover="inspect(code)" > <span> {{ getCodeTitle(code) }} </span> <div class="code-menu"> <button v-if="code.identifier != null && code.category === 'custom'" type="button" class="btn-edit-custom-code" @click="(e) => deleteCustomCode(e, code.identifier)" > × </button> </div> </li> <li v-if="category.identifier === 'loader'" :class="stageLoaderSelected ? 'checked' : ''" @click="toggleStageLoader()" @mouseover="showStageLoaderHelp()" > {{ getStageLoaderLabel() }} </li> </ul> </div> </div> </template> <script> import { translateCode, translate } from '../i18n/localeHelper'; import codeCategories from '../data/codeCategories.json'; import presetCategories from '../data/presetCategories.json'; import {lskeyLDC} from './DownloadButton.vue'; export default { props: { version: { type: String }, codes: { type: Array }, onSelectionChanged: { type: Function }, onInspect: { type: Function }, onStageLoaderToggle: { type: Function }, onInspectStageLoader: { type: Function }, }, mounted() { this.populate(); }, watch: { codes: function () { this.populate(); this.unselectStageLoader(); }, }, data() { return { customCodes: [], customCodeInEditMode: null, availableCodes: [], codeCategories, presetCategories, stageLoaderSelected: false, generation: 0, }; }, methods: { getPresetOptions() { return [ ...presetCategories.map((c) => ({ label: c.i18nKey, value: c.identifier, })), { label: 'generatorconfig.presets.@lastDLCodes', value: lskeyLDC, }, ]; }, emitChangeEvent() { const selectedCodes = this.availableCodes.filter((c) => c.selected); // add dependencies const deps = new Set(selectedCodes.flatMap(c => c.dependencies)); selectedCodes.push(...this.codes.filter(c => deps.has(c.id))); // emit this.onSelectionChanged(selectedCodes); }, loadPreset(identifier) { if ( (this.stageLoaderSelected || this.availableCodes.find((c) => c.selected)) && !confirm(translate('common.selectionreset', this.$lang)) ) { this.generation++; return; } if (identifier === lskeyLDC) { // previous downloaded code let titles = new Set(); try { const o = JSON.parse(localStorage.getItem(lskeyLDC)); if (o instanceof Array) { titles = new Set(o); } } catch {} for (const code of this.availableCodes) { code.selected = titles.has(code.title.find(o => o.lang === 'en-US').content); } } else { // regular preset for (const code of this.availableCodes) { code.selected = code.presets.includes(identifier); } } this.unselectStageLoader(); this.refreshDisabledCodes(); this.emitChangeEvent(); this.generation++; }, getPresetPlaceholder() { return translate('common.loadpresetplaceholder', this.$lang); }, unselectStageLoader() { if (this.stageLoaderSelected) { this.stageLoaderSelected = false; this.onStageLoaderToggle(false); } }, getCodeTitle(code) { return translateCode(code, this.$lang).title; }, getCategoryTitle(category) { return translate(category.i18nKey, this.$lang); }, getStageLoaderLabel() { return translate('headers.stageloader', this.$lang); }, toggleStageLoader() { for (const c of this.availableCodes.filter((c) => c.category === 'loader' && c.selected)) { c.selected = false; } const newState = !this.stageLoaderSelected; this.stageLoaderSelected = newState; this.onStageLoaderToggle(newState); this.refreshDisabledCodes(); this.emitChangeEvent(); }, refreshDisabledCodes() { for (const code of this.availableCodes) { if (code.dependsOn) { if (code.dependsOn === 'loader' && this.stageLoaderSelected) { code.disabled = false; } else if ( !this.availableCodes.some((c) => c.selected && c.category === code.dependsOn) ) { code.disabled = true; code.selected = false; } else { code.disabled = false; } } } }, initCustomCodeModal() { this.customCodeInEditMode = { identifier: btoa(new Date().toISOString()), title: undefined, value: undefined, }; }, closeCustomClodeModal() { this.customCodeInEditMode = null; }, deleteCustomCode(e, identifier) { e.stopPropagation(); this.customCodes = this.customCodes.filter((c) => c.identifier !== identifier); localStorage.setItem('custom-codes', JSON.stringify(this.customCodes)); this.availableCodes = this.availableCodes.filter((c) => c.identifier !== identifier); this.emitChangeEvent(); }, saveCustomCode(identifier, title, value) { const updatedCode = { identifier, author: '-', title: [ { lang: 'en-US', content: title, }, ], description: [ { lang: 'en-US', content: '-', html: '<p>-</p>', }, ], version: '-', date: new Date().toLocaleDateString('en-US', { month: 'short', day: '2-digit', year: 'numeric', }), source: value, presets: [], category: 'custom', dependsOn: null, createdOnVersion: this.version, }; this.customCodes = [ ...this.customCodes.filter((c) => c.identifier !== identifier), updatedCode, ]; localStorage.setItem('custom-codes', JSON.stringify(this.customCodes)); this.availableCodes = [ ...this.availableCodes.filter((c) => c.identifier !== identifier), { ...updatedCode, selected: false }, ]; this.closeCustomClodeModal(); }, toggle(code) { if (!code.selected && codeCategories.find((c) => c.identifier === code.category).exclusive) { for (const availableCode of this.availableCodes.filter( (c) => c.category === code.category && c.selected, )) { availableCode.selected = false; } } if (!code.selected && code.category === 'loader' && this.stageLoaderSelected) { this.stageLoaderSelected = false; this.onStageLoaderToggle(false); } code.selected = code.disabled ? false : !code.selected; this.refreshDisabledCodes(); this.emitChangeEvent(); }, populate() { const storedCustomCodes = localStorage.getItem('custom-codes'); if (storedCustomCodes) { try { const parsedCodes = JSON.parse(storedCustomCodes); this.customCodes = parsedCodes; } catch (err) { this.customCodes = []; } } else { this.customCodes = []; } this.availableCodes = [ ...this.codes.map((c) => ({ ...c, selected: false })), ...this.customCodes.map((c) => ({ ...c, selected: false })), ]; this.refreshDisabledCodes(); }, inspect(code) { this.onInspect(code); }, showStageLoaderHelp() { this.onInspectStageLoader(); }, }, }; </script> <style scoped> .category-title { position: relative; color: white; font-weight: 500; text-align: center; background: #383838b5; padding: 2px; margin-bottom: 0; display: grid; grid-template-columns: auto min-content; } .btn-add-custom-code { min-width: unset; width: auto; } .btn-edit-custom-code { background: transparent; border: none; font-size: 1.2em; font-weight: bold; color: red; cursor: pointer; } .category-title ~ ul { margin-top: 0; } .preset-select { margin-bottom: 20px; } .code-group { border: 1px solid #d7d7d7; margin-bottom: 20px; } .code-group ul { margin-bottom: 0; } ul { list-style-type: none; padding-left: 0; } ul li { cursor: pointer; color: #262626; transition: 0.1s; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; outline: none; display: block; overflow: hidden; padding-right: 5px; min-width: 260px; max-width: 260px; text-align: left; position: relative; display: grid; grid-template-columns: auto min-content min-content; } ul li > span { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; } ul li:nth-child(odd) { background: #f3f3f3; } ul li:not(.disabled):hover { background: #3eaf7c; color: #fff; } ul li.checked:hover { background: #3eaf7c; color: #fff; } ul li.checked { background: #434343; color: #fff; } ul li.disabled { background: #c7c7c7; color: #767676; } ul li.disabled:hover { cursor: not-allowed; } li { position: relative; padding-left: 26px; } li::before { content: ''; position: absolute; border-color: #e7e7e7; border-style: solid; border-width: 2px; border-radius: 50%; left: 6px; top: 6px; height: 10px; width: 10px; } li:not(.disabled):not(.checked):hover::before { border-color: #fff; background-color: #1fa76e; } li.checked::before { border-color: #fff; background-color: #d85e55; } @media screen and (max-width: 1100px) { ul li { max-width: 100%; } } </style>