diff --git a/spine-ts/player/example/generator/embedding-generator.html b/spine-ts/player/example/generator/embedding-generator.html
new file mode 100644
index 000000000..29e82c7d7
--- /dev/null
+++ b/spine-ts/player/example/generator/embedding-generator.html
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Choose .skel/.json, .atlas, and .png files, or drop them here.
+
+
+
+
+
+
+
+ General
+ Animations
+ Viewports
+ Skins
+ Debug
+
+
+
+ Animations
+
+
+ Viewports
+
+
+ Skins
+
+
+ Debug
+
+
+
+
+
\ No newline at end of file
diff --git a/spine-ts/player/example/generator/embedding-generator.js b/spine-ts/player/example/generator/embedding-generator.js
new file mode 100644
index 000000000..5f5d451cd
--- /dev/null
+++ b/spine-ts/player/example/generator/embedding-generator.js
@@ -0,0 +1,401 @@
+window.addEventListener("load", function(event) {
+ setupDropZone();
+});
+
+if (!String.prototype.endsWith) {
+ String.prototype.endsWith = function(search, this_len) {
+ if (this_len === undefined || this_len > this.length) {
+ this_len = this.length;
+ }
+ return this.substring(this_len - search.length, this_len) === search;
+ };
+}
+
+var appState = {
+ dataUrls: null,
+ jsonFile: null,
+ skelFile: null,
+ atlasFile: null,
+ minorVersion: null,
+ majorVersion: null,
+ player: null
+}
+
+function loadFiles(files) {
+ var skels = 0;
+ var skelFile = null;
+ var jsons = 0;
+ var jsonFile = null;
+ var atlases = 0;
+ var atlasFile = null;
+ var pngs = 0;
+
+ for (var i = 0; i < files.length; i++) {
+ var file = files[i].name.toLowerCase();
+ if (file.endsWith(".skel")) {
+ skels++;
+ skelFile = file;
+ }
+ if (file.endsWith(".json")) {
+ jsons++;
+ jsonFile = file;
+ }
+ if (file.endsWith(".atlas")) {
+ atlases++;
+ atlasFile = file;
+ }
+ if (file.endsWith(".png")) pngs++;
+ }
+
+ if ((skels == 0 && jsons == 0) || (skels != 0 && jsons != 0) || skels > 1 || jsons > 1) {
+ showError("Please specify a single .skel or .json file.");
+ return;
+ }
+
+ if (atlases != 1) {
+ showError("Please specify a single .atlas file.");
+ return;
+ }
+
+ var filesToLoad = files.length;
+ var dataUrls = {};
+ for (var i = 0; i < files.length; i++) {
+ var file = files[i];
+ var reader = new FileReader();
+ reader.onload = function(file) {
+ return function(dataUrl) {
+ console.log("Loaded " + file.name);
+ dataUrls[file.name] = dataUrl.target.result;
+ filesToLoad--;
+ if (filesToLoad == 0) {
+ setupPlayer(dataUrls, jsonFile, skelFile, atlasFile);
+ }
+ };
+ }(file);
+ reader.onerror = function () {
+ showError("Sorry, couldn't load all files.");
+ }
+ reader.readAsDataURL(file);
+ }
+}
+
+function setupPlayer(dataUrls, jsonFile, skelFile, atlasFile) {
+ var version = getSkeletonVersion(dataUrls, jsonFile, skelFile);
+ var major = parseInt(version.split("\.")[0]);
+ var minor = parseInt(version.split("\.")[1]);
+
+ appState.dataUrls = dataUrls;
+ appState.jsonFile = jsonFile;
+ appState.skelFile = skelFile;
+ appState.atlasFile = atlasFile;
+ appState.majorVersion = major;
+ appState.minorVersion = minor;
+
+ if (major == 3 && minor < 8) {
+ showError("Couldn't load script for Spine version " + version + ". Only skeletons with version >= 3.8 are supported.");
+ return;
+ }
+
+ var cssUrl = "https://esotericsoftware.com/files/spine-player/" + major + "." + minor + "/spine-player.css";
+ spine = null;
+ loadCSS(cssUrl, function () {
+ var playerUrl = "https://esotericsoftware.com/files/spine-player/" + major + "." + minor + "/spine-player.js";
+ loadJavaScript(playerUrl, function() {
+ document.getElementById("sp_generator_editor").classList.remove("sp_generator_hidden");
+ document.getElementById("sp_generator_drop_zone").classList.add("sp_generator_hidden");
+ var player = document.getElementById("sp_generator_player");
+ player.innerHTML = "";
+
+ var config = {
+ jsonUrl: jsonFile,
+ skelUrl: skelFile,
+ atlasUrl: atlasFile,
+ rawDataURIs: dataUrls,
+ success: setupConfigUI,
+ alpha: true // needed so we can emulate shizzle
+ };
+
+ appState.player = new spine.SpinePlayer(player, config);
+
+ }, function() {
+ showError("Couldn't load script for Spine version " + version + ". Only skeletons with version 3.8+ are supported.");
+ });
+ }, function () {
+ showError("Couldn't load CSS for Spine version " + version + ". Only skeletons with version 3.8+ are supported.");
+ });
+}
+
+function setupConfigUI() {
+ // Setup tabs
+ var tabs = document.getElementsByClassName("sp_generator_tabs")[0];
+ var children = tabs.getElementsByTagName("span");
+ for (var i = 0; i < children.length; i++) {
+ (function (tab) {
+ tab.onclick = function () {
+ var panelId = tab.getAttribute("data-tab");
+ var panels = document.getElementById("sp_generator_config").getElementsByClassName("sp_generator_panel");
+ for (var i = 0; i < panels.length; i++) {
+ var panel = panels[i];
+ if (panelId == panel.getAttribute("id")) {
+ tab.classList.add("sp_generator_selected_tab");
+ panel.classList.remove("sp_generator_hidden");
+ } else {
+ tab.classList.remove("sp_generator_selected_tab");
+ panel.classList.add("sp_generator_hidden");
+ }
+ }
+ }
+ })(children[i]);
+ }
+
+ // Fill general tab
+ var showControls = document.getElementById("sp_generator_show_controls");
+ showControls.onchange = function () {
+ appState.player.config.showControls = showControls.checked;
+ };
+ var canvasAlpha = document.getElementById("sp_generator_canvas_alpha");
+ canvasAlpha.onchange = function () {
+ var re = /[0-9A-Fa-f]{2}/g;
+ if (canvasAlpha.value.length > 2 || !re.test(canvasAlpha.value))
+ canvasAlpha.value = "FF";
+ else
+ canvasAlpha.value = canvasAlpha.value.toUpperCase();
+ var alpha = Number.parseInt(canvasAlpha.value, 16);
+ appState.player.config.alpha = alpha != 0xff;
+ appState.player.config.backgroundColor = document.getElementById("sp_generator_background").value + canvasAlpha.value;
+ }
+ var premultipliedAlpha = document.getElementById("sp_generator_premultiplied_alpha");
+ var premultipliedAlpha = document.getElementById("sp_generator_premultiplied_alpha");
+ premultipliedAlpha.onchange = function () {
+ appState.player.config.premultipliedAlpha = premultipliedAlpha.checked;
+ }
+ var backgroundImage = document.getElementById("sp_generator_background_image");
+ backgroundImage.innerHTML = "";
+ var noneImage = document.createElement("option");
+ noneImage.value = "none";
+ noneImage.innerText = "None";
+ noneImage.selected = true;
+ backgroundImage.append(noneImage);
+ for(var data in appState.dataUrls) {
+ if (data.toLowerCase().endsWith(".png")) {
+ var image = document.createElement("option");
+ image.value = data;
+ image.innerText = data;
+ backgroundImage.append(image);
+ }
+ }
+ backgroundImage.onchange = function() {
+ var imageUrl = backgroundImage.value;
+ if (imageUrl != "none" && !appState.player.assetManager.get(imageUrl)) {
+ appState.player.assetManager.loadTexture(imageUrl);
+ }
+
+ if (appState.player.config.backgroundImage) {
+ appState.player.config.backgroundImage.url = imageUrl != "none" ? imageUrl: null;
+ } else {
+ appState.player.config.backgroundImage = {
+ url: imageUrl != "none" ? imageUrl : null
+ }
+ }
+ }
+
+ // Fill animations tab
+
+ // Fill viewports tab
+
+ // Fill skins tab
+
+ // Fill debug tab
+}
+
+function changeBackgroundColor(background) {
+ appState.player.config.backgroundColor = background.valueElement.value + document.getElementById("sp_generator_canvas_alpha").value;
+}
+
+function changeFullscreenBackgroundColor(background) {
+ appState.player.config.fullScreenBackgroundColor = background.valueElement.value;
+}
+
+function getSkeletonVersion(dataUrls, jsonFile, skelFile) {
+ if (jsonFile) {
+ var json = JSON.parse(atob(dataUrls[jsonFile].split(',')[1]));
+ return json.skeleton.spine;
+ } else {
+ var bytes = atob(dataUrls[skelFile].split(',')[1]);
+ var array = new Uint8Array(new ArrayBuffer(bytes.length));
+ for (var i = 0; i < bytes.length; i++) {
+ array[i] = bytes.charCodeAt(i);
+ }
+
+ var input = new BinaryInput(array);
+ input.readString();
+ var version = input.readString();
+ return version;x
+ }
+}
+
+function loadJavaScript(url, success, error) {
+ var script = document.createElement('script');
+ script.setAttribute('src', url);
+ script.setAttribute('type', 'text/javascript');
+ script.onload = success;
+ script.onerror = error;
+ document.getElementsByTagName("head")[0].appendChild(script);
+};
+
+function loadCSS(url, success, error) {
+ var script = document.createElement('link');
+ script.setAttribute('href', url);
+ script.setAttribute('rel', 'stylesheet');
+ script.onload = success;
+ script.onerror = error;
+ document.getElementsByTagName("head")[0].appendChild(script);
+ };
+
+function showError(error) {
+ alert(error);
+}
+
+function setupDropZone() {
+ var fileButton = document.getElementById("sp_generator_file_button");
+ var dropZone = document.getElementById("sp_generator_drop_zone");
+ dropZone.onclick = function() {
+ fileButton.click();
+ };
+ dropZone.addEventListener("dragenter", function (event) {
+ event.stopPropagation();
+ event.preventDefault();
+ }, false);
+ dropZone.addEventListener("dragover", function (event) {
+ event.stopPropagation();
+ event.preventDefault();
+ }, false);
+ dropZone.addEventListener("drop", function (event) {
+ event.stopPropagation();
+ event.preventDefault();
+
+ loadFiles(event.dataTransfer.files);
+ }, false);
+
+
+ fileButton.onchange = function () {
+ loadFiles(fileButton.files);
+ fileButton.value = "";
+ };
+}
+
+function generateScript(jsonFile, skelFile, atlasFile, dataUrls) {
+ var shortVersion = major + "." + minor;
+ var scriptCode =
+ '