message this user

Name

3dspread3

Global

#import getscript
#import parallel
#import bs58
#import subchannel

function templatelist() {
 return ['loan'];
}

function template_loan() {
 return `["0:0:0:Start Date","0:1:0:Principle","0:2:0:Term (in years)","0:3:0:Rate (in percent)","0:4:0:Periods per year","0:6:0:P Payment","0:7:0:D Rate","3:0:0:Due Date","3:1:0:Remaining","3:2:0:Periods left","3:3:0:Principle","3:4:0:Interest","3:5:0:Total","3:6:0:Real Paid","3:7:0:Notes","1:1:0:10000","1:0:0:=new Date('10/26/2018')","1:2:0:4","1:3:0:3.5","1:4:0:24","1:6:0:=c1:1:0/c1:2:0/c1:4:0","1:7:0:=(Math.pow(c1:3:0/100+1,1/c1:4:0)-1)*100","4:0:0:=c1:0:0","4:1:0:=c1:1:0","4:2:0:=c1:2:0*c1:4:0","4:3:0:=c4:1:0/c4:2:0","4:4:0:=c4:1:0*c$1:7:0/100","4:5:0:=Math.round(c4:3:0+c4:4:0)","5:0:0:=new Date(c4:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","5:1:0:=c4:1:0+c4:4:0-(c4:6:0===null?c4:5:0:c4:6:0)","5:2:0:=c4:2:0-1","5:3:0:=c5:1:0/c5:2:0","5:4:0:=c5:1:0*c$1:7:0/100","5:5:0:=Math.round(c5:3:0+c5:4:0)","6:0:0:=new Date(c5:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","6:1:0:=c5:1:0+c5:4:0-(c5:6:0===null?c5:5:0:c5:6:0)","6:2:0:=c5:2:0-1","6:3:0:=c6:1:0/c6:2:0","6:4:0:=c6:1:0*c$1:7:0/100","6:5:0:=Math.round(c6:3:0+c6:4:0)","7:0:0:=new Date(c6:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","7:1:0:=c6:1:0+c6:4:0-(c6:6:0===null?c6:5:0:c6:6:0)","7:2:0:=c6:2:0-1","7:3:0:=c7:1:0/c7:2:0","7:4:0:=c7:1:0*c$1:7:0/100","7:5:0:=Math.round(c7:3:0+c7:4:0)","8:0:0:=new Date(c7:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","8:1:0:=c7:1:0+c7:4:0-(c7:6:0===null?c7:5:0:c7:6:0)","8:2:0:=c7:2:0-1","8:3:0:=c8:1:0/c8:2:0","8:4:0:=c8:1:0*c$1:7:0/100","8:5:0:=Math.round(c8:3:0+c8:4:0)","9:0:0:=new Date(c8:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","9:1:0:=c8:1:0+c8:4:0-(c8:6:0===null?c8:5:0:c8:6:0)","9:2:0:=c8:2:0-1","9:3:0:=c9:1:0/c9:2:0","9:4:0:=c9:1:0*c$1:7:0/100","9:5:0:=Math.round(c9:3:0+c9:4:0)","10:0:0:=new Date(c9:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","10:1:0:=c9:1:0+c9:4:0-(c9:6:0===null?c9:5:0:c9:6:0)","10:2:0:=c9:2:0-1","10:3:0:=c10:1:0/c10:2:0","10:4:0:=c10:1:0*c$1:7:0/100","10:5:0:=Math.round(c10:3:0+c10:4:0)","11:0:0:=new Date(c10:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","11:1:0:=c10:1:0+c10:4:0-(c10:6:0===null?c10:5:0:c10:6:0)","11:2:0:=c10:2:0-1","11:3:0:=c11:1:0/c11:2:0","11:4:0:=c11:1:0*c$1:7:0/100","11:5:0:=Math.round(c11:3:0+c11:4:0)","12:0:0:=new Date(c11:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","12:1:0:=c11:1:0+c11:4:0-(c11:6:0===null?c11:5:0:c11:6:0)","12:2:0:=c11:2:0-1","12:3:0:=c12:1:0/c12:2:0","12:4:0:=c12:1:0*c$1:7:0/100","12:5:0:=Math.round(c12:3:0+c12:4:0)","13:0:0:=new Date(c12:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","13:1:0:=c12:1:0+c12:4:0-(c12:6:0===null?c12:5:0:c12:6:0)","13:2:0:=c12:2:0-1","13:3:0:=c13:1:0/c13:2:0","13:4:0:=c13:1:0*c$1:7:0/100","13:5:0:=Math.round(c13:3:0+c13:4:0)","14:0:0:=new Date(c13:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","14:1:0:=c13:1:0+c13:4:0-(c13:6:0===null?c13:5:0:c13:6:0)","14:2:0:=c13:2:0-1","14:3:0:=c14:1:0/c14:2:0","14:4:0:=c14:1:0*c$1:7:0/100","14:5:0:=Math.round(c14:3:0+c14:4:0)","15:0:0:=new Date(c14:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","15:1:0:=c14:1:0+c14:4:0-(c14:6:0===null?c14:5:0:c14:6:0)","15:2:0:=c14:2:0-1","15:3:0:=c15:1:0/c15:2:0","15:4:0:=c15:1:0*c$1:7:0/100","15:5:0:=Math.round(c15:3:0+c15:4:0)","16:0:0:=new Date(c15:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","16:1:0:=c15:1:0+c15:4:0-(c15:6:0===null?c15:5:0:c15:6:0)","16:2:0:=c15:2:0-1","16:3:0:=c16:1:0/c16:2:0","16:4:0:=c16:1:0*c$1:7:0/100","16:5:0:=Math.round(c16:3:0+c16:4:0)","17:0:0:=new Date(c16:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","17:1:0:=c16:1:0+c16:4:0-(c16:6:0===null?c16:5:0:c16:6:0)","17:2:0:=c16:2:0-1","17:3:0:=c17:1:0/c17:2:0","17:4:0:=c17:1:0*c$1:7:0/100","17:5:0:=Math.round(c17:3:0+c17:4:0)","18:0:0:=new Date(c17:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","18:1:0:=c17:1:0+c17:4:0-(c17:6:0===null?c17:5:0:c17:6:0)","18:2:0:=c17:2:0-1","18:3:0:=c18:1:0/c18:2:0","18:4:0:=c18:1:0*c$1:7:0/100","18:5:0:=Math.round(c18:3:0+c18:4:0)","19:0:0:=new Date(c18:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","19:1:0:=c18:1:0+c18:4:0-(c18:6:0===null?c18:5:0:c18:6:0)","19:2:0:=c18:2:0-1","19:3:0:=c19:1:0/c19:2:0","19:4:0:=c19:1:0*c$1:7:0/100","19:5:0:=Math.round(c19:3:0+c19:4:0)","20:0:0:=new Date(c19:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","20:1:0:=c19:1:0+c19:4:0-(c19:6:0===null?c19:5:0:c19:6:0)","20:2:0:=c19:2:0-1","20:3:0:=c20:1:0/c20:2:0","20:4:0:=c20:1:0*c$1:7:0/100","20:5:0:=Math.round(c20:3:0+c20:4:0)","21:0:0:=new Date(c20:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","21:1:0:=c20:1:0+c20:4:0-(c20:6:0===null?c20:5:0:c20:6:0)","21:2:0:=c20:2:0-1","21:3:0:=c21:1:0/c21:2:0","21:4:0:=c21:1:0*c$1:7:0/100","21:5:0:=Math.round(c21:3:0+c21:4:0)","22:0:0:=new Date(c21:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","22:1:0:=c21:1:0+c21:4:0-(c21:6:0===null?c21:5:0:c21:6:0)","22:2:0:=c21:2:0-1","22:3:0:=c22:1:0/c22:2:0","22:4:0:=c22:1:0*c$1:7:0/100","22:5:0:=Math.round(c22:3:0+c22:4:0)","23:0:0:=new Date(c22:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","23:1:0:=c22:1:0+c22:4:0-(c22:6:0===null?c22:5:0:c22:6:0)","23:2:0:=c22:2:0-1","23:3:0:=c23:1:0/c23:2:0","23:4:0:=c23:1:0*c$1:7:0/100","23:5:0:=Math.round(c23:3:0+c23:4:0)","24:0:0:=new Date(c23:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","24:1:0:=c23:1:0+c23:4:0-(c23:6:0===null?c23:5:0:c23:6:0)","24:2:0:=c23:2:0-1","24:3:0:=c24:1:0/c24:2:0","24:4:0:=c24:1:0*c$1:7:0/100","24:5:0:=Math.round(c24:3:0+c24:4:0)","25:0:0:=new Date(c24:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","25:1:0:=c24:1:0+c24:4:0-(c24:6:0===null?c24:5:0:c24:6:0)","25:2:0:=c24:2:0-1","25:3:0:=c25:1:0/c25:2:0","25:4:0:=c25:1:0*c$1:7:0/100","25:5:0:=Math.round(c25:3:0+c25:4:0)","26:0:0:=new Date(c25:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","26:1:0:=c25:1:0+c25:4:0-(c25:6:0===null?c25:5:0:c25:6:0)","26:2:0:=c25:2:0-1","26:3:0:=c26:1:0/c26:2:0","26:4:0:=c26:1:0*c$1:7:0/100","26:5:0:=Math.round(c26:3:0+c26:4:0)","27:0:0:=new Date(c26:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","27:1:0:=c26:1:0+c26:4:0-(c26:6:0===null?c26:5:0:c26:6:0)","27:2:0:=c26:2:0-1","27:3:0:=c27:1:0/c27:2:0","27:4:0:=c27:1:0*c$1:7:0/100","27:5:0:=Math.round(c27:3:0+c27:4:0)","28:0:0:=new Date(c27:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","28:1:0:=c27:1:0+c27:4:0-(c27:6:0===null?c27:5:0:c27:6:0)","28:2:0:=c27:2:0-1","28:3:0:=c28:1:0/c28:2:0","28:4:0:=c28:1:0*c$1:7:0/100","28:5:0:=Math.round(c28:3:0+c28:4:0)","29:0:0:=new Date(c28:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","29:1:0:=c28:1:0+c28:4:0-(c28:6:0===null?c28:5:0:c28:6:0)","29:2:0:=c28:2:0-1","29:3:0:=c29:1:0/c29:2:0","29:4:0:=c29:1:0*c$1:7:0/100","29:5:0:=Math.round(c29:3:0+c29:4:0)","30:0:0:=new Date(c29:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","30:1:0:=c29:1:0+c29:4:0-(c29:6:0===null?c29:5:0:c29:6:0)","30:2:0:=c29:2:0-1","30:3:0:=c30:1:0/c30:2:0","30:4:0:=c30:1:0*c$1:7:0/100","30:5:0:=Math.round(c30:3:0+c30:4:0)","31:0:0:=new Date(c30:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","31:1:0:=c30:1:0+c30:4:0-(c30:6:0===null?c30:5:0:c30:6:0)","31:2:0:=c30:2:0-1","31:3:0:=c31:1:0/c31:2:0","31:4:0:=c31:1:0*c$1:7:0/100","31:5:0:=Math.round(c31:3:0+c31:4:0)","32:0:0:=new Date(c31:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","32:1:0:=c31:1:0+c31:4:0-(c31:6:0===null?c31:5:0:c31:6:0)","32:2:0:=c31:2:0-1","32:3:0:=c32:1:0/c32:2:0","32:4:0:=c32:1:0*c$1:7:0/100","32:5:0:=Math.round(c32:3:0+c32:4:0)","33:0:0:=new Date(c32:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","33:1:0:=c32:1:0+c32:4:0-(c32:6:0===null?c32:5:0:c32:6:0)","33:2:0:=c32:2:0-1","33:3:0:=c33:1:0/c33:2:0","33:4:0:=c33:1:0*c$1:7:0/100","33:5:0:=Math.round(c33:3:0+c33:4:0)","34:0:0:=new Date(c33:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","34:1:0:=c33:1:0+c33:4:0-(c33:6:0===null?c33:5:0:c33:6:0)","34:2:0:=c33:2:0-1","34:3:0:=c34:1:0/c34:2:0","34:4:0:=c34:1:0*c$1:7:0/100","34:5:0:=Math.round(c34:3:0+c34:4:0)","35:0:0:=new Date(c34:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","35:1:0:=c34:1:0+c34:4:0-(c34:6:0===null?c34:5:0:c34:6:0)","35:2:0:=c34:2:0-1","35:3:0:=c35:1:0/c35:2:0","35:4:0:=c35:1:0*c$1:7:0/100","35:5:0:=Math.round(c35:3:0+c35:4:0)","36:0:0:=new Date(c35:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","36:1:0:=c35:1:0+c35:4:0-(c35:6:0===null?c35:5:0:c35:6:0)","36:2:0:=c35:2:0-1","36:3:0:=c36:1:0/c36:2:0","36:4:0:=c36:1:0*c$1:7:0/100","36:5:0:=Math.round(c36:3:0+c36:4:0)","37:0:0:=new Date(c36:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","37:1:0:=c36:1:0+c36:4:0-(c36:6:0===null?c36:5:0:c36:6:0)","37:2:0:=c36:2:0-1","37:3:0:=c37:1:0/c37:2:0","37:4:0:=c37:1:0*c$1:7:0/100","37:5:0:=Math.round(c37:3:0+c37:4:0)","38:0:0:=new Date(c37:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","38:1:0:=c37:1:0+c37:4:0-(c37:6:0===null?c37:5:0:c37:6:0)","38:2:0:=c37:2:0-1","38:3:0:=c38:1:0/c38:2:0","38:4:0:=c38:1:0*c$1:7:0/100","38:5:0:=Math.round(c38:3:0+c38:4:0)","39:0:0:=new Date(c38:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","39:1:0:=c38:1:0+c38:4:0-(c38:6:0===null?c38:5:0:c38:6:0)","39:2:0:=c38:2:0-1","39:3:0:=c39:1:0/c39:2:0","39:4:0:=c39:1:0*c$1:7:0/100","39:5:0:=Math.round(c39:3:0+c39:4:0)","40:0:0:=new Date(c39:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","40:1:0:=c39:1:0+c39:4:0-(c39:6:0===null?c39:5:0:c39:6:0)","40:2:0:=c39:2:0-1","40:3:0:=c40:1:0/c40:2:0","40:4:0:=c40:1:0*c$1:7:0/100","40:5:0:=Math.round(c40:3:0+c40:4:0)","41:0:0:=new Date(c40:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","41:1:0:=c40:1:0+c40:4:0-(c40:6:0===null?c40:5:0:c40:6:0)","41:2:0:=c40:2:0-1","41:3:0:=c41:1:0/c41:2:0","41:4:0:=c41:1:0*c$1:7:0/100","41:5:0:=Math.round(c41:3:0+c41:4:0)","42:0:0:=new Date(c41:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","42:1:0:=c41:1:0+c41:4:0-(c41:6:0===null?c41:5:0:c41:6:0)","42:2:0:=c41:2:0-1","42:3:0:=c42:1:0/c42:2:0","42:4:0:=c42:1:0*c$1:7:0/100","42:5:0:=Math.round(c42:3:0+c42:4:0)","43:0:0:=new Date(c42:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","43:1:0:=c42:1:0+c42:4:0-(c42:6:0===null?c42:5:0:c42:6:0)","43:2:0:=c42:2:0-1","43:3:0:=c43:1:0/c43:2:0","43:4:0:=c43:1:0*c$1:7:0/100","43:5:0:=Math.round(c43:3:0+c43:4:0)","44:0:0:=new Date(c43:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","44:1:0:=c43:1:0+c43:4:0-(c43:6:0===null?c43:5:0:c43:6:0)","44:2:0:=c43:2:0-1","44:3:0:=c44:1:0/c44:2:0","44:4:0:=c44:1:0*c$1:7:0/100","44:5:0:=Math.round(c44:3:0+c44:4:0)","45:0:0:=new Date(c44:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","45:1:0:=c44:1:0+c44:4:0-(c44:6:0===null?c44:5:0:c44:6:0)","45:2:0:=c44:2:0-1","45:3:0:=c45:1:0/c45:2:0","45:4:0:=c45:1:0*c$1:7:0/100","45:5:0:=Math.round(c45:3:0+c45:4:0)","46:0:0:=new Date(c45:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","46:1:0:=c45:1:0+c45:4:0-(c45:6:0===null?c45:5:0:c45:6:0)","46:2:0:=c45:2:0-1","46:3:0:=c46:1:0/c46:2:0","46:4:0:=c46:1:0*c$1:7:0/100","46:5:0:=Math.round(c46:3:0+c46:4:0)","47:0:0:=new Date(c46:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","47:1:0:=c46:1:0+c46:4:0-(c46:6:0===null?c46:5:0:c46:6:0)","47:2:0:=c46:2:0-1","47:3:0:=c47:1:0/c47:2:0","47:4:0:=c47:1:0*c$1:7:0/100","47:5:0:=Math.round(c47:3:0+c47:4:0)","48:0:0:=new Date(c47:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","48:1:0:=c47:1:0+c47:4:0-(c47:6:0===null?c47:5:0:c47:6:0)","48:2:0:=c47:2:0-1","48:3:0:=c48:1:0/c48:2:0","48:4:0:=c48:1:0*c$1:7:0/100","48:5:0:=Math.round(c48:3:0+c48:4:0)","49:0:0:=new Date(c48:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","49:1:0:=c48:1:0+c48:4:0-(c48:6:0===null?c48:5:0:c48:6:0)","49:2:0:=c48:2:0-1","49:3:0:=c49:1:0/c49:2:0","49:4:0:=c49:1:0*c$1:7:0/100","49:5:0:=Math.round(c49:3:0+c49:4:0)","50:0:0:=new Date(c49:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","50:1:0:=c49:1:0+c49:4:0-(c49:6:0===null?c49:5:0:c49:6:0)","50:2:0:=c49:2:0-1","50:3:0:=c50:1:0/c50:2:0","50:4:0:=c50:1:0*c$1:7:0/100","50:5:0:=Math.round(c50:3:0+c50:4:0)","51:0:0:=new Date(c50:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","51:1:0:=c50:1:0+c50:4:0-(c50:6:0===null?c50:5:0:c50:6:0)","51:2:0:=c50:2:0-1","51:3:0:=c51:1:0/c51:2:0","51:4:0:=c51:1:0*c$1:7:0/100","51:5:0:=Math.round(c51:3:0+c51:4:0)","52:0:0:=new Date(c51:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","52:1:0:=c51:1:0+c51:4:0-(c51:6:0===null?c51:5:0:c51:6:0)","52:2:0:=c51:2:0-1","52:3:0:=c52:1:0/c52:2:0","52:4:0:=c52:1:0*c$1:7:0/100","52:5:0:=Math.round(c52:3:0+c52:4:0)","53:0:0:=new Date(c52:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","53:1:0:=c52:1:0+c52:4:0-(c52:6:0===null?c52:5:0:c52:6:0)","53:2:0:=c52:2:0-1","53:3:0:=c53:1:0/c53:2:0","53:4:0:=c53:1:0*c$1:7:0/100","53:5:0:=Math.round(c53:3:0+c53:4:0)","54:0:0:=new Date(c53:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","54:1:0:=c53:1:0+c53:4:0-(c53:6:0===null?c53:5:0:c53:6:0)","54:2:0:=c53:2:0-1","54:3:0:=c54:1:0/c54:2:0","54:4:0:=c54:1:0*c$1:7:0/100","54:5:0:=Math.round(c54:3:0+c54:4:0)","55:0:0:=new Date(c54:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","55:1:0:=c54:1:0+c54:4:0-(c54:6:0===null?c54:5:0:c54:6:0)","55:2:0:=c54:2:0-1","55:3:0:=c55:1:0/c55:2:0","55:4:0:=c55:1:0*c$1:7:0/100","55:5:0:=Math.round(c55:3:0+c55:4:0)","56:0:0:=new Date(c55:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","56:1:0:=c55:1:0+c55:4:0-(c55:6:0===null?c55:5:0:c55:6:0)","56:2:0:=c55:2:0-1","56:3:0:=c56:1:0/c56:2:0","56:4:0:=c56:1:0*c$1:7:0/100","56:5:0:=Math.round(c56:3:0+c56:4:0)","57:0:0:=new Date(c56:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","57:1:0:=c56:1:0+c56:4:0-(c56:6:0===null?c56:5:0:c56:6:0)","57:2:0:=c56:2:0-1","57:3:0:=c57:1:0/c57:2:0","57:4:0:=c57:1:0*c$1:7:0/100","57:5:0:=Math.round(c57:3:0+c57:4:0)","58:0:0:=new Date(c57:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","58:1:0:=c57:1:0+c57:4:0-(c57:6:0===null?c57:5:0:c57:6:0)","58:2:0:=c57:2:0-1","58:3:0:=c58:1:0/c58:2:0","58:4:0:=c58:1:0*c$1:7:0/100","58:5:0:=Math.round(c58:3:0+c58:4:0)","59:0:0:=new Date(c58:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","59:1:0:=c58:1:0+c58:4:0-(c58:6:0===null?c58:5:0:c58:6:0)","59:2:0:=c58:2:0-1","59:3:0:=c59:1:0/c59:2:0","59:4:0:=c59:1:0*c$1:7:0/100","59:5:0:=Math.round(c59:3:0+c59:4:0)","60:0:0:=new Date(c59:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","60:1:0:=c59:1:0+c59:4:0-(c59:6:0===null?c59:5:0:c59:6:0)","60:2:0:=c59:2:0-1","60:3:0:=c60:1:0/c60:2:0","60:4:0:=c60:1:0*c$1:7:0/100","60:5:0:=Math.round(c60:3:0+c60:4:0)","61:0:0:=new Date(c60:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","61:1:0:=c60:1:0+c60:4:0-(c60:6:0===null?c60:5:0:c60:6:0)","61:2:0:=c60:2:0-1","61:3:0:=c61:1:0/c61:2:0","61:4:0:=c61:1:0*c$1:7:0/100","61:5:0:=Math.round(c61:3:0+c61:4:0)","62:0:0:=new Date(c61:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","62:1:0:=c61:1:0+c61:4:0-(c61:6:0===null?c61:5:0:c61:6:0)","62:2:0:=c61:2:0-1","62:3:0:=c62:1:0/c62:2:0","62:4:0:=c62:1:0*c$1:7:0/100","62:5:0:=Math.round(c62:3:0+c62:4:0)","63:0:0:=new Date(c62:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","63:1:0:=c62:1:0+c62:4:0-(c62:6:0===null?c62:5:0:c62:6:0)","63:2:0:=c62:2:0-1","63:3:0:=c63:1:0/c63:2:0","63:4:0:=c63:1:0*c$1:7:0/100","63:5:0:=Math.round(c63:3:0+c63:4:0)","64:0:0:=new Date(c63:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","64:1:0:=c63:1:0+c63:4:0-(c63:6:0===null?c63:5:0:c63:6:0)","64:2:0:=c63:2:0-1","64:3:0:=c64:1:0/c64:2:0","64:4:0:=c64:1:0*c$1:7:0/100","64:5:0:=Math.round(c64:3:0+c64:4:0)","65:0:0:=new Date(c64:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","65:1:0:=c64:1:0+c64:4:0-(c64:6:0===null?c64:5:0:c64:6:0)","65:2:0:=c64:2:0-1","65:3:0:=c65:1:0/c65:2:0","65:4:0:=c65:1:0*c$1:7:0/100","65:5:0:=Math.round(c65:3:0+c65:4:0)","66:0:0:=new Date(c65:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","66:1:0:=c65:1:0+c65:4:0-(c65:6:0===null?c65:5:0:c65:6:0)","66:2:0:=c65:2:0-1","66:3:0:=c66:1:0/c66:2:0","66:4:0:=c66:1:0*c$1:7:0/100","66:5:0:=Math.round(c66:3:0+c66:4:0)","67:0:0:=new Date(c66:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","67:1:0:=c66:1:0+c66:4:0-(c66:6:0===null?c66:5:0:c66:6:0)","67:2:0:=c66:2:0-1","67:3:0:=c67:1:0/c67:2:0","67:4:0:=c67:1:0*c$1:7:0/100","67:5:0:=Math.round(c67:3:0+c67:4:0)","68:0:0:=new Date(c67:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","68:1:0:=c67:1:0+c67:4:0-(c67:6:0===null?c67:5:0:c67:6:0)","68:2:0:=c67:2:0-1","68:3:0:=c68:1:0/c68:2:0","68:4:0:=c68:1:0*c$1:7:0/100","68:5:0:=Math.round(c68:3:0+c68:4:0)","69:0:0:=new Date(c68:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","69:1:0:=c68:1:0+c68:4:0-(c68:6:0===null?c68:5:0:c68:6:0)","69:2:0:=c68:2:0-1","69:3:0:=c69:1:0/c69:2:0","69:4:0:=c69:1:0*c$1:7:0/100","69:5:0:=Math.round(c69:3:0+c69:4:0)","70:0:0:=new Date(c69:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","70:1:0:=c69:1:0+c69:4:0-(c69:6:0===null?c69:5:0:c69:6:0)","70:2:0:=c69:2:0-1","70:3:0:=c70:1:0/c70:2:0","70:4:0:=c70:1:0*c$1:7:0/100","70:5:0:=Math.round(c70:3:0+c70:4:0)","71:0:0:=new Date(c70:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","71:1:0:=c70:1:0+c70:4:0-(c70:6:0===null?c70:5:0:c70:6:0)","71:2:0:=c70:2:0-1","71:3:0:=c71:1:0/c71:2:0","71:4:0:=c71:1:0*c$1:7:0/100","71:5:0:=Math.round(c71:3:0+c71:4:0)","72:0:0:=new Date(c71:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","72:1:0:=c71:1:0+c71:4:0-(c71:6:0===null?c71:5:0:c71:6:0)","72:2:0:=c71:2:0-1","72:3:0:=c72:1:0/c72:2:0","72:4:0:=c72:1:0*c$1:7:0/100","72:5:0:=Math.round(c72:3:0+c72:4:0)","73:0:0:=new Date(c72:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","73:1:0:=c72:1:0+c72:4:0-(c72:6:0===null?c72:5:0:c72:6:0)","73:2:0:=c72:2:0-1","73:3:0:=c73:1:0/c73:2:0","73:4:0:=c73:1:0*c$1:7:0/100","73:5:0:=Math.round(c73:3:0+c73:4:0)","74:0:0:=new Date(c73:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","74:1:0:=c73:1:0+c73:4:0-(c73:6:0===null?c73:5:0:c73:6:0)","74:2:0:=c73:2:0-1","74:3:0:=c74:1:0/c74:2:0","74:4:0:=c74:1:0*c$1:7:0/100","74:5:0:=Math.round(c74:3:0+c74:4:0)","75:0:0:=new Date(c74:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","75:1:0:=c74:1:0+c74:4:0-(c74:6:0===null?c74:5:0:c74:6:0)","75:2:0:=c74:2:0-1","75:3:0:=c75:1:0/c75:2:0","75:4:0:=c75:1:0*c$1:7:0/100","75:5:0:=Math.round(c75:3:0+c75:4:0)","76:0:0:=new Date(c75:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","76:1:0:=c75:1:0+c75:4:0-(c75:6:0===null?c75:5:0:c75:6:0)","76:2:0:=c75:2:0-1","76:3:0:=c76:1:0/c76:2:0","76:4:0:=c76:1:0*c$1:7:0/100","76:5:0:=Math.round(c76:3:0+c76:4:0)","77:0:0:=new Date(c76:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","77:1:0:=c76:1:0+c76:4:0-(c76:6:0===null?c76:5:0:c76:6:0)","77:2:0:=c76:2:0-1","77:3:0:=c77:1:0/c77:2:0","77:4:0:=c77:1:0*c$1:7:0/100","77:5:0:=Math.round(c77:3:0+c77:4:0)","78:0:0:=new Date(c77:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","78:1:0:=c77:1:0+c77:4:0-(c77:6:0===null?c77:5:0:c77:6:0)","78:2:0:=c77:2:0-1","78:3:0:=c78:1:0/c78:2:0","78:4:0:=c78:1:0*c$1:7:0/100","78:5:0:=Math.round(c78:3:0+c78:4:0)","79:0:0:=new Date(c78:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","79:1:0:=c78:1:0+c78:4:0-(c78:6:0===null?c78:5:0:c78:6:0)","79:2:0:=c78:2:0-1","79:3:0:=c79:1:0/c79:2:0","79:4:0:=c79:1:0*c$1:7:0/100","79:5:0:=Math.round(c79:3:0+c79:4:0)","80:0:0:=new Date(c79:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","80:1:0:=c79:1:0+c79:4:0-(c79:6:0===null?c79:5:0:c79:6:0)","80:2:0:=c79:2:0-1","80:3:0:=c80:1:0/c80:2:0","80:4:0:=c80:1:0*c$1:7:0/100","80:5:0:=Math.round(c80:3:0+c80:4:0)","81:0:0:=new Date(c80:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","81:1:0:=c80:1:0+c80:4:0-(c80:6:0===null?c80:5:0:c80:6:0)","81:2:0:=c80:2:0-1","81:3:0:=c81:1:0/c81:2:0","81:4:0:=c81:1:0*c$1:7:0/100","81:5:0:=Math.round(c81:3:0+c81:4:0)","82:0:0:=new Date(c81:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","82:1:0:=c81:1:0+c81:4:0-(c81:6:0===null?c81:5:0:c81:6:0)","82:2:0:=c81:2:0-1","82:3:0:=c82:1:0/c82:2:0","82:4:0:=c82:1:0*c$1:7:0/100","82:5:0:=Math.round(c82:3:0+c82:4:0)","83:0:0:=new Date(c82:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","83:1:0:=c82:1:0+c82:4:0-(c82:6:0===null?c82:5:0:c82:6:0)","83:2:0:=c82:2:0-1","83:3:0:=c83:1:0/c83:2:0","83:4:0:=c83:1:0*c$1:7:0/100","83:5:0:=Math.round(c83:3:0+c83:4:0)","84:0:0:=new Date(c83:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","84:1:0:=c83:1:0+c83:4:0-(c83:6:0===null?c83:5:0:c83:6:0)","84:2:0:=c83:2:0-1","84:3:0:=c84:1:0/c84:2:0","84:4:0:=c84:1:0*c$1:7:0/100","84:5:0:=Math.round(c84:3:0+c84:4:0)","85:0:0:=new Date(c84:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","85:1:0:=c84:1:0+c84:4:0-(c84:6:0===null?c84:5:0:c84:6:0)","85:2:0:=c84:2:0-1","85:3:0:=c85:1:0/c85:2:0","85:4:0:=c85:1:0*c$1:7:0/100","85:5:0:=Math.round(c85:3:0+c85:4:0)","86:0:0:=new Date(c85:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","86:1:0:=c85:1:0+c85:4:0-(c85:6:0===null?c85:5:0:c85:6:0)","86:2:0:=c85:2:0-1","86:3:0:=c86:1:0/c86:2:0","86:4:0:=c86:1:0*c$1:7:0/100","86:5:0:=Math.round(c86:3:0+c86:4:0)","87:0:0:=new Date(c86:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","87:1:0:=c86:1:0+c86:4:0-(c86:6:0===null?c86:5:0:c86:6:0)","87:2:0:=c86:2:0-1","87:3:0:=c87:1:0/c87:2:0","87:4:0:=c87:1:0*c$1:7:0/100","87:5:0:=Math.round(c87:3:0+c87:4:0)","88:0:0:=new Date(c87:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","88:1:0:=c87:1:0+c87:4:0-(c87:6:0===null?c87:5:0:c87:6:0)","88:2:0:=c87:2:0-1","88:3:0:=c88:1:0/c88:2:0","88:4:0:=c88:1:0*c$1:7:0/100","88:5:0:=Math.round(c88:3:0+c88:4:0)","89:0:0:=new Date(c88:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","89:1:0:=c88:1:0+c88:4:0-(c88:6:0===null?c88:5:0:c88:6:0)","89:2:0:=c88:2:0-1","89:3:0:=c89:1:0/c89:2:0","89:4:0:=c89:1:0*c$1:7:0/100","89:5:0:=Math.round(c89:3:0+c89:4:0)","90:0:0:=new Date(c89:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","90:1:0:=c89:1:0+c89:4:0-(c89:6:0===null?c89:5:0:c89:6:0)","90:2:0:=c89:2:0-1","90:3:0:=c90:1:0/c90:2:0","90:4:0:=c90:1:0*c$1:7:0/100","90:5:0:=Math.round(c90:3:0+c90:4:0)","91:0:0:=new Date(c90:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","91:1:0:=c90:1:0+c90:4:0-(c90:6:0===null?c90:5:0:c90:6:0)","91:2:0:=c90:2:0-1","91:3:0:=c91:1:0/c91:2:0","91:4:0:=c91:1:0*c$1:7:0/100","91:5:0:=Math.round(c91:3:0+c91:4:0)","92:0:0:=new Date(c91:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","92:1:0:=c91:1:0+c91:4:0-(c91:6:0===null?c91:5:0:c91:6:0)","92:2:0:=c91:2:0-1","92:3:0:=c92:1:0/c92:2:0","92:4:0:=c92:1:0*c$1:7:0/100","92:5:0:=Math.round(c92:3:0+c92:4:0)","93:0:0:=new Date(c92:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","93:1:0:=c92:1:0+c92:4:0-(c92:6:0===null?c92:5:0:c92:6:0)","93:2:0:=c92:2:0-1","93:3:0:=c93:1:0/c93:2:0","93:4:0:=c93:1:0*c$1:7:0/100","93:5:0:=Math.round(c93:3:0+c93:4:0)","94:0:0:=new Date(c93:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","94:1:0:=c93:1:0+c93:4:0-(c93:6:0===null?c93:5:0:c93:6:0)","94:2:0:=c93:2:0-1","94:3:0:=c94:1:0/c94:2:0","94:4:0:=c94:1:0*c$1:7:0/100","94:5:0:=Math.round(c94:3:0+c94:4:0)","95:0:0:=new Date(c94:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","95:1:0:=c94:1:0+c94:4:0-(c94:6:0===null?c94:5:0:c94:6:0)","95:2:0:=c94:2:0-1","95:3:0:=c95:1:0/c95:2:0","95:4:0:=c95:1:0*c$1:7:0/100","95:5:0:=Math.round(c95:3:0+c95:4:0)","96:0:0:=new Date(c95:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","96:1:0:=c95:1:0+c95:4:0-(c95:6:0===null?c95:5:0:c95:6:0)","96:2:0:=c95:2:0-1","96:3:0:=c96:1:0/c96:2:0","96:4:0:=c96:1:0*c$1:7:0/100","96:5:0:=Math.round(c96:3:0+c96:4:0)","97:0:0:=new Date(c96:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","97:1:0:=c96:1:0+c96:4:0-(c96:6:0===null?c96:5:0:c96:6:0)","97:2:0:=c96:2:0-1","97:3:0:=c97:1:0/c97:2:0","97:4:0:=c97:1:0*c$1:7:0/100","97:5:0:=Math.round(c97:3:0+c97:4:0)","98:0:0:=new Date(c97:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","98:1:0:=c97:1:0+c97:4:0-(c97:6:0===null?c97:5:0:c97:6:0)","98:2:0:=c97:2:0-1","98:3:0:=c98:1:0/c98:2:0","98:4:0:=c98:1:0*c$1:7:0/100","98:5:0:=Math.round(c98:3:0+c98:4:0)","99:0:0:=new Date(c98:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","99:1:0:=c98:1:0+c98:4:0-(c98:6:0===null?c98:5:0:c98:6:0)","99:2:0:=c98:2:0-1","99:3:0:=c99:1:0/c99:2:0","99:4:0:=c99:1:0*c$1:7:0/100","99:5:0:=Math.round(c99:3:0+c99:4:0)","100:0:0:=new Date(c99:0:0.getTime()+1000*60*60*24*365.25/c$1:4:0)","100:1:0:=c99:1:0+c99:4:0-(c99:6:0===null?c99:5:0:c99:6:0)"]`;
}

