diff --git a/web/app/Helpers/AssetHelper.php b/web/app/Helpers/AssetHelper.php index 559fabd..d3afcc0 100644 --- a/web/app/Helpers/AssetHelper.php +++ b/web/app/Helpers/AssetHelper.php @@ -41,6 +41,19 @@ class AssetHelper if(!$marketplaceResult->ok() || !$assetResult->ok()) return false; + $assetTypeId = $marketplaceResult['AssetTypeId']; + if( + $assetTypeId == 41 || // Hair Accessory + $assetTypeId == 42 || // Face Accessory + $assetTypeId == 43 || // Neck Accessory + $assetTypeId == 44 || // Shoulder Accessory + $assetTypeId == 45 || // Front Accessory + $assetTypeId == 46 || // Back Accessory + $assetTypeId == 47 // Waist Accessory + ) { + $assetTypeId = 8; + } + $assetContent = Http::get($assetResult['locations'][0]['location']); $hash = CdnHelper::SaveContent($assetContent->body(), $assetContent->header('Content-Type')); $asset = Asset::create([ @@ -50,7 +63,7 @@ class AssetHelper 'approved' => true, 'priceInTokens' => $marketplaceResult['PriceInRobux'] ?: 0, 'onSale' => $marketplaceResult['IsForSale'], - 'assetTypeId' => $marketplaceResult['AssetTypeId'], + 'assetTypeId' => $assetTypeId, 'assetVersionId' => 0 ]); $assetVersion = AssetVersion::create([ @@ -82,6 +95,19 @@ class AssetHelper if(!$marketplaceResult->ok()) return false; + $assetTypeId = $marketplaceResult['AssetTypeId']; + if( + $assetTypeId == 41 || // Hair Accessory + $assetTypeId == 42 || // Face Accessory + $assetTypeId == 43 || // Neck Accessory + $assetTypeId == 44 || // Shoulder Accessory + $assetTypeId == 45 || // Front Accessory + $assetTypeId == 46 || // Back Accessory + $assetTypeId == 47 // Waist Accessory + ) { + $assetTypeId = 8; + } + $hash = CdnHelper::SaveContentB64($b64Content, 'application/octet-stream'); $asset = Asset::create([ 'creatorId' => ($uploadToHolder ? 1 : Auth::user()->id), @@ -90,7 +116,7 @@ class AssetHelper 'approved' => true, 'priceInTokens' => $marketplaceResult['PriceInRobux'] ?: 0, 'onSale' => $marketplaceResult['IsForSale'], - 'assetTypeId' => $marketplaceResult['AssetTypeId'], + 'assetTypeId' => $assetTypeId, 'assetVersionId' => 0 ]); $assetVersion = AssetVersion::create([ diff --git a/web/app/Helpers/BrickColorHelper.php b/web/app/Helpers/BrickColorHelper.php new file mode 100644 index 0000000..11b1071 --- /dev/null +++ b/web/app/Helpers/BrickColorHelper.php @@ -0,0 +1,851 @@ + [ + 'Color' => [242, 243, 243], + 'Name' => 'White' + ], + 2 => [ + 'Color' => [161, 165, 162], + 'Name' => 'Grey' + ], + 3 => [ + 'Color' => [249, 233, 153], + 'Name' => 'Light yellow' + ], + 5 => [ + 'Color' => [215, 197, 154], + 'Name' => 'Brick yellow' + ], + 6 => [ + 'Color' => [194, 218, 184], + 'Name' => 'Light green (Mint)' + ], + 9 => [ + 'Color' => [232, 186, 200], + 'Name' => 'Light reddish violet' + ], + 11 => [ + 'Color' => [0x80, 0xbb, 0xdb], + 'Name' => 'Pastel Blue' + ], + 12 => [ + 'Color' => [203, 132, 66], + 'Name' => 'Light orange brown' + ], + 18 => [ + 'Color' => [204, 142, 105], + 'Name' => 'Nougat' + ], + 21 => [ + 'Color' => [196, 40, 28], + 'Name' => 'Bright red' + ], + 22 => [ + 'Color' => [196, 112, 160], + 'Name' => 'Med. reddish violet' + ], + 23 => [ + 'Color' => [13, 105, 172], + 'Name' => 'Bright blue' + ], + 24 => [ + 'Color' => [245, 205, 48], + 'Name' => 'Bright yellow' + ], + 25 => [ + 'Color' => [98, 71, 50], + 'Name' => 'Earth orange' + ], + 26 => [ + 'Color' => [27, 42, 53], + 'Name' => 'Black' + ], + 27 => [ + 'Color' => [109, 110, 108], + 'Name' => 'Dark grey' + ], + 28 => [ + 'Color' => [40, 127, 71], + 'Name' => 'Dark green' + ], + 29 => [ + 'Color' => [161, 196, 140], + 'Name' => 'Medium green' + ], + 36 => [ + 'Color' => [243, 207, 155], + 'Name' => 'Lig. Yellowich orange' + ], + 37 => [ + 'Color' => [75, 151, 75], + 'Name' => 'Bright green' + ], + 38 => [ + 'Color' => [160, 95, 53], + 'Name' => 'Dark orange' + ], + 39 => [ + 'Color' => [193, 202, 222], + 'Name' => 'Light bluish violet' + ], + 40 => [ + 'Color' => [236, 236, 236], + 'Name' => 'Transparent' + ], + 41 => [ + 'Color' => [205, 84, 75], + 'Name' => 'Tr. Red' + ], + 42 => [ + 'Color' => [193, 223, 240], + 'Name' => 'Tr. Lg blue' + ], + 43 => [ + 'Color' => [123, 182, 232], + 'Name' => 'Tr. Blue' + ], + 44 => [ + 'Color' => [247, 241, 141], + 'Name' => 'Tr. Yellow' + ], + 45 => [ + 'Color' => [180, 210, 228], + 'Name' => 'Light blue' + ], + 47 => [ + 'Color' => [217, 133, 108], + 'Name' => 'Tr. Flu. Reddish orange' + ], + 48 => [ + 'Color' => [132, 182, 141], + 'Name' => 'Tr. Green' + ], + 49 => [ + 'Color' => [248, 241, 132], + 'Name' => 'Tr. Flu. Green' + ], + 50 => [ + 'Color' => [236, 232, 222], + 'Name' => 'Phosph. White' + ], + 100 => [ + 'Color' => [238, 196, 182], + 'Name' => 'Light red' + ], + 101 => [ + 'Color' => [218, 134, 122], + 'Name' => 'Medium red' + ], + 102 => [ + 'Color' => [110, 153, 202], + 'Name' => 'Medium blue' + ], + 103 => [ + 'Color' => [199, 193, 183], + 'Name' => 'Light grey' + ], + 104 => [ + 'Color' => [107, 50, 124], + 'Name' => 'Bright violet' + ], + 105 => [ + 'Color' => [226, 155, 64], + 'Name' => 'Br. yellowish orange' + ], + 106 => [ + 'Color' => [218, 133, 65], + 'Name' => 'Bright orange' + ], + 107 => [ + 'Color' => [0, 143, 156], + 'Name' => 'Bright bluish green' + ], + 108 => [ + 'Color' => [104, 92, 67], + 'Name' => 'Earth yellow' + ], + 110 => [ + 'Color' => [67, 84, 147], + 'Name' => 'Bright bluish violet' + ], + 111 => [ + 'Color' => [191, 183, 177], + 'Name' => 'Tr. Brown' + ], + 112 => [ + 'Color' => [104, 116, 172], + 'Name' => 'Medium bluish violet' + ], + 113 => [ + 'Color' => [228, 173, 200], + 'Name' => 'Tr. Medi. reddish violet' + ], + 115 => [ + 'Color' => [199, 210, 60], + 'Name' => 'Med. yellowish green' + ], + 116 => [ + 'Color' => [85, 165, 175], + 'Name' => 'Med. bluish green' + ], + 118 => [ + 'Color' => [183, 215, 213], + 'Name' => 'Light bluish green' + ], + 119 => [ + 'Color' => [164, 189, 71], + 'Name' => 'Br. yellowish green' + ], + 120 => [ + 'Color' => [217, 228, 167], + 'Name' => 'Lig. yellowish green' + ], + 121 => [ + 'Color' => [231, 172, 88], + 'Name' => 'Med. yellowish orange' + ], + 123 => [ + 'Color' => [211, 111, 76], + 'Name' => 'Br. reddish orange' + ], + 124 => [ + 'Color' => [146, 57, 120], + 'Name' => 'Bright reddish violet' + ], + 125 => [ + 'Color' => [234, 184, 146], + 'Name' => 'Light orange' + ], + 126 => [ + 'Color' => [165, 165, 203], + 'Name' => 'Tr. Bright bluish violet' + ], + 127 => [ + 'Color' => [220, 188, 129], + 'Name' => 'Gold' + ], + 128 => [ + 'Color' => [174, 122, 89], + 'Name' => 'Dark nougat' + ], + 131 => [ + 'Color' => [156, 163, 168], + 'Name' => 'Silver' + ], + 133 => [ + 'Color' => [213, 115, 61], + 'Name' => 'Neon orange' + ], + 134 => [ + 'Color' => [216, 221, 86], + 'Name' => 'Neon green' + ], + 135 => [ + 'Color' => [116, 134, 157], + 'Name' => 'Sand blue' + ], + 136 => [ + 'Color' => [135, 124, 144], + 'Name' => 'Sand violet' + ], + 137 => [ + 'Color' => [224, 152, 100], + 'Name' => 'Medium orange' + ], + 138 => [ + 'Color' => [149, 138, 115], + 'Name' => 'Sand yellow' + ], + 140 => [ + 'Color' => [32, 58, 86], + 'Name' => 'Earth blue' + ], + 141 => [ + 'Color' => [39, 70, 45], + 'Name' => 'Earth green' + ], + 143 => [ + 'Color' => [207, 226, 247], + 'Name' => 'Tr. Flu. Blue' + ], + 145 => [ + 'Color' => [121, 136, 161], + 'Name' => 'Sand blue metallic' + ], + 146 => [ + 'Color' => [149, 142, 163], + 'Name' => 'Sand violet metallic' + ], + 147 => [ + 'Color' => [147, 135, 103], + 'Name' => 'Sand yellow metallic' + ], + 148 => [ + 'Color' => [87, 88, 87], + 'Name' => 'Dark grey metallic' + ], + 149 => [ + 'Color' => [22, 29, 50], + 'Name' => 'Black metallic' + ], + 150 => [ + 'Color' => [171, 173, 172], + 'Name' => 'Light grey metallic' + ], + 151 => [ + 'Color' => [120, 144, 130], + 'Name' => 'Sand green' + ], + 153 => [ + 'Color' => [149, 121, 119], + 'Name' => 'Sand red' + ], + 154 => [ + 'Color' => [123, 46, 47], + 'Name' => 'Dark red' + ], + 157 => [ + 'Color' => [255, 246, 123], + 'Name' => 'Tr. Flu. Yellow' + ], + 158 => [ + 'Color' => [225, 164, 194], + 'Name' => 'Tr. Flu. Red' + ], + 168 => [ + 'Color' => [117, 108, 98], + 'Name' => 'Gun metallic' + ], + 176 => [ + 'Color' => [151, 105, 91], + 'Name' => 'Red flip/flop' + ], + 178 => [ + 'Color' => [180, 132, 85], + 'Name' => 'Yellow flip/flop' + ], + 179 => [ + 'Color' => [137, 135, 136], + 'Name' => 'Silver flip/flop' + ], + 180 => [ + 'Color' => [215, 169, 75], + 'Name' => 'Curry' + ], + 190 => [ + 'Color' => [249, 214, 46], + 'Name' => 'Fire Yellow' + ], + 191 => [ + 'Color' => [232, 171, 45], + 'Name' => 'Flame yellowish orange' + ], + 192 => [ + 'Color' => [105, 64, 40], + 'Name' => 'Reddish brown' + ], + 193 => [ + 'Color' => [207, 96, 36], + 'Name' => 'Flame reddish orange' + ], + 194 => [ + 'Color' => [163, 162, 165], + 'Name' => 'Medium stone grey' + ], + 195 => [ + 'Color' => [70, 103, 164], + 'Name' => 'Royal blue' + ], + 196 => [ + 'Color' => [35, 71, 139], + 'Name' => 'Dark Royal blue' + ], + 198 => [ + 'Color' => [142, 66, 133], + 'Name' => 'Bright reddish lilac' + ], + 199 => [ + 'Color' => [99, 95, 98], + 'Name' => 'Dark stone grey' + ], + 200 => [ + 'Color' => [130, 138, 93], + 'Name' => 'Lemon metalic' + ], + 208 => [ + 'Color' => [229, 228, 223], + 'Name' => 'Light stone grey' + ], + 209 => [ + 'Color' => [176, 142, 68], + 'Name' => 'Dark Curry' + ], + 210 => [ + 'Color' => [112, 149, 120], + 'Name' => 'Faded green' + ], + 211 => [ + 'Color' => [121, 181, 181], + 'Name' => 'Turquoise' + ], + 212 => [ + 'Color' => [159, 195, 233], + 'Name' => 'Light Royal blue' + ], + 213 => [ + 'Color' => [108, 129, 183], + 'Name' => 'Medium Royal blue' + ], + 216 => [ + 'Color' => [143, 76, 42], + 'Name' => 'Rust' + ], + 217 => [ + 'Color' => [124, 92, 70], + 'Name' => 'Brown' + ], + 218 => [ + 'Color' => [150, 112, 159], + 'Name' => 'Reddish lilac' + ], + 219 => [ + 'Color' => [107, 98, 155], + 'Name' => 'Lilac' + ], + 220 => [ + 'Color' => [167, 169, 206], + 'Name' => 'Light lilac' + ], + 221 => [ + 'Color' => [205, 98, 152], + 'Name' => 'Bright purple' + ], + 222 => [ + 'Color' => [228, 173, 200], + 'Name' => 'Light purple' + ], + 223 => [ + 'Color' => [220, 144, 149], + 'Name' => 'Light pink' + ], + 224 => [ + 'Color' => [240, 213, 160], + 'Name' => 'Light brick yellow' + ], + 225 => [ + 'Color' => [235, 184, 127], + 'Name' => 'Warm yellowish orange' + ], + 226 => [ + 'Color' => [253, 234, 141], + 'Name' => 'Cool yellow' + ], + 232 => [ + 'Color' => [125, 187, 221], + 'Name' => 'Dove blue' + ], + 268 => [ + 'Color' => [52, 43, 117], + 'Name' => 'Medium lilac' + ], + 301 => [ + 'Color' => [80, 109, 84], + 'Name' => 'Slime green' + ], + 302 => [ + 'Color' => [91, 93, 105], + 'Name' => 'Smoky grey' + ], + 303 => [ + 'Color' => [0, 16, 176], + 'Name' => 'Dark blue' + ], + 304 => [ + 'Color' => [44, 101, 29], + 'Name' => 'Parsley green' + ], + 305 => [ + 'Color' => [82, 124, 174], + 'Name' => 'Steel blue' + ], + 306 => [ + 'Color' => [51, 88, 130], + 'Name' => 'Storm blue' + ], + 307 => [ + 'Color' => [16, 42, 220], + 'Name' => 'Lapis' + ], + 308 => [ + 'Color' => [61, 21, 133], + 'Name' => 'Dark indigo' + ], + 309 => [ + 'Color' => [52, 142, 64], + 'Name' => 'Sea green' + ], + 310 => [ + 'Color' => [91, 154, 76], + 'Name' => 'Shamrock' + ], + 311 => [ + 'Color' => [159, 161, 172], + 'Name' => 'Fossil' + ], + 312 => [ + 'Color' => [89, 34, 89], + 'Name' => 'Mulberry' + ], + 313 => [ + 'Color' => [31, 128, 29], + 'Name' => 'Forest green' + ], + 314 => [ + 'Color' => [159, 173, 192], + 'Name' => 'Cadet blue' + ], + 315 => [ + 'Color' => [9, 137, 207], + 'Name' => 'Electric blue' + ], + 316 => [ + 'Color' => [123, 0, 123], + 'Name' => 'Eggplant' + ], + 317 => [ + 'Color' => [124, 156, 107], + 'Name' => 'Moss' + ], + 318 => [ + 'Color' => [138, 171, 133], + 'Name' => 'Artichoke' + ], + 319 => [ + 'Color' => [185, 196, 177], + 'Name' => 'Sage green' + ], + 320 => [ + 'Color' => [202, 203, 209], + 'Name' => 'Ghost grey' + ], + 321 => [ + 'Color' => [167, 94, 155], + 'Name' => 'Lilac' + ], + 322 => [ + 'Color' => [123, 47, 123], + 'Name' => 'Plum' + ], + 323 => [ + 'Color' => [148, 190, 129], + 'Name' => 'Olivine' + ], + 324 => [ + 'Color' => [168, 189, 153], + 'Name' => 'Laurel green' + ], + 325 => [ + 'Color' => [223, 223, 222], + 'Name' => 'Quill grey' + ], + 327 => [ + 'Color' => [151, 0, 0], + 'Name' => 'Crimson' + ], + 328 => [ + 'Color' => [177, 229, 166], + 'Name' => 'Mint' + ], + 329 => [ + 'Color' => [152, 194, 219], + 'Name' => 'Baby blue' + ], + 330 => [ + 'Color' => [255, 152, 220], + 'Name' => 'Carnation pink' + ], + 331 => [ + 'Color' => [255, 89, 89], + 'Name' => 'Persimmon' + ], + 332 => [ + 'Color' => [117, 0, 0], + 'Name' => 'Maroon' + ], + 333 => [ + 'Color' => [239, 184, 56], + 'Name' => 'Gold' + ], + 334 => [ + 'Color' => [248, 217, 109], + 'Name' => 'Daisy orange' + ], + 335 => [ + 'Color' => [231, 231, 236], + 'Name' => 'Pearl' + ], + 336 => [ + 'Color' => [199, 212, 228], + 'Name' => 'Fog' + ], + 337 => [ + 'Color' => [255, 148, 148], + 'Name' => 'Salmon' + ], + 338 => [ + 'Color' => [190, 104, 98], + 'Name' => 'Terra Cotta' + ], + 339 => [ + 'Color' => [86, 36, 36], + 'Name' => 'Cocoa' + ], + 340 => [ + 'Color' => [241, 231, 199], + 'Name' => 'Wheat' + ], + 341 => [ + 'Color' => [254, 243, 187], + 'Name' => 'Buttermilk' + ], + 342 => [ + 'Color' => [224, 178, 208], + 'Name' => 'Mauve' + ], + 343 => [ + 'Color' => [212, 144, 189], + 'Name' => 'Sunrise' + ], + 344 => [ + 'Color' => [150, 85, 85], + 'Name' => 'Tawny' + ], + 345 => [ + 'Color' => [143, 76, 42], + 'Name' => 'Rust' + ], + 346 => [ + 'Color' => [211, 190, 150], + 'Name' => 'Cashmere' + ], + 347 => [ + 'Color' => [226, 220, 188], + 'Name' => 'Khaki' + ], + 348 => [ + 'Color' => [237, 234, 234], + 'Name' => 'Lily white' + ], + 349 => [ + 'Color' => [233, 218, 218], + 'Name' => 'Seashell' + ], + 350 => [ + 'Color' => [136, 62, 62], + 'Name' => 'Burgundy' + ], + 351 => [ + 'Color' => [188, 155, 93], + 'Name' => 'Cork' + ], + 352 => [ + 'Color' => [199, 172, 120], + 'Name' => 'Burlap' + ], + 353 => [ + 'Color' => [202, 191, 163], + 'Name' => 'Beige' + ], + 354 => [ + 'Color' => [187, 179, 178], + 'Name' => 'Oyster' + ], + 355 => [ + 'Color' => [108, 88, 75], + 'Name' => 'Pine Cone' + ], + 356 => [ + 'Color' => [160, 132, 79], + 'Name' => 'Fawn brown' + ], + 357 => [ + 'Color' => [149, 137, 136], + 'Name' => 'Hurricane grey' + ], + 358 => [ + 'Color' => [171, 168, 158], + 'Name' => 'Cloudy grey' + ], + 359 => [ + 'Color' => [175, 148, 131], + 'Name' => 'Linen' + ], + 360 => [ + 'Color' => [150, 103, 102], + 'Name' => 'Copper' + ], + 361 => [ + 'Color' => [86, 66, 54], + 'Name' => 'Dirt brown' + ], + 362 => [ + 'Color' => [126, 104, 63], + 'Name' => 'Bronze' + ], + 363 => [ + 'Color' => [105, 102, 92], + 'Name' => 'Flint' + ], + 364 => [ + 'Color' => [90, 76, 66], + 'Name' => 'Dark taupe' + ], + 365 => [ + 'Color' => [106, 57, 9], + 'Name' => 'Burnt Sienna' + ], + 1001 => [ + 'Color' => [248, 248, 248], + 'Name' => 'Institutional white' + ], + 1002 => [ + 'Color' => [205, 205, 205], + 'Name' => 'Mid gray' + ], + 1003 => [ + 'Color' => [17, 17, 17], + 'Name' => 'Really black' + ], + 1004 => [ + 'Color' => [255, 0, 0], + 'Name' => 'Really red' + ], + 1005 => [ + 'Color' => [255, 175, 0], + 'Name' => 'Deep orange' + ], + 1006 => [ + 'Color' => [180, 128, 255], + 'Name' => 'Alder' + ], + 1007 => [ + 'Color' => [163, 75, 75], + 'Name' => 'Dusty Rose' + ], + 1008 => [ + 'Color' => [193, 190, 66], + 'Name' => 'Olive' + ], + 1009 => [ + 'Color' => [255, 255, 0], + 'Name' => 'New Yeller' + ], + 1010 => [ + 'Color' => [0, 0, 255], + 'Name' => 'Really blue' + ], + 1011 => [ + 'Color' => [0, 32, 96], + 'Name' => 'Navy blue' + ], + 1012 => [ + 'Color' => [33, 84, 185], + 'Name' => 'Deep blue' + ], + 1013 => [ + 'Color' => [4, 175, 236], + 'Name' => 'Cyan' + ], + 1014 => [ + 'Color' => [170, 85, 0], + 'Name' => 'CGA brown' + ], + 1015 => [ + 'Color' => [170, 0, 170], + 'Name' => 'Magenta' + ], + 1016 => [ + 'Color' => [255, 102, 204], + 'Name' => 'Pink' + ], + 1017 => [ + 'Color' => [255, 175, 0], + 'Name' => 'Deep orange' + ], + 1018 => [ + 'Color' => [18, 238, 212], + 'Name' => 'Teal' + ], + 1019 => [ + 'Color' => [0, 255, 255], + 'Name' => 'Toothpaste' + ], + 1020 => [ + 'Color' => [0, 255, 0], + 'Name' => 'Lime green' + ], + 1021 => [ + 'Color' => [58, 125, 21], + 'Name' => 'Camo' + ], + 1022 => [ + 'Color' => [127, 142, 100], + 'Name' => 'Grime' + ], + 1023 => [ + 'Color' => [140, 91, 159], + 'Name' => 'Lavender' + ], + 1024 => [ + 'Color' => [175, 221, 255], + 'Name' => 'Pastel light blue' + ], + 1025 => [ + 'Color' => [255, 201, 201], + 'Name' => 'Pastel orange' + ], + 1026 => [ + 'Color' => [177, 167, 255], + 'Name' => 'Pastel violet' + ], + 1027 => [ + 'Color' => [159, 243, 233], + 'Name' => 'Pastel blue-green' + ], + 1028 => [ + 'Color' => [204, 255, 204], + 'Name' => 'Pastel green' + ], + 1029 => [ + 'Color' => [255, 255, 204], + 'Name' => 'Pastel yellow' + ], + 1030 => [ + 'Color' => [255, 204, 153], + 'Name' => 'Pastel brown' + ], + 1031 => [ + 'Color' => [98, 37, 209], + 'Name' => 'Royal purple' + ], + 1032 => [ + 'Color' => [255, 0, 191], + 'Name' => 'Hot pink' + ] + ]; + + public static function IsValidColor($colorId) + { + return array_key_exists($colorId, self::$brickColors); + } +} diff --git a/web/app/Helpers/CdnHelper.php b/web/app/Helpers/CdnHelper.php index a5fb6c1..fa9a22e 100644 --- a/web/app/Helpers/CdnHelper.php +++ b/web/app/Helpers/CdnHelper.php @@ -44,6 +44,21 @@ class CdnHelper return $hash; } + public static function Delete($hash) + { + $disk = self::GetDisk(); + + $cdnHash = CdnHash::where('hash', $hash); + if($disk->exists($hash) && $cdnHash->exists()) { + $cdnHash->delete(); + $disk->delete($hash); + + return true; + } + + return false; + } + public static function SaveContentB64($contentB64, $mime) { return self::SaveContent(base64_decode($contentB64), $mime); diff --git a/web/app/Helpers/GridHelper.php b/web/app/Helpers/GridHelper.php index 5b15978..808311f 100644 --- a/web/app/Helpers/GridHelper.php +++ b/web/app/Helpers/GridHelper.php @@ -123,7 +123,7 @@ class GridHelper public static function getDefaultThumbnail($fileName) { - $disk = $self::getThumbDisk(); + $disk = self::getThumbDisk(); if(!$disk->exists($fileName)) throw new Exception('Unable to locate template file.'); @@ -133,7 +133,26 @@ class GridHelper public static function getUnknownThumbnail() { - return $self::getDefaultThumbnail('UnknownThumbnail.png'); + return self::getDefaultThumbnail('UnknownThumbnail.png'); + } + + private static function getGameDisk() + { + return Storage::build([ + 'driver' => 'local', + 'root' => storage_path('app/grid/game'), + ]); + } + + public static function getBodyColorsXML() + { + $disk = self::getGameDisk(); + $fileName = 'BodyColors.xml'; + + if(!$disk->exists($fileName)) + throw new Exception('Unable to locate template file.'); + + return $disk->get($fileName); } public static function getArbiter($name) diff --git a/web/app/Http/Controllers/Api/AvatarController.php b/web/app/Http/Controllers/Api/AvatarController.php new file mode 100644 index 0000000..5d7a8e7 --- /dev/null +++ b/web/app/Http/Controllers/Api/AvatarController.php @@ -0,0 +1,263 @@ +redraw(); + + return response(['success' => true]); + } + + public static function GetUserAssets($userId) + { + return UserAsset::where('owner_id', $userId) + ->whereRelation('asset', 'moderated', false) + ->orderByDesc('id'); + } + + public function listAssets(Request $request) + { + $validator = Validator::make($request->all(), [ + 'assetTypeId' => ['required', 'int'] + ]); + + if($validator->fails()) { + return ValidationHelper::generateValidatorError($validator); + } + + $valid = $validator->valid(); + + if(!in_array($valid['assetTypeId'], $this->validAssetTypeIds)) { + $validator->errors()->add('assetTypeId', 'Invalid assetTypeId supplied.'); + return ValidationHelper::generateValidatorError($validator); + } + + $userAssets = self::GetUserAssets(Auth::user()->id) + ->whereRelation('asset', 'assetTypeId', $valid['assetTypeId']) + ->groupBy('asset_id') + ->paginate(12); + $data = []; + + foreach($userAssets as $userAsset) + { + $asset = $userAsset->asset; + + array_push($data, [ + 'id' => $asset->id, + 'Url' => route('shop.asset', ['asset' => $asset, 'assetName' => Str::slug($asset->name, '-')]), + 'Thumbnail' => $asset->getThumbnail(), + 'Name' => $asset->name, + 'Wearing' => Auth::user()->isWearing($asset->id) + ]); + } + + return response([ + 'data' => $data, + 'pages' => ($userAssets->hasPages() ? $userAssets->lastPage() : 1) + ]); + } + + public function listWearing(Request $request) + { + $avatarAssets = AvatarAsset::where('owner_id', Auth::user()->id)->get(); + $data = []; + + foreach($avatarAssets as $avatarAsset) + { + $asset = $avatarAsset->asset; + + array_push($data, [ + 'id' => $asset->id, + 'Url' => route('shop.asset', ['asset' => $asset, 'assetName' => Str::slug($asset->name, '-')]), + 'Thumbnail' => $asset->getThumbnail(), + 'Name' => $asset->name, + 'Wearing' => true + ]); + } + + return response([ + 'data' => $data + ]); + } + + public function wearAsset(Request $request) + { + $validator = Validator::make($request->all(), [ + 'id' => [ + 'required', + Rule::exists('App\Models\Asset', 'id')->where(function($query) { + return $query->where('moderated', false); + }) + ] + ]); + + if($validator->fails()) { + return ValidationHelper::generateValidatorError($validator); + } + + $valid = $validator->valid(); + + $userAsset = self::GetUserAssets(Auth::user()->id) + ->where('asset_id', $valid['id']) + ->first(); + + if(!$userAsset) { + $validator->errors()->add('id', 'User does not own asset.'); + return ValidationHelper::generateValidatorError($validator); + } + + if(Auth::user()->isWearing($valid['id']) && $userAsset->asset->assetTypeId == 8) { // 8 = hat + $validator->errors()->add('id', 'User is already wearing asset.'); + return ValidationHelper::generateValidatorError($validator); + } + + if(!in_array($userAsset->asset->assetTypeId, $this->validAssetTypeIds)) { + $validator->errors()->add('id', 'This asset cannot be worn.'); + return ValidationHelper::generateValidatorError($validator); + } + + $wornItems = AvatarAsset::where('owner_id', Auth::user()->id) + ->whereRelation('asset', 'assetTypeId', $userAsset->asset->assetTypeId); + if($userAsset->asset->assetTypeId != 8 && $wornItems->exists()) // 8 = hat + { + $wornItems->delete(); + } + elseif($userAsset->asset->assetTypeId == 8 && $wornItems->count() >= 10) + { + $validator->errors()->add('id', 'User has hit the wearing limit on this asset type.'); + return ValidationHelper::generateValidatorError($validator); + } + + AvatarAsset::Create([ + 'owner_id' => Auth::user()->id, + 'asset_id' => $valid['id'] + ]); + + Auth::user()->redraw(); + + return response(['success' => true]); + } + + public function removeAsset(Request $request) + { + $validator = Validator::make($request->all(), [ + 'id' => [ + 'required', + Rule::exists('App\Models\Asset', 'id')->where(function($query) { + return $query->where('moderated', false); + }) + ] + ]); + + if($validator->fails()) { + return ValidationHelper::generateValidatorError($validator); + } + + $valid = $validator->valid(); + + if(!Auth::user()->isWearing($valid['id'])) { + $validator->errors()->add('id', 'User is not wearing asset.'); + return ValidationHelper::generateValidatorError($validator); + } + + AvatarAsset::where('owner_id', Auth::user()->id) + ->where('asset_id', $valid['id']) + ->delete(); + + Auth::user()->redraw(); + + return response(['success' => true]); + } + + public function setBodyColor(Request $request) + { + $validator = Validator::make($request->all(), [ + 'part' => ['required', 'regex:/(Head|Torso|LeftArm|RightArm|LeftLeg|RightLeg)/i'], + 'color' => ['required', 'int'] + ]); + + if($validator->fails()) { + return ValidationHelper::generateValidatorError($validator); + } + + $valid = $validator->valid(); + + if(!BrickColorHelper::isValidColor($valid['color'])) { + $validator->errors()->add('color', 'Invalid color id.'); + return ValidationHelper::generateValidatorError($validator); + } + + $part = strtolower($valid['part']); + switch($part) + { + case 'leftarm': + $part = 'leftArm'; + break; + case 'rightarm': + $part = 'rightArm'; + break; + case 'leftleg': + $part = 'leftLeg'; + break; + case 'rightleg': + $part = 'rightLeg'; + break; + } + + $bodyColors = Auth::user()->getBodyColors(); + $bodyColors->{$part} = $valid['color']; + $bodyColors->save(); + + Auth::user()->redraw(); + + return response(['success' => true]); + } + + public function getBodyColors(Request $request) + { + $bodyColors = Auth::user()->getBodyColors(); + + return response([ + 'data' => [ + 'Head' => $bodyColors->head, + 'Torso' => $bodyColors->torso, + 'RightArm' => $bodyColors->rightArm, + 'LeftArm' => $bodyColors->leftArm, + 'RightLeg' => $bodyColors->rightLeg, + 'LeftLeg' => $bodyColors->leftLeg + ] + ]); + } +} diff --git a/web/app/Http/Controllers/Web/Auth/RegisteredUserController.php b/web/app/Http/Controllers/Web/Auth/RegisteredUserController.php index eafe076..b746f7a 100644 --- a/web/app/Http/Controllers/Web/Auth/RegisteredUserController.php +++ b/web/app/Http/Controllers/Web/Auth/RegisteredUserController.php @@ -2,15 +2,19 @@ namespace App\Http\Controllers\Web\Auth; -use App\Models\User; -use App\Providers\RouteServiceProvider; use Illuminate\Auth\Events\Registered; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rules; + use App\Http\Controllers\Controller; +use App\Models\AvatarAsset; +use App\Models\DefaultUserAsset; +use App\Models\UserAsset; +use App\Models\User; +use App\Providers\RouteServiceProvider; class RegisteredUserController extends Controller { @@ -53,7 +57,22 @@ class RegisteredUserController extends Controller 'email' => $request->email, 'password' => Hash::make($request->password), ]); - + + foreach(DefaultUserAsset::all() as $defaultAsset) + { + UserAsset::createSerialed($user->id, $defaultAsset->asset_id); + + if($defaultAsset->wearing) + { + AvatarAsset::create([ + 'owner_id' => $user->id, + 'asset_id' => $defaultAsset->asset_id + ]); + } + } + + $user->redraw(); + event(new Registered($user)); Auth::login($user); diff --git a/web/app/Http/Controllers/Web/ClientAvatarController.php b/web/app/Http/Controllers/Web/ClientAvatarController.php new file mode 100644 index 0000000..935946a --- /dev/null +++ b/web/app/Http/Controllers/Web/ClientAvatarController.php @@ -0,0 +1,87 @@ +all(), [ + 'userId' => [ + 'required', + Rule::exists('App\Models\User', 'id') + ] + ]); + + if($validator->fails()) { + return ValidationHelper::generateValidatorError($validator); + } + + $valid = $validator->valid(); + $user = User::where('id', $valid['userId'])->first(); + + if($user->hasActivePunishment() && $user->getPunishment()->isDeletion()) { + $validator->errors()->add('id', 'User is moderated.'); + return ValidationHelper::generateValidatorError($validator); + } + + $document = simplexml_load_string(GridHelper::getBodyColorsXML()); + $bodyColors = $user->getBodyColors(); + + $document->xpath('//int[@name="HeadColor"]')[0][0] = $bodyColors->head; + $document->xpath('//int[@name="TorsoColor"]')[0][0] = $bodyColors->torso; + $document->xpath('//int[@name="LeftArmColor"]')[0][0] = $bodyColors->leftArm; + $document->xpath('//int[@name="LeftLegColor"]')[0][0] = $bodyColors->leftLeg; + $document->xpath('//int[@name="RightArmColor"]')[0][0] = $bodyColors->rightArm; + $document->xpath('//int[@name="RightLegColor"]')[0][0] = $bodyColors->rightLeg; + + return response($document->asXML()) + ->header('Content-Type', 'application/xml'); + } + + public function characterFetch(Request $request) + { + $validator = Validator::make($request->all(), [ + 'userId' => [ + 'required', + Rule::exists('App\Models\User', 'id') + ] + ]); + + if($validator->fails()) { + return ValidationHelper::generateValidatorError($validator); + } + + $valid = $validator->valid(); + $user = User::where('id', $valid['userId'])->first(); + + if($user->hasActivePunishment() && $user->getPunishment()->isDeletion()) { + $validator->errors()->add('id', 'User is moderated.'); + return ValidationHelper::generateValidatorError($validator); + } + + $charApp = ''; + $charApp .= route('client.bodyColors', ['userId' => $user->id]); + + foreach($user->getWearing()->get() as $avatarAsset) + { + $charApp .= ';' . route('client.asset', ['id' => $avatarAsset->asset->id]); + + if($avatarAsset->asset->assetTypeId == 19) // Gear + $charApp .= '&equipped=1'; + } + + return response($charApp) + ->header('Content-Type', 'text/plain'); + } +} diff --git a/web/app/Jobs/ArbiterRender.php b/web/app/Jobs/ArbiterRender.php index 8770f3b..9507eef 100644 --- a/web/app/Jobs/ArbiterRender.php +++ b/web/app/Jobs/ArbiterRender.php @@ -100,12 +100,17 @@ class ArbiterRender implements ShouldQueue array_push($arguments, 0); // Camera Offset X array_push($arguments, 0); // Camera Offset Y case 'Avatar': - $arguments[0] = url('test', ['id' => $this->assetId]); // TODO: this + $arguments[0] = route('client.characterFetch', ['userId' => $this->assetId]); break; + case 'Torso': + case 'Right Arm': + case 'Left Arm': + case 'Left Leg': + case 'Right Leg': + $this->type = 'BodyPart'; case 'Head': case 'Shirt': case 'Pants': - case 'BodyPart': // TODO: XlXi: Move this to config, as it could be different from prod in a testing environment. Also move this to it's own asset (not loading from roblox). array_push($arguments, 'https://www.roblox.com/asset/?id=1785197'); // Rig break; diff --git a/web/app/Models/AvatarAsset.php b/web/app/Models/AvatarAsset.php new file mode 100644 index 0000000..aa2b1e0 --- /dev/null +++ b/web/app/Models/AvatarAsset.php @@ -0,0 +1,26 @@ +belongsTo(Asset::class, 'asset_id'); + } +} diff --git a/web/app/Models/AvatarColor.php b/web/app/Models/AvatarColor.php new file mode 100644 index 0000000..100c3f0 --- /dev/null +++ b/web/app/Models/AvatarColor.php @@ -0,0 +1,59 @@ + $userId, + 'head' => $headColor, + 'torso' => $torsoColor, + 'leftArm' => $headColor, + 'rightArm' => $headColor, + 'leftLeg' => 102, + 'rightLeg' => 102 + ]); + } +} diff --git a/web/app/Models/AvatarOutfit.php b/web/app/Models/AvatarOutfit.php new file mode 100644 index 0000000..eff44e2 --- /dev/null +++ b/web/app/Models/AvatarOutfit.php @@ -0,0 +1,11 @@ +save(); } + public function redraw() + { + $oldHashes = [ + $this->thumbnailBustHash, + $this->thumbnail2DHash, + $this->thumbnail3DHash + ]; + + $this->thumbnailBustHash = null; + $this->thumbnail2DHash = null; + $this->thumbnail3DHash = null; + $this->timestamps = false; + $this->save(); + + foreach($oldHashes as $hash) + { + if(!User::where('thumbnailBustHash', $hash)->orWhere('thumbnail2DHash', $hash)->orWhere('thumbnail3DHash', $hash)->exists()) + CdnHelper::Delete($hash); + } + } + + public function isWearing($assetId) + { + return AvatarAsset::where('owner_id', $this->id) + ->where('asset_id', $assetId) + ->exists(); + } + + public function getWearing() + { + return AvatarAsset::where('owner_id', $this->id) + ->whereRelation('asset', 'moderated', 0); + } + + public function getBodyColors() + { + $colors = AvatarColor::user($this->id); + if($colors->exists()) + return $colors->first(); + + return AvatarColor::newForUser($this->id); + } + public function userToJson() { return [ diff --git a/web/config/database.php b/web/config/database.php index fc18290..c5a2a2e 100644 --- a/web/config/database.php +++ b/web/config/database.php @@ -87,6 +87,15 @@ return [ 'prefix' => '', 'prefix_indexes' => true, 'strict' => true, + 'modes' => [ + //'ONLY_FULL_GROUP_BY', + 'STRICT_TRANS_TABLES', + 'NO_ZERO_IN_DATE', + 'NO_ZERO_DATE', + 'ERROR_FOR_DIVISION_BY_ZERO', + 'NO_AUTO_CREATE_USER', + 'NO_ENGINE_SUBSTITUTION' + ], 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), diff --git a/web/database/migrations/2023_01_19_202239_create_avatar_assets_table.php b/web/database/migrations/2023_01_19_202239_create_avatar_assets_table.php new file mode 100644 index 0000000..1a46c43 --- /dev/null +++ b/web/database/migrations/2023_01_19_202239_create_avatar_assets_table.php @@ -0,0 +1,35 @@ +id(); + + $table->unsignedBigInteger('owner_id'); + $table->unsignedBigInteger('asset_id'); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('avatar_assets'); + } +}; diff --git a/web/database/migrations/2023_01_20_232605_create_avatar_colors_table.php b/web/database/migrations/2023_01_20_232605_create_avatar_colors_table.php new file mode 100644 index 0000000..0f801b1 --- /dev/null +++ b/web/database/migrations/2023_01_20_232605_create_avatar_colors_table.php @@ -0,0 +1,40 @@ +id(); + + $table->unsignedBigInteger('owner_id')->unique(); + $table->unsignedBigInteger('head'); + $table->unsignedBigInteger('torso'); + $table->unsignedBigInteger('leftArm'); + $table->unsignedBigInteger('rightArm'); + $table->unsignedBigInteger('leftLeg'); + $table->unsignedBigInteger('rightLeg'); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('avatar_colors'); + } +}; diff --git a/web/database/migrations/2023_01_20_234149_create_default_user_assets_table.php b/web/database/migrations/2023_01_20_234149_create_default_user_assets_table.php new file mode 100644 index 0000000..34ff121 --- /dev/null +++ b/web/database/migrations/2023_01_20_234149_create_default_user_assets_table.php @@ -0,0 +1,35 @@ +id(); + + $table->unsignedBigInteger('asset_id'); + $table->boolean('wearing'); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('default_user_assets'); + } +}; diff --git a/web/database/migrations/2023_01_21_033046_create_avatar_outfits_table.php b/web/database/migrations/2023_01_21_033046_create_avatar_outfits_table.php new file mode 100644 index 0000000..cb1d340 --- /dev/null +++ b/web/database/migrations/2023_01_21_033046_create_avatar_outfits_table.php @@ -0,0 +1,34 @@ +id(); + + $table->unsignedBigInteger('owner_id'); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('avatar_outfits'); + } +}; diff --git a/web/database/migrations/2023_01_21_033053_create_avatar_outfit_assets_table.php b/web/database/migrations/2023_01_21_033053_create_avatar_outfit_assets_table.php new file mode 100644 index 0000000..e3460d0 --- /dev/null +++ b/web/database/migrations/2023_01_21_033053_create_avatar_outfit_assets_table.php @@ -0,0 +1,35 @@ +id(); + + $table->unsignedBigInteger('outfit_id'); + $table->unsignedBigInteger('asset_id'); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('avatar_outfit_assets'); + } +}; diff --git a/web/resources/js/components/AvatarEditor.js b/web/resources/js/components/AvatarEditor.js new file mode 100644 index 0000000..cea89da --- /dev/null +++ b/web/resources/js/components/AvatarEditor.js @@ -0,0 +1,882 @@ +/* + Copyright © XlXi 2022 +*/ + +import { Component, createRef, createElement } from 'react'; +import axios from 'axios'; + +import classNames from 'classnames/bind'; + +import { buildGenericApiUrl } from '../util/HTTP.js'; +import ProgressiveImage from './ProgressiveImage'; +import Loader from './Loader'; + +import ThumbnailTool from './ThumbnailTool'; + +axios.defaults.withCredentials = true; + +const brickColors = [ + {id: 1, name: 'White'}, + {id: 2, name: 'Grey'}, + {id: 3, name: 'Light yellow'}, + {id: 5, name: 'Brick yellow'}, + {id: 6, name: 'Light green (Mint)'}, + {id: 9, name: 'Light reddish violet'}, + {id: 11, name: 'Pastel Blue'}, + {id: 12, name: 'Light orange brown'}, + {id: 18, name: 'Nougat'}, + {id: 21, name: 'Bright red'}, + {id: 22, name: 'Med. reddish violet'}, + {id: 23, name: 'Bright blue'}, + {id: 24, name: 'Bright yellow'}, + {id: 25, name: 'Earth orange'}, + {id: 26, name: 'Black'}, + {id: 27, name: 'Dark grey'}, + {id: 28, name: 'Dark green'}, + {id: 29, name: 'Medium green'}, + {id: 36, name: 'Lig. Yellowich orange'}, + {id: 37, name: 'Bright green'}, + {id: 38, name: 'Dark orange'}, + {id: 39, name: 'Light bluish violet'}, + {id: 40, name: 'Transparent'}, + {id: 41, name: 'Tr. Red'}, + {id: 42, name: 'Tr. Lg blue'}, + {id: 43, name: 'Tr. Blue'}, + {id: 44, name: 'Tr. Yellow'}, + {id: 45, name: 'Light blue'}, + {id: 47, name: 'Tr. Flu. Reddish orange'}, + {id: 48, name: 'Tr. Green'}, + {id: 49, name: 'Tr. Flu. Green'}, + {id: 50, name: 'Phosph. White'}, + {id: 100, name: 'Light red'}, + {id: 101, name: 'Medium red'}, + {id: 102, name: 'Medium blue'}, + {id: 103, name: 'Light grey'}, + {id: 104, name: 'Bright violet'}, + {id: 105, name: 'Br. yellowish orange'}, + {id: 106, name: 'Bright orange'}, + {id: 107, name: 'Bright bluish green'}, + {id: 108, name: 'Earth yellow'}, + {id: 110, name: 'Bright bluish violet'}, + {id: 111, name: 'Tr. Brown'}, + {id: 112, name: 'Medium bluish violet'}, + {id: 113, name: 'Tr. Medi. reddish violet'}, + {id: 115, name: 'Med. yellowish green'}, + {id: 116, name: 'Med. bluish green'}, + {id: 118, name: 'Light bluish green'}, + {id: 119, name: 'Br. yellowish green'}, + {id: 120, name: 'Lig. yellowish green'}, + {id: 121, name: 'Med. yellowish orange'}, + {id: 123, name: 'Br. reddish orange'}, + {id: 124, name: 'Bright reddish violet'}, + {id: 125, name: 'Light orange'}, + {id: 126, name: 'Tr. Bright bluish violet'}, + {id: 127, name: 'Gold'}, + {id: 128, name: 'Dark nougat'}, + {id: 131, name: 'Silver'}, + {id: 133, name: 'Neon orange'}, + {id: 134, name: 'Neon green'}, + {id: 135, name: 'Sand blue'}, + {id: 136, name: 'Sand violet'}, + {id: 137, name: 'Medium orange'}, + {id: 138, name: 'Sand yellow'}, + {id: 140, name: 'Earth blue'}, + {id: 141, name: 'Earth green'}, + {id: 143, name: 'Tr. Flu. Blue'}, + {id: 145, name: 'Sand blue metallic'}, + {id: 146, name: 'Sand violet metallic'}, + {id: 147, name: 'Sand yellow metallic'}, + {id: 148, name: 'Dark grey metallic'}, + {id: 149, name: 'Black metallic'}, + {id: 150, name: 'Light grey metallic'}, + {id: 151, name: 'Sand green'}, + {id: 153, name: 'Sand red'}, + {id: 154, name: 'Dark red'}, + {id: 157, name: 'Tr. Flu. Yellow'}, + {id: 158, name: 'Tr. Flu. Red'}, + {id: 168, name: 'Gun metallic'}, + {id: 176, name: 'Red flip/flop'}, + {id: 178, name: 'Yellow flip/flop'}, + {id: 179, name: 'Silver flip/flop'}, + {id: 180, name: 'Curry'}, + {id: 190, name: 'Fire Yellow'}, + {id: 191, name: 'Flame yellowish orange'}, + {id: 192, name: 'Reddish brown'}, + {id: 193, name: 'Flame reddish orange'}, + {id: 194, name: 'Medium stone grey'}, + {id: 195, name: 'Royal blue'}, + {id: 196, name: 'Dark Royal blue'}, + {id: 198, name: 'Bright reddish lilac'}, + {id: 199, name: 'Dark stone grey'}, + {id: 200, name: 'Lemon metalic'}, + {id: 208, name: 'Light stone grey'}, + {id: 209, name: 'Dark Curry'}, + {id: 210, name: 'Faded green'}, + {id: 211, name: 'Turquoise'}, + {id: 212, name: 'Light Royal blue'}, + {id: 213, name: 'Medium Royal blue'}, + {id: 216, name: 'Rust'}, + {id: 217, name: 'Brown'}, + {id: 218, name: 'Reddish lilac'}, + {id: 219, name: 'Lilac'}, + {id: 220, name: 'Light lilac'}, + {id: 221, name: 'Bright purple'}, + {id: 222, name: 'Light purple'}, + {id: 223, name: 'Light pink'}, + {id: 224, name: 'Light brick yellow'}, + {id: 225, name: 'Warm yellowish orange'}, + {id: 226, name: 'Cool yellow'}, + {id: 232, name: 'Dove blue'}, + {id: 268, name: 'Medium lilac'}, + {id: 301, name: 'Slime green'}, + {id: 302, name: 'Smoky grey'}, + {id: 303, name: 'Dark blue'}, + {id: 304, name: 'Parsley green'}, + {id: 305, name: 'Steel blue'}, + {id: 306, name: 'Storm blue'}, + {id: 307, name: 'Lapis'}, + {id: 308, name: 'Dark indigo'}, + {id: 309, name: 'Sea green'}, + {id: 310, name: 'Shamrock'}, + {id: 311, name: 'Fossil'}, + {id: 312, name: 'Mulberry'}, + {id: 313, name: 'Forest green'}, + {id: 314, name: 'Cadet blue'}, + {id: 315, name: 'Electric blue'}, + {id: 316, name: 'Eggplant'}, + {id: 317, name: 'Moss'}, + {id: 318, name: 'Artichoke'}, + {id: 319, name: 'Sage green'}, + {id: 320, name: 'Ghost grey'}, + {id: 321, name: 'Lilac'}, + {id: 322, name: 'Plum'}, + {id: 323, name: 'Olivine'}, + {id: 324, name: 'Laurel green'}, + {id: 325, name: 'Quill grey'}, + {id: 327, name: 'Crimson'}, + {id: 328, name: 'Mint'}, + {id: 329, name: 'Baby blue'}, + {id: 330, name: 'Carnation pink'}, + {id: 331, name: 'Persimmon'}, + {id: 332, name: 'Maroon'}, + {id: 333, name: 'Gold'}, + {id: 334, name: 'Daisy orange'}, + {id: 335, name: 'Pearl'}, + {id: 336, name: 'Fog'}, + {id: 337, name: 'Salmon'}, + {id: 338, name: 'Terra Cotta'}, + {id: 339, name: 'Cocoa'}, + {id: 340, name: 'Wheat'}, + {id: 341, name: 'Buttermilk'}, + {id: 342, name: 'Mauve'}, + {id: 343, name: 'Sunrise'}, + {id: 344, name: 'Tawny'}, + {id: 345, name: 'Rust'}, + {id: 346, name: 'Cashmere'}, + {id: 347, name: 'Khaki'}, + {id: 348, name: 'Lily white'}, + {id: 349, name: 'Seashell'}, + {id: 350, name: 'Burgundy'}, + {id: 351, name: 'Cork'}, + {id: 352, name: 'Burlap'}, + {id: 353, name: 'Beige'}, + {id: 354, name: 'Oyster'}, + {id: 355, name: 'Pine Cone'}, + {id: 356, name: 'Fawn brown'}, + {id: 357, name: 'Hurricane grey'}, + {id: 358, name: 'Cloudy grey'}, + {id: 359, name: 'Linen'}, + {id: 360, name: 'Copper'}, + {id: 361, name: 'Dirt brown'}, + {id: 362, name: 'Bronze'}, + {id: 363, name: 'Flint'}, + {id: 364, name: 'Dark taupe'}, + {id: 365, name: 'Burnt Sienna'}, + {id: 1001, name: 'Institutional white'}, + {id: 1002, name: 'Mid gray'}, + {id: 1003, name: 'Really black'}, + {id: 1004, name: 'Really red'}, + {id: 1005, name: 'Deep orange'}, + {id: 1006, name: 'Alder'}, + {id: 1007, name: 'Dusty Rose'}, + {id: 1008, name: 'Olive'}, + {id: 1009, name: 'New Yeller'}, + {id: 1010, name: 'Really blue'}, + {id: 1011, name: 'Navy blue'}, + {id: 1012, name: 'Deep blue'}, + {id: 1013, name: 'Cyan'}, + {id: 1014, name: 'CGA brown'}, + {id: 1015, name: 'Magenta'}, + {id: 1016, name: 'Pink'}, + {id: 1017, name: 'Deep orange'}, + {id: 1018, name: 'Teal'}, + {id: 1019, name: 'Toothpaste'}, + {id: 1020, name: 'Lime green'}, + {id: 1021, name: 'Camo'}, + {id: 1022, name: 'Grime'}, + {id: 1023, name: 'Lavender'}, + {id: 1024, name: 'Pastel light blue'}, + {id: 1025, name: 'Pastel orange'}, + {id: 1026, name: 'Pastel violet'}, + {id: 1027, name: 'Pastel blue-green'}, + {id: 1028, name: 'Pastel green'}, + {id: 1029, name: 'Pastel yellow'}, + {id: 1030, name: 'Pastel brown'}, + {id: 1031, name: 'Royal purple'}, + {id: 1032, name: 'Hot pink'} +]; + +class EditorItemCard extends Component { + constructor(props) { + super(props); + this.state = { + worn: false + }; + + this.wearAssetId = this.wearAssetId.bind(this); + } + + componentDidMount() { + if(this.props.item.Wearing) + this.setState({ worn: true }); + } + + wearAssetId(assetId) { + this.setState({ worn: !this.state.worn }); + if(this.state.worn) + this.props.unwearAssetId(assetId); + else + this.props.wearAssetId(assetId); + } + + render() { + var item = this.props.item; + + return ( +
+ + + +
+

{ item.Name }

+
+
+ +
+
+ ); + } +} + +class WardrobeTab extends Component { + constructor(props) { + super(props); + this.state = { + loaded: false, + assetTabs: [ + {label: 'Heads', typeId: 17}, + {label: 'Faces', typeId: 18}, + {label: 'Hats', typeId: 8}, + {label: 'T-Shirts', typeId: 2}, + {label: 'Shirts', typeId: 11}, + {label: 'Pants', typeId: 12}, + {label: 'Gear', typeId: 19}, + {label: 'Torsos', typeId: 27}, + {label: 'Left Arms', typeId: 29}, + {label: 'Right Arms', typeId: 28}, + {label: 'Left Legs', typeId: 30}, + {label: 'Right Legs', typeId: 31}, + {label: 'Packages', typeId: 32} + ], + pageKey: 0, + pageNumber: 1, + pages: 0, + items: [] + }; + + this.setTypeId = this.setTypeId.bind(this); + this.incrementPage = this.incrementPage.bind(this); + this.loadPage = this.loadPage.bind(this); + this.refresh = this.refresh.bind(this); + } + + componentDidMount() { + this.setTypeId(8, true); // XlXi: Bypass needed, else the initial page load isn't going to happen. + } + + setTypeId(assetTypeId, forceLoad = false) { + this.setState({ selectedTypeId: assetTypeId}, function() { + this.loadPage(1, !forceLoad); + }); + } + + refresh() { + this.loadPage(this.state.pageNumber); + } + + incrementPage(amount) { + this.loadPage(this.state.pageNumber + amount); + } + + loadPage(pageNum, noBypass = true) { + if(!this.state.loaded && noBypass) + return; + + this.setState({ loaded: false }); + + let oldPageNum = this.state.pageNumber; + this.setState({ pageNumber: pageNum }); + + axios.get(buildGenericApiUrl('api', `avatar/v1/list?assetTypeId=${this.state.selectedTypeId}${ pageNum > 1 ? '&page=' + pageNum : '' }`)) + .then(res => { + const items = res.data; + let newKey = this.state.pageKey + 1; + + this.setState({ pageKey: newKey, items: items.data, pages: items.pages, loaded: true }); + }) + .catch(() => { + this.props.setError('Error loading wardrobe page.'); + this.setState({ pageNumber: oldPageNum, loaded: true }); + }); + } + + render() { + return ( + <> + + { + !this.state.loaded + ? +
+ +
+ : + null + } + { + this.state.items.length == 0 + ? +

Nothing found.

+ : +
+ { + this.state.items.map((item, index) => + + ) + } +
+ } + { + this.state.pages > 1 ? + + : + null + } + + ); + } +} + +class OutfitsTab extends Component { + constructor(props) { + super(props); + } + + render() { + return (<>

outfits

); + } +} + +// TODO: XlXi: Move this out of this component. I was too lazy to do it initially but it can be done. +class BodyColorPaneBodyPart extends Component { + constructor(props) { + super(props); + this.state = { + color: 'vb-bc-194' + } + + this.setColor = this.setColor.bind(this); + } + + setColor(color) { + this.setState({ color: color }); + } + + render() { + return ( + + ) + } +} + +class BodyColorSelectionModal extends Component { + constructor(props) { + super(props); + this.state = { + }; + + this.ModalRef = createRef(); + this.Modal = null; + } + + componentDidMount() { + this.Modal = new Bootstrap.Modal(this.ModalRef.current); + this.Modal.show(); + + this.ModalRef.current.addEventListener('hidden.bs.modal', (event) => { + this.props.setModal(null); + }); + + var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="bc-tooltip"]')); + var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) { + return new Bootstrap.Tooltip(tooltipTriggerEl) + }); + } + + componentWillUnmount() { + this.Modal.dispose(); + } + + render() { + return ( +
+
+
+
+
Set Color
+ +
+
+
+
+ { + brickColors.map(({ id, name }) => +
this.props.setBodyColor(id) } data-bs-dismiss="modal" data-bs-toggle="bc-tooltip" data-bs-placement="top" title="" data-bs-original-title={ name }>
+ ) + } +
+
+
+
+
+
+ ); + } +} + +class BodyColorPane extends Component { + constructor(props) { + super(props); + this.state = { + loading: true, + showModal: false + }; + + this.bodyParts = [ + {name: 'Head', className: 'head', ref: createRef()}, + {name: 'Torso', className: 'torso', ref: createRef()}, + {name: 'RightArm', className: 'limb', ref: createRef()}, + {name: 'LeftArm', className: 'limb', ref: createRef()}, + {name: 'RightLeg', className: 'limb', ref: createRef()}, + {name: 'LeftLeg', className: 'limb', ref: createRef()} + ]; + + this.findPart = this.findPart.bind(this); + this.setModal = this.setModal.bind(this); + this.load = this.load.bind(this); + this.openColorModal = this.openColorModal.bind(this); + this.setBodyColor = this.setBodyColor.bind(this); + this.getBodyPartElem = this.getBodyPartElem.bind(this); + } + + findPart(name) { + return this.bodyParts.find(obj => { + return obj.name === name + }); + } + + setModal(modal = null) { + this.visibleModal = modal; + + if(modal) { + this.setState({'showModal': true}); + } else { + this.setState({'showModal': false}); + } + } + + load() { + this.setState({ loading: true }); + + axios.get(buildGenericApiUrl('api', 'avatar/v1/body-color')) + .then(res => { + Object.keys(res.data.data).map(key => { + let part = this.findPart(key); + part.ref.current.setColor(`vb-bc-${ res.data.data[key] }`); + }); + + this.setState({ loading: false }); + }) + .catch(() => { + this.props.setError('Error loading body colors pane.'); + this.setState({ loading: false }); + }); + } + + componentDidMount() { + this.load(); + } + + openColorModal(name) { + this.setModal( this.setBodyColor(name, color) } />); + } + + setBodyColor(name, color) { + let part = this.findPart(name); + part.ref.current.setColor(`vb-bc-${ color }`); + + this.props.setBodyColor(name, color); + } + + getBodyPartElem(name) { + let part = this.findPart(name); + + if(!part.elem) + { + let elem = createElement( + BodyColorPaneBodyPart, + { + className: part.className, + onClick: (() => this.openColorModal(part.name)), + ref: part.ref, + key: part.name + } + ); + part.elem = elem; + return elem + } + + return part.elem; + } + + render() { + return ( + <> + { + this.state.loading + ? +
+ +
+ : + null + } + + { this.getBodyPartElem('Head') } +
+ { this.getBodyPartElem('RightArm') } + { this.getBodyPartElem('Torso') } + { this.getBodyPartElem('LeftArm') } +
+
+ { this.getBodyPartElem('RightLeg') } + { this.getBodyPartElem('LeftLeg') } +
+ + { this.state.showModal ? this.visibleModal : null } + + ); + } +} + +class WearingPane extends Component { + constructor(props) { + super(props); + this.state = { + loading: true, + items: [], + pageKey: 0 + }; + + this.load = this.load.bind(this); + } + + componentDidMount() { + this.load(); + } + + load() { + this.setState({ loading: true }); + + axios.get(buildGenericApiUrl('api', 'avatar/v1/wearing')) + .then(res => { + const items = res.data; + let newKey = this.state.pageKey + 1; + + this.setState({ pageKey: newKey, items: items.data, loading: false }); + }) + .catch(() => { + this.props.setError('Error loading wearing pane.'); + this.setState({ pageNumber: oldPageNum, loading: false }); + }); + } + + render() { + return ( + <> + { + (this.state.loading) + ? +
+ +
+ : + null + } + { + this.state.items.length == 0 + ? +

Nothing found.

+ : +
+ { + this.state.items.map((item, index) => + + ) + } +
+ } + + ); + } +} + +class AvatarEditor extends Component { + constructor(props) { + super(props); + this.state = { + loading: false, + thumbnailLoading: false, + tabLoaded: false, + tabs: [ + {label: 'Wardrobe', name: 'wardrobe', ref: createRef()}, + {label: 'Outfits', name: 'outfits', ref: createRef()} + ], + error: null, + thumbKey: 0 + }; + + this.tabIndex = 0; + + this.wearingPane = createRef(); + this.bodyColorPane = createRef(); + this.tabPane = createRef(); + + this.setCurrentTab = this.setCurrentTab.bind(this); + this.setTab = this.setTab.bind(this); + this.redrawCharacter = this.redrawCharacter.bind(this); + this.reloadCharacter = this.reloadCharacter.bind(this); + this.setError = this.setError.bind(this); + this.thumbCallback = this.thumbCallback.bind(this); + this.wearAssetId = this.wearAssetId.bind(this); + this.unwearAssetId = this.unwearAssetId.bind(this); + this.setBodyColor = this.setBodyColor.bind(this); + } + + componentDidMount() { + this.setTab('wardrobe'); + } + + setCurrentTab(instance) + { + this.currentTab = instance; + this.setState({tabLoaded: true}); + } + + setTab(tabType) { + this.setState({tabLoaded: false}); + this.tabIndex += 1; + + let component = { + wardrobe: WardrobeTab, + outfits: OutfitsTab + }[tabType]; + + this.setCurrentTab(createElement( + component, + { + setError: this.setError, + reloadCharacter: this.reloadCharacter, + wearAssetId: this.wearAssetId, + unwearAssetId: this.unwearAssetId, + ref: this.tabPane, + key: this.tabIndex + } + )); + + this.state.tabs.map(({ name, ref }) => { + if(name == tabType) + ref.current.classList.add('active'); + else + ref.current.classList.remove('active'); + }); + } + + redrawCharacter() { + if(this.state.loading) return; + + this.setState({ loading: true }); + + axios.post(buildGenericApiUrl('api', 'avatar/v1/redraw')) + .then(res => { + this.reloadCharacter(); + this.setState({ loading: false }); + }) + .catch(() => { + this.setError('An error occurred while redrawing.'); + this.setState({ loading: false }); + }); + } + + setError(state) { + this.setState({ error: state }); + setTimeout(function(){ + this.setState({ error: null }); + }.bind(this), 2000); + } + + reloadCharacter() { + let thumbKey = this.state.thumbKey + 1; + + this.wearingPane.current.load(); + this.bodyColorPane.current.load(); + this.tabPane.current.refresh(); + + this.setState({ thumbKey: thumbKey }); + } + + thumbCallback(loading) { + this.setState({ thumbnailLoading: loading }); + } + + wearAssetId(assetId, callback) { + if(this.state.thumbnailLoading) return; + + axios.post(buildGenericApiUrl('api', `avatar/v1/wear?id=${ assetId }`)) + .then(res => { + this.reloadCharacter(); + if(callback) callback(); + }) + .catch(() => { + this.setError('An error occurred while trying to wear this item.'); + }); + } + + unwearAssetId(assetId, callback) { + if(this.state.thumbnailLoading) return; + + axios.post(buildGenericApiUrl('api', `avatar/v1/unwear?id=${ assetId }`)) + .then(res => { + this.reloadCharacter(); + if(callback) callback(); + }) + .catch(() => { + this.setError('An error occurred while trying to take off this item.'); + }); + } + + setBodyColor(name, color) { + if(this.state.thumbnailLoading) return; + + axios.post(buildGenericApiUrl('api', `avatar/v1/set-body-color?part=${ name }&color=${ color }`)) + .then(res => { + this.reloadCharacter(); + }) + .catch(() => { + this.setError('An error occurred while trying to change body color.'); + }); + } + + render() { + return ( + <> + { + this.state.error + ? +
{ this.state.error }
+ : + null + } +
+
+ { + this.state.loading + ? +
+ +
+ : + + } +
+

Is something wrong with your avatar?

+ Click here to re-draw it! + +

Colors

+
+ +
+
+
+ +
+ { this.state.tabLoaded ? this.currentTab : } +
+ +

Currently Wearing

+
+ +
+
+ + ); + } +} + +export default AvatarEditor; \ No newline at end of file diff --git a/web/resources/js/components/Shop.js b/web/resources/js/components/Shop.js index fd1d280..a4066a9 100644 --- a/web/resources/js/components/Shop.js +++ b/web/resources/js/components/Shop.js @@ -270,6 +270,7 @@ class Shop extends Component { super(props); this.state = { selectedCategoryId: -1, + pageKey: 0, pageItems: [], pageLoaded: true, pageNumber: null, @@ -332,11 +333,12 @@ class Shop extends Component { url += ((paramIterator++ == 0 ? '?' : '&') + `${key}=${data[key]}`); }); + let newKey = this.state.pageKey + 1; axios.get(url) .then(res => { const items = res.data; - this.setState({ pageItems: items.data, pageCount: items.pages, pageLoaded: true, error: false }); + this.setState({ pageKey: newKey, pageItems: items.data, pageCount: items.pages, pageLoaded: true, error: false }); }).catch(err => { const data = err.response.data; @@ -344,7 +346,7 @@ class Shop extends Component { if(data.errors) errorMessage = data.errors[0].message; - this.setState({ pageItems: [], pageCount: 1, pageNumber: 1, pageLoaded: true, error: errorMessage }); + this.setState({ pageKey: newKey, pageItems: [], pageCount: 1, pageNumber: 1, pageLoaded: true, error: errorMessage }); this.inputBox.current.focus(); }); } @@ -404,7 +406,7 @@ class Shop extends Component { (this.state.pageItems.length == 0 && !this.state.error) ?

Nothing found.

: -
+
{ this.state.pageItems.map((item, index) => diff --git a/web/resources/js/components/ThumbnailTool.js b/web/resources/js/components/ThumbnailTool.js index 3aeac27..fb81ff9 100644 --- a/web/resources/js/components/ThumbnailTool.js +++ b/web/resources/js/components/ThumbnailTool.js @@ -20,7 +20,7 @@ import Loader from './Loader'; axios.defaults.withCredentials = true; -const Scene = ({json}) => { +function Scene({json}) { const mtl = useLoader(MTLLoader, json.mtl); const obj = useLoader(OBJLoader, json.obj, (loader) => { mtl.preload(); @@ -29,7 +29,8 @@ const Scene = ({json}) => { let controls = useRef(); let midPoint; - useThree(({camera, scene}) => { + const {camera, scene} = useThree(); + useEffect(() => { let aabbMax = json.AABB.max; let aabbMin = json.AABB.min; aabbMax = new THREE.Vector3(aabbMax.x, aabbMax.y, aabbMax.z); @@ -53,27 +54,24 @@ const Scene = ({json}) => { camera.translateZ(0.5); // lighting - // FIXME: XlXi: if you toggle 3d on and off it'll create these twice - let ambient = new THREE.AmbientLight(0x878780); + const ambient = new THREE.AmbientLight(0x7F7F7F); scene.add(ambient); - let sunLight = new THREE.DirectionalLight(0xacacac); - sunLight.position.set(0.671597898, 0.671597898, -0.312909544).normalize(); + const sunLight = new THREE.DirectionalLight(0xFFFFFF); + sunLight.position.set(-0.17786, 0.2563, -0.2787).normalize(); scene.add(sunLight); - let backLight = new THREE.DirectionalLight(0x444444); - let backLightPos = new THREE.Vector3() + const backLight = new THREE.DirectionalLight(0xB3B3B8); + const backLightPos = new THREE.Vector3() .copy(sunLight.position) .negate() .normalize(); // inverse of sun direction backLight.position.set(backLightPos); scene.add(backLight); - }); - - useEffect(() => { + controls.current.target = midPoint; controls.current.update(); - }); + }, []); return ( <> @@ -98,8 +96,9 @@ class ThumbnailTool extends Component { super(props); this.state = { initialLoading: true, - loading: false, + loading: true, is3d: false, + image2d: null, seed3d: 0 }; @@ -112,6 +111,7 @@ class ThumbnailTool extends Component { let thumbnailElement = this.props.element; if (thumbnailElement) { this.thumbnail2d = thumbnailElement.getAttribute('data-asset-thumbnail-2d'); + this.thumbnail3d = thumbnailElement.getAttribute('data-asset-thumbnail-3d'); this.assetId = thumbnailElement.getAttribute('data-asset-id'); this.assetName = thumbnailElement.getAttribute('data-asset-name'); this.wearable = Boolean(thumbnailElement.getAttribute('data-wearable')); @@ -119,13 +119,29 @@ class ThumbnailTool extends Component { this.setState({ initialLoading: false }); + if(this.props.thumbLoadCallback) + this.props.thumbLoadCallback(true); + if(this.renderable3d && localStorage.getItem('vb-use-3d-thumbnails') === 'true') this.toggle3D(); + else + { + if(this.thumbnail2d && this.thumbnail2d.match(/^https?:\/\/api\./gi)) + this.loadThumbnail(this.thumbnail2d, false); + else + this.setState({ loading: false, image2d: this.thumbnail2d }); + } } } + componentDidUpdate(oldProps, oldState) + { + if(oldState.loading != this.state.loading && this.props.thumbLoadCallback) + this.props.thumbLoadCallback(this.state.loading); + } + loadThumbnail(url, is3d) { - axios.get(buildGenericApiUrl('api', url)) + axios.get(url) .then(res => { let data = res.data; @@ -149,7 +165,7 @@ class ThumbnailTool extends Component { this.setState({ loading: false, json3d: res.data }); }); } else { - this.setState({ loading: false }); + this.setState({ loading: false, image2d: data.data }); } } else { let lt = this.loadThumbnail; @@ -162,7 +178,7 @@ class ThumbnailTool extends Component { let is3d = !this.state.is3d; this.setState({ loading: true, is3d: is3d }); - this.loadThumbnail(`thumbnails/v1/try-asset?id=${this.assetId}&type=${this.state.is3d ? '3D' : '2D'}`, is3d); + this.loadThumbnail(buildGenericApiUrl('api', `thumbnails/v1/try-asset?id=${this.assetId}&type=${this.state.is3d ? '3D' : '2D'}`), is3d); } toggle3D() { @@ -171,70 +187,74 @@ class ThumbnailTool extends Component { this.setState({ loading: true, is3d: is3d, seed3d: Math.random() }); localStorage.setItem('vb-use-3d-thumbnails', is3d); - if(is3d) { - this.loadThumbnail(`thumbnails/v1/asset?id=${this.assetId}&type=3D`, true); - } else { - this.setState({ loading: false }); + if(is3d) + this.loadThumbnail(this.thumbnail3d, true); + else + { + if(this.thumbnail2d && this.thumbnail2d.match(/^https?:\/\/api\./gi)) + this.loadThumbnail(this.thumbnail2d, false); + else + this.setState({ loading: false }); } } render() { return ( <> - { - this.state.initialLoading - ? -
- -
- : - <> - { - this.state.loading - ? -
- -
- : - ( - this.state.is3d - ? - - - - - - : - - ) - } - { this.wearable || this.renderable3d ? -
- { - this.wearable ? - - : - null - } - { - this.renderable3d ? - - : - null - } + { + this.state.initialLoading + ? +
+
: - null - } - - } + <> + { + this.state.loading + ? +
+ +
+ : + ( + this.state.is3d + ? + + + + + + : + + ) + } + { this.wearable || this.renderable3d ? +
+ { + this.wearable ? + + : + null + } + { + this.renderable3d ? + + : + null + } +
+ : + null + } + + } ); } diff --git a/web/resources/js/pages/AvatarEditor.js b/web/resources/js/pages/AvatarEditor.js new file mode 100644 index 0000000..dc42be1 --- /dev/null +++ b/web/resources/js/pages/AvatarEditor.js @@ -0,0 +1,19 @@ +/* + Copyright © XlXi 2022 +*/ + +import $ from 'jquery'; + +import React from 'react'; +import { render } from 'react-dom'; + +import AvatarEditor from '../components/AvatarEditor'; + +const editorId = 'vb-avatar-editor'; + +$(document).ready(function() { + if (document.getElementById(editorId)) { + let eElem = document.getElementById(editorId); + render(, eElem); + } +}); \ No newline at end of file diff --git a/web/resources/sass/BrickColors.scss b/web/resources/sass/BrickColors.scss new file mode 100644 index 0000000..794ccba --- /dev/null +++ b/web/resources/sass/BrickColors.scss @@ -0,0 +1,218 @@ +// © XlXi 2023 + +$brickColors: ( + 1: #f2f3f3, // White + 2: #a1a5a2, // Grey + 3: #f9e999, // Light yellow + 5: #d7c59a, // Brick yellow + 6: #c2dab8, // Light green (Mint) + 9: #e8bac8, // Light reddish violet + 11: #80bbdb, // Pastel Blue + 12: #cb8442, // Light orange brown + 18: #cc8e69, // Nougat + 21: #c4281c, // Bright red + 22: #c470a0, // Med. reddish violet + 23: #0d69ac, // Bright blue + 24: #f5cd30, // Bright yellow + 25: #624732, // Earth orange + 26: #1b2a35, // Black + 27: #6d6e6c, // Dark grey + 28: #287f47, // Dark green + 29: #a1c48c, // Medium green + 36: #f3cf9b, // Lig. Yellowich orange + 37: #4b974b, // Bright green + 38: #a05f35, // Dark orange + 39: #c1cade, // Light bluish violet + 40: #ececec, // Transparent + 41: #cd544b, // Tr. Red + 42: #c1dff0, // Tr. Lg blue + 43: #7bb6e8, // Tr. Blue + 44: #f7f18d, // Tr. Yellow + 45: #b4d2e4, // Light blue + 47: #d9856c, // Tr. Flu. Reddish orange + 48: #84b68d, // Tr. Green + 49: #f8f184, // Tr. Flu. Green + 50: #ece8de, // Phosph. White + 100: #eec4b6, // Light red + 101: #da867a, // Medium red + 102: #6e99ca, // Medium blue + 103: #c7c1b7, // Light grey + 104: #6b327c, // Bright violet + 105: #e29b40, // Br. yellowish orange + 106: #da8541, // Bright orange + 107: #008f9c, // Bright bluish green + 108: #685c43, // Earth yellow + 110: #435493, // Bright bluish violet + 111: #bfb7b1, // Tr. Brown + 112: #6874ac, // Medium bluish violet + 113: #e4adc8, // Tr. Medi. reddish violet + 115: #c7d23c, // Med. yellowish green + 116: #55a5af, // Med. bluish green + 118: #b7d7d5, // Light bluish green + 119: #a4bd47, // Br. yellowish green + 120: #d9e4a7, // Lig. yellowish green + 121: #e7ac58, // Med. yellowish orange + 123: #d36f4c, // Br. reddish orange + 124: #923978, // Bright reddish violet + 125: #eab892, // Light orange + 126: #a5a5cb, // Tr. Bright bluish violet + 127: #dcbc81, // Gold + 128: #ae7a59, // Dark nougat + 131: #9ca3a8, // Silver + 133: #d5733d, // Neon orange + 134: #d8dd56, // Neon green + 135: #74869d, // Sand blue + 136: #877c90, // Sand violet + 137: #e09864, // Medium orange + 138: #958a73, // Sand yellow + 140: #203a56, // Earth blue + 141: #27462d, // Earth green + 143: #cfe2f7, // Tr. Flu. Blue + 145: #7988a1, // Sand blue metallic + 146: #958ea3, // Sand violet metallic + 147: #938767, // Sand yellow metallic + 148: #575857, // Dark grey metallic + 149: #161d32, // Black metallic + 150: #abadac, // Light grey metallic + 151: #789082, // Sand green + 153: #957977, // Sand red + 154: #7b2e2f, // Dark red + 157: #fff67b, // Tr. Flu. Yellow + 158: #e1a4c2, // Tr. Flu. Red + 168: #756c62, // Gun metallic + 176: #97695b, // Red flip/flop + 178: #b48455, // Yellow flip/flop + 179: #898788, // Silver flip/flop + 180: #d7a94b, // Curry + 190: #f9d62e, // Fire Yellow + 191: #e8ab2d, // Flame yellowish orange + 192: #694028, // Reddish brown + 193: #cf6024, // Flame reddish orange + 194: #a3a2a5, // Medium stone grey + 195: #4667a4, // Royal blue + 196: #23478b, // Dark Royal blue + 198: #8e4285, // Bright reddish lilac + 199: #635f62, // Dark stone grey + 200: #828a5d, // Lemon metalic + 208: #e5e4df, // Light stone grey + 209: #b08e44, // Dark Curry + 210: #709578, // Faded green + 211: #79b5b5, // Turquoise + 212: #9fc3e9, // Light Royal blue + 213: #6c81b7, // Medium Royal blue + 216: #8f4c2a, // Rust + 217: #7c5c46, // Brown + 218: #96709f, // Reddish lilac + 219: #6b629b, // Lilac + 220: #a7a9ce, // Light lilac + 221: #cd6298, // Bright purple + 222: #e4adc8, // Light purple + 223: #dc9095, // Light pink + 224: #f0d5a0, // Light brick yellow + 225: #ebb87f, // Warm yellowish orange + 226: #fdea8d, // Cool yellow + 232: #7dbbdd, // Dove blue + 268: #342b75, // Medium lilac + 301: #506d54, // Slime green + 302: #5b5d69, // Smoky grey + 303: #0010b0, // Dark blue + 304: #2c651d, // Parsley green + 305: #527cae, // Steel blue + 306: #335882, // Storm blue + 307: #102adc, // Lapis + 308: #3d1585, // Dark indigo + 309: #348e40, // Sea green + 310: #5b9a4c, // Shamrock + 311: #9fa1ac, // Fossil + 312: #592259, // Mulberry + 313: #1f801d, // Forest green + 314: #9fadc0, // Cadet blue + 315: #0989cf, // Electric blue + 316: #7b007b, // Eggplant + 317: #7c9c6b, // Moss + 318: #8aab85, // Artichoke + 319: #b9c4b1, // Sage green + 320: #cacbd1, // Ghost grey + 321: #a75e9b, // Lilac + 322: #7b2f7b, // Plum + 323: #94be81, // Olivine + 324: #a8bd99, // Laurel green + 325: #dfdfde, // Quill grey + 327: #970000, // Crimson + 328: #b1e5a6, // Mint + 329: #98c2db, // Baby blue + 330: #ff98dc, // Carnation pink + 331: #ff5959, // Persimmon + 332: #750000, // Maroon + 333: #efb838, // Gold + 334: #f8d96d, // Daisy orange + 335: #e7e7ec, // Pearl + 336: #c7d4e4, // Fog + 337: #ff9494, // Salmon + 338: #be6862, // Terra Cotta + 339: #562424, // Cocoa + 340: #f1e7c7, // Wheat + 341: #fef3bb, // Buttermilk + 342: #e0b2d0, // Mauve + 343: #d490bd, // Sunrise + 344: #965555, // Tawny + 345: #8f4c2a, // Rust + 346: #d3be96, // Cashmere + 347: #e2dcbc, // Khaki + 348: #edeaea, // Lily white + 349: #e9dada, // Seashell + 350: #883e3e, // Burgundy + 351: #bc9b5d, // Cork + 352: #c7ac78, // Burlap + 353: #cabfa3, // Beige + 354: #bbb3b2, // Oyster + 355: #6c584b, // Pine Cone + 356: #a0844f, // Fawn brown + 357: #958988, // Hurricane grey + 358: #aba89e, // Cloudy grey + 359: #af9483, // Linen + 360: #966766, // Copper + 361: #564236, // Dirt brown + 362: #7e683f, // Bronze + 363: #69665c, // Flint + 364: #5a4c42, // Dark taupe + 365: #6a3909, // Burnt Sienna + 1001: #f8f8f8, // Institutional white + 1002: #cdcdcd, // Mid gray + 1003: #111111, // Really black + 1004: #ff0000, // Really red + 1005: #ffaf00, // Deep orange + 1006: #b480ff, // Alder + 1007: #a34b4b, // Dusty Rose + 1008: #c1be42, // Olive + 1009: #ffff00, // New Yeller + 1010: #0000ff, // Really blue + 1011: #002060, // Navy blue + 1012: #2154b9, // Deep blue + 1013: #04afec, // Cyan + 1014: #aa5500, // CGA brown + 1015: #aa00aa, // Magenta + 1016: #ff66cc, // Pink + 1017: #ffaf00, // Deep orange + 1018: #12eed4, // Teal + 1019: #00ffff, // Toothpaste + 1020: #00ff00, // Lime green + 1021: #3a7d15, // Camo + 1022: #7f8e64, // Grime + 1023: #8c5b9f, // Lavender + 1024: #afddff, // Pastel light blue + 1025: #ffc9c9, // Pastel orange + 1026: #b1a7ff, // Pastel violet + 1027: #9ff3e9, // Pastel blue-green + 1028: #ccffcc, // Pastel green + 1029: #ffffcc, // Pastel yellow + 1030: #ffcc99, // Pastel brown + 1031: #6225d1, // Royal purple + 1032: #ff00bf // Hot pink +); + +@each $color-id, $color in $brickColors { + .vb-bc-#{$color-id} { + background-color: $color; + } +} \ No newline at end of file diff --git a/web/resources/sass/Variables.scss b/web/resources/sass/Variables.scss index 1dace50..6c314e6 100644 --- a/web/resources/sass/Variables.scss +++ b/web/resources/sass/Variables.scss @@ -1,4 +1,4 @@ -// © XlXi 2021 +// © XlXi 2023 @function tint-color($color, $percent){ @return mix(white, $color, $percent); diff --git a/web/resources/sass/VirtuBrick.scss b/web/resources/sass/VirtuBrick.scss index dbe75fb..2f3c848 100644 --- a/web/resources/sass/VirtuBrick.scss +++ b/web/resources/sass/VirtuBrick.scss @@ -1,10 +1,11 @@ -// © XlXi 2021 +// © XlXi 2023 @use "sass:math"; // Lumen 5.0.1 // Bootswatch +@import "BrickColors"; @import "./scss/fontawesome.scss"; @import "./scss/brands.scss"; @import "./scss/duotone.scss"; @@ -45,6 +46,22 @@ img.twemoji { // Shop + +.vb-avatar-editor-card { + height: 300px; +} + +.vb-wardrobe-nav > .nav-item > .nav-link { + padding-right: 0.5rem!important; + padding-left: 0.5rem!important; + padding-bottom: 0!important; + padding-top: 0!important; +} + +.vb-wardrobe-nav > .nav-item > .nav-link:not(:last-child) { + margin-left: 0.25rem!important; +} + .virtubrick-tokens { background: 0 1px/contain no-repeat url("/images/symbols/token.svg"); color: #e59800!important; @@ -154,6 +171,11 @@ img.twemoji { background-size: cover; } +.virtubrick-item-card > span > a > img { + background: url("/Images/Item-Image-Vignette.png"); + background-size: cover; +} + .virtubrick-game-card { @media (max-width: 576px) { width: math.div(100%, 2) @@ -176,6 +198,28 @@ img.twemoji { } } +.virtubrick-avatar-card { + @media (max-width: 576px) { + width: math.div(100%, 2) + } + + @media (min-width: 576px) { + width: math.div(100%, 3) + } + + @media (min-width: 768px) { + width: math.div(100%, 4) + } + + @media (min-width: 992px) { + width: math.div(100%, 5) + } + + @media (min-width: 1200px) { + width: 157px; + } +} + .virtubrick-shop-overlay { position: absolute; width: 100%; @@ -538,6 +582,65 @@ html { } } +// Avatar + +.vb-character { + border: 1px solid $gray-500; + transition: background-color .1s; + margin: 0.125rem; + + &-head { + margin: 0.125rem auto; + width: 50px!important; + height: 50px!important; + } + + &-limb { + width: 50px!important; + height: 100px!important; + } + + &-torso { + width: 104px!important; + height: 100px!important; + } +} + +$hex-size: 32px; +$hex-margin: .04rem; +$hex-calc: calc(1.732 * $hex-size + 4 * $hex-margin - 1px); + +.vb-hex { + display: flex; + text-align: left; + + &-container { + font-size: 0; + + & div { + width: $hex-size; + height: calc($hex-size * 1.1547); + margin: $hex-margin; + margin-bottom: calc($hex-margin - $hex-size * 0.2885); + display: inline-block; + font-size: initial; + clip-path: polygon(0% 25%, 0% 75%, 50% 100%, 100% 75%, 100% 25%, 50% 0%); + border: 0; + cursor: pointer; + } + + &::before { + content: ""; + width: calc($hex-size/2 + $hex-margin); + float: left; + height: 120%; + shape-outside: repeating-linear-gradient( + #0000 0 calc($hex-calc - 3px), + #000 0 $hex-calc); + } + } +} + // Navbar .navbar { diff --git a/web/resources/views/web/avatar/editor.blade.php b/web/resources/views/web/avatar/editor.blade.php index 38f9673..f520833 100644 --- a/web/resources/views/web/avatar/editor.blade.php +++ b/web/resources/views/web/avatar/editor.blade.php @@ -2,22 +2,39 @@ @section('title', 'Avatar Editor') +@section('page-specific') + +@endsection + @section('content') +
+

Todo:

+
    +
  • Section for wearing outfits
  • +
+

Avatar Editor

-
-
-
+
+
+

Colors

-
+
-
-