tag:blogger.com,1999:blog-2262230732054169552024-03-13T14:17:28.670-06:00Mr. Hay's TechnologyVarious technology-related musings.David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.comBlogger224125tag:blogger.com,1999:blog-226223073205416955.post-21140022488791411722023-11-27T06:27:00.000-07:002023-11-27T06:27:00.180-07:00Countdown Timer in Google Sheets<p> <a href="https://blogger.googleusercontent.com/img/a/AVvXsEicLcUIanUV-nzKshAup-tzRUBanx2jANXvd3_uNP9z4NDNVGnS7_CjV00aEQkR9Rk9vEVooTnhj73NJPpETcvrC5JMgeBce4e1N5EcQC4x1sxymBYR4tzv4d5uIG4eRmeyzQudRWt-UlVi1lswzcfveb300aJyZMGuTQdl6e6DyaG1TDLsD_KWNlf-" style="margin-left: 1em; margin-right: 1em; text-align: center;"><img alt="" data-original-height="272" data-original-width="299" height="240" src="https://blogger.googleusercontent.com/img/a/AVvXsEicLcUIanUV-nzKshAup-tzRUBanx2jANXvd3_uNP9z4NDNVGnS7_CjV00aEQkR9Rk9vEVooTnhj73NJPpETcvrC5JMgeBce4e1N5EcQC4x1sxymBYR4tzv4d5uIG4eRmeyzQudRWt-UlVi1lswzcfveb300aJyZMGuTQdl6e6DyaG1TDLsD_KWNlf-" width="264" /></a></p><p>If you've ever wanted to have a Google Sheet up the screen together with a countdown timer, for example if you have a <a href="https://haytech.blogspot.com/2016/03/creating-leaderboard-using-google-drive.html" target="_blank">pivot table leaderboard</a> for a <a href="https://www.callysto.ca/classroom-services/" target="_blank">hackathon</a>, you can use Google Apps Script.</p><p>From your Google Sheet, click on the <b>Extensions</b> menu and choose <b>Apps Script</b> to open the script editor.</p><p>Paste in the following code into the editor:</p><p><br /></p><pre class="javascript" style="font-family: monospace;"><span style="color: #000066; font-weight: bold;">function</span> onOpen<span style="color: #009900;">(</span>e<span style="color: #009900;">)</span> <span style="color: #009900;">{</span>
<span style="color: #000066; font-weight: bold;">var</span> ui <span style="color: #339933;">=</span> SpreadsheetApp.<span style="color: #660066;">getUi</span><span style="color: #009900;">(</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span>
ui.<span style="color: #660066;">createMenu</span><span style="color: #009900;">(</span><span style="color: #3366cc;">'Timer'</span><span style="color: #009900;">)</span>
.<span style="color: #660066;">addItem</span><span style="color: #009900;">(</span><span style="color: #3366cc;">'Countdown Timer in Sidebar'</span><span style="color: #339933;">,</span> <span style="color: #3366cc;">'showSidebar'</span><span style="color: #009900;">)</span>
.<span style="color: #660066;">addToUi</span><span style="color: #009900;">(</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span>
<span style="color: #009900;">}</span>
<span style="color: #000066; font-weight: bold;">function</span> showSidebar<span style="color: #009900;">(</span><span style="color: #009900;">)</span> <span style="color: #009900;">{</span>
<span style="color: #000066; font-weight: bold;">var</span> html <span style="color: #339933;">=</span> HtmlService.<span style="color: #660066;">createHtmlOutputFromFile</span><span style="color: #009900;">(</span><span style="color: #3366cc;">'countdown'</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span>
html.<span style="color: #660066;">setTitle</span><span style="color: #009900;">(</span><span style="color: #3366cc;">'Countdown Timer'</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span>
SpreadsheetApp.<span style="color: #660066;">getUi</span><span style="color: #009900;">(</span><span style="color: #009900;">)</span>.<span style="color: #660066;">showSidebar</span><span style="color: #009900;">(</span>html<span style="color: #009900;">)</span><span style="color: #339933;">;</span>
<span style="color: #009900;">}</span></pre><p><br /></p><p>Then click the <b>+</b> button near the top left to add a new file, and choose <b>HTML</b>. Type <b>countdown</b> as the file name, then paste the following code into the editor:</p><pre class="javascript" style="background-color: white; box-sizing: border-box; color: #212529; flex-shrink: 0; font-size: 0.875em; margin-bottom: 1rem; margin-top: var(--bs-gutter-y); max-width: 100%; overflow: auto; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); width: 854px;"><pre class="javascript" style="box-sizing: border-box; flex-shrink: 0; font-size: 0.875em; margin-bottom: 1rem; margin-top: var(--bs-gutter-y); max-width: 100%; overflow: auto; padding-left: calc(var(--bs-gutter-x) * .5); padding-right: calc(var(--bs-gutter-x) * .5); width: 854px;"><span style="box-sizing: border-box; color: #339933;"><!</span>DOCTYPE html<span style="box-sizing: border-box; color: #339933;">></span>
<span style="box-sizing: border-box; color: #339933;"><</span>html<span style="box-sizing: border-box; color: #339933;">></span>
<span style="box-sizing: border-box; color: #339933;"><</span>head<span style="box-sizing: border-box; color: #339933;">></span>
<span style="box-sizing: border-box; color: #339933;"><</span>base target<span style="box-sizing: border-box; color: #339933;">=</span><span style="box-sizing: border-box; color: #3366cc;">"_top"</span><span style="box-sizing: border-box; color: #339933;">></span>
<span style="box-sizing: border-box; color: #339933;"></</span>head<span style="box-sizing: border-box; color: #339933;">></span>
<span style="box-sizing: border-box; color: #339933;"><</span>body<span style="box-sizing: border-box; color: #339933;">></span>
<span style="box-sizing: border-box; color: #339933;"><</span>div id<span style="box-sizing: border-box; color: #339933;">=</span><span style="box-sizing: border-box; color: #3366cc;">"buttons"</span><span style="box-sizing: border-box; color: #339933;">></span>
<span style="box-sizing: border-box; color: #339933;"><</span>button id<span style="box-sizing: border-box; color: #339933;">=</span><span style="box-sizing: border-box; color: #3366cc;">"thirtyMinutes"</span> onclick<span style="box-sizing: border-box; color: #339933;">=</span><span style="box-sizing: border-box; color: #3366cc;">"thirtyMinutes()"</span><span style="box-sizing: border-box; color: #339933;">></span><span style="box-sizing: border-box; color: #cc0000;">30</span> minutes<span style="box-sizing: border-box; color: #339933;"></</span>button<span style="box-sizing: border-box; color: #339933;">><</span>br<span style="box-sizing: border-box; color: #339933;">></span>
<span style="box-sizing: border-box; color: #339933;"><</span>button id<span style="box-sizing: border-box; color: #339933;">=</span><span style="box-sizing: border-box; color: #3366cc;">"fifteenMinutes"</span> onclick<span style="box-sizing: border-box; color: #339933;">=</span><span style="box-sizing: border-box; color: #3366cc;">"fifteenMinutes()"</span><span style="box-sizing: border-box; color: #339933;">></span><span style="box-sizing: border-box; color: #cc0000;">15</span> minutes<span style="box-sizing: border-box; color: #339933;"></</span>button<span style="box-sizing: border-box; color: #339933;">><</span>br<span style="box-sizing: border-box; color: #339933;">></span>
<span style="box-sizing: border-box; color: #339933;"><</span>button id<span style="box-sizing: border-box; color: #339933;">=</span><span style="box-sizing: border-box; color: #3366cc;">"tenMinutes"</span> onclick<span style="box-sizing: border-box; color: #339933;">=</span><span style="box-sizing: border-box; color: #3366cc;">"tenMinutes()"</span><span style="box-sizing: border-box; color: #339933;">></span><span style="box-sizing: border-box; color: #cc0000;">10</span> minutes<span style="box-sizing: border-box; color: #339933;"></</span>button<span style="box-sizing: border-box; color: #339933;">><</span>br<span style="box-sizing: border-box; color: #339933;">></span>
<span style="box-sizing: border-box; color: #339933;"><</span>button id<span style="box-sizing: border-box; color: #339933;">=</span><span style="box-sizing: border-box; color: #3366cc;">"twoMinutes"</span> onclick<span style="box-sizing: border-box; color: #339933;">=</span><span style="box-sizing: border-box; color: #3366cc;">"twoMinutes()"</span><span style="box-sizing: border-box; color: #339933;">></span><span style="box-sizing: border-box; color: #cc0000;">2</span> minutes<span style="box-sizing: border-box; color: #339933;"></</span>button<span style="box-sizing: border-box; color: #339933;">><</span>br<span style="box-sizing: border-box; color: #339933;">></span>
<span style="box-sizing: border-box; color: #339933;"><</span>button id<span style="box-sizing: border-box; color: #339933;">=</span><span style="box-sizing: border-box; color: #3366cc;">"oneMinute"</span> onclick<span style="box-sizing: border-box; color: #339933;">=</span><span style="box-sizing: border-box; color: #3366cc;">"oneMinute()"</span><span style="box-sizing: border-box; color: #339933;">></span><span style="box-sizing: border-box; color: #cc0000;">1</span> minute<span style="box-sizing: border-box; color: #339933;"></</span>button<span style="box-sizing: border-box; color: #339933;">><</span>br<span style="box-sizing: border-box; color: #339933;">></span>
<span style="box-sizing: border-box; color: #339933;"></</span>div<span style="box-sizing: border-box; color: #339933;">></span>
<span style="box-sizing: border-box; color: #339933;"><</span>div id<span style="box-sizing: border-box; color: #339933;">=</span><span style="box-sizing: border-box; color: #3366cc;">"countdown"</span> style<span style="box-sizing: border-box; color: #339933;">=</span><span style="box-sizing: border-box; color: #3366cc;">"text-align:center;font-size:50px"</span><span style="box-sizing: border-box; color: #339933;">></</span>div<span style="box-sizing: border-box; color: #339933;">></span>
<span style="box-sizing: border-box; color: #339933;"><</span>script<span style="box-sizing: border-box; color: #339933;">></span>
<span style="box-sizing: border-box; color: #000066; font-weight: bold;">function</span> thirtyMinutes<span style="box-sizing: border-box; color: #009900;">(</span><span style="box-sizing: border-box; color: #009900;">)</span> <span style="box-sizing: border-box; color: #009900;">{</span>startCountdown<span style="box-sizing: border-box; color: #009900;">(</span><span style="box-sizing: border-box; color: #cc0000;">30</span><span style="box-sizing: border-box; color: #009900;">)</span><span style="box-sizing: border-box; color: #339933;">;</span><span style="box-sizing: border-box; color: #009900;">}</span>
<span style="box-sizing: border-box; color: #000066; font-weight: bold;">function</span> fifteenMinutes<span style="box-sizing: border-box; color: #009900;">(</span><span style="box-sizing: border-box; color: #009900;">)</span> <span style="box-sizing: border-box; color: #009900;">{</span>startCountdown<span style="box-sizing: border-box; color: #009900;">(</span><span style="box-sizing: border-box; color: #cc0000;">15</span><span style="box-sizing: border-box; color: #009900;">)</span><span style="box-sizing: border-box; color: #339933;">;</span><span style="box-sizing: border-box; color: #009900;">}</span>
<span style="box-sizing: border-box; color: #000066; font-weight: bold;">function</span> tenMinutes<span style="box-sizing: border-box; color: #009900;">(</span><span style="box-sizing: border-box; color: #009900;">)</span> <span style="box-sizing: border-box; color: #009900;">{</span>startCountdown<span style="box-sizing: border-box; color: #009900;">(</span><span style="box-sizing: border-box; color: #cc0000;">10</span><span style="box-sizing: border-box; color: #009900;">)</span><span style="box-sizing: border-box; color: #339933;">;</span><span style="box-sizing: border-box; color: #009900;">}</span>
<span style="box-sizing: border-box; color: #000066; font-weight: bold;">function</span> twoMinutes<span style="box-sizing: border-box; color: #009900;">(</span><span style="box-sizing: border-box; color: #009900;">)</span> <span style="box-sizing: border-box; color: #009900;">{</span>startCountdown<span style="box-sizing: border-box; color: #009900;">(</span><span style="box-sizing: border-box; color: #cc0000;">2</span><span style="box-sizing: border-box; color: #009900;">)</span><span style="box-sizing: border-box; color: #339933;">;</span><span style="box-sizing: border-box; color: #009900;">}</span>
<span style="box-sizing: border-box; color: #000066; font-weight: bold;">function</span> oneMinute<span style="box-sizing: border-box; color: #009900;">(</span><span style="box-sizing: border-box; color: #009900;">)</span> <span style="box-sizing: border-box; color: #009900;">{</span>startCountdown<span style="box-sizing: border-box; color: #009900;">(</span><span style="box-sizing: border-box; color: #cc0000;">1</span><span style="box-sizing: border-box; color: #009900;">)</span><span style="box-sizing: border-box; color: #339933;">;</span><span style="box-sizing: border-box; color: #009900;">}</span>
<span style="box-sizing: border-box; color: #000066; font-weight: bold;">function</span> startCountdown<span style="box-sizing: border-box; color: #009900;">(</span>minutes<span style="box-sizing: border-box; color: #009900;">)</span> <span style="box-sizing: border-box; color: #009900;">{</span>
document.<span style="box-sizing: border-box; color: #660066;">getElementById</span><span style="box-sizing: border-box; color: #009900;">(</span><span style="box-sizing: border-box; color: #3366cc;">"buttons"</span><span style="box-sizing: border-box; color: #009900;">)</span>.<span style="box-sizing: border-box; color: #660066;">style</span>.<span style="box-sizing: border-box; color: #660066;">display</span> <span style="box-sizing: border-box; color: #339933;">=</span> <span style="box-sizing: border-box; color: #3366cc;">"none"</span><span style="box-sizing: border-box; color: #339933;">;</span> <span style="box-sizing: border-box; color: #006600; font-style: italic;">// hide the buttons div</span>
let count <span style="box-sizing: border-box; color: #339933;">=</span> parseInt<span style="box-sizing: border-box; color: #009900;">(</span>minutes<span style="box-sizing: border-box; color: #009900;">)</span> <span style="box-sizing: border-box; color: #339933;">*</span> <span style="box-sizing: border-box; color: #cc0000;">60</span><span style="box-sizing: border-box; color: #339933;">;</span>
<span style="box-sizing: border-box; color: #000066; font-weight: bold;">if</span> <span style="box-sizing: border-box; color: #009900;">(</span><span style="box-sizing: border-box; color: #339933;">!</span>count <span style="box-sizing: border-box; color: #339933;">></span> <span style="box-sizing: border-box; color: #cc0000;">0</span><span style="box-sizing: border-box; color: #009900;">)</span> <span style="box-sizing: border-box; color: #009900;">{</span>
count <span style="box-sizing: border-box; color: #339933;">=</span> <span style="box-sizing: border-box; color: #cc0000;">60</span><span style="box-sizing: border-box; color: #339933;">;</span>
<span style="box-sizing: border-box; color: #009900;">}</span>
let displayTime <span style="box-sizing: border-box; color: #339933;">=</span> <span style="box-sizing: border-box; color: #3366cc;">"0:00"</span><span style="box-sizing: border-box; color: #339933;">;</span>
<span style="box-sizing: border-box; color: #000066; font-weight: bold;">const</span> timer <span style="box-sizing: border-box; color: #339933;">=</span> setInterval<span style="box-sizing: border-box; color: #009900;">(</span><span style="box-sizing: border-box; color: #000066; font-weight: bold;">function</span><span style="box-sizing: border-box; color: #009900;">(</span><span style="box-sizing: border-box; color: #009900;">)</span> <span style="box-sizing: border-box; color: #009900;">{</span>
count<span style="box-sizing: border-box; color: #339933;">--;</span>
displayTime <span style="box-sizing: border-box; color: #339933;">=</span> <span style="box-sizing: border-box;">Math</span>.<span style="box-sizing: border-box; color: #660066;">floor</span><span style="box-sizing: border-box; color: #009900;">(</span>count <span style="box-sizing: border-box; color: #339933;">/</span> <span style="box-sizing: border-box; color: #cc0000;">60</span><span style="box-sizing: border-box; color: #009900;">)</span> <span style="box-sizing: border-box; color: #339933;">+</span> <span style="box-sizing: border-box; color: #3366cc;">":"</span> <span style="box-sizing: border-box; color: #339933;">+</span> <span style="box-sizing: border-box; color: #009900;">(</span><span style="box-sizing: border-box; color: #3366cc;">"0"</span> <span style="box-sizing: border-box; color: #339933;">+</span> count <span style="box-sizing: border-box; color: #339933;">%</span> <span style="box-sizing: border-box; color: #cc0000;">60</span><span style="box-sizing: border-box; color: #009900;">)</span>.<span style="box-sizing: border-box; color: #660066;">slice</span><span style="box-sizing: border-box; color: #009900;">(</span><span style="box-sizing: border-box; color: #339933;">-</span><span style="box-sizing: border-box; color: #cc0000;">2</span><span style="box-sizing: border-box; color: #009900;">)</span><span style="box-sizing: border-box; color: #339933;">;</span>
document.<span style="box-sizing: border-box; color: #660066;">getElementById</span><span style="box-sizing: border-box; color: #009900;">(</span><span style="box-sizing: border-box; color: #3366cc;">"countdown"</span><span style="box-sizing: border-box; color: #009900;">)</span>.<span style="box-sizing: border-box; color: #660066;">innerHTML</span> <span style="box-sizing: border-box; color: #339933;">=</span> displayTime<span style="box-sizing: border-box; color: #339933;">;</span>
<span style="box-sizing: border-box; color: #000066; font-weight: bold;">if</span> <span style="box-sizing: border-box; color: #009900;">(</span>count <span style="box-sizing: border-box; color: #339933;"><</span> <span style="box-sizing: border-box; color: #cc0000;">60</span><span style="box-sizing: border-box; color: #009900;">)</span> <span style="box-sizing: border-box; color: #009900;">{</span>
document.<span style="box-sizing: border-box; color: #660066;">getElementById</span><span style="box-sizing: border-box; color: #009900;">(</span><span style="box-sizing: border-box; color: #3366cc;">"countdown"</span><span style="box-sizing: border-box; color: #009900;">)</span>.<span style="box-sizing: border-box; color: #660066;">style</span>.<span style="box-sizing: border-box; color: #660066;">color</span> <span style="box-sizing: border-box; color: #339933;">=</span> <span style="box-sizing: border-box; color: #3366cc;">"red"</span><span style="box-sizing: border-box; color: #339933;">;</span>
<span style="box-sizing: border-box; color: #009900;">}</span>
<span style="box-sizing: border-box; color: #000066; font-weight: bold;">if</span> <span style="box-sizing: border-box; color: #009900;">(</span>count <span style="box-sizing: border-box; color: #339933;">===</span> <span style="box-sizing: border-box; color: #cc0000;">0</span><span style="box-sizing: border-box; color: #009900;">)</span> <span style="box-sizing: border-box; color: #009900;">{</span>
clearInterval<span style="box-sizing: border-box; color: #009900;">(</span>timer<span style="box-sizing: border-box; color: #009900;">)</span><span style="box-sizing: border-box; color: #339933;">;</span>
document.<span style="box-sizing: border-box; color: #660066;">getElementById</span><span style="box-sizing: border-box; color: #009900;">(</span><span style="box-sizing: border-box; color: #3366cc;">"countdown"</span><span style="box-sizing: border-box; color: #009900;">)</span>.<span style="box-sizing: border-box; color: #660066;">innerHTML</span> <span style="box-sizing: border-box; color: #339933;">=</span> <span style="box-sizing: border-box; color: #3366cc;">"0"</span><span style="box-sizing: border-box; color: #339933;">;</span>
<span style="box-sizing: border-box; color: #006600; font-style: italic;">//alert("Time's up!");</span>
<span style="box-sizing: border-box; color: #009900;">}</span>
<span style="box-sizing: border-box; color: #000066; font-weight: bold;">if</span> <span style="box-sizing: border-box; color: #009900;">(</span>count <span style="box-sizing: border-box; color: #339933;"><</span> <span style="box-sizing: border-box; color: #cc0000;">0</span><span style="box-sizing: border-box; color: #009900;">)</span> <span style="box-sizing: border-box; color: #009900;">{</span>
clearInterval<span style="box-sizing: border-box; color: #009900;">(</span>timer<span style="box-sizing: border-box; color: #009900;">)</span><span style="box-sizing: border-box; color: #339933;">;</span>
document.<span style="box-sizing: border-box; color: #660066;">getElementById</span><span style="box-sizing: border-box; color: #009900;">(</span><span style="box-sizing: border-box; color: #3366cc;">"countdown"</span><span style="box-sizing: border-box; color: #009900;">)</span>.<span style="box-sizing: border-box; color: #660066;">innerHTML</span> <span style="box-sizing: border-box; color: #339933;">=</span> <span style="box-sizing: border-box; color: #3366cc;">"0"</span><span style="box-sizing: border-box; color: #339933;">;</span>
<span style="box-sizing: border-box; color: #009900;">}</span>
<span style="box-sizing: border-box; color: #009900;">}</span><span style="box-sizing: border-box; color: #339933;">,</span> <span style="box-sizing: border-box; color: #cc0000;">1000</span><span style="box-sizing: border-box; color: #009900;">)</span><span style="box-sizing: border-box; color: #339933;">;</span>
<span style="box-sizing: border-box; color: #009900;">}</span>
<span style="box-sizing: border-box; color: #339933;"></</span>script<span style="box-sizing: border-box; color: #339933;">></span>
<span style="box-sizing: border-box; color: #339933;"></</span>body<span style="box-sizing: border-box; color: #339933;">></span>
<span style="box-sizing: border-box; color: #339933;"></</span>html<span style="box-sizing: border-box; color: #339933;">></span></pre></pre><p>You can also edit the code to add or remove buttons, and change the style of the buttons and text.</p><div>Then everything should be set up. The next time you load that Google Sheet there will be a custom menu called <b>Timer</b> that will show a sidebar with your new timer in it.</div>David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.com0tag:blogger.com,1999:blog-226223073205416955.post-43371747488707750322023-10-03T06:25:00.024-06:002023-10-03T07:02:14.303-06:00I've changed my mind about AI governance<p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHkIVqqNxd5GD5HJwp8t8em733FMNFjYY9lEQTQEB1JuA5J4Zcq-H0jo9SSU4TQk_osWHpcWiiGoQNBfs_NIGXyrUfMZCcwAqUIxK_E6zIWpzMuxduTbGXeoabcUu1fJBQjr7Mv0_vTldXo0fY0S5bA8F4APpfnZHpIn1OiwZl1Wqb4QihaanZgflg/s1024/legislator_and_robot.jpeg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img alt="legislator_and_robot" border="0" data-original-height="1024" data-original-width="1024" height="162" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHkIVqqNxd5GD5HJwp8t8em733FMNFjYY9lEQTQEB1JuA5J4Zcq-H0jo9SSU4TQk_osWHpcWiiGoQNBfs_NIGXyrUfMZCcwAqUIxK_E6zIWpzMuxduTbGXeoabcUu1fJBQjr7Mv0_vTldXo0fY0S5bA8F4APpfnZHpIn1OiwZl1Wqb4QihaanZgflg/w162-h162/legislator_and_robot.jpeg" title="Generated by DALL-E 3" width="162" /></a></div><br />Recently, the Canadian government <a href="https://ised-isde.canada.ca/site/ised/en/voluntary-code-conduct-responsible-development-and-management-advanced-generative-ai-systems" target="_blank">launched a voluntary code of conduct related to generative AI</a>. Many organizations have agreed to this code of conduct, although support <a href="https://www.cbc.ca/news/business/ai-code-of-conduct-stopgap-1.6983064#:~:text=Tobi%20L%C3%BCtke%20wrote%20that%20he,'come%20build%20here.'%22" target="_blank">has not been universal</a>.<p></p><p>This was the topic of our last biweekly "AI Issues and Ethics" discussion, what role should there be for regulation of artificial intelligence? It was a great conversation with plenty of dissent, as always, and I found my opinion changing as a result of the wisom of the participants.</p><p>I used to be skeptical about the idea of government involvement in regulating artificial intelligence, assuming that policymakers lacked the expertise and agility to make informed decisions. This is a rapidly evolving and technically complicated field and, as others have argued, regulation must stifle innovation. I often argued for fewer regulations and restrictions on technologies, or at least suggested that existing rules and laws related to privacy or safety were sufficient to address these new technologies.</p><p>However, my views have evolved, and I now firmly believe that some level of governance is essential to ensure the responsible and ethical development and deployment of AI technologies.</p><p>Lawmakers don't need to understand the complexities of internal combustion engines, or electric vehicles for that matter, in order to set speed limits on our roads. They rely on experts, data, and an understanding of societal needs to establish safe parameters. Speed limits are not designed to stifle our driving.</p><p>And to use a similar analogy, there was a time in Alberta the seatbelt use in cars was not compulsory. I'll admit that I didn't always wear a seatbelt, even though I knew it was safer to do so. States with fewer requirements for motorcycle helmets have <a href="https://www.cdc.gov/mmwr/preview/mmwrhtml/mm6123a1.htm#:~:text=Research%20has%20shown%20that%20when,%2C%20injuries%2C%20and%20costs%20increase." target="_blank">increased rates</a> of injuries and deaths. Humans don't always make the best decisions around our own safety.</p><p>That's not to say that the government will always make the best decisions either. There will be instances where regulations fall short or overreach, but we shouldn't abandon the idea of AI governance entirely. Through an iterative process that is open to feedback and fosters collaboration among stakeholders, experts, and policymakers, legislation can be amended and refined. Regulations should be adaptive and responsive to evolving needs and norms of society.</p><p>I would argue that we don't require an absence of regulations in order to innovate, nor should we rely solely on the judgement of technology organizations to work for the good of humankind. By fostering collaboration, making informed decisions, and committing to ethical principles, A voluntary code of conduct is a good start, and I hope that we can create an and safe suite of artificial intelligence tools for everyone.</p><p>And let me know if you'd be interested in participating in some of these "AI Issues and Ethics" discussions.</p>David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.com0tag:blogger.com,1999:blog-226223073205416955.post-68732996837113520132023-02-21T17:36:00.002-07:002023-02-21T17:36:45.305-07:00Ethical Questions Related to Discriminative and Generative Artificial Intelligence<p>It suddenly seems that everyone is talking about artificial intelligence, computers doing things that look like they require intelligence. Tools that generate images or text have gotten fairly good and easy to use.</p><p style="text-align: center;"><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" frameborder="0" height="158" src="https://www.youtube.com/embed/TriS-DUvLqM" title="YouTube video player" width="280"></iframe></p><p>These generative AI tools are trained to create new content based on their input data sets. Discriminative AI tools, on the other hand, are trained to differentiate among different classes of input and predict which class a new observation should belong to.</p><p>My impression is that people are more comfortable with discriminative AI, with applications in autonomous vehicles and facial recognition, than with generative AI that seems to intrude on our uniquely human creativity. Of course there is a spectrum of opinions from excited techno-optimism to worries that this will bring about the end of civilization.</p><p>Lately I've been a part of many discussions about ethical questions surrounding artificial intelligence, particularly in education. For many of them there aren't, or aren't yet, good answers, but they certainly make for interesting debate. These are in no particular order, and feel free to use them in your own conversations.</p><p></p><ul style="text-align: left;"><li>Is it plagiarism or cheating to use generative AI tools?</li><li>Can we accurately detect if students are using these tools? Is this something we should worry about?</li><li>What are we training students for? Is school about job training?</li><li>Why do we make students write?</li><li>What are uniquely human skills and competencies we need to foster?</li><li>If we block AI tools on our educational networks and devices, does the problem go away?</li><li>Are we comfortable with doctors or drivers using AI?</li><li>Can AI take over some of the mundane parts of our jobs (or lives)?</li><li>How would we feel about a facial recognition attendance system?</li><li>Is it ethical to have AI help us draft emails, or blog posts?</li><li>What does education look like if teachers use AI to help generate questions and students use it to help generate answers?</li><li>Are we okay with corporations using student questions and responses to help train their models?</li><li>Will bias in the training data lead to increased societal polarization?</li><li>Are there analogies to historical inventions that we can learn from?</li><li>Will AI disintermediate students and learning?</li><li>Does over-reliance on AI lead to <a href="https://en.wikipedia.org/wiki/The_Feeling_of_Power" target="_blank">skill loss</a>?</li><li>Will we lose jobs? Will this change jobs?</li><li>Does AI exacerbate inequality?</li><li>Will AI lead to homogenization of culture?</li><li>Do we like spell check, autocorrect, autofill, predictive text, and autoreplies?</li><li>Will generative AI answers spell the end of internet search?</li><li>If we enjoy doing things, should we use AI or machines to do those things? Should we try to prevent AI or machines from doing those things?</li><li>Is AI worth the environmental impact?</li><li>What might AI tools look like in six months? How about in five years?</li></ul><p>Hopefully these questions can help spark some thoughful and spirited discourse.</p>David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.com0tag:blogger.com,1999:blog-226223073205416955.post-60206007240249693522022-05-07T13:09:00.000-06:002022-05-07T13:09:06.805-06:00Creating, Editing, and Sharing Jupyter Notebooks<div>I have been working with the <a href="https://callysto.ca" target="_blank">Callysto</a> project, which involves fostering computational thinking and data science within the regular curriculum for grades 5 to 12. One of the services provided by Callysto is the <a href="https://hub.callysto.ca">Callysto Hub</a>, a free online environment hosting an open-source instance of <a href="https://jupyter.org/hub" target="_blank">Jupyter Hub</a> that allows you to run, edit, and create Jupyter notebooks <a href="https://callysto.ca/learning_modules/" target="_blank">such as these</a>.</div><div><br /></div><div>While <a href="https://colab.research.google.com/" target="_blank">some</a> <a href="https://notebooks.azure.com/" target="_blank">commercial</a> <a href="https://cocalc.com/" target="_blank">platforms</a> have good <a href="https://deepnote.com/" target="_blank">sharing</a> options, there isn't currently a great open-source solution for sharing Jupyter notebooks with collaborators and students. I can show you my current workflow, though, for creating, editing, and sharing Jupyter notebooks.</div><div><br /></div><div>There are two programs I have installed on the computer in front of me <a href="https://code.visualstudio.com/" target="_blank">Visual Studio Code</a> for running and editing Jupyter notebooks on this computer and <a href="https://desktop.github.com/" target="_blank">GitHub Desktop</a> for synchronizing with <a href="https://github.com/" target="_blank">GitHub</a>.</div><div><br /></div><div>We'll start with <a href="https://www.youtube.com/watch?v=w3jLJU7DT5E" target="_blank">GitHub</a>, this is the site used by many software developers to share and collaborate. You'll need to <a href="https://github.com/join" target="_blank">create a free account</a>.</div><div><br /></div><div>Next we'll use the <a href="https://desktop.github.com/" target="_blank">GitHub Desktop</a> application. Download, install, and run it, then log in with the GitHub account you just created. Now when you visit a GitHub repository, such as <a href="https://github.com/callysto/curriculum-notebooks" target="_blank">Callysto Curriculum Notebooks</a>, you can click the green <b>Code</b> button near the top right of the page and then <b>Open with GitHub Desktop</b>. It should pop you back into the GitHub Desktop program, and you can click the <b>Clone</b> button to download the code from that <a href="https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/about-repositories" target="_blank">repository</a>.</div><div><br /></div><div>To set up <a href="https://code.visualstudio.com/" target="_blank">Visual Studio Code</a>, download and install it. You'll also need to download and install <a href="https://www.python.org/downloads/" target="_blank">Python</a>, the <a href="https://marketplace.visualstudio.com/items?itemName=ms-python.python" target="_blank">VS Code Python Extension</a>, and the <a href="https://marketplace.visualstudio.com/items?itemName=ms-toolsai.jupyter">VS Code Jupyter extension</a>. You should then be able to open, run, and edit Juypter notebooks, as well as create new ones.</div><div><br /></div><div>If you encounter errors running notebooks, you likely need to install some Python libraries with commands like <font face="courier">!pip install pandas</font> , post in the comments (or <a href="https://twitter.com/misterhay" target="_blank">reach out on Twitter</a>) for help with that if you need.</div><div><br /></div><div>At this point you're basically set up, but it gets a little more complicated if you want to share notebooks you create. You'll want to <a href="https://docs.github.com/en/desktop/getting-started-with-github-desktop/creating-your-first-repository-using-github-desktop" target="_blank">create a new GitHub repository</a>, add your notebook file to that repository folder on your computer, and use GitHub Desktop to commit and push it to GitHub.</div><div><br /></div><div>Once the notebook file is on GitHub, you can either have your students or colleagues go through this setup process to download your new repository, or you can send them a <a href="https://haytech.blogspot.com/2019/10/nbgitpuller-bookmarklet.html" target="_blank">Callysto nbgitpuller link</a>.</div><div><br /></div><div>Hopefully that's not too complicated, but feel free to reach out if you have questions or would like help with this.</div><div><br /></div><div>(Updated for 2022)</div>David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.com0tag:blogger.com,1999:blog-226223073205416955.post-16929348577967870022021-12-11T10:06:00.000-07:002021-12-11T10:06:06.218-07:00Automatically Changing a YouTube Video's Privacy With Google Apps Script<p> Recently we had posted a video that needed to be available during a specific time. The starting availability is taken care of by <a href="https://support.google.com/youtube/answer/9080341?hl=en" target="_blank">setting it to premiere</a>. However there's no option for having it unavailable at the end of the time period, so we needed to write some code for that.</p><p>Google Apps Script is a good choice for this, since it has a <a href="https://developers.google.com/apps-script/advanced/youtube" target="_blank">YouTube service</a> that you can enable as well as <a href="https://developers.google.com/apps-script/guides/triggers/installable" target="_blank">triggers</a> that can run code at a certain time.</p><p>Create a <a href="https://script.google.com/home/start" target="_blank">new Apps Script project</a> and paste in the <a href="https://gist.github.com/misterhay/0e5819a920fd6d93c1daf8df94acdb8b" target="_blank">following code</a>:</p>
<script src="https://gist.github.com/misterhay/0e5819a920fd6d93c1daf8df94acdb8b.js"></script>
<p><br /></p><p>Then <a href="https://developers.google.com/apps-script/advanced/youtube" target="_blank">enable the YouTube Service</a> and <a href="https://developers.google.com/apps-script/guides/triggers/installable" target="_blank">create a new trigger</a> to run the <span style="font-family: courier;">updateVideoPrivacy</span> function at a time-driven specific date and time. The code will then set your most recently uploaded YouTube video to private at the time you specified.</p>David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.com0tag:blogger.com,1999:blog-226223073205416955.post-15121751015295929952021-10-30T14:40:00.001-06:002021-10-30T14:40:00.160-06:00Coding Challenge 3<p>The third coding challenge is up at <a href="http://misterhay.github.io/coding-challenges">misterhay.github.io/coding-challenges</a>, along with a possible solution to this challenge and last month's challenge. </p>David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.com0tag:blogger.com,1999:blog-226223073205416955.post-73577957142452298242021-09-30T19:19:00.001-06:002021-09-30T19:19:32.623-06:00Small Coding Challenge 2<p>The second coding challenge is up at <a href="http://misterhay.github.io/coding-challenges">misterhay.github.io/coding-challenges</a>, along with a possible solution to this challenge and last month's challenge.</p>David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.com0tag:blogger.com,1999:blog-226223073205416955.post-7564775064412783712021-09-11T18:50:00.003-06:002021-09-11T18:50:45.845-06:00Fixing a formula in multiple Google Sheets<p>Recently we realized that there was a calculation error in a Google Sheet template, and unfortunately we had already made a copy of it for each teacher.</p><p>Rather than opening each Sheet and manually making the correction, I wrote a <a href="https://www.google.com/script/start/" target="_blank">Google Apps Script</a> to automatically make the replacements. Here is the code in case it is helpful for anyone else:</p><p><br /></p>
<pre style="background: rgb(255, 255, 255); color: black;"><span style="color: maroon; font-weight: bold;">function</span> loopThem<span style="color: #808030;">(</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">var</span> folderId <span style="color: #808030;">=</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">aaaaaaaaa</span><span style="color: maroon;">'</span><span style="color: purple;">;</span> <span style="color: dimgrey;">// the folder containing all of the sheets to be corrected</span>
<span style="color: maroon; font-weight: bold;">var</span> folder <span style="color: #808030;">=</span> DriveApp<span style="color: #808030;">.</span>getFolderById<span style="color: #808030;">(</span>folderId<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">var</span> files <span style="color: #808030;">=</span> folder<span style="color: #808030;">.</span>getFiles<span style="color: #808030;">(</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">while</span> <span style="color: #808030;">(</span>files<span style="color: #808030;">.</span>hasNext<span style="color: #808030;">(</span><span style="color: #808030;">)</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">var</span> file <span style="color: #808030;">=</span> files<span style="color: #808030;">.</span>next<span style="color: #808030;">(</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">var</span> id <span style="color: #808030;">=</span> file<span style="color: #808030;">.</span>getId<span style="color: #808030;">(</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
fixIt<span style="color: #808030;">(</span>id<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span>
<span style="color: purple;">}</span>
<span style="color: maroon; font-weight: bold;">function</span> fixIt<span style="color: #808030;">(</span>id<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">var</span> rangeToFix <span style="color: #808030;">=</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">P1:P50</span><span style="color: maroon;">'</span><span style="color: purple;">;</span> <span style="color: dimgrey;">// the range in each sheet that contains the error</span>
<span style="color: maroon; font-weight: bold;">var</span> replaceThis <span style="color: #808030;">=</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">$A</span><span style="color: maroon;">'</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">var</span> replaceWithThis <span style="color: #808030;">=</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">$C</span><span style="color: maroon;">'</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">var</span> ss <span style="color: #808030;">=</span> SpreadsheetApp<span style="color: #808030;">.</span>openById<span style="color: #808030;">(</span>id<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">var</span> range <span style="color: #808030;">=</span> ss<span style="color: #808030;">.</span>getRange<span style="color: #808030;">(</span>rangeToFix<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">var</span> formulas <span style="color: #808030;">=</span> range<span style="color: #808030;">.</span>getFormulas<span style="color: #808030;">(</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">for</span> <span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">var</span> i<span style="color: #808030;">=</span><span style="color: #008c00;">0</span><span style="color: purple;">;</span> i<span style="color: #808030;"><</span>formulas<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">length</span><span style="color: purple;">;</span> i<span style="color: #808030;">++</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">var</span> formula <span style="color: #808030;">=</span> formulas<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">var</span> newFormula <span style="color: #808030;">=</span> formula<span style="color: #808030;">.</span>replaceAll<span style="color: #808030;">(</span>replaceThis<span style="color: #808030;">,</span> replaceWithThis<span style="color: #808030;">)</span><span style="color: purple;">;</span>
formulas<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span> <span style="color: #808030;">=</span> newFormula<span style="color: purple;">;</span>
<span style="color: purple;">}</span>
range<span style="color: #808030;">.</span>setFormulas<span style="color: #808030;">(</span>formulas<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span>
</pre>
David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.com0tag:blogger.com,1999:blog-226223073205416955.post-68332548170429633002021-08-29T14:59:00.001-06:002021-08-29T14:59:16.249-06:00Small Coding Challenge 1<p>This year I'm going to design some coding challenges, and hopefully release them at least once a month.</p><p>The first coding challenge is up at <a href="http://misterhay.github.io/coding-challenges">misterhay.github.io/coding-challenges</a>.</p>David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.com0tag:blogger.com,1999:blog-226223073205416955.post-27126864644865433042021-03-13T10:24:00.000-07:002021-03-13T10:24:18.399-07:00Quick Exploratory Data Analysis with SweetViz<p>I recently came across a Python library that is useful for quick exploration of a dataset (or two) in a Jupyter notebook, <a href="https://github.com/fbdesignpro/sweetviz" target="_blank">SweetViz</a>.</p><p>Code:</p><p>
</p><pre style="background: rgb(255, 255, 255); color: black;"><span style="color: maroon; font-weight: bold;">try</span><span style="color: #808030;">:</span>
<span style="color: maroon; font-weight: bold;">import</span> sweetviz
<span style="color: maroon; font-weight: bold;">except</span><span style="color: #808030;">:</span>
!pip install sweetviz <span style="color: #44aadd;">-</span><span style="color: #44aadd;">-</span>user
<span style="color: maroon; font-weight: bold;">import</span> sweetviz
<span style="color: maroon; font-weight: bold;">import</span> pandas <span style="color: maroon; font-weight: bold;">as</span> pd
df <span style="color: #808030;">=</span> pd<span style="color: #808030;">.</span>read_csv<span style="color: #808030;">(</span><span style="color: #0000e6;">'https://raw.githubusercontent.com/callysto/hackathon/f7454f5d9234df2575ceb7b8983340e512fad24d/SustainabilityOnMars/Tutorials/pets_from_bootstrap_world.csv'</span><span style="color: #808030;">)</span>
analysis <span style="color: #808030;">=</span> sweetviz<span style="color: #808030;">.</span>analyze<span style="color: #808030;">(</span>df<span style="color: #808030;">)</span>
analysis<span style="color: #808030;">.</span>show_notebook<span style="color: #808030;">(</span><span style="color: #808030;">)</span> <span style="color: dimgrey;"># or export with show_html()</span>
</pre>
<p></p><p><br /></p><p>Here is a <a href="https://colab.research.google.com/drive/1dqaKCYtflGxz_lGqZ_t-x5154DpXWtpL?usp=sharing">brief notebook demonstration</a>.</p>David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.com0tag:blogger.com,1999:blog-226223073205416955.post-11095488861868534642021-01-09T17:30:00.004-07:002021-10-23T15:53:26.640-06:00Getting Student Submission Data from Brightspace with Python and Selenium<p>As a teacher with students in multiple Brightspace courses, I was looking for a dashboard to show which students have unsubmitted assignments. While Brightspace does have an <a href="https://docs.valence.desire2learn.com/reference.html">API available</a>, I decided that it wasn't going to work for a few reasons. There are also commercial (non-free) plugins that can do most of what I was looking for, but this was a good opportunity to explore scraping of content from dynamic web pages with Python.</p><p>You may be familiar with the Python <a href="https://pypi.org/project/requests/">Requests</a> and <a href="https://pypi.org/project/beautifulsoup4/">Beautiful Soup</a> libraries, which are great, but since Brightspace is requiring a Microsoft login I needed to go with <a href="https://pypi.org/project/selenium/">Selenium</a>. Selenium is designed for automating web browser interactions, which means we can use it to log in to a site and scrape pages.</p><p>While Selenium can be installed and run locally, it also works in a <a href="http://colab.research.google.com/">Colab</a> notebook:</p>
<div style="background: rgb(255, 255, 255); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: red;">!</span>apt update
<span style="color: red;">!</span>apt install chromium<span style="color: #44aadd;">-</span>chromedriver
<span style="color: red;">!</span>pip install selenium
<span style="color: maroon; font-weight: bold;">from</span> selenium <span style="color: maroon; font-weight: bold;">import</span> webdriver
<span style="color: maroon; font-weight: bold;">from</span> selenium<span style="color: #808030;">.</span>webdriver<span style="color: #808030;">.</span>support<span style="color: #808030;">.</span>ui <span style="color: maroon; font-weight: bold;">import</span> WebDriverWait
<span style="color: maroon; font-weight: bold;">from</span> selenium<span style="color: #808030;">.</span>webdriver<span style="color: #808030;">.</span>support <span style="color: maroon; font-weight: bold;">import</span> expected_conditions <span style="color: maroon; font-weight: bold;">as</span> EC
<span style="color: maroon; font-weight: bold;">from</span> selenium<span style="color: #808030;">.</span>webdriver<span style="color: #808030;">.</span>common<span style="color: #808030;">.</span>by <span style="color: maroon; font-weight: bold;">import</span> By
options <span style="color: #808030;">=</span> webdriver<span style="color: #808030;">.</span>ChromeOptions<span style="color: #808030;">(</span><span style="color: #808030;">)</span>
options<span style="color: #808030;">.</span>add_argument<span style="color: #808030;">(</span><span style="color: #0000e6;">'--headless'</span><span style="color: #808030;">)</span>
options<span style="color: #808030;">.</span>add_argument<span style="color: #808030;">(</span><span style="color: #0000e6;">'--no-sandbox'</span><span style="color: #808030;">)</span>
browser <span style="color: #808030;">=</span> webdriver<span style="color: #808030;">.</span>Chrome<span style="color: #808030;">(</span>options<span style="color: #808030;">=</span>options<span style="color: #808030;">)</span></pre></pre></div>
<p>Once that is set up, we need to log in to our Brightspace server:</p>
<div style="background: rgb(255, 255, 255); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">email <span style="color: #808030;">=</span> <span style="color: #0000e6;">'teacher@example.com'</span>
base_url <span style="color: #808030;">=</span> <span style="color: #0000e6;">'https://example.brightspace.com'</span>
<span style="color: maroon; font-weight: bold;">import</span> getpass <span style="color: dimgrey;"># so you don't show your password in the sourcecode</span>
password <span style="color: #808030;">=</span> getpass<span style="color: #808030;">.</span>getpass<span style="color: #808030;">(</span><span style="color: #808030;">)</span>
email_field <span style="color: #808030;">=</span> <span style="color: #808030;">(</span>By<span style="color: #808030;">.</span><span style="color: #400000;">ID</span><span style="color: #808030;">,</span> <span style="color: #0000e6;">'i0116'</span><span style="color: #808030;">)</span>
password_field <span style="color: #808030;">=</span> <span style="color: #808030;">(</span>By<span style="color: #808030;">.</span><span style="color: #400000;">ID</span><span style="color: #808030;">,</span> <span style="color: #0000e6;">'i0118'</span><span style="color: #808030;">)</span>
next_button <span style="color: #808030;">=</span> <span style="color: #808030;">(</span>By<span style="color: #808030;">.</span><span style="color: #400000;">ID</span><span style="color: #808030;">,</span> <span style="color: #0000e6;">'idSIButton9'</span><span style="color: #808030;">)</span>
browser<span style="color: #808030;">.</span>get<span style="color: #808030;">(</span>base_url<span style="color: #808030;">)</span>
WebDriverWait<span style="color: #808030;">(</span>browser<span style="color: #808030;">,</span><span style="color: #008c00;">10</span><span style="color: #808030;">)</span><span style="color: #808030;">.</span>until<span style="color: #808030;">(</span>EC<span style="color: #808030;">.</span>element_to_be_clickable<span style="color: #808030;">(</span>email_field<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: #808030;">.</span>send_keys<span style="color: #808030;">(</span>email<span style="color: #808030;">)</span>
WebDriverWait<span style="color: #808030;">(</span>browser<span style="color: #808030;">,</span><span style="color: #008c00;">10</span><span style="color: #808030;">)</span><span style="color: #808030;">.</span>until<span style="color: #808030;">(</span>EC<span style="color: #808030;">.</span>element_to_be_clickable<span style="color: #808030;">(</span>next_button<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: #808030;">.</span>click<span style="color: #808030;">(</span><span style="color: #808030;">)</span>
WebDriverWait<span style="color: #808030;">(</span>browser<span style="color: #808030;">,</span><span style="color: #008c00;">10</span><span style="color: #808030;">)</span><span style="color: #808030;">.</span>until<span style="color: #808030;">(</span>EC<span style="color: #808030;">.</span>element_to_be_clickable<span style="color: #808030;">(</span>password_field<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: #808030;">.</span>send_keys<span style="color: #808030;">(</span>password<span style="color: #808030;">)</span>
WebDriverWait<span style="color: #808030;">(</span>browser<span style="color: #808030;">,</span><span style="color: #008c00;">10</span><span style="color: #808030;">)</span><span style="color: #808030;">.</span>until<span style="color: #808030;">(</span>EC<span style="color: #808030;">.</span>element_to_be_clickable<span style="color: #808030;">(</span>next_button<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: #808030;">.</span>click<span style="color: #808030;">(</span><span style="color: #808030;">)</span>
WebDriverWait<span style="color: #808030;">(</span>browser<span style="color: #808030;">,</span><span style="color: #008c00;">10</span><span style="color: #808030;">)</span><span style="color: #808030;">.</span>until<span style="color: #808030;">(</span>EC<span style="color: #808030;">.</span>element_to_be_clickable<span style="color: #808030;">(</span>next_button<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: #808030;">.</span>click<span style="color: #808030;">(</span><span style="color: #808030;">)</span></pre></pre></div>
<p>From there it's a matter of scraping the course progress pages as we loop through the course IDs and student IDs. I may update this post later with an automated way to scrape these IDs, but for now we need to look them up on Brightspace and code them in:</p>
<div style="background: rgb(255, 255, 255); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: maroon; font-weight: bold;">import</span> pandas <span style="color: maroon; font-weight: bold;">as</span> pd
df <span style="color: #808030;">=</span> pd<span style="color: #808030;">.</span>Series<span style="color: #808030;">(</span>students<span style="color: #808030;">)</span><span style="color: #808030;">.</span>to_frame<span style="color: #808030;">(</span><span style="color: #0000e6;">'ID'</span><span style="color: #808030;">)</span>
<span style="color: maroon; font-weight: bold;">for</span> course <span style="color: maroon; font-weight: bold;">in</span> courses<span style="color: #808030;">:</span>
course_id <span style="color: #808030;">=</span> courses<span style="color: #808030;">[</span>course<span style="color: #808030;">]</span>
submissions <span style="color: #808030;">=</span> <span style="color: #808030;">[</span><span style="color: #808030;">]</span>
<span style="color: maroon; font-weight: bold;">for</span> student <span style="color: maroon; font-weight: bold;">in</span> students<span style="color: #808030;">:</span>
student_id <span style="color: #808030;">=</span> students<span style="color: #808030;">[</span>student<span style="color: #808030;">]</span>
url <span style="color: #808030;">=</span> base_url<span style="color: #44aadd;">+</span><span style="color: #0000e6;">'/d2l/le/classlist/userprogress/'</span><span style="color: #44aadd;">+</span><span style="color: #400000;">str</span><span style="color: #808030;">(</span>student_id<span style="color: #808030;">)</span><span style="color: #44aadd;">+</span><span style="color: #0000e6;">'/'</span><span style="color: #44aadd;">+</span><span style="color: #400000;">str</span><span style="color: #808030;">(</span>course_id<span style="color: #808030;">)</span><span style="color: #44aadd;">+</span><span style="color: #0000e6;">'/Assignments/Details'</span>
browser<span style="color: #808030;">.</span>get<span style="color: #808030;">(</span>url<span style="color: #808030;">)</span>
WebDriverWait<span style="color: #808030;">(</span>browser<span style="color: #808030;">,</span> <span style="color: #008c00;">10</span><span style="color: #808030;">)</span><span style="color: #808030;">.</span>until<span style="color: #808030;">(</span>EC<span style="color: #808030;">.</span>presence_of_element_located<span style="color: #808030;">(</span><span style="color: #808030;">(</span>By<span style="color: #808030;">.</span>CSS_SELECTOR<span style="color: #808030;">,</span> <span style="color: #0000e6;">"span[class^='d2l-textblock']"</span><span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: #808030;">)</span>
elements <span style="color: #808030;">=</span> browser<span style="color: #808030;">.</span>find_elements_by_css_selector<span style="color: #808030;">(</span><span style="color: #0000e6;">"span[class^='d2l-textblock']"</span><span style="color: #808030;">)</span>
<span style="color: dimgrey;">#elements = browser.find_element(by=) # because the other thing is deprecated I guess</span>
<span style="color: dimgrey;">#submitted = elements[13].text[1:-1] # to get a fraction</span>
submitted <span style="color: #808030;">=</span> elements<span style="color: #808030;">[</span><span style="color: #008c00;">13</span><span style="color: #808030;">]</span><span style="color: #808030;">.</span>text<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">:</span><span style="color: #44aadd;">-</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">.</span>split<span style="color: #808030;">(</span><span style="color: #0000e6;">'/'</span><span style="color: #808030;">)</span><span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span> <span style="color: dimgrey;"># to get just the numerator</span>
submissions<span style="color: #808030;">.</span>append<span style="color: #808030;">(</span>submitted<span style="color: #808030;">)</span>
df<span style="color: #808030;">[</span>course<span style="color: #808030;">]</span> <span style="color: #808030;">=</span> submissions
df<span style="color: #808030;">.</span>to_csv<span style="color: #808030;">(</span><span style="color: #0000e6;">'student-submissions.csv'</span><span style="color: #808030;">)</span>
df</pre></pre></div>
<p>This will give us a <a href="https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html">Pandas DataFrame</a> (and a CSV file) with information about how many assignments each student has submitted in each course. We can manipulate this a bit to create some visualizations, but perhaps that's a post for later. For now, here's part of a chart that we could generate:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-zjp9QN49PTU/X_oALizVG8I/AAAAAAANpCg/M68B-savYuMjT_VXLtAYzJgbdfLGUVHxQCLcBGAsYHQ/s351/example-student-submission-chart.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="351" data-original-width="239" height="320" src="https://1.bp.blogspot.com/-zjp9QN49PTU/X_oALizVG8I/AAAAAAANpCg/M68B-savYuMjT_VXLtAYzJgbdfLGUVHxQCLcBGAsYHQ/s320/example-student-submission-chart.jpg" /></a></div><br /><p>Let me know if you try this or if you come across anything that should be corrected.</p>David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.com2tag:blogger.com,1999:blog-226223073205416955.post-7590347292621555852020-09-19T09:28:00.002-06:002020-09-19T09:28:31.262-06:00Bookmarklet for Generating a Link to Copy a Google Doc, Sheet, Slides, or Drawing<p>If you want students to create a copy of a Google Doc, Sheet, Slides, or Drawing, you can <a href="https://support.google.com/a/users/answer/9308866?hl=en" target="_blank">replace</a> the <span style="font-family: courier;">/edit</span> at the end of the link with <span style="font-family: courier;">/copy</span>.</p><p>To make that easier, I've created a bookmarklet. To set it up for yourself, drag the following link to your bookmark bar or menu:</p><p><a href="javascript:(function(){var url=location.href;if(url.includes('docs.google.com')){var urlc=url.substring(0,url.lastIndexOf('/'))+'/copy';window.prompt('Copy this URL',urlc);}else{window.alert('Run this on a Google Doc, Sheet, Slides, or Drawing');}})();">MakeCopy</a></p>Then when you have a Doc, Sheet, Slides, or Drawing open (and you've set the sharing permissions) you can click the bookmarklet and it will generate a link that you can copy and send to your students. When they click the link it will prompt them to make a copy.David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.com0tag:blogger.com,1999:blog-226223073205416955.post-90314857632244067192020-06-09T20:24:00.000-06:002020-06-09T20:24:06.289-06:00Streaming OBS Recordings to YouTube<div style="background-color: white; box-sizing: border-box; color: #515151; font-family: "Segoe UI", SegoeUI, Roboto, "Segoe WP", "Helvetica Neue", Helvetica, Tahoma, Arial, sans-serif; font-size: 20px; margin-bottom: 10px; padding: 0px;">
Currently <a href="https://obsproject.com/" style="background-color: transparent; box-sizing: border-box; color: #1756a9; text-decoration-line: none;">OBS Studio</a> can only stream to a single service, such as <a href="https://www.facebook.com/business/help/1968707740106188?id=648321075955172" style="background-color: transparent; box-sizing: border-box; color: #1756a9; text-decoration-line: none;">Facebook</a> or <a href="https://support.google.com/youtube/answer/2907883" style="background-color: transparent; box-sizing: border-box; color: #1756a9; text-decoration-line: none;">YouTube</a>, but we are going to set up a way to stream to another service at the same time. Assuming that you are already comfortable streaming to Facebook, YouTube will be our second service.</div>
<div style="background-color: white; box-sizing: border-box; color: #515151; font-family: "Segoe UI", SegoeUI, Roboto, "Segoe WP", "Helvetica Neue", Helvetica, Tahoma, Arial, sans-serif; font-size: 20px; margin-bottom: 10px; padding: 0px;">
You'll need to install <a href="https://ffmpeg.org/" style="background-color: transparent; box-sizing: border-box; color: #1756a9; text-decoration-line: none;">FFmpeg</a> and <a href="https://www.python.org/downloads" style="background-color: transparent; box-sizing: border-box; color: #1756a9; text-decoration-line: none;">Python 3</a>.</div>
<div style="background-color: white; box-sizing: border-box; color: #515151; font-family: "Segoe UI", SegoeUI, Roboto, "Segoe WP", "Helvetica Neue", Helvetica, Tahoma, Arial, sans-serif; font-size: 20px; margin-bottom: 10px; padding: 0px;">
The following Python code can be saved as something like <code style="background-color: #eeeeff; border-radius: 3px; border: 1px solid rgb(232, 232, 232); box-sizing: border-box; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace; font-size: 12px; padding: 1px 5px;">second_stream.py</code> and run from there.</div>
<div style="background-color: white; box-sizing: border-box; color: #515151; font-family: "Segoe UI", SegoeUI, Roboto, "Segoe WP", "Helvetica Neue", Helvetica, Tahoma, Arial, sans-serif; font-size: 20px; margin-bottom: 10px; padding: 0px;">
Replace <code style="background-color: #eeeeff; border-radius: 3px; border: 1px solid rgb(232, 232, 232); box-sizing: border-box; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace; font-size: 12px; padding: 1px 5px;">xxxx-xxxx-xxxx-xxxx</code> with your stream key from <a href="https://studio.youtube.com/" style="background-color: transparent; box-sizing: border-box; color: #1756a9; text-decoration-line: none;">YouTube Studio</a>, and <code style="background-color: #eeeeff; border-radius: 3px; border: 1px solid rgb(232, 232, 232); box-sizing: border-box; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace; font-size: 12px; padding: 1px 5px;">/home/username/Videos</code> with the <a href="https://www.obs.live/articles/2019/3/4/where-does-obs-save-recordings" style="background-color: transparent; box-sizing: border-box; color: #1756a9; text-decoration-line: none;">path to the folder where OBS records your videos</a>. You may also need to include the <code style="background-color: #eeeeff; border-radius: 3px; border: 1px solid rgb(232, 232, 232); box-sizing: border-box; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace; font-size: 12px; padding: 1px 5px;">ffmpeg_path</code>.</div>
<div style="background-color: white; box-sizing: border-box; color: #515151; font-family: "Segoe UI", SegoeUI, Roboto, "Segoe WP", "Helvetica Neue", Helvetica, Tahoma, Arial, sans-serif; font-size: 20px; margin-bottom: 10px; padding: 0px;">
This code finds the most recent file in your OBS recordings folder and streams that file to YouTube. You may want to enable the setting "Automatically record when streaming" in OBS, otherwise you'll need to click "Start Streaming" and "Start Recording" each time.</div>
<div style="background-color: white; box-sizing: border-box; color: #515151; font-family: "Segoe UI", SegoeUI, Roboto, "Segoe WP", "Helvetica Neue", Helvetica, Tahoma, Arial, sans-serif; font-size: 20px; margin-bottom: 10px; padding: 0px;">
Start recording in OBS then run the code, and it should start streaming the recording to YouTube without interfering with your primary stream. You will, of course, need enough upload bandwidth for both streams.</div>
<div style="background-color: white; box-sizing: border-box; color: #515151; font-family: "Segoe UI", SegoeUI, Roboto, "Segoe WP", "Helvetica Neue", Helvetica, Tahoma, Arial, sans-serif; font-size: 20px; margin-bottom: 10px; padding: 0px;">
Potentially you could have another copy of this Python script running to streams the recording to a third service, such as Twitch.</div>
<iframe allowfullscreen="" frameborder="0" height="356" marginheight="0" marginwidth="0" src="https://trinket.io/embed/python3/5465cee4d3" width="100%"></iframe>
<br />
<div style="background-color: white; box-sizing: border-box; color: #515151; font-family: "Segoe UI", SegoeUI, Roboto, "Segoe WP", "Helvetica Neue", Helvetica, Tahoma, Arial, sans-serif; font-size: 20px; margin-bottom: 10px; padding: 0px;">
Hopefully that helps get you started with secondary streams from OBS Studio. Let me know if any of this doesn't work for you.</div>
David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.com0tag:blogger.com,1999:blog-226223073205416955.post-24871246474221957092020-02-29T15:16:00.002-07:002020-02-29T15:16:55.703-07:00Authoring Open Educational Resources using only Open Source SoftwareRecently a leader in the <a href="http://albertaoer.com/">Alberta OER</a> community, <a href="https://www.ualberta.ca/education/about-us/professor-profiles/michael-mcnally">Michael McNally</a>, suggested that it is difficult or impossible to only use open source software (OSS) when creating open educational resources (OER). I agree with his point that using only OSS doesn't make OER more "pure", but perhaps it is still an interesting challenge.<br />
<br />
Here are some of my suggestions, please comment if anything is missing. <i>And I do understand the hypocrisy of posting this on a Google-hosted blog.</i><br />
<br />
<b>Writing Text</b><br />
<br />
Text is still often the primary medium for OER, and there are a number of great open-source text-authoring tools. <a href="https://www.libreoffice.org/">LibreOffice</a> is a great office suite, and it is similar to traditional office suites so there shouldn't be much of a learning curve.<br />
<br />
If you prefer collaborative writing, perhaps check out <a href="https://nextcloud.com/">Nextcloud</a>. You'll need to host it somewhere, if you are in Alberta consider Cybera's <a href="https://www.cybera.ca/services/rapid-access-cloud/">Rapid Access Cloud</a> which uses <a href="https://www.openstack.org/">OpenStack</a>.<br />
<br />
<b>Diagrams and Graphics</b><br />
<br />
<a href="https://inkscape.org/">Inkscape</a> is a great vector editing and layout program. For image editing and creation, check out <a href="https://www.gimp.org/">GIMP</a>, <a href="https://krita.org/en/">Krita</a>, or <a href="http://mypaint.org/about/">MyPaint</a>.<br />
<br />
<b>Audio</b><br />
<br />
One of the best simple audio recording and editing programs is also open source, <a href="https://www.audacityteam.org/">Audacity</a>. There are others, of course, but it should do everything you need.<br />
<br />
<b>Video</b><br />
<br />
My favorite open-source video editing program is <a href="https://www.openshot.org/">Open Shot</a>, but you may also want to check out <a href="https://shotcut.org/">Shotcut</a>.<br />
<br />
Hosting video is another issue, though. You can host videos in a learning management system such as <a href="https://moodle.org/">Moodle</a>, or check out alternatives such as <a href="https://mediagoblin.org/">MediaGoblin</a>, <a href="https://github.com/kaltura/platform-install-packages">Kaltura</a>, or <a href="https://clipbucket.com/">ClipBucket</a>.<br />
<br />
<b>Operating System</b><br />
<br />
Linux has gotten much easier to install and use if you'd like to replace Windows or MacOS. My current favorite distribution is <a href="https://peppermintos.com/">Peppermint</a>.<br />
<br />
<b>Hosting</b><br />
<br />
As previously mentioned, Albertans can avoid the <a href="https://en.wikipedia.org/wiki/Big_Tech">big five</a> cloud providers by running servers on <a href="https://www.cybera.ca/services/rapid-access-cloud/">RAC</a>, but your institution may have self-hosted instances of <a href="https://pressbooks.com/">Pressbooks</a> or similar open-source hosting platforms.<br />
<br />
<br />
Hopefully those cover anything you may need to use when creating OER with OSS. In some cases these tools are preferable to commercial products.<br />
<br />
Of course if you are philosophically opposed to proprietary software then you are probably already familiar with most of these.<br />
<br />
As always, please comment if you have any other suggestions.David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.com0tag:blogger.com,1999:blog-226223073205416955.post-53945514843910758982020-02-03T07:21:00.000-07:002020-02-03T13:56:48.828-07:00Getting new copies of Jupyter notebooks with shutil and nbgitpuller<b>Getting a Fresh Set of Jupyter Notebooks</b><br />
<br />
If you would like to update your copy of notebooks, for example on the <a href="http://hub.callysto.ca/">Callysto Hub</a>, you can delete the folder and pull the files from GitHub again. This is useful if something no longer works, or if the repository has been updated.<br />
<br />
Unfortunately you can’t just select a directory in Jupyter hub and delete it if it contains files. One way to delete a folder, though, is to use the Python command <b>shutil.rmtree()</b> which is a shell utility command that will remove a whole directory tree.<br />
<br />
To remove a folder, create a new Python 3 notebook in the same folder as the one you want to delete (but not inside the folder to be deleted).
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-BmqSJDTOm6I/XjgrvJ_vIiI/AAAAAAANKUM/RcNcSP_sXZgTxnyKF6jlThwADmhPyBW_QCPcBGAYYCw/s1600/Jupyter%2Bnew%2Bnotebook.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="316" data-original-width="533" src="https://1.bp.blogspot.com/-BmqSJDTOm6I/XjgrvJ_vIiI/AAAAAAANKUM/RcNcSP_sXZgTxnyKF6jlThwADmhPyBW_QCPcBGAYYCw/s1600/Jupyter%2Bnew%2Bnotebook.png" /></a></div>
<br />
<br />
In a code cell, type (or paste) the following two lines:<br />
<br />
<pre>import shutil</pre>
<pre>shutil.rmtree('curriculum-notebooks')</pre>
<br />
Replace curriculum-notebooks with the name of the folder you would like to delete. Then run the cell, and you should see that the folder no longer exists.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-P1rtkJcIBA8/Xjgr8oTLL3I/AAAAAAANKUQ/RjQR0JmganIeEtY7iltEgwNWD1UNAqqGgCPcBGAYYCw/s1600/Jupyter%2Bnotebook%2Bdeleted.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="313" data-original-width="535" src="https://1.bp.blogspot.com/-P1rtkJcIBA8/Xjgr8oTLL3I/AAAAAAANKUQ/RjQR0JmganIeEtY7iltEgwNWD1UNAqqGgCPcBGAYYCw/s1600/Jupyter%2Bnotebook%2Bdeleted.png" /></a></div>
<br />
<br />
Then you can click on <a href="http://haytech.blogspot.com/2019/10/nbgitpuller-bookmarklet.html">an nbgitpuller link</a>, for example from <a href="http://callysto.ca/">callysto.ca</a>, that pulls down a new copy of the repository or notebook files that you are interested in.<br />
<br />
You can also see the process in <a href="https://youtu.be/9WlxLlwS5t8">this video</a>.David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.com0tag:blogger.com,1999:blog-226223073205416955.post-70377075978644561702020-01-29T09:30:00.002-07:002020-01-29T09:30:48.953-07:00Using the Bitly API with Google Apps Script (JavaScript)To get started with <a href="https://dev.bitly.com/v4_documentation.html">version 4 of the Bitly api</a> in <a href="https://developers.google.com/sheets/api/quickstart/apps-script">Google Apps Script</a>, ensure that you have a Bitly account with a username and password (not using the Facebook, Twitter, or Google login options).<br />
<br />
Assuming that you are developing applications or scripts that will be used for <a href="https://dev.bitly.com/v4/#section/Application-using-a-single-account">a single account</a> you don't need to worry about <a href="https://dev.bitly.com/v4/#section/Authentication">OAuth authentication</a>, you can get a generic access token from <a href="https://bitly.is/accesstoken">this link</a>.<br />
<br />
If you haven't already, create a <a href="https://developers.google.com/sheets/api/quickstart/apps-script">Google Apps Script</a>. Here are some functions you can use:<br />
<br />
<div class="gistLoad" data-id="38a500545ce7abc75b875f33f01c9f51" id="gist-38a500545ce7abc75b875f33f01c9f51">
<a href="https://gist.github.com/misterhay/38a500545ce7abc75b875f33f01c9f51">https://gist.github.com/misterhay/38a500545ce7abc75b875f33f01c9f51</a><br />
<script src="https://gist.github.com/misterhay/38a500545ce7abc75b875f33f01c9f51.js" type="text/javascript"></script>
<br />
<br />
Hopefully that's enough to get you started. You can browse the rest of the <a href="https://dev.bitly.com/v4_documentation.html">Bitly API documentation</a> for other functions.</div>
David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.com3tag:blogger.com,1999:blog-226223073205416955.post-81318406228518799362020-01-02T12:39:00.000-07:002020-01-02T12:39:46.997-07:00Looking forward to technologies of the 2020sYears ago I heard the phrase "In 2021, hindsight will be 20/20". I'm certainly not the prognosticator on the level of <a href="https://en.wikipedia.org/wiki/Ray_Kurzweil">Ray Kurzeweil</a>, but as we start turn over the year number in the tens place I'm thinking about technologies that I'm looking forward to in the next ten years. In no particular order I am anticipating:<br />
<br />
<b>Better digital assistants and smart devices</b><br />
Smart speakers and phone-based digital assistants are useful for some things, but we still find ourselves repeating or rewording commands. There are also quite a few instances of "I can't help with that yet". I'm optimistic that we will get to the point of our digital assistants learning how to work for us, and our smart homes adjusting to our lifestyles and patterns without us having to continually yell at them and reconnect devices to the WiFi. These devices and services should make it less work for us to accomplish tasks.<br />
<br />
<b>More automation of uninteresting tasks (including driving)</b><br />
I'm a big fan of freeing up humans to accomplish uniquely human achievements, or watch more streaming video if they prefer. To help achieve that, I'm looking forward to more automation of tedious or dangerous tasks. While this may lead to more unemployment, or less depending on who you listen to, maybe we can get to the point as a society of eliminating jobs that people dislike. And perhaps decrease our workload without decreasing our standard of living, so that we have more time for volunteering and leisure activities.<br />
<br />
<b>Better virtual reality and augmented reality</b><br />
VR seems to be used primarily for leisure activities such as video games, and perhaps some basic <a href="https://edu.google.com/products/vr-ar/expeditions">virtual field trips</a>, but there are many other <a href="https://en.wikipedia.org/wiki/Virtual_reality_applications">interesting applications</a>. It still feels like early days, especially with AR, but the technological capabilities and developer proficiencies are improving. I'm particularly excited about the potential for psychological treatments using VR and AR.<br />
<br />
<b>Advances in psychology and neuroscience</b><br />
Technologies such as functional magnetic resonance imaging are giving us better insights into how brains work, and it feels like we are just at the beginning of our understanding. We may start to see useful mind-computer interfaces, and at some point even the ability to create backups...<br />
<br />
<b>Improved information literacy</b><br />
As we understand more about how our minds work, perhaps we can devote more resources to helping people distinguish reality from fiction. There seems to be a growing recognition that disinformation and "face news" are issues, but no consensus about what is fake and how to deal with this issue. I'm optimistic, however, that it is a solvable problem. It may require a re-think of how we remunerate individuals and organizations, and our advertising-based economy.<br />
<br />
<b>eSports</b><br />
We've seen a growing phenomenon of watching other people play video games, and "cyberathletes" making the kind of money that professional athletes can make. Not sure if this will supplant traditional sports, or if we would want them to, but the risk of concussions is lower. There is automated comment moderation in some streams, but the communities tend to be somewhat toxic, especially for female gamers. Although I don't usually recommend technological solutions to behavioral problems, internet filtering for example, maybe technology can help teach us to be better people.<br />
<br />
<b>Ubiquitous data access</b><br />
I am in favor of better data access, and improved information for education and decision making (I believe Wikipedia is one of humanity's greatest achievements). We are seeing intriguing ideas about ways to provide internet access in remote areas, such as drones, balloons, or low Earth orbit satellites (which hopefully won't interfere with astronomy). This is particularly useful for underprivileged areas and people groups.<br />
<br />
<b>Blockchain</b><br />
Beyond cryptocurrencies, which may help when government-backed currencies are subject to catastrophic inflation, there are other applications of blockchain ledgers for identity management and verification, supply chain tracking, contracts and asset transfer, and other financial services such as payments and escrow. We may even see blockchain-based voting.<br />
<br />
These are some of the technologies and trends that I'm thinking about these days. Of course we will likely see some unexpected dark horse technologies have significant impacts on our lives and society. But as they say, predictions are difficult. What are your thoughts?David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.com0tag:blogger.com,1999:blog-226223073205416955.post-87594623826113186982019-11-18T23:10:00.001-07:002019-11-22T07:21:34.639-07:00Automated Scheduling of Facebook Live Video via Google Apps ScriptCurrently Facebook allows users to <a href="https://www.facebook.com/help/publisher/2087325741287572">schedule a live video broadcast</a> only a week in advance. If you broadcast weekly this can get tedious, so with a little bit of coding we can automate the process.<br />
<br />
This will require a <span style="color: #0b5394;">F</span>acebook account (with admin rights to a page if you broadcast through that page) as well as a <span style="color: #38761d;">G</span>oogle account. We are going to create a Google Spreadsheet with an associated Google Apps Script that will run every week to schedule your live video.<br />
<ol>
<li><span style="color: #38761d;">C</span>reate a <a href="http://sheet.new/">Google Spreadsheet</a></li>
<li><span style="color: #38761d;">U</span>nder the <b>Tools</b> menu, click <b>Script editor</b></li>
<li><span style="color: #38761d;">I</span>nto that new script paste the contents of <a href="https://raw.githubusercontent.com/misterhay/Facebook-Live-Video-Scheduling/master/facebook-live-video-scheduler.gs">this file</a></li>
<li><span style="color: #0b5394;">C</span>reate a new Facebook App under the <b>My Apps</b> menu on <a href="https://developers.facebook.com/">developers.facebook.com</a></li>
<li><span style="color: #0b5394;">U</span>nder <b>Settings</b> click on <b>Basic</b> and copy your App ID and App Secret (click <b>Show</b>) into the appropriate places back in your <span style="color: #38761d;">G</span>oogle Script editor</li>
<li><span style="color: #38761d;">S</span>etup <a href="https://github.com/gsuitedevs/apps-script-oauth2#setup">gsuitedevs' OAuth2 library for Google Apps Script</a> by following the four steps there</li>
<li><span style="color: #38761d;">R</span>un the <b>logRedirectUri</b> function in your Google Script, then under the <b>View</b> menu click <b>Logs</b>, copy the Redirect URL (starting with http)</li>
<li><span style="color: #0b5394;">O</span>n the Facebook developer page, set up <b>Facebook Login</b> for your app</li>
<li><span style="color: #0b5394;">U</span>nder the Facebook Login Settings, paste in the OAuth Redirect URI that you copied earlier</li>
<li><span style="color: #38761d;">I</span>n your Google Script, run the <b>scheduleLivestream</b> function, this won't actually schedule anything yet but instead will log a URL for you to paste into your browser to authorize the script (under the View menu click Logs, or look at the original spreadsheet)</li>
<li><span style="color: #0b5394;">U</span>sing the <a href="https://developers.facebook.com/tools/explorer">Facebook Graph API Explorer</a>, add the following permissions to your app: <b>pages_show_list</b>, <b>manage_pages</b>, <b>publish_pages</b>, <b>publish_video</b></li>
<li><span style="color: #38761d;">R</span>un the Google Script function <b>scheduleLivestream</b> again and watch for any errors, if it successfully schedules a broadcast then you'll see a new line in your spreadsheet</li>
<li><span style="color: #38761d;">A</span>dd triggers to your Google Script: under the <b>Edit</b> menu click <b>Current project's triggers</b></li>
<ol>
<li>Click <b>Add Trigger</b> and set the options:</li>
<ol>
<li>scheduleLivestream</li>
<li>Head</li>
<li>Time-driven</li>
<li>Week timer</li>
<li>Every Wednesday (or whatever you prefer)</li>
<li>1pm to 2pm (or whatever)</li>
<li>then click <b>Save</b></li>
</ol>
<li>Click <b>Add Trigger</b> and set the options:</li>
<ol>
<li>onOpen</li>
<li>Head</li>
<li>From spreadsheet</li>
<li>On open</li>
<li>then click <b>Save</b></li>
</ol>
</ol>
<li><span style="color: #38761d;">I</span>n your Google Script, change the following lines:</li>
<ol>
<li><span style="color: purple;">var</span> <span style="color: blue;">status</span> = <span style="color: red;">'SCHEDULED_UNPUBLISHED'</span>; to <span style="color: purple;">var</span> <span style="color: blue;">status</span> = <span style="color: red;">'SCHEDULED_LIVE'</span>;</li>
<li><span style="color: purple;">var</span> <span style="color: blue;">day</span> = <span style="color: #3d85c6;">dateNow</span>.getDate() + ((<b>7</b> - <span style="color: #3d85c6;">dateNow</span>.getDay()) % 7); to <span style="color: purple;">var</span> <span style="color: blue;">day</span> = <span style="color: #3d85c6;">dateNow</span>.getDate() + ((<b>9</b> - <span style="color: #3d85c6;">dateNow</span>.getDay()) % 7); // for Tuesday</li>
<li><span style="color: purple;">var</span> <span style="color: blue;">newDate</span> = <span style="color: purple;">new</span> Date(<span style="color: #0b5394;">year</span>, <span style="color: #0b5394;">month</span>, <span style="color: #0b5394;">day</span>, 10, 25); to the time you'd like to start the broadcast</li>
<li>you can also change the <span style="color: purple;">var</span> <span style="color: blue;">title</span> line to whatever you'd like</li>
</ol>
<li><span style="color: #38761d;">R</span>un the <b>scheduleLivestream</b> function again to test, and to make sure everything has been authorized</li>
<li>Check at some point after the Trigger time you set to make sure that the broadcast has been scheduled</li>
</ol>
Hopefully all of that worked, let me know if I've missed any steps or if this needs more detail.<br />
<ol>
</ol>
David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.com0tag:blogger.com,1999:blog-226223073205416955.post-31689727535335610312019-11-07T16:53:00.000-07:002019-11-07T16:53:00.141-07:00The Benefits of Small ConferencesIn the last few weeks I have been to quite a few conferences around Alberta, and have been reflecting on the ideal conference size. A conference should involve information sharing and discussions or networking, the latter may be less possible at larger conferences.<br />
<br />
Events like the annual ISTE Conference with about 20,000 attendees are impressive, and you may <a href="https://edtechmagazine.com/k12/media/video/iste-2012-mayim-bialik-wants-help-stem-blossom">meet someone like Mayim Bialik</a>, but it feels like the odds of meeting someone with whom you can have sustained and ongoing discussions are decreased. There is an ocean of information available, but I'd argue that through the internet we already have that available.<br />
<br />
So perhaps the ideal conference size is between 150 and 500 participants. Large enough for a diversity of perspectives, but small enough to be able to meaningfully share those perspectives and get to know people. We tend to value growth as a measure of success, but perhaps we need other metrics.<br />
<br />
Thoughts?David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.com0tag:blogger.com,1999:blog-226223073205416955.post-89779095101867477332019-11-04T09:35:00.000-07:002019-11-04T09:35:06.992-07:00Colab Git Puller BookmarkletSimilar to the <a href="http://haytech.blogspot.com/2019/10/nbgitpuller-bookmarklet.html">Callysto nbgitpuller bookmarklet</a>, here's the <a href="https://gist.github.com/misterhay/97119a75d67c0070481a747176dd108b">code</a> for a bookmarklet to open a <a href="https://en.wikipedia.org/wiki/Project_Jupyter#Jupyter_Notebook">Jupyter notebook</a> from <a href="http://github.com/">GitHub</a> in <a href="https://colab.research.google.com/">Google Colab</a>. Drag the following link to your bookmark bar/menu:<br />
<br />
<a href="javascript:(function(){var url=location.href;var res=url.split("github.com/");var site=res[1];colabGitUrl="https://colab.research.google.com/github/"+site;window.prompt("Colab gitpuller link",colabGitUrl);})();">ColabGitPuller</a><br />
<br />
Click that bookmarklet when you are viewing a Jupyter notebook on GitHub, it will pop up a window with a link. Copy that link and you can use it to open the notebook in Colab.<br />
<br />
If you prefer a bookmarklet that just opens the notebook in Colab (rather than giving you a link to paste somewhere) then use this one:<br />
<br />
<a href="javascript:(function(){var url=location.href;var res=url.split("github.com/");var site=res[1];colabGitUrl="https://colab.research.google.com/github/"+site;window.open(colabGitUrl);})();">ColabGitOpener</a><br />
<br />
As always, comment if you encounter any issues or have any suggestions.David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.com0tag:blogger.com,1999:blog-226223073205416955.post-73374584005693796342019-10-16T10:05:00.002-06:002020-04-15T14:28:44.778-06:00nbgitpuller bookmarkletWe use <a href="https://github.com/jupyterhub/nbgitpuller">nbgitpuller</a> with <a href="https://jupyter.org/hub">JupyterHub</a> to distribute notebooks from <a href="http://github.com/">GitHub</a>. Nbgitpuller is a tool for copying a code repository from GitHub to your own Jupyter server.<br />
<br />
Instead of manually formatting the nbgitpuller URLs or using a <a href="https://jupyterhub.github.io/nbgitpuller/link">link generator</a>, I wanted to automate the process. So here's a basic bookmarklet that automatically generates nbgitpuller links for our <a href="http://hub.callysto.ca/">Callysto Hub</a>:<br />
<br />
<pre>javascript:(function(){var url=location.href;var res=url.split("/");var site=res[2];var user=res[3];var repo=res[4];var treeBlob=res[5];var branch=res[6];var nbgitputllerUrl="https://hub.callysto.ca/jupyter/hub/user-redirect/git-pull?repo=";if(site=="github.com"){if(treeBlob){var subPath=url.substring(url.indexOf(branch)+branch.length+1);nbgitputllerUrl+=encodeURIComponent("https://github.com/"+user+"/")+repo+"&branch="+branch+"&subPath="+subPath+"&depth=1";}else{nbgitputllerUrl+=url;}}window.prompt("Callysto nbgitpuller link",nbgitputllerUrl);})();
</pre>
<br />
If you're not familiar with <a href="https://en.wikipedia.org/wiki/Bookmarklet">bookmarklets</a>, they're basically bits of Javascript in the URL portion of a browser bookmark. To set one up, drag this link to your bookmark bar/menu:<br />
<br />
<a href="javascript:(function(){var url=location.href;var res=url.split("/");var site=res[2];var user=res[3];var repo=res[4];var treeBlob=res[5];var branch=res[6];var nbgitputllerUrl="https://hub.callysto.ca/jupyter/hub/user-redirect/git-pull?repo=";if(site=="github.com"){if(treeBlob){var urlpath="notebooks/"+repo+url.substring(url.indexOf(branch)+branch.length);nbgitputllerUrl+="https://github.com/"+user+"/"+repo+"&branch="+branch+"&urlpath="+urlpath;}else{nbgitputllerUrl+=url;}}window.prompt("Callysto nbgitpuller link",nbgitputllerUrl);})();">CallystoGitPuller</a><br />
<br />
Then when you are on a GitHub page you can click that bookmark and it will pop up a prompt with an nbgitpuller link to open that GitHub notebook or repository in CallystoHub.<br />
<br />
Here is the code as a <a href="https://gist.github.com/misterhay/99230efa6ab967a6fefb1dfe03e9efce">GitHub gist</a> if you'd like to make your own and perhaps use <a href="http://wbic16.xedoloh.com/cruncher.html">JavaScript Cruncher</a> to condense it to a single URL-encoded link.<br />
<br />
Edit:<br />
<br />
I've also posted about a <a href="http://haytech.blogspot.com/2019/11/colab-git-puller-bookmarklet.html">similar bookmarklet to open notebooks in Colab</a>.David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.com0tag:blogger.com,1999:blog-226223073205416955.post-33188770951034632722019-10-13T14:54:00.004-06:002019-10-13T14:54:59.347-06:00SRT vs NDII've previously posted about both <a href="https://en.wikipedia.org/wiki/Network_Device_Interface">NDI</a> and <a href="https://en.wikipedia.org/wiki/Secure_Reliable_Transport">SRT</a>, they are great video transmission protocols. Both are unicast by default, but can support multiple clients or players. There are some differences that might make you choose one over the other, here are a few that I can think of:<br />
<br />
SRT is open source, NDI is royalty-free<br />
SRT is low latency, NDI is very low latency<br />
SRT can work over WiFi or across public internet, NDI works best over gigabit LAN<br />
SRT support is coming to OBS, NDI is available in OBS via a plugin<br />
SRT seems to be supported by more digital signage players<br />
NDI seems to be supported by more cameras<br />
<br />
For myself, I'll continue to implement both for different use cases.<br />
<br />
Please comment below about missing information or errors.David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.com2tag:blogger.com,1999:blog-226223073205416955.post-54739207255584160212019-10-03T21:08:00.001-06:002021-04-19T22:56:37.949-06:00Low Latency Streaming from OBS to BrightSign Using SRTThere are a few free video streaming services that support low-latency streaming over the internet, notably <a href="https://mixer.com/">Mixer</a> and <a href="https://support.google.com/youtube/answer/7444635?hl=en">YouTube ultra-low latency</a>. However if you prefer local streaming (perhaps to conserve bandwidth) or prefer open source, let's take a look at how you can stream from <a href="https://obsproject.com/">OBS Studio</a> to <a href="https://www.brightsign.biz/">BrightSign</a> players (not open source) or other devices that support <a href="https://www.haivision.com/products/srt-secure-reliable-transport/">SRT streaming protocol</a> such as <a href="http://videolan.org/">VLC</a>.<br />
<br />Edit: <a href="https://obsproject.com/wiki/Streaming-With-SRT-Protocol#option-1-stream-srt-using-the-streaming-output">OBS supports SRT as of version 25</a>, but I'll leave this post up for historical reasons.<div><br /></div><div>--- <br /><br />
Hopefully at some point soon <a href="https://www.srtalliance.org/srt-alliance-announces-the-addition-of-the-srt-low-latency-protocol-to-open-broadcaster-softwares-obs-studio/">OBS will support SRT</a>, but for now we'll need to run a separate application. Assuming that you are already using OBS for streaming to an external destination, we'll use the "recording" feature to send to Haivision's <a href="https://github.com/Haivision/srt/blob/master/docs/stransmit.md">srt-live-transmit</a> application.<br />
<br />
There's some work required to compile <b>srt-live-transmit</b>, and I'll admit I haven't gotten it to work on Windows yet, but it's easy on <a href="https://github.com/Haivision/srt#for-linux">Linux</a> (including <a href="https://docs.microsoft.com/en-us/windows/wsl/install-win10">Windows Subsystem for Linux</a>) and Mac.<br />
<h4>
SRT for Mac</h4>
Getting SRT on Mac just involves launching the "Terminal" program and running two commands:<br />
<br />
<pre>brew update
brew install srt
</pre>
<br />
This will install <b>srt-live-transmit</b> at:<br />
<br />
<pre>/usr/local/Cellar/srt/1.4.0/bin/srt-live-transmit</pre>
<h4>
SRT for Linux or WSL</h4>
If you have Ubuntu installed under Windows 10 through Windows Subsystem for Linux, you can run the following commands in the terminal:<br />
<br />
<pre>sudo apt update
sudo apt upgrade
sudo apt install tclsh pkg-config cmake libssl-dev build-essential git
git clone https://github.com/Haivision/srt.git
cd srt
./configure
make
</pre>
<br />
After the compilation process, you should have:<br />
<br />
<pre>~/srt/srt-live-transmit</pre>
<br />
<h3>
Sending from OBS to srt-live-transmit</h3>
From the terminal (on Mac, Linux, or WSL), run the following command:<br />
<br />
<pre>./srt-live-transmit udp://:1234 srt://:5678 -v</pre>
<br />
<i>On your Mac you may first need to change to the right directory:</i><br />
<pre>cd /usr/local/Cellar/srt/1.4.0/bin/</pre>
<br />
That command listens for a UDP stream (that we'll send from OBS) and listens for a request to unicast it as an SRT stream. The -v means verbose mode, which allows us to read what it is doing.<br />
<br />
In OBS we need to go into <b>Settings</b>, click on <b>Output</b>, switch the Output Mode to <b>Advanced</b> and click on the <b>Recording</b> tab. Switch the Type to <b>Custom Output (FFmpeg)</b> and the FFmepeg Output Type to <b>Output to URL</b>.<br />
<br />
The File path or URL should be set to <b>udp://127.0.0.1:1234?pkt_size=1316</b> (127.0.0.1 means sending to the same computer that OBS is running on) and the Container Format should be changed to <b>mpegts</b>. I haven't done any testing, but I'm assuming that decreasing the Keyframe interval should decrease latency, and decreasing the Audio Bitrate is probably fine to decrease bandwidth usage.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-Fc_aRIIVllo/XZQsHuHDJ1I/AAAAAAAMy9k/DNeifRItP14IN8fVvUQgCY-em3n7sP_EACLcBGAsYHQ/s1600/OBS_SRT_Settings.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1214" data-original-width="1600" height="484" src="https://1.bp.blogspot.com/-Fc_aRIIVllo/XZQsHuHDJ1I/AAAAAAAMy9k/DNeifRItP14IN8fVvUQgCY-em3n7sP_EACLcBGAsYHQ/s640/OBS_SRT_Settings.png" width="640" /></a></div>
<br />
<br />
Click <b>OK</b>, then the <b>Start Recording</b> button. Have a look at the terminal window where srt-live-transmit is running. You should see information about the stream.<br />
<br />
Add the SRT source to BrightSign or play it as a network source in VLC (if your sending computer is at 10.0.0.200 then <b>srt://10.0.0.200:5678</b>). You should see your stream that is being transmitted from OBS and relayed by srt-live-transmit.<br />
<br />
Hopefully this works for you, feel free to comment if you encounter issues.</div>David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.com0tag:blogger.com,1999:blog-226223073205416955.post-39967218519546977012019-10-03T16:00:00.003-06:002019-10-03T16:00:20.756-06:00Using Git from the Command LineThis post is more for my future self, but others may find it useful. Feel free to comment if I've made any errors or missed anything important.<br />
<br />
Assuming that Git is already installed, to start using it with terminal commands we need to set up a couple of things:<br />
<br />
<pre>git config --global user.email "INSERT_EMAIL_ADDRESS_HERE"
git config --global user.name "MisterHay"
git config --global core.editor nano</pre>
<br />
It's also a good idea to <a href="https://help.github.com/en/articles/connecting-to-github-with-ssh">set up SSH keys</a>.<br />
<br />
and then clone the appropriate repository:<br />
<br />
<pre>git clone https://github.com/callysto/curriculum-notebooks.git</pre>
<br />
or with ssh:<br />
<br />
<pre>git clone git@github.com:callysto/curriculum-notebooks.git</pre>
<br />
and some useful commands:<br />
<br />
list branches:<br />
<pre>git branch -a</pre>
<br />
get files from GitHub:<br />
<pre>git checkout origin/staging</pre>
<br />
create branch from staging:<br />
<pre>git checkout origin/staging -b NameOfNewBranch</pre>
<br />
change to branch (if not already there):<br />
<pre>git checkout --track origin/NameOfBranch</pre>
<br />
edit files, then (stage changes):<br />
<pre>git add changed_file.ipynb</pre>
or <br />
<pre>git add ./*</pre>
<br />
check with:<br />
<pre>git status</pre>
<br />
discard all local changes to all files permanently:<br />
<pre>git reset --hard</pre>
<br />
commit to local:<br />
<pre>git commit</pre>
<br />
push to remote:<br />
<pre>git push -u origin NameOfBranch</pre>
<br />
then create a pull request to staging from that_branch using GitHub.com interface<br />
<br />
To see what has been happening, check the top of<br />
<pre>git log</pre>
<br />
Hopefully that's enough to get you (future me back) up to speed.David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.com0tag:blogger.com,1999:blog-226223073205416955.post-22832288723633905322019-09-11T16:30:00.000-06:002019-09-11T16:30:00.384-06:00My new job: Callysto AmbassadorSince I haven't written a blog post in a while, perhaps I can talk briefly about my new job. Funding has been <a href="https://www.canada.ca/en/innovation-science-economic-development/news/2019/09/government-of-canada-announces-new-recipients-under-second-phase-of-cancode-program.html">officially announced</a> for <a href="https://www.pims.math.ca/">PIMS</a> and <a href="http://cybera.ca/">Cybera</a>'s <a href="https://www.ic.gc.ca/eic/site/121.nsf/eng/00003.html">CanCode</a> project entitled "<a href="http://callysto.ca/">Callysto</a>: Building Tomorrow's Digital Leaders". I'm taking a year off from teaching to help with resource development and teacher training for the Callysto project.<br />
<br />
Callysto aims to foster computational thinking and data science skills in students and for teachers. There are some <a href="https://github.com/callysto/curriculum-notebooks">curriculum-related notebooks</a>, a <a href="https://hub.callysto.ca/">Jupyter Hub</a>, <a href="https://courses.callysto.ca/">online courses</a> (being developed), supports for teacher to develop notebooks, as well as opportunities to offer (call for proposals coming soon) or attend professional learning activities, or participate in educational research.<br />
<br />
Everything through the Callysto project is available for free (<a href="https://en.wikipedia.org/wiki/Gratis_versus_libre">gratis and libre</a>).<br />
<br />
Let us know if you're <a href="https://callysto.ca/contact/">interested</a>.David Hayhttp://www.blogger.com/profile/05136336165017176455noreply@blogger.com0