function controls(sheet,tabledriver) {
 var controls = document.createElement('div');
 controls.classList.add('controls');
 var saveloadbox = document.createElement('textarea');
 controls.saveloadbox=saveloadbox;
 saveloadbox.classList.add('saveloadbox');
 var save = document.createElement('a');
 save.innerText='[save]';
 save.addEventListener('click',()=>{
  saveloadbox.value=JSON.stringify(sheet.history);
  saveloadbox.select();
  saveloadbox.setSelectionRange(0, 99999);
  document.execCommand("copy");
  alert("Copied to clipbord");
 });
 var load = document.createElement('a');
 load.innerText='[load]';
 function reloadwithtext(text) {
  var loadcontent = text?JSON.parse(text):[];
  var [x,y,z]=dimforhistory(loadcontent);
  console.log(x,y,z);
  sheet = createnewsheet(x,y,z);
  tabledriver.remove();
  tabledriver = driver(sheet);
  controls.parentElement.insertBefore(tabledriver,controls.nextElementSibling);
  applyhistorytosheet(sheet,loadcontent);
 }
 load.addEventListener('click',()=>reloadwithtext(saveloadbox.value));
 var importa = document.createElement('a');
 importa.innerText='[import file]';
 importa.addEventListener('click',()=>{
  var fi = document.createElement('input');
  fi.type='file';
  fi.onchange=()=>{
   console.log('change happened');
   window.file=fi.files[0];
   fi.files[0].text().then(text=>{
    console.log('We have text',text);
    saveloadbox.value=text;
    reloadwithtext(text);
   });
  };
  fi.click();
 });
 var exporta = document.createElement('a');
 exporta.innerText='[export file]';
 exporta.addEventListener('click',()=>pushdownload(JSON.stringify(sheet.history),'sheet.3dspread3'));
 var csavea = document.createElement('a');
 csavea.innerText = '[save to cloud]';
 csavea.addEventListener('click',()=>{
  var overlayc=document.createElement('div');
  removeonclick(overlayc);
  overlayc.classList.add('overlayc');
  var saveinput=document.createElement('input');
  saveinput.placeholder='Save name';
  if(location.pathname.includes('/doc/')) {
   saveinput.value=location.pathname.split('/doc/').pop();
  }
  overlayc.appendChild(saveinput);
  document.body.appendChild(overlayc);
  saveinput.focus();
  saveinput.addEventListener('keypress',e=>{
   if(e.keyCode==13) {
    savedocument(saveinput.value,sheet.history,err=>{
     if(err) alert(err);
     overlayc.remove();
    });
   }
  });
  
 });
 var cloada = document.createElement('a');
 cloada.innerText = '[load from cloud]';
 cloada.addEventListener('click',()=>{
  var overlayc=document.createElement('div');
  removeonclick(overlayc);
  overlayc.classList.add('overlayc');
  document.body.appendChild(overlayc);
  getdocumentlist((err,list)=>{
   list.forEach(i=>{
    var option=document.createElement('a');
    option.classList.add('option');
    var ospan=document.createElement('span');
    ospan.innerText=i;
    var close=document.createElement('span');
    close.classList.add('close');
    close.innerText='x';
    option.appendChild(ospan);
    option.appendChild(close);
    option.href='/app/3dspread3/doc/'+i;
    option.target='_blank';
    overlayc.appendChild(option);
    close.addEventListener('click',e=>{
     e.preventDefault();
     var overlayc2 = document.createElement('div');
     removeonclick(overlayc2);
     overlayc2.classList.add('overlayc');
     overlayc2.classList.add('overlayc2');
     window.overlayc2 = overlayc2;
     var confirm = document.createElement('a');
     confirm.innerText='Are you sure?';
     confirm.classList.add('option');
     overlayc2.appendChild(confirm);
     document.body.appendChild(overlayc2);
     confirm.addEventListener('click',e=>{
      e.preventDefault();
      removedocument(i,()=>{
       option.remove();
       overlayc2.remove();
      });
      return false;
     });
     return false;
    });
   });
  });
 });
 var colab = document.createElement('a');
 colab.innerText = '[colab]';
 colab.addEventListener('click',()=>{
  var overlayc=document.createElement('div');
  removeonclick(overlayc);
  overlayc.classList.add('overlayc');
  var create = document.createElement('a');
  create.classList.add('option');
  create.innerText='Create';
  var join = document.createElement('a');
  join.classList.add('option');
  join.innerText='Join';
  overlayc.appendChild(create);
  overlayc.appendChild(join);
  document.body.appendChild(overlayc);
  create.addEventListener('click',()=>{
   create.remove();
   join.remove();
   var createinput=document.createElement('input');
   createinput.placeholder='Room key';
   createinput.value=randbs58id(10);
   overlayc.appendChild(createinput);
   createinput.focus();
   createinput.addEventListener('keypress',e=>{
    if(e.keyCode==13) {
     var roomkey='3dspread3/'+createinput.value;
     var ws = new WebSocket(location.protocol.replace('http','ws')+'//'+location.host+'/crcreate/'+roomkey);
     window.ws=ws;
     ws.onopen = ()=>{
      addcolab(window.user+'/'+createinput.value);
      ws.send(JSON.stringify({cmd:'getall'}));
     }
     ws.onmessage = message=>{
      console.log('message',message);
      message = JSON.parse(message.data);
      message = [].concat(message).map(m=>m.msg);
      applyhistorytosheet(sheet,message);
     }
     var explaindiv=document.createElement('div');
     explaindiv.innerText='Room URL';
     overlayc.insertBefore(explaindiv,createinput);
     createinput.value=window.user+'/'+createinput.value;
     overlayc.addEventListener('click',()=>overlayc.remove());
     createinput.select();
     createinput.setSelectionRange(0, 99999);
     document.execCommand("copy");
     alert("Copied to clipbord");
    }
   });
  });
  join.addEventListener('click',()=>{
   create.remove();
   join.remove();
   var joininput=document.createElement('input');
   joininput.placeholder='Room key';
   overlayc.appendChild(joininput);
   joininput.focus();
   joininput.addEventListener('keypress',e=>{
    if(e.keyCode==13) {
     overlayc.remove();
     var [roomowner,roomkey] = joininput.value.split('/');
     roomkey=roomowner+'/3dspread3/'+roomkey;
     var ws = new WebSocket(location.protocol.replace('http','ws')+'//'+location.host+'/crjoin/'+roomkey);
     window.ws=ws;
     ws.onopen = ()=>{
      addcolab(joininput);
      ws.send(JSON.stringify({cmd:'getall'}));
     }
     ws.onmessage = message=>{
      console.log('message',message);
      message = JSON.parse(message.data);
      message = [].concat(message).map(m=>m.msg);
      applyhistorytosheet(sheet,message);
     }
    }
   });
  });
 });
 var templatemenu=document.createElement('a');
 templatemenu.innerText='[use template]';
 templatemenu.addEventListener('click',()=>{
  var overlayc=document.createElement('div');
  removeonclick(overlayc);
  overlayc.classList.add('overlayc');
  var uitemplatelist=document.createElement('div');
  uitemplatelist.classList.add('uitemplatelist');
  templatelist().forEach(templatename=>{
   var templatelink=document.createElement('a');
   templatelink.innerText=templatename;
   templatelink.addEventListener('click',()=>{
    saveloadbox.value=window['template_'+templatename]();
    load.click();
    overlayc.remove();
   });
   uitemplatelist.appendChild(templatelink);
  });
  overlayc.appendChild(uitemplatelist);
  document.body.appendChild(overlayc);
 });
 var setserver=document.createElement('a');
 setserver.innerText='[set js provider]';
 setserver.addEventListener('click',()=>{
  var overlayc=document.createElement('div');
  removeonclick(overlayc);
  overlayc.classList.add('overlayc');
  var remoteserveri=document.createElement('input');
  remoteserveri.placeholder='Remote server url';
  remoteserveri.addEventListener('keypress',e=>{
   if(e.keyCode==13) {
    window.jsserver = remoteserveri.value;
    overlayc.remove();
   }
  });
  overlayc.appendChild(remoteserveri);
  document.body.appendChild(overlayc);
 });
 var sortb = document.createElement('a');
 sortb.innerText='[sort]';
 var sortoverlayc=null;
 var sortrange=null;
 sortb.addEventListener('click',()=>{
  //if(sortoverlayc) {
  // return document.body.append(sortoverlayc);
  //}
  sortrange=contiguousrange(sheet,textarea.activetd.x,textarea.activetd.y,sheet.z);
  var overlayc=document.createElement('div');
  sortoverlayc=overlayc;
  var overlayi=document.createElement('form');
  overlayc.append(overlayi);
  removeonclick(overlayc);
  overlayc.classList.add('overlayc');
  overlayi.classList.add('overlayi');
  var rowcolumnselect=document.createElement('div');
  rowcolumnselect.innerText="Sort: ";
  var columnr=document.createElement('input');
  columnr.type='radio';
  columnr.name='rowcolumn';
  columnr.value='column';
  var rowr=document.createElement('input');
  rowr.type='radio';
  rowr.checked=true;
  rowr.name='rowcolumn';
  rowr.value='row';
  rowcolumnselect.append(rowr);
  rowcolumnselect.append('Row');
  rowcolumnselect.append(columnr);
  rowcolumnselect.append('Column');
  var viewdataselect=document.createElement('div');
  viewdataselect.innerText='Order: ';
  var datar=document.createElement('input');
  datar.type='radio';
  datar.checked='true';
  datar.name='viewdata';
  datar.value='data';
  var viewr=document.createElement('input');
  viewr.type='radio';
  viewr.name='viewdata';
  viewr.value='view';
  var viewrtitle=document.createElement('span');
  viewrtitle.innerText='Rows';
  viewdataselect.append(datar);
  viewdataselect.append('Data');
  viewdataselect.append(viewr);
  viewdataselect.append(viewrtitle);
  var sortbutton=document.createElement('button');
  sortbutton.innerText='Sort!';
  sortbutton.style.display='none';
  var sortlinesdiv = document.createElement('div');
  console.log('sortrange',sortrange);
  createsortline(sortrange[1],sortrange[4],sortlinesdiv,sortbutton);
  overlayi.append(rowcolumnselect);
  overlayi.append(viewdataselect);
  overlayi.append(sortlinesdiv);
  overlayc.append(sortbutton);
  document.body.append(overlayc);
  rowr.addEventListener('click',()=>viewrtitle.innerText='Rows');
  columnr.addEventListener('click',()=>viewrtitle.innerText='Columns');
  sortbutton.addEventListener('click',()=>{
   var order=[];
   for(var x=sortrange[0];x<=sortrange[3];++x) {
    order.push(x); //Fill it initially
   }
   sortlinesdiv.childNodes.forEach(sortline=>{
    var elems=sortline.children;
    var column=parseInt(elems[0].value);
    if(!column) return;
    var mapfunc=elems[1].value;
    var compfunc=elems[2].value?eval(elems[2].value):stdcompare;
    var array=order.map(row=>sheet[row][column][sheet.z].oc)
    console.log('array',array);
    if(mapfunc) array=array.map(eval(mapfunc));
    console.log('mapped array',array);
    order.sort((a,b)=>compfunc(array[a-sortrange[0]],array[b-sortrange[0]]));
    console.log('order',order);
    var ucarray = order.map((row,index)=>sheet[row].slice(sortrange[1],sortrange[4]+1).map(i=>{
     var uc = i[0].uc;
     if(uc[0]=='=') {
      var offset=index+sortrange[0]-row;
      console.log('offset',offset,row,index,sortrange[0]);
      uc = translateFormulaSpacially(uc,[offset,0,0]);
     }
     return uc;
    }));
    console.log('ucarray',ucarray);
    var modified=new Set();
    for(var x=sortrange[0];x<=sortrange[3];++x) {
     for(var y=sortrange[1];y<=sortrange[4];++y) {
      var uc=ucarray[x-sortrange[0]][y-sortrange[1]];
      assigntocell(sheet,x,y,sheet.z,ucarray[x-sortrange[0]][y-sortrange[1]]).forEach(m=>modified.add(m));
     }
    }
    renderaltered(modified);
    console.log('order',order);
   });
   overlayc.remove();
  });
 });
 var help = document.createElement('a');
 help.classList.add('help');
 help.href='https://jssocial.pw/ppkey/fget/3dspread2/upload/w3uWRteRa7.html';
 help.target='_blank';
 help.innerText='[Help]';
 var spacer = document.createElement('span');
 spacer.innerText=' ';
 [save,load,importa,exporta,csavea,cloada,colab,templatemenu,spacer,setserver,sortb,help].forEach(e=>controls.appendChild(e));
 return controls;
}

