A lot of things.
This commit is contained in:
@@ -17,6 +17,9 @@
|
||||
@import './components/notifications.css';
|
||||
@import './components/settings.css';
|
||||
@import './components/queue.css';
|
||||
@import './components/drafts.css';
|
||||
@import './components/modcp.css';
|
||||
@import './components/tools.css';
|
||||
|
||||
@import './components/easymde.css';
|
||||
|
||||
|
||||
@@ -182,3 +182,9 @@
|
||||
.spin {
|
||||
animation: spin 1s infinite linear;
|
||||
}
|
||||
|
||||
.search-button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
160
resources/css/components/drafts.css
Normal file
160
resources/css/components/drafts.css
Normal file
@@ -0,0 +1,160 @@
|
||||
.drafts-count {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text2);
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.drafts-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
padding: 80px 20px;
|
||||
background-color: var(--bg2);
|
||||
border: 1px dashed var(--border);
|
||||
text-align: center;
|
||||
color: var(--text2);
|
||||
|
||||
h3 {
|
||||
font-size: 1.1rem;
|
||||
color: var(--text);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 0.9rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.drafts-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.drafts-item {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
background-color: var(--bg2);
|
||||
border: 1px solid var(--border);
|
||||
border-left: 3px solid var(--rhpz-orange);
|
||||
padding: 20px;
|
||||
transition: border-color 0.15s;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--rhpz-orange);
|
||||
}
|
||||
}
|
||||
|
||||
.drafts-cover {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
flex-shrink: 0;
|
||||
background-color: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.drafts-cover-placeholder {
|
||||
color: var(--border);
|
||||
}
|
||||
|
||||
.drafts-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.drafts-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.drafts-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
margin-bottom: 6px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.drafts-meta {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.drafts-dates {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
text-align: right;
|
||||
font-size: 0.78rem;
|
||||
color: var(--text2);
|
||||
flex-shrink: 0;
|
||||
|
||||
span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.drafts-progress {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.drafts-progress-bar {
|
||||
flex: 1;
|
||||
height: 4px;
|
||||
background-color: var(--bg4);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.drafts-progress-fill {
|
||||
height: 100%;
|
||||
background-color: var(--rhpz-orange);
|
||||
transition: width 0.3s ease;
|
||||
|
||||
.complete {
|
||||
background-color: var(--success);
|
||||
}
|
||||
}
|
||||
.drafts-progress-label {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text2);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.drafts-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
.btn {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
320
resources/css/components/modcp.css
Normal file
320
resources/css/components/modcp.css
Normal file
@@ -0,0 +1,320 @@
|
||||
.modcp-wrapper {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
align-items: flex-start;
|
||||
min-height: calc(100vh - 60px);
|
||||
}
|
||||
|
||||
.modcp-sidebar {
|
||||
width: 220px;
|
||||
flex-shrink: 0;
|
||||
background-color: var(--bg2);
|
||||
border: 1px solid var(--border);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
align-self: flex-start;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.modcp-sidebar-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 14px 16px;
|
||||
font-weight: 600;
|
||||
font-size: 0.88rem;
|
||||
color: var(--text);
|
||||
border-bottom: 1px solid var(--border);
|
||||
background-color: var(--bg3);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
|
||||
.modcp-nav { padding: 8px 0; }
|
||||
|
||||
.modcp-nav-group { margin-bottom: 4px; }
|
||||
|
||||
.modcp-nav-label {
|
||||
display: block;
|
||||
padding: 8px 16px 4px;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.8px;
|
||||
color: var(--text2);
|
||||
}
|
||||
|
||||
.modcp-nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 9px;
|
||||
padding: 8px 16px;
|
||||
font-size: 0.88rem;
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
border-left: 3px solid transparent;
|
||||
transition: background-color 0.1s, border-color 0.1s;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bg3);
|
||||
text-decoration: none;
|
||||
}
|
||||
.active {
|
||||
background-color: var(--bg3);
|
||||
border-left-color: var(--rhpz-orange);
|
||||
color: var(--text);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.modcp-nav-badge {
|
||||
margin-left: auto;
|
||||
background-color: var(--rhpz-orange);
|
||||
color: #111;
|
||||
font-size: 0.65rem;
|
||||
font-weight: 700;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 9px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.modcp-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
background-color: var(--bg2);
|
||||
border: 1px solid var(--border);
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.modcp-page-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
margin-bottom: 25px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.modcp-count {
|
||||
margin-left: auto;
|
||||
font-size: 0.85rem;
|
||||
font-weight: normal;
|
||||
color: var(--text2);
|
||||
}
|
||||
|
||||
.modcp-section-title {
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.7px;
|
||||
color: var(--text2);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.modcp-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 12px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.modcp-stat-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
background-color: var(--bg3);
|
||||
border: 1px solid var(--border);
|
||||
border-left: 3px solid var(--border);
|
||||
text-decoration: none;
|
||||
transition: border-color 0.15s, background-color 0.15s;
|
||||
color: var(--text);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bg4);
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.modcp-stat-card--orange { border-left-color: var(--rhpz-orange); }
|
||||
.modcp-stat-card--danger { border-left-color: var(--error); }
|
||||
.modcp-stat-card--muted { cursor: default; }
|
||||
|
||||
.modcp-stat-icon { color: var(--text2); }
|
||||
.modcp-stat-card--orange .modcp-stat-icon { color: var(--rhpz-orange); }
|
||||
.modcp-stat-card--danger .modcp-stat-icon { color: var(--error); }
|
||||
|
||||
.modcp-stat-info { display: flex; flex-direction: column; }
|
||||
.modcp-stat-value { font-size: 1.4rem; font-weight: 700; color: var(--text); line-height: 1; }
|
||||
.modcp-stat-label { font-size: 0.75rem; color: var(--text2); margin-top: 3px; }
|
||||
|
||||
.modcp-quick-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.modcp-quick-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
padding: 8px 14px;
|
||||
background-color: var(--bg3);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text);
|
||||
font-size: 0.85rem;
|
||||
text-decoration: none;
|
||||
transition: background-color 0.1s, border-color 0.1s;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bg4);
|
||||
border-color: var(--rhpz-orange);
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.modcp-list { display: flex; flex-direction: column; }
|
||||
|
||||
.modcp-list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
padding: 12px 15px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
transition: background-color 0.1s;
|
||||
}
|
||||
|
||||
.modcp-list-item:last-child { border-bottom: none; }
|
||||
.modcp-list-item:hover { background-color: var(--bg3); }
|
||||
.modcp-list-item--deleted { opacity: 0.8; }
|
||||
|
||||
.modcp-list-item-cover {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
flex-shrink: 0;
|
||||
background-color: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
color: var(--border);
|
||||
}
|
||||
|
||||
.modcp-list-item-cover img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.modcp-list-item-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.modcp-list-item-title {
|
||||
font-size: 0.92rem;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.modcp-list-item-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 0.78rem;
|
||||
color: var(--text2);
|
||||
}
|
||||
|
||||
.modcp-list-item-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.modcp-list-item-edit {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modcp-list-item-edit .form-input {
|
||||
flex: 1;
|
||||
padding: 5px 10px;
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.modcp-list-see-all {
|
||||
display: block;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--rhpz-orange);
|
||||
border-top: 1px solid var(--border);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.modcp-add-form {
|
||||
background-color: var(--bg3);
|
||||
border: 1px solid var(--border);
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.modcp-add-form-inner {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modcp-add-form-inner .form-input { flex: 1; }
|
||||
|
||||
.modcp-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 50px 20px;
|
||||
color: var(--text2);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mod-alert {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 15px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 0.88rem;
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
.mod-alert--success {
|
||||
background-color: rgba(129, 199, 132, 0.08);
|
||||
border-color: rgba(129, 199, 132, 0.3);
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.modcp-list-item-edit--game {
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.modcp-list-item-edit--game .form-input { min-width: 180px; flex: 2; }
|
||||
.modcp-list-item-edit--game .form-select { flex: 1; min-width: 120px; }
|
||||
83
resources/css/components/tools.css
Normal file
83
resources/css/components/tools.css
Normal file
@@ -0,0 +1,83 @@
|
||||
.patcher-container {
|
||||
background-color: var(--bg2);
|
||||
border: 1px solid var(--border);
|
||||
padding: 25px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.patcher-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.patcher-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.patcher-dropzone {
|
||||
border: 2px dashed var(--border);
|
||||
background-color: var(--bg3);
|
||||
padding: 55px 20px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.patcher-dropzone:hover, .patcher-dropzone.dragover {
|
||||
border-color: var(--rhpz-orange);
|
||||
background-color: var(--bg4);
|
||||
}
|
||||
|
||||
.patcher-dropzone.has-file {
|
||||
border-color: var(--success);
|
||||
background-color: rgba(129, 199, 132, 0.02);
|
||||
}
|
||||
|
||||
.patcher-status-box {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
border: 1px solid var(--border);
|
||||
background-color: var(--bg3);
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
background-color: var(--bg3);
|
||||
border-color: var(--border);
|
||||
color: var(--text2);
|
||||
}
|
||||
|
||||
.embed-patch-box {
|
||||
border: 1px solid var(--border);
|
||||
background-color: var(--bg3);
|
||||
padding: 25px;
|
||||
height: 85%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
}
|
||||
.embed-patch-box-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
.embed-patch-box-icon-block {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background-color: var(--bg2);
|
||||
border: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
@@ -23,24 +23,6 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 2px;
|
||||
padding: 5px 10px;
|
||||
width: 300px;
|
||||
input {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text);
|
||||
outline: none;
|
||||
margin-left: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.topbar-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
@@ -53,6 +35,24 @@
|
||||
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 2px;
|
||||
padding: 5px 10px;
|
||||
width: 300px;
|
||||
input {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text);
|
||||
outline: none;
|
||||
margin-left: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
#content {
|
||||
flex-grow: 1;
|
||||
padding: 30px;
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
gap: 30px;
|
||||
|
||||
.entry-cover {
|
||||
width: 200px;
|
||||
height: 280px;
|
||||
width: 220px;
|
||||
height: 220px;
|
||||
background-color: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
display: flex;
|
||||
@@ -29,7 +29,8 @@
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-fit: contain;
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
114
resources/js/RomPatcher.js
Normal file
114
resources/js/RomPatcher.js
Normal file
@@ -0,0 +1,114 @@
|
||||
export function RomPatcher( initialPatches = {} ) {
|
||||
|
||||
let patchesArray = [];
|
||||
if (initialPatches) {
|
||||
patchesArray = Array.isArray(initialPatches) ? initialPatches : [initialPatches];
|
||||
}
|
||||
patchesArray = patchesArray.filter(p => p && p.file);
|
||||
|
||||
return {
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
romFileName: '',
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
patchFileName: '',
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
isRomDragOver: false,
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
isPatchDragOver: false,
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
showStatusBox: false,
|
||||
|
||||
/**
|
||||
* @type {object}
|
||||
*/
|
||||
patchesData: patchesArray,
|
||||
hasEmbedded: patchesArray.length > 0,
|
||||
|
||||
init() {
|
||||
|
||||
const CONFIG = {language: 'en', requireValidation: false};
|
||||
|
||||
if (!RomPatcherWeb.isInitialized()){
|
||||
if (this.hasEmbedded) {
|
||||
RomPatcherWeb.initialize(CONFIG, ...this.patchesData);
|
||||
} else {
|
||||
RomPatcherWeb.initialize(CONFIG);
|
||||
}
|
||||
}
|
||||
|
||||
const STAT_BOX = document.getElementById('patcher-status');
|
||||
const OBSERVER = new MutationObserver(() => {
|
||||
const CRC = document.getElementById('rom-patcher-span-crc32').textContent;
|
||||
const DESC = document.getElementById('rom-patcher-patch-description').textContent;
|
||||
const PATCH = document.getElementById('rom-patcher-patch-requirements-value').textContent;
|
||||
|
||||
this.showStatusBox = CRC.trim().length > 0 || DESC.trim().length > 0 || PATCH.trim().length > 0;
|
||||
});
|
||||
|
||||
OBSERVER.observe(STAT_BOX, { childList: true, subtree: true, characterData: true });
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} id
|
||||
*/
|
||||
triggerFileInput(id){
|
||||
const I = document.getElementById(id);
|
||||
if( !I.disabled )
|
||||
I.click();
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {string} type
|
||||
*/
|
||||
handleInputChange(e, type){
|
||||
const file = e.target.files[0];
|
||||
if(file){
|
||||
if( type === 'rom' ) this.romFileName = file.name;
|
||||
if( type === 'patch' ) this.patchFileName = file.name;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {string} type
|
||||
*/
|
||||
handleDrop(e, type ){
|
||||
|
||||
const file = e.dataTransfer.files[0];
|
||||
if( !file )
|
||||
return;
|
||||
|
||||
const ID = type === 'rom' ? 'rom-patcher-input-file-rom' : 'rom-patcher-input-file-patch';
|
||||
const I = document.getElementById(ID);
|
||||
|
||||
if( I.disabled )
|
||||
return;
|
||||
|
||||
I.files = e.dataTransfer.files;
|
||||
|
||||
if( type === 'rom' ) this.romFileName = file.name;
|
||||
if( type === 'patch' ) this.patchFileName = file.name;
|
||||
|
||||
I.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,16 @@ export function FSFileData(name, totalChunks, rawFile ) {
|
||||
*/
|
||||
state: 'public',
|
||||
|
||||
/**
|
||||
* If the online patcher is enabled
|
||||
*/
|
||||
meta_online_patcher: false,
|
||||
|
||||
/**
|
||||
* If this patch is a secondary patch.
|
||||
*/
|
||||
meta_secondary_online_patcher: false,
|
||||
|
||||
/**
|
||||
* Look if this file is currently uploading.
|
||||
* @returns {boolean}
|
||||
|
||||
@@ -7,6 +7,7 @@ import hovercard from "./hovercard.js";
|
||||
import notifications from "./notifications.js";
|
||||
import conversations from "./conversations.js";
|
||||
import settings from "./settings.js";
|
||||
import {RomPatcher} from "./RomPatcher.js";
|
||||
|
||||
/**
|
||||
* Get config defined in meta.blade.php
|
||||
@@ -43,3 +44,6 @@ Alpine.store('conversations', conversations() );
|
||||
|
||||
// Settings
|
||||
Alpine.store('settings', settings() );
|
||||
|
||||
// ROMPatcher
|
||||
window.RomPatcher = RomPatcher;
|
||||
|
||||
@@ -372,6 +372,12 @@ window.Submission = function(){
|
||||
this.errorKey = null; // Reset.
|
||||
this.duringSubmissionProcess = true;
|
||||
|
||||
const STATE = document.querySelector('select[name="submit-state"]')?.value;
|
||||
if( STATE === 'draft' ){
|
||||
e.target.submit();
|
||||
return;
|
||||
}
|
||||
|
||||
if( !this.verifyForm() ){
|
||||
|
||||
this.scrollToError();
|
||||
|
||||
@@ -63,10 +63,10 @@
|
||||
</div>
|
||||
|
||||
<div class="hovercard-actions">
|
||||
<a href="#" class="btn" title="View profile">
|
||||
<a :href="`{{ xfRoute('members') }}/${$store.hovercard.data.user_id}/`" class="btn" title="View profile">
|
||||
<i data-lucide="user" size="14"></i>
|
||||
</a>
|
||||
<a href="#" class="btn" title="Send message">
|
||||
<a :href="`{{ xfRoute('direct-messages/add') }}?to=${$store.hovercard.data.username.replace(' ', '+')}`" class="btn" title="Send message">
|
||||
<i data-lucide="mail" size="14"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -15,10 +15,12 @@
|
||||
<div class="menu-group">
|
||||
<div class="menu-group-title">{{ $menu['name'] }}</div>
|
||||
@foreach( $menu['items'] as $item )
|
||||
<a href="{{ isset($item['xf_route']) ? xfRoute($item['xf_route']) : route($item['route']) }}"
|
||||
@class(['menu-item', 'active' => request()->routeIs( $item['route'] ?? '' )]) >
|
||||
<i data-lucide="{{ $item['icon'] }}"></i><span>{{ $item['name'] }}</span>
|
||||
</a>
|
||||
@if( !isset( $item['condition'] ) || $item['condition']() )
|
||||
<a href="{{ isset($item['xf_route']) ? xfRoute($item['xf_route']) : route($item['route']) }}"
|
||||
@class(['menu-item', 'active' => request()->routeIs( $item['route'] ?? '' )]) >
|
||||
<i data-lucide="{{ $item['icon'] }}"></i><span>{{ $item['name'] }}</span>
|
||||
</a>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
6
resources/views/components/modcp-search.blade.php
Normal file
6
resources/views/components/modcp-search.blade.php
Normal file
@@ -0,0 +1,6 @@
|
||||
<form class="search-bar" style="margin-bottom: 15px;">
|
||||
<input type="text" name="{{ $param }}" placeholder="{{ $placeholder }}" value="{{ request($param) }}" autocomplete="off" />
|
||||
<button type="submit" class="search-button">
|
||||
<i data-lucide="search" size="18" color="var(--text2)"></i>
|
||||
</button>
|
||||
</form>
|
||||
@@ -1,6 +1,7 @@
|
||||
<div class="submit-level" x-data="{
|
||||
nsfw: null,
|
||||
state: '{{ old('submit-state', $defaultState) }}',
|
||||
deleteOpen: false,
|
||||
init(){
|
||||
this.$watch('nsfw', (val) => {
|
||||
if( val && this.state === 'published' ) {
|
||||
@@ -9,6 +10,13 @@
|
||||
});
|
||||
}
|
||||
}" x-init="init()">
|
||||
@if($isEdit)
|
||||
<div>
|
||||
<button type="button" class="btn danger" @click="deleteOpen = true; $dispatch('modal:opened')">
|
||||
<i data-lucide="trash-2" size="13"></i> Delete
|
||||
</button>
|
||||
</div>
|
||||
@endif
|
||||
<div>
|
||||
@if( section_must_be( [ 'romhacks', 'homebrew' ], $section ) && !$isEdit )
|
||||
<label class="nsfw-label"><input id="nsfw-checkbox" type="checkbox" name="nsfw-entry" x-model="nsfw" style="transform: scale(1.5)"> NSFW</label>
|
||||
@@ -25,4 +33,49 @@
|
||||
@endif
|
||||
@endforeach
|
||||
</select>
|
||||
|
||||
@if($isEdit)
|
||||
<template x-teleport="body">
|
||||
<div
|
||||
class="modal-overlay"
|
||||
x-cloak
|
||||
x-show="deleteOpen"
|
||||
x-transition.opacity
|
||||
@click.self="deleteOpen = false"
|
||||
@keydown.escape.window="deleteOpen = false"
|
||||
@modal:opened.window="refreshIcons($el)"
|
||||
>
|
||||
<div class="modal-window" x-show="deleteOpen" x-transition>
|
||||
<div class="modal-header">
|
||||
<span class="modal-title">Delete entry</span>
|
||||
<button type="button" class="modal-close" @click="deleteOpen = false">
|
||||
<i data-lucide="x" size="20"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<p style="margin-bottom: 1.5rem; color: var(--text, #333);">
|
||||
Are you sure you want to delete this entry? This action cannot be undone.
|
||||
</p>
|
||||
|
||||
<form action="{{ route('submit.destroy', ['section' => $section, 'entry' => $entry ]) }}" method="POST">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
|
||||
<div class="queue-mod-actions">
|
||||
<button type="button" class="btn" @click="deleteOpen = false">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" class="btn danger">
|
||||
<i data-lucide="trash-2" size="14"></i>
|
||||
Confirm deletion
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@@ -4,14 +4,16 @@
|
||||
<i data-lucide="menu"></i>
|
||||
</button>
|
||||
|
||||
<div class="search-bar">
|
||||
<i data-lucide="search" size="18" color="var(--text2)"></i>
|
||||
<input type="text">Search</input>
|
||||
</div>
|
||||
<form class="search-bar" action="{{ route('entries.index') }}">
|
||||
<input type="text" name="s" placeholder="Search" />
|
||||
<button type="submit" class="search-button">
|
||||
<i data-lucide="search" size="18" color="var(--text2)"></i>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="topbar-actions">
|
||||
|
||||
@if( !\Auth::guest() && \Auth::user()->is_admin === 1 )
|
||||
@can('is-admin')
|
||||
@php $topbarAdminSeparator = true; @endphp
|
||||
<a href="{{ config('app.forum_url') . '/admin.php' }}" class="btn">
|
||||
<i data-lucide="landmark" size="18"></i>
|
||||
@@ -19,15 +21,15 @@
|
||||
<a href="{{ config('app.url') . '/manage' }}" class="btn">
|
||||
<i data-lucide="shield-cog" size="18"></i>
|
||||
</a>
|
||||
@endif
|
||||
@endcan
|
||||
|
||||
@if( $topbarAdminSeparator )
|
||||
<div class="vertical-separator"></div>
|
||||
@endif
|
||||
|
||||
@if( !\Auth::guest() && \Auth::user()->is_moderator === 1 )
|
||||
@can('is-mod')
|
||||
@php $topbarModSeparator = true; @endphp
|
||||
<a href="#" class="btn">
|
||||
<a href="{{ route('modcp.index') }}" class="btn">
|
||||
<i data-lucide="siren" size="18"></i>
|
||||
</a>
|
||||
<a href="{{ xfRoute('approval-queue') }}" class="btn">
|
||||
@@ -36,7 +38,7 @@
|
||||
<a href="{{ xfRoute('reports') }}" class="btn">
|
||||
<i data-lucide="triangle-alert" size="18"></i>
|
||||
</a>
|
||||
@endif
|
||||
@endcan
|
||||
|
||||
@if( $topbarModSeparator )
|
||||
<div class="vertical-separator"></div>
|
||||
|
||||
54
resources/views/entries/draft_item.blade.php
Normal file
54
resources/views/entries/draft_item.blade.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<div class="drafts-item">
|
||||
<div class="drafts-cover">
|
||||
@if($entry->main_image)
|
||||
<img src="{{ Storage::url($entry->main_image) }}">
|
||||
@else
|
||||
<div class="drafts-cover-placeholder">
|
||||
<i data-lucide="image" size="24"></i>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="drafts-info">
|
||||
<div class="drafts-top">
|
||||
<div>
|
||||
<h3 class="drafts-title">
|
||||
{{ $entry->complete_title }}
|
||||
</h3>
|
||||
<div class="drafts-meta">
|
||||
<span class="badge {{ $entry->type }}">
|
||||
{{ \App\Livewire\Database::ENTRY_TYPES[$entry->type] }}
|
||||
</span>
|
||||
@if( $entry->getRealPlatform() )
|
||||
<span class="badge">{{ $entry->getRealPlatform()->name }}</span>
|
||||
@endif
|
||||
@if( $entry->version )
|
||||
<span class="badge">{{ $entry->version }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="drafts-dates">
|
||||
<span>
|
||||
<i data-lucide="pencil" size="12"></i>
|
||||
Last edited {{ $draft->updated_at->diffForHumans() }}
|
||||
</span>
|
||||
<span>
|
||||
<i data-lucide="calendar" size="12"></i>
|
||||
Created {{ $draft->created_at->format('d M Y') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="drafts-actions">
|
||||
<a href="{{ route('submit.edit', ['section' =>$entry->type, 'entry' => $entry]) }}" class="btn primary">
|
||||
<i data-lucide="pen" size="13"></i>
|
||||
Continue editing
|
||||
</a>
|
||||
<a href="{{ route('entries.show', ['section' => $entry->type, 'entry' => $entry ] ) }}" class="btn" target="_blank">
|
||||
<i data-lucide="eye" size="13"></i>
|
||||
Preview
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
25
resources/views/entries/drafts.blade.php
Normal file
25
resources/views/entries/drafts.blade.php
Normal file
@@ -0,0 +1,25 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('page-title', "My Drafts - " . config('app.name'))
|
||||
|
||||
@section('content')
|
||||
<div class="page-title">
|
||||
My Drafts
|
||||
</div>
|
||||
@if($drafts->isEmpty())
|
||||
<div class="drafts-empty">
|
||||
<i data-lucide="pen" size="48"></i>
|
||||
<p>No drafts here</p>
|
||||
</div>
|
||||
@else
|
||||
<div class="drafts-count">
|
||||
<span>{{ $drafts->total() }} draft{{ $drafts->total() > 1 ? 's' : '' }}</span>
|
||||
</div>
|
||||
<div class="drafts-list">
|
||||
@foreach($drafts as $draft)
|
||||
@include('entries.draft_item', ['entry' => $draft])
|
||||
@endforeach
|
||||
</div>
|
||||
{{ $drafts->links() }}
|
||||
@endif
|
||||
@endsection
|
||||
@@ -83,28 +83,28 @@
|
||||
</div>
|
||||
<div class="entry-meta-grid">
|
||||
@if( $entry->game )
|
||||
<x-entry-meta-item label="Game Name" value="{{ $entry->game->name }}" />
|
||||
<x-entry-meta-item label="Game Name" value="{{ $entry->game->name }}" route="{!! databaseRoute( [ 'games' => [ $entry->game->id ], 'platforms' => [ $entry->getRealPlatform()?->id ] ] ) !!}" />
|
||||
@endif
|
||||
@if( $entry->getRealPlatform() )
|
||||
<x-entry-meta-item label="Platform" value="{{ ($entry->getRealPlatform())->name }}" />
|
||||
<x-entry-meta-item label="Platform" value="{{ ($entry->getRealPlatform())->name }}" route="{!! databaseRoute( ['platforms' => [ $entry->getRealPlatform()->id ] ] ) !!}" />
|
||||
@endif
|
||||
@if( $entry->game && $entry->game->genre )
|
||||
<x-entry-meta-item label="Genre" value="{{ $entry->game->genre->name }}" />
|
||||
<x-entry-meta-item label="Genre" value="{{ $entry->game->genre->name }}" route="{!! databaseRoute( ['genres' => [ $entry->game->genre->id ] ]) !!}" />
|
||||
@endif
|
||||
@if( $entry->languages->isNotEmpty() )
|
||||
<x-entry-meta-item label="Language" value="{{ $entry->languages->pluck('name')->implode(', ') }}" route="none" />
|
||||
<x-entry-meta-item label="Language" value="{{ $entry->languages->pluck('name')->implode(', ') }}" route="{!! databaseRoute( [ 'languages' => $entry->languages->pluck('id')->toArray() ]) !!}" />
|
||||
@endif
|
||||
@if( $entry->status_id )
|
||||
<x-entry-meta-item label="Status" value="{{ $entry->status->name }}" />
|
||||
<x-entry-meta-item label="Status" value="{{ $entry->status->name }}" route="{!! databaseRoute( ['statuses' => [ $entry->status->id ] ]) !!}" />
|
||||
@endif
|
||||
@if( $entry->modifications->isNotEmpty() )
|
||||
<x-entry-meta-item label="Type of hack" value="{{ $entry->modifications->pluck('name')->implode(', ') }}" route="{!! databaseRoute( [ 'modifications' => $entry->modifications->pluck('id')->toArray() ] ) !!}" />
|
||||
@endif
|
||||
@if( $entry->version )
|
||||
<x-entry-meta-item label="Version" value="{{ $entry->version }}" route="none" />
|
||||
@endif
|
||||
@if( $entry->release_date )
|
||||
<x-entry-meta-item label="Release Date" value="{{ $entry->release_date }}" />
|
||||
@endif
|
||||
@if( $entry->modifications->isNotEmpty() )
|
||||
<x-entry-meta-item label="Type of hack" value="{{ $entry->modifications->pluck('name')->implode(', ') }}" route="none" />
|
||||
<x-entry-meta-item label="Release Date" value="{{ $entry->release_date->format('Y-m-d') }}" route="none" />
|
||||
@endif
|
||||
</div>
|
||||
<div class="hack-actions" style="display:flex;gap:10px;">
|
||||
@@ -206,7 +206,7 @@
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
@if( $entry->staff_credits )
|
||||
@if( $entry->parseStaffCredits() )
|
||||
<x-entry-section-title label="Staff Credits" icon="users-round" />
|
||||
<div class="entry-description">
|
||||
<ul>
|
||||
|
||||
95
resources/views/layouts/modcp.blade.php
Normal file
95
resources/views/layouts/modcp.blade.php
Normal file
@@ -0,0 +1,95 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="modcp-wrapper">
|
||||
<aside class="modcp-sidebar">
|
||||
<div class="modcp-sidebar-header">
|
||||
<i data-lucide="shield" size="16"></i>
|
||||
Mod CP
|
||||
</div>
|
||||
|
||||
<nav class="modcp-nav">
|
||||
|
||||
<div class="modcp-nav-group">
|
||||
<span class="modcp-nav-label">Overview</span>
|
||||
<a href="{{ route('modcp.index') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.index') ? 'active' : '' }}>
|
||||
<i data-lucide="layout-dashboard" size="15"></i>
|
||||
Dashboard
|
||||
</a>
|
||||
<a href="{{ route('queue.index') }}" class="modcp-nav-item" {{ request()->routeIs('queue.index') ? 'active' : '' }}>
|
||||
<i data-lucide="gavel" size="15"></i>
|
||||
Submissions Queue
|
||||
@if(( $pending = \App\Models\Entry::where('state','pending')->count() ) > 0)
|
||||
<span class="modcp-nav-badge">{{ $pending }}</span>
|
||||
@endif
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="modcp-nav-group">
|
||||
<span class="modcp-nav-label">Content</span>
|
||||
<a href="{{ route('modcp.locked') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.locked') ? 'active' : '' }}>
|
||||
<i data-lucide="lock" size="15"></i>
|
||||
Locked entries
|
||||
</a>
|
||||
@can('is-admin')
|
||||
<a href="{{ route('modcp.draft') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.draft') ? 'active' : '' }}>
|
||||
<i data-lucide="scissors" size="15"></i>
|
||||
Draft entries
|
||||
</a>
|
||||
<a href="{{ route('modcp.hidden') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.hidden') ? 'active' : '' }}>
|
||||
<i data-lucide="eye-off" size="15"></i>
|
||||
Hidden entries
|
||||
</a>
|
||||
<a href="{{ route('modcp.deleted') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.deleted') ? 'active' : '' }}>
|
||||
<i data-lucide="trash-2" size="15"></i>
|
||||
Deleted entries
|
||||
</a>
|
||||
@endcan
|
||||
</div>
|
||||
|
||||
<div class="modcp-nav-group">
|
||||
<span class="modcp-nav-label">Resources</span>
|
||||
<a href="{{ route('modcp.games.index') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.games.*') ? 'active' : '' }}">
|
||||
<i data-lucide="gamepad-2" size="15"></i>
|
||||
Games
|
||||
</a>
|
||||
<a href="{{ route('modcp.languages.index') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.languages.*') ? 'active' : '' }}">
|
||||
<i data-lucide="languages" size="15"></i>
|
||||
Languages
|
||||
</a>
|
||||
<a href="{{ route('modcp.authors.index') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.authors.*') ? 'active' : '' }}">
|
||||
<i data-lucide="users" size="15"></i>
|
||||
Authors
|
||||
</a>
|
||||
@can('is-admin')
|
||||
<a href="{{ route('modcp.platforms.index') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.platforms.*') ? 'active' : '' }}">
|
||||
<i data-lucide="gamepad-directional" size="15"></i>
|
||||
Platforms
|
||||
</a>
|
||||
<a href="{{ route('modcp.genres.index') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.genres.*') ? 'active' : '' }}">
|
||||
<i data-lucide="box" size="15"></i>
|
||||
Genres
|
||||
</a>
|
||||
@endcan
|
||||
</div>
|
||||
|
||||
<div class="modcp-nav-group">
|
||||
<span class="modcp-nav-label">Community</span>
|
||||
<a href="{{ xfRoute('reports') }}" class="modcp-nav-item">
|
||||
<i data-lucide="triangle-alert" size="15"></i>
|
||||
Reports
|
||||
</a>
|
||||
<a href="{{ xfRoute('approval-queue') }}" class="modcp-nav-item">
|
||||
<i data-lucide="message-circle-check" size="15"></i>
|
||||
Approval Queue
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<div class="modcp-content">
|
||||
@yield('modcp-content')
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -37,6 +37,9 @@
|
||||
{{-- Platforms --}}
|
||||
<x-database-filter-without-mode title="Platform" :items="$allPlatforms" model="platforms"/>
|
||||
|
||||
{{-- Genres --}}
|
||||
<x-database-filter-without-mode title="Genre" :items="$allGenres" model="genres"/>
|
||||
|
||||
{{-- Statuses --}}
|
||||
<x-database-filter-without-mode title="Status" :items="$allStatuses" model="statuses"/>
|
||||
|
||||
|
||||
89
resources/views/modcp/authors.blade.php
Normal file
89
resources/views/modcp/authors.blade.php
Normal file
@@ -0,0 +1,89 @@
|
||||
@extends('layouts.modcp')
|
||||
|
||||
@section('modcp-content')
|
||||
|
||||
<div class="modcp-page-title">
|
||||
Authors
|
||||
<span class="modcp-count">{{ $items->total() }}</span>
|
||||
</div>
|
||||
|
||||
<x-mod-c-p-search placeholder="Search an author..."/>
|
||||
|
||||
<div class="modcp-add-form">
|
||||
<form action="{{ route('modcp.authors.store') }}" method="POST">
|
||||
@csrf
|
||||
<div class="modcp-add-form-inner">
|
||||
<input type="text" name="name" class="form-input"
|
||||
placeholder="Author name..." required>
|
||||
<div style="width:20%">
|
||||
<livewire:xf-user-selector />
|
||||
</div>
|
||||
<input type="text" name="website" class="form-input"
|
||||
placeholder="Website">
|
||||
<button type="submit" class="btn primary">
|
||||
<i data-lucide="plus" size="14"></i> Add
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modcp-list">
|
||||
@forelse($items as $author)
|
||||
<div class="modcp-list-item" x-data="{ editing: false }">
|
||||
|
||||
<div class="modcp-list-item-info" x-show="!editing">
|
||||
<span class="modcp-list-item-title">{{ $author->name }}</span>
|
||||
<span class="modcp-list-item-meta">
|
||||
<span class="badge">{{ $author->website ?? '—' }}</span>
|
||||
@if(($xfUser = $author->user()) !== null )
|
||||
<span class="badge">
|
||||
<x-xf-username-link :user="$xfUser" />
|
||||
</span>
|
||||
@endif
|
||||
· {{ $author->entries_count }} {{ Str::plural('entry', $author->entries_count) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<form action="{{ route('modcp.authors.update', $author) }}" method="POST"
|
||||
class="modcp-list-item-edit modcp-list-item-edit--game"
|
||||
x-show="editing" x-cloak>
|
||||
@csrf @method('PATCH')
|
||||
<input type="text" name="name" class="form-input"
|
||||
placeholder="Author name..." value="{{ $author->name }}" required>
|
||||
<div style="width:20%">
|
||||
<livewire:xf-user-selector :initial-user-id="$author->user_id" />
|
||||
</div>
|
||||
<input type="text" name="website" class="form-input"
|
||||
placeholder="Website" value="{{ $author->website }}">
|
||||
<button type="submit" class="btn primary">
|
||||
<i data-lucide="check" size="13"></i>
|
||||
</button>
|
||||
<button type="button" class="btn" @click="editing = false">
|
||||
<i data-lucide="x" size="13"></i>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="modcp-list-item-actions" x-show="!editing">
|
||||
<button type="button" class="btn" @click="editing = true">
|
||||
<i data-lucide="pen" size="13"></i>
|
||||
</button>
|
||||
<form action="{{ route('modcp.authors.destroy', $author) }}" method="POST"
|
||||
style="display:inline"
|
||||
onsubmit="return confirm('Delete {{ addslashes($author->name) }}?')">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="btn danger"
|
||||
{{ $author->entries_count > 0 ? 'disabled title=Has entries' : '' }}>
|
||||
<i data-lucide="trash-2" size="13"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@empty
|
||||
<div class="modcp-empty"><p>No authors yet.</p></div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
{{ $items->links() }}
|
||||
|
||||
@endsection
|
||||
61
resources/views/modcp/deleted.blade.php
Normal file
61
resources/views/modcp/deleted.blade.php
Normal file
@@ -0,0 +1,61 @@
|
||||
@extends('layouts.modcp')
|
||||
|
||||
@section('page-title', 'Deleted entries - ' . config('app.name') )
|
||||
|
||||
@section('modcp-content')
|
||||
|
||||
<div class="modcp-page-title">
|
||||
<i data-lucide="trash-2" size="20"></i>
|
||||
Deleted entries
|
||||
<span class="modcp-count">{{ $entries->total() }}</span>
|
||||
</div>
|
||||
|
||||
@if($entries->isEmpty())
|
||||
<div class="modcp-empty">
|
||||
<i data-lucide="check-circle" size="36"></i>
|
||||
<p>No deleted entries.</p>
|
||||
</div>
|
||||
@else
|
||||
<div class="modcp-list">
|
||||
@foreach($entries as $entry)
|
||||
<div class="modcp-list-item modcp-list-item--deleted">
|
||||
<div class="modcp-list-item-cover">
|
||||
@if($entry->main_image)
|
||||
<img src="{{ Storage::url($entry->main_image) }}" alt="">
|
||||
@else
|
||||
<i data-lucide="image" size="20"></i>
|
||||
@endif
|
||||
</div>
|
||||
<div class="modcp-list-item-info">
|
||||
<span class="modcp-list-item-title">{{ $entry->complete_title ?? $entry->title }}</span>
|
||||
<span class="modcp-list-item-meta">
|
||||
<span class="badge {{ $entry->type }}">{{ $entry->type }}</span>
|
||||
@php $daysLeft = max(0, 7 - (int) now()->diffInDays($entry->deleted_at)) @endphp
|
||||
<span style="color: var(--error)">
|
||||
Deleted {{ $entry->deleted_at->diffForHumans() }}
|
||||
@if($daysLeft > 0) · purged in {{ $daysLeft }}d @endif
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="modcp-list-item-actions">
|
||||
<form action="{{ route('modcp.restore', $entry->id) }}" method="POST" style="display:inline">
|
||||
@csrf @method('PATCH')
|
||||
<button type="submit" class="btn success">
|
||||
<i data-lucide="rotate-ccw" size="13"></i> Restore
|
||||
</button>
|
||||
</form>
|
||||
<form action="{{ route('modcp.destroy', $entry->id) }}" method="POST" style="display:inline"
|
||||
@submit="if (!confirm('Permanently delete this entry?')) $event.preventDefault()">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="btn danger">
|
||||
<i data-lucide="trash-2" size="13"></i> Purge
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
{{ $entries->links() }}
|
||||
@endif
|
||||
|
||||
@endsection
|
||||
52
resources/views/modcp/entries.blade.php
Normal file
52
resources/views/modcp/entries.blade.php
Normal file
@@ -0,0 +1,52 @@
|
||||
@extends('layouts.modcp')
|
||||
|
||||
@section('page-title', $pageTitle . ' - ' . config('app.name') )
|
||||
|
||||
@section('modcp-content')
|
||||
<div class="modcp-page-title">
|
||||
{{ $pageTitle }}
|
||||
<span class="modcp-count">{{ $entries->count() }}</span>
|
||||
</div>
|
||||
|
||||
@if($entries->isEmpty())
|
||||
<div class="modcp-empty">
|
||||
<i data-lucide="check-circle" size="36"></i>
|
||||
<p>No {{ $state }} entries.</p>
|
||||
</div>
|
||||
@else
|
||||
<div class="modcp-list">
|
||||
@foreach($entries as $entry)
|
||||
<div class="modcp-list-item">
|
||||
<div class="modcp-list-item-cover">
|
||||
@if($entry->main_image)
|
||||
<img src="{{ Storage::url($entry->main_image) }}" alt="">
|
||||
@else
|
||||
<i data-lucide="image" size="20"></i>
|
||||
@endif
|
||||
</div>
|
||||
<div class="modcp-list-item-info">
|
||||
<span class="modcp-list-item-title">{{ $entry->complete_title }}</span>
|
||||
<span class="modcp-list-item-meta">
|
||||
<span class="badge {{ $entry->type }}">{{ \App\Livewire\Database::ENTRY_TYPES[$entry->type] }}</span>
|
||||
@if($entry->getRealPlatform())
|
||||
<span class="badge">{{ $entry->getRealPlatform()->name }}</span>
|
||||
@endif
|
||||
Added {{ $entry->created_at->format('d M Y') }} by<x-xf-username-link :user-id="$entry->user_id" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="modcp-list-item-actions">
|
||||
<a href="{{ route('entries.show', ['section' => $entry->type, 'entry' => $entry]) }}"
|
||||
class="btn" target="_blank">
|
||||
<i data-lucide="eye" size="13"></i> View
|
||||
</a>
|
||||
<a href="{{ route('submit.edit', [$entry->type, $entry->id]) }}"
|
||||
class="btn">
|
||||
<i data-lucide="pen" size="13"></i> Edit
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
{{ $entries->links() }}
|
||||
@endif
|
||||
@endsection
|
||||
103
resources/views/modcp/games.blade.php
Normal file
103
resources/views/modcp/games.blade.php
Normal file
@@ -0,0 +1,103 @@
|
||||
@extends('layouts.modcp')
|
||||
|
||||
@section('modcp-content')
|
||||
|
||||
<div class="modcp-page-title">
|
||||
Games
|
||||
<span class="modcp-count">{{ $items->total() }}</span>
|
||||
</div>
|
||||
|
||||
<x-mod-c-p-search placeholder="Search a game..." />
|
||||
|
||||
<div class="modcp-add-form">
|
||||
<form action="{{ route('modcp.games.store') }}" method="POST">
|
||||
@csrf
|
||||
<div class="modcp-add-form-inner">
|
||||
<input type="text" name="name" class="form-input"
|
||||
placeholder="Game name..." required>
|
||||
<select name="platform_id" class="form-select" required style="width: 20%">
|
||||
<option value="" disabled selected>Platform...</option>
|
||||
@foreach($platforms as $platform)
|
||||
<option value="{{ $platform->id }}">{{ $platform->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="genre_id" class="form-select" required style="width: 20%">
|
||||
<option value="" disabled selected>Genre...</option>
|
||||
@foreach($genres as $genre)
|
||||
<option value="{{ $genre->id }}">{{ $genre->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<button type="submit" class="btn primary">
|
||||
<i data-lucide="plus" size="14"></i> Add
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modcp-list">
|
||||
@forelse($items as $game)
|
||||
<div class="modcp-list-item" x-data="{ editing: false }">
|
||||
|
||||
<div class="modcp-list-item-info" x-show="!editing">
|
||||
<span class="modcp-list-item-title">{{ $game->name }}</span>
|
||||
<span class="modcp-list-item-meta">
|
||||
<span class="badge">{{ $game->platform->name ?? '—' }}</span>
|
||||
<span class="badge">{{ $game->genre->name ?? '—' }}</span>
|
||||
· {{ $game->entries_count }} {{ Str::plural('entry', $game->entries_count) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<form action="{{ route('modcp.games.update', $game) }}" method="POST"
|
||||
class="modcp-list-item-edit modcp-list-item-edit--game"
|
||||
x-show="editing" x-cloak>
|
||||
@csrf @method('PATCH')
|
||||
<input type="text" name="name" class="form-input"
|
||||
value="{{ $game->name }}" required>
|
||||
<select name="platform_id" class="form-select form-select--small" required>
|
||||
@foreach($platforms as $platform)
|
||||
<option value="{{ $platform->id }}"
|
||||
{{ $game->platform_id == $platform->id ? 'selected' : '' }}>
|
||||
{{ $platform->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="genre_id" class="form-select form-select--small" required>
|
||||
@foreach($genres as $genre)
|
||||
<option value="{{ $genre->id }}"
|
||||
{{ $game->genre_id == $genre->id ? 'selected' : '' }}>
|
||||
{{ $genre->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<button type="submit" class="btn primary">
|
||||
<i data-lucide="check" size="13"></i>
|
||||
</button>
|
||||
<button type="button" class="btn" @click="editing = false">
|
||||
<i data-lucide="x" size="13"></i>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="modcp-list-item-actions" x-show="!editing">
|
||||
<button type="button" class="btn" @click="editing = true">
|
||||
<i data-lucide="pen" size="13"></i>
|
||||
</button>
|
||||
<form action="{{ route('modcp.games.destroy', $game) }}" method="POST"
|
||||
style="display:inline"
|
||||
onsubmit="return confirm('Delete {{ addslashes($game->name) }}?')">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="btn danger"
|
||||
{{ $game->entries_count > 0 ? 'disabled title=Has entries' : '' }}>
|
||||
<i data-lucide="trash-2" size="13"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@empty
|
||||
<div class="modcp-empty"><p>No games yet.</p></div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
{{ $items->links() }}
|
||||
|
||||
@endsection
|
||||
91
resources/views/modcp/index.blade.php
Normal file
91
resources/views/modcp/index.blade.php
Normal file
@@ -0,0 +1,91 @@
|
||||
@extends('layouts.modcp')
|
||||
|
||||
@section('page-title', "Dashboard - " . config('app.name') )
|
||||
|
||||
@section('modcp-content')
|
||||
<div class="modcp-page-title">
|
||||
Dashboard
|
||||
</div>
|
||||
<div class="modcp-stats">
|
||||
<a href="{{ route('queue.index') }}" class="modcp-stat-card modcp-stat-card--orange">
|
||||
<div class="modcp-stat-icon"><i data-lucide="clipboard-list" size="22"></i></div>
|
||||
<div class="modcp-stat-info">
|
||||
<span class="modcp-stat-value">{{ $stats['pending'] }}</span>
|
||||
<span class="modcp-stat-label">In queue</span>
|
||||
</div>
|
||||
</a>
|
||||
<a href="{{ route('modcp.locked') }}" class="modcp-stat-card">
|
||||
<div class="modcp-stat-icon"><i data-lucide="lock" size="22"></i></div>
|
||||
<div class="modcp-stat-info">
|
||||
<span class="modcp-stat-value">{{ $stats['locked'] }}</span>
|
||||
<span class="modcp-stat-label">Locked</span>
|
||||
</div>
|
||||
</a>
|
||||
@can('is-admin')
|
||||
<a href="{{ route('modcp.draft') }}" class="modcp-stat-card">
|
||||
<div class="modcp-stat-icon"><i data-lucide="scissors" size="22"></i></div>
|
||||
<div class="modcp-stat-info">
|
||||
<span class="modcp-stat-value">{{ $stats['draft'] }}</span>
|
||||
<span class="modcp-stat-label">Draft</span>
|
||||
</div>
|
||||
</a>
|
||||
<a href="{{ route('modcp.hidden') }}" class="modcp-stat-card">
|
||||
<div class="modcp-stat-icon"><i data-lucide="eye-off" size="22"></i></div>
|
||||
<div class="modcp-stat-info">
|
||||
<span class="modcp-stat-value">{{ $stats['hidden'] }}</span>
|
||||
<span class="modcp-stat-label">Hidden</span>
|
||||
</div>
|
||||
</a>
|
||||
<a href="{{ route('modcp.deleted') }}" class="modcp-stat-card modcp-stat-card--danger">
|
||||
<div class="modcp-stat-icon"><i data-lucide="trash-2" size="22"></i></div>
|
||||
<div class="modcp-stat-info">
|
||||
<span class="modcp-stat-value">{{ $stats['deleted'] }}</span>
|
||||
<span class="modcp-stat-label">Deleted</span>
|
||||
</div>
|
||||
</a>
|
||||
@endcan
|
||||
<div class="modcp-stat-card modcp-stat-card--muted">
|
||||
<div class="modcp-stat-icon"><i data-lucide="database" size="22"></i></div>
|
||||
<div class="modcp-stat-info">
|
||||
<span class="modcp-stat-value">{{ $stats['total'] }}</span>
|
||||
<span class="modcp-stat-label">Total entries</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($recentDeleted->isNotEmpty())
|
||||
<div class="modcp-section-title" style="margin-top: 25px;">Recently deleted</div>
|
||||
<div class="modcp-list">
|
||||
@foreach($recentDeleted as $entry)
|
||||
<div class="modcp-list-item">
|
||||
<div class="modcp-list-item-info">
|
||||
<span class="modcp-list-item-title">{{ $entry->complete_title ?? $entry->title }}</span>
|
||||
<span class="modcp-list-item-meta">
|
||||
<span class="badge {{ $entry->type }}">{{ $entry->type }}</span>
|
||||
Deleted {{ $entry->deleted_at->diffForHumans() }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="modcp-list-item-actions">
|
||||
<form action="{{ route('modcp.restore', $entry->id) }}" method="POST" style="display:inline">
|
||||
@csrf @method('PATCH')
|
||||
<button type="submit" class="btn success">
|
||||
<i data-lucide="rotate-ccw" size="13"></i> Restore
|
||||
</button>
|
||||
</form>
|
||||
<form action="{{ route('modcp.destroy', $entry->id) }}" method="POST" style="display:inline"
|
||||
onsubmit="return confirm('Permanently delete?')">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="btn danger">
|
||||
<i data-lucide="trash-2" size="13"></i> Purge
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
<a href="{{ route('modcp.deleted') }}" class="modcp-list-see-all">
|
||||
See all deleted entries →
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@endsection
|
||||
76
resources/views/modcp/resources.blade.php
Normal file
76
resources/views/modcp/resources.blade.php
Normal file
@@ -0,0 +1,76 @@
|
||||
@extends('layouts.modcp')
|
||||
|
||||
@section('page-title', $title . ' - ' . config('app.name'))
|
||||
|
||||
@section('modcp-content')
|
||||
|
||||
<div class="modcp-page-title">
|
||||
{{ $title }}
|
||||
<span class="modcp-count">{{ $items->total() }}</span>
|
||||
</div>
|
||||
|
||||
<x-mod-c-p-search placeholder="Search a {{ $singular }}..." />
|
||||
|
||||
<div class="modcp-add-form">
|
||||
<form action="{{ route($storeRoute) }}" method="POST" class="modcp-add-form-inner">
|
||||
@csrf
|
||||
<input type="text" name="name" class="form-input" placeholder="Add new {{ strtolower($singular) }}..." required>
|
||||
@if(isset($extraFields))
|
||||
@foreach($extraFields as $field)
|
||||
<input type="text" name="{{ $field['name'] }}" class="form-input" placeholder="{{ $field['placeholder'] }}">
|
||||
@endforeach
|
||||
@endif
|
||||
<button type="submit" class="btn primary">
|
||||
<i data-lucide="plus" size="14"></i> Add
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modcp-list">
|
||||
@forelse($items as $item)
|
||||
<div class="modcp-list-item" x-data="{ editing: false }">
|
||||
<div class="modcp-list-item-info" x-show="!editing">
|
||||
<span class="modcp-list-item-title">{{ $item->name }}</span>
|
||||
<span class="modcp-list-item-meta">
|
||||
slug: {{ $item->slug }}
|
||||
@isset($item->entries_count)
|
||||
· {{ $item->entries_count }} {{ Str::plural('entry', $item->entries_count) }}
|
||||
@endisset
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<form action="{{ route($updateRoute, $item) }}" method="POST"
|
||||
class="modcp-list-item-edit" x-show="editing" x-cloak>
|
||||
@csrf @method('PATCH')
|
||||
<input type="text" name="name" class="form-input" value="{{ $item->name }}">
|
||||
<button type="submit" class="btn primary">
|
||||
<i data-lucide="check" size="13"></i>
|
||||
</button>
|
||||
<button type="button" class="btn" @click="editing = false">
|
||||
<i data-lucide="x" size="13"></i>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="modcp-list-item-actions" x-show="!editing">
|
||||
<button type="button" class="btn" @click="editing = true">
|
||||
<i data-lucide="pen" size="13"></i>
|
||||
</button>
|
||||
<form action="{{ route($destroyRoute, $item) }}" method="POST" style="display:inline"
|
||||
onsubmit="return confirm('Delete {{ $item->name }}?')">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="btn danger">
|
||||
<i data-lucide="trash-2" size="13"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="modcp-empty">
|
||||
<p>No {{ strtolower($title) }} yet.</p>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
{{ $items->links() }}
|
||||
|
||||
@endsection
|
||||
@@ -137,7 +137,7 @@
|
||||
@endif
|
||||
|
||||
<x-form-group-title label="{{ $words['attachments'] }}" icon="paperclip" />
|
||||
<x-main-image-field :old-path="old('main-image', $entry->main_image ?? '')" />
|
||||
<x-main-image-field :old-path="old('main-image', $entry->main_image ?? '') ?? ''" />
|
||||
<x-gallery-field :old-paths="old('gallery', $entry->gallery->pluck('image')->toArray() ?? [] )"/>
|
||||
@error('gallery')
|
||||
<x-form-error-text message="{{ $message }}" />
|
||||
@@ -181,6 +181,18 @@
|
||||
<livewire:xf-user-selector :initial-user-id="$entry->user_id" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group grid-c2">
|
||||
<div>
|
||||
<x-form-field-title name="XenForo Comments Thread ID" />
|
||||
<input type="text" name="comments_thread_id" class="form-input" value="{{ old('comments_thread_id', $entry->comments_thread_id) }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<x-form-field-title name="Metadata" required="true" />
|
||||
<div class="form-type-of-checkboxes form-group level" id="entry-metadata">
|
||||
<label><input class="form-checkbox" type="checkbox" name="featured" value="1" {{ old('featured', $entry->featured ) ? 'checked' : '' }}>Featured entry</label>
|
||||
</div>
|
||||
</div>
|
||||
@endcan
|
||||
@cannot('moderate', $entry)
|
||||
|
||||
|
||||
@@ -36,21 +36,18 @@
|
||||
'upload-item-done': file.done,
|
||||
'upload-item-error': file.error
|
||||
}">
|
||||
<template x-if="!file.done && !file.error">
|
||||
<i data-lucide="loader-2" class="spin"></i>
|
||||
</template>
|
||||
<template x-if="file.error">
|
||||
<i data-lucide="alert-circle"></i>
|
||||
</template>
|
||||
<template x-if="file.done && file.state === 'public'">
|
||||
<i data-lucide="eye" class="file-state-icon file-state-icon--public"></i>
|
||||
</template>
|
||||
<template x-if="file.done && file.state === 'private'">
|
||||
<i data-lucide="eye-off" class="file-state-icon file-state-icon--private"></i>
|
||||
</template>
|
||||
<template x-if="file.done && file.state === 'archived'">
|
||||
<i data-lucide="archive" class="file-state-icon file-state-icon--archived"></i>
|
||||
</template>
|
||||
|
||||
<div class="file-status-icons">
|
||||
<i x-show="!file.done && !file.error" data-lucide="loader-2" class="spin"></i>
|
||||
|
||||
<i x-show="file.error" data-lucide="alert-circle"></i>
|
||||
|
||||
<i x-show="file.done && file.state === 'public'" data-lucide="eye" class="file-state-icon file-state-icon--public"></i>
|
||||
|
||||
<i x-show="file.done && file.state === 'private'" data-lucide="eye-off" class="file-state-icon file-state-icon--private"></i>
|
||||
|
||||
<i x-show="file.done && file.state === 'archived'" data-lucide="archive" class="file-state-icon file-state-icon--archived"></i>
|
||||
</div>
|
||||
|
||||
<div class="upload-item-info">
|
||||
<span class="upload-item-name" x-text="file.name"></span>
|
||||
@@ -103,13 +100,46 @@
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
<div class="upload-item-actions">
|
||||
<div class="upload-item-actions" x-data="{ showMetadata: false }">
|
||||
<button type="button" class="btn" x-show="file.error" @click="handleRetryFile(i)">
|
||||
<i data-lucide="refresh-cw"></i>
|
||||
</button>
|
||||
@if($isEdit)
|
||||
<button type="button" class="btn" x-show="file.done" @click="showMetadata = true">
|
||||
<i data-lucide="settings"></i>
|
||||
</button>
|
||||
@endif
|
||||
<button type="button" class="btn" x-show="file.done || file.error" @click="handleRemoveFile(i)">
|
||||
<i data-lucide="x"></i>
|
||||
</button>
|
||||
<template x-teleport="body">
|
||||
<div class="modal-overlay"
|
||||
x-cloak
|
||||
x-show="showMetadata"
|
||||
x-transition.opacity.duration.300ms
|
||||
@click.self="showMetadata = false"
|
||||
@keydown.escape.window="showMetadata = false">
|
||||
<div class="modal-window" x-show="showMetadata" x-transition>
|
||||
|
||||
<div class="modal-header">
|
||||
<span class="modal-title" style="display: flex; align-items: center; gap: 8px;">
|
||||
File Settings: <span x-text="file.name" style="color: var(--rhpz-orange);"></span>
|
||||
</span>
|
||||
<button type="button" class="modal-close" @click="showMetadata = false">
|
||||
<i data-lucide="x" size="20"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-content">
|
||||
<div class="form-group">
|
||||
<x-form-group-title label="Online patcher" />
|
||||
<label><input type="checkbox" class="form-checkbox" :name="'files_metadata[' + file.uuid + '][online_patcher]'" x-model="file.meta_online_patcher"> Enable it</label>
|
||||
<label x-show="file.meta_online_patcher"><input type="checkbox" class="form-checkbox" :name="'files_metadata[' + file.meta_secondary_online_patcher + '][secondary_online_patcher]'" x-model="file.meta_secondary_online_patcher"> Mark as a secondary patch</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<input type="hidden" name="files_uuid[]" :value="file.uuid" x-show="file.done">
|
||||
@if($isEdit)
|
||||
|
||||
92
resources/views/tools/patcher.blade.php
Normal file
92
resources/views/tools/patcher.blade.php
Normal file
@@ -0,0 +1,92 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('page-title', "ROM Patcher - " . config('app.name'))
|
||||
|
||||
@push('scripts')
|
||||
<script type="text/javascript" src="{{ asset('rom-patcher-js/RomPatcher.webapp.js') }}"></script>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="page-title">
|
||||
<span>ROM Patcher</span>
|
||||
</div>
|
||||
|
||||
<div id="rom-patcher-container" class="patcher-container" x-data="RomPatcher({{ Js::from($patches ?? []) }})">
|
||||
|
||||
<div class="patcher-grid">
|
||||
<div class="form-group">
|
||||
<label class="form-label">1. Original ROM</label>
|
||||
<div class="patcher-dropzone" id="rom-dropzone"
|
||||
:class="{ 'dragover': isRomDragOver, 'has-file': romFileName !== '' }"
|
||||
@click="triggerFileInput('rom-patcher-input-file-rom')"
|
||||
@dragover.prevent="isRomDragOver = true"
|
||||
@dragleave.prevent="isRomDragOver = false"
|
||||
@drop.prevent="isRomDragOver = false; handleDrop($event, 'rom')">
|
||||
|
||||
<input type="file" id="rom-patcher-input-file-rom" style="display: none;" @change="handleInputChange($event, 'rom')" disabled />
|
||||
<i data-lucide="gamepad-2" size="40" :style="romFileName ? 'color: var(--rhpz-orange)' : 'color: var(--text2)'"></i>
|
||||
<span style="color: var(--text); font-weight: 500;" x-text="romFileName ? romFileName : 'Drag n\'drop your file here or click'"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">2. Patch file</label>
|
||||
|
||||
<div class="patcher-dropzone" id="patch-dropzone"
|
||||
x-show="!hasEmbedded"
|
||||
:class="{ 'dragover': isPatchDragOver, 'has-file': patchFileName !== '' }"
|
||||
@click="triggerFileInput('rom-patcher-input-file-patch')"
|
||||
@dragover.prevent="isPatchDragOver = true"
|
||||
@dragleave.prevent="isPatchDragOver = false"
|
||||
@drop.prevent="isPatchDragOver = false; handleDrop($event, 'patch')">
|
||||
|
||||
<input type="file" id="rom-patcher-input-file-patch" style="display: none;" @change="handleInputChange($event, 'patch')" disabled />
|
||||
<i data-lucide="file-archive" size="40" :style="patchFileName ? 'color: var(--rhpz-orange)' : 'color: var(--text2)'"></i>
|
||||
<span style="color: var(--text); font-weight: 500;" x-text="patchFileName ? patchFileName : 'Drag n\'drop your file here or click'"></span>
|
||||
</div>
|
||||
|
||||
<div x-show="hasEmbedded" class="embed-patch-box">
|
||||
<div class="embed-patch-box-icon">
|
||||
<div class="embed-patch-box-icon-block">
|
||||
<i data-lucide="package" size="24" color="var(--rhpz-orange)"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-weight: 600; color: var(--text); font-size: 1.1rem;">Patch file</div>
|
||||
<div style="font-size: 0.85rem; color: var(--text2);">Select if there is multiple patch files</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rom-patcher-container-input" style="margin-top: 10px;">
|
||||
<select id="rom-patcher-select-patch" class="form-select" style="width: 100%; cursor: pointer;"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="patcher-status-box" id="patcher-status" x-show="showStatusBox" x-transition x-cloak style="margin-top: 20px;">
|
||||
<div class="rom-patcher-row" style="color: var(--text2);">
|
||||
<div style="color: var(--rhpz-orange); font-weight: bold;">Checksums:</div>
|
||||
<ul style="margin-bottom: 0">
|
||||
<li>CRC32: <span id="rom-patcher-span-crc32"></span></li>
|
||||
<li>MD5: <span id="rom-patcher-span-md5"></span></li>
|
||||
<li>SHA-1: <span id="rom-patcher-span-sha1"></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<span id="rom-patcher-span-rom-info"></span>
|
||||
<div class="rom-patcher-row margin-bottom" id="rom-patcher-row-patch-description">
|
||||
<div style="color: var(--rhpz-orange); font-weight: bold;">Description:</div>
|
||||
<div id="rom-patcher-patch-description"></div>
|
||||
</div>
|
||||
<div class="rom-patcher-row margin-bottom" id="rom-patcher-row-patch-requirements">
|
||||
<div id="rom-patcher-patch-requirements-type" style="color: var(--rhpz-orange); font-weight: bold;">ROM requirements:</div>
|
||||
<div id="rom-patcher-patch-requirements-value"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 25px; border-top: 1px solid var(--border); padding-top: 20px; text-align: right;">
|
||||
<button type="button" class="btn primary" id="rom-patcher-button-apply" disabled>
|
||||
<i data-lucide="wrench" size="16"></i> Apply patch
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
Reference in New Issue
Block a user