A lot of things

This commit is contained in:
2026-06-16 16:21:43 +02:00
parent 4f9f6c63b3
commit 7e1e26f20b
126 changed files with 7917 additions and 204 deletions

View File

@@ -0,0 +1,85 @@
import { RomPatcher } from './RomPatcher.js';
window.PlayOnline = function( initialPatches = {}, emulatorJsConfig = {} ){
const parent = RomPatcher( initialPatches );
return {
...parent,
currentBlobUrl: null,
emuConfig: emulatorJsConfig,
launchGame: false,
init(){
parent.init({
language: 'en',
requireValidation: false,
onpatch: this.handlePatchedRomFile.bind(this),
});
},
cleanEmulatorJsVars() {
['EJS_player','EJS_core','EJS_gameUrl','EJS_pathtodata',
'EJS_startOnLoaded','EJS_threads']
.forEach(k => delete window[k]);
},
prepareEmulatorJs(){
window.EJS_player = '#game';
window.EJS_core = this.emuConfig.core;
window.EJS_gameUrl = this.currentBlobUrl;
window.EJS_pathtodata = "https://cdn.emulatorjs.org/stable/data/";
window.EJS_startOnLoaded = true;
window.EJS_threads = this.emuConfig.threads ?? false;
},
launchEmulatorJs(){
if(!this.currentBlobUrl){
console.error("EmulatorJS: Empty Blob field");
return;
}
console.log(this.currentBlobUrl);
this.cleanEmulatorJsVars();
this.prepareEmulatorJs();
const script = document.createElement('script');
script.id = 'ejs-loader';
script.src = 'https://cdn.emulatorjs.org/stable/data/loader.js';
document.body.appendChild(script);
this.launchGame = true;
},
/**
* @param {BinFile} patchedRomFile
*/
handlePatchedRomFile( patchedRomFile ){
patchedRomFile.save = function(){
// Remove save.
return;
}
const u8 = patchedRomFile._u8array;
if( !u8 || u8.byteLength === 0 ){
console.error("Patch error: Empty ROM file");
return;
}
if(this.currentBlobUrl){
URL.revokeObjectURL(this.currentBlobUrl);
}
const blob = new Blob([u8], { type: 'application/octet-stream' });
this.currentBlobUrl = URL.createObjectURL(blob);
this.launchEmulatorJs()
}
}
}

View File