function get3dspreadCSS() {
 return `
  #changez {
   width:3em;
  }
  .selectedtd {
   border:3px solid #0000ff55;
  }
  .tmpselectedtd {
   border:2px solid #00aaff44;
  }
  .uitemplatelist {
   background:white;
  }
  .uitemplatelist a {
   color:blue;
   textdecoration:underline;
   cursor:pointer;
  }
  body {
   display:flex;
   flex-flow:column;
   font-family:helvetica,sans;
   font-size:15px;
  }
  .controls {
   display:flex;
   flex-flow:row;
  }
  .controls > * {
   cursor:pointer;
  }
  .help {
   margin-left:auto;
  }
  table {
   border-collapse: collapse;
  }
  td {
   margin:0;
   padding:0;
   width:256px;
   border:1px solid #0003;
   line-height: 24px;
   text-overflow: ellipsis;
   overflow: hidden;
   white-space: nowrap;
  }
  thead td {
   text-align:center;
   background: #0453;
   font-weight:700;
  }
  .rhead {
   text-align:center;
   padding:0 6px;
   width:auto;
   background: #0452;
  }
  .corner {
   width:auto;
   background:#fff;
  }
  .cellinput {
   resize:none;
   margin:-1px 0 0 -1px;
   box-sizing:border-box;
  }
  .formulabar {
   display:flex;
   align-items:center;
  }
  .formulabar > textarea {
   resize:none;
   box-sizing:border-box;
   width:100%;
   height:2.3em;
   padding:0.4em;

  }
  .saveloadbox {
   margin-right:16px;
   height:8em;
  }
  td.adder {
   width:15px;
   line-height:15px;
   cursor:pointer;
  }
  .overlayc {
   position: absolute;
   top: 0;
   left: 0;
   background: #0004;
   width: 100vw;
   height: 100vh;
   display: flex;
   align-items: center;
   justify-content: center;
   flex-wrap:wrap;
  }
  .overlayi {
   display: flex;
   flex-flow:column;
   padding:15px;
   background:white;
   border-radius:4px;
   border:1px solid black;
  }
  .option {
   font-weight:700;
   font-size:18px;
   padding:8px;
   background:#8b90dd;
   border-radius:32px;
   border:1.5px solid #dddbdb;
   margin:8px;
   cursor:pointer;
  }
  .close {
   margin-left:8px;
   color:black;
   text-decoration:none;
  }
 `
}

