-
-
Notifications
You must be signed in to change notification settings - Fork 162
/
Copy pathexport.js
1 lines (1 loc) · 15.8 KB
/
export.js
1
const application=require("application"),fs=require("uxp").storage.localFileSystem,formats=require("uxp").storage.formats,{Artboard:Artboard,Text:Text,Path:Path,Rectangle:Rectangle,Ellipse:Ellipse,Group:Group,SymbolInstance:SymbolInstance,ImageFill:ImageFill,RepeatGrid:RepeatGrid,BooleanGroup:BooleanGroup,GraphicNode:GraphicNode,Color:Color,LinearGradient:LinearGradient,RadialGradientFill:RadialGradientFill,Line:Line,Polygon:Polygon}=require("scenegraph"),fontWeightMap={Thin:100,ExtraLight:200,UltraLight:200,Light:300,Regular:400,Normal:400,Medium:500,SemiBold:600,DemiBold:600,Bold:700,ExtraBold:800,UltraBold:800,Black:900,Heavy:900},Tar=require("./lib/tar/tar.js");class ExportDoc{constructor(e,t,i){this.baseUrl=t,this.vectorList=[],this.guidToFileName={},this.displayNameToFileName=[],this.extractedGuids={},this.fileNames={},this.assetsFolder,this.imagesFolder,this.xdRoot=e,this.stateObj=i,this.artBoardNames={},this.artBoardNameCollisionCount=1,this.nodeIdToGameDefEntry={},this.gameDef={},this.gameDef.docType="gamecommerce_game_definition",this.gameDef.version=4,this.gameDef.Copyright="GameCommerce Inc (2019)",this.gameDef.screens=[],this.websiteMode}sanitizeDisplayName(e){let t=e.trim();return"Group"!==(t=t.replace("[^a-zA-Z0-9_.$- ]","_"))&&"Element"!==t||(t+="1"),this.artBoardNames[t]&&(t=t+"-"+this.artBoardNameCollisionCount,this.artBoardNameCollisionCount++),t}static deleteFolder(e){return new Promise(async function(t,i){try{if(e.isFile)await e.delete();else if(e.isFolder){let t=await e.getEntries();for(let e of t)e.isFile?e.delete():await ExportDoc.deleteFolder(e);await e.delete()}}catch(t){console.log("Failed to delete file/folder: "+e.name),console.log(t)}t()})}sanitizeFileName(e,t){(e=e.trim()).length;e.length>35&&(e=e.substring(0,35));let i=new RegExp("^[_$a-zA-Z][a-zA-Z_$0-9]*"),a=e.match(i),o="";if(a&&1===a.length&&a[0]===e)o=e;else{e.match(/^[A-Za-z_].*/)||(o="_"+o);let t=!1;for(let i=0;i<e.length;i++){let a=e.charAt(i);a>="0"&&a<="9"||a>="a"&&a<="z"||a>="A"&&a<="Z"||"_"===a||"$"===a||"-"===a?(t=!1,o+=a):(t||(o+="_"),t=!0)}}return o.length<4&&(o+=t),o}getUniqueIndex(e){let t=0;return e=e.toLowerCase(),null!==this.extractedGuids[e]&&void 0!==this.extractedGuids[e]?(this.extractedGuids[e]=this.extractedGuids[e]+1,t=this.extractedGuids[e]):(this.extractedGuids[e]=0,t=0),t}checkForFlatten(e){let t=!1;return e.mask&&(t=!0),t}getStyleRanges(e){let t=[];e.styleRanges&&e.styleRanges.length>1&&(t=e.styleRanges);for(let e of t){let t=e.fontFamily,i=t.split(" "),a=i[i.length-1],o=fontWeightMap[a];if(o){t=i.slice(0,i.length-1).join(" ")}else o=400;let n=e.fill.toHex(!0);e.color=n.substring(1,n.length),e.fontFamily=t,e.fontWeight=o,e.charSpacing=e.charSpacing?e.charSpacing/100:null}return t}async processNode(e,t,i,a,o,n){let s={comment:"Generated by game"};if(s.x=Math.round(e.globalDrawBounds.x-t.globalDrawBounds.x+e.globalDrawBounds.width/2),s.y=Math.round(e.globalDrawBounds.y-t.globalDrawBounds.y+e.globalDrawBounds.height/2),s.width=e.globalDrawBounds.width,s.height=e.globalDrawBounds.height,s["ext-guid"]=n>4?o+": "+e.guid:e.guid,n>4&&(s.name=e.name),!1!==e.visible?s.visible=!0:s.visible=!1,this.websiteMode&&e===t&&(s.visible=!1),e instanceof Rectangle||e instanceof Polygon||e instanceof Ellipse||e instanceof Path||e instanceof SymbolInstance&&(!e.children||0===e.children.length)||e instanceof BooleanGroup||e instanceof Line||e instanceof Group&&this.checkForFlatten(e)){let t=this.sanitizeDisplayName(e.name);s.displayName=t;let i=this.getUniqueIndex(t);s.uniqueIndex=i;let a=this.displayNameToFileName[t],o="image",n=".png";for(this.isPng(e)?(o="image",n=".png"):(o="svg",n=".svg"),this.vectorList.push(e),this.nodeIdToGameDefEntry[e.guid]={},this.nodeIdToGameDefEntry[e.guid].gameDefEntry=s,(a=this.sanitizeFileName(t,e.guid)+(i>0?"_"+i:"")).endsWith(n)||(a+=n);null!==this.fileNames[a.toLowerCase()]&&void 0!==this.fileNames[a.toLowerCase()];){let e=1;a=(a=a.substring(0,a.length-4))+"_"+e+n,e++}this.fileNames[a.toLowerCase()]=a,this.displayNameToFileName[t]=a,this.guidToFileName[e.guid]=a,s.imageFileName=a,s.type=o}else if(e instanceof Text){s.type="text";let t=e.text;t&&(t=t.replace(/\u2028/g,"")),s.text=t;let i=this.sanitizeDisplayName(e.name.substring(0,20)),a=i+".svg",o=await this.getTxtSvg(e,a);s.lines=o.lines,s.ys=o.ys,s.displayName=i;let n=this.getUniqueIndex(i);s.uniqueIndex=n,e.opacity&&(s.opacity=100*e.opacity);let l=e.fontFamily,r=l.split(" "),g=r[r.length-1],d=fontWeightMap[g];if(d){l=r.slice(0,r.length-1).join(" ")}else{let t=e.fontStyle;e.fontStyle&&(d=fontWeightMap[t])||(d=400)}s.font=l,s["font-size"]=e.fontSize.toString()+" px",s.fontWeight=d,s.justification=e.textAlign.toLowerCase(),s.letterSpacing=e.charSpacing?e.charSpacing/100:null,e.lineSpacing>0&&(s.lineHeightPx=e.lineSpacing),s.allowMultiLine=!!e.areaBox,e.areaBox&&(s.width=e.areaBox.width,s.height=e.areaBox.height),s.clip=e.clippedByArea,s.paragraphSpacing=e.paragraphSpacing,s.textTransform=e.textTransform,s.charSpacing=e.charSpacing,s.underline=e.underline,s.strikethrough=e.strikethrough,s.textScript=e.textScript,s.rotation=e.rotation;let h=e.fill.toHex(!0);s.color=h.substring(1,h.length),s.styleRanges=this.getStyleRanges(e)}else if(e instanceof Group||e instanceof Artboard||e instanceof SymbolInstance){e.opacity&&(s.opacity=100*e.opacity);let i=this.sanitizeDisplayName(e.name);if(s.displayName=i,s.uniqueIndex=this.getUniqueIndex(i),this.websiteMode&&e===t){a.find(t=>t.id===e.guid).uniqueIndex=s.uniqueIndex}if(e.children){s.children=[];for(let i=e.children.length-1;i>=0;i--){let l=e.children.at(i);await this.processNode(l,t,s,a,o,n)}}}else if(e instanceof RepeatGrid){e.opacity&&(s.opacity=100*e.opacity);let i=this.sanitizeDisplayName(e.name);if(s.displayName=i,s.uniqueIndex=this.getUniqueIndex(i),s.type="RepeatGrid",s.numColumns=e.numColumns,s.numRows=e.numRows,s.paddingX=e.paddingX,s.paddingY=e.paddingY,this.websiteMode&&e===t){a.find(t=>t.id===e.guid).uniqueIndex=s.uniqueIndex}if(e.children){s.children=[];for(let i=e.children.length-1;i>=0;i--){let l=e.children.at(i);await this.processNode(l,t,s,a,o,n)}}}else console.log("Unknown node type"),console.log(e);null!==s.displayName&&void 0!==s.displayName&&i.children.push(s)}getTxtSvg(e,t){let i=this;return new Promise(async function(t,a){let o=[],n=[];try{let t="__internal_text.svg",a=await i.assetsFolder.createFile(t,{overwrite:!0}),s=[{node:e,outputFile:a,type:application.RenditionType.SVG,scale:1,minify:!0,embedImages:!1}],l=(await application.createRenditions(s),await i.assetsFolder.getEntry(t)),r=(await l.read()).toString().split("/tspan>"),g=new RegExp("<tspan(.+?)>(.+?)<$"),d=new RegExp('y="([0-9]+?)"');for(let e=0;e<r.length;e++){let t=g.exec(r[e]);if(t&&t.length>1){o.push(t[2]);let e=d.exec(t[1]);e&&n.push(e[1])}}await l.delete()}catch(e){console.log("Failed to get details"),console.log(e)}t({lines:o,ys:n})})}async getScreenshot(e,t,i){let a=180/t,o=[{node:e,outputFile:await this.assetsFolder.createFile("screenshot.png",{overwrite:!0}),type:application.RenditionType.PNG,scale:a}];try{await application.createRenditions(o)}catch(e){return console.log(e),console.log("2. Something went wrong. Let the user know.")}}isPng(e){try{if(e instanceof GraphicNode&&(e.fillEnabled&&(e.fill instanceof Color||e.fill instanceof LinearGradient||e.fill instanceof RadialGradientFill)||!e.fillEnabled))return!1}catch(e){}return!0}async createProject(e,t,i,a){let o=this.baseUrl+"/api/creator/createProject";t||(t="My XD Project: "+function(e){let t=function(e){return e<10?"0"+e:e};return e.getUTCFullYear()+"-"+t(e.getUTCMonth()+1)+"-"+t(e.getUTCDate())+"T"+t(e.getUTCHours())+":"+t(e.getUTCMinutes())+"Z"}(new Date));let n=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify({name:t,description:i,template:9,linked_to_design:1,type:e,source:"XD",figmaFileKey:"null",extractVersion:5,isComponent:a?1:0})});const s=await n.json();let l=null,r=null;return"ok"!==s.status?console.log("Create Project failed"):(l=s.id,r=s.serverTime,console.log("Created project: "+l)),{projectId:l,serverTime:r}}async getServerTime(){let e=this.baseUrl+"/api/creator/getServerTime",t=await fetch(e,{method:"GET",credentials:"include"});const i=await t.json();let a=null;return"ok"!==i.status?console.log("Get server time failed"):a=i.serverTime,{serverTime:a}}async uploadGameDef(e,t){let i=this.baseUrl+"/api/ide/uploadDef/"+e,a=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:t});"ok"!==(await a.json()).status&&console.log("Upload def failed. "+new Date)}writeStats(e,t,i){this.stateObj.writeStats(e,t,i)}async uploadImage(e,t,i,a){let o=this.baseUrl+"/api/ide/uploadImage/"+e+"/"+t;a&&(o+="?isScreenshot=true");let n="image/png";t.endsWith(".jpg")?n="image/jpeg":t.endsWith(".tar")&&(n="application/tar");let s=await fetch(o,{method:"POST",headers:{"Content-Type":n},credentials:"include",body:i});"ok"!==(await s.json()).status&&console.log("Upload image failed")}async exportRenditions(e){let t,i=[],a=[],o={};console.log("Using 2x scale for export ? "),console.log(e);let n=async function(e){try{if(console.log("Running renditions for :"+e.length+" : "+new Date),e.length>0){await application.createRenditions(e)}}catch(e){console.log("1. Something went wrong. Let the user know."),console.log(e)}},s=[];for(let l of this.vectorList){if((o={}).node=l,!0!==l.visible){try{l.visible=!0}catch(e){}s.push(l)}t=this.guidToFileName[l.guid];let r=await this.imagesFolder.createFile(t,{overwrite:!0});o.outputFile=r;let g=!1,d=1;if(this.isPng(l)?(o.type=application.RenditionType.PNG,g=!0):o.type=application.RenditionType.SVG,this.websiteMode&&g&&e){let i=e.v,a=e.s,o=Math.max(l.globalDrawBounds.width,l.globalDrawBounds.height);for(let e=0;e<i.length;e++)if(o<i[e]){d=a[e],console.log("Using scale "+d+" for file ="+t);break}}o.type===application.RenditionType.PNG?(o.scale=d,i.push(o)):(o.minify=!0,o.embedImages=!0,a.push(o)),a.length>50&&(await n(a),a=[]),i.length>50&&(await n(i),i=[])}await n(i),await n(a);for(let e=0;e<s.length;e++)try{s[e].visible=!1}catch(e){}a=[],i=[]}importXD(e,t,i,a,o,n,s){this.writeStats("export","xd_plugin","export_started");let l=this;return new Promise(async function(r,g){try{r(await l.__importXD(e,t,i,a,o,n,s))}catch(e){g(e)}})}__cleanup(e){let t=Date.now();return new Promise(async function(i,a){console.log("Cleaning up the old directories.");for(let i of e)if("assets"===i.name);else{(await i.getMetadata()).dateModified.getTime()<t-864e5&&(console.log("Deleting old folder :"+i.name),await ExportDoc.deleteFolder(i))}console.log("Finished cleaning up the old directories.")})}async __importXD(e,t,i,a,o,n,s){console.log("Begin : "+new Date);let l=document.getElementById("processingCount");l.style.display="block",l.innerHTML="Parsing the design document.",this.gameDef={},this.gameDef.docType="gamecommerce_game_definition",this.gameDef.version=4,this.gameDef.Copyright="GameCommerce Inc (2019)",this.gameDef.screens=[];let r=[],g=t;this.isComponent="component"===e;let d="component"===e?"website":e;this.websiteMode="website"===d;let h={};this.xdRoot.children.forEach(e=>{a.find(t=>t.node.guid===e.guid)&&(h[e.guid]=!0)});const c=await fs.getTemporaryFolder();if(!c)return console.log("Could not get temp folder.");let p,m,f,u,w=await c.getEntries(),y=(new Date).getTime();for(let e of w)"assets"===e.name&&await e.moveTo(c,{newName:"assets"+y,overwrite:"true"});console.log("Start of images: "+new Date),this.assetsFolder=await c.createFolder("assets"),this.imagesFolder=await this.assetsFolder.createFolder("images"),this.images2Folder=await this.assetsFolder.createFolder("images2");let x,b,D=0,F=0;if("-1"===g){let e=await this.createProject(d,i,this.xdRoot.guid,this.isComponent);console.log("Server time : "+e.serverTime),g=e.projectId,b=e.serverTime?parseInt(e.serverTime/1e3):parseInt(Date.now()/1e3)}else{let e=await this.getServerTime();b=e.serverTime?parseInt(e.serverTime/1e3):parseInt(Date.now()/1e3),console.log("2. Server time : "+e.serverTime)}if(this.websiteMode){u="Artboards",p={comment:"Generated by game",uniqueIndex:this.getUniqueIndex(u),visible:!0,displayName:u,children:[]},this.gameDef.screens.push(p)}if(this.websiteMode)for(let e of a){let t=this.sanitizeDisplayName(e.name);this.artBoardNames[t]=!0}for(let e=0;e<this.xdRoot.children.length;e++){let t=this.xdRoot.children.at(e),i=null;if(t instanceof Artboard&&h[t.guid]){u=this.sanitizeDisplayName(t.name),m=t.width,f=t.height;try{if(t.fill instanceof Color){let e=t.fill.toHex(!0);x=e.substring(1,e.length)}else if(t.fill&&t.fill.colorStops){x=t.pathData;try{i={colorStops:t.fill.colorStops,startX:t.fill.startX,startY:t.fill.startY,endX:t.fill.endX,endY:t.fill.endY,endPoints:t.fill.getEndPoints()}}catch(e){console.log(e),console.log("Cannot get bg color using rendition")}}else console.log("2. no bg color ? for :"+t.name),console.log("2. Type of fill :"+typeof t.fill)}catch(e){console.log(e),console.log("no bg color ? for :"+t.name),console.log("Type of fill :"+typeof t.fill)}if(!this.websiteMode){p={comment:"Generated by game",uniqueIndex:this.getUniqueIndex(u),visible:!0,displayName:u,children:[],bgColor:x,bgColorGradient:i,"ext-guid":o>4?g+":"+t.guid:t.guid}}if(D=m>D?m:D,F=f>F?f:F,r.push({node:t,id:t.guid,name:t.name,width:m,height:f,bgColor:x}),this.websiteMode)await this.processNode(t,t,p,r,g,o);else{if(t.children)for(let e=t.children.length-1;e>=0;e--){let i=t.children.at(e);await this.processNode(i,t,p,r,g,o)}this.gameDef.screens.push(p)}}}let T=[],S=function(e){for(let t=0;t<r.length;t++)if(r[t].id===e)return r[t];return null};if(this.websiteMode){let e,t=null,i=0;for(let o of a)(e=S(o.node.guid))&&(0===i&&(t=e),i++,T.push({name:e.name,minWidth:o.minWidth,maxWidth:o.maxWidth,width:e.width,height:e.height,bgColor:e.bgColor}));x=t?t.bgColor:"#FFFFFF",await this.getScreenshot(t.node,t.width,t.height),this.gameDef.width=D,this.gameDef.height=F,this.gameDef.game_type=d,this.gameDef.game_source="XD",this.gameDef.artBoards=T,this.gameDef.bgColor=x,this.gameDef.defaultLanguage=application.appLanguage,this.gameDef.defaultLocale=application.systemLocale}else x=r[0]?r[0].bgColor:"#FFFFFF",await this.getScreenshot(r[0].node,r[0].width,r[0].height),this.gameDef.width=r[0].width,this.gameDef.height=r[0].height,this.gameDef.game_type=d,this.gameDef.game_source="XD",this.gameDef.bgColor=x,this.gameDef.defaultLanguage=application.appLanguage,this.gameDef.defaultLocale=application.systemLocale;if(await this.exportRenditions(s),n(),console.log("Finished with the lock :"+new Date),g){let e,t=await this.assetsFolder.getEntries(),i=await this.imagesFolder.getEntries(),a=(await this.images2Folder.getEntries(),t.length+i.length),o=1,n=[];for(let i of t)l.innerHTML="Processing file "+o+" of "+a,"screenshot.png"===i.name?(e=await i.read({format:formats.binary}),await this.uploadImage(g,i.name,e,!0)):o++;let s,r=new Tar,d=function(e,t){return new Promise(function(i,a){s=r.append(e,new Uint8Array(t),{mtime:b},function(){i()})})},h=0;for(let e of i){l.innerHTML="Processing file "+o+" of "+a;const t=await e.read({format:formats.binary});t.byteLength>15e6?(console.log("File is too large, skipping. Name: "+e.name),this.writeStats("export_error","xd_plugin","file_too_large:"+t.byteLength)):(await d(e.name,t),n.push(e.name)),o++,h++}if(0===h){const e=await fs.getPluginFolder(),t=await e.getEntry("images/1px.png"),i=await t.read({format:formats.binary});await d("1px.png",i),n.push("1px.png"),h=1}if(h>0){let e=await this.imagesFolder.createFile("all_images.tar",{overwrite:!0});await e.write(s,{fromat:formats.binary}),l.innerHTML="Uploading image data.",await this.uploadImage(g,"all_images.tar",s.buffer,!1)}console.log("Finished uploading images:"+new Date),this.gameDef.sprite_images=n;let c=JSON.stringify(this.gameDef);this.__cleanup(w),await this.uploadGameDef(g,c),l.style.display="none";let p=this.baseUrl+"/platform/editor/"+g;return console.log("Project URL is: "+p),console.log("Done!"),l.innerHTML="Export complete.",this.writeStats("export","xd_plugin","export_finished"),{url:p,id:g}}}}module.exports={ExportDoc:ExportDoc};