Bug Fixes und Icons Refactioring
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
#Mon Mar 23 21:09:40 CET 2026
|
||||
#Tue Mar 24 06:41:43 CET 2026
|
||||
display=\:0
|
||||
host=mario-mint
|
||||
process-id=80279
|
||||
process-id=4231
|
||||
user=mario
|
||||
|
||||
@@ -226,3 +226,88 @@ Binding(CTRL+SHIFT+T,
|
||||
,,true),null),
|
||||
org.eclipse.ui.defaultAcceleratorConfiguration,
|
||||
org.eclipse.ui.contexts.window,,,system)
|
||||
|
||||
!ENTRY org.springframework.tooling.boot.ls 1 0 2026-03-23 23:23:49.341
|
||||
!MESSAGE DelegatingStreamConnectionProvider - Stopping Boot LS
|
||||
|
||||
!ENTRY org.springframework.tooling.ls.eclipse.commons 1 0 2026-03-23 23:23:50.541
|
||||
!MESSAGE executing callback sts4.classpath.bMrwvfMB FAILED
|
||||
|
||||
!ENTRY org.springframework.tooling.ls.eclipse.commons 4 0 2026-03-23 23:23:50.542
|
||||
!MESSAGE java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.CompletableFuture$UniCompose@203dc124 rejected from java.util.concurrent.ThreadPoolExecutor@6d6cd631[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 194]
|
||||
!STACK 0
|
||||
java.util.concurrent.ExecutionException: java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.CompletableFuture$UniCompose@203dc124 rejected from java.util.concurrent.ThreadPoolExecutor@6d6cd631[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 194]
|
||||
at java.base/java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:396)
|
||||
at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2096)
|
||||
at org.springframework.tooling.ls.eclipse.commons.LSP4ECommandExecutor.executeClientCommand(LSP4ECommandExecutor.java:31)
|
||||
at org.springframework.tooling.jdt.ls.commons.classpath.SendClasspathNotificationsJob.flush(SendClasspathNotificationsJob.java:192)
|
||||
at org.springframework.tooling.jdt.ls.commons.classpath.SendClasspathNotificationsJob.run(SendClasspathNotificationsJob.java:151)
|
||||
at org.eclipse.core.internal.jobs.Worker.run(Worker.java:63)
|
||||
Caused by: java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.CompletableFuture$UniCompose@203dc124 rejected from java.util.concurrent.ThreadPoolExecutor@6d6cd631[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 194]
|
||||
at java.base/java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2081)
|
||||
at java.base/java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:841)
|
||||
at java.base/java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1376)
|
||||
at java.base/java.util.concurrent.Executors$DelegatedExecutorService.execute(Executors.java:754)
|
||||
at java.base/java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:1184)
|
||||
at java.base/java.util.concurrent.CompletableFuture.thenComposeAsync(CompletableFuture.java:2352)
|
||||
at org.eclipse.lsp4e.LanguageServerWrapper.executeImpl(LanguageServerWrapper.java:1054)
|
||||
at org.eclipse.lsp4e.LanguageServers.lambda$23(LanguageServers.java:445)
|
||||
at java.base/java.util.concurrent.CompletableFuture.uniApplyNow(CompletableFuture.java:684)
|
||||
at java.base/java.util.concurrent.CompletableFuture.uniApplyStage(CompletableFuture.java:662)
|
||||
at java.base/java.util.concurrent.CompletableFuture.thenApply(CompletableFuture.java:2200)
|
||||
at org.eclipse.lsp4e.LanguageServers.lambda$22(LanguageServers.java:443)
|
||||
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
|
||||
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1708)
|
||||
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
|
||||
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
|
||||
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:575)
|
||||
at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260)
|
||||
at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616)
|
||||
at org.eclipse.lsp4e.LanguageServers.computeFirst(LanguageServers.java:181)
|
||||
at org.eclipse.lsp4e.LanguageServers.computeFirst(LanguageServers.java:145)
|
||||
at org.springframework.tooling.ls.eclipse.commons.LSP4ECommandExecutor.executeClientCommand(LSP4ECommandExecutor.java:29)
|
||||
... 3 more
|
||||
!SESSION 2026-03-24 06:41:38.442 -----------------------------------------------
|
||||
eclipse.buildId=4.39.0.20260305-0817
|
||||
java.version=21.0.6
|
||||
java.vendor=Eclipse Adoptium
|
||||
BootLoader constants: OS=linux, ARCH=x86_64, WS=gtk, NL=de_DE
|
||||
Framework arguments: -product org.eclipse.epp.package.java.product
|
||||
Command-line arguments: -os linux -ws gtk -arch x86_64 -clean -product org.eclipse.epp.package.java.product
|
||||
|
||||
!ENTRY ch.qos.logback.classic 1 0 2026-03-24 06:41:39.956
|
||||
!MESSAGE Activated before the state location was initialized. Retry after the state location is initialized.
|
||||
|
||||
!ENTRY ch.qos.logback.classic 1 0 2026-03-24 06:41:44.218
|
||||
!MESSAGE Logback config file: /home/mario/Workspaces/xxx-thegame/.metadata/.plugins/org.eclipse.m2e.logback/logback.2.7.101.20251017-1242.xml
|
||||
|
||||
!ENTRY org.eclipse.ui 2 0 2026-03-24 06:41:44.360
|
||||
!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points.
|
||||
!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-24 06:41:44.360
|
||||
!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory'
|
||||
|
||||
!ENTRY org.eclipse.ui 2 0 2026-03-24 06:41:44.512
|
||||
!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points.
|
||||
!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-24 06:41:44.512
|
||||
!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory'
|
||||
|
||||
!ENTRY org.eclipse.jface 2 0 2026-03-24 06:46:26.895
|
||||
!MESSAGE Keybinding conflicts occurred. They may interfere with normal accelerator operation.
|
||||
!SUBENTRY 1 org.eclipse.jface 2 0 2026-03-24 06:46:26.895
|
||||
!MESSAGE A conflict occurred for CTRL+SHIFT+T:
|
||||
Binding(CTRL+SHIFT+T,
|
||||
ParameterizedCommand(Command(org.eclipse.jdt.ui.navigate.open.type,Open Type,
|
||||
Open a type in a Java editor,
|
||||
Category(org.eclipse.ui.category.navigate,Navigate,null,true),
|
||||
WorkbenchHandlerServiceHandler("org.eclipse.jdt.ui.navigate.open.type"),
|
||||
,,true),null),
|
||||
org.eclipse.ui.defaultAcceleratorConfiguration,
|
||||
org.eclipse.ui.contexts.window,,,system)
|
||||
Binding(CTRL+SHIFT+T,
|
||||
ParameterizedCommand(Command(org.eclipse.lsp4e.symbolInWorkspace,Go to Symbol in Workspace,
|
||||
,
|
||||
Category(org.eclipse.lsp4e.category,Language Servers,null,true),
|
||||
WorkbenchHandlerServiceHandler("org.eclipse.lsp4e.symbolInWorkspace"),
|
||||
,,true),null),
|
||||
org.eclipse.ui.defaultAcceleratorConfiguration,
|
||||
org.eclipse.ui.contexts.window,,,system)
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<typeInfoHistroy>
|
||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.aufgaben{DefaultFiller.java[DefaultFiller" modifiers="1" timestamp="1772437686926"/>
|
||||
<typeInfo handle="=xxxthegame/src\/main\/java=/gradle_scope=/main=/=/gradle_used_by_scope=/main,test=/<de.oaa.xxx.aufgaben.controller{FillerController.java[FillerController" modifiers="1" timestamp="1772385528555"/>
|
||||
</typeInfoHistroy>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<section name="DialogBoundsSettings">
|
||||
<item key="DIALOG_HEIGHT" value="500"/>
|
||||
<item key="DIALOG_WIDTH" value="600"/>
|
||||
<item key="DIALOG_X_ORIGIN" value="980"/>
|
||||
<item key="DIALOG_X_ORIGIN" value="680"/>
|
||||
<item key="DIALOG_Y_ORIGIN" value="351"/>
|
||||
<item key="DIALOG_FONT_NAME" value="1|Ubuntu|10.0|0|GTK|1|"/>
|
||||
</section>
|
||||
|
||||
@@ -5,3 +5,4 @@
|
||||
2026-03-23 17:36:54,482 [Worker-8: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
|
||||
2026-03-23 17:38:51,039 [Worker-7: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
|
||||
2026-03-23 21:09:44,347 [Worker-7: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
|
||||
2026-03-24 06:41:47,661 [Worker-2: Loading available Gradle versions] INFO o.e.b.c.i.u.g.PublishedGradleVersions - Gradle version information cache is up-to-date. Trying to read.
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#Mon Mar 23 21:09:40 CET 2026
|
||||
#Tue Mar 24 06:41:43 CET 2026
|
||||
org.eclipse.core.runtime=2
|
||||
org.eclipse.platform=4.39.0.v20260226-0420
|
||||
|
||||
@@ -4,7 +4,12 @@ import de.oaa.xxx.aufgaben.AufgabenGruppe;
|
||||
import de.oaa.xxx.aufgaben.Toy;
|
||||
import de.oaa.xxx.aufgaben.entity.AufgabenGruppeEntity;
|
||||
import de.oaa.xxx.aufgaben.entity.ToyEntity;
|
||||
import de.oaa.xxx.aufgaben.repository.AufgabeRepository;
|
||||
import de.oaa.xxx.aufgaben.repository.AufgabenGruppeRepository;
|
||||
import de.oaa.xxx.aufgaben.repository.FinisherRepository;
|
||||
import de.oaa.xxx.aufgaben.repository.GruppenAboRepository;
|
||||
import de.oaa.xxx.aufgaben.repository.SperreRepository;
|
||||
import de.oaa.xxx.aufgaben.repository.StrafeRepository;
|
||||
import de.oaa.xxx.aufgaben.repository.ToyRepository;
|
||||
import de.oaa.xxx.meldung.MeldungEntity;
|
||||
import de.oaa.xxx.meldung.MeldungRepository;
|
||||
@@ -31,16 +36,31 @@ public class AdminController {
|
||||
private final UserRepository userRepository;
|
||||
private final MeldungRepository meldungRepository;
|
||||
private final AufgabenGruppeRepository aufgabenGruppeRepository;
|
||||
private final AufgabeRepository aufgabeRepository;
|
||||
private final StrafeRepository strafeRepository;
|
||||
private final SperreRepository sperreRepository;
|
||||
private final FinisherRepository finisherRepository;
|
||||
private final GruppenAboRepository gruppenAboRepository;
|
||||
private final ToyRepository toyRepository;
|
||||
|
||||
public AdminController(AdminRepository adminRepository, UserRepository userRepository,
|
||||
MeldungRepository meldungRepository,
|
||||
AufgabenGruppeRepository aufgabenGruppeRepository,
|
||||
AufgabeRepository aufgabeRepository,
|
||||
StrafeRepository strafeRepository,
|
||||
SperreRepository sperreRepository,
|
||||
FinisherRepository finisherRepository,
|
||||
GruppenAboRepository gruppenAboRepository,
|
||||
ToyRepository toyRepository) {
|
||||
this.adminRepository = adminRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.meldungRepository = meldungRepository;
|
||||
this.aufgabenGruppeRepository = aufgabenGruppeRepository;
|
||||
this.aufgabeRepository = aufgabeRepository;
|
||||
this.strafeRepository = strafeRepository;
|
||||
this.sperreRepository = sperreRepository;
|
||||
this.finisherRepository = finisherRepository;
|
||||
this.gruppenAboRepository = gruppenAboRepository;
|
||||
this.toyRepository = toyRepository;
|
||||
}
|
||||
|
||||
@@ -57,6 +77,8 @@ public class AdminController {
|
||||
|
||||
record StatusRequest(MeldungStatus status) {}
|
||||
|
||||
record UserSearchDto(UUID userId, String name) {}
|
||||
|
||||
// ── Hilfsmethoden ────────────────────────────────────────────────────────
|
||||
|
||||
private AdminEntity requireAdmin(Principal principal) {
|
||||
@@ -115,7 +137,7 @@ public class AdminController {
|
||||
public ResponseEntity<Void> updateMeldung(@PathVariable("id") UUID id,
|
||||
@RequestBody StatusRequest body,
|
||||
Principal principal) {
|
||||
var admin = requireAdmin(principal);
|
||||
requireAdmin(principal);
|
||||
var user = userRepository.findByEmail(principal.getName()).orElseThrow();
|
||||
MeldungEntity meldung = meldungRepository.findById(id)
|
||||
.orElseThrow(() -> new org.springframework.web.server.ResponseStatusException(
|
||||
@@ -173,6 +195,11 @@ public class AdminController {
|
||||
if (entity.getUserId() != null) {
|
||||
return ResponseEntity.status(403).build(); // Nur System-Gruppen
|
||||
}
|
||||
gruppenAboRepository.deleteByAufgabenGruppe(entity);
|
||||
aufgabeRepository.deleteAll(entity.getAufgaben());
|
||||
strafeRepository.deleteAll(entity.getStrafen());
|
||||
sperreRepository.deleteAll(entity.getSperren());
|
||||
finisherRepository.deleteAll(entity.getFinisher());
|
||||
aufgabenGruppeRepository.delete(entity);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
@@ -232,6 +259,21 @@ public class AdminController {
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
// ── Benutzer-Suche (nur SUPERADMIN) ──────────────────────────────────────
|
||||
|
||||
@GetMapping("/users/search")
|
||||
public ResponseEntity<List<UserSearchDto>> searchUsers(
|
||||
@RequestParam String q, Principal principal) {
|
||||
requireSuperAdmin(principal);
|
||||
if (q == null || q.isBlank()) return ResponseEntity.ok(List.of());
|
||||
List<UserEntity> users = userRepository.findByNameContainingIgnoreCase(q.trim());
|
||||
return ResponseEntity.ok(users.stream()
|
||||
.filter(u -> !adminRepository.existsByUserId(u.getUserId()))
|
||||
.limit(20)
|
||||
.map(u -> new UserSearchDto(u.getUserId(), u.getName()))
|
||||
.toList());
|
||||
}
|
||||
|
||||
// ── Admin-Verwaltung (nur SUPERADMIN) ────────────────────────────────────
|
||||
|
||||
@GetMapping("/admins")
|
||||
|
||||
@@ -24,7 +24,7 @@ public class SseService {
|
||||
}
|
||||
|
||||
public SseEmitter subscribe(UUID userId) {
|
||||
SseEmitter emitter = new SseEmitter(300_000L); // 5 min – Client reconnects automatically
|
||||
SseEmitter emitter = new SseEmitter(1_800_000L); // 30 min – Client reconnects automatically
|
||||
emitters.computeIfAbsent(userId, k -> new CopyOnWriteArrayList<>()).add(emitter);
|
||||
Runnable cleanup = () -> removeEmitter(userId, emitter);
|
||||
emitter.onCompletion(cleanup);
|
||||
|
||||
@@ -418,6 +418,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bestätigungs-Modal -->
|
||||
<div class="modal-backdrop" id="confirmModal">
|
||||
<div class="modal" style="max-width:380px;">
|
||||
<h2>Wirklich löschen?</h2>
|
||||
<p id="confirmModalText" style="font-size:0.9rem;color:var(--color-muted);margin:0 0 0.5rem 0;"></p>
|
||||
<div class="modal-actions">
|
||||
<button class="btn-cancel" id="confirmModalCancel">Abbrechen</button>
|
||||
<button class="btn-save" id="confirmModalOk">Löschen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="content">
|
||||
|
||||
@@ -502,21 +514,23 @@
|
||||
<div class="form-section">
|
||||
<h3>Admin hinzufügen</h3>
|
||||
<div class="form-row">
|
||||
<input type="text" id="adminUserId" placeholder="User-ID (UUID)">
|
||||
<input type="text" id="adminSearch" placeholder="Benutzername suchen…" oninput="searchAdminUsers()">
|
||||
<select id="adminRolle">
|
||||
<option value="ADMIN">Admin</option>
|
||||
<option value="SUPERADMIN">Superadmin</option>
|
||||
</select>
|
||||
<button class="btn-primary" onclick="createAdmin()">Hinzufügen</button>
|
||||
</div>
|
||||
<div id="adminSearchResults" style="margin-top:0.5rem;display:none;
|
||||
background:var(--color-secondary);border-radius:8px;overflow:hidden;"></div>
|
||||
<div id="adminAddError" style="color:var(--color-primary);font-size:0.82rem;margin-top:0.4rem;min-height:1em;"></div>
|
||||
</div>
|
||||
<div class="table-card">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr><th>Benutzername</th><th>User-ID</th><th>Rolle</th><th>Seit</th><th></th></tr>
|
||||
<tr><th>Benutzername</th><th>Rolle</th><th>Seit</th><th></th></tr>
|
||||
</thead>
|
||||
<tbody id="adminsBody">
|
||||
<tr><td colspan="5" class="empty-hint">Wird geladen…</td></tr>
|
||||
<tr><td colspan="4" class="empty-hint">Wird geladen…</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -546,6 +560,12 @@ async function init() {
|
||||
loadAdminGruppen();
|
||||
loadAdminToys();
|
||||
if (admin.rolle === 'SUPERADMIN') loadAdmins();
|
||||
|
||||
const _savedAdminTab = localStorage.getItem('tab_admin');
|
||||
if (_savedAdminTab) {
|
||||
const _btn = document.querySelector(`.tab-btn[data-tab="${_savedAdminTab}"]`);
|
||||
if (_btn && _btn.offsetParent !== null) _btn.click();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tab-Navigation ────────────────────────────────────────────────────────
|
||||
@@ -556,6 +576,7 @@ document.querySelectorAll('.tab-btn[data-tab]').forEach(btn => {
|
||||
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
document.getElementById('panel-' + btn.dataset.tab).classList.add('active');
|
||||
localStorage.setItem('tab_admin', btn.dataset.tab);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -595,6 +616,26 @@ async function setMeldungStatus(id, status) {
|
||||
if (r.ok) loadMeldungen();
|
||||
}
|
||||
|
||||
// ── Bestätigungs-Modal ────────────────────────────────────────────────────
|
||||
|
||||
let _confirmCallback = null;
|
||||
function openConfirmModal(text, callback) {
|
||||
document.getElementById('confirmModalText').textContent = text;
|
||||
_confirmCallback = callback;
|
||||
document.getElementById('confirmModal').classList.add('open');
|
||||
}
|
||||
function closeConfirmModal() {
|
||||
_confirmCallback = null;
|
||||
document.getElementById('confirmModal').classList.remove('open');
|
||||
}
|
||||
document.getElementById('confirmModalCancel').addEventListener('click', closeConfirmModal);
|
||||
document.getElementById('confirmModalOk').addEventListener('click', () => {
|
||||
const cb = _confirmCallback; closeConfirmModal(); if (cb) cb();
|
||||
});
|
||||
document.getElementById('confirmModal').addEventListener('click', e => {
|
||||
if (e.target === document.getElementById('confirmModal')) closeConfirmModal();
|
||||
});
|
||||
|
||||
// ── Aufgabengruppen ───────────────────────────────────────────────────────
|
||||
|
||||
let pendingExpandId = null;
|
||||
@@ -761,14 +802,15 @@ const ITEM_DELETE_FIELD = { aufgabe: 'aufgabeId', strafe: 'strafeId', zeitstrafe
|
||||
|
||||
function deleteItem(kind, itemId, gruppenId, event) {
|
||||
event.stopPropagation();
|
||||
if (!confirm('Eintrag wirklich löschen?')) return;
|
||||
fetch(ITEM_DELETE_URL[kind], {
|
||||
method: 'DELETE', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ [ITEM_DELETE_FIELD[kind]]: itemId })
|
||||
}).then(r => {
|
||||
if (r.ok || r.status === 202) { openItemId = null; pendingExpandId = gruppenId; loadAdminGruppen(); }
|
||||
else document.getElementById('gruppeActionError').textContent = 'Fehler beim Löschen (HTTP ' + r.status + ').';
|
||||
}).catch(() => { document.getElementById('gruppeActionError').textContent = 'Verbindungsfehler.'; });
|
||||
openConfirmModal('Eintrag wirklich löschen?', () => {
|
||||
fetch(ITEM_DELETE_URL[kind], {
|
||||
method: 'DELETE', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ [ITEM_DELETE_FIELD[kind]]: itemId })
|
||||
}).then(r => {
|
||||
if (r.ok || r.status === 202) { openItemId = null; pendingExpandId = gruppenId; loadAdminGruppen(); }
|
||||
else document.getElementById('gruppeActionError').textContent = 'Fehler beim Löschen (HTTP ' + r.status + ').';
|
||||
}).catch(() => { document.getElementById('gruppeActionError').textContent = 'Verbindungsfehler.'; });
|
||||
});
|
||||
}
|
||||
|
||||
async function duplicateItem(kind, itemId, gruppenId, event) {
|
||||
@@ -885,14 +927,15 @@ gruppeModal.addEventListener('click', e => { if (e.target === gruppeModal) close
|
||||
|
||||
document.getElementById('gruppeDeleteBtn').addEventListener('click', () => {
|
||||
if (!selectedGruppeId) return;
|
||||
if (!confirm('System-Aufgabengruppe und alle Inhalte wirklich löschen?')) return;
|
||||
const btn = document.getElementById('gruppeDeleteBtn'); btn.disabled = true;
|
||||
fetch(`/admin/aufgabengruppen/${selectedGruppeId}`, { method: 'DELETE' })
|
||||
.then(r => {
|
||||
if (r.ok || r.status === 204) { loadAdminGruppen(); }
|
||||
else { document.getElementById('gruppeActionError').textContent = 'Fehler beim Löschen (HTTP ' + r.status + ').'; btn.disabled = false; }
|
||||
})
|
||||
.catch(() => { document.getElementById('gruppeActionError').textContent = 'Verbindungsfehler.'; btn.disabled = false; });
|
||||
openConfirmModal('System-Aufgabengruppe und alle Inhalte wirklich löschen?', () => {
|
||||
const btn = document.getElementById('gruppeDeleteBtn'); btn.disabled = true;
|
||||
fetch(`/admin/aufgabengruppen/${selectedGruppeId}`, { method: 'DELETE' })
|
||||
.then(r => {
|
||||
if (r.ok || r.status === 204) { loadAdminGruppen(); }
|
||||
else { document.getElementById('gruppeActionError').textContent = 'Fehler beim Löschen (HTTP ' + r.status + ').'; btn.disabled = false; }
|
||||
})
|
||||
.catch(() => { document.getElementById('gruppeActionError').textContent = 'Verbindungsfehler.'; btn.disabled = false; });
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('gruppeDuplicateBtn').addEventListener('click', async () => {
|
||||
@@ -1300,36 +1343,73 @@ async function loadAdmins() {
|
||||
const r = await fetch('/admin/admins'); if (!r.ok) return;
|
||||
const list = await r.json();
|
||||
const tbody = document.getElementById('adminsBody');
|
||||
if (!list.length) { tbody.innerHTML = '<tr><td colspan="5" class="empty-hint">Keine Admins vorhanden.</td></tr>'; return; }
|
||||
if (!list.length) { tbody.innerHTML = '<tr><td colspan="4" class="empty-hint">Keine Admins vorhanden.</td></tr>'; return; }
|
||||
tbody.innerHTML = list.map(a => `
|
||||
<tr>
|
||||
<td>${esc(a.userName)}</td>
|
||||
<td class="word-break">${a.userId}</td>
|
||||
<td><span class="badge-status badge-${a.rolle.toLowerCase()}">${a.rolle}</span></td>
|
||||
<td style="white-space:nowrap">${fmtDate(a.createdAt)}</td>
|
||||
<td><button class="tbl-btn" onclick="deleteAdmin('${a.adminId}')">Entfernen</button></td>
|
||||
<td><button class="tbl-btn" onclick="deleteAdmin('${a.adminId}', '${esc(a.userName)}')">Entfernen</button></td>
|
||||
</tr>`).join('');
|
||||
}
|
||||
|
||||
async function createAdmin() {
|
||||
const userId = document.getElementById('adminUserId').value.trim();
|
||||
const rolle = document.getElementById('adminRolle').value;
|
||||
if (!userId) { alert('User-ID eingeben.'); return; }
|
||||
const r = await fetch('/admin/admins', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userId, rolle })
|
||||
});
|
||||
if (r.status === 201) { document.getElementById('adminUserId').value = ''; loadAdmins(); }
|
||||
else if (r.status === 404) alert('User nicht gefunden.');
|
||||
else if (r.status === 409) alert('Benutzer ist bereits Admin.');
|
||||
else alert('Fehler: ' + r.status);
|
||||
let _adminSearchTimer = null;
|
||||
function searchAdminUsers() {
|
||||
clearTimeout(_adminSearchTimer);
|
||||
const q = document.getElementById('adminSearch').value.trim();
|
||||
const box = document.getElementById('adminSearchResults');
|
||||
if (!q) { box.style.display = 'none'; box.innerHTML = ''; return; }
|
||||
_adminSearchTimer = setTimeout(async () => {
|
||||
const r = await fetch(`/admin/users/search?q=${encodeURIComponent(q)}`);
|
||||
if (!r.ok) return;
|
||||
const list = await r.json();
|
||||
if (!list.length) {
|
||||
box.innerHTML = '<div style="padding:0.6rem 0.9rem;font-size:0.88rem;color:var(--color-muted);">Keine Benutzer gefunden.</div>';
|
||||
} else {
|
||||
box.innerHTML = list.map(u => `
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;
|
||||
padding:0.5rem 0.9rem;border-bottom:1px solid rgba(255,255,255,0.05);cursor:pointer;"
|
||||
onmouseenter="this.style.background='rgba(255,255,255,0.04)'"
|
||||
onmouseleave="this.style.background=''">
|
||||
<span style="font-size:0.9rem;">${esc(u.name)}</span>
|
||||
<button class="tbl-btn tbl-btn-ok" onclick="addAdminFromSearch('${u.userId}', '${esc(u.name)}')">+ Hinzufügen</button>
|
||||
</div>`).join('');
|
||||
}
|
||||
box.style.display = 'block';
|
||||
}, 300);
|
||||
}
|
||||
|
||||
async function deleteAdmin(id) {
|
||||
if (!confirm('Admin-Berechtigung wirklich entziehen?')) return;
|
||||
const r = await fetch(`/admin/admins/${id}`, { method: 'DELETE' });
|
||||
if (r.ok || r.status === 204) loadAdmins();
|
||||
else if (r.status === 400) alert('Du kannst dich nicht selbst entfernen.');
|
||||
else alert('Fehler: ' + r.status);
|
||||
async function addAdminFromSearch(userId, userName) {
|
||||
const rolle = document.getElementById('adminRolle').value;
|
||||
const errEl = document.getElementById('adminAddError');
|
||||
errEl.textContent = '';
|
||||
const r = await fetch('/admin/admins', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ userId, rolle })
|
||||
});
|
||||
if (r.status === 201) {
|
||||
document.getElementById('adminSearch').value = '';
|
||||
document.getElementById('adminSearchResults').style.display = 'none';
|
||||
loadAdmins();
|
||||
} else if (r.status === 409) {
|
||||
errEl.textContent = `${userName} ist bereits Admin.`;
|
||||
} else {
|
||||
errEl.textContent = `Fehler beim Hinzufügen (HTTP ${r.status}).`;
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteAdmin(id, userName) {
|
||||
openConfirmModal(`Admin-Berechtigung von „${userName}" wirklich entziehen?`, async () => {
|
||||
const r = await fetch(`/admin/admins/${id}`, { method: 'DELETE' });
|
||||
if (r.ok || r.status === 204) {
|
||||
loadAdmins();
|
||||
} else if (r.status === 400) {
|
||||
document.getElementById('adminAddError').textContent = 'Du kannst dich nicht selbst entfernen.';
|
||||
document.querySelector('.tab-btn[data-tab="admins"]')?.click();
|
||||
} else {
|
||||
document.getElementById('adminAddError').textContent = `Fehler (HTTP ${r.status}).`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ── Hilfsfunktionen ───────────────────────────────────────────────────────
|
||||
|
||||
@@ -574,7 +574,8 @@
|
||||
loadProfilPosts();
|
||||
profilPostsObserver.observe(document.getElementById('profilPostsSentinel'));
|
||||
}
|
||||
} catch {
|
||||
} catch(e) {
|
||||
console.error('[benutzer] loadProfile Fehler:', e);
|
||||
document.getElementById('loadingHint').textContent = 'Fehler beim Laden.';
|
||||
document.getElementById('loadingHint').style.display = '';
|
||||
}
|
||||
@@ -613,7 +614,7 @@
|
||||
|
||||
// Action buttons
|
||||
const actions = document.getElementById('profileActions');
|
||||
const isViewingOwnProfile = me && me.userId === profile.userId;
|
||||
const isViewingOwnProfile = myUserId && myUserId === profile.userId;
|
||||
if (isOwnProfile) {
|
||||
actions.innerHTML = `<a href="/profile.html" class="btn">Profil bearbeiten</a>`;
|
||||
} else if (isViewingOwnProfile) {
|
||||
@@ -884,12 +885,16 @@
|
||||
|
||||
// ── Spielhistorie ──
|
||||
const GAME_TYPE_ICON = {
|
||||
CARDLOCK: '<span style="position:relative;display:inline-block;line-height:1;"><img src="/img/card.png" style="width:2.7rem;height:2.7rem;object-fit:contain;display:block;"><span style="position:absolute;bottom:-2px;right:-4px;font-size:1.3rem;line-height:1;">🔒</span></span>',
|
||||
TIMELOCK: '<span style="position:relative;display:inline-block;line-height:1;"><span style="font-size:2.7rem;">⏰</span><span style="position:absolute;bottom:-2px;right:-4px;font-size:1.3rem;line-height:1;">🔒</span></span>',
|
||||
BDSM: '⛓️',
|
||||
VANILLA: '❤️'
|
||||
CARDLOCK: IChtml('GAME_CARDLOCK'),
|
||||
TIMELOCK: IChtml('GAME_TIMELOCK'),
|
||||
BDSM: IC('GAME_BDSM'),
|
||||
VANILLA: IC('GAME_VANILLA')
|
||||
};
|
||||
const ROLE_BADGE = {
|
||||
KEYHOLDER: IC('ROLE_KEYHOLDER'),
|
||||
LOCKEE: IC('ROLE_LOCKEE'),
|
||||
PLAYER: ''
|
||||
};
|
||||
const ROLE_BADGE = { KEYHOLDER: '🔑', LOCKEE: '🔒', PLAYER: '' };
|
||||
|
||||
let gameHistoryLoaded = false;
|
||||
async function loadGameHistory() {
|
||||
@@ -906,10 +911,10 @@
|
||||
if (entries.length === 0) { empty.style.display = ''; return; }
|
||||
|
||||
list.innerHTML = entries.map(e => {
|
||||
const gameIconRaw = GAME_TYPE_ICON[e.gameType] || '🎮';
|
||||
const gameIcon = (e.gameType === 'CARDLOCK' || e.gameType === 'TIMELOCK')
|
||||
const gameIconRaw = GAME_TYPE_ICON[e.gameType];
|
||||
const gameIcon = gameIconRaw
|
||||
? gameIconRaw
|
||||
: `<span style="font-size:2.7rem;line-height:1;">${gameIconRaw}</span>`;
|
||||
: `<span style="font-size:2.7rem;line-height:1;">🎮</span>`;
|
||||
|
||||
const days = Math.floor(e.durationMinutes / 1440);
|
||||
const hours = Math.floor((e.durationMinutes % 1440) / 60);
|
||||
|
||||
@@ -92,8 +92,8 @@
|
||||
<div class="content">
|
||||
|
||||
<div class="tabs">
|
||||
<button class="tab-btn active" id="tabMine" onclick="switchTab('mine', this)">Mein Feed</button>
|
||||
<button class="tab-btn" id="tabPublic" onclick="switchTab('public', this)">Öffentlicher Feed</button>
|
||||
<button class="tab-btn active" id="tabMine" data-tab="mine" onclick="switchTab('mine', this)">Mein Feed</button>
|
||||
<button class="tab-btn" id="tabPublic" data-tab="public" onclick="switchTab('public', this)">Öffentlicher Feed</button>
|
||||
</div>
|
||||
|
||||
<!-- Mein Feed -->
|
||||
@@ -194,8 +194,14 @@
|
||||
btn.classList.add('active');
|
||||
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
|
||||
document.getElementById('tab-' + name).classList.add('active');
|
||||
localStorage.setItem('tab_feed', name);
|
||||
if (!feedState[name].loaded) loadFeed(name);
|
||||
}
|
||||
const _savedFeedTab = localStorage.getItem('tab_feed');
|
||||
if (_savedFeedTab) {
|
||||
const _btn = document.querySelector(`.tab-btn[data-tab="${_savedFeedTab}"]`);
|
||||
if (_btn) switchTab(_savedFeedTab, _btn);
|
||||
}
|
||||
|
||||
// ── Feed loading ──
|
||||
async function loadFeed(tab) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/img/icon.png" type="image/png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Freunde – XXX The Game</title>
|
||||
@@ -134,8 +134,8 @@
|
||||
<h1 style="margin-bottom: 1.25rem;">Freunde</h1>
|
||||
|
||||
<div class="tabs">
|
||||
<button class="tab-btn active" onclick="switchTab('friends', this)">Freunde</button>
|
||||
<button class="tab-btn" onclick="switchTab('pending', this)" id="pendingTabBtn">
|
||||
<button class="tab-btn active" data-tab="friends" onclick="switchTab('friends', this)">Freunde</button>
|
||||
<button class="tab-btn" data-tab="pending" onclick="switchTab('pending', this)" id="pendingTabBtn">
|
||||
Anfragen<span class="tab-badge" id="pendingBadge" style="display:none;"></span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -166,7 +166,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/icons.js"></script>
|
||||
<script src="/js/icons.js"></script>
|
||||
<script src="/js/sidebar.js"></script>
|
||||
<script src="/js/social-sidebar.js"></script>
|
||||
<script>
|
||||
@@ -175,6 +175,12 @@
|
||||
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
document.getElementById('tab-' + name).classList.add('active');
|
||||
localStorage.setItem('tab_freunde', name);
|
||||
}
|
||||
const _savedFreundeTab = localStorage.getItem('tab_freunde');
|
||||
if (_savedFreundeTab) {
|
||||
const _btn = document.querySelector(`.tab-btn[data-tab="${_savedFreundeTab}"]`);
|
||||
if (_btn) switchTab(_savedFreundeTab, _btn);
|
||||
}
|
||||
|
||||
function esc(s) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/img/icon.png" type="image/png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Gruppe – XXX The Game</title>
|
||||
@@ -154,9 +154,9 @@
|
||||
</div>
|
||||
|
||||
<div class="tabs" id="tabBar">
|
||||
<button class="tab-btn active" onclick="switchTab('posts', this)">Beiträge</button>
|
||||
<button class="tab-btn" onclick="switchTab('members', this)">Mitglieder</button>
|
||||
<button class="tab-btn" id="adminTabBtn" style="display:none;" onclick="switchTab('admin', this)">Admin <span class="social-badge" id="adminBadge" style="display:none;"></span></button>
|
||||
<button class="tab-btn active" data-tab="posts" onclick="switchTab('posts', this)">Beiträge</button>
|
||||
<button class="tab-btn" data-tab="members" onclick="switchTab('members', this)">Mitglieder</button>
|
||||
<button class="tab-btn" data-tab="admin" id="adminTabBtn" style="display:none;" onclick="switchTab('admin', this)">Admin <span class="social-badge" id="adminBadge" style="display:none;"></span></button>
|
||||
</div>
|
||||
|
||||
<!-- Posts Tab -->
|
||||
@@ -295,7 +295,7 @@
|
||||
</div>
|
||||
|
||||
<script src="/js/shared.js"></script>
|
||||
<script src="/js/icons.js"></script>
|
||||
<script src="/js/icons.js"></script>
|
||||
<script src="/js/sidebar.js"></script>
|
||||
<script src="/js/social-sidebar.js"></script>
|
||||
<script>
|
||||
@@ -355,6 +355,7 @@
|
||||
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
document.getElementById('tab-' + name).classList.add('active');
|
||||
localStorage.setItem('tab_gruppe_' + gruppeId, name);
|
||||
if (name === 'members') loadMembers();
|
||||
if (name === 'admin') { loadAdminRequests(); loadReports(); }
|
||||
}
|
||||
@@ -374,6 +375,12 @@
|
||||
|
||||
await loadGruppe();
|
||||
await loadPosts();
|
||||
|
||||
const _savedTab = localStorage.getItem('tab_gruppe_' + gruppeId);
|
||||
if (_savedTab) {
|
||||
const _btn = document.querySelector(`.tab-btn[data-tab="${_savedTab}"]`);
|
||||
if (_btn && _btn.style.display !== 'none') switchTab(_savedTab, _btn);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadGruppe() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/img/icon.png" type="image/png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Gruppen – XXX The Game</title>
|
||||
@@ -68,9 +68,9 @@
|
||||
</div>
|
||||
|
||||
<div class="tabs">
|
||||
<button class="tab-btn active" onclick="switchTab('mine', this)">Meine Gruppen</button>
|
||||
<button class="tab-btn" onclick="switchTab('discover', this)">Entdecken</button>
|
||||
<button class="tab-btn" onclick="switchTab('requests', this)">Meine Anfragen</button>
|
||||
<button class="tab-btn active" data-tab="mine" onclick="switchTab('mine', this)">Meine Gruppen</button>
|
||||
<button class="tab-btn" data-tab="discover" onclick="switchTab('discover', this)">Entdecken</button>
|
||||
<button class="tab-btn" data-tab="requests" onclick="switchTab('requests', this)">Meine Anfragen</button>
|
||||
</div>
|
||||
|
||||
<!-- Meine Gruppen -->
|
||||
@@ -136,7 +136,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/icons.js"></script>
|
||||
<script src="/js/icons.js"></script>
|
||||
<script src="/js/sidebar.js"></script>
|
||||
<script src="/js/social-sidebar.js"></script>
|
||||
<script>
|
||||
@@ -145,9 +145,15 @@
|
||||
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
document.getElementById('tab-' + name).classList.add('active');
|
||||
localStorage.setItem('tab_gruppen', name);
|
||||
if (name === 'mine') loadMine();
|
||||
if (name === 'requests') loadRequests();
|
||||
}
|
||||
const _savedGruppenTab = localStorage.getItem('tab_gruppen');
|
||||
if (_savedGruppenTab) {
|
||||
const _btn = document.querySelector(`.tab-btn[data-tab="${_savedGruppenTab}"]`);
|
||||
if (_btn) switchTab(_savedGruppenTab, _btn);
|
||||
}
|
||||
|
||||
function esc(s) { const d = document.createElement('div'); d.textContent = s ?? ''; return d.innerHTML; }
|
||||
|
||||
|
||||
@@ -1,32 +1,49 @@
|
||||
/**
|
||||
* Zentrale Icon-Verwaltung – XXX The Game
|
||||
* Alle Emojis und Symbole der App werden hier definiert.
|
||||
* Typen: emoji (Standard-Emoji), symbol (Unicode-Symbol), image (Pfad zu Bilddatei)
|
||||
*
|
||||
* Typen:
|
||||
* emoji – Standard-Emoji oder Unicode-Zeichen (value: string)
|
||||
* symbol – Unicode-Symbol (value: string)
|
||||
* image – Pfad zu einer Bilddatei (value: string)
|
||||
* compound – Doppel-Icon: base-Icon + kleines Overlay-Icon (bottom-right)
|
||||
* Felder: base { type, value }, overlay { type, value }
|
||||
* base kann emoji, symbol oder image sein.
|
||||
*/
|
||||
window.ICONS = {
|
||||
// ── Navigation / Sidebar ──
|
||||
HOME: { type: 'emoji', value: '⊞' },
|
||||
VANILLA: { type: 'symbol', value: '♡' },
|
||||
BDSM: { type: 'symbol', value: '◆' },
|
||||
CHASTITY: { type: 'symbol', value: '⊗' },
|
||||
// ── Navigation / Sidebar ──────────────────────────────────────────────
|
||||
HOME: { type: 'emoji', value: '🏠' },
|
||||
VANILLA: { type: 'emoji', value: '⚪' },
|
||||
BDSM: { type: 'emoji', value: '⛓️' },
|
||||
CHASTITY: { type: 'emoji', value: '🔒' },
|
||||
|
||||
// ── Aktionen ──
|
||||
PLAY_NEW: { type: 'symbol', value: '▷' },
|
||||
PLAY_ACTIVE: { type: 'symbol', value: '▶' },
|
||||
// ── Aktionen ──────────────────────────────────────────────────────────
|
||||
PLAY_NEW: { type: 'emoji', value: '🆕' },
|
||||
PLAY_ACTIVE: { type: 'emoji', value: '▶️' },
|
||||
ACTIVE_LOCK: { type: 'emoji', value: '▶️' },
|
||||
WAITING: { type: 'emoji', value: '⏳' },
|
||||
CHECK: { type: 'symbol', value: '✓' },
|
||||
DISCOVER: { type: 'symbol', value: '⊙' },
|
||||
ARROW: { type: 'symbol', value: '▸' },
|
||||
CHECK: { type: 'emoji', value: '✅' },
|
||||
DISCOVER: { type: 'emoji', value: '🗺️' },
|
||||
ARROW: { type: 'emoji', value: '▶️' },
|
||||
|
||||
// ── Chastity Game ──
|
||||
// ── UI-Symbole ────────────────────────────────────────────────────────
|
||||
CLOSE: { type: 'symbol', value: '✕' }, // Schließen / Ablehnen / Löschen
|
||||
CONFIRM: { type: 'symbol', value: '✓' }, // Bestätigen / Abschließen / Annehmen
|
||||
LIKE: { type: 'symbol', value: '♥' }, // Like-Button
|
||||
AVATAR: { type: 'symbol', value: '◉' }, // Avatar-Platzhalter (kein Bild)
|
||||
ERROR: { type: 'emoji', value: '❌' }, // Fehlerzustand
|
||||
TIMER: { type: 'emoji', value: '⏱️' }, // Zeitanzeige / Stoppuhr
|
||||
LIGHTNING: { type: 'emoji', value: '⚡' }, // Aktion (z. B. Zeit entfernen)
|
||||
EMOJI_PICKER: { type: 'emoji', value: '😊' }, // Emoji-Picker öffnen
|
||||
REMOVE: { type: 'symbol', value: '⊗' }, // Eintrag/Spiel entfernen
|
||||
|
||||
// ── Chastity Game ─────────────────────────────────────────────────────
|
||||
NEW_LOCK: { type: 'emoji', value: '🆕' },
|
||||
LOCK: { type: 'emoji', value: '🔒' },
|
||||
KEY: { type: 'emoji', value: '🔑' },
|
||||
HISTORY: { type: 'emoji', value: '🔙' },
|
||||
VOTES: { type: 'emoji', value: '🗳️' },
|
||||
|
||||
// ── Social ──
|
||||
// ── Social ────────────────────────────────────────────────────────────
|
||||
FEED: { type: 'emoji', value: '📰' },
|
||||
SEARCH: { type: 'emoji', value: '🔍' },
|
||||
FRIENDS: { type: 'emoji', value: '❤️' },
|
||||
@@ -35,14 +52,79 @@ window.ICONS = {
|
||||
GROUPS: { type: 'emoji', value: '👥' },
|
||||
INVITATIONS: { type: 'emoji', value: '✨' },
|
||||
SETTINGS: { type: 'emoji', value: '⚙️' },
|
||||
LOGOUT: { type: 'symbol', value: '⏏' },
|
||||
PROFILE: { type: 'symbol', value: '◉' },
|
||||
LOGOUT: { type: 'emoji', value: '⏏️' },
|
||||
PROFILE: { type: 'emoji', value: '👤' },
|
||||
|
||||
// ── Aufgaben / Items ──
|
||||
TOYS: { type: 'symbol', value: '◈' },
|
||||
// ── Aufgaben / Items ──────────────────────────────────────────────────
|
||||
TOYS: { type: 'emoji', value: '➰' },
|
||||
|
||||
// ── Spielhistorie – Spieltypen ────────────────────────────────────────
|
||||
// Einfache Spieltypen
|
||||
GAME_BDSM: { type: 'emoji', value: '⛓️' },
|
||||
GAME_VANILLA: { type: 'emoji', value: '❤️' },
|
||||
|
||||
// Doppel-Icons: großes Basis-Icon + kleines 🔒-Overlay
|
||||
GAME_CARDLOCK: {
|
||||
type: 'compound',
|
||||
base: { type: 'image', value: '/img/card.png' },
|
||||
overlay: { type: 'emoji', value: '🔒' }
|
||||
},
|
||||
GAME_TIMELOCK: {
|
||||
type: 'compound',
|
||||
base: { type: 'emoji', value: '⏰' },
|
||||
overlay: { type: 'emoji', value: '🔒' }
|
||||
},
|
||||
|
||||
// ── Spielhistorie – Rollen-Badges ─────────────────────────────────────
|
||||
ROLE_KEYHOLDER: { type: 'emoji', value: '🔑' },
|
||||
ROLE_LOCKEE: { type: 'emoji', value: '🔒' },
|
||||
};
|
||||
|
||||
/** Gibt nur den Wert (String) zurück – für einfache Einbindung in Templates */
|
||||
// ── Hilfsfunktionen ───────────────────────────────────────────────────────────
|
||||
|
||||
/** Gibt den rohen Wert-String zurück (nur für einfache Icons; '' für compound). */
|
||||
window.IC = function(key) {
|
||||
return (window.ICONS[key] || {}).value || '';
|
||||
const icon = window.ICONS[key];
|
||||
return (icon && icon.type !== 'compound') ? (icon.value || '') : '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Gibt ein fertiges HTML-Fragment zurück, das das Icon darstellt.
|
||||
*
|
||||
* @param {string} key – Schlüssel aus window.ICONS
|
||||
* @param {number} [size] – Basisgröße in rem (Standard: 2.7)
|
||||
* @returns {string} HTML-String
|
||||
*/
|
||||
window.IChtml = function(key, size) {
|
||||
const icon = window.ICONS[key];
|
||||
if (!icon) return '';
|
||||
return _iconToHtml(icon, size != null ? size : 2.7);
|
||||
};
|
||||
|
||||
function _iconToHtml(icon, size) {
|
||||
switch (icon.type) {
|
||||
case 'emoji':
|
||||
case 'symbol':
|
||||
return `<span style="font-size:${size}rem;line-height:1;">${icon.value}</span>`;
|
||||
case 'image':
|
||||
return `<img src="${icon.value}" style="width:${size}rem;height:${size}rem;object-fit:contain;display:block;" alt="">`;
|
||||
case 'compound': {
|
||||
const baseHtml = _compoundBase(icon.base, size);
|
||||
const overlayHtml = _compoundOverlay(icon.overlay, size * 0.48);
|
||||
return `<span style="position:relative;display:inline-block;line-height:1;">${baseHtml}${overlayHtml}</span>`;
|
||||
}
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function _compoundBase(base, size) {
|
||||
if (base.type === 'image') {
|
||||
return `<img src="${base.value}" style="width:${size}rem;height:${size}rem;object-fit:contain;display:block;" alt="">`;
|
||||
}
|
||||
return `<span style="font-size:${size}rem;line-height:1;">${base.value}</span>`;
|
||||
}
|
||||
|
||||
function _compoundOverlay(overlay, size) {
|
||||
return `<span style="position:absolute;bottom:-2px;right:-4px;font-size:${size.toFixed(2)}rem;line-height:1;">${overlay.value}</span>`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user