function createnewsheet(x,y,z) {
 x=x||10;
 y=y||10;
 z=z||1;
 var retr=new Array(x);
 for(var xi=0;xi<x;++xi) {
  retr[xi]=new Array(y);
  for(var yi=0;yi<y;++yi) {
   retr[xi][yi]=new Array(z);
   for(var zi=0;zi<z;++zi) {
    retr[xi][yi][zi]={uc:"",oc:null,up:new Set(),down:new Set(),location:{x:xi,y:yi,z:zi,a:`${xi}:${yi}:${zi}`}};
   }
  }
 }
 retr.history=[];
 return retr;
}

function sum(list) {
 return list.reduce((a,b)=>a+b);
}

function median(list) {
 var sorted = list.sort();
 var index = list.length/2-0.5;
 return (sorted[Math.floor(index)]+sorted[Math.ceil(index)])/2;
}

function min(list) {
 var min=Infinity;
 list.forEach(i=>{
  if(i<min) {
   min=i;
  }
 });
 return min;
}

function max(list) {
 var min=-Infinity;
 list.forEach(i=>{
  if(i>min) {
   min=i;
  }
 });
 return min;
}

function maxindex(list) {
 var max=-Infinity
 var idx=-1;
 list.forEach((v,i)=>{
  if(v>max) {
   max=v;
   idx=i;
  }
 });
 return idx;
}

