Added galery
This commit is contained in:
@@ -124,28 +124,41 @@
|
||||
background: var(--dbm-bg-tertiary);
|
||||
}
|
||||
|
||||
.tree-header:hover .tree-toggle {
|
||||
background: rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.tree-header.active {
|
||||
background: var(--dbm-bg-tertiary);
|
||||
border-left-color: var(--dbm-accent);
|
||||
}
|
||||
|
||||
.tree-toggle {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 4px;
|
||||
margin-right: 2px;
|
||||
margin-left: -4px;
|
||||
color: var(--dbm-text-muted);
|
||||
transition: var(--dbm-transition);
|
||||
flex-shrink: 0;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tree-toggle:hover {
|
||||
background: var(--dbm-bg-tertiary);
|
||||
color: var(--dbm-text);
|
||||
}
|
||||
|
||||
.tree-toggle svg,
|
||||
.tree-toggle [data-lucide] {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
transition: transform 0.15s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tree-item.expanded > .tree-header .tree-toggle svg,
|
||||
@@ -505,12 +518,7 @@ a.tree-label:hover {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sql-keyword { color: #555; font-weight: 600; }
|
||||
.sql-function { color: #666; }
|
||||
.sql-string { color: #888; }
|
||||
.sql-number { color: #777; }
|
||||
.sql-operator { color: #444; }
|
||||
.sql-comment { color: #aaa; font-style: italic; }
|
||||
/* SQL highlighting colors moved to sql/index.php view */
|
||||
|
||||
.dbm-sql-actions {
|
||||
display: flex;
|
||||
|
||||
474
public/css/gallery.css
Normal file
474
public/css/gallery.css
Normal file
@@ -0,0 +1,474 @@
|
||||
.gallery-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.gallery-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.gallery-header h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.gallery-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.gallery-back {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #666;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.gallery-back:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.gallery-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.gallery-btn-primary {
|
||||
background: #333;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.gallery-btn-primary:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
.gallery-btn-secondary {
|
||||
background: #f0f0f0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.gallery-btn-secondary:hover {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
|
||||
.gallery-btn-danger {
|
||||
background: #dc3545;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.gallery-btn-danger:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
|
||||
.gallery-btn-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 6px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
color: #666;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.gallery-btn-icon:hover {
|
||||
background: #fff;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.gallery-btn-icon.gallery-btn-danger:hover {
|
||||
background: #dc3545;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.gallery-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.gallery-item {
|
||||
position: relative;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: #f0f0f0;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.gallery-item img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.gallery-item:hover img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.gallery-item-overlay {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 16px;
|
||||
background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
|
||||
color: #fff;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.gallery-item:hover .gallery-item-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.gallery-item-title {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.gallery-item-author {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
opacity: 0.8;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.gallery-item-visibility {
|
||||
display: inline-block;
|
||||
font-size: 10px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.gallery-item-visibility.public {
|
||||
background: rgba(40, 167, 69, 0.8);
|
||||
}
|
||||
|
||||
.gallery-item-visibility.private {
|
||||
background: rgba(255, 193, 7, 0.8);
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.gallery-item-owned {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.gallery-item-owned .gallery-item-actions {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.gallery-item-owned:hover .gallery-item-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.gallery-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.gallery-empty-icon {
|
||||
color: #ccc;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.gallery-empty h3 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.gallery-empty p {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.gallery-pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-top: 32px;
|
||||
padding-top: 24px;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.gallery-pagination-info {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.gallery-view {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 350px;
|
||||
gap: 32px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.gallery-view {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.gallery-view-image {
|
||||
background: #f0f0f0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.gallery-view-image img {
|
||||
max-width: 100%;
|
||||
max-height: 70vh;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.gallery-view-info h1 {
|
||||
margin: 0 0 16px;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.gallery-view-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.gallery-view-author,
|
||||
.gallery-view-date,
|
||||
.gallery-view-size {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.gallery-view-author a {
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.gallery-view-author a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.gallery-view-description {
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #555;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.gallery-view-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.gallery-form-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.gallery-form-container h1 {
|
||||
margin: 0 0 24px;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.gallery-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.gallery-form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.gallery-form-label {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.gallery-form-input,
|
||||
.gallery-form-textarea {
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-family: inherit;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.gallery-form-input:focus,
|
||||
.gallery-form-textarea:focus {
|
||||
outline: none;
|
||||
border-color: #333;
|
||||
}
|
||||
|
||||
.gallery-form-textarea {
|
||||
resize: vertical;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.gallery-form-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.gallery-form-checkbox input {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.gallery-form-hint {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.gallery-form-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.gallery-upload-zone {
|
||||
position: relative;
|
||||
border: 2px dashed #ddd;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.gallery-upload-zone:hover,
|
||||
.gallery-upload-zone.dragover {
|
||||
border-color: #333;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.gallery-upload-zone input[type="file"] {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.gallery-upload-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px 20px;
|
||||
color: #888;
|
||||
text-align: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.gallery-upload-placeholder small {
|
||||
font-size: 12px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.gallery-upload-preview {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.gallery-upload-preview img {
|
||||
max-width: 100%;
|
||||
max-height: 300px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.gallery-upload-remove {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: #333;
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.gallery-upload-remove:hover {
|
||||
background: #dc3545;
|
||||
}
|
||||
|
||||
.gallery-edit-preview {
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.gallery-edit-preview img {
|
||||
max-width: 200px;
|
||||
max-height: 200px;
|
||||
border-radius: 8px;
|
||||
object-fit: cover;
|
||||
}
|
||||
12
public/gallery_uploads/.htaccess
Normal file
12
public/gallery_uploads/.htaccess
Normal file
@@ -0,0 +1,12 @@
|
||||
# Deny access to all files except images
|
||||
<FilesMatch "\.(?!(jpg|jpeg|png|gif|webp)$)[^.]+$">
|
||||
Deny from all
|
||||
</FilesMatch>
|
||||
|
||||
# Disable directory listing
|
||||
Options -Indexes
|
||||
|
||||
# Disable script execution
|
||||
<FilesMatch "\.(php|php5|phtml|cgi|pl|py)$">
|
||||
Deny from all
|
||||
</FilesMatch>
|
||||
1
public/gallery_uploads/enc_69773206330d4_1769419270.bin
Normal file
1
public/gallery_uploads/enc_69773206330d4_1769419270.bin
Normal file
File diff suppressed because one or more lines are too long
1
public/gallery_uploads/enc_69773211625f5_1769419281.bin
Normal file
1
public/gallery_uploads/enc_69773211625f5_1769419281.bin
Normal file
File diff suppressed because one or more lines are too long
1
public/gallery_uploads/enc_697733fc9d48c_1769419772.bin
Normal file
1
public/gallery_uploads/enc_697733fc9d48c_1769419772.bin
Normal file
File diff suppressed because one or more lines are too long
1
public/gallery_uploads/enc_69773584ad61d_1769420164.bin
Normal file
1
public/gallery_uploads/enc_69773584ad61d_1769420164.bin
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -11,27 +11,40 @@ const DBManager = {
|
||||
|
||||
initTree() {
|
||||
document.querySelectorAll('.tree-header').forEach(header => {
|
||||
// Skip if already initialized
|
||||
if (header.dataset.treeInit) return;
|
||||
header.dataset.treeInit = 'true';
|
||||
|
||||
header.addEventListener('click', (e) => {
|
||||
const item = header.closest('.tree-item');
|
||||
const children = item.querySelector('.tree-children');
|
||||
const href = header.dataset.href;
|
||||
const toggle = e.target.closest('.tree-toggle');
|
||||
|
||||
// If clicking on toggle icon, just expand/collapse
|
||||
// If clicking on toggle icon, just expand/collapse (don't navigate)
|
||||
if (toggle) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Lazy-load tables for databases if needed
|
||||
if (item.dataset.db && children && !children.dataset.loaded) {
|
||||
this.loadTables(item.dataset.db, children);
|
||||
}
|
||||
// Lazy-load columns for tables if needed
|
||||
else if (item.dataset.table && children && !children.dataset.loaded) {
|
||||
// Find the parent database name
|
||||
const dbItem = item.closest('.tree-item[data-db]');
|
||||
if (dbItem) {
|
||||
this.loadColumns(dbItem.dataset.db, item.dataset.table, children);
|
||||
}
|
||||
}
|
||||
this.toggleTreeItem(item);
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's a table or database item with href, navigate directly
|
||||
// If it's a database or table item with href, navigate
|
||||
if (href) {
|
||||
// For databases, also load tables if not loaded
|
||||
// For databases, also expand and load tables
|
||||
if (item.dataset.db && children && !children.dataset.loaded) {
|
||||
this.loadTables(item.dataset.db, children);
|
||||
}
|
||||
@@ -68,6 +81,37 @@ const DBManager = {
|
||||
}
|
||||
},
|
||||
|
||||
async loadColumns(dbName, tableName, container) {
|
||||
container.innerHTML = '<div class="tree-loading">Loading...</div>';
|
||||
container.dataset.loaded = 'true';
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}database/getColumns/${encodeURIComponent(dbName)}/${encodeURIComponent(tableName)}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.columns) {
|
||||
let html = '';
|
||||
data.columns.forEach(col => {
|
||||
html += `
|
||||
<div class="tree-item">
|
||||
<div class="tree-header">
|
||||
<span class="tree-icon ${col.Key === 'PRI' ? 'key' : 'column'}">
|
||||
${col.Key === 'PRI' ? this.icons.key : this.icons.column}
|
||||
</span>
|
||||
<span class="tree-label">${this.escapeHtml(col.Field)}</span>
|
||||
<span class="tree-badge">${this.escapeHtml(col.Type)}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
container.innerHTML = html || '<div class="tree-empty">No columns</div>';
|
||||
this.refreshIcons();
|
||||
}
|
||||
} catch (error) {
|
||||
container.innerHTML = '<div class="tree-error">Failed to load</div>';
|
||||
}
|
||||
},
|
||||
|
||||
renderTableTreeItem(dbName, tableName, columns) {
|
||||
const columnsHtml = columns.map(col => `
|
||||
<div class="tree-item">
|
||||
|
||||
Reference in New Issue
Block a user