Autosave: 20260506-233434

This commit is contained in:
Flatlogic Bot 2026-05-06 23:34:35 +00:00
parent d7e2a86f09
commit 3b20de71b5
4 changed files with 223 additions and 25 deletions

View File

@ -59,7 +59,6 @@ function scitemcustom_bootstrap(): void
cl_scitemcustom_obj_id INT(10) UNSIGNED NOT NULL,
cl_scitemcustom_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (cl_scitemcustom_id),
UNIQUE KEY uq_scitemcustom_owner_obj (cl_scitemcustom_owner_auth_id, cl_scitemcustom_obj_id),
KEY idx_scitemcustom_owner (cl_scitemcustom_owner_auth_id),
KEY idx_scitemcustom_obj (cl_scitemcustom_obj_id),
CONSTRAINT fk_scitemcustom_owner_auth FOREIGN KEY (cl_scitemcustom_owner_auth_id)
@ -91,11 +90,8 @@ function scitemcustom_bootstrap(): void
);
}
if (!scitemcustom_index_exists($db, 'tbl_scitemcustom', 'uq_scitemcustom_owner_obj')) {
$db->exec(
'ALTER TABLE tbl_scitemcustom
ADD UNIQUE KEY uq_scitemcustom_owner_obj (cl_scitemcustom_owner_auth_id, cl_scitemcustom_obj_id)'
);
if (scitemcustom_index_exists($db, 'tbl_scitemcustom', 'uq_scitemcustom_owner_obj')) {
$db->exec('ALTER TABLE tbl_scitemcustom DROP INDEX uq_scitemcustom_owner_obj');
}
if (!scitemcustom_foreign_key_exists($db, 'tbl_scitemcustom', 'fk_scitemcustom_owner_auth')) {

View File

@ -760,3 +760,37 @@ function scmanutention_fetch_custom_stats_map(PDO $db, array $item_rows): array
return $stats_map;
}
function scmanutention_fetch_custom_stats_preview_map(PDO $db, array $custom_ids): array
{
$custom_ids = array_values(array_unique(array_map('intval', array_filter($custom_ids))));
if ($custom_ids === []) {
return [];
}
$placeholders = implode(',', array_fill(0, count($custom_ids), '?'));
$stmt = $db->prepare(
"SELECT
cs.cl_scitemcustomstat_itemcustom_id,
st.cl_scstatsitem_name,
st.cl_scstatsitem_unit,
cs.cl_scitemcustomstat_sign,
cs.cl_scitemcustomstat_value
FROM tbl_scitemcustomstat cs
INNER JOIN tbl_scstatsitem st ON st.cl_scstatsitem_id = cs.cl_scitemcustomstat_stat_id
WHERE cs.cl_scitemcustomstat_itemcustom_id IN ({$placeholders})
ORDER BY st.cl_scstatsitem_name ASC, cs.cl_scitemcustomstat_id ASC"
);
$stmt->execute($custom_ids);
$stats_map = [];
foreach ($stmt->fetchAll() as $row) {
$itemcustom_id = (int) $row['cl_scitemcustomstat_itemcustom_id'];
if (!isset($stats_map[$itemcustom_id])) {
$stats_map[$itemcustom_id] = [];
}
$stats_map[$itemcustom_id][] = $row;
}
return $stats_map;
}

View File

@ -55,6 +55,35 @@ function scitemcustom_escape_like(string $value): string
]);
}
function scitemcustom_normalize_rarity(?string $rarity): string
{
return strtoupper(trim((string) $rarity));
}
function scitemcustom_rarity_class(?string $rarity): string
{
return match (scitemcustom_normalize_rarity($rarity)) {
'L' => 'rarity-L',
'E' => 'rarity-E',
'R' => 'rarity-R',
'U' => 'rarity-U',
'C' => 'rarity-C',
default => '',
};
}
function scitemcustom_rarity_label(?string $rarity): string
{
return match (scitemcustom_normalize_rarity($rarity)) {
'L' => 'Légendaire',
'E' => 'Épique',
'R' => 'Rare',
'U' => 'Peu commun',
'C' => 'Commun',
default => '',
};
}
function scitemcustom_search_available_items(PDO $db, int $ownerAuthId, string $query, ?int $limit = null): array
{
$query = trim($query);
@ -71,14 +100,9 @@ function scitemcustom_search_available_items(PDO $db, int $ownerAuthId, string $
$limitClause = ' LIMIT ' . (int) $limit;
}
$sql = "SELECT cl_scobjs_id, cl_scobjs_name, cl_scobjs_uuid, cl_scobjs_type, cl_scobjs_subtype
$sql = "SELECT cl_scobjs_id, cl_scobjs_name, cl_scobjs_uuid, cl_scobjs_type, cl_scobjs_subtype, cl_scobjs_rarity
FROM tbl_scobjs
WHERE cl_scobjs_id NOT IN (
SELECT cl_scitemcustom_obj_id
FROM tbl_scitemcustom
WHERE cl_scitemcustom_owner_auth_id = :owner_auth_id
)
AND (
WHERE (
cl_scobjs_name LIKE :contains_name
OR cl_scobjs_type LIKE :contains_type
OR cl_scobjs_subtype LIKE :contains_subtype
@ -101,7 +125,6 @@ function scitemcustom_search_available_items(PDO $db, int $ownerAuthId, string $
$stmt = $db->prepare($sql);
$stmt->execute([
'owner_auth_id' => $ownerAuthId,
'contains_name' => $contains,
'contains_type' => $contains,
'contains_subtype' => $contains,
@ -379,12 +402,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET' && (string) ($_GET['ajax'] ?? '') === '
if (mb_strlen($query) >= 3) {
$items = array_map(static function (array $row): array {
$rarity = (string) ($row['cl_scobjs_rarity'] ?? '');
return [
'id' => (int) $row['cl_scobjs_id'],
'name' => (string) $row['cl_scobjs_name'],
'uuid' => (string) $row['cl_scobjs_uuid'],
'type' => (string) $row['cl_scobjs_type'],
'subtype' => (string) ($row['cl_scobjs_subtype'] ?? ''),
'rarity' => scitemcustom_normalize_rarity($rarity),
'rarity_class' => scitemcustom_rarity_class($rarity),
'rarity_label' => scitemcustom_rarity_label($rarity),
];
}, scitemcustom_search_available_items($db, $current_owner_auth_id, $query));
}
@ -452,6 +480,11 @@ $current_session_user = $_SESSION['user'] ?? '';
--border-glow: rgba(162, 155, 120, 0.25);
--danger: #ff4d4d;
--success: #00ff88;
--rarity-L: #ff8000;
--rarity-E: #a335ee;
--rarity-R: #0070dd;
--rarity-U: #1eff00;
--rarity-C: #ffffff;
}
@font-face {
@ -850,6 +883,12 @@ $current_session_user = $_SESSION['user'] ?? '';
font-size: 0.92rem;
}
.rarity-L { color: var(--rarity-L); text-shadow: 0 0 12px rgba(255, 128, 0, 0.28); }
.rarity-E { color: var(--rarity-E); text-shadow: 0 0 12px rgba(163, 53, 238, 0.28); }
.rarity-R { color: var(--rarity-R); text-shadow: 0 0 12px rgba(0, 112, 221, 0.28); }
.rarity-U { color: var(--rarity-U); text-shadow: 0 0 12px rgba(30, 255, 0, 0.28); }
.rarity-C { color: var(--rarity-C); }
.item-submeta {
color: #96a0b5;
font-size: 0.78rem;
@ -1227,7 +1266,8 @@ $current_session_user = $_SESSION['user'] ?? '';
<div class="item-meta">
<img src="https://cstone.space/uifimages/<?php echo htmlspecialchars($result['cl_scobjs_uuid'], ENT_QUOTES, 'UTF-8'); ?>.png" class="item-preview" alt="">
<div class="search-item-content">
<strong class="item-name"><?php echo htmlspecialchars($result['cl_scobjs_name'], ENT_QUOTES, 'UTF-8'); ?></strong>
<?php $result_rarity_class = scitemcustom_rarity_class($result['cl_scobjs_rarity'] ?? ''); ?>
<strong class="item-name <?php echo htmlspecialchars($result_rarity_class, ENT_QUOTES, 'UTF-8'); ?>"><?php echo htmlspecialchars($result['cl_scobjs_name'], ENT_QUOTES, 'UTF-8'); ?></strong>
<div class="item-submeta">
<?php echo htmlspecialchars($result['cl_scobjs_type'], ENT_QUOTES, 'UTF-8'); ?>
<?php if (!empty($result['cl_scobjs_subtype'])): ?> / <?php echo htmlspecialchars($result['cl_scobjs_subtype'], ENT_QUOTES, 'UTF-8'); ?><?php endif; ?>
@ -1303,10 +1343,13 @@ $current_session_user = $_SESSION['user'] ?? '';
<div class="item-meta">
<img src="https://cstone.space/uifimages/<?php echo htmlspecialchars($item['cl_scobjs_uuid'], ENT_QUOTES, 'UTF-8'); ?>.png" class="item-preview" alt="">
<div>
<strong class="item-name"><?php echo htmlspecialchars($item['cl_scobjs_name'], ENT_QUOTES, 'UTF-8'); ?></strong>
<?php $item_rarity_class = scitemcustom_rarity_class($item['cl_scobjs_rarity'] ?? ''); ?>
<?php $item_rarity_label = scitemcustom_rarity_label($item['cl_scobjs_rarity'] ?? ''); ?>
<strong class="item-name <?php echo htmlspecialchars($item_rarity_class, ENT_QUOTES, 'UTF-8'); ?>"><?php echo htmlspecialchars($item['cl_scobjs_name'], ENT_QUOTES, 'UTF-8'); ?></strong>
<div class="item-submeta">
<?php echo htmlspecialchars($item['cl_scobjs_type'], ENT_QUOTES, 'UTF-8'); ?>
<?php if (!empty($item['cl_scobjs_subtype'])): ?> / <?php echo htmlspecialchars($item['cl_scobjs_subtype'], ENT_QUOTES, 'UTF-8'); ?><?php endif; ?><br>
<?php if (!empty($item['cl_scobjs_subtype'])): ?> / <?php echo htmlspecialchars($item['cl_scobjs_subtype'], ENT_QUOTES, 'UTF-8'); ?><?php endif; ?>
<?php if ($item_rarity_label !== ''): ?> — Rareté : <?php echo htmlspecialchars($item_rarity_label, ENT_QUOTES, 'UTF-8'); ?><?php endif; ?><br>
UUID: <?php echo htmlspecialchars($item['cl_scobjs_uuid'], ENT_QUOTES, 'UTF-8'); ?>
</div>
</div>
@ -1476,6 +1519,7 @@ $current_session_user = $_SESSION['user'] ?? '';
var addSubmit = document.getElementById('item-add-submit');
var addReset = document.getElementById('item-add-reset');
var addClear = document.getElementById('item-add-clear');
var pickerRarityClasses = ['rarity-L', 'rarity-E', 'rarity-R', 'rarity-U', 'rarity-C'];
var pickerRequestTimer = null;
var pickerRequestToken = 0;
var selectedItem = null;
@ -1524,6 +1568,20 @@ $current_session_user = $_SESSION['user'] ?? '';
setPickerExpanded(true);
}
function applyRarityClass(element, rarityClass) {
if (!element) {
return;
}
pickerRarityClasses.forEach(function (className) {
element.classList.remove(className);
});
if (rarityClass && pickerRarityClasses.indexOf(rarityClass) !== -1) {
element.classList.add(rarityClass);
}
}
function updateSelectedItem(item) {
selectedItem = item || null;
@ -1537,6 +1595,7 @@ $current_session_user = $_SESSION['user'] ?? '';
addSelection.classList.add('hidden-by-filter');
addSelectionName.textContent = '';
addSelectionMeta.textContent = '';
applyRarityClass(addSelectionName, '');
addSelectionImage.classList.add('hidden-by-filter');
addSelectionImage.setAttribute('src', '');
addSelectionImage.setAttribute('alt', '');
@ -1547,6 +1606,7 @@ $current_session_user = $_SESSION['user'] ?? '';
addSubmit.disabled = !addSelectedIdInput.value;
addSelection.classList.remove('hidden-by-filter');
addSelectionName.textContent = selectedItem.name || '';
applyRarityClass(addSelectionName, selectedItem.rarityClass || '');
var meta = selectedItem.type || '';
if (selectedItem.subtype) {
@ -1555,6 +1615,9 @@ $current_session_user = $_SESSION['user'] ?? '';
if (selectedItem.uuid) {
meta += (meta ? ' — ' : '') + selectedItem.uuid;
}
if (selectedItem.rarityLabel) {
meta += (meta ? ' — ' : '') + 'Rareté : ' + selectedItem.rarityLabel;
}
addSelectionMeta.textContent = meta;
if (selectedItem.uuid) {
@ -1585,10 +1648,16 @@ $current_session_user = $_SESSION['user'] ?? '';
meta += (meta ? ' / ' : '') + escapeHtml(item.subtype);
}
var rarityClass = item.rarity_class || '';
var rarityLabel = item.rarity_label || '';
if (rarityLabel) {
meta += (meta ? ' — ' : '') + 'Rareté : ' + escapeHtml(rarityLabel);
}
return '' +
'<button type="button" class="item-picker-option' + (index === 0 ? ' is-active' : '') + '" role="option" data-item-id="' + escapeHtml(item.id) + '" data-item-name="' + escapeHtml(item.name) + '" data-item-type="' + escapeHtml(item.type || '') + '" data-item-subtype="' + escapeHtml(item.subtype || '') + '" data-item-uuid="' + escapeHtml(item.uuid || '') + '">' +
'<button type="button" class="item-picker-option' + (index === 0 ? ' is-active' : '') + '" role="option" data-item-id="' + escapeHtml(item.id) + '" data-item-name="' + escapeHtml(item.name) + '" data-item-type="' + escapeHtml(item.type || '') + '" data-item-subtype="' + escapeHtml(item.subtype || '') + '" data-item-uuid="' + escapeHtml(item.uuid || '') + '" data-item-rarity="' + escapeHtml(item.rarity || '') + '" data-item-rarity-class="' + escapeHtml(rarityClass) + '" data-item-rarity-label="' + escapeHtml(rarityLabel) + '">' +
'<div class="search-item-content">' +
'<strong class="item-name">' + escapeHtml(item.name || '') + '</strong>' +
'<strong class="item-name' + (rarityClass ? ' ' + escapeHtml(rarityClass) : '') + '">' + escapeHtml(item.name || '') + '</strong>' +
'<div class="item-submeta">' + meta + '</div>' +
'</div>' +
'</button>';
@ -1683,7 +1752,10 @@ $current_session_user = $_SESSION['user'] ?? '';
name: option.getAttribute('data-item-name') || '',
type: option.getAttribute('data-item-type') || '',
subtype: option.getAttribute('data-item-subtype') || '',
uuid: option.getAttribute('data-item-uuid') || ''
uuid: option.getAttribute('data-item-uuid') || '',
rarity: option.getAttribute('data-item-rarity') || '',
rarityClass: option.getAttribute('data-item-rarity-class') || '',
rarityLabel: option.getAttribute('data-item-rarity-label') || ''
};
addSearchInput.value = item.name || '';

View File

@ -133,20 +133,37 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET' && (string) ($_GET['ajax'] ?? '') === '
header('Content-Type: application/json; charset=utf-8');
$query = trim((string) ($_GET['q'] ?? ''));
$items = array_map(static function (array $row): array {
$suggestion_rows = scmanutention_search_available_items($db, $current_owner_auth_id, $query, 14);
$suggestion_custom_ids = [];
foreach ($suggestion_rows as $row) {
if (((string) ($row['result_source'] ?? '')) === 'custom' && !empty($row['result_scitemcustom_id'])) {
$suggestion_custom_ids[] = (int) $row['result_scitemcustom_id'];
}
}
$suggestion_stats_map = scmanutention_fetch_custom_stats_preview_map($db, $suggestion_custom_ids);
$items = array_map(static function (array $row) use ($suggestion_stats_map): array {
$custom_id = (int) ($row['result_scitemcustom_id'] ?? 0);
$custom_stats = $custom_id > 0 ? ($suggestion_stats_map[$custom_id] ?? []) : [];
$stats_preview = array_map('scmanutention_format_stat_preview', array_slice($custom_stats, 0, 4));
return [
'key' => (string) $row['result_key'],
'source' => (string) $row['result_source'],
'sourceLabel' => ((string) $row['result_source']) === 'custom' ? 'Objet perso' : 'Base d\'objets',
'scobjs_id' => (int) ($row['result_scobjs_id'] ?? 0),
'scitemcustom_id' => (int) ($row['result_scitemcustom_id'] ?? 0),
'scitemcustom_id' => $custom_id,
'name' => (string) ($row['result_name'] ?? ''),
'type' => (string) ($row['result_type'] ?? ''),
'subtype' => (string) ($row['result_subtype'] ?? ''),
'uuid' => (string) ($row['result_uuid'] ?? ''),
'rarity' => (string) ($row['result_rarity'] ?? ''),
'statsPreview' => $stats_preview,
'statsPreviewMore' => max(0, count($custom_stats) - count($stats_preview)),
];
}, scmanutention_search_available_items($db, $current_owner_auth_id, $query, 14));
}, $suggestion_rows);
echo json_encode(['items' => $items], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
@ -813,6 +830,56 @@ $page_access_widget = auth_render_page_access_widget('scmanutention.php', 'Manut
cursor: pointer;
}
.picker-option-row {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 0.8rem;
align-items: start;
}
.picker-option-main {
min-width: 0;
display: grid;
gap: 0.3rem;
}
.picker-option-stats {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
align-items: flex-start;
gap: 0.35rem;
max-width: min(48%, 420px);
}
.picker-option-stats .stat-pill {
min-height: 26px;
padding: 0.28rem 0.55rem;
font-size: 0.74rem;
white-space: nowrap;
}
.picker-option-stats-empty {
align-self: center;
color: var(--text-soft);
font-size: 0.82rem;
white-space: nowrap;
}
.picker-option-more {
display: inline-flex;
align-items: center;
min-height: 26px;
padding: 0.28rem 0.55rem;
border-radius: 12px;
background: rgba(255,255,255,0.08);
border: 1px solid rgba(255,255,255,0.14);
color: var(--text-soft);
font-size: 0.74rem;
font-weight: 700;
white-space: nowrap;
}
.picker-option:hover,
.picker-option.is-active {
background: rgba(255, 255, 255, 0.07);
@ -975,6 +1042,15 @@ $page_access_widget = auth_render_page_access_widget('scmanutention.php', 'Manut
grid-template-columns: 1fr;
}
.picker-option-row {
grid-template-columns: 1fr;
}
.picker-option-stats {
justify-content: flex-start;
max-width: 100%;
}
.picker-selection,
.item-head,
.hero-title-row,
@ -1527,6 +1603,21 @@ $page_access_widget = auth_render_page_access_widget('scmanutention.php', 'Manut
meta.push(escapeHtml(item.sourceLabel));
}
var statsPreview = Array.isArray(item.statsPreview) ? item.statsPreview : [];
var statsHtml = '';
if ((item.source || '') === 'custom') {
if (statsPreview.length > 0) {
statsHtml = '<div class="picker-option-stats">' +
statsPreview.map(function (statText) {
return '<span class="stat-pill">' + escapeHtml(statText) + '</span>';
}).join('') +
((item.statsPreviewMore || 0) > 0 ? '<span class="picker-option-more">+' + escapeHtml(item.statsPreviewMore) + '</span>' : '') +
'</div>';
} else {
statsHtml = '<div class="picker-option-stats picker-option-stats-empty">Aucune stat</div>';
}
}
return '' +
'<button type="button" class="picker-option' + (index === 0 ? ' is-active' : '') + '" ' +
'data-key="' + escapeHtml(item.key || '') + '" ' +
@ -1539,8 +1630,13 @@ $page_access_widget = auth_render_page_access_widget('scmanutention.php', 'Manut
'data-subtype="' + escapeHtml(item.subtype || '') + '" ' +
'data-uuid="' + escapeHtml(item.uuid || '') + '" ' +
'data-rarity="' + escapeHtml(item.rarity || '') + '">' +
'<strong class="item-name ' + escapeHtml(rarityClass(item.rarity || '')) + '">' + escapeHtml(item.name || '') + '</strong>' +
'<div class="item-submeta">' + meta.join(' / ') + '</div>' +
'<div class="picker-option-row">' +
'<div class="picker-option-main">' +
'<strong class="item-name ' + escapeHtml(rarityClass(item.rarity || '')) + '">' + escapeHtml(item.name || '') + '</strong>' +
'<div class="item-submeta">' + meta.join(' / ') + '</div>' +
'</div>' +
statsHtml +
'</div>' +
'</button>';
}).join('');
showDropdown();