@@ -1,4 +1,4 @@
export function RomPatcher( initialPatches = {} ) {
export const RomPatcher = function( initialPatches = {} ) {
let patchesArray = [];
if (initialPatches) {
@@ -39,9 +39,9 @@ export function RomPatcher( initialPatches = {} ) {
patchesData: patchesArray,
hasEmbedded: patchesArray.length > 0,
init() {
init( config = {language: 'en', requireValidation: false} ) {
const CONFIG = {language: 'en', requireValidation: false};
const CONFIG = config;
if (!RomPatcherWeb.isInitialized()){
if (this.hasEmbedded) {
@@ -112,3 +112,7 @@ export function RomPatcher( initialPatches = {} ) {
}
}
}
window.RomPatcher = RomPatcher;

View File

@@ -2,6 +2,10 @@
export const CHUNK_SIZE = 8192;
const PATCH_EXTENSIONS = new Set([
'ips', 'bps', 'ups', 'aps', 'ppf', 'xdelta', "zip"
]);
/**
* An uploaded file instance.
* Create a new file data.
@@ -12,6 +16,8 @@ export const CHUNK_SIZE = 8192;
*/
export function FSFileData(name, totalChunks, rawFile ) {
const extension = name.split('.').pop().toLowerCase();
return {
/**
@@ -66,6 +72,8 @@ export function FSFileData(name, totalChunks, rawFile ) {
*/
state: 'public',
can_be_online_patched: PATCH_EXTENSIONS.has(extension),
/**
* If the online patcher is enabled
*/
@@ -76,6 +84,21 @@ export function FSFileData(name, totalChunks, rawFile ) {
*/
meta_secondary_online_patcher: false,
/**
* If this patch can be played online.
*/
meta_play_online: false,
/**
* Selected core for play online
*/
meta_play_online_core: null,
/**
* If the threads are enabled for playing online.
*/
meta_play_online_threads: null,
/**
* Look if this file is currently uploading.
* @returns {boolean}
@@ -164,6 +187,6 @@ export function FSFileData(name, totalChunks, rawFile ) {
}
}
}
}
}

View File

@@ -12,6 +12,8 @@ export function GalleryManager() {
*/
images: [],
dragSrcI: null,
/**
* Forward to this.images.length
* @returns {number}
@@ -123,6 +125,25 @@ export function GalleryManager() {
handleRemoveFile(index){
this.images[index].handleRemoveFile(null);
this.images.splice(index, 1);
},
dragStart(index){
this.dragSrcI = index;
},
dragOver(e, index){
e.preventDefault();
if( this.dragSrcI === null || this.dragSrcI === index )
return;
const moved = this.images.splice(this.dragSrcI, 1)[0];
this.images.splice(index, 0, moved);
this.dragSrcI = index;
},
dragEnd(){
this.dragSrcI = null;
}
}
}

View File

@@ -7,7 +7,6 @@ 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
@@ -15,7 +14,7 @@ import {RomPatcher} from "./RomPatcher.js";
* @return {string|null}
*/
window.getConfig = function( key ){
return document.querySelector('meta[name="config-' + key + '"]').getAttribute('content') ?? null;
return document.querySelector('meta[name="config-' + key + '"]')?.getAttribute('content') ?? null;
}
// Lucide icons.
@@ -44,6 +43,3 @@ Alpine.store('conversations', conversations() );
// Settings
Alpine.store('settings', settings() );
// ROMPatcher
window.RomPatcher = RomPatcher;

View File

@@ -0,0 +1,169 @@
import { GalleryManager } from "./SubmissionsClass/GalleryManager.js";
/**
* If there is some server side errors.
* We may need reload some things.
* @type {boolean}
*/
const SERVER_SIDE_ERRORS = document.querySelector('meta[name="submission-has-errors"]')?.content === '1';
/**
* Object map of errors messages
* @type {Object<string,string>}
*/
const ERROR_TABLE = {
noDescription: "Please provide a description.",
noGalleryImages: "Please select at least a gallery image.",
isSubmitting: "The entry is already during submission."
}
window.GalleryManager = GalleryManager;
/**
* Verify if an EasyMDE field is filled.
*
* @param {string} fieldName
* @returns {boolean}
*/
function verifyMDE( fieldName ){
const textarea = document.querySelector('#field_' + fieldName);
if( textarea && textarea.value.trim().length > 0 ) {
return true;
}
const field = window['mde_' + fieldName] || null;
return field && typeof field.value === 'function' && field.value().trim().length > 0;
}
window.SubmissionVerifications = {
/**
* Verify if the description field has at least one character.
* @returns {boolean}
*/
step1_VerifyDescription: function(){
return verifyMDE('description');
},
/**
* Verify if at least one image is uploaded in the gallery.
* @param element this.$el
* @return {boolean}
*/
step2_verifyGallery: function( element){
let GalleryData = element.querySelector('[x-data="GalleryManager()"]');
GalleryData = GalleryData ? Alpine.$data(GalleryData) : null;
if( ! GalleryData ){
return false;
}
return GalleryData.number > 0 && GalleryData.allUploaded;
}
}
/**
* Handle entire submission process.
*/
window.NewsSubmission = function(){
return {
/**
* If the script is during a try of submission process.
* @type {boolean}
*/
duringSubmissionProcess: false,
/**
* Error checked.
* @type {string|null}
*/
errorKey: null,
/**
* Return error message.
* @return {string}
*/
get errorMessage(){
return ERROR_TABLE[this.errorKey] ?? "Unknown error";
},
init(){
},
/**
* Do each form verifications.
* Update also this.errorKey.
*
* @returns {boolean}
*/
verifyForm(){
console.log( "Step 1" );
if( !SubmissionVerifications.step1_VerifyDescription() ){
this.errorKey = "noDescription";
return false;
}
console.log( "Step 2" );
if( !SubmissionVerifications.step2_verifyGallery( this.$el )){
this.errorKey = "noGalleryImages";
return false;
}
return true;
},
/**
* Scroll to the specific error field.
*/
scrollToError(){
const refMap = {
noDescription: 'descriptionField',
noGalleryImages: 'gallery-field',
isSubmitting: 'submitButton'
};
const target = this.$refs[refMap[this.errorKey]]
|| this.$el.querySelector('.upload-list')
|| this.$el.querySelector('.form-upload');
if (target) {
target.scrollIntoView({behavior: 'smooth', block: 'center'});
return;
}
},
/**
* If you want to submit the form.
* @param {Event} e
*/
submitForm( e ){
if( this.duringSubmissionProcess )
return; // Don't submit two times.
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();
this.duringSubmissionProcess = false;
return;
}
e.target.submit();
}
}
}

View File

@@ -14,20 +14,15 @@ export default function settings() {
*/
xfUrls: {},
/**
* @type {number[]}
*/
entriesPerPage: [ 12, 30, 48 ],
/**
* @type {string}
*/
currentTheme: Cookies.get("theme") ?? 'default',
currentTheme: 'default',
/**
* @type {number}
* @type {list|null}
*/
currentEntriesPerPage: Cookies.get("entries_per_page") ?? 30,
currentActivityFilters: null,
/**
*
@@ -43,7 +38,7 @@ export default function settings() {
this.currentTheme = newTheme;
document.documentElement.classList.toggle('light-mode', this.currentTheme === 'alternate');
Cookies.set('theme', this.currentTheme, { expires: 365, path: '/', domain: window.getConfig('session-domain') } );
// Cookies.set('theme', this.currentTheme, { expires: 365, path: '/', domain: window.getConfig('session-domain') } );
this.syncXF();
},
@@ -62,22 +57,48 @@ export default function settings() {
this.themeChanged(this.currentTheme === 'default' ? 'alternate' : 'default');
},
/**
*
* @param n
*/
entriesPerPageChanged( n ){
if( !this.entriesPerPage.includes(n) )
return;
this.entriesPerPage = n;
Cookies.set('entries_per_page', this.entriesPerPage, { expires: 365, path: '/', domain: window.getConfig('session-domain') } );
if( window.Livewire ){
Livewire.dispatch('entriesPerPageChanged', {n});
}
},
open(){ this.start = !this.start; },
close(){ this.start = false; },
async toggleActivityFilter( type ){
if( this.currentActivityFilters === null )
return;
const i = this.currentActivityFilters.indexOf( type );
if( i !== -1 && this.currentActivityFilters.length === 1)
return;
if( i === -1 )
this.currentActivityFilters.push( type );
else
this.currentActivityFilters.splice( i, 1 );
Cookies.set( 'activity_filters', JSON.stringify(this.currentActivityFilters), { expires: 365, path: '/', domain: window.getConfig('session-domain') } );
await this.syncTimeline();
},
async syncTimeline(){
const tl = document.getElementById('activity-timeline');
if( !tl )
return;
tl.style.opacity = 0.5;
const params = this.currentActivityFilters.join(',');
const response = await fetch(`/api/dynamic/activity/feed?filters=${params}`);
const data = await response.json();
if( !data.html )
return;
tl.innerHTML = data.html;
tl.style.opacity = 1;
refreshIcons(tl);
}
}
}

View File

@@ -38,6 +38,7 @@ const ERROR_TABLE = {
* @constructor
*/
const SECTION = () => document.querySelector("meta[name='fs-section']")?.content ?? '';
const CSRF = () => document.querySelector("meta[name='csrf-token']")?.content ?? '';
window.FSUploader = FSUploader;
window.HashesManager = HashesManager;
@@ -403,6 +404,31 @@ window.Submission = function(){
}
e.target.submit();
},
async requestFeatured( entryId ){
const csrf = CSRF();
const response = await fetch(`/api/entry/${entryId}/featured`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrf
}
});
const json = await response.json();
const entry_featured_button = document.querySelector('#entry-featured-button');
const entry_featured_body = document.querySelector('#entry-featured-body');
if( json.success ){
entry_featured_body.innerHTML = '<p>Request submitted</p>';
entry_featured_button.style.display = 'none';
} else {
entry_featured_body.innerHTML = '<p>Request failed. Please refresh the page and retry.</p>';
entry_featured_button.style.display = 'none';
}
}
}