function E(cmd) {
 if(window.jsserver) {
  var xmlr=new XMLHttpRequest();
  xmlr.open('GET',window.jsserver+'/'+cmd,false);
  xmlr.send();
  return JSON.parse(xmlr.responseText);
 }
 else {
  return eval(cmd);
 }
}

function contiguousrange(sheet,x,y,z) {
 var minx=x;
 var miny=y;
 var minz=z;
 var maxx=x;
 var maxy=y;
 var maxz=z;
 var didexpand=true;
 while(didexpand) {
  didexpand=false;
  var test;
  //Expand up
  outminx:
  if(minx-1>=0) {
   for(var sy=miny;sy<=maxy;++sy) {
    for(var sz=minz;sz<=maxz;++sz) {
     if(sheet[minx-1][sy][sz].oc!==null) {
      --minx;
      didexpand=true;
      break outminx;
     }
    }
   }
  }
  //Expand down
  outmaxx:
  if(maxx+1<sheet.length) {
   for(var sy=miny;sy<=maxy;++sy) {
    for(var sz=minz;sz<=maxz;++sz) {
     if(sheet[maxx+1][sy][sz].oc!==null) {
      ++maxx;
      didexpand=true;
      break outmaxx;
     }
    }
   }
  }
  //Expand left
  outminy:
  if(miny-1>=0) {
   for(var sx=minx;sx<=maxx;++sx) {
    for(var sz=minz;sz<=maxz;++sz) {
     if(sheet[sx][miny-1][sz].oc!==null) {
      --miny;
      didexpand=true;
      break outminy;
     }
    }
   }
  }
  //Expand right
  outmaxy:
  if(maxy+1<sheet[0].length) {
   for(var sx=minx;sx<=maxx;++sx) {
    for(var sz=minz;sz<=maxz;++sz) {
     if(sheet[sx][maxy+1][sz].oc!==null) {
      ++maxy;
      didexpand=true;
      break outmaxy;
     }
    }
   }
  }
  //Expand in
  outminz:
  if(minz-1>=0) {
   for(var sx=minx;sx<=maxx;++sx) {
    for(var sy=miny;sy<=maxy;++sy) {
     if(sheet[sx][sy][minz-1].oc!==null) {
      --minz;
      didexpand=true;
      break outminz;
     }
    }
   }
  }
  //Expand out
  outmaxz:
  if(maxz+1<sheet[0][0].length) {
   for(var sx=minx;sx<=maxx;++sx) {
    for(var sy=miny;sy<=maxy;++sy) {
     if(sheet[sx][sy][maxz+1].oc!==null) {
      ++maxz;
      didexpand=true;
      break outmaxz;
     }
    }
   }
  }
 }
 return [minx,miny,minz,maxx,maxy,maxz];
}

function assigntocellhelper(sheet,cell,uc,lockset,willrecalc,isfirst) {
 if(lockset.has(cell)) return lockset;
 lockset.add(cell);
 if(!isfirst) {
  cell.up.forEach(parent=>{
   if(willrecalc.has(parent)) {
    assigntocellhelper(sheet,parent,parent.uc,lockset,willrecalc);
   }
  });
 }
 cell.uc=uc;
 cell.up.forEach(u=>u.down.delete(cell));
 cell.up=new Set();
 var oc=null;
 if(uc=='') {
  cell.oc=null;
 }
 else if(uc[0]=="=") {
  //This is a formula
  var rregexp = /c\$?([0-9]+):\$?([0-9]+):\$?([0-9]+)c\$?([0-9]+):\$?([0-9]+):\$?([0-9]+)/g;  //This is a regex for a range
  var erregexp = /c\$?([0-9]+):\$?([0-9]+):\$?([0-9]+)([udlrio])/g; //Auto expand range up
  var cregexp = /c\$?([0-9]+):\$?([0-9]+):\$?([0-9]+)/g; //This is a regex for a non-range address
  var suc=uc.slice(1);
  var cmd=suc;
  (cmd.match(rregexp)||[]).forEach(range=>{  //translate range into javascript
   var [x1,y1,z1,x2,y2,z2]=range.slice(1).replace('c',':').split(':').map(i=>{
    if(i[0]=='$') return parseInt(i.slice(1));
    return parseInt(i);
   });
   console.log(x1,y1,z1,x2,y2,z2);
   var expressionparts=[];
   for(var xi=x1;xi<=x2;++xi) {
    for(var yi=y1;yi<=y2;++yi) {
     for(var zi=z1;zi<=z2;++zi) {
      expressionparts.push(`sheet[${xi}][${yi}][${zi}].oc`);
      var link = sheet[xi][yi][zi];
      cell.up.add(link);
      link.down.add(cell);
     }
    }
   }
   cmd=cmd.replace(range,'['+expressionparts.join(',')+']');
  });
  (cmd.match(erregexp)||[]).forEach(range=>{
   var d=range.slice(-1);
   var [y,x,z]=range.slice(1,-1).split(':');
   console.log({y,x,z});
   var expressionparts=[];
   if(d=='d') {
    for(y=y;y<sheet.length&&sheet[y][x][z].uc;++y) {
     expressionparts.push(`sheet[${y}][${x}][${z}].oc`);
     var link=sheet[y][x][z];
     cell.up.add(link);
     link.down.add(cell);
    }
   }
   if(d=='u') {
    for(y=y;y>=0&&sheet[y][x][z].uc;--y) {
     expressionparts.push(`sheet[${y}][${x}][${z}].oc`);
     var link=sheet[y][x][z];
     cell.up.add(link);
     link.down.add(cell);
    }
   }
   if(d=='l') {
    for(x=x;x>=0&&sheet[y][x][z].uc;--x) {
     expressionparts.push(`sheet[${y}][${x}][${z}].oc`);
     var link=sheet[y][x][z];
     cell.up.add(link);
     link.down.add(cell);
    }
   }
   if(d=='r') {
    for(x=x;x<sheet[0].length&&sheet[y][x][z].uc;++x) {
     expressionparts.push(`sheet[${y}][${x}][${z}].oc`);
     var link=sheet[y][x][z];
     cell.up.add(link);
     link.down.add(cell);
    }
   }
   if(d=='i') {
    for(z=z;z>=0&&sheet[y][x][z].uc;--z) {
     expressionparts.push(`sheet[${y}][${x}][${z}].oc`);
     var link=sheet[y][x][z];
     cell.up.add(link);
     link.down.add(cell);
    }
   }
   if(d=='o') {
    for(z=z;z<sheet[0][0].length&&sheet[y][x][z].uc;++z) {
     expressionparts.push(`sheet[${y}][${x}][${z}].oc`);
     var link=sheet[y][x][z];
     cell.up.add(link);
     link.down.add(cell);
    }
   }
   //Link one further;
   if(y>=0&&x>=0&&z>=0&&y<sheet.length&&x<sheet[0].length&&z<sheet[0][0].length) { //If the one further location is legal
    var link=sheet[y][x][z];
    cell.up.add(link);
    link.down.add(cell);
   }
   console.log({x,y,z});
   cmd=cmd.replace(range,'['+expressionparts.join(',')+']');
  });
  cmd=cmd.replace(cregexp,"sheet[$1][$2][$3].oc");
  console.log('cmd',cmd);
  try {
   cell.oc = eval(cmd);
  }
  catch(e) {
   console.log('cell err',e);
   cell.oc = 'Err';
  }
  (suc.match(cregexp)||[]).forEach(link=>{  //Translate address into javasciprt
   var [lx,ly,lz]=link.slice(1).split(':').map(i=>{
    if(i[0]=='$') return parseInt(i.slice(1));
    return parseInt(i);
   });
   var link = sheet[lx][ly][lz];
   cell.up.add(link);
   link.down.add(cell);
  });
 }
 else {
  try {
   cell.oc=JSON.parse(uc);
  } 
  catch(e) {
   if(uc[0]=="'") {
    cell.oc=uc.slice(1);
   }
   else {
    cell.oc=uc;
   }
  }
 }
 cell.down.forEach(out=>assigntocellhelper(sheet,out,out.uc,lockset,willrecalc));
 return lockset; //Returns what updated
}

