diff --git a/gaseous-server/Classes/Metadata/Covers.cs b/gaseous-server/Classes/Metadata/Covers.cs index 08d22c3..ef4c2ec 100644 --- a/gaseous-server/Classes/Metadata/Covers.cs +++ b/gaseous-server/Classes/Metadata/Covers.cs @@ -7,7 +7,7 @@ using Microsoft.CodeAnalysis.Elfie.Model.Strings; namespace gaseous_server.Classes.Metadata { - public class Covers + public class Covers { const string fieldList = "fields alpha_channel,animated,checksum,game,game_localization,height,image_id,url,width;"; @@ -70,7 +70,7 @@ namespace gaseous_server.Classes.Metadata returnValue = await GetObjectFromServer(WhereClause, ImagePath); Storage.NewCacheValue(returnValue); forceImageDownload = true; - break; + break; case Storage.CacheStatus.Expired: try { @@ -135,6 +135,6 @@ namespace gaseous_server.Classes.Metadata return result; } - } + } } diff --git a/gaseous-server/Classes/Metadata/Games.cs b/gaseous-server/Classes/Metadata/Games.cs index 18e4e2b..7e1fbc4 100644 --- a/gaseous-server/Classes/Metadata/Games.cs +++ b/gaseous-server/Classes/Metadata/Games.cs @@ -5,8 +5,8 @@ using IGDB.Models; namespace gaseous_server.Classes.Metadata { - public class Games - { + public class Games + { const string fieldList = "fields age_ratings,aggregated_rating,aggregated_rating_count,alternative_names,artworks,bundles,category,checksum,collection,collections,cover,created_at,dlcs,expanded_games,expansions,external_games,first_release_date,follows,forks,franchise,franchises,game_engines,game_localizations,game_modes,genres,hypes,involved_companies,keywords,language_supports,multiplayer_modes,name,parent_game,platforms,player_perspectives,ports,rating,rating_count,release_dates,remakes,remasters,screenshots,similar_games,slug,standalone_expansions,status,storyline,summary,tags,themes,total_rating,total_rating_count,updated_at,url,version_parent,version_title,videos,websites;"; public Games() @@ -15,9 +15,9 @@ namespace gaseous_server.Classes.Metadata } public class InvalidGameId : Exception - { + { public InvalidGameId(long Id) : base("Unable to find Game by id " + Id) - {} + { } } public static Game? GetGame(long Id, bool getAllMetadata, bool followSubGames, bool forceRefresh) @@ -125,17 +125,17 @@ namespace gaseous_server.Classes.Metadata private static void UpdateSubClasses(Game Game, bool getAllMetadata, bool followSubGames, bool forceRefresh) { // required metadata - if (Game.Cover != null) - { - try - { - Cover GameCover = Covers.GetCover(Game.Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game), forceRefresh); - } - catch (Exception ex) - { - Logging.Log(Logging.LogType.Critical, "Game Metadata", "Unable to fetch cover artwork.", ex); - } - } + // if (Game.Cover != null) + // { + // try + // { + // Cover GameCover = Covers.GetCover(Game.Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game), forceRefresh); + // } + // catch (Exception ex) + // { + // Logging.Log(Logging.LogType.Critical, "Game Metadata", "Unable to fetch cover artwork.", ex); + // } + // } if (Game.Genres != null) { @@ -285,7 +285,7 @@ namespace gaseous_server.Classes.Metadata { try { - Screenshot GameScreenshot = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game), forceRefresh); + Screenshot GameScreenshot = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game), forceRefresh); } catch (Exception ex) { @@ -347,7 +347,7 @@ namespace gaseous_server.Classes.Metadata // get missing metadata from parent if this is a port if (result.Category == Category.Port) - { + { if (result.Summary == null) { if (result.ParentGame != null) @@ -364,7 +364,7 @@ namespace gaseous_server.Classes.Metadata return result; } - + public static void AssignAllGamesToPlatformIdZero() { Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); @@ -428,7 +428,7 @@ namespace gaseous_server.Classes.Metadata } string sql = "SELECT Game.Id, Game.`Name`, Game.Slug, Relation_Game_Platforms.PlatformsId AS PlatformsId, Game.Summary FROM gaseous.Game JOIN Relation_Game_Platforms ON Game.Id = Relation_Game_Platforms.GameId WHERE " + whereClause + ";"; - + // get Game metadata Game[]? results = new Game[0]; @@ -439,7 +439,8 @@ namespace gaseous_server.Classes.Metadata DataTable data = db.ExecuteCMD(sql, dbDict); foreach (DataRow row in data.Rows) { - Game game = new Game{ + Game game = new Game + { Id = (long)row["Id"], Name = (string)Common.ReturnValueIfNull(row["Name"], ""), Slug = (string)Common.ReturnValueIfNull(row["Slug"], ""), @@ -476,12 +477,12 @@ namespace gaseous_server.Classes.Metadata searchBody = "where platforms = (" + PlatformId + ") & name ~ \"" + SearchString + "\";"; break; } - + // check search cache Game[]? games = Communications.GetSearchCache(searchFields, searchBody); if (games == null) - { + { // cache miss // get Game metadata Communications comms = new Communications(); @@ -513,7 +514,7 @@ namespace gaseous_server.Classes.Metadata foreach (DataRow row in data.Rows) { KeyValuePair valuePair = new KeyValuePair((long)row["PlatformId"], (string)row["Name"]); - platforms.Add(valuePair); + platforms.Add(valuePair); } return platforms; @@ -533,7 +534,7 @@ namespace gaseous_server.Classes.Metadata { } - + public MinimalGameItem(Game gameObject) { this.Id = gameObject.Id; diff --git a/gaseous-server/Classes/Metadata/Storage.cs b/gaseous-server/Classes/Metadata/Storage.cs index d95b6c0..7005889 100644 --- a/gaseous-server/Classes/Metadata/Storage.cs +++ b/gaseous-server/Classes/Metadata/Storage.cs @@ -7,19 +7,19 @@ using Microsoft.Extensions.Caching.Memory; namespace gaseous_server.Classes.Metadata { - public class Storage - { - public enum CacheStatus - { - NotPresent, - Current, - Expired - } + public class Storage + { + public enum CacheStatus + { + NotPresent, + Current, + Expired + } - public static CacheStatus GetCacheStatus(string Endpoint, string Slug) - { + public static CacheStatus GetCacheStatus(string Endpoint, string Slug) + { return _GetCacheStatus(Endpoint, "slug", Slug); - } + } public static CacheStatus GetCacheStatus(string Endpoint, long Id) { @@ -47,124 +47,124 @@ namespace gaseous_server.Classes.Metadata } private static CacheStatus _GetCacheStatus(string Endpoint, string SearchField, object SearchValue) - { + { Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); - string sql = "SELECT lastUpdated FROM " + Endpoint + " WHERE " + SearchField + " = @" + SearchField; + string sql = "SELECT lastUpdated FROM " + Endpoint + " WHERE " + SearchField + " = @" + SearchField; - Dictionary dbDict = new Dictionary(); - dbDict.Add("Endpoint", Endpoint); - dbDict.Add(SearchField, SearchValue); - - DataTable dt = db.ExecuteCMD(sql, dbDict); - if (dt.Rows.Count == 0) - { - // no data stored for this item, or lastUpdated - return CacheStatus.NotPresent; - } - else - { - DateTime CacheExpiryTime = DateTime.UtcNow.AddHours(-168); - if ((DateTime)dt.Rows[0]["lastUpdated"] < CacheExpiryTime) - { - return CacheStatus.Expired; - } - else - { - return CacheStatus.Current; - } - } + Dictionary dbDict = new Dictionary(); + dbDict.Add("Endpoint", Endpoint); + dbDict.Add(SearchField, SearchValue); + + DataTable dt = db.ExecuteCMD(sql, dbDict); + if (dt.Rows.Count == 0) + { + // no data stored for this item, or lastUpdated + return CacheStatus.NotPresent; + } + else + { + DateTime CacheExpiryTime = DateTime.UtcNow.AddHours(-168); + if ((DateTime)dt.Rows[0]["lastUpdated"] < CacheExpiryTime) + { + return CacheStatus.Expired; + } + else + { + return CacheStatus.Current; + } + } } - public static void NewCacheValue(object ObjectToCache, bool UpdateRecord = false) - { - // get the object type name - string ObjectTypeName = ObjectToCache.GetType().Name; + public static void NewCacheValue(object ObjectToCache, bool UpdateRecord = false) + { + // get the object type name + string ObjectTypeName = ObjectToCache.GetType().Name; - // build dictionary - string objectJson = Newtonsoft.Json.JsonConvert.SerializeObject(ObjectToCache); - Dictionary objectDict = Newtonsoft.Json.JsonConvert.DeserializeObject>(objectJson); + // build dictionary + string objectJson = Newtonsoft.Json.JsonConvert.SerializeObject(ObjectToCache); + Dictionary objectDict = Newtonsoft.Json.JsonConvert.DeserializeObject>(objectJson); objectDict.Add("dateAdded", DateTime.UtcNow); objectDict.Add("lastUpdated", DateTime.UtcNow); - // generate sql - string fieldList = ""; - string valueList = ""; - string updateFieldValueList = ""; - foreach (KeyValuePair key in objectDict) - { - if (fieldList.Length > 0) - { - fieldList = fieldList + ", "; - valueList = valueList + ", "; - } - fieldList = fieldList + key.Key; - valueList = valueList + "@" + key.Key; - if ((key.Key != "id") && (key.Key != "dateAdded")) + // generate sql + string fieldList = ""; + string valueList = ""; + string updateFieldValueList = ""; + foreach (KeyValuePair key in objectDict) + { + if (fieldList.Length > 0) + { + fieldList = fieldList + ", "; + valueList = valueList + ", "; + } + fieldList = fieldList + key.Key; + valueList = valueList + "@" + key.Key; + if ((key.Key != "id") && (key.Key != "dateAdded")) { if (updateFieldValueList.Length > 0) { updateFieldValueList = updateFieldValueList + ", "; } updateFieldValueList += key.Key + " = @" + key.Key; - } + } - // check property type - Type objectType = ObjectToCache.GetType(); - if (objectType != null) - { - PropertyInfo objectProperty = objectType.GetProperty(key.Key); - if (objectProperty != null) - { - string compareName = objectProperty.PropertyType.Name.ToLower().Split("`")[0]; - var objectValue = objectProperty.GetValue(ObjectToCache); - if (objectValue != null) - { - string newObjectValue; - Dictionary newDict; + // check property type + Type objectType = ObjectToCache.GetType(); + if (objectType != null) + { + PropertyInfo objectProperty = objectType.GetProperty(key.Key); + if (objectProperty != null) + { + string compareName = objectProperty.PropertyType.Name.ToLower().Split("`")[0]; + var objectValue = objectProperty.GetValue(ObjectToCache); + if (objectValue != null) + { + string newObjectValue; + Dictionary newDict; switch (compareName) - { - case "identityorvalue": + { + case "identityorvalue": newObjectValue = Newtonsoft.Json.JsonConvert.SerializeObject(objectValue); - newDict = Newtonsoft.Json.JsonConvert.DeserializeObject>(newObjectValue); + newDict = Newtonsoft.Json.JsonConvert.DeserializeObject>(newObjectValue); objectDict[key.Key] = newDict["Id"]; break; case "identitiesorvalues": newObjectValue = Newtonsoft.Json.JsonConvert.SerializeObject(objectValue); newDict = Newtonsoft.Json.JsonConvert.DeserializeObject>(newObjectValue); - newObjectValue = Newtonsoft.Json.JsonConvert.SerializeObject(newDict["Ids"]); + newObjectValue = Newtonsoft.Json.JsonConvert.SerializeObject(newDict["Ids"]); objectDict[key.Key] = newObjectValue; StoreRelations(ObjectTypeName, key.Key, (long)objectDict["Id"], newObjectValue); break; - case "int32[]": + case "int32[]": newObjectValue = Newtonsoft.Json.JsonConvert.SerializeObject(objectValue); objectDict[key.Key] = newObjectValue; break; } - } - } - } - } - - string sql = ""; - if (UpdateRecord == false) - { - sql = "INSERT INTO " + ObjectTypeName + " (" + fieldList + ") VALUES (" + valueList + ")"; + } + } + } + } + + string sql = ""; + if (UpdateRecord == false) + { + sql = "INSERT INTO " + ObjectTypeName + " (" + fieldList + ") VALUES (" + valueList + ")"; + } + else + { + sql = "UPDATE " + ObjectTypeName + " SET " + updateFieldValueList + " WHERE Id = @Id"; } - else - { - sql = "UPDATE " + ObjectTypeName + " SET " + updateFieldValueList + " WHERE Id = @Id"; - } // execute sql Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); - db.ExecuteCMD(sql, objectDict); + db.ExecuteCMD(sql, objectDict); } - public static T GetCacheValue(T EndpointType, string SearchField, object SearchValue) - { + public static T GetCacheValue(T EndpointType, string SearchField, object SearchValue) + { string Endpoint = EndpointType.GetType().Name; Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); @@ -178,20 +178,20 @@ namespace gaseous_server.Classes.Metadata DataTable dt = db.ExecuteCMD(sql, dbDict); if (dt.Rows.Count == 0) { - // no data stored for this item - throw new Exception("No record found that matches endpoint " + Endpoint + " with search value " + SearchValue); + // no data stored for this item + throw new Exception("No record found that matches endpoint " + Endpoint + " with search value " + SearchValue); } else { - DataRow dataRow = dt.Rows[0]; + DataRow dataRow = dt.Rows[0]; object returnObject = BuildCacheObject(EndpointType, dataRow); - + return (T)returnObject; } } - public static T BuildCacheObject(T EndpointType, DataRow dataRow) - { + public static T BuildCacheObject(T EndpointType, DataRow dataRow) + { foreach (PropertyInfo property in EndpointType.GetType().GetProperties()) { if (dataRow.Table.Columns.Contains(property.Name)) @@ -428,11 +428,35 @@ namespace gaseous_server.Classes.Metadata } } + public static void CreateRelationsTables() + { + string PrimaryTable = typeof(T).Name; + foreach (PropertyInfo property in typeof(T).GetProperties()) + { + string SecondaryTable = property.Name; + + if (property.PropertyType.Name == "IdentitiesOrValues`1") + { + + string TableName = "Relation_" + PrimaryTable + "_" + SecondaryTable; + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT * FROM information_schema.tables WHERE table_schema = '" + Config.DatabaseConfiguration.DatabaseName + "' AND table_name = '" + TableName + "';"; + DataTable data = db.ExecuteCMD(sql); + if (data.Rows.Count == 0) + { + // table doesn't exist, create it + sql = "CREATE TABLE `" + Config.DatabaseConfiguration.DatabaseName + "`.`" + TableName + "` (`" + PrimaryTable + "Id` BIGINT NOT NULL, `" + SecondaryTable + "Id` BIGINT NOT NULL, PRIMARY KEY (`" + PrimaryTable + "Id`, `" + SecondaryTable + "Id`), INDEX `idx_PrimaryColumn` (`" + PrimaryTable + "Id` ASC) VISIBLE);"; + db.ExecuteCMD(sql); + } + } + } + } + private class MemoryCacheObject { public object Object { get; set; } public DateTime CreationTime { get; } = DateTime.UtcNow; - public DateTime ExpiryTime + public DateTime ExpiryTime { get { diff --git a/gaseous-server/Controllers/V1.0/SearchController.cs b/gaseous-server/Controllers/V1.0/SearchController.cs index 3589410..6a3e46b 100644 --- a/gaseous-server/Controllers/V1.0/SearchController.cs +++ b/gaseous-server/Controllers/V1.0/SearchController.cs @@ -73,7 +73,8 @@ namespace gaseous_server.Controllers private static async Task> _SearchForGame(long PlatformId, string SearchString) { string searchBody = ""; - string searchFields = "fields cover,first_release_date,name,platforms,slug; "; + // string searchFields = "fields cover,first_release_date,name,platforms,slug; "; + string searchFields = "fields *; "; searchBody += "search \"" + SearchString + "\";"; searchBody += "where platforms = (" + PlatformId + ");"; searchBody += "limit 100;"; @@ -86,12 +87,12 @@ namespace gaseous_server.Controllers // get Game metadata from data source Communications comms = new Communications(); var results = await comms.APIComm(IGDBClient.Endpoints.Games, searchFields, searchBody); - + List games = new List(); foreach (Game game in results.ToList()) { Storage.CacheStatus cacheStatus = Storage.GetCacheStatus("Game", (long)game.Id); - switch(cacheStatus) + switch (cacheStatus) { case Storage.CacheStatus.NotPresent: Storage.NewCacheValue(game, false); diff --git a/gaseous-server/Models/GaseousGame.cs b/gaseous-server/Models/GaseousGame.cs index 150f108..0f02acf 100644 --- a/gaseous-server/Models/GaseousGame.cs +++ b/gaseous-server/Models/GaseousGame.cs @@ -16,7 +16,7 @@ namespace gaseous_server.Models { var targetType = this.GetType(); var sourceType = game.GetType(); - foreach(var prop in targetType.GetProperties(BindingFlags.Instance | BindingFlags.Public| BindingFlags.SetProperty)) + foreach (var prop in targetType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty)) { // check whether source object has the the property var sp = sourceType.GetProperty(prop.Name); @@ -39,7 +39,11 @@ namespace gaseous_server.Models { if (this.Cover.Id != null) { - IGDB.Models.Cover cover = Covers.GetCover(Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(this), false); + // IGDB.Models.Cover cover = Covers.GetCover(Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(this), false); + IGDB.Models.Cover cover = new IGDB.Models.Cover() + { + Id = this.Cover.Id + }; return cover; } diff --git a/gaseous-server/Program.cs b/gaseous-server/Program.cs index 02db4a2..7dcfb31 100644 --- a/gaseous-server/Program.cs +++ b/gaseous-server/Program.cs @@ -36,6 +36,9 @@ db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.Conn // set up db db.InitDB(); +// create relation tables if they don't exist +Storage.CreateRelationsTables(); +Storage.CreateRelationsTables(); // populate db with static data for lookups AgeRatings.PopulateAgeMap(); @@ -273,7 +276,7 @@ using (var scope = app.Services.CreateScope()) { var roleManager = scope.ServiceProvider.GetRequiredService(); var roles = new[] { "Admin", "Gamer", "Player" }; - + foreach (var role in roles) { if (await roleManager.FindByNameAsync(role, CancellationToken.None) == null) @@ -303,11 +306,11 @@ app.Use(async (context, next) => string correlationId = Guid.NewGuid().ToString(); CallContext.SetData("CorrelationId", correlationId); CallContext.SetData("CallingProcess", context.Request.Method + ": " + context.Request.Path); - + string userIdentity; try { - userIdentity = context.User.Claims.Where(x=>x.Type==System.Security.Claims.ClaimTypes.NameIdentifier).FirstOrDefault().Value; + userIdentity = context.User.Claims.Where(x => x.Type == System.Security.Claims.ClaimTypes.NameIdentifier).FirstOrDefault().Value; } catch { @@ -329,7 +332,7 @@ app.Use(async (context, next) => // - the server will not start while the RecoverAccount.txt file exists string PasswordRecoveryFile = Path.Combine(Config.LibraryConfiguration.LibraryRootDirectory, "RecoverAccount.txt"); if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("recoveraccount"))) -{ +{ if (File.Exists(PasswordRecoveryFile)) { // password has already been set - do nothing and just exit @@ -345,7 +348,7 @@ if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("recoveraccount"))) string password = new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray()); File.WriteAllText(PasswordRecoveryFile, password); - + // reset the password using (var scope = app.Services.CreateScope()) { diff --git a/gaseous-server/wwwroot/scripts/main.js b/gaseous-server/wwwroot/scripts/main.js index 32fc9d5..05d1424 100644 --- a/gaseous-server/wwwroot/scripts/main.js +++ b/gaseous-server/wwwroot/scripts/main.js @@ -39,7 +39,7 @@ function ajaxCall(endpoint, method, successFunction, errorFunction, body) { function getQueryString(stringName, type) { const urlParams = new URLSearchParams(window.location.search); - var myParam = urlParams.get(stringName); + var myParam = urlParams.get(stringName); switch (type) { case "int": @@ -63,9 +63,9 @@ function getQueryString(stringName, type) { function setCookie(cname, cvalue, exdays) { const d = new Date(); - d.setTime(d.getTime() + (exdays*24*60*60*1000)); + d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); if (exdays) { - let expires = "expires="+ d.toUTCString(); + let expires = "expires=" + d.toUTCString(); document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/"; } else { document.cookie = cname + "=" + cvalue + ";path=/"; @@ -76,14 +76,14 @@ function getCookie(cname) { let name = cname + "="; let decodedCookie = decodeURIComponent(document.cookie); let ca = decodedCookie.split(';'); - for(let i = 0; i ' + state.text + '' + releaseDate + '' + '' ); } else { response = $( @@ -317,8 +317,8 @@ function CreateEditableTable(TableName, Headers) { var addButton = document.createElement('button'); addButton.value = 'Add Row'; addButton.innerHTML = 'Add Row'; - - $(addButton).click(function() { + + $(addButton).click(function () { eTable.appendChild(AddEditableTableRow(Headers)); }); @@ -463,10 +463,10 @@ function SetPreference(Setting, Value) { ajaxCall( '/api/v1.1/Account/Preferences', 'POST', - function(result) { + function (result) { SetPreference_Local(Setting, Value); }, - function(error) { + function (error) { SetPreference_Local(Setting, Value); }, JSON.stringify(model) @@ -478,12 +478,12 @@ function SetPreference_Batch(model) { ajaxCall( '/api/v1.1/Account/Preferences', 'POST', - function(result) { + function (result) { for (var i = 0; i < model.length; i++) { SetPreference_Local(model[i].setting, model[i].value.toString()); } }, - function(error) { + function (error) { for (var i = 0; i < model.length; i++) { SetPreference_Local(model[i].setting, model[i].value.toString()); } @@ -509,11 +509,11 @@ function SetPreference_Local(Setting, Value) { } } -function Uint8ToString(u8a){ +function Uint8ToString(u8a) { var CHUNK_SZ = 0x8000; var c = []; - for (var i=0; i < u8a.length; i+=CHUNK_SZ) { - c.push(String.fromCharCode.apply(null, u8a.subarray(i, i+CHUNK_SZ))); + for (var i = 0; i < u8a.length; i += CHUNK_SZ) { + c.push(String.fromCharCode.apply(null, u8a.subarray(i, i + CHUNK_SZ))); } return c.join(""); } diff --git a/gaseous-server/wwwroot/styles/style.css b/gaseous-server/wwwroot/styles/style.css index 90bacfb..f141bd6 100644 --- a/gaseous-server/wwwroot/styles/style.css +++ b/gaseous-server/wwwroot/styles/style.css @@ -23,21 +23,29 @@ h3 { border-bottom-width: 1px; /*border-image: linear-gradient(to right, blue 25%, yellow 25%, yellow 50%,red 50%, red 75%, teal 75%) 5;*/ - border-image: linear-gradient(to right, rgba(255,0,0,1) 0%, rgba(251,255,0,1) 16%, rgba(0,255,250,1) 30%, rgba(0,16,255,1) 46%, rgba(250,0,255,1) 62%, rgba(255,0,0,1) 78%, rgba(255,237,0,1) 90%, rgba(20,255,0,1) 100%) 5; + border-image: linear-gradient(to right, rgba(255, 0, 0, 1) 0%, rgba(251, 255, 0, 1) 16%, rgba(0, 255, 250, 1) 30%, rgba(0, 16, 255, 1) 46%, rgba(250, 0, 255, 1) 62%, rgba(255, 0, 0, 1) 78%, rgba(255, 237, 0, 1) 90%, rgba(20, 255, 0, 1) 100%) 5; } /* The Modal (background) */ .modal { - display: none; /* Hidden by default */ - position: fixed; /* Stay in place */ - z-index: 100; /* Sit on top */ + display: none; + /* Hidden by default */ + position: fixed; + /* Stay in place */ + z-index: 100; + /* Sit on top */ left: 0; top: 0; - width: 100%; /* Full width */ - height: 100%; /* Full height */ - overflow: none; /* Enable scroll if needed */ - background-color: rgb(0,0,0); /* Fallback color */ - background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ + width: 100%; + /* Full width */ + height: 100%; + /* Full height */ + overflow: none; + /* Enable scroll if needed */ + background-color: rgb(0, 0, 0); + /* Fallback color */ + background-color: rgba(0, 0, 0, 0.4); + /* Black w/ opacity */ backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); filter: drop-shadow(5px 5px 10px #000); @@ -47,22 +55,28 @@ h3 { /* Modal Content/Box */ .modal-content { background-color: #383838; - margin: 10% auto; /* 15% from the top and centered */ + margin: 10% auto; + /* 15% from the top and centered */ padding: 10px; border: 1px solid #888; border-radius: 10px; - width: 700px; /* Could be more or less, depending on screen size */ + width: 700px; + /* Could be more or less, depending on screen size */ min-height: 358px; } + .modal-content-sub { background-color: #383838; - margin: 20% auto; /* 20% from the top and centered */ + margin: 20% auto; + /* 20% from the top and centered */ padding: 10px; border: 1px solid #888; border-radius: 10px; - width: 300px; /* Could be more or less, depending on screen size */ + width: 300px; + /* Could be more or less, depending on screen size */ min-height: 110px; } + #modal-heading { margin-block: 5px; border-bottom-style: solid; @@ -70,8 +84,9 @@ h3 { border-bottom-width: 3px; /*border-image: linear-gradient(to right, blue 25%, yellow 25%, yellow 50%,red 50%, red 75%, teal 75%) 5;*/ - border-image: linear-gradient(to right, rgba(255,0,0,1) 0%, rgba(251,255,0,1) 16%, rgba(0,255,250,1) 30%, rgba(0,16,255,1) 46%, rgba(250,0,255,1) 62%, rgba(255,0,0,1) 78%, rgba(255,237,0,1) 90%, rgba(20,255,0,1) 100%) 5; + border-image: linear-gradient(to right, rgba(255, 0, 0, 1) 0%, rgba(251, 255, 0, 1) 16%, rgba(0, 255, 250, 1) 30%, rgba(0, 16, 255, 1) 46%, rgba(250, 0, 255, 1) 62%, rgba(255, 0, 0, 1) 78%, rgba(255, 237, 0, 1) 90%, rgba(20, 255, 0, 1) 100%) 5; } + #modal-content { height: 100%; } @@ -268,7 +283,7 @@ h3 { } #games_filter { - + width: 200px; /* border-style: solid; border-width: 1px; @@ -296,7 +311,11 @@ h3 { z-index: 1; } -input[type='text'], input[type='number'], input[type="email"], input[type="password"], input[type="datetime-local"] { +input[type='text'], +input[type='number'], +input[type="email"], +input[type="password"], +input[type="datetime-local"] { background-color: #2b2b2b; color: white; padding: 4px; @@ -313,7 +332,11 @@ input[type='text'], input[type='number'], input[type="email"], input[type="passw height: 21px; } -input[type='text']:hover, input[type='number']:hover, input[type="email"]:hover, input[type="password"]:hover, input[type="datetime-local"]:hover { +input[type='text']:hover, +input[type='number']:hover, +input[type="email"]:hover, +input[type="password"]:hover, +input[type="datetime-local"]:hover { border-color: #939393; } @@ -351,9 +374,7 @@ input[name='filter_panel_range_max'] { background: #555; } -.text_link { - -} +.text_link {} .text_link:hover { cursor: pointer; @@ -390,9 +411,9 @@ input[name='filter_panel_range_max'] { background-color: rgba(0, 22, 56, 0.8); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); - box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); + box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -webkit-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); } .games_pager_number { @@ -525,13 +546,13 @@ input[name='filter_panel_range_max'] { overflow-y: auto; /* display: flex; */ justify-content: center; - align-items: center; + align-items: center; } #games_library_alpha_pager { width: 50px; justify-content: center; - align-items: center; + align-items: center; } .games_library_alpha_pager_letter { @@ -604,6 +625,12 @@ input[name='filter_panel_range_max'] { border: 1px solid #2b2b2b; } +.game_tile_small_search { + min-height: 50px; + min-width: 50px; + width: 80px; +} + .game_tile_row { padding: 5px; display: block; @@ -706,9 +733,9 @@ input[name='filter_panel_range_max'] { } .game_tile_image_shadow { - box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); + box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -webkit-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); } .game_tile_image_row { @@ -720,11 +747,13 @@ input[name='filter_panel_range_max'] { background-color: transparent; } -.game_tile_image, .unknown { +.game_tile_image, +.unknown { background-color: transparent; } -.game_tile_image_row, .unknown { +.game_tile_image_row, +.unknown { background-color: transparent; } @@ -790,9 +819,9 @@ input[name='filter_panel_range_max'] { max-width: 250px; max-height: 350px; width: 100%; - box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); + box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -webkit-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); } .gamegenrelabel { @@ -842,9 +871,9 @@ input[name='filter_panel_range_max'] { padding: 10px; /*height: 350px;*/ margin-bottom: 20px; - box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); + box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -webkit-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); } #gamescreenshots_main { @@ -903,11 +932,16 @@ iframe { background-color: #383838; color: black; text-align: center; - user-select: none; /* standard syntax */ - -webkit-user-select: none; /* webkit (safari, chrome) browsers */ - -moz-user-select: none; /* mozilla browsers */ - -khtml-user-select: none; /* webkit (konqueror) browsers */ - -ms-user-select: none; /* IE10+ */ + user-select: none; + /* standard syntax */ + -webkit-user-select: none; + /* webkit (safari, chrome) browsers */ + -moz-user-select: none; + /* mozilla browsers */ + -khtml-user-select: none; + /* webkit (konqueror) browsers */ + -ms-user-select: none; + /* IE10+ */ } .gamescreenshots_arrows:hover { @@ -981,9 +1015,7 @@ iframe { background-color: rgba(56, 56, 56, 0.9); } -#gamesummarytext_label { - -} +#gamesummarytext_label {} .line-clamp-4 { overflow: hidden; @@ -1057,9 +1089,9 @@ th { -webkit-border-radius: 5px 5px 5px 5px; -moz-border-radius: 5px 5px 5px 5px; border: 1px solid #19d348; - box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); + box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -webkit-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); } .romstart:hover { @@ -1084,9 +1116,9 @@ th { border-color: white; background-color: blue; outline-color: blue; - box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); + box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -webkit-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); } .properties_button:hover { @@ -1115,14 +1147,18 @@ th { height: 100%; } -div[name="properties_toc_item"],div[name="properties_user_toc_item"],div[name="properties_profile_toc_item"] { +div[name="properties_toc_item"], +div[name="properties_user_toc_item"], +div[name="properties_profile_toc_item"] { padding: 10px; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #2b2b2b; } -div[name="properties_toc_item"]:hover,div[name="properties_user_toc_item"]:hover,div[name="properties_profile_toc_item"]:hover { +div[name="properties_toc_item"]:hover, +div[name="properties_user_toc_item"]:hover, +div[name="properties_profile_toc_item"]:hover { background-color: #2b2b2b; cursor: pointer; } @@ -1150,7 +1186,8 @@ div[name="properties_toc_item"]:hover,div[name="properties_user_toc_item"]:hover border-radius: 5px; } -.select2-container--default:hover, .select2-selection--multiple:hover { +.select2-container--default:hover, +.select2-selection--multiple:hover { border-color: #939393; } @@ -1197,7 +1234,8 @@ div[name="properties_toc_item"]:hover,div[name="properties_user_toc_item"]:hover border-radius: 5px; } -.select2-selection--single:hover, .select2-selection__rendered:hover { +.select2-selection--single:hover, +.select2-selection__rendered:hover { border-color: #939393; } @@ -1302,9 +1340,7 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled { background-color: #555; } -#emulator { - -} +#emulator {} .emulator_partscreen { margin: 0 auto; @@ -1377,15 +1413,14 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled { margin-left: 15px; } -.rom_checkbox_box { - -} +.rom_checkbox_box {} .rom_checkbox_box_hidden { display: none; } -#rom_edit, #rom_edit_delete { +#rom_edit, +#rom_edit_delete { float: right; } @@ -1398,9 +1433,9 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled { } #game { - box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); - -moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); + box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -webkit-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); + -moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44); } #gametitle_criticrating { @@ -1440,7 +1475,7 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled { width: 1000px; height: 90%; margin: 25px auto; -/* overflow-x: scroll;*/ + /* overflow-x: scroll;*/ position: relative; } @@ -1468,7 +1503,8 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled { } .bgalt1 { - background-color: transparent;; + background-color: transparent; + ; } .logs_table_cell_150px { @@ -1520,13 +1556,33 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled { background-color: #383838; } -.string { color: lightblue; } -.number { color: lightblue; } -.boolean { color: lightblue; } -.null { color: magenta; } -.key { color: greenyellow; } -.brace { color: #888; } -.square { color: #fff000; } +.string { + color: lightblue; +} + +.number { + color: lightblue; +} + +.boolean { + color: lightblue; +} + +.null { + color: magenta; +} + +.key { + color: greenyellow; +} + +.brace { + color: #888; +} + +.square { + color: #fff000; +} .tagBox { position: absolute; @@ -1557,12 +1613,16 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled { } .loginwindow { - position: fixed; /* Stay in place */ + position: fixed; + /* Stay in place */ left: 0; top: 0; - width: 100%; /* Full width */ - height: 100%; /* Full height */ - overflow: auto; /* Enable scroll if needed */ + width: 100%; + /* Full width */ + height: 100%; + /* Full height */ + overflow: auto; + /* Enable scroll if needed */ /*background-color: rgb(0,0,0); /* Fallback color */ /*background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ /*backdrop-filter: blur(8px); @@ -1575,11 +1635,13 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled { .loginwindow-content { position: relative; background-color: #383838; - margin: 15% auto; /* 15% from the top and centered */ + margin: 15% auto; + /* 15% from the top and centered */ padding: 10px; border: 1px solid #888; border-radius: 10px; - width: 350px; /* Could be more or less, depending on screen size */ + width: 350px; + /* Could be more or less, depending on screen size */ min-height: 250px; } @@ -1615,7 +1677,8 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled { } /* Links inside the dropdown */ -.dropdown-content a, .dropdown-content span { +.dropdown-content a, +.dropdown-content span { color: black; padding: 12px 16px; text-decoration: none; @@ -1625,12 +1688,16 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled { .dropdown-content span { cursor: auto; } - + /* Change color of dropdown links on hover */ -.dropdown-content a:hover {background-color: #ddd;} - +.dropdown-content a:hover { + background-color: #ddd; +} + /* Show the dropdown menu (use JS to add this class to the .dropdown-content container when the user clicks on the dropdown button) */ -.show {display:block;} +.show { + display: block; +} .dropdownroleitem { text-transform: capitalize;