diff --git a/Jenkinsfile b/Jenkinsfile index bce278f83..cc0fc7105 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -67,6 +67,9 @@ pipeline { } } post { + always { + jiraSendBuildInfo site: 'kanocomputing.atlassian.net' + } regression { script { email.notifyCulprits() diff --git a/config/default.json b/config/default.json index 44b1ced24..8f028de9e 100644 --- a/config/default.json +++ b/config/default.json @@ -13,7 +13,7 @@ "EXTENSIONS": [{ "TYPE": "FILE_TYPE_ASSOCIATION", "NAME": "make-art", - "LOGO": "assets/kano-draw-windows.png", + "LOGO": "icon/kano-draw-windows.png", "DISPLAY_NAME": "Make Art", "SUPPORTED_FILE_TYPES": [{ "CONTENT_TYPE": "text/draw", @@ -21,7 +21,7 @@ }] }] }, - "BACKGROUND_COLOR": "#51555c", + "BACKGROUND_COLOR": "#333333", "CONFIG": { "DEBUG_LEVEL" : 1, "PRODUCTION" : false, diff --git a/ga.js b/ga.js index 8662e2ad1..3daf0b96b 100644 --- a/ga.js +++ b/ga.js @@ -1,195 +1,14 @@ +window.GA_MEASUREMENT_ID = 'UA-45135100-24'; -// Copyright 2012 Google Inc. All rights reserved. -(function(){ +const script = document.createElement('script'); +script.async = 1; +script.src = `https://www.googletagmanager.com/gtag/js?id=${window.GA_MEASUREMENT_ID}`; +document.head.appendChild(script); - var data = { - "resource": { - "version":"1", - - "macros":[], - "tags":[], - "predicates":[], - "rules":[] - }, - "runtime":[ - [],[] - ] - - - - }; - var aa,ba=this||self,da=/^[\w+/_-]+[=]{0,2}$/,ea=null;var fa=function(){},ha=function(a){return"function"==typeof a},ia=function(a){return"string"==typeof a},ja=function(a){return"number"==typeof a&&!isNaN(a)},ka=function(a){return"[object Array]"==Object.prototype.toString.call(Object(a))},la=function(a,b){if(Array.prototype.indexOf){var c=a.indexOf(b);return"number"==typeof c?c:-1}for(var d=0;db)a=0,b=2147483647;return Math.floor(Math.random()*(b-a+1)+a)},qa=function(a,b){for(var c=new pa,d=0;d"+a+"";b=b.lastChild;for(var c=[];b.firstChild;)c.push(b.removeChild(b.firstChild));return c},Ya=function(a,b,c){c=c||100;for(var d={},e=0;ec?a.href:a.href.substr(0,c)}return b}, - jb=function(a){var b=C.createElement("a");a&&(b.href=a);var c=b.pathname;"/"!==c[0]&&(a||bb("TAGGING",1),c="/"+c);var d=b.hostname.replace(db,"");return{href:b.href,protocol:b.protocol,host:b.host,hostname:d,pathname:c,search:b.search,hash:b.hash,port:b.port}};var kb=function(a,b,c){for(var d=[],e=String(b||document.cookie).split(";"),g=0;gb)){var c=a.substring(0,b);if(Cc.test(c)){for(var d=a.substring(b+1).split("/"),e=0;e=ad--?(bb("GTM",1),Zc[Wc]=!0):($c.Hg(),Pa(bd()),Xc[Wc]=!0,Yc=""))},bd=function(){var a=Wc;if(void 0===a)return"";var b=cb("GTM"),c=cb("TAGGING");return[dd,Xc[a]?"":"&es=1",ed[a],b?"&u="+b:"",c?"&ut="+c:"",Uc(),Yc,"&z=0"].join("")},fd=function(){return[Qc,"&v=3&t=t","&pid="+oa(),"&rv="+Hc.Ab].join("")},gd="0.005000"> - Math.random(),dd=fd(),hd=function(){dd=fd()},Xc={},Yc="",Wc=void 0,ed={},Zc={},Vc=void 0,$c=function(a,b){var c=0,d=0;return{mg:function(){if(c=b&&(c=0);return c>=a},Hg:function(){wa()-d>=b&&(c=0);c++;d=wa()}}}(2,1E3),ad=1E3,id=function(a,b){if(gd&&!Zc[a]&&Wc!==a){cd();Wc=a;Yc="";var c;c=0===b.indexOf("gtm.")?encodeURIComponent(b):"*";ed[a]="&e="+c+"&eid="+a;Vc||(Vc=u.setTimeout(cd,500))}},jd=function(a,b,c){if(gd&&!Zc[a]&&b){a!==Wc&&(cd(),Wc=a);var d=String(b[fc.ka]||"").replace(/_/g, - "");0===d.indexOf("cvt")&&(d="cvt");var e=c+d;Yc=Yc?Yc+"."+e:"&tr="+e;Vc||(Vc=u.setTimeout(cd,500));2022<=bd().length&&cd()}};var kd={},ld=new pa,md={},nd={},rd={name:"dataLayer",set:function(a,b){f(od(a,b),md);pd()},get:function(a){return qd(a,2)},reset:function(){ld=new pa;md={};pd()}},qd=function(a,b){if(2!=b){var c=ld.get(a);if(gd){var d=sd(a);c!==d&&bb("GTM",5)}return c}return sd(a)},sd=function(a,b,c){var d=a.split("."),e=!1,g=void 0;var h=function(k,l){for(var m=0;void 0!==k&&mk;k++){var l=h[k].src;if(l){l=l.toLowerCase();if(0===l.indexOf(e)){b=3;break a}1===g&&0===l.indexOf(d)&&(g=2)}}b=g}else b=a;return b}; - var Dd=function(a,b,c){if(u[a.functionName])return b.Kc&&D(b.Kc),u[a.functionName];var d=Cd();u[a.functionName]=d;if(a.Cb)for(var e=0;ela(c,k))if(l&&0 - la(c,l[p])){bb("GTM",11);n=!1;break a}}else{n=!1;break a}n=!0}m=n}var t=!1;if(d){var q=0<=la(e,k);if(q)t=q;else{var r=qa(e,l||[]);r&&bb("GTM",10);t=r}}var v=!m||t;v||!(0<=la(l,"sandboxedScripts"))||c&&-1!==la(c,"sandboxedScripts")||(v=qa(e,Sd));return g[k]=v}},Td=function(){return Pd.test(u.location&&u.location.hostname)};var Vd={Gf:function(a,b){b[fc.ed]&&"string"===typeof a&&(a=1==b[fc.ed]?a.toLowerCase():a.toUpperCase());b.hasOwnProperty(fc.gd)&&null===a&&(a=b[fc.gd]);b.hasOwnProperty(fc.jd)&&void 0===a&&(a=b[fc.jd]);b.hasOwnProperty(fc.hd)&&!0===a&&(a=b[fc.hd]);b.hasOwnProperty(fc.fd)&&!1===a&&(a=b[fc.fd]);return a}};var Wd={active:!0,isWhitelisted:function(){return!0}},Xd=function(a){var b=Ic.zones;!b&&a&&(b=Ic.zones=a());return b};var Yd=!1,Zd=0,$d=[];function ae(a){if(!Yd){var b=C.createEventObject,c="complete"==C.readyState,d="interactive"==C.readyState;if(!a||"readystatechange"!=a.type||c||!b&&d){Yd=!0;for(var e=0;e<$d.length;e++)D($d[e])}$d.push=function(){for(var g=0;gZd){Zd++;try{C.documentElement.doScroll("left"),ae()}catch(a){u.setTimeout(be,50)}}}var ce=function(a){Yd?a():$d.push(a)};var de={},ee={},fe=function(a,b,c,d){if(!ee[a]||Kc[b]||"__zone"===b)return-1;var e={};Ha(d)&&(e=f(d,e));e.id=c;e.status="timeout";return ee[a].tags.push(e)-1},ge=function(a,b,c,d){if(ee[a]){var e=ee[a].tags[b];e&&(e.status=c,e.executionTime=d)}};function he(a){for(var b=de[a]||[],c=0;c=c&&he(a)})},qf:function(){d=!0;b>=c&&he(a)}}};var le=function(){function a(d){return!ja(d)||0>d?0:d}if(!Ic._li&&u.performance&&u.performance.timing){var b=u.performance.timing.navigationStart,c=ja(rd.get("gtm.start"))?rd.get("gtm.start"):0;Ic._li={cst:a(c-b),cbt:a(Oc-b)}}};var pe=!1,qe=function(){return u.GoogleAnalyticsObject&&u[u.GoogleAnalyticsObject]},re=!1; - var se=function(a){u.GoogleAnalyticsObject||(u.GoogleAnalyticsObject=a||"ga");var b=u.GoogleAnalyticsObject;if(u[b])u.hasOwnProperty(b)||bb("GTM",12);else{var c=function(){c.q=c.q||[];c.q.push(arguments)};c.l=Number(new Date);u[b]=c}le();return u[b]},te=function(a,b,c,d){b=String(b).replace(/\s+/g,"").split(",");var e=qe();e(a+"require","linker");e(a+"linker:autoLink",b,c,d)}; - var ve=function(){},ue=function(){return u.GoogleAnalyticsObject||"ga"};var Ce=function(a){};function Be(a,b){a.containerId=Hc.i;var c={type:"GENERIC",value:a};b.length&&(c.trace=b);return c};function De(a,b,c,d){var e=Tb[a],g=Ee(a,b,c,d);if(!g)return null;var h=bc(e[fc.Dd],c,[]);if(h&&h.length){var k=h[0];g=De(k.index,{I:g,S:1===k.Nd?b.terminate:g,terminate:b.terminate},c,d)}return g} - function Ee(a,b,c,d){function e(){if(g[fc.We])k();else{var w=cc(g,c,[]),x=fe(c.id,String(g[fc.ka]),Number(g[fc.Ed]),w[fc.Xe]),y=!1;w.vtp_gtmOnSuccess=function(){if(!y){y=!0;var A=wa()-B;jd(c.id,Tb[a],"5");ge(c.id,x,"success",A);h()}};w.vtp_gtmOnFailure=function(){if(!y){y=!0;var A=wa()-B;jd(c.id,Tb[a],"6");ge(c.id,x,"failure",A);k()}};w.vtp_gtmTagId=g.tag_id; - w.vtp_gtmEventId=c.id;jd(c.id,g,"1");var z=function(A){var E=wa()-B;Ce(A);jd(c.id,g,"7");ge(c.id,x,"exception",E);y||(y=!0,k())};var B=wa();try{ac(w,c)}catch(A){z(A)}}}var g=Tb[a],h=b.I,k=b.S,l=b.terminate;if(c.Cc(g))return null;var m=bc(g[fc.Fd],c,[]);if(m&&m.length){var n=m[0],p=De(n.index,{I:h,S:k,terminate:l},c,d);if(!p)return null;h=p;k=2===n.Nd?l:p}if(g[fc.Cd]||g[fc.Ye]){var t=g[fc.Cd]?Ub:c.Rg,q=h,r=k;if(!t[a]){e=ya(e);var v=Fe(a,t,e);h=v.I;k=v.S}return function(){t[a](q,r)}}return e} - function Fe(a,b,c){var d=[],e=[];b[a]=Ge(d,e,c);return{I:function(){b[a]=He;for(var g=0;ge?1:dk?1:ha.length||!ia(a[1])||!Ha(b))return;var c=Ec(a[1]);if(!c)return;ef()?(ff(c),jf(c)):hf();gf(c.id);var d=c.id,e=b[G.fc]||"default";e=e.toString().split(",");for(var g=0;g>21:d;return[Math.round(2147483647*Math.random())^d&2147483647,Math.round(wa()/1E3)].join(".")},rg=function(a,b,c,d){var e=pg(b);return nb(a,e,qg(c),d)},sg=function(a,b,c,d){var e=""+pg(c),g=qg(d);1>2,m=(g&3)<<4|h>>4,n=(h&15)<<2|k>>6,p=k&63;e||(p=64,d||(n=64));b.push(Bg[l],Bg[m],Bg[n],Bg[p])}return b.join("")},Fg=function(a){function b(l){for(;d>4);64!=h&&(c+=String.fromCharCode(g<<4&240|h>>2),64!=k&&(c+=String.fromCharCode(h<<6&192|k)))}};var Gg;function Hg(a,b){if(!a||b===C.location.hostname)return!1;for(var c=0;cg;g++){for(var h=g,k=0;8>k;k++)h=h&1?h>>>1^3988292384:h>>>1;e[g]=h}d=e}Gg=d;for(var l=4294967295,m=0;m>>8^Gg[(l^c.charCodeAt(m))&255];return((l^-1)>>>0).toString(36)},Ug=function(){return function(a){var b=jb(u.location.href),c=b.search.replace("?",""),d=eb(c,"_gl",!0)||"";a.query=Tg(d)||{};var e=ib(b,"fragment").match(Qg);a.fragment=Tg(e&&e[3]|| - "")||{}}},Vg=function(){var a=Ug(),b=Kg();b.data||(b.data={query:{},fragment:{}},a(b.data));var c={},d=b.data;d&&(za(c,d.query),za(c,d.fragment));return c},Tg=function(a){var b;b=void 0===b?3:b;try{if(a){var c;a:{for(var d=a,e=0;3>e;++e){var g=Mg.exec(d);if(g){c=g;break a}d=decodeURIComponent(d)}c=void 0}var h=c;if(h&&"1"===h[1]){var k=h[3],l;a:{for(var m=h[2],n=0;nq){t=!0;break b}t=!1}t||sb(m,n,c,d,0==e?void 0:new Date(p+1E3*(null==e?7776E3:e)),!0)}}}var w={prefix:b,path:c,domain:d};jh(hh(g.gclid,g.gclsrc),w);},Bi=function(a,b,c,d,e){nh(a,b,c,d,e);},Ci=function(a,b){if(Ad()){ - b&&D(b)}else Oa(a,b)},Di=function(a){return!!ng(a,"init",!1)},Ei=function(a){lg(a,"init",!0)},Fi=function(a,b,c){var d=(void 0===c?0:c)?"www.googletagmanager.com/gtag/js":Mc;d+="?id="+encodeURIComponent(a)+"&l=dataLayer";b&&ra(b,function(e,g){g&&(d+="&"+e+"="+encodeURIComponent(g))});L(H("https://","http://",d))},Gi=function(a,b){var c=a[b];return c};var Hi=function(a,b,c,d,e,g){var h={config:a,gtm:zh()};c&&(yg(d,void 0,e,g),h.auiddc=ug[vg(d)]);b&&(h.loadInsecure=b);M("__dc_ns_processor",[]).push(h);L((b?"http":"https")+"://www.googletagmanager.com/dclk/ns/v1.js")}; - var Ii=gg.og; - var Ji=new pa,Ki=function(a,b){function c(h){var k=jb(h),l=ib(k,"protocol"),m=ib(k,"host",!0),n=ib(k,"port"),p=ib(k,"path").toLowerCase().replace(/\/$/,"");if(void 0===l||"http"==l&&"80"==n||"https"==l&&"443"==n)l="web",n="default";return[l,m,n,p]}for(var d=c(String(a)),e=c(String(b)),g=0;g=Number(c);case "_gt":return Number(b)>Number(c);case "_lc":var n;n=String(b).split(","); - return 0<=la(n,String(c));case "_le":return Number(b)<=Number(c);case "_lt":return Number(b)la(P,G.nb)&&(B.cookieName=Z+"_ga")}var ca=String(Pc);m(B,"cookieDomain","auto");m(z,"forceSSL",!0);var Ca="general";0<=la("add_payment_info add_to_cart add_to_wishlist begin_checkout checkout_progress purchase refund remove_from_cart set_checkout_option".split(" "),ca)?Ca="ecommerce":0<=la("generate_lead login search select_content share sign_up view_item view_item_list view_promotion view_search_results".split(" "),ca)?Ca="engagement":"exception"==ca&&(Ca="error");m(y,"eventCategory",Ca);0<=la(["view_item", - "view_item_list","view_promotion","view_search_results"],ca)&&m(z,"nonInteraction",!0);"login"==ca||"sign_up"==ca||"share"==ca?m(y,"eventLabel",vd(G.Le,v)):"search"==ca||"view_search_results"==ca?m(y,"eventLabel",vd(G.Re,v)):"select_content"==ca&&m(y,"eventLabel",vd(G.Ae,v));var R=y[G.ia]||{},O=R[G.Pa];O||0!=O&&R[G.B]?B.allowLinker=!0:!1===O&&m(B,"useAmpClientId",!1);if(!1===vd(G.ye,v)||!1===vd(G.N,v))z.allowAdFeatures=!1;B.name=w;z[">m"]=zh(!0);z.hitCallback=x;var na=vd("_x_19",v)||vi("gtag.remote_config."+ - v+".url"),Ta=vd("_x_20",v)||vi("gtag.remote_config."+v+".dualId");na&&(B._x_19=na);Ta&&(B._x_20=Ta);y.ca=z;y.Ba=B;return y},p=function(v){function w(J){var P=f(J);P.list=J.list_name;P.listPosition=J.list_position;P.position=J.list_position||J.creative_slot;P.creative=J.creative_name;return P}function x(J){for(var P=[],U=0;J&&U 0) { $rootScope.loadUserProfile(user.id); @@ -66,6 +68,7 @@ app.run(['$rootScope', '$window', '$location', '_config', ($rootScope, $window, } else { $rootScope.loggedIn = false; } + $rootScope.user = user; prepareGamification(api.client, user ? user.id : 'anonymous', 'anonymous') .then((gamificationClient) => { $rootScope.gamification = gamificationClient; @@ -204,6 +207,10 @@ app.run(['$rootScope', '$window', '$location', '_config', ($rootScope, $window, var redirection = route.$$route ? route.$$route.redirectTo : null; $rootScope.basePath = path ? path.split('/')[1] : ''; if (path && !redirection) { + if (window.gtag) { + window.gtag( + 'config', window.GA_MEASUREMENT_ID, { page_path: window.location.pathname }); + } Telemetry.trackPageView({ page: window.location.pathname }); } }); diff --git a/lib/directive/display.js b/lib/directive/display.js index 7b41c1e89..5d443183c 100644 --- a/lib/directive/display.js +++ b/lib/directive/display.js @@ -1,20 +1,22 @@ -"use strict"; import i18n from '../i18n.js'; import app from '../app.js'; import session from '../language/session.js'; import fileUtil from '../util/file.js'; import { throttle } from '../util/throttle.js'; -var firstRender = true, THROTTLE_MS, VALIDATE_DELAY, config; - import language from '../language/index.js'; +import { setWallpaper } from '../platform/uwp/wallpaper.js'; +import { isUWP } from '../platform/index.js'; +import notify from '../api/notify.js'; +import { renderWallpaper } from '../wallpaper/render-wallpaper.js'; +import { saveFile } from '../platform/uwp/file-picker.js'; + +var firstRender = true, THROTTLE_MS, VALIDATE_DELAY, config; /* * Display directive * * Handles live updatingm execution of code and rendering coming from its model */ - - app.directive('display', ['$window', '$rootScope', '_config', function ($window, $rootScope, _config) { config = $rootScope.cfg; THROTTLE_MS = config.OFFLINE ? 1000 : 1; @@ -35,6 +37,18 @@ app.directive('display', ['$window', '$rootScope', '_config', function ($window, var timer, win = angular.element($window), loadInput = element.find('input')[0]; + scope.setWallpaper = () => { + const width = window.screen.availWidth; + const height = window.screen.availHeight; + const wallpaperCanvas = renderWallpaper(scope.source, width, height); + setWallpaper(wallpaperCanvas).then(() => { + notify.message('Wallpaper updated', 'success'); + }); + }; + + + scope.canSetWallapper = () => isUWP(); + // Attach workspace to scope scope.workspace = workspaceCtl || null; /* @@ -271,7 +285,15 @@ app.directive('display', ['$window', '$rootScope', '_config', function ($window, blob = new Blob([scope.source], { type: 'text/plain' }); - fileUtil.downloadBlob(blob, 'creation.draw'); + if (isUWP()) { + saveFile(blob, 'creation.draw') + .then(() => { + notify.message('Creation saved', 'success'); + }); + } else { + fileUtil.downloadBlob(blob, 'creation.draw'); + } + $rootScope.gamification.trigger({ name: 'make-art-code-written', detail: { count: scope.source.split('\n').length } }); }; diff --git a/lib/directive/export-modal.js b/lib/directive/export-modal.js index e58af2b98..736932ac8 100644 --- a/lib/directive/export-modal.js +++ b/lib/directive/export-modal.js @@ -92,7 +92,7 @@ app.directive('exportModal', ['$rootScope', '$routeParams', '_config', ($rootSco return; } - if (!$rootScope.user.attributes.consent) { + if (('consent' in $rootScope.user.attributes) && !$rootScope.user.attributes.consent) { notify.message('Ask your parents to check their email. \nCurrently, you cannot publish your creations.', 'fail'); scope.close(); return; diff --git a/lib/language/index.js b/lib/language/index.js index db3d42cc9..f66037f3c 100644 --- a/lib/language/index.js +++ b/lib/language/index.js @@ -64,9 +64,11 @@ function run(code, settings) { }; } + const post = compiled.js.replace(/\(\)\s?=>/, 'function ()'); + // Evaluate compiled JavaScript in build context try { - evalInContext.bind({})(compiled.js); + evalInContext.bind({})(post); } catch (err) { // Trace back error location from compiled source map var jsLoc = getErrorLocation(err), @@ -209,7 +211,7 @@ function evalInContext(code) { ${code} }`); - const runner = fn() + const runner = fn(); runner(...values); } @@ -220,4 +222,5 @@ export default { checkValidity: checkValidity, evalInContext: evalInContext, cursorPosition: () => session.pos, + getBackground: () => session.settings.bg, }; diff --git a/lib/platform/index.js b/lib/platform/index.js new file mode 100644 index 000000000..04c16a458 --- /dev/null +++ b/lib/platform/index.js @@ -0,0 +1,5 @@ +export function isUWP() { + return ('Windows' in window); +} + +export default { isUWP }; diff --git a/lib/platform/uwp/file-picker.js b/lib/platform/uwp/file-picker.js new file mode 100644 index 000000000..9193c36cd --- /dev/null +++ b/lib/platform/uwp/file-picker.js @@ -0,0 +1,24 @@ +import { blobToString } from '../../util/file.js'; + +export function saveFile(blob, filename) { + const savePicker = new Windows.Storage.Pickers.FileSavePicker(); + savePicker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.DocumentsLibrary; + // Dropdown of file types the user can save the file as + savePicker.fileTypeChoices.insert('Make Art Creation', ['.draw']); + // Default file name if the user does not type one in or select a file to replace + savePicker.suggestedFileName = filename; + + return savePicker.pickSaveFileAsync() + .then((file) => { + if (!file) { + throw new Error('Could not save file: User cancelled'); + } + // Prevent updates to the remote version of the file until + // we finish making changes and call CompleteUpdatesAsync. + Windows.Storage.CachedFileManager.deferUpdates(file); + + return blobToString(blob) + .then((contents) => Windows.Storage.FileIO.writeTextAsync(file, contents)) + .then(() => Windows.Storage.CachedFileManager.completeUpdatesAsync(file)); + }); +} diff --git a/lib/platform/uwp/wallpaper.js b/lib/platform/uwp/wallpaper.js new file mode 100644 index 000000000..f90295b75 --- /dev/null +++ b/lib/platform/uwp/wallpaper.js @@ -0,0 +1,29 @@ +function saveWallpaper(canvas) { + const { Storage } = window.Windows; + const blob = canvas.msToBlob(); + const input = blob.msDetachStream(); + return Storage.ApplicationData.current.localFolder.createFileAsync('wallpaper.png', Storage.CreationCollisionOption.generateUniqueName) + .then(f => f.openAsync(Storage.FileAccessMode.readWrite) + .then((stream) => { + const output = stream.getOutputStreamAt(0); + return Storage.Streams.RandomAccessStream.copyAsync(input, output) + .then(() => stream.flushAsync()) + .then(() => { + output.close(); + stream.close(); + return f; + }); + })); +} + +export function setWallpaper(canvas) { + const { System } = window.Windows; + const { UserProfilePersonalizationSettings } = System.UserProfile; + return saveWallpaper(canvas) + .then((f) => { + const profileSettings = UserProfilePersonalizationSettings.current; + return profileSettings.trySetWallpaperImageAsync(f); + }); +} + +export default { setWallpaper }; \ No newline at end of file diff --git a/lib/util/file.js b/lib/util/file.js index 117c6dbf6..9eeea6eb1 100644 --- a/lib/util/file.js +++ b/lib/util/file.js @@ -56,8 +56,23 @@ function downloadBlob(blob, filename) { document.body.removeChild(link); } +export function blobToString(blob) { + return new Promise((resolve) => { + const reader = new FileReader(); + + // This fires after the blob has been read/loaded. + reader.addEventListener('loadend', (e) => { + const text = e.srcElement.result; + resolve(text); + }); + + // Start reading the blob as text. + reader.readAsText(blob); + }); +} export default { - dataURItoBlob : dataURItoBlob, - downloadBlob : downloadBlob + dataURItoBlob, + downloadBlob, + blobToString, }; \ No newline at end of file diff --git a/lib/wallpaper/render-wallpaper.js b/lib/wallpaper/render-wallpaper.js new file mode 100644 index 000000000..a07b7ee32 --- /dev/null +++ b/lib/wallpaper/render-wallpaper.js @@ -0,0 +1,23 @@ +import language from '../language/index.js'; + +export function renderWallpaper(code, width, height) { + const size = Math.min(width, height); + const renderSettings = {}; + renderSettings.canvas = document.createElement('canvas'); + renderSettings.canvas.width = size; + renderSettings.canvas.height = size; + renderSettings.ctx = renderSettings.canvas.getContext('2d'); + renderSettings.width = 500; + renderSettings.height = 500; + renderSettings.ratio = size / 500; + language.run(code, renderSettings); + const bg = language.getBackground(); + const finalCanvas = document.createElement('canvas'); + finalCanvas.width = width; + finalCanvas.height = height; + const ctx = finalCanvas.getContext('2d'); + ctx.fillStyle = bg || '#ffffff'; + ctx.fillRect(0, 0, width, height); + ctx.drawImage(renderSettings.canvas, (width - size) / 2, (height - size) / 2); + return finalCanvas; +} diff --git a/locales/en/directive.json b/locales/en/directive.json index 20a3f9b45..3f760a34e 100644 --- a/locales/en/directive.json +++ b/locales/en/directive.json @@ -1,4 +1,5 @@ { + "set_wallpaper": "set as wallpaper", "save_to_gallery": "save to gallery", "save_drawing": "save drawing", "title": "Title", diff --git a/package.json b/package.json index 06d6ae55e..251394191 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "draw", - "version": "1.1.0", + "version": "1.2.0", "description": "App to learn programming using a basic CoffeeScript drawing API", "main": "index.js", "scripts": { diff --git a/styles/elements/display.styl b/styles/elements/display.styl index 767080978..07a7e957e 100644 --- a/styles/elements/display.styl +++ b/styles/elements/display.styl @@ -48,16 +48,22 @@ display background #f5f5f5 .actions - position relative - padding-bottom 20px - padding-top 10px + display flex + flex-direction row + align-items center + justify-content space-around + padding 20px 0 text-align center + + &.challenge + .position-indicator + position absolute + left 16px .position-indicator + width 100px + text-align left display inline-block - margin 7px 15px - position absolute - left 0 font-weight bold font-size 13px text-transform uppercase @@ -81,12 +87,10 @@ display .action smooth-font() display inline-block - margin 0 15px text-transform uppercase font-size 14px font-weight bold position relative - top 4px cursor pointer &.action-share @@ -97,6 +101,9 @@ display &.action-reset color color-grey-light + + &.action-set-wallpaper + color color-red &.action-load color color-orange diff --git a/terraform/main.tf b/terraform/main.tf index a9dac183e..772921217 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -92,6 +92,7 @@ module "prod" { source = "git@github.com:KanoComputing/kes-terraform-modules//modules/aws/s3-cloudfront-website" fqdn = "art-prod.kano.me" + aliases = ["art.kano.me"] ssl_certificate_arn = "${aws_acm_certificate_validation.cert.certificate_arn}" allowed_ips = "${local.cf_ips}" diff --git a/terraform/route53.tf b/terraform/route53.tf index 6e3e5a9e9..f223d45d9 100644 --- a/terraform/route53.tf +++ b/terraform/route53.tf @@ -3,21 +3,26 @@ resource "aws_acm_certificate" "cert" { provider = "aws.cloudfront" - domain_name = "*.${var.domain}" + domain_name = "art.kano.me" + + subject_alternative_names = ["art-dev.kano.me", "art-staging.kano.me", "art-prod.kano.me"] validation_method = "DNS" lifecycle { create_before_destroy = true } } - resource "aws_route53_record" "cert_validation" { - provider = "aws.main" - name = "${aws_acm_certificate.cert.domain_validation_options.0.resource_record_name}" - type = "${aws_acm_certificate.cert.domain_validation_options.0.resource_record_type}" - zone_id = "${data.aws_route53_zone.main.id}" - records = ["${aws_acm_certificate.cert.domain_validation_options.0.resource_record_value}"] - ttl = 60 - lifecycle { + count = "4" + + provider = "aws.cloudfront" + zone_id = "${element(data.aws_route53_zone.main.*.id, 0)}" + + name = "${lookup(aws_acm_certificate.cert.domain_validation_options[count.index], "resource_record_name")}" + type = "${lookup(aws_acm_certificate.cert.domain_validation_options[count.index], "resource_record_type")}" + records = ["${lookup(aws_acm_certificate.cert.domain_validation_options[count.index], "resource_record_value")}"] + ttl = 60 + + lifecycle { create_before_destroy = true } } @@ -25,7 +30,7 @@ resource "aws_route53_record" "cert_validation" { resource "aws_acm_certificate_validation" "cert" { provider = "aws.cloudfront" certificate_arn = "${aws_acm_certificate.cert.arn}" - validation_record_fqdns = ["${aws_route53_record.cert_validation.fqdn}"] + validation_record_fqdns = ["${aws_route53_record.cert_validation.*.fqdn}"] lifecycle { create_before_destroy = true } diff --git a/views/directive/display.jade b/views/directive/display.jade index ebed04ef9..37926036f 100644 --- a/views/directive/display.jade +++ b/views/directive/display.jade @@ -17,7 +17,7 @@ p ${{ directive.error_text }}$ - .actions + .actions(ng-class="mode") .position-indicator i(ng-class="{'icon-mouse-pointer': mouse, 'icon-plus': !mouse}") @@ -35,11 +35,15 @@ i.icon-reset | ${{ directive.reset }}$ + a.action.action-set-wallpaper(ng-if='canSetWallapper() && mode === "playground"', ng-click='setWallpaper()') + i.icon-reset + | ${{ directive.set_wallpaper }}$ + .action.action-load(ng-show='mode === "playground"') i.icon-arrow-up | ${{ directive.load }}$ input.file-load(type='file', accept='.draw') - export-modal(display='this', action='"save"') - export-modal(display='this', action='"share"') +export-modal(display='this', action='"save"') +export-modal(display='this', action='"share"') diff --git a/views/partial/header.jade b/views/partial/header.jade index 7d407c6fb..36c227ee4 100644 --- a/views/partial/header.jade +++ b/views/partial/header.jade @@ -29,8 +29,8 @@ header: .main-navigation.page-width | ${{ partial.signup }}$ li(ng-if='!offline && loggedIn') - a.user-display(ng-href='http://world.kano.me/users/{{ user.username }}', target='_blank') - .avatar: img(ng-src='{{ user.avatar.urls.circle || cfg.DEFAULT_AVATAR }}') + a.user-display(ng-href='http://world.kano.me/profile/creations', target='_blank') + .avatar: img(ng-src='{{ user.avatar }}') | {{ user.username }} li(ng-if='!offline && loggedIn'): a(href='', ng-click='logout()')