function alldown(cell,downset) {
 if(downset.has(cell)) return;
 downset.add(cell);
 cell.down.forEach(sub=>alldown(sub,downset));
 return downset;
}

function assigntocell(sheet,x,y,z,uc,blockws) {
 var cell = sheet[x][y][z];
 if(cell.uc==uc) return new Set();  //Do nothing
 var histadd=x+':'+y+':'+z+':'+uc
 sheet.history.push(histadd);
 if(!blockws && window.ws) {
  var message=JSON.stringify({cmd:'msg',msg:histadd});
  console.log('sending message',message);
  ws.send(JSON.stringify({cmd:"msg",msg:histadd}));
 }
 var willrecalc = alldown(cell,new Set());
 var changed = assigntocellhelper(sheet,cell,uc,new Set(),willrecalc,true);
 return changed;
}

function applyhistorytosheet(sheet,history,blockws) {
 if(typeof(history)=='string') return applyhistorytosheet(sheet,JSON.parse(history));
 var modified=new Set();
 history.forEach(h=>{
  var [x,y,z,...uc] = h.split(':');
  uc=uc.join(':');
  try {
   assigntocell(sheet,x,y,z,uc,blockws).forEach(cell=>{
    modified.add(cell);
   });
  }
  catch(e) {
   console.log(e,x,y,z,uc);
  }
 });
 renderaltered(modified);
}



function stringify(dv) {
 if(dv===null) return "";
 if(dv===Infinity) return "Infinity";
 dv=JSON.stringify(dv);
 if(dv[0]=='"') dv=dv.slice(1,-1);
 return dv;
}

function getSelectedTds() {
 if(window.chrome) { //We have to do selection ourselves
  return document.querySelectorAll('.selectedtd');
 }
 //Not chrome so we use the browser's selection
 var s=window.getSelection();
 var tds=[];
 for(var c=0;c<s.rangeCount;++c) {
  var range = s.getRangeAt(c);
  for(var b=range.startOffset;b<range.endOffset;++b) {
   tds.push(range.startContainer.childNodes[b]);
  }
 }
 return tds;
}

function getAboveSelectedTd(td,selectedset) {
 var retr=td;
 var x=td.x;
 var y=td.y;
 while(x&&selectedset.has(sheet[x-1][y][sheet.z].td)) {
  --x;
  retr=sheet[x-1][y][sheet.z].td;
 }
 return td;
}

function getTopSelectedAbove(td,selectedset) {
 var retr=td;
 var x=td.x;
 var y=td.y;
 while(x&&selectedset.has(sheet[x-1][y][sheet.z].td)) {
  --x;
  retr=sheet[x][y][sheet.z].td;
 }
 return retr;
}

function getXYZlistForSelection() {
 if(window.chrome) { //Not on firefox
  var tds=getSelectedTds();
  var retr=[];
  tds.forEach(td=>retr.push([td.x,td.y,td.z]));
  return retr;
 }
 var s = window.getSelection();
 var ranges=[];
 for(var c=0;c<s.rangeCount;++c) {
  ranges.push(s.getRangeAt(c));
 }
 return ranges.map(r=>r.cloneContents().childNodes[0]).map(n=>[n.dataset.x,n.dataset.y,n.dataset.z].map(e=>parseInt(e)));
}

function selection2ucarray(sheet) {
 var targets = getXYZlistForSelection();
 var minX=Infinity;
 var minY=Infinity;
 var minZ=Infinity;
 targets.forEach(t=>{
  console.log({t,targets});
  t[3]=sheet[t[0]][t[1]][t[2]].uc;
  t[4]=stringify(sheet[t[0]][t[1]][t[2]].oc);
  if(t[0]<minX) minX=t[0];
  if(t[1]<minY) minY=t[1];
  if(t[2]<minZ) minZ=t[2];
 });
 targets.forEach(t=>{
  t[0]-=minX;
  t[1]-=minY;
  t[2]-=minZ;
 });
 var maxX=-Infinity;
 var maxY=-Infinity;
 targets.forEach(t=>{
  if(t[0]>maxX) maxX=t[0];
  if(t[1]>maxY) maxY=t[0];
 });
 var textgrid=[];
 for(var x=0;x<=maxX;++x) {
  textgrid[x]=[];
 }
 console.log({minX,minY,maxX,maxY,textgrid});
 targets.forEach(t=>{
  console.log({t,minX,minY,sheet,index:t[0]+minX});
  textgrid[t[0]][t[1]]=stringify(sheet[t[0]+minX][t[1]+minY][t[2]].oc);
 });
 textgrid=textgrid.map(r=>r.join('\t')).join('\n');
 return [targets,minX,minY,minZ,textgrid];
}


function absoluteOffset(elem) {
 var left=0;
 var top=0;
 while(elem) {
  left+=elem.offsetLeft;
  top+=elem.offsetTop;
  elem=elem.offsetParent;
 }
 return [left,top];
}

function offsetuc(uc,deltas) {
 var regexp = /c(\$?[0-9]+):(\$?[0-9]+):(\$?[0-9]+)/g;
 var matchAddresses = uc.match(regexp)||[];
 console.log('matchAddresses',matchAddresses);
 matchAddresses.forEach(sample=>{
  var [ax,ay,az]=sample.slice(1).split(':').map((val,index)=>{
   if(val[0]=='$') return val;
   return parseInt(val)+deltas[index];
  });
  uc=uc.replace(sample,`c${ax}:${ay}:${az}`);
 });
 return uc;
}

function renderaltered(altered) {
 console.log('rendering altered',altered);
 altered.forEach(cell=>{
  if(cell.td) {
   var text=stringify(cell.oc);
   console.log('rendering',cell,text,cell.oc);
   cell.td.innerText=text;
  }
 });
}

function translateFormulaSpacially(uc,deltas,regexp) {
 regexp = regexp || /c(\$?[0-9]+):(\$?[0-9]+):(\$?[0-9]+)/g;
 var matchAddresses = uc.match(regexp)||[];
 console.log('matchAddresses',matchAddresses);
 matchAddresses.forEach(sample=>{
  var [ax,ay,az]=sample.slice(1).split(':').map((val,index)=>{
   if(val[0]=='$') return val;
   return parseInt(val)+deltas[index];
  });
  uc=uc.replace(sample,`c${ax}:${ay}:${az}`);
 });
 return uc;
}

function driver(sheet) {
 return mirroredformuladriver(sheet);
}

function mirroredformuladriver(sheet) {
 var tabledriver=basicdriver(sheet);
 var textarea=tabledriver.querySelector('.cellinput');
 var retr = document.createElement('div');
 var formuladiv=document.createElement('div');
 var formulabar=document.createElement('textarea');
 formulabar.noblur=true;
 formuladiv.classList.add('formulabar');
 formuladiv.append('F(x)=',formulabar);
 retr.append(formuladiv,tabledriver);
 textarea.addEventListener('input',()=>formulabar.value=textarea.value);
 formulabar.addEventListener('input',()=>textarea.value=formulabar.value);
 formulabar.addEventListener('blur',e=>{
  if(e.relatedTarget!=textarea) {
   textarea.onblur(e);
   //e.preventDefault();
   //return false;
  }
 });
 textarea.addEventListener('focus',()=>formulabar.value=textarea.value);
 return retr;
}

