HISE Logo Forum
    • Categories
    • Register
    • Login

    WebView - Preset Browser not working in Standalone but works in HISE?

    Scheduled Pinned Locked Moved Unsolved General Questions
    6 Posts 2 Posters 106 Views
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • HISEnbergH
      HISEnberg
      last edited by

      I'm just testing out creating a preset browser in Webview but I notice that simple functions like loading a preset are not working (at least in Standalone). My example basically follows the HISE-Tutorial example so I am trying to figure out where I am going wrong.

      const var wv = Content.getComponent("WebView1");
      
      const var uph = Engine.createUserPresetHandler();
      const var webroot = FileSystem.getFolder(FileSystem.AudioFiles).getParentDirectory().getChildFile("Images/new-preset-browser");
      const var userPresetObject = [];
      
      
      wv.reset();
      wv.set("enableCache", true); // false for development
      wv.set("enablePersistence", true);
      wv.setIndexFile(webroot.getChildFile("index.html"));
      

      So I enableCache for the compiled plugin Binary. The rest of my script is pretty well identical to the tutorial.

      Perhaps it is my lack of understanding around the UserPresetHandler which is causing me issues here (I haven't edited any of the Project Settings)? I'll test the HISE-tutorial version as well to see if the issue is the same!

      In HISE (responsive):

      hise.gif

      In Standalone (unresponsive):
      stndln.gif

      HISEnbergH 1 Reply Last reply Reply Quote 0
      • HISEnbergH
        HISEnberg @HISEnberg
        last edited by

        It looks like it is similar behaviour in the tutorial version as well. I also ran this test by closing HISE first then launching the standalone app. @Christoph-Hart is this a bug or am I failing to understand something regarding the Webview or User Preset Handler?

        ScreenRecording2025-10-08at7.19.25AM-ezgif.com-video-to-gif-converter.gif

        HISEnbergH 2 Replies Last reply Reply Quote 0
        • HISEnbergH
          HISEnberg @HISEnberg
          last edited by

          Just wanted to put a bump on this if anyone has any ideas!

          HISEnbergH 1 Reply Last reply Reply Quote 0
          • HISEnbergH
            HISEnberg @HISEnberg
            last edited by

            This post is deleted!
            1 Reply Last reply Reply Quote 0
            • HISEnbergH
              HISEnberg @HISEnberg
              last edited by

              @Straticah just carrying over the conversation from here:

              https://forum.hise.audio/topic/7859/webview-not-scaling-with-zoom-factor/20

              Just wondering since you said your preset browser was working, did you build it from the tutorial? I am actually having quite a number of issues even getting it working so I am wondering if there is something I missed?

              StraticahS 1 Reply Last reply Reply Quote 0
              • StraticahS
                Straticah @HISEnberg
                last edited by Straticah

                @HISEnberg Yes HISE tutorial then custom UI instead of the simple HTML list.

                I never compiled as standalone tho?

                here is my adjusted script from Example:
                Its probably not usable and makes it even harder to debug but might give enough context.

                The HSIE example worked inside a VST for me i believe.

                /** Custom User Preset Browser using a webview!
                
                	This example project shows how to use a webview to implement a 
                	custom preset browser using a HTML webview.
                	
                	It supports bidirectional synchronisation, so if you load a user preset,
                	it will update the selection in the webview and vice versa!
                	
                	The state here is just a single knob that will send it's value to the webview to
                	be displayed.
                	
                	The HTML side only consists of a javascript file that defines the
                	callbacks and populates a standard HTML select element. You probably want to use something
                	more fancy in the end, but the communication will be pretty similar.
                */
                
                // All const var declarations must be at the very top in HISE
                const var userPresetObject = [];
                const var expansions = Engine.getExpansionList();
                const var expansionInfo = [];
                const var wv = Content.getComponent("WebView1");
                const var webroot = FileSystem.getFolder(FileSystem.AudioFiles).getParentDirectory().getChildFile("Images/preset-browser");
                const var uph = Engine.createUserPresetHandler();
                var expansion = null;
                var expansionPresetFolder = null;
                var expansionPreset = null;
                var userPresetsFolder = null;
                var expansionFolders = [];
                var presetPath = null;
                var pathParts = null;
                var webviewInitialized = false; // Flag to prevent re-initialization
                var expansionFolderName = null;
                var folderExists = false;
                var j = 0;
                var k = 0;
                var expansionObject = null;
                var rootFolder = null;
                var folderName = null;
                var wildcardRef = null;
                var sourceArtwork = null;
                var artworkFolder = null;
                var targetFileName = null;
                var targetFile = null;
                const var initTimer = Engine.createTimerObject();
                const var startupTimer = Engine.createTimerObject();
                const var presetTimer = Engine.createTimerObject();
                
                //Content.makeFrontInterface(600, 600);
                
                // This is required because JS will mess up the "\\" path on Windows
                inline function getEscapedFilePath(file)
                {
                	return file.toString(0).replace("\\", "/");
                }
                
                // Let's build up the preset database. For our example we'll use an array of
                // JSON objects with a `name` and a `file` key that will be consumed by the Webview and
                // populate the select element, but you're free to attach whatever other things you might need
                
                // Get root presets
                for(userPreset in FileSystem.findFiles(FileSystem.getFolder(FileSystem.UserPresets), "*.preset", true))
                {
                	userPresetObject.push({
                		"name": userPreset.toString(1),
                		"file": getEscapedFilePath(userPreset)
                	});
                }
                
                // Get expansion presets
                Console.print("Found " + expansions.length + " expansions");
                
                // Collect expansion info for the webview
                var expansionFolders = []; // Track unique expansion folder names
                
                for(i = 0; i < expansions.length; i++)
                {
                	expansion = expansions[i];
                	Console.print("Processing expansion: " + expansion.getExpansionType());
                	
                	// Try to get expansion root folder and look for UserPresets subfolder
                	expansionPresetFolder = expansion.getRootFolder();
                	
                	if(expansionPresetFolder)
                	{
                		Console.print("Expansion root folder: " + expansionPresetFolder.toString(0));
                		
                	// Look for UserPresets folder within the expansion
                	userPresetsFolder = expansionPresetFolder.getChildFile("UserPresets");
                	
                	if(userPresetsFolder)
                	{
                		Console.print("Found UserPresets folder in expansion: " + userPresetsFolder.toString(0));
                		
                		// Get presets from this expansion's UserPresets folder
                		for(expansionPreset in FileSystem.findFiles(userPresetsFolder, "*.preset", true))
                		{
                			userPresetObject.push({
                				"name": expansionPreset.toString(1),
                				"file": getEscapedFilePath(expansionPreset)
                			});
                			
                			// Extract expansion folder name from the preset path (0.2.2 working approach)
                			presetPath = getEscapedFilePath(expansionPreset);
                			pathParts = presetPath.split("/");
                			
                			// Look for the expansion folder name in the path
                			// Typically: .../Expansions/ExpansionName/UserPresets/...
                			for(j = 0; j < pathParts.length; j++)
                			{
                				if(pathParts[j] == "Expansions" && j + 1 < pathParts.length)
                				{
                					expansionFolderName = pathParts[j + 1];
                					// Check if we already have this folder name
                					folderExists = false;
                					for(k = 0; k < expansionFolders.length; k++)
                					{
                						if(expansionFolders[k] == expansionFolderName)
                						{
                							folderExists = true;
                							break;
                						}
                					}
                					if(!folderExists)
                					{
                						expansionFolders.push(expansionFolderName);
                						Console.print("Found expansion folder: " + expansionFolderName);
                					}
                					break;
                				}
                			}
                		}
                	}
                	else
                	{
                		Console.print("No UserPresets folder found in expansion: " + expansion.getExpansionType());
                	}
                	}
                	else
                	{
                		Console.print("No root folder found for expansion: " + expansion.getExpansionType());
                	}
                }
                
                // Convert array to expansion info for the webview
                // Metadata for known expansions: descriptions and tags
                var expansionMeta = {
                	"Hardware-Lab": {
                		"description": "A collection of distorted, textured sounds with that classic, vintage synth touch. Expect experimental tones, heavy synth sounds, glitches, and that analog character processed through hardware pedals.",
                		"tags": "Analog, Bright, Vintage"
                	},
                	"Immersive": {
                		"description": "Cinematic sounds inspired by legendary film composers like Hans Zimmer and Ludwig Göransson, but also epic composers in the rap industry such as Mike Dean. Cinematic, Melodic, and built for epic atmospheres.",
                		"tags": "Epic, Melodic, Cinematic"
                	},
                	"Pro-Essentials": {
                		"description": "Your go-to expansion of proven sounds, first designed after studying the sound behind various hit songs of popular albums. Everything from simple and essential sounds to carefully processed and reliable tones.",
                		"tags": "Essential, Polished, Reliable"
                	}
                };
                
                for(i = 0; i < expansionFolders.length; i++)
                {
                	// Use the expansion handler approach - get the expansion object
                	expansionObject = null;
                	for(j = 0; j < expansions.length; j++)
                	{
                		expansion = expansions[j];
                		rootFolder = expansion.getRootFolder();
                		if(rootFolder)
                		{
                			// Handle Windows/Mac path compatibility
                			var rootPath = rootFolder.toString(0).replace("\\", "/");
                			folderName = rootPath.split("/").pop();
                			if(folderName === expansionFolders[i])
                			{
                				expansionObject = expansion;
                				break;
                			}
                		}
                	}
                	
                	// Use specific metadata if available, otherwise fallback
                	var meta = expansionMeta[expansionFolders[i]];
                	var expansionDescription = meta ? meta.description : ("High-quality presets for " + expansionFolders[i]);
                	var expansionTags = meta ? meta.tags : "";
                	
                	// Use HISE founder's method - generate complete HTML with {EXP::...} syntax
                	// Use exact expansion folder name (with spaces) for {EXP::...} syntax
                	var expansionNameFormatted = expansionFolders[i]; // Keep original name with spaces
                	var expansionHTML = '<img class="expansion-cover" src="{EXP::' + expansionNameFormatted + '}artwork.png" alt="' + expansionFolders[i] + ' cover art" onerror="this.style.display=\'none\';" />';
                	
                	Console.print("Generated HTML for " + expansionFolders[i] + ": " + expansionHTML);
                	
                	expansionInfo.push({
                		"name": expansionFolders[i],
                		"type": "expansion", 
                		"description": expansionDescription,
                		"tags": expansionTags,
                		"htmlContent": expansionHTML
                	});
                }
                
                Console.print("Total presets found: " + userPresetObject.length);
                Console.print("Total expansion folders found: " + expansionInfo.length);
                
                // Debug: Show what we're sending to the webview
                Console.print("Expansion info being sent to webview:");
                for(i = 0; i < expansionInfo.length; i++)
                {
                	Console.print("  " + expansionInfo[i].name + " - " + expansionInfo[i].coverArt);
                }
                	
                // Grab a reference to our little budddy
                
                // That's a bit ugly, but we grab the image folder by getting the HISE project AudioFiles folder
                // and then doing some next level-hacking to get a subdirectory of the Images folder which I think is a nice
                // place for web stuff...
                
                // Call this to make sure that there is no intermediate temp stuff being carried around
                wv.reset();
                
                // set the index file (this is already the default, but I'll call it here so that you know it exists)
                wv.setIndexFile(webroot.getChildFile("index.html"));
                
                // Enable caching for better performance
                //wv.set("enableCache", true);
                
                wv.set("enablePersistence", true);
                
                // Initialize webview with proper timing
                inline function initializeWebview()
                {
                	Console.print("=== INITIALIZING WEBVIEW ===");
                	
                	// Wait a bit for webview to be ready, then populate data
                	initTimer.setTimerCallback(function() {
                		Console.print("=== POPULATING WEBVIEW DATA (TIMER) ===");
                		Console.print("Current HISE zoom level: " + Settings.getZoomLevel());
                		
                		// Now we call the updatePresetList function that is defined in the Javascript of the Webview 
                		// (and tucked onto the global `window` object).
                		wv.callFunction("updatePresetList", userPresetObject);
                		
                		// Also populate the left panel with expansion info
                		wv.callFunction("updateExpansionPanel", expansionInfo);
                		
                		Console.print("=== WEBVIEW DATA POPULATED (TIMER) ===");
                		
                		this.stopTimer();
                	});
                	initTimer.startTimer(500); // Wait 500ms for webview to be ready
                }
                
                // Add webview ready callback to ensure data is sent when webview is actually ready
                wv.bindCallback("webviewReady", function(args)
                {
                	Console.print("=== WEBVIEW READY CALLBACK TRIGGERED ===");
                	Console.print("Current HISE zoom level: " + Settings.getZoomLevel());
                	
                	// Send data to webview now that it's confirmed ready
                	// HISE should handle webview scaling automatically
                	wv.callFunction("updatePresetList", userPresetObject);
                	wv.callFunction("updateExpansionPanel", expansionInfo);
                	
                	Console.print("=== DATA SENT TO READY WEBVIEW ===");
                });
                
                // Note: Scale factor handling removed - letting HISE handle webview scaling natively
                // The webview should automatically scale when HISE's zoom level changes
                
                // Initialize the webview with a delay to ensure it's ready
                startupTimer.setTimerCallback(function() {
                	Console.print("=== STARTING WEBVIEW INITIALIZATION ===");
                	initializeWebview();
                	this.stopTimer();
                });
                startupTimer.startTimer(200); // Wait 200ms before initializing
                
                inline function onKnob1Control(component, value)
                {
                	// This just simulates the communication from HISE -> WebView
                	// (the function window.updateValue() is defined in browser.js)
                	wv.callFunction("updateValue", value);
                };
                
                Content.getComponent("Knob1").setControlCallback(onKnob1Control);
                
                // This binding will call the given function when the Webview is
                // calling the loadUserPreset function (which happens in the onselection callback)
                wv.bindCallback("loadUserPreset", function(args)
                {
                	Console.print("LOAD FROM WEBVIEW");
                	Console.print("Args received: " + args);
                	Console.print("Args[0]: " + args[0]);
                	Console.print("Args[0] type: " + typeof args[0]);
                	Console.print("Checking if args[0] == '__REQUEST_CURRENT_PRESET__': " + (args[0] == "__REQUEST_CURRENT_PRESET__"));
                	
                	// Check if this is a test from filter click
                	if (args[0] == "__FILTER_CLICKED_TEST__") {
                		Console.print("🎯 FILTER CLICKED - BROWSER CAN CALL HISE!");
                		return;
                	}
                	
                	// Check if this is a request for current preset
                	if (args[0] == "__REQUEST_CURRENT_PRESET__") {
                		Console.print("=== REQUEST CURRENT PRESET FROM WEBVIEW ===");
                		Console.print("getUserPresetList() returned: " + Engine.getUserPresetList().length + " presets");
                		Console.print("uph object available: " + (typeof uph !== 'undefined'));
                		
                		if (typeof uph !== 'undefined') {
                			Console.print("uph.getCurrentUserPresetFile available: " + (typeof uph.getCurrentUserPresetFile === 'function'));
                			Console.print("getCurrentUserPresetFile() result: " + uph.getCurrentUserPresetFile());
                			
                			if (uph.getCurrentUserPresetFile()) {
                				Console.print("Sending back to webview: " + getEscapedFilePath(uph.getCurrentUserPresetFile()));
                				wv.callFunction("setSelectedPreset", getEscapedFilePath(uph.getCurrentUserPresetFile()));
                			} else {
                				Console.print("No current preset file found");
                			}
                		}
                		Console.print("=== END REQUEST CURRENT PRESET ===");
                	} else {
                		Engine.loadUserPreset(FileSystem.fromAbsolutePath(args[0]));
                	}
                });
                
                // This binding will call the given function when the Webview is
                // calling the savePresetInHise function (which happens when saving a preset)
                wv.bindCallback("savePresetInHise", function(args)
                {
                	Console.print("=== SAVE PRESET CALLBACK TRIGGERED ===");
                	Console.print("SAVE FROM WEBVIEW");
                	Console.print("Args received: " + args);
                	Console.print("Args type: " + typeof args);
                	Console.print("Args length: " + (args ? args.length : "null"));
                	
                	if (args && args.length > 0) {
                		var filePath = args[0];
                		Console.print("File path received: " + filePath);
                		
                		// Create folder if the path contains a slash (indicating subfolder)
                		var userPresetsDirectory = FileSystem.getFolder(FileSystem.UserPresets);
                		Console.print("UserPresets directory: " + userPresetsDirectory.toString(0));
                		
                		// Handle Windows/Mac path compatibility for folder detection
                		var normalizedPath = filePath.replace("\\", "/");
                		if (normalizedPath.contains("/")) {
                			var folder = normalizedPath.split("/")[0];
                			Console.print("Creating folder: " + folder);
                			var folderCreated = userPresetsDirectory.createDirectory(folder);
                			Console.print("Directory created: " + folderCreated);
                		}
                		
                		// Create a File object from the path
                		var presetFile = userPresetsDirectory.getChildFile(filePath);
                		Console.print("Created file object: " + presetFile.toString(0));
                		
                		// Save the current preset to this file (remove .preset extension for Engine.saveUserPreset)
                		var presetPath = presetFile.toString(0).replace(".preset", "");
                		Console.print("Preset path for Engine.saveUserPreset: " + presetPath);
                		
                		// Call Engine.saveUserPreset with the path string
                		Engine.saveUserPreset(presetPath);
                		Console.print("✅ Engine.saveUserPreset() called successfully");
                		Console.print("✅ Preset save process completed");
                	} else {
                		Console.print("❌ Invalid preset data received");
                		Console.print("Expected: file path string");
                		Console.print("Received: " + args);
                	}
                	
                	Console.print("=== END SAVE PRESET DEBUG ===");
                });
                
                // This binding will call the given function when the Webview is
                // calling the overridePreset function (which happens when overriding current preset)
                wv.bindCallback("overridePreset", function(args)
                {
                	Console.print("=== OVERRIDE PRESET CALLBACK TRIGGERED ===");
                	Console.print("OVERRIDE FROM WEBVIEW");
                	
                	// Get the current user preset file path
                	if (typeof uph !== 'undefined' && typeof uph.getCurrentUserPresetFile === 'function') {
                		var currentPresetFile = uph.getCurrentUserPresetFile();
                		Console.print("Current preset file: " + currentPresetFile);
                		
                		if (currentPresetFile) {
                			// Extract the path without extension for Engine.saveUserPreset
                			var presetPath = currentPresetFile.toString(0).replace(".preset", "");
                			Console.print("Preset path for Engine.saveUserPreset: " + presetPath);
                			
                			// Call Engine.saveUserPreset with the current preset path
                			Engine.saveUserPreset(presetPath);
                			Console.print("✅ Engine.saveUserPreset() called successfully for override");
                			Console.print("✅ Preset override process completed");
                			
                			// Send success callback to webview
                			wv.callFunction("onOverrideSuccess", currentPresetFile.toString(0));
                		} else {
                			Console.print("❌ No current preset file found - cannot override");
                			wv.callFunction("onOverrideError", "No current preset selected");
                		}
                	} else {
                		Console.print("❌ UserPresetHandler not available");
                		wv.callFunction("onOverrideError", "UserPresetHandler not available");
                	}
                	
                	Console.print("=== END OVERRIDE PRESET DEBUG ===");
                });
                
                // This will attach a WebView function call to anytime a user preset is loaded
                // and will update the selection (window.setSelectedPreset is defined in browser.js)
                
                uph.setPostCallback(function(presetFile)
                {
                	Console.print("=== PRESET LOADED CALLBACK ===");
                	Console.print("Raw presetFile: " + presetFile);
                	Console.print("Type of presetFile: " + typeof presetFile);
                	Console.print("Escaped path: " + getEscapedFilePath(presetFile));
                	Console.print("=== END PRESET LOADED CALLBACK ===");
                	
                	wv.callFunction("setSelectedPreset", getEscapedFilePath(presetFile));
                });
                
                // Handle request for current preset selection from webview
                Console.print("=== BINDING REQUEST CURRENT PRESET CALLBACK ===");
                wv.bindCallback("requestCurrentPreset", function(args)
                {
                	Console.print("=== REQUEST CURRENT PRESET FROM WEBVIEW ===");
                	
                	// Try to get current preset using Engine API
                	Console.print("getUserPresetList() returned: " + Engine.getUserPresetList().length + " presets");
                	
                	// Try to get current preset file from uph
                	Console.print("uph object available: " + (typeof uph !== 'undefined'));
                	
                	if (typeof uph !== 'undefined') {
                		Console.print("uph.getCurrentUserPresetFile available: " + (typeof uph.getCurrentUserPresetFile === 'function'));
                		
                		Console.print("getCurrentUserPresetFile() result: " + uph.getCurrentUserPresetFile());
                		
                		if (uph.getCurrentUserPresetFile()) {
                			Console.print("Sending back to webview: " + getEscapedFilePath(uph.getCurrentUserPresetFile()));
                			wv.callFunction("setSelectedPreset", getEscapedFilePath(uph.getCurrentUserPresetFile()));
                		} else {
                			Console.print("No current preset file found");
                		}
                	}
                	
                	Console.print("=== END REQUEST CURRENT PRESET ===");
                });
                Console.print("=== REQUEST CURRENT PRESET CALLBACK BOUND ===");
                
                // Trigger preset selection update when webview loads
                // This handles the case where plugin reopens with a preset already loaded
                
                Console.print("=== PRESET SELECTION HANDLED BY UPH CALLBACK ===");
                
                // This is just a zoom factor combobox that demonstrates the correct scaling
                // of the WebView - HISE handles webview scaling natively
                inline function onZoomFactorControl(component, value)
                {
                	if(value)
                	{
                		Settings.setZoomLevel(component.getItemText());
                		Console.print("Zoom changed to: " + component.getItemText() + " - HISE will handle webview scaling automatically");
                	}
                };
                
                Content.getComponent("ZoomFactor").setControlCallback(onZoomFactorControl);
                

                Also make sure if offline you have persistance and cache enabled.
                I also clear the exported_webviews folder from time to time

                If it works in HISE it usually works in VST build aswell at least on my end.
                The scaling and initializing bugs i had were both in HISE and build.

                creating user interfaces: www.vst-design.com
                founder @prototype.audio https://www.prototype.audio/

                1 Reply Last reply Reply Quote 1
                • First post
                  Last post

                25

                Online

                2.0k

                Users

                12.6k

                Topics

                109.8k

                Posts