From 70daf38e5ee9e8f2b437bccc9c9513723f8ebe57 Mon Sep 17 00:00:00 2001 From: Kakarot <57848836+GhzGarage@users.noreply.github.com> Date: Sun, 11 May 2025 21:13:37 -0500 Subject: [PATCH] updates --- fxmanifest.lua | 7 +- html/css/editor.css | 779 ++++++++++---------------------------- html/css/styleguide.css | 228 ++++++++++++ html/js/editor.js | 806 ++++++++++++++-------------------------- shared/loader.lua | 10 +- 5 files changed, 720 insertions(+), 1110 deletions(-) create mode 100644 html/css/styleguide.css diff --git a/fxmanifest.lua b/fxmanifest.lua index db518a2..a08cd79 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -18,7 +18,8 @@ client_scripts { 'client/functions.lua', 'client/loops.lua', 'client/events.lua', - 'client/drawtext.lua' + 'client/drawtext.lua', + 'client/editor.lua' } server_scripts { @@ -38,9 +39,11 @@ files { 'html/index.html', 'html/css/*.css', 'html/js/*.js', - 'shared/jobs.json', + 'shared/config.json', 'shared/gangs.json', 'shared/items.json', + 'shared/jobs.json', + 'shared/player_defaults.json', 'shared/vehicles.json', 'shared/weapons.json' } diff --git a/html/css/editor.css b/html/css/editor.css index 96bc1ec..613acba 100644 --- a/html/css/editor.css +++ b/html/css/editor.css @@ -1,95 +1,6 @@ -@import url("https://fonts.googleapis.com/css2?family=Exo+2:wght@300;400;500;600;700&display=swap"); - -:root { - /* Primary colors */ - --md-primary: #f44336; - --md-on-primary: #ffffff; - --md-primary-container: #ffdad6; - --md-on-primary-container: #410002; - - /* Secondary colors */ - --md-secondary: #d32f2f; - --md-on-secondary: #ffffff; - --md-secondary-container: #ffdad5; - --md-on-secondary-container: #410001; - - /* Tertiary colors */ - --md-tertiary: #ff8a65; - --md-on-tertiary: #ffffff; - --md-tertiary-container: #ffdacc; - --md-on-tertiary-container: #410002; - - /* Surface colors (dark theme) */ - --md-surface: #1c1b1f; - --md-on-surface: #e6e1e5; - --md-surface-variant: #49454f; - --md-on-surface-variant: #c9c5d0; - --md-surface-container-lowest: #0f0d13; - --md-surface-container-low: #1d1b20; - --md-surface-container: #211f26; - --md-surface-container-high: #2b2930; - --md-surface-container-highest: #36343b; - - /* Error colors */ - --md-error: #b3261e; - --md-on-error: #ffffff; - --md-error-container: #93000a; - --md-on-error-container: #ffdad5; - - /* Notification colors */ - --md-success: #9bd880; - --md-on-success: #193800; - --md-success-container: #275000; - --md-on-success-container: #b6f397; - - --md-warning: #ffba47; - --md-on-warning: #422b00; - --md-warning-container: #5f3f00; - --md-on-warning-container: #ffddb0; - - --md-info: #b3c5ff; - --md-on-info: #002a77; - --md-info-container: #003ea7; - --md-on-info-container: #dae1ff; - - /* Neutrals */ - --md-outline: #79747e; - --md-outline-variant: #49454f; - --md-inverse-surface: #e6e1e5; - --md-inverse-on-surface: #1c1b1f; - --md-scrim: rgba(0, 0, 0, 0.6); - --md-shadow: rgba(0, 0, 0, 0.15); - - /* Elevation */ - --md-elevation-1: 0px 1px 3px 1px rgba(0, 0, 0, 0.15); - --md-elevation-2: 0px 2px 6px 2px rgba(0, 0, 0, 0.15); - --md-elevation-3: 0px 4px 8px 3px rgba(0, 0, 0, 0.15); - - /* Shapes */ - --md-radius-small: 8px; - --md-radius-medium: 12px; - --md-radius-large: 16px; - - /* Typography */ - --font-primary: "Exo 2", sans-serif; - --font-weight-regular: 400; - --font-weight-medium: 500; - --font-weight-bold: 700; -} - -* { - margin: 0; - padding: 0; - box-sizing: border-box; - font-family: var(--font-primary); -} - -body { - background-color: transparent; - color: var(--md-on-surface); - overflow: hidden; -} +@import url("styleguide.css"); +/* Editor container base */ #editor-container { position: absolute; top: 50%; @@ -97,273 +8,193 @@ body { transform: translate(-50%, -50%); width: 80vw; height: 80vh; - background-color: var(--md-surface); - border-radius: var(--md-radius-medium); - box-shadow: var(--md-elevation-3); display: flex; flex-direction: column; + background-color: var(--md-dark-surface); + border-radius: var(--md-radius-medium); + box-shadow: var(--md-elevation-3); overflow: hidden; } /* Header */ -header { - padding: 16px; - background-color: var(--md-surface-container-high); +#editor-container > header { display: flex; justify-content: space-between; align-items: center; + padding: 10px; + background-color: var(--md-dark-surface-container-high); } - -header h1 { - font-size: 22px; - color: var(--md-primary); +#editor-container > header h1 { + margin: 0; + font-size: var(--md-typescale-title-large-size); + color: var(--md-dark-on-surface); } - -.controls { +#editor-container > header .controls { display: flex; gap: 12px; } - -button { - padding: 8px 16px; - border: none; - border-radius: var(--md-radius-small); - cursor: pointer; +#save-btn, +#close-btn { display: flex; align-items: center; gap: 8px; - font-weight: 500; + padding: 8px 16px; + border: none; + border-radius: var(--md-radius-small); + font-size: var(--md-typescale-body-medium-size); + cursor: pointer; transition: background-color 0.2s; } - #save-btn { - background-color: var(--md-success); - color: var(--md-on-success); + background-color: var(--md-dark-primary); + color: var(--md-dark-on-primary); } - #save-btn:hover { - box-shadow: var(--md-elevation-1); - filter: brightness(1.1); + background-color: var(--md-dark-primary-container); } - #close-btn { - background-color: var(--md-secondary); - color: var(--md-on-secondary); + background-color: var(--md-dark-secondary); + color: var(--md-dark-on-secondary); +} +#close-btn:hover { + background-color: var(--md-dark-secondary-container); } -#close-btn:hover { +/* Main layout */ +#editor-container .content { + flex: 1; + display: flex; + overflow: hidden; +} + +/* Sidebar */ +#editor-container .sidebar { + width: 240px; + padding: 16px; + background-color: var(--md-dark-surface-container); + box-shadow: inset -1px 0 0 0 var(--md-dark-outline-variant); +} +#editor-container .sidebar h2 { + margin-top: 0; + margin-bottom: 12px; + font-size: var(--md-typescale-title-small-size); + color: var(--md-dark-on-surface); +} +#editor-container .file-list { + list-style: none; + margin: 0; + padding: 0; +} +#editor-container .file-list li { + display: flex; + align-items: center; + gap: 8px; + padding: 8px; + border-radius: var(--md-radius-small); + cursor: pointer; + transition: background-color 0.2s; +} +#editor-container .file-list li:hover { + background-color: rgba(255, 255, 255, var(--md-state-hover-dark-opacity)); +} +#editor-container .file-list li.active { + color: var(--md-dark-on-primary); +} + +/* Main content area */ +#editor-container .main-content { + flex: 1; + display: flex; + flex-direction: column; + padding: 16px; + overflow: hidden; +} +#editor-container .search-container { + margin-bottom: 12px; +} +#search-input { + width: 100%; + padding: 8px 12px; + border: 1px solid var(--md-dark-outline-variant); + border-radius: var(--md-radius-small); + background-color: var(--md-dark-surface-container-low); + color: var(--md-dark-on-surface); + font-size: var(--md-typescale-body-medium-size); + transition: border-color 0.2s; +} +#search-input:focus { + border-color: var(--md-dark-primary); + outline: none; +} + +/* Data container and cards */ +#data-container { + flex: 1; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 12px; + overflow-y: auto; + padding: 8px; +} +.data-card { + background-color: var(--md-dark-surface-container-low); + border-radius: var(--md-radius-medium); box-shadow: var(--md-elevation-1); - filter: brightness(1.1); + padding: 16px; + display: flex; + flex-direction: column; + justify-content: space-between; + transition: transform 0.1s, box-shadow 0.1s; +} +.data-card:hover { + transform: translateY(-2px); + box-shadow: var(--md-elevation-2); +} +.data-card h3 { + margin: 0 0 8px 0; + font-size: var(--md-typescale-title-medium-size); + color: var(--md-dark-on-surface); +} +.btn-edit { + align-self: flex-end; + padding: 4px 8px; + border: none; + border-radius: var(--md-radius-small); + background-color: var(--md-dark-secondary); + color: var(--md-dark-on-secondary); + cursor: pointer; + transition: background-color 0.2s; +} +.btn-edit:hover { + background-color: var(--md-dark-secondary-container); } /* Floating Action Button */ -.fab { +#add-item-fab { position: fixed; - right: 24px; bottom: 24px; + right: 24px; width: 56px; height: 56px; border-radius: 50%; - background-color: var(--md-primary); - color: var(--md-on-primary); - box-shadow: var(--md-elevation-3); + background-color: var(--md-dark-primary); + color: var(--md-dark-on-primary); display: flex; align-items: center; justify-content: center; cursor: pointer; + box-shadow: var(--md-elevation-3); transition: transform 0.2s; } - -.fab:hover { +#add-item-fab:hover { transform: translateY(-4px); box-shadow: var(--md-elevation-2); } -.fab:active { - transform: translateY(0); -} - -/* Content layout */ -.content { - display: flex; - flex: 1; - overflow: hidden; - height: calc(100vh - 130px); - max-height: calc(100% - 60px); -} - -.sidebar { - background-color: var(--md-surface-container-high); - width: 220px; - padding: 16px; -} - -.sidebar h2 { - font-size: 16px; - font-weight: 500; - margin-bottom: 16px; - color: var(--md-on-surface); -} - -.file-list { - list-style: none; -} - -.file-list li { - padding: 12px; - border-radius: var(--md-radius-small); - cursor: pointer; - display: flex; - gap: 12px; -} - -.file-list li .material-symbols-rounded { - font-size: 20px; -} - -.file-list li:hover { - background-color: var(--md-surface-container-high); - transform: translateY(-2px); - box-shadow: var(--md-elevation-1); -} - -.file-list li.active { - background-color: var(--md-primary); - color: var(--md-on-primary); - transform: translateY(-2px); - box-shadow: var(--md-elevation-1); -} - -.main-content { - flex: 1; - padding: 16px; - overflow-y: auto; - display: flex; - flex-direction: column; - background-color: var(--md-surface-container-low); -} - -/* Search Input */ -.search-container { - margin-bottom: 16px; -} - -.search-wrapper { - position: relative; - width: 100%; -} - -.search-icon { - position: absolute; - left: 12px; - top: 50%; - transform: translateY(-50%); - color: var(--md-outline); - pointer-events: none; -} - -#search-input { - width: 100%; - padding: 12px 12px 12px 40px; - border: 1px solid var(--md-outline-variant); - border-radius: var(--md-radius-small); - background-color: var(--md-surface-container); - color: var(--md-on-surface); -} - -#search-input:focus { - border-color: var(--md-primary); - outline: none; -} - -#data-container { - flex: 1; - overflow-y: auto; - max-height: calc(100vh - 200px); - scrollbar-width: thin; /* For Firefox */ - scrollbar-color: var(--md-outline-variant) transparent; -} - -#data-container::-webkit-scrollbar { - width: 6px; /* Scrollbar width for WebKit browsers */ -} - -#data-container::-webkit-scrollbar-thumb { - background-color: var(--md-outline-variant); /* Thumb color */ - border-radius: 3px; /* Rounded corners for the thumb */ -} - -#data-container::-webkit-scrollbar-track { - background-color: transparent; /* Track color */ -} - -/* Cards for displaying data items */ -.data-card { - background-color: var(--md-surface-container); - border-radius: var(--md-radius-medium); - padding: 16px; - margin-bottom: 12px; - cursor: pointer; - transition: transform 0.2s, box-shadow 0.2s; - overflow: hidden; - word-break: break-word; - position: relative; -} - -.data-card::after { - content: ""; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: var(--md-primary); - opacity: 0; - transition: opacity 0.2s; - pointer-events: none; -} - -.data-card:hover { - background-color: var(--md-surface-container-high); - transform: translateY(-2px); - box-shadow: var(--md-elevation-1); -} - -.data-card:hover::after { - opacity: 0.05; -} - -.data-card h3 { - color: var(--md-primary); - margin-bottom: 12px; - font-weight: 500; -} - -.data-properties { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); - gap: 12px; -} - -.property { - display: flex; - flex-direction: column; -} - -.property-label { - font-size: 12px; - color: var(--md-outline); -} - -.property-value { - font-size: 14px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - max-width: 100%; -} - /* Modal */ +#edit-modal.hidden { + display: none; +} #edit-modal { position: absolute; top: 0; @@ -371,304 +202,104 @@ button { width: 100%; height: 100%; display: grid; - place-items: center; /* Perfectly centers the modal */ - background-color: rgba(0, 0, 0, 0.6); - backdrop-filter: blur(2px); + place-items: center; + background-color: var(--md-dark-scrim); z-index: 1000; } - -#edit-modal.hidden { - animation: modal-disappear 0.2s ease-out forwards; - display: none; -} - .modal-content { - background-color: var(--md-surface-container); - border-radius: var(--md-radius-large); - width: 600px; + background-color: var(--md-dark-surface); + border-radius: var(--md-radius-medium); + width: 500px; max-width: 90%; - max-height: 90vh; display: flex; flex-direction: column; - box-shadow: var(--md-elevation-3); - animation: modal-appear 0.2s ease-out; + box-shadow: var(--md-elevation-4); overflow: hidden; } - .modal-header { - padding: 20px; display: flex; justify-content: space-between; align-items: center; - border-bottom: 1px solid var(--md-outline-variant); - background-color: var(--md-surface-container-high); + padding: 12px; + background-color: var(--md-dark-surface-container-high); + border-bottom: 1px solid var(--md-dark-outline-variant); } - .modal-header h2 { - font-size: 20px; - font-weight: 400; - color: var(--md-primary); + margin: 0; + font-size: var(--md-typescale-title-medium-size); + color: var(--md-dark-on-surface); } - #close-modal-btn { background: none; - color: var(--md-on-surface-variant); + border: none; font-size: 24px; - padding: 0; - width: 32px; - height: 32px; + color: var(--md-dark-on-surface-variant); + cursor: pointer; display: flex; - justify-content: center; align-items: center; - border-radius: 50%; + justify-content: center; } - -#close-modal-btn:hover { - color: var(--md-error); - background-color: var(--md-surface-container-high); -} - .modal-body { - padding: 24px; + padding: 16px; overflow-y: auto; - max-height: 60vh; - scrollbar-width: thin; - scrollbar-color: var(--md-outline-variant) transparent; + flex: 1; } - -.modal-body::-webkit-scrollbar { - width: 6px; -} - -.modal-body::-webkit-scrollbar-thumb { - background-color: var(--md-outline-variant); - border-radius: 3px; -} - .modal-footer { - padding: 16px 24px; display: flex; justify-content: flex-end; gap: 12px; - border-top: 1px solid var(--md-outline-variant); - background-color: var(--md-surface-container-high); + padding: 12px; + background-color: var(--md-dark-surface-container-high); + border-top: 1px solid var(--md-dark-outline-variant); } - -#save-item-btn { - background-color: var(--md-success); - color: var(--md-on-success); -} - -#save-item-btn:hover { - box-shadow: var(--md-elevation-1); - filter: brightness(1.1); -} - #cancel-edit-btn { - background-color: var(--md-secondary); - color: var(--md-on-secondary); -} - -#cancel-edit-btn:hover { - box-shadow: var(--md-elevation-1); -} - -/* Form elements */ -.form-group { - margin-bottom: 16px; -} - -.form-group label { - display: block; - margin-bottom: 8px; - color: var(--md-on-surface); - font-size: 14px; -} - -.form-control { - width: 100%; - padding: 12px; - border: 1px solid var(--md-outline-variant); + padding: 8px 16px; + border: none; border-radius: var(--md-radius-small); - background-color: var(--md-surface-container-low); - color: var(--md-on-surface); - transition: border-color 0.2s; - scrollbar-width: thin; - scrollbar-color: var(--md-outline-variant) transparent; -} - -.form-control:focus { - border-color: var(--md-primary); - outline: none; -} - -textarea.form-control { - min-height: 100px; - font-family: monospace; -} - -.form-control::-webkit-scrollbar { - width: 6px; -} - -.form-control::-webkit-scrollbar-thumb { - background-color: var(--md-outline-variant); - border-radius: 3px; -} - -.complex-object { - border: 1px solid var(--md-outline-variant); - border-radius: var(--md-radius-small); - padding: 12px; - margin-top: 8px; - background-color: var(--md-surface-container); -} - -.complex-object-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 12px; -} - -.complex-object-title { - font-weight: 500; - color: var(--md-primary); -} - -.add-item-btn { - background-color: var(--md-primary-container); - color: var(--md-on-primary-container); - padding: 4px 8px; - font-size: 12px; - border-radius: var(--md-radius-small); -} - -.add-item-btn:hover { - filter: brightness(1.1); -} - -/* For handling arrays */ -.array-item { - border: 1px solid var(--md-outline-variant); - border-radius: var(--md-radius-small); - padding: 12px; - margin-bottom: 12px; - position: relative; - background-color: var(--md-surface-container-low); -} - -.remove-item-btn { - position: absolute; - top: 8px; - right: 8px; - background-color: var(--md-error-container); - color: var(--md-on-error-container); - width: 24px; - height: 24px; - font-size: 12px; - display: flex; - justify-content: center; - align-items: center; - transition: all 0.2s; -} - -.remove-item-btn:hover { - filter: brightness(1.1); -} - -/* Improved checkbox styling */ -input[type="checkbox"] { - /* Remove default styling */ - appearance: none; - -webkit-appearance: none; - - /* Create custom checkbox */ - width: 20px; - height: 20px; - border: 2px solid var(--md-outline-variant); - border-radius: 4px; - background-color: var(--md-surface-container-low); - position: relative; + background-color: var(--md-dark-secondary); + color: var(--md-dark-on-secondary); cursor: pointer; - vertical-align: middle; - margin-right: 8px; - transition: all 0.2s; + transition: background-color 0.2s; +} +#cancel-edit-btn:hover { + background-color: var(--md-dark-secondary-container); +} +#save-item-btn { + padding: 8px 16px; + border: none; + border-radius: var(--md-radius-small); + background-color: var(--md-dark-primary); + color: var(--md-dark-on-primary); + cursor: pointer; + transition: background-color 0.2s; +} +#save-item-btn:hover { + background-color: var(--md-dark-primary-container); } -/* Checked state */ -input[type="checkbox"]:checked { - background-color: var(--md-primary); - border-color: var(--md-primary); +/* small monospace preview under each card title */ +.data-preview { + margin: 8px 0; + font-family: monospace; + white-space: pre-wrap; + color: var(--md-dark-on-surface-variant); } -/* Checkmark */ -input[type="checkbox"]:checked::after { - content: "✓"; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - color: var(--md-on-primary); - font-size: 14px; - opacity: 0; - animation: fadeIn 0.2s forwards; +/* nested object fieldsets styling */ +.nested-fieldset { + border: 1px solid var(--md-dark-outline-variant); + padding: 12px; + margin: 8px 0; + border-radius: var(--md-radius-small); + background-color: var(--md-dark-surface-container-low); +} +.nested-fieldset legend { + font-weight: 600; + padding: 0 6px; + color: var(--md-dark-on-surface); } -/* Hover state */ -input[type="checkbox"]:hover { - border-color: var(--md-primary); -} - -input[type="checkbox"]:active { - transform: scale(0.9); -} - -/* Add this to the form-group for boolean/checkbox fields */ -.form-group.checkbox-group { - display: flex; - align-items: center; -} - -.form-group.checkbox-group label { - margin-bottom: 0; - margin-left: 8px; - order: 2; -} - -.form-group.checkbox-group input { - order: 1; -} - -@keyframes modal-appear { - from { - opacity: 0; - transform: scale(0.9); - } - to { - opacity: 1; - transform: scale(1); - } -} - -@keyframes modal-disappear { - from { - opacity: 1; - transform: scale(1); - } - to { - opacity: 0; - transform: scale(0.9); - } -} - -@keyframes fadeIn { - from { - opacity: 0; - transform: translate(-50%, -60%); - } - to { - opacity: 1; - transform: translate(-50%, -50%); - } +/* indent array entries a bit */ +.array-container > .form-group { + margin-left: 16px; } diff --git a/html/css/styleguide.css b/html/css/styleguide.css new file mode 100644 index 0000000..c1c4369 --- /dev/null +++ b/html/css/styleguide.css @@ -0,0 +1,228 @@ +:root { + /* Light Theme */ + --md-light-primary: #f44336; + --md-light-on-primary: #ffffff; + --md-light-primary-container: #ffdad6; + --md-light-on-primary-container: #410002; + --md-light-secondary: #d32f2f; + --md-light-on-secondary: #ffffff; + --md-light-secondary-container: #ffdad5; + --md-light-on-secondary-container: #410001; + --md-light-tertiary: #ff8a65; + --md-light-on-tertiary: #ffffff; + --md-light-tertiary-container: #ffdacc; + --md-light-on-tertiary-container: #410002; + --md-light-surface: #fffbff; + --md-light-on-surface: #1c1b1f; + --md-light-surface-variant: #e7e0ec; + --md-light-on-surface-variant: #49454f; + --md-light-surface-container-lowest: #ffffff; + --md-light-surface-container-low: #f7f2fa; + --md-light-surface-container: #f3edf7; + --md-light-surface-container-high: #ece8f0; + --md-light-surface-container-highest: #e6e1e5; + --md-light-error: #b3261e; + --md-light-on-error: #ffffff; + --md-light-error-container: #f9dedc; + --md-light-on-error-container: #410e0b; + --md-light-outline: #79747e; + --md-light-outline-variant: #c4c7c5; + --md-light-inverse-surface: #313033; + --md-light-inverse-on-surface: #f4eff4; + --md-light-scrim: rgba(0, 0, 0, 0.4); + --md-light-shadow: rgba(0, 0, 0, 0.08); + --md-light-success: #386a20; + --md-light-on-success: #ffffff; + --md-light-success-container: #b6f397; + --md-light-on-success-container: #0c2000; + --md-light-warning: #825500; + --md-light-on-warning: #ffffff; + --md-light-warning-container: #ffddb0; + --md-light-on-warning-container: #281800; + --md-light-info: #0055d5; + --md-light-on-info: #ffffff; + --md-light-info-container: #dae1ff; + --md-light-on-info-container: #001847; + + /* Dark Theme (your existing variables) */ + --md-dark-primary: #f44336; + --md-dark-on-primary: #ffffff; + --md-dark-primary-container: #ffdad6; + --md-dark-on-primary-container: #410002; + --md-dark-secondary: #d32f2f; + --md-dark-on-secondary: #ffffff; + --md-dark-secondary-container: #ffdad5; + --md-dark-on-secondary-container: #410001; + --md-dark-tertiary: #ff8a65; + --md-dark-on-tertiary: #ffffff; + --md-dark-tertiary-container: #ffdacc; + --md-dark-on-tertiary-container: #410002; + --md-dark-surface: #1c1b1f; + --md-dark-on-surface: #e6e1e5; + --md-dark-surface-container-lowest: #0f0d13; + --md-dark-surface-container-low: #1d1b20; + --md-dark-surface-container: #211f26; + --md-dark-surface-container-high: #2b2930; + --md-dark-surface-container-highest: #36343b; + --md-dark-error: #b3261e; + --md-dark-on-error: #ffffff; + --md-dark-error-container: #93000a; + --md-dark-on-error-container: #ffdad5; + --md-dark-outline: #79747e; + --md-dark-outline-variant: #49454f; + --md-dark-inverse-surface: #e6e1e5; + --md-dark-inverse-on-surface: #1c1b1f; + --md-dark-scrim: rgba(0, 0, 0, 0.6); + --md-dark-shadow: rgba(0, 0, 0, 0.15); + --md-dark-success: #9bd880; + --md-dark-on-success: #193800; + --md-dark-success-container: #275000; + --md-dark-on-success-container: #b6f397; + --md-dark-warning: #ffba47; + --md-dark-on-warning: #422b00; + --md-dark-warning-container: #5f3f00; + --md-dark-on-warning-container: #ffddb0; + --md-dark-info: #b3c5ff; + --md-dark-on-info: #002a77; + --md-dark-info-container: #003ea7; + --md-dark-on-info-container: #dae1ff; + + /* System states */ + --md-state-hover-light-opacity: 0.08; + --md-state-focus-light-opacity: 0.12; + --md-state-pressed-light-opacity: 0.12; + --md-state-dragged-light-opacity: 0.16; + --md-state-hover-dark-opacity: 0.08; + --md-state-focus-dark-opacity: 0.12; + --md-state-pressed-dark-opacity: 0.12; + --md-state-dragged-dark-opacity: 0.16; + + /* Elevation */ + --md-elevation-1: 0px 1px 3px 1px rgba(0, 0, 0, 0.15); + --md-elevation-2: 0px 2px 6px 2px rgba(0, 0, 0, 0.15); + --md-elevation-3: 0px 4px 8px 3px rgba(0, 0, 0, 0.15); + --md-elevation-4: 0px 6px 10px 4px rgba(0, 0, 0, 0.15); + --md-elevation-5: 0px 8px 12px 6px rgba(0, 0, 0, 0.15); + + /* Shapes */ + --md-radius-small: 8px; + --md-radius-medium: 12px; + --md-radius-large: 16px; + --md-radius-extra-large: 28px; + + /* Typography */ + --md-typescale-display-large-size: 57px; + --md-typescale-display-medium-size: 45px; + --md-typescale-display-small-size: 36px; + --md-typescale-headline-large-size: 32px; + --md-typescale-headline-medium-size: 28px; + --md-typescale-headline-small-size: 24px; + --md-typescale-title-large-size: 22px; + --md-typescale-title-medium-size: 16px; + --md-typescale-title-small-size: 14px; + --md-typescale-body-large-size: 16px; + --md-typescale-body-medium-size: 14px; + --md-typescale-body-small-size: 12px; + --md-typescale-label-large-size: 14px; + --md-typescale-label-medium-size: 12px; + --md-typescale-label-small-size: 11px; + + /* Exo 2 Typography */ + /* @import url("https://fonts.googleapis.com/css2?family=Exo+2:wght@300;400;500;600;700&display=swap"); */ + --font-primary: "Exo 2", sans-serif; + --font-weight-regular: 400; + --font-weight-medium: 500; + --font-weight-bold: 700; +} + +/* Default to light theme */ +:root { + --md-primary: var(--md-light-primary); + --md-on-primary: var(--md-light-on-primary); + --md-primary-container: var(--md-light-primary-container); + --md-on-primary-container: var(--md-light-on-primary-container); + --md-secondary: var(--md-light-secondary); + --md-on-secondary: var(--md-light-on-secondary); + --md-secondary-container: var(--md-light-secondary-container); + --md-on-secondary-container: var(--md-light-on-secondary-container); + --md-tertiary: var(--md-light-tertiary); + --md-on-tertiary: var(--md-light-on-tertiary); + --md-tertiary-container: var(--md-light-tertiary-container); + --md-on-tertiary-container: var(--md-light-on-tertiary-container); + --md-surface: var(--md-light-surface); + --md-on-surface: var(--md-light-on-surface); + --md-surface-variant: var(--md-light-surface-variant); + --md-on-surface-variant: var(--md-light-on-surface-variant); + --md-surface-container-lowest: var(--md-light-surface-container-lowest); + --md-surface-container-low: var(--md-light-surface-container-low); + --md-surface-container: var(--md-light-surface-container); + --md-surface-container-high: var(--md-light-surface-container-high); + --md-surface-container-highest: var(--md-light-surface-container-highest); + --md-error: var(--md-light-error); + --md-on-error: var(--md-light-on-error); + --md-error-container: var(--md-light-error-container); + --md-on-error-container: var(--md-light-on-error-container); + --md-outline: var(--md-light-outline); + --md-outline-variant: var(--md-light-outline-variant); + --md-inverse-surface: var(--md-light-inverse-surface); + --md-inverse-on-surface: var(--md-light-inverse-on-surface); + --md-scrim: var(--md-light-scrim); + --md-shadow: var(--md-light-shadow); + --md-success: var(--md-light-success); + --md-on-success: var(--md-light-on-success); + --md-success-container: var(--md-light-success-container); + --md-on-success-container: var(--md-light-on-success-container); + --md-warning: var(--md-light-warning); + --md-on-warning: var(--md-light-on-warning); + --md-warning-container: var(--md-light-warning-container); + --md-on-warning-container: var(--md-light-on-warning-container); + --md-info: var(--md-light-info); + --md-on-info: var(--md-light-on-info); + --md-info-container: var(--md-light-info-container); + --md-on-info-container: var(--md-light-on-info-container); +} + +/* Dark theme class */ +.dark-theme { + --md-primary: var(--md-dark-primary); + --md-on-primary: var(--md-dark-on-primary); + --md-primary-container: var(--md-dark-primary-container); + --md-on-primary-container: var(--md-dark-on-primary-container); + --md-secondary: var(--md-dark-secondary); + --md-on-secondary: var(--md-dark-on-secondary); + --md-secondary-container: var(--md-dark-secondary-container); + --md-on-secondary-container: var(--md-dark-on-secondary-container); + --md-tertiary: var(--md-dark-tertiary); + --md-on-tertiary: var(--md-dark-on-tertiary); + --md-tertiary-container: var(--md-dark-tertiary-container); + --md-on-tertiary-container: var(--md-dark-on-tertiary-container); + --md-surface: var(--md-dark-surface); + --md-on-surface: var(--md-dark-on-surface); + --md-surface-container-lowest: var(--md-dark-surface-container-lowest); + --md-surface-container-low: var(--md-dark-surface-container-low); + --md-surface-container: var(--md-dark-surface-container); + --md-surface-container-high: var(--md-dark-surface-container-high); + --md-surface-container-highest: var(--md-dark-surface-container-highest); + --md-error: var(--md-dark-error); + --md-on-error: var(--md-dark-on-error); + --md-error-container: var(--md-dark-error-container); + --md-on-error-container: var(--md-dark-on-error-container); + --md-outline: var(--md-dark-outline); + --md-outline-variant: var(--md-dark-outline-variant); + --md-inverse-surface: var(--md-dark-inverse-surface); + --md-inverse-on-surface: var(--md-dark-inverse-on-surface); + --md-scrim: var(--md-dark-scrim); + --md-shadow: var(--md-dark-shadow); + --md-success: var(--md-dark-success); + --md-on-success: var(--md-dark-on-success); + --md-success-container: var(--md-dark-success-container); + --md-on-success-container: var(--md-dark-on-success-container); + --md-warning: var(--md-dark-warning); + --md-on-warning: var(--md-dark-on-warning); + --md-warning-container: var(--md-dark-warning-container); + --md-on-warning-container: var(--md-dark-on-warning-container); + --md-info: var(--md-dark-info); + --md-on-info: var(--md-dark-on-info); + --md-info-container: var(--md-dark-info-container); + --md-on-info-container: var(--md-dark-on-info-container); +} diff --git a/html/js/editor.js b/html/js/editor.js index 785c886..fb34337 100644 --- a/html/js/editor.js +++ b/html/js/editor.js @@ -1,579 +1,329 @@ -// Main data store -let configData = {}; -let currentFile = "config"; -let currentEditItem = null; -let editPath = null; +// editor.js – NUI-based config editor with type-aware, recursive editing -// Elements -const dataContainer = document.getElementById("data-container"); -const searchInput = document.getElementById("search-input"); -const saveBtn = document.getElementById("save-btn"); -const closeBtn = document.getElementById("close-btn"); -const editModal = document.getElementById("edit-modal"); -const closeModalBtn = document.getElementById("close-modal-btn"); -const saveItemBtn = document.getElementById("save-item-btn"); -const cancelEditBtn = document.getElementById("cancel-edit-btn"); -const editorForm = document.getElementById("editor-form"); -const fileListItems = document.querySelectorAll(".file-list li"); -const fabButton = document.getElementById("add-item-fab"); -let fileInput; +// State +let allData = {}; +let currentFile = null; -// Initialize -document.addEventListener("DOMContentLoaded", () => { - // Add event listeners - searchInput.addEventListener("input", filterData); - saveBtn.addEventListener("click", saveChanges); - closeBtn.addEventListener("click", closeEditor); - closeModalBtn.addEventListener("click", closeModal); - saveItemBtn.addEventListener("click", saveItemChanges); - cancelEditBtn.addEventListener("click", closeModal); - fabButton.addEventListener("click", addItemModal); - - fileListItems.forEach((item) => { - item.addEventListener("click", () => { - // Update active state - fileListItems.forEach((i) => i.classList.remove("active")); - item.classList.add("active"); - - // Change current file - currentFile = item.dataset.file; - - // Load file data - in a real implementation, this would trigger a fetch to the server - // For now, we'll just use the data we already have - currentFile = item.dataset.file; - renderData(); - }); - }); - - // Listen for NUI messages from FiveM - window.addEventListener("message", function (event) { - const data = event.data; - - if (data.action === "populateData") { - // Expect data.data to be an object with each file's data - configData = data.data; - renderData(); - } - }); -}); - -const templates = { - jobs: { - defaultDuty: false, - offDutyPay: false, - type: "", - label: "", - grades: [ - { - name: "", - isboss: false, - payment: 0, - }, - ], - }, - gangs: { - label: "", - grades: [ - { - name: "", - isboss: false, - level: 0, - }, - ], - }, - items: { - unique: false, - shouldClose: false, - useable: false, - name: "", - label: "", - weight: 0, - type: "", - image: "", - description: "", - ammotype: "", - }, - vehicles: { - brand: "", - price: 0, - name: "", - category: "", - model: "", - type: "", - shop: ["pdm"], - }, - weapons: { - name: "", - label: "", - damageReason: "", - weapontype: "", - ammotype: "", - }, -}; - -function addItemModal() { - const modal = document.getElementById("edit-modal"); - const editorForm = document.getElementById("editor-form"); - - // Clear the form and set it up for adding a new item - editorForm.innerHTML = ""; // Clear any existing content - modal.classList.remove("hidden"); // Show the modal - - // Get the template for the current category - const template = templates[currentFile] || {}; - - // Add fields for the new item based on the template - Object.entries(template).forEach(([key, value]) => { - const formGroup = document.createElement("div"); - formGroup.className = "form-group"; - - const label = document.createElement("label"); - label.textContent = key; - formGroup.appendChild(label); - - if (typeof value === "boolean" || typeof value === "string" || typeof value === "number") { - const field = createInputField({ - key, - value, - onChange: () => {}, // No need to handle it now, we'll read from DOM on save - }); - formGroup.appendChild(field.querySelector("input")); - } else if (Array.isArray(value)) { - const arrayField = createArrayField({ - key, - array: value, - onItemChange: () => {}, // Again, no live tracking needed - onItemRemove: (index) => { - value.splice(index, 1); - addItemModal(); // re-render modal - }, - onItemAdd: () => { - // Clone first item or fallback - if (value.length > 0 && typeof value[0] === "object") { - const newItem = {}; - Object.entries(value[0]).forEach(([k, v]) => { - newItem[k] = typeof v === "number" ? 0 : typeof v === "boolean" ? false : ""; - }); - value.push(newItem); - } else { - value.push(""); - } - addItemModal(); // re-render modal - }, - }); - arrayField.id = `new-item-${key}`; - formGroup.appendChild(arrayField); - } else { - const input = document.createElement("input"); - input.type = "text"; - input.className = "form-control"; - input.value = value; - formGroup.appendChild(input); - } - - editorForm.appendChild(formGroup); - }); - - // Add Save and Cancel buttons - const saveButton = document.getElementById("save-item-btn"); - saveButton.onclick = saveNewItem; - - const cancelButton = document.getElementById("cancel-edit-btn"); - cancelButton.onclick = () => { - modal.classList.add("hidden"); // Hide the modal - }; -} - -function saveNewItem() { - const template = templates[currentFile] || {}; - const newItem = {}; - - // Populate the new item based on the template - Object.entries(template).forEach(([key, value]) => { - if (Array.isArray(value)) { - // Handle arrays - const arrayContainer = document.getElementById(`new-item-${key}`); - const arrayItems = Array.from(arrayContainer.querySelectorAll(".array-item")); - newItem[key] = arrayItems.map((itemGroup) => { - const item = {}; - Array.from(itemGroup.querySelectorAll("input")).forEach((input) => { - const itemKey = input.dataset.itemKey; - item[itemKey] = input.type === "number" ? parseFloat(input.value) || 0 : input.value; - }); - return item; - }); - } else { - // Handle simple values - const input = document.getElementById(`new-item-${key}`); - if (input) { - newItem[key] = input.type === "checkbox" ? input.checked : input.value; - } - } - }); - - // Add the new item to the current category - if (!configData[currentFile]) { - configData[currentFile] = {}; - } - const itemName = document.getElementById("new-item-name").value.trim(); - if (!itemName) { - alert("Please provide a name for the new item."); - return; - } - configData[currentFile][itemName] = newItem; - - // Update the UI +// Select a file and render its data +function selectFile(file, liElement) { + document.querySelectorAll(".file-list li").forEach((li) => li.classList.remove("active")); + liElement.classList.add("active"); + currentFile = file; renderData(); - - // Close the modal - document.getElementById("edit-modal").classList.add("hidden"); } -// Render data in the container +// Render JSON data for the current file function renderData() { - dataContainer.innerHTML = ""; - - if (!configData[currentFile]) { - dataContainer.innerHTML = "
No data available for this file.
"; + if (!currentFile) return; + const data = allData[currentFile]; + if (!data) { + console.error(`No data found for ${currentFile}`); return; } + buildEditor(data); +} - const data = configData[currentFile]; - const searchText = searchInput.value.toLowerCase(); - - // For object-based data (like jobs) +// Build main editor UI from data object +function buildEditor(data) { + const container = document.getElementById("data-container"); + container.innerHTML = ""; Object.entries(data).forEach(([key, value]) => { - // If there's a search term, filter the data - if (searchText && !key.toLowerCase().includes(searchText) && !JSON.stringify(value).toLowerCase().includes(searchText)) { - return; - } - const card = document.createElement("div"); card.className = "data-card"; card.dataset.key = key; + card.innerHTML = `