diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..76add87
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+dist
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..928edc6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,5 @@
+## SubaTimer
+
+**SubaTimer** is a little Windows utility that makes easy bla bla bla.
+
+
diff --git a/app-screenshot.png b/app-screenshot.png
new file mode 100644
index 0000000..5e9f633
Binary files /dev/null and b/app-screenshot.png differ
diff --git a/config.json b/config.json
new file mode 100644
index 0000000..b05837a
--- /dev/null
+++ b/config.json
@@ -0,0 +1,7 @@
+{
+ "dest": "dist/installers/",
+ "icon": "resources/Icon.ico",
+ "tags": [
+ "Utility"
+ ]
+}
\ No newline at end of file
diff --git a/fonts/InputMonoNarrowBold.woff b/fonts/InputMonoNarrowBold.woff
new file mode 100644
index 0000000..0c60715
Binary files /dev/null and b/fonts/InputMonoNarrowBold.woff differ
diff --git a/fonts/InputMonoNarrowBold.woff2 b/fonts/InputMonoNarrowBold.woff2
new file mode 100644
index 0000000..94b4fda
Binary files /dev/null and b/fonts/InputMonoNarrowBold.woff2 differ
diff --git a/fonts/InputMonoNarrowRegular.woff b/fonts/InputMonoNarrowRegular.woff
new file mode 100644
index 0000000..acb3d6b
Binary files /dev/null and b/fonts/InputMonoNarrowRegular.woff differ
diff --git a/fonts/InputMonoNarrowRegular.woff2 b/fonts/InputMonoNarrowRegular.woff2
new file mode 100644
index 0000000..c4d3fdb
Binary files /dev/null and b/fonts/InputMonoNarrowRegular.woff2 differ
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..817c4cf
--- /dev/null
+++ b/index.html
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+ SubaTimer v2.0
+
+
+
+
+
Settings
+
+
+
+
Timer
+
+ {{notice}}
+
+
+
+
+
+
Update Time
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/main.js b/main.js
new file mode 100644
index 0000000..3c2b21b
--- /dev/null
+++ b/main.js
@@ -0,0 +1,51 @@
+const { app, BrowserWindow } = require('electron')
+const path = require('path')
+
+if (require('electron-squirrel-startup')) app.quit()
+
+function createWindow() {
+ const gotTheLock = app.requestSingleInstanceLock()
+
+ if (!gotTheLock) {
+ app.quit()
+ } else {
+ app.on('second-instance', (event, commandLine, workingDirectory) => {
+ // Someone tried to run a second instance, we should focus our window.
+ if (win) {
+ if (win.isMinimized()) win.restore()
+ win.focus()
+ }
+ })
+ }
+ const win = new BrowserWindow({
+ width: 800,
+ height: 600,
+ icon: 'resources/Icon.ico',
+
+ webPreferences: {
+ // devTools: true,
+ preload: path.join(__dirname, 'preload.js'),
+ nodeIntegration: true,
+ contextIsolation: false
+ }
+ })
+
+ win.loadFile('index.html')
+ win.setMenu(null)
+}
+
+app.whenReady().then(() => {
+ createWindow()
+
+ app.on('activate', () => {
+ if (BrowserWindow.getAllWindows().length === 0) {
+ createWindow()
+ }
+ })
+})
+
+app.on('window-all-closed', () => {
+ if (process.platform !== 'darwin') {
+ app.quit()
+ }
+})
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..8ba7123
--- /dev/null
+++ b/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "SubaTimer",
+ "version": "2.0.0",
+ "main": "main.js",
+ "author": "Goofer",
+ "license": "MIT",
+ "description": "A timer app for Subathons",
+ "scripts": {
+ "start": "electron .",
+ "build": "npx electron-packager . SubaTimer --platform win32 --arch x64 --out dist/ --icon=./resources/Icon.ico",
+ "installer": "electron-installer-windows --src dist/SubaTimer-win32-x64/ --config config.json"
+ },
+ "devDependencies": {
+ "electron": "^19.0.8",
+ "electron-packager": "^15.5.1"
+ },
+ "dependencies": {
+ "electron-installer-windows": "^3.0.0",
+ "electron-squirrel-startup": "^1.0.0"
+ }
+}
diff --git a/preload.js b/preload.js
new file mode 100644
index 0000000..00862f0
--- /dev/null
+++ b/preload.js
@@ -0,0 +1,10 @@
+window.addEventListener('DOMContentLoaded', () => {
+ const replaceText = (selector, text) => {
+ const element = document.getElementById(selector)
+ if (element) element.innerText = text
+ }
+
+ for (const type of ['chrome', 'node', 'electron']) {
+ replaceText(`${type}-version`, process.versions[type])
+ }
+})
diff --git a/resources/Icon.ico b/resources/Icon.ico
new file mode 100644
index 0000000..143bb2e
Binary files /dev/null and b/resources/Icon.ico differ
diff --git a/scripts/moment.min.js b/scripts/moment.min.js
new file mode 100644
index 0000000..57cd2d4
--- /dev/null
+++ b/scripts/moment.min.js
@@ -0,0 +1,2 @@
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.moment=t()}(this,function(){"use strict";var e,i;function f(){return e.apply(null,arguments)}function o(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function u(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function m(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function l(e){if(Object.getOwnPropertyNames)return 0===Object.getOwnPropertyNames(e).length;for(var t in e)if(m(e,t))return;return 1}function r(e){return void 0===e}function h(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function a(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function d(e,t){for(var n=[],s=0;s>>0,s=0;sFe(e)?(r=e+1,a-Fe(e)):(r=e,a);return{year:r,dayOfYear:o}}function Ae(e,t,n){var s,i,r=Ge(e.year(),t,n),a=Math.floor((e.dayOfYear()-r-1)/7)+1;return a<1?s=a+je(i=e.year()-1,t,n):a>je(e.year(),t,n)?(s=a-je(e.year(),t,n),i=e.year()+1):(i=e.year(),s=a),{week:s,year:i}}function je(e,t,n){var s=Ge(e,t,n),i=Ge(e+1,t,n);return(Fe(e)-s+i)/7}C("w",["ww",2],"wo","week"),C("W",["WW",2],"Wo","isoWeek"),L("week","w"),L("isoWeek","W"),A("week",5),A("isoWeek",5),ce("w",te),ce("ww",te,Q),ce("W",te),ce("WW",te,Q),ge(["w","ww","W","WW"],function(e,t,n,s){t[s.substr(0,1)]=Z(e)});function Ie(e,t){return e.slice(t,7).concat(e.slice(0,t))}C("d",0,"do","day"),C("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),C("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),C("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),C("e",0,0,"weekday"),C("E",0,0,"isoWeekday"),L("day","d"),L("weekday","e"),L("isoWeekday","E"),A("day",11),A("weekday",11),A("isoWeekday",11),ce("d",te),ce("e",te),ce("E",te),ce("dd",function(e,t){return t.weekdaysMinRegex(e)}),ce("ddd",function(e,t){return t.weekdaysShortRegex(e)}),ce("dddd",function(e,t){return t.weekdaysRegex(e)}),ge(["dd","ddd","dddd"],function(e,t,n,s){var i=n._locale.weekdaysParse(e,s,n._strict);null!=i?t.d=i:y(n).invalidWeekday=e}),ge(["d","e","E"],function(e,t,n,s){t[s]=Z(e)});var Ze="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),ze="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),$e="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),qe=de,Be=de,Je=de;function Qe(){function e(e,t){return t.length-e.length}for(var t,n,s,i,r=[],a=[],o=[],u=[],l=0;l<7;l++)t=_([2e3,1]).day(l),n=me(this.weekdaysMin(t,"")),s=me(this.weekdaysShort(t,"")),i=me(this.weekdays(t,"")),r.push(n),a.push(s),o.push(i),u.push(n),u.push(s),u.push(i);r.sort(e),a.sort(e),o.sort(e),u.sort(e),this._weekdaysRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+o.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+a.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+r.join("|")+")","i")}function Xe(){return this.hours()%12||12}function Ke(e,t){C(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function et(e,t){return t._meridiemParse}C("H",["HH",2],0,"hour"),C("h",["hh",2],0,Xe),C("k",["kk",2],0,function(){return this.hours()||24}),C("hmm",0,0,function(){return""+Xe.apply(this)+T(this.minutes(),2)}),C("hmmss",0,0,function(){return""+Xe.apply(this)+T(this.minutes(),2)+T(this.seconds(),2)}),C("Hmm",0,0,function(){return""+this.hours()+T(this.minutes(),2)}),C("Hmmss",0,0,function(){return""+this.hours()+T(this.minutes(),2)+T(this.seconds(),2)}),Ke("a",!0),Ke("A",!1),L("hour","h"),A("hour",13),ce("a",et),ce("A",et),ce("H",te),ce("h",te),ce("k",te),ce("HH",te,Q),ce("hh",te,Q),ce("kk",te,Q),ce("hmm",ne),ce("hmmss",se),ce("Hmm",ne),ce("Hmmss",se),ye(["H","HH"],Me),ye(["k","kk"],function(e,t,n){var s=Z(e);t[Me]=24===s?0:s}),ye(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e}),ye(["h","hh"],function(e,t,n){t[Me]=Z(e),y(n).bigHour=!0}),ye("hmm",function(e,t,n){var s=e.length-2;t[Me]=Z(e.substr(0,s)),t[De]=Z(e.substr(s)),y(n).bigHour=!0}),ye("hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[Me]=Z(e.substr(0,s)),t[De]=Z(e.substr(s,2)),t[Se]=Z(e.substr(i)),y(n).bigHour=!0}),ye("Hmm",function(e,t,n){var s=e.length-2;t[Me]=Z(e.substr(0,s)),t[De]=Z(e.substr(s))}),ye("Hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[Me]=Z(e.substr(0,s)),t[De]=Z(e.substr(s,2)),t[Se]=Z(e.substr(i))});var tt=z("Hours",!0);var nt,st={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",w:"a week",ww:"%d weeks",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Te,monthsShort:Ne,week:{dow:0,doy:6},weekdays:Ze,weekdaysMin:$e,weekdaysShort:ze,meridiemParse:/[ap]\.?m?\.?/i},it={},rt={};function at(e){return e?e.toLowerCase().replace("_","-"):e}function ot(e){for(var t,n,s,i,r=0;r=t&&function(e,t){for(var n=Math.min(e.length,t.length),s=0;s=t-1)break;t--}r++}return nt}function ut(t){var e;if(void 0===it[t]&&"undefined"!=typeof module&&module&&module.exports)try{e=nt._abbr,require("./locale/"+t),lt(e)}catch(e){it[t]=null}return it[t]}function lt(e,t){var n;return e&&((n=r(t)?dt(e):ht(e,t))?nt=n:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+e+" not found. Did you forget to load it?")),nt._abbr}function ht(e,t){if(null===t)return delete it[e],null;var n,s=st;if(t.abbr=e,null!=it[e])Y("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),s=it[e]._config;else if(null!=t.parentLocale)if(null!=it[t.parentLocale])s=it[t.parentLocale]._config;else{if(null==(n=ut(t.parentLocale)))return rt[t.parentLocale]||(rt[t.parentLocale]=[]),rt[t.parentLocale].push({name:e,config:t}),null;s=n._config}return it[e]=new x(b(s,t)),rt[e]&&rt[e].forEach(function(e){ht(e.name,e.config)}),lt(e),it[e]}function dt(e){var t;if(e&&e._locale&&e._locale._abbr&&(e=e._locale._abbr),!e)return nt;if(!o(e)){if(t=ut(e))return t;e=[e]}return ot(e)}function ct(e){var t,n=e._a;return n&&-2===y(e).overflow&&(t=n[ve]<0||11xe(n[pe],n[ve])?ke:n[Me]<0||24je(n,r,a)?y(e)._overflowWeeks=!0:null!=u?y(e)._overflowWeekday=!0:(o=Ee(n,s,i,r,a),e._a[pe]=o.year,e._dayOfYear=o.dayOfYear)}(e),null!=e._dayOfYear&&(r=St(e._a[pe],s[pe]),(e._dayOfYear>Fe(r)||0===e._dayOfYear)&&(y(e)._overflowDayOfYear=!0),n=Ve(r,0,e._dayOfYear),e._a[ve]=n.getUTCMonth(),e._a[ke]=n.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=u[t]=s[t];for(;t<7;t++)e._a[t]=u[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[Me]&&0===e._a[De]&&0===e._a[Se]&&0===e._a[Ye]&&(e._nextDay=!0,e._a[Me]=0),e._d=(e._useUTC?Ve:function(e,t,n,s,i,r,a){var o;return e<100&&0<=e?(o=new Date(e+400,t,n,s,i,r,a),isFinite(o.getFullYear())&&o.setFullYear(e)):o=new Date(e,t,n,s,i,r,a),o}).apply(null,u),i=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[Me]=24),e._w&&void 0!==e._w.d&&e._w.d!==i&&(y(e).weekdayMismatch=!0)}}function Ot(e){if(e._f!==f.ISO_8601)if(e._f!==f.RFC_2822){e._a=[],y(e).empty=!0;for(var t,n,s,i,r,a,o,u=""+e._i,l=u.length,h=0,d=H(e._f,e._locale).match(N)||[],c=0;cn.valueOf():n.valueOf()"}),pn.toJSON=function(){return this.isValid()?this.toISOString():null},pn.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},pn.unix=function(){return Math.floor(this.valueOf()/1e3)},pn.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},pn.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},pn.eraName=function(){for(var e,t=this.localeData().eras(),n=0,s=t.length;nthis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},pn.isLocal=function(){return!!this.isValid()&&!this._isUTC},pn.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},pn.isUtc=At,pn.isUTC=At,pn.zoneAbbr=function(){return this._isUTC?"UTC":""},pn.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},pn.dates=n("dates accessor is deprecated. Use date instead.",fn),pn.months=n("months accessor is deprecated. Use month instead",Ue),pn.years=n("years accessor is deprecated. Use year instead",Le),pn.zone=n("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,t){return null!=e?("string"!=typeof e&&(e=-e),this.utcOffset(e,t),this):-this.utcOffset()}),pn.isDSTShifted=n("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!r(this._isDSTShifted))return this._isDSTShifted;var e,t={};return v(t,this),(t=bt(t))._a?(e=(t._isUTC?_:Tt)(t._a),this._isDSTShifted=this.isValid()&&0 !!map[val.toLowerCase()] : val => !!map[val];
+ }
+
+ /**
+ * dev only flag -> name mapping
+ */
+ const PatchFlagNames = {
+ [1 /* TEXT */]: `TEXT`,
+ [2 /* CLASS */]: `CLASS`,
+ [4 /* STYLE */]: `STYLE`,
+ [8 /* PROPS */]: `PROPS`,
+ [16 /* FULL_PROPS */]: `FULL_PROPS`,
+ [32 /* HYDRATE_EVENTS */]: `HYDRATE_EVENTS`,
+ [64 /* STABLE_FRAGMENT */]: `STABLE_FRAGMENT`,
+ [128 /* KEYED_FRAGMENT */]: `KEYED_FRAGMENT`,
+ [256 /* UNKEYED_FRAGMENT */]: `UNKEYED_FRAGMENT`,
+ [512 /* NEED_PATCH */]: `NEED_PATCH`,
+ [1024 /* DYNAMIC_SLOTS */]: `DYNAMIC_SLOTS`,
+ [2048 /* DEV_ROOT_FRAGMENT */]: `DEV_ROOT_FRAGMENT`,
+ [-1 /* HOISTED */]: `HOISTED`,
+ [-2 /* BAIL */]: `BAIL`
+ };
+
+ /**
+ * Dev only
+ */
+ const slotFlagsText = {
+ [1 /* STABLE */]: 'STABLE',
+ [2 /* DYNAMIC */]: 'DYNAMIC',
+ [3 /* FORWARDED */]: 'FORWARDED'
+ };
+
+ const GLOBALS_WHITE_LISTED = 'Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,' +
+ 'decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,' +
+ 'Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt';
+ const isGloballyWhitelisted = /*#__PURE__*/ makeMap(GLOBALS_WHITE_LISTED);
+
+ const range = 2;
+ function generateCodeFrame(source, start = 0, end = source.length) {
+ // Split the content into individual lines but capture the newline sequence
+ // that separated each line. This is important because the actual sequence is
+ // needed to properly take into account the full line length for offset
+ // comparison
+ let lines = source.split(/(\r?\n)/);
+ // Separate the lines and newline sequences into separate arrays for easier referencing
+ const newlineSequences = lines.filter((_, idx) => idx % 2 === 1);
+ lines = lines.filter((_, idx) => idx % 2 === 0);
+ let count = 0;
+ const res = [];
+ for (let i = 0; i < lines.length; i++) {
+ count +=
+ lines[i].length +
+ ((newlineSequences[i] && newlineSequences[i].length) || 0);
+ if (count >= start) {
+ for (let j = i - range; j <= i + range || end > count; j++) {
+ if (j < 0 || j >= lines.length)
+ continue;
+ const line = j + 1;
+ res.push(`${line}${' '.repeat(Math.max(3 - String(line).length, 0))}| ${lines[j]}`);
+ const lineLength = lines[j].length;
+ const newLineSeqLength = (newlineSequences[j] && newlineSequences[j].length) || 0;
+ if (j === i) {
+ // push underline
+ const pad = start - (count - (lineLength + newLineSeqLength));
+ const length = Math.max(1, end > count ? lineLength - pad : end - start);
+ res.push(` | ` + ' '.repeat(pad) + '^'.repeat(length));
+ }
+ else if (j > i) {
+ if (end > count) {
+ const length = Math.max(Math.min(end - count, lineLength), 1);
+ res.push(` | ` + '^'.repeat(length));
+ }
+ count += lineLength + newLineSeqLength;
+ }
+ }
+ break;
+ }
+ }
+ return res.join('\n');
+ }
+
+ /**
+ * On the client we only need to offer special cases for boolean attributes that
+ * have different names from their corresponding dom properties:
+ * - itemscope -> N/A
+ * - allowfullscreen -> allowFullscreen
+ * - formnovalidate -> formNoValidate
+ * - ismap -> isMap
+ * - nomodule -> noModule
+ * - novalidate -> noValidate
+ * - readonly -> readOnly
+ */
+ const specialBooleanAttrs = `itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly`;
+ const isSpecialBooleanAttr = /*#__PURE__*/ makeMap(specialBooleanAttrs);
+ /**
+ * Boolean attributes should be included if the value is truthy or ''.
+ * e.g. `