other
75 TopicsDrawing Annotation on Storyline Slide
Demo: https://360.articulate.com/review/content/518383b2-1161-408d-b9f5-adb9a6f57a11/review Inspired by code discussed on: https://img.ly/blog/how-to-draw-on-an-image-with-javascript/ Using Charts in your Storyline | Articulate - Community About This is a brief example of using an HTML canvas element to enable annotation on a Storyline slide. The example displays an image on a slide, sets a few variables, and the accessibility tags of a some slide objects. It then triggers a JavaScript trigger to create a canvas over the image, and then watches the mouse buttons and movement to allow drawing on the canvas. How it works A canvas element is created, filled with the specified base image, and inserted below a small rectangle (canvasAnchor) that is the same width as and placed directly above the image. Another rectangle (canvasClickArea) overlays and is sized to match the image. This is the area that allows drawing on the canvas (where the mouse is watched). Brush width and color can be controlled. The drawing can be cleared. It also resizes with the slide. To improve The events to watch the mouse and clear button should be better handled to allow removal when a new canvas is created. A mechanism to allow a blank base (clear) should be reinstated. Right now it just relies on the use of the initial image from the slide. Both options have their uses. Since the canvas is a raster image, resizing to very small and then very large results in poor image quality. The image can be extracted from the canvas. This could be saved or printed. More drawing options are available with the canvas element. Credit: X-ray images from https://learningradiology.com/204Views6likes8CommentsXLF Version 2.1.
I have subscribed to the Advance version of DeepL as a translating tool. DeepL requires an XLF 2.1. version for translation but Rise 360 only export in version 1.2. Has anyone been able to solve the problem when exporting for translation? Can Articulate update Rise export XLF files for translation to a 2.1. version? ThanksSolved519Views5likes67CommentsFrequent discussion board issues
Hi there, I'd like to report a lot of frequent issues with the new discussion board. Here's what I frequently experience throughout the week since the new community has been launched: Selecting reply will frequently result in this: Sometimes when I reply, my post disappears, so I reply again, it disappears, so I reply again, it disappears etc. Sometime later all of my replies show up making it look like I'm spamming a post. Sometimes the reply and like buttons are disabled and cannot be selected. Sometimes when I go to my profile, no discussion load. Just an empty profile. Sometimes when I select a link to add video the From video URL link isn't there. The software just seems really buggy and seems a bit hit and miss whether I'm going to be able to post, reply or view issues. I'm on a very stable broadband connection (fibre to the office), and when I've had issues, have checked my connection and has been fine. Hopefully these get resolved quickly as I just give up sometimes. If this is happening to other people frequently, it's going to impact the community. Cheers, Sam42Views3likes3CommentsArticulate Status and Software
I'd like to open a conversation about disconnecting the use of Storyline from the need to constantly authenticate. I am currently in the middle of an important lesson that has a very specific amount of time budgeted for it, and have essentially been locked out of it. As a government contractor creating emergency training material for use in mission-critical space scenarios, I can't afford to keep defending the use of software that is controlled off-premises, and that can be shut down without warning. We have to fight to negotiate for the time and money to create critical training, and it is unhelpful to be signed out of or wholesale lose access randomly. This, the budding inclusion of AI tools, and the heightened need for cybersecurity awareness makes for a convincing argument to move to a different tool platform that does not need to call home to be used. Please consider giving us better options going forward, to ensure that this work stoppage does not hinder future project timelines.166Views3likes10CommentsArticulate, let's talk pricing...
With the recent release of AI-powered features, a new pricing model is now available for Articulate 360: Let’s take a look at what this actually includes: Storyline 360 with a few AI features: The industry-standard tool for e-learning designers. Some like to call it a "glorified PowerPoint," but it's undeniably powerful. Rise 360: A simplified, web-based version that’s easy to use. Stock Library: A collection of assets to enhance your projects. Screen Recorder: While functional, it's a bit outdated and lacks some advanced features. A few other smaller apps and a review tool to gather feedback. To give you some context, here’s how the pricing for Adobe Creative Cloud stacks up. This includes all the apps (except the 3D Substance suite) with AI functionality integrated into their key products like Photoshop, After Effects, Premiere, and more. Adobe’s offering is over $1,000 cheaper than Articulate’s new AI-feature pricing, per year! And that’s with access to a large suite of tools, many of them advanced and complex: Now, I understand software development isn’t cheap, and Articulate has a strong foothold in the e-learning market, which is fairly niche. But personally, the pricing feels very steep. What do you think? Let’s keep this conversation civil and focused on the topic please.103Views2likes2CommentsScoring User Drawn Images in Storyline
Huh, my whole previous post just vanished. Trying again... This is a follow-up to a previous discussion post on drawing annotations on a Storyline slide. In a previous post, I demonstrated an approach allowing the user to draw on an image to indicate some kind of response. It utilized a canvas element and intercepted mouse-clicks and movements to draw paths. The next step, as pointed out by Math Notermans, was to score the user’s input in some way. There are several JavaScript libraries available that perform image comparisons, usually returning some kind of quantified pixel difference as a result. Resemble.js is one such option. It returns differences as a percentage of pixels compared to the entire image size. The question is then, how to turn this into usable score? Demo: https://360.articulate.com/review/content/d96de9cf-2fd1-45a5-a41a-4a35bf5a1735/review In this example, I made a few improvements to the annotation script that was posted previously. Most notably, I added a simple undo option that records and recreates user drawings. This also allows for the user’s drawing to maintain its sharpness after resize events, instead of the previous approach of straight scaling. I also changed it to allow drawing on a touch screen (limited testing). I included a loader for Resemble.js, and some code connected to the Check button to evaluate what the user has drawn. While this example is really just meant to demonstrate the process and help you visualize the results, the idea could easily be applied to some kind of complex user interaction that is not better served by more traditional point-and-click or drag-and-drop selections. As this demo shows, it could be particularly well-suited for having users determine the proper pathway for something in a more free-response fashion, as opposed to just selecting things from a list, or dropping shapes. After drawing a response to the prompt, clicking Check will generate a score. The score is based on the comparison of the user’s response to predetermined keys, which are images that you include when building the interaction. I used two keys here, one for the ‘correct’ answer, and one for a ‘close’ answer. You can set it up to include more key options if you need more complexity. Since all we get from Resemble is a difference score, we need to convert that into a similarity score. To do that, I followed these steps. Copy the key images to individual canvases. Create a blank canvas for comparisons. Convert these and the user drawing canvas to blobs to send to Resemble. Compare the user drawing to the blank (transparent) canvas get its base difference. Compare each of the keys in the same way to get their base difference scores. These, along with the visualized differences, are shown on the top three inset images. Then, compare each key with the user drawing to get the compared differences. The comparison order needs to be consistent here. These are shown on the lower two inset images. Calculate the similarity scores (this will be slightly different between scenarios, so you need to customize it to create the score ranges you expect. The similarity is essentially a score that ranges from 0 to 1, with 1 being the most similar. When creating your keys, you need to note what brush sizes and colors you are using. Those should be specified to the user, or preset for best results. Resemble has some comparison options, but you want to make the user’s expected response as similar to the key as you can. For the ‘Correct’ answer: The similarity is just: 1 - (compared difference) / (user base difference + key base difference) To properly range this from 0 to 1, we make also make some adjustments. We cap the (user + key) sum at 100%, and then set the Similarity floor to 0. We also divide this result by an adjustment factor. This factor is essentially the best uncorrected score you could achieve by drawing the result on the slide. Here, I could not really get much over 85%, so we normalize this to become 100%. Next, we do an adjustment that weighs the total area of the ‘Correct’ key to the total area drawn by the user. If the user draws a lot more or less than the correct answer actually contains, we do not want the result to be unduly affected. This eliminates much of the influence caused by scribbling rough answers across the general correct location. Before, scribbling could often increase the final score. This fixed that. Our adjustment is to multiply our current similarity score by: (the lesser of the user or key drawing base differences) / (the square of the greater of the base differences) We use the square in the denominator to ensure that drawing too much or too little will rapidly decrease the overall similarity score. We again cap this final adjusted similarity score at 1, ensuring a working range of 0 to 1. For the ‘Close’ answer: The idea is similar, but may need adjustment. If your close answer is similar in size to the correct answer, then the same procedure will work. In our case, I used a region around the correct answer to give partial credit. This region is roughly 2 times the size of the correct answer. As a result, we only expect a reasonable answer to cover about 50% of the close answer at best, so our minimum compared difference should be about half of the key base difference value. To compensate, we add an additional adjustment factor for the ratio between ‘close’ and ‘correct’ answers (here 2). We set our other adjustment factor like we did before, with the highest achievable uncorrected score (which unsurprisingly is about 0.4 now instead of 0.85). The Final Score is just the greater of the similarity scores times a weighting factor (1 for ‘correct’, 0.8 for ‘close’), converted to a percentage. To improve To make this more useful, you would probably want to load it on the master slide, and toggle triggers from your other slides to make comparisons. Rearrange the code to only call for the processing of the keys and blank canvas once per slide, or only after resizing, instead of each time Check is clicked to save some overhead. Probably should actively remove the previous canvas elements and event handlers when replaced. This uses a bunch of callback functions while processing blobs and comparing images, which requires several interval timers to know when each step is complete before starting the next. Might be able to do it better using promises, or restructuring the code a bit. I think Resemble just works on files, blobs, and dataURIs (e.g., base64 encoded images). Haven’t checked if it can work directly from elements or src links, but I don’t think so. Probably should load Resemble from static code to ensure functionality. Could also load key images from files instead of slide objects. That might be easier for users to locate and view however. There are other library options for comparing images. Some may be faster or more suited to your needs. If they produce a difference score, then the same approach should mostly apply. Fix the sliding aspect of the slide on mobile when drawing with touch.24Views1like0CommentsIssue with Execute JavaScript Trigger in Storyline 360
I am working on a project in Articulate Storyline 360 and I'm trying to execute a JavaScript function when the timeline starts on a slide. The function is supposed to replace the default values of project text variables with specific messages based on the slide number. Here is the JavaScript code I'm using: function setTexts(slideNumber) { console.log("setTexts function called with slideNumber: " + slideNumber); // Log the function call var correctTexts = [ "This is a credit card statement with the amount of money that student owes.", "This document is intended to give a record of purchases and payments. It gives the card holder a summary of how much the card has been used during the billing period, as well as the amount that is due for that billing cycle." ]; var incorrectPromptTexts = [ "Here's a hint, there are dollar amounts on there and itemized activity, what type of document best fit these?", "Think about why you would need a statement for bills, please try again." ]; var studentUnderstandTexts = [ "Got it! I always get these confused with my car insurance for some reason.", "Yes now that I think about it, having an itemized record is very helpful, even if it's quite annoying to always get these in the mail." ]; var positiveFeedbackTexts = [ "_user_ you answered correctly! Awesome job!", "_user_ you got the question correct!", "_user_ you answered correctly awesome job!", "_user_! You answered correctly!", "_user_! You answered the question right!", "_user_, You answered right!", "_user_, You answered the question right!", "_user_, you answered correctly! Keep it up!", "_user_, You answered the question right! Keep up the great work", "_user_ you are doing great! Keep it up!" ]; var negativeFeedbackTexts = [ "_user_, sorry. the answer you gave wasn't what I was looking for.", "_user_, your answer was not quite right.", "_user_, It looks like you picked the wrong answer.", "_user_, I'm afraid the answer you chose wasn't the best one." ]; var player = GetPlayer(); // Log the text being set for each variable var correctText = correctTexts[slideNumber - 1]; console.log("Setting Correct to: " + correctText); player.SetVar("Correct", correctText); var incorrectPromptText = incorrectPromptTexts[slideNumber - 1]; console.log("Setting IncorrectPrompt to: " + incorrectPromptText); player.SetVar("IncorrectPrompt", incorrectPromptText); var studentUnderstandText = studentUnderstandTexts[slideNumber - 1]; console.log("Setting StudentUnderstand to: " + studentUnderstandText); player.SetVar("StudentUnderstand", studentUnderstandText); // Randomly select positive and negative feedback var randomPositiveFeedback = positiveFeedbackTexts[Math.floor(Math.random() * positiveFeedbackTexts.length)]; var randomNegativeFeedback = negativeFeedbackTexts[Math.floor(Math.random() * negativeFeedbackTexts.length)]; console.log("Setting PositiveFeedbacktoUser to: " + randomPositiveFeedback); player.SetVar("PositiveFeedbacktoUser", randomPositiveFeedback); console.log("Setting NegativeFeedbacktoUser to: " + randomNegativeFeedback); player.SetVar("NegativeFeedbacktoUser", randomNegativeFeedback); } // Call the function with the current slide number setTexts(GetPlayer().GetCurrentSlide().GetSlideNumber()); When I preview the project, the console provides the following message: bootstrapper.min.js:2 actionator::exeJavaScript - Script1 is not defined I have defined the setTexts function and ensured that it is called correctly in the "Execute JavaScript" trigger, but the error persists. Steps I've Taken: Defined thesetTextsfunction. Added the function callsetTexts(GetPlayer().GetCurrentSlide().GetSlideNumber());in the "Execute JavaScript" trigger. Verified that the function is being called correctly. Any help or suggestions on how to resolve this issue would be greatly appreciated. Thank you!14Views1like0CommentsSelling my course to different learning centers with different LMS
Hi everyone! As I'm new to this, would love to get your advice on the matter. I Have a couple of courses made in Storyline, which I would like to sell to different learning centers, each center uses its own different LMS (some are well known, some less). Since I don't have control over the learning center's LMS, what's my best option? Should I provide the SCORM package somehow? Can I upload my courses in a different format? I don't want to send out the zip files for the different centers to upload to their own LMS, because I want to keep my courses protected. What do you think is my best option? Thanks!74Views1like4CommentsHow to Communicate Between Storyline and a mySQL Database
Communicating With a mySQL Database from Inside Storyline 360. Ever wondered how to connect to a database from inside Storyline? Need to run your course from a webserver instead of an LMS and don’t have access to a Learning Record Store to save data? Want to pull data from a large collection that can’t be included in your project? Just want to learn something new? If any of these sound like you, then you may be interested in this article. Demo: https://360.articulate.com/review/content/c3f3c563-bde2-4fa1-96e4-c8ab5b55f991/review Note: If the database in the demo stops working, it is probably because I forgot to renew it. This free site requires weekly renewal. I’ve been toying with parts of this on and off over the last couple of years. When I saw this question come up recently, I thought it might be time to put it all together. This approach was drawn from several online resources, but the specifics on database connections came from this very informative video series. What You Need Storyline 360 An online web server with PHP available A mySQL database that is accessible from the web Some knowledge of JavaScript Passing familiarity with reading and editing a PHP script Basic understanding of mySQL queries (and how to get your data into a database) Overview The overall process is that you build a Storyline slide that includes a web object. This does not need to be visible, but it will point to a web site on your web server that includes an index.php or index.html file (more on that later). You will use some JavaScript to pass your data from Storyline to the web object. The index file on your web server will receive this data. It then determines what to do with the data you passed and sends requests to the database. The database responds to the request, returning new data. The web server then sends this data back through the web object, to Storyline. From there, you can do whatever you want with it. In keeping with the original question, this example queries a database of users using a username that you type. It then returns biographical information and image data to Storyline, which displays it on the slide. In Storyline The required variables are: action - “fetch” for this example, can be made into whatever action you want username – the username you entered in the text entry bio, loc, name – receives data returned from the database (for display) imgTag_1 (and 2) – these are the images used to display the returned image data Since we need to communicate through the web object, the JavaScript used in the trigger first checks to see if the iframe is ready on the slide (bottom of script). It won’t appear until ready, so we need to wait until it is. When it’s ready, it calls the postMessage function. This builds your message from the action and your data (username), specifies who is supposed to receive it (approvedTarget), and uses postMessage to send it to the iframe. The function also creates an event handler to listen for return messages from the iframe. This is where any data returned from the database will get processed. This handler first checks to see if the received message came from where we expected (approvedOrigin). If so, then we use the data found in event.data. For this example, the returned data is a delimited string holding the name, location, biography, image URL, and base-64 image data from the database. Each entry is separated by a double quote. How you assemble and return the data string is up to you (more later). Here, we send the information back to Storyline variables and use the image data to swap the displayed images in our tagged slide pictures. If an empty string is returned, then nothing matched our request. On the Web Server On your web server, you need a basic webpage (index.php) and a folder with a couple of other files. The easiest way to create these files is to create a new folder somewhere on your PC. Inside this, create a file called “index.php”. You also need to secure your website against allowing people to browse your files. In this folder, create a file called “.htaccess”. Make sure the first letter is a period. Inside that file, enter the following one line of text, and then save this file: Options -Indexes Now create another folder called “includes”. Inside includes, create two files called “formhandler.inc.php” and “dbh.inc.php”. When we are done editing these files, you can zip the index.php file and the includes folder together to upload to your website folder on the web server. Then unzip them and you’re ready to go. Delete the zip file after unzipping. index.php (or index.html) This is your webpage. All you need is the basics: <!DOCTYPE html> <html> <head> </head> <body> <script> //JavaScript goes here </script> </body> </html> You will include some JavaScript in this file that will receive the message you sent from Storyline. It will decide how to handle the data you included and then it will make a POST request to transfer that data to another file in the includes folder. That is where the database communication will occur. The POST request here is akin to filling out a web form and clicking a submit button. This page will also listen for return messages from the POST request, and then, in turn, return the data in these messages back to Storyline. The index.php file contains the <script></script> section. This holds the JavaScript. This script has a few functions and a main routine. The main routine first creates an event handler to listen for messages sent from Storyline. It verifies the origin sent with the one specified here to make sure the message is one you want to process. If not, it is ignored. The passed message data is split apart (using comma delimiters). It looks for “fetch” or “put” actions in the first position (we only use fetch here). You can change these to whatever suits your needs (adjust formhandler.inc.php accordingly). Fetch will send your specified username to the database and then return its response. Because we need to wait for the response, we want to use an asynchronous function here. First, we create a request object (for sending the POST). I used XMLHttpRequest because I know how, but there are other possibly better ways. Then we set up an event handler to lister for this object to say it finished its task. If it succeeded, then we can call another function that will return the message with the database data to Storyline. Once the handler is set up, we make the POST request with the data bound for the database and then exit, waiting for a response. formhandler.inc.php The POST request goes to the PHP script on this page. Where JavaScript runs on your webpages in the browser (as you use it Storyline), PHP runs only on the server. It runs before the webpages go to the browser, so it is invisible. No one can see what goes on here. This PHP script verifies that the call to this page was for a POST. If not, it is ignored. If POST, get the passed variable (username). Then establish a database connect by including another PHP script file (dbh.inc.php). Then, we create a mySQL query statement. The passed data is bound separately to the query (instead of including it in the query directly). This separation prevents malicious data from hijacking your query. The statement is executed. We expect a single row to match, so we use fetch() to get it. We assemble the fetched data into a string delimited by a double quote character (use whatever is appropriate for your data). When ready, we use echo to output the text string, which is what gets returned to the POST request. <?php //Note: don’t use a closing tag in this script, leave it open if ($_SERVER["REQUEST_METHOD"] == "POST") { // get data from mySql, and echo output to return the data to the requester $username = $_POST["username"]; try { require_once "dbh.inc.php";//has DB connection information $query = "SELECT * FROM users WHERE username = :username;"; $stmt = $pdo->prepare($query); $stmt->bindParam(":username", $username); $stmt->execute(); $row = $stmt->fetch();//get next line of data (all requested fields) $sep = '"';//specify data seperator for returned data //echo text to return the data to the POST requesting routine echo $row["name"].$sep.$row["location"].$sep.$row["bio"].$sep.$row["url"].$sep.$row["image"];//get column contents //clear the database connecction $pdo = null; $stmt = null; die();//exit script } catch (PDOException $e) { die("Query failed: " . $e->getMessage());//if connection problem, exit script } } else { die("Invalid Request");//if not a POST request, exit script } dbh.inc.php This PHP script is included into the previous script. It contains the connection details for the database and creates the connection. Update the database host, database name, database username, and database password with your own information. Do not share it. <?php //Note: don’t use a closing tag in this script, leave it open $dsn = "mysql:host=hhhhhh;dbname=nnnnnnn"; $dbusername = "uuuuuuuuu"; $dbpassword = "ppppppppp"; try { $pdo = new PDO($dsn, $dbusername, $dbpassword); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch (PDOException $e) { echo "Connection failed: " . $e->getMessage(); } Final Thoughts Requesting data from the database requires the asynchronous function so your script isn’t stuck waiting for a response. Just sending data to the database (the “put” section of the script in the index.php file) does not have to wait, so it uses a synchronous function. To add additional functionality, you can create more action tags, pass them to index.php, and either make POST requests to additional formhandler script files, or update the existing fornhandler.inc.php file to accommodate more functions. You can have your database on or separate from your web server. As long as you can communicate with it, this should work. I am unfamiliar with other types of databases, so I don’t know what specific changes might be required to connect to them. The database structure is as shown below.142Views1like3CommentsProblems with flashcard grid since update
Hi there Since the recent update, there has been issues appearing in our microlearnings, specifically with the flashcard boxes. They look fine in edit mode but it doesn't look right in the preview or published versions. Images attached to show the difference. Had a similar issue in the same learning with the numbered list where it was spilling over into the next block - removed the image background from the numbered list and made it a plain colour which corrected it. Any ideas or solutions? Thanks Sam94Views1like6Comments