function basicdriver(sheet) {
 sheet = sheet || createnewsheet(20,10,1);
 window.sheet=sheet;
 var table=document.createElement('table');
 var thead=document.createElement('thead');
 var theadtr = document.createElement('tr');
 var corner = document.createElement('td');
 var currentz = 0;
 sheet.z = currentz;
 corner.classList.add('corner');
 theadtr.append(corner);
 for(var thy=0;thy<sheet[0].length;++thy) {
  var theadtd=document.createElement('td');
  theadtd.innerText=thy;
  theadtr.append(theadtd);
 }
 var addcolumn = document.createElement('td');
 addcolumn.innerText='+';
 addcolumn.classList.add('adder');
 theadtr.append(addcolumn);
 thead.append(theadtr);
 var tbody=document.createElement('tbody');
 var recentUCCopy = null;
 var recentContentCopy = null;
 tbody.oncopy = e=>{
  recentUCCopy = selection2ucarray(sheet);
  console.log({recentUCCopy});
  if(window.chrome) { //We are not firefox
   recentContentCopy = recentUCCopy[4];
   e.preventDefault();
   if(e.clipboardData) {
    e.clipboardData.setData('text/plain',recentContentCopy);
   }
   else if(window.clipboardData) {
    e.clipboardData.setData('Text',recentContentCopy);
   }
   else {
    console.log("Custom copy not supported");
   }
   console.log({recentContentCopy});
   return;
  }
  recentContentCopy = window.getSelection().toString();
 }
 if(!document.body.deletebinded) {
  document.body.deletebinded=true;
  document.body.addEventListener('keydown',e=>{
   console.log('tbody keydown',e);
   if(e.keyCode==8 || e.keyCode==46) { //This is a delete event
    console.log('delete event');
    var tds = getSelectedTds();
    console.log('tds',tds);
    var changed=new Set();
    tds.forEach(td=>{
     assigntocell(sheet,td.x,td.y,sheet.z,"").forEach(cell=>changed.add(cell));
    });
    changed.forEach(cell=>{
     if(cell.td) {
      cell.td.innerText=stringify(cell.oc);
     }
    });
   }
  });
 }
 table.appendChild(thead);
 table.appendChild(tbody);
 var textarea=document.createElement('textarea');
 textarea.classList.add('cellinput');
 textarea.style.position='absolute';
 textarea.style.display='none';
 window.textarea=textarea;
 var shiftKey=false;
 textarea.onkeydown=(e)=>{
  console.log(e.keyCode);
  if(e.keyCode==16) { //Shift key
   shiftKey=true;
   e.preventDefault();
   return false;
  }
  if(e.keyCode==13) { //Return key
   textarea.blur();
   sheet[textarea.activetd.x+1][textarea.activetd.y][sheet.z].td.click();
   e.preventDefault();
   return false;
  }
  if(e.keyCode==9) { //Tab key
   textarea.blur();
   sheet[textarea.activetd.x][textarea.activetd.y+1][sheet.z].td.click();
   e.preventDefault();
   return false;
  }
  if(e.keyCode==38 && textarea.selectionEnd==0 && textarea.activetd.x!=0) { //Up key
   textarea.blur();
   sheet[textarea.activetd.x-1][textarea.activetd.y][sheet.z].td.click();
   e.preventDefault();
   return false;
  }
  if(e.keyCode==40 && textarea.selectionStart==textarea.value.length && textarea.activetd.x!=sheet.length-1) { //Down key
   textarea.blur();
   sheet[textarea.activetd.x+1][textarea.activetd.y][sheet.z].td.click();
   e.preventDefault();
   return false;
  }
  if(e.keyCode==37 && textarea.selectionEnd==0 && textarea.activetd.y!=0) { //Left key
   textarea.blur();
   sheet[textarea.activetd.x][textarea.activetd.y-1][sheet.z].td.click();
   e.preventDefault();
   return false;
  }
  if(e.keyCode==39 && textarea.selectionStart==textarea.value.length && textarea.activetd.y!=sheet[0].length-1) { //Right key
   textarea.blur();
   sheet[textarea.activetd.x][textarea.activetd.y+1][sheet.z].td.click();
   e.preventDefault();
   return false;
  }
 }
 textarea.onkeyup=(e)=>{
  if(e.keyCode==16) {
   shiftKey=false;
  }
 }
 textarea.onpaste=e=>{
  var paste = (e.clipboardData || window.clipboardData).getData('text');
  console.log('paste',paste);
  console.log('recentContentCopy',recentContentCopy);
  window.c1=paste;
  window.c2=recentContentCopy;
  if(recentContentCopy&&paste==recentContentCopy.split('\r').join('')) {
   console.log('From here','Copy UC');
   var x=textarea.activetd.x;
   var y=textarea.activetd.y;
   var deltas=[x-recentUCCopy[1],y-recentUCCopy[2],sheet.z-recentUCCopy[3]];
   var regexp = /c(\$?[0-9]+):(\$?[0-9]+):(\$?[0-9]+)/g;
   recentUCCopy[0].forEach(i=>{
    console.log(i);
    var uc=i[3];
    var matchAddresses = uc.match(regexp)||[];
    console.log('matchAddresses',matchAddresses);
    matchAddresses.forEach(sample=>{
     var [ax,ay,az]=sample.slice(1).split(':').map((val,index)=>{
      if(val[0]=='$') return val;
      return parseInt(val)+deltas[index];
     });
     uc=uc.replace(sample,`c${ax}:${ay}:${az}`);
    });
    if(i[0]==0&&i[1]==0) textarea.value=uc;
    while(x+i[0]>=sheet.length) {
     addrow.click();
    }
    while(y+i[1]>=sheet[0].length) {
     addcolumn.click();
    }
    assigntocell(sheet,x+i[0],y+i[1],sheet.z,uc);
    sheet[x+i[0]][y+i[1]][sheet.z].td.innerText=stringify(sheet[x+i[0]][y+i[1]][sheet.z].oc);
   });
   e.preventDefault();
  }
  else {
   //Copy from clipboard
   var x=textarea.activetd.x;
   var y=textarea.activetd.y;
   paste.split('\n').forEach((line,xo)=>{
    line.split('\t').forEach((uc,yo)=>{
     if(xo==0&&yo==0) textarea.value=uc;
     while(x+xo>=sheet.length) {
      addrow.click();
     }
     while(y+yo>=sheet[0].length) {
      addcolumn.click();
     }
     assigntocell(sheet,x+xo,y+yo,sheet.z,uc);
     sheet[x+xo][y+yo][sheet.z].td.innerText=stringify(sheet[x+xo][y+yo][sheet.z].oc);
    });
   });
   e.preventDefault();
  }
 }
 textarea.onblur=(e)=>{
  console.log('blur',e);
  if(shiftKey || (e.relatedTarget&&e.relatedTarget.noblur==true)) {
    e.preventDefault();
    textarea.focus();
    console.log('shiftBlur');
    return false;
  }
  textarea.style.display='none';
  var targ= textarea.activetd;
  var modified = assigntocell(sheet,targ.x,targ.y,sheet.z,textarea.value);
  textarea.value="";
  renderaltered(modified);
  targ.innerText=stringify(sheet[targ.x][targ.y][sheet.z].oc);
  console.log(sheet[targ.x][targ.y][sheet.z].oc);
 }
 var tbodyonmousemove=(e)=>{
  if(!window.chrome) return;  //Already does what it should
  if(!e.ctrlKey) return; //Ctrl key isn't down
  if(e.buttons!=1) return; //Our mouse isn't down
  var starttd=e.target;
  var endtd=starttd;
  tbody.onmousemove=null; //Set it to nothing while we are completing the rest of the action
  tbody.onmousemove=(e)=>{
   endtd=e.target;
   document.querySelectorAll('.tmpselectedtd').forEach(e=>e.classList.remove('tmpselectedtd'));
   var startx=Math.min(starttd.x,endtd.x);
   var endx=Math.max(starttd.x,endtd.x);
   var starty=Math.min(starttd.y,endtd.y);
   var endy=Math.max(starttd.y,endtd.y);
   for(var x=startx;x<=endx;++x) {
    for(var y=starty;y<=endy;++y) {
     sheet[x][y][sheet.z].td.classList.add('tmpselectedtd');
    }
   }
  };
  tbody.onmouseup=(e)=>{ //Action is ending
   tbody.onmousemove=tbodyonmousemove;
   tbody.onmouseup=null;
   console.log({starttd,endtd});
   document.querySelectorAll('.tmpselectedtd').forEach(e=>e.classList.remove('tmpselectedtd'));
   var startx=Math.min(starttd.x,endtd.x);
   var endx=Math.max(starttd.x,endtd.x);
   var starty=Math.min(starttd.y,endtd.y);
   var endy=Math.max(starttd.y,endtd.y);
   for(var x=startx;x<=endx;++x) {
    for(var y=starty;y<=endy;++y) {
     sheet[x][y][sheet.z].td.classList.add('selectedtd');
    }
   }
  }
 };
 tbody.onmousemove=tbodyonmousemove;
 var onclick=(e)=>{
  var targ=e.target;
  if(e.shiftKey) {
   e.preventDefault();
   e.stopPropagation();
   console.log('shiftKey');
   var insertIndex=textarea.selectionStart;
   var insertText=`c${targ.x}:${targ.y}:${targ.z}`
   textarea.value=textarea.value.slice(0,insertIndex)+insertText+textarea.value.slice(insertIndex);
   textarea.focus();
   textarea.selectionEnd=insertIndex+insertText.length;
   return false;
  }
  if(e.ctrlKey) {
   console.log("Ctrl-click",e);
   if(!window.chrome) return; //This is probably firefox, which already does what it should.  Do nothing more.
   targ.classList.toggle('selectedtd');
   return;
  }
  textarea.value=sheet[targ.x][targ.y][sheet.z].uc;
  var offset=absoluteOffset(targ);
  textarea.style.width=targ.offsetWidth+'px';
  textarea.style.height=targ.offsetHeight+'px';
  textarea.style.left=offset[0]+'px';
  textarea.style.top=offset[1]+'px';
  textarea.style.display='inline';
  textarea.focus();
  textarea.activetd=targ;
  if(window.chrome) {
   document.querySelectorAll('.selectedtd').forEach(e=>e.classList.remove('selectedtd'));
  }
 }
 for(var x=0;x<sheet.length;++x) {
  var tr=document.createElement('tr');
  var htd=document.createElement('td');
  htd.classList.add('rhead');
  htd.innerText=x;
  tr.append(htd);
  tbody.appendChild(tr);
  for(var y=0;y<sheet[x].length;++y) {
   var td=document.createElement('td');
   td.innerText=stringify(sheet[x][y][sheet.z].oc);
   tr.appendChild(td);
   sheet[x][y][sheet.z].td=td;
   td.x=x;
   td.y=y;
   td.z=sheet.z
   td.dataset.x=x;
   td.dataset.y=y;
   td.dataset.z=sheet.z;
   td.onclick=onclick;
   td.ondrag=ondrag;
   /*input.onblur=(e)=>{
    console.log('onblur')
    var targ= e.target
    var modified = assigntocell(sheet,targ.x,targ.y,0,targ.value);
    modified.forEach(cell=>cell.input.value=stringify(cell.oc));
    targ.value=stringify(sheet[targ.x][targ.y][0].oc);
    console.log(sheet[targ.x][targ.y][0].oc);
   };*/
  }
 }
 var tr=document.createElement('tr');
 var addrow = document.createElement('td');
 var changez = document.createElement('td');
 changez.innerText=' z: ';
 var changezinput = document.createElement('input');
 changezinput.id="changez";
 changezinput.value=currentz;
 changez.append(changezinput);
 addrow.innerText='+';
 addrow.classList.add('adder');
 addrow.classList.add('rhead');
 if(window.chrome) {
  changez.addEventListener('click',e=>{
   e.preventDefault();
   getSelectedTds().forEach(t=>t.classList.remove('selectedtd'));
  });
 }
 changezinput.addEventListener('change',()=>{
  var value=parseInt(changezinput.value);
  if(isNaN(value) || value==currentz) return;
  //We are about to rerender the document;
  var oldz=currentz;
  currentz=value;
  sheet.z=value;
  for(var x=0;x<sheet.length;++x) { //Ensure sheet is expanded
   for(var y=0;y<sheet[x].length;++y) {
    if(!sheet[x][y][currentz]) {
     sheet[x][y][currentz]={uc:'',oc:null,up:new Set(),down:new Set(),location:{x,y,z:currentz,a:`${x}:${y}:${currentz}`}};
    }
   }
  }
  for(var x=0;x<sheet.length;++x) { //Adjust tds content to reflect content of the new z
   for(var y=0;y<sheet[x].length;++y) {
    var td=sheet[x][y][oldz].td;
    td.z=currentz;
    td.dataset.z=currentz;
    td.innerText=stringify(sheet[x][y][currentz].oc);
   }
  }
  for(var x=0;x<sheet.length;++x) { //Assign and unassign tds
   for(var y=0;y<sheet[x].length;++y) {
    var td=sheet[x][y][oldz].td;
    sheet[x][y][currentz].td=td;
    delete sheet[x][y][oldz].td;
   }
  }
 });
 addrow.addEventListener('click',()=>{
  var cx=sheet.length;
  var cy=sheet[0].length;
  var cz=sheet[0][0].length;
  var row=[];
  sheet.push(row);
  for(var y=0;y<cy;++y) {
   var log=[];
   row.push(log);
   for(var z=0;z<cz;++z) {
    log.push({uc:"",oc:null,up:new Set(),down:new Set(),location:{x,y,z,a:`${x}:${y}:${z}`}});
   }
  }
  var tr=document.createElement('tr');
  var rhead=document.createElement('td');
  rhead.innerText=cx;
  rhead.classList.add('rhead');
  tr.append(rhead);
  for(var y=0;y<cy;++y) {
   var td=document.createElement('td');
   td.innerText=stringify(sheet[cx][y][sheet.z].oc);
   tr.appendChild(td);
   sheet[cx][y][sheet.z].td=td;
   td.x=cx;
   td.y=y;
   td.z=sheet.z;
   td.dataset.x=cx;
   td.dataset.y=y;
   td.dataset.z=sheet.z;
   td.onclick=onclick;
  }
  tbody.insertBefore(tr,tbody.lastElementChild);
 });
 addcolumn.addEventListener('click',()=>{
  var cx=sheet.length;
  var cy=sheet[0].length;
  var cz=sheet[0][0].length;
  sheet.forEach((row,x)=>{
   var log=[];
   row.push(log);
   for(var z=0;z<cz;++z) {
    log.push({uc:"",oc:null,up:new Set(),down:new Set(),location:{x,y:cy,z,a:`${x}:${cy}:${z}`}})
   }
  });
  var headtd=document.createElement('td');
  headtd.innerText=cy;
  theadtr.insertBefore(headtd,theadtr.lastElementChild);
  sheet.forEach((row,x)=>{
   var tr=sheet[x][0][sheet.z].td.parentElement;
   var td=document.createElement('td');
   td.innerText=stringify(sheet[x][cy][sheet.z].oc);
   tr.appendChild(td);
   sheet[x][cy][sheet.z].td=td;
   td.x=x;
   td.y=cy;
   td.z=sheet.z;
   td.dataset.x=x;
   td.dataset.y=cy;
   td.dataset.z=sheet.z;
   td.onclick=onclick;
  });
 });
 tr.append(addrow);
 tr.append(changez);
 tbody.append(tr);
 table.appendChild(textarea);
 const INSERT=0;
 const NORMAL=1;
 var mode=INSERT;
 var normalbuffer=[];
 var normalcount='';
 window.onkeydown=e=>{
  if(e.keyCode==27) {
   normalbuffer=[];
   normalcount='';
   mode=NORMAL;
   return false;
  }
  if(e.keyCode==68 && e.ctrlKey) { //Fill down
   e.preventDefault();
   var altered=new Set();
   var tds = getSelectedTds();
   console.log('tds',tds);
   var tdset = new Set(tds);
   tds.forEach(td=>{
    var top=getTopSelectedAbove(td,tdset);
    if(top==td) return;
    console.log(top,td);
    var uc=sheet[top.x][top.y][sheet.z].uc;
    console.log('from uc',uc);
    uc=offsetuc(uc,[td.x-top.x,td.y-top.y,0]); //Those are deltas
    console.log('to uc',uc);
    assigntocell(sheet,td.x,td.y,sheet.z,uc).forEach(i=>altered.add(i));
   });
   renderaltered(altered);
  }
  if(mode==NORMAL) {
   if(e.keyCode==123) { //F12
    return true; //Don't prevent default
   }
   e.preventDefault();
   console.log('window',e);
   var altered=new Set();
   if(e.key==='0' && normalcount==='') { //Move to begining
    var targettd=sheet[textarea.activetd.x][0][sheet.z].td;
    if(targettd) targettd.click();
    normalbuffer=[];
    normalcount='';
   }
   else if(e.keyCode>=48 && e.keyCode<=57) { //Adjust normal count
    normalcount+=e.key;
    console.log('normalcount',normalcount);
   }
   else { //Wasn't a number
    normalbuffer.push(e.key);
   }
   console.log(parseInt(normalcount)||1,normalbuffer.join(''));
   if(e.key=='i'){
    normalbuffer=[];
    normalcount='';
    mode=INSERT;
   }
   if(e.key=='I') {
    var targettd=sheet[textarea.activetd.x][0][sheet.z].td;
    if(targettd) {
     var wasshiftkey=shiftKey;
     shiftKey=false; //Essencially we are not shift-clicking.
     textarea.blur();
     targettd.click();
     shiftKey=wasshiftkey;
    }
    normalbuffer=[];
    normalcount='';
    mode=INSERT;
   }
   if(normalbuffer.join('')=='dd') {
    normalcount=parseInt(normalcount)||1;
    var x=textarea.activetd.x;
    textarea.value='';
    for(var c=0;c<normalcount;++c) {
     for(var y=0;y<sheet[x].length;++y) {
      assigntocell(sheet,x,y,sheet.z,'').forEach(alt=>altered.add(alt));
     }
     ++x;
     if(x>=sheet.length) {
      break;
     }
    }
    normalbuffer=[];
    normalcount='';
   }
   if(e.key=='x') {
    normalcount=parseInt(normalcount)||1;
    var x=textarea.activetd.x;
    var y=textarea.activetd.y;
    console.log('normalcount',normalcount);
    textarea.value='';
    for(var c=0;c<normalcount;++c) {
     assigntocell(sheet,x,y,sheet.z,'').forEach(alt=>altered.add(alt));
     ++y;
     if(y>=sheet[0].length) {
      y=0;
      ++x;
      if(x>=sheet.length) {
       break;
      }
     }
    }
    normalbuffer=[];
    normalcount='';
   }
   if(e.key=='G') {
    var wasshiftkey=shiftKey;
    shiftKey=false; //Essencially we are not shift-clicking.
    var target=Math.min(parseInt(normalcount)||Infinity,sheet.length-1);
    sheet[target][0][sheet.z].td.click();
    normalbuffer=[];
    normalcount='';
    shiftKey=wasshiftkey;
   }
   if(normalbuffer.join('')=='gg') {
    var target=Math.max(parseInt(normalcount)||0,0);
    textarea.blur();
    sheet[target][0][sheet.z].td.click();
    normalbuffer=[];
    normalcount='';
   }
   renderaltered(altered);
  }
 };
 return table;
}



function convertarrastringto3dspread(data) {
 data = JSON.parse(data);
 var [x,y,z]=data;
 data = data.slice(3);
 var sheet = createnewsheet(x,y,z);
 for(var xi=0;xi<x;++xi) {
  for(var yi=0;yi<y;++yi) {
   for(var zi=0;zi<z;++zi) {
    var uc=data.shift();
    console.log('uc',uc);
    if(uc) {
     assigntocell(sheet,xi,yi,zi,uc);
    }
   }
  }
 }
 return sheet;
}

function convert3dspreadtoucarraystring(sheet) {
 var retr=[];
 retr.push(sheet.length);
 retr.push(sheet[0].length);
 retr.push(sheet[0][0].length);
 sheet.forEach(r1=>r1.forEach(r2=>r2.forEach(c=>retr.push(c.uc))));
 return stringify(retr);
}

function removeonclick(elem) {
 elem.addEventListener('click',e=>{
  if(e.target==elem) {
   console.log(e);
   elem.remove();
  }
 });
}

function reinitializewithdata(data) {
 document.body.childNodes.forEach((i,e)=>i.remove());
 document.body.appendChild(driver(convertarrastringto3dspread(data)));
}

function pushdownload(text,filename) {
 var blob = new Blob([text], {type: "text/plain"});
 var url = window.URL.createObjectURL(blob);
 var a = document.createElement("a");
 a.href = url;
 a.download = filename;
 a.click();
}

function addcolab(i,cb) {
 getcolablist((err,list)=>{
  if(list.indexOf(i)!=-1) list=list.filter(a=>a!=i);
  list.unshift(i);
  writecolablist(list,cb);
 });
}

function removecolab(i,cb) {
 getcolablist((err,list)=>{
  list = list.filter(a=>a!=i);
  writecolablist(list,cb);
 });
}

function removedocument(i,cb) {
 getdocumentlist((err,list)=>{
  list = list.filter(a=>a!=i);
  writedocumentlist(list,cb);
 });
}

function getcolablist(cb) {
 var xmlr = new XMLHttpRequest();
 xmlr.onreadystatechange=()=>{
  if(xmlr.readyState=== XMLHttpRequest.DONE) {
   console.log('ready');
   if(!xmlr.responseText) {
    return cb(null,[]);
   }
   try {
    cb(null,JSON.parse(xmlr.responseText));
   }
   catch(e) {
    cb(e);
   }
  }
 };
 xmlr.open('GET','/skey/get/3dspread3/colabs',true);
 xmlr.send();
}

function getdocumentlist(cb) {
 var xmlr = new XMLHttpRequest();
 xmlr.onreadystatechange=()=>{
  if(xmlr.readyState=== XMLHttpRequest.DONE) {
   console.log('ready');
   if(!xmlr.responseText) {
    return cb(null,[]);
   }
   try {
    cb(null,JSON.parse(xmlr.responseText));
   }
   catch(e) {
    cb(e);
   }
  }
 };
 xmlr.open('GET','/skey/get/3dspread3/documents',true);
 xmlr.send();
}

function writecolablist(list,cb) {
 var xmlr = new XMLHttpRequest();
 xmlr.onreadystatechange=()=>{
  if(xmlr.readyState=== XMLHttpRequest.DONE && cb) {
   cb();
  }
 };
 xmlr.open('POST','/skey/set/3dspread3/colabs',true);
 xmlr.send(JSON.stringify(list));
}

function writedocumentlist(list,cb) {
 var xmlr = new XMLHttpRequest();
 xmlr.onreadystatechange=()=>{
  if(xmlr.readyState=== XMLHttpRequest.DONE) {
   cb();
  }
 };
 xmlr.open('POST','/skey/set/3dspread3/documents',true);
 xmlr.send(JSON.stringify(list));
}

function savedocumentsub(name,history,cb) {
 var xmlr = new XMLHttpRequest();
 xmlr.onreadystatechange=()=>{
  if(xmlr.readyState=== XMLHttpRequest.DONE) {
   cb();
  }
 };
 xmlr.open('POST','/skey/set/3dspread3/documents/'+name,true);
 xmlr.send(JSON.stringify(history));
}
function savedocument(name,history,cb) {
 parallel([
  cb=>savedocumentsub(name,history,cb),
  cb=>getdocumentlist((err,list)=>{
   if(err) return cb(err);
   console.log('document list',list);
   list = list.filter(i=>i!=name);
   list.unshift(name);
   writedocumentlist(list,cb);
  })
 ],cb);
}

function loaddocument(name,cb) {
 var xmlr = new XMLHttpRequest();
 xmlr.onreadystatechange=()=>{
  if(xmlr.readyState=== XMLHttpRequest.DONE) {
   cb(null,JSON.parse(xmlr.responseText));
  }
 };
 xmlr.open('POST','/skey/get/3dspread3/documents/'+name,true);
 xmlr.send();
}

function createsortline(lowindex,highindex,parentelem,sortbutton) {
 var nextcreated=false;
 var retr = document.createElement('div');
 var select = document.createElement('select');
 var nulloption = document.createElement('option');
 select.append(nulloption);
 for(var c=lowindex;c<=highindex;++c) {
  var option = document.createElement('option');
  option.value=c;
  option.innerText=c;
  select.append(option);
 }
 var mapinput = document.createElement('input');
 mapinput.placeholder='(optional) Map function';
 mapinput.style.display='none';
 var compareinput = document.createElement('input');
 compareinput.placeholder='(optional) Compare function';
 compareinput.style.display='none';
 var remove=document.createElement('span');
 remove.innerText='x';
 remove.style.display='none';
 remove.style.color='red';
 remove.style.cursor='pointer';
 retr.append(select);
 retr.append(mapinput);
 retr.append(compareinput);
 retr.append(remove);
 parentelem.append(retr);
 select.addEventListener('change',()=>{
  if(select.value.length) {
   //Show map and compare function inputs
   mapinput.style.color='black';
   compareinput.style.color='black';
   mapinput.style.display='inline';
   compareinput.style.display='inline';
   remove.style.display='inline';
   if(sortbutton) sortbutton.style.display='inline';
   if(!nextcreated) {
    nextcreated=true;
    var next=createsortline(lowindex,highindex,parentelem);
   }
  }
  else {
   //Gray map and compare function inputs
   mapinput.style.color='gray';
   compareinput.style.color='gray';
  }
 });
 remove.addEventListener('click',()=>retr.remove());
}



function stdcompare(a,b) {
 if(a>b) {
  return 1;
 }
 else if(b<a) {
  return -1;
 }
 else {
  return 0;
 }
}

function dimforhistory(history) {
 var maxX=19;
 var maxY=9;
 var maxZ=0;
 history.forEach(i=>{
  var [x,y,z,...uc]=i.split(':').slice(0,3).map(c=>parseInt(c));
  console.log(x,y,z);
  maxX=Math.max(x,maxX);
  maxY=Math.max(y,maxY);
  maxZ=Math.max(z,maxZ);
 });
 return [maxX+1,maxY+1,maxZ+1];
}

function sheetfromsuburl(suburl,cb) {
 var [cmd,...location]=suburl.split('/');
 location=location.join('/');
 if(cmd=='doc') {
  loaddocument(location,(err,history)=>{
   var [x,y,z]=dimforhistory(history);
   sheet = createnewsheet(x,y,z);
   applyhistorytosheet(sheet,history);
   cb(null,sheet);
  });
 }
}

function buildwithinitialsheet(sheet) {
  document.body.childNodes.forEach((i,e)=>i.remove());
  var tabledriver = driver(sheet);
  var ctrls=controls(sheet,tabledriver);
  document.body.appendChild(ctrls);
  document.body.appendChild(tabledriver);
  document.body.appendChild(ctrls.saveloadbox);
}
function mean(array) {
 return sum(array)/array.length;
}

function variance(array) {
 var m=mean(array);
 var variance=mean(array.map(a=>(m-a)*(m-a)));
 return variance;
}

function softmaxinterpolate(domainSamples, rangeSamples) {
  return function(domain) {
    const presoftmaxweights = domainSamples.map(i => -Math.abs(i - domain));
    const unscaledsoftmax = presoftmaxweights.map(i => Math.exp(i));
    const softmaxsum = unscaledsoftmax.reduce((a, b) => a + b, 0);
    const softmaxweights = unscaledsoftmax.map(i => i / softmaxsum);
    const scaledranges = rangeSamples.map((r, idx) => r * softmaxweights[idx]);
    return scaledranges.reduce((a, b) => a + b, 0);
  };
}

function randint(min, max) {
 if(max===undefined) return randint(0,min);
 return Math.floor(Math.random() * (max - min + 1)) + min;
}

function main() {
 var suburl=appsubchannel();
 window.suburl=suburl;
 document.title='3dspread3';
 add_style(get3dspreadCSS());
 if(suburl) {
  return sheetfromsuburl(suburl,(err,sheet)=>buildwithinitialsheet(sheet));
 }
 window.addEventListener('load',()=>{
  buildwithinitialsheet(createnewsheet(20,10,1));
 });
}

Init

main();

Build

3dspread3