This commit is contained in:
2018-06-28 23:08:13 +00:00
commit 877637f1e5
69 changed files with 13521 additions and 0 deletions

46
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,46 @@
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceRoot}/bin/Debug/netcoreapp2.1/pecklist.dll",
"args": [],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"internalConsoleOptions": "openOnSessionStart",
"launchBrowser": {
"enabled": true,
"args": "${auto-detect-url}",
"windows": {
"command": "cmd.exe",
"args": "/C start http://localhost:3000/index.html"
},
"osx": {
"command": "open"
},
"linux": {
"command": "xdg-open"
}
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceRoot}/Views"
}
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickProcess}"
}
]
}

16
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,16 @@
{
"version": "0.1.0",
"command": "dotnet",
"isShellCommand": true,
"args": [],
"tasks": [
{
"taskName": "build",
"args": [
"${workspaceRoot}/pecklist.csproj"
],
"isBuildCommand": true,
"problemMatcher": "$msCompile"
}
]
}

View File

@@ -0,0 +1,115 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using GZTW.Pecklist.Models;
using GZTW.Pecklist.Util;
using System.Linq;
using System;
//required to inject configuration in constructor
using Microsoft.Extensions.Configuration;
namespace GZTW.Pecklist.Controllers
{
//Authentication controller
public class AuthController : Controller
{
private readonly PecklistContext _context;
private readonly IConfiguration _configuration;
public AuthController(PecklistContext context, IConfiguration configuration)//these two are injected, see startup.cs
{
_context = context;
//_configuration = configuration;
//guard clause
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
}
//AUTHENTICATE CREDS
//RETURN JWT
[HttpPost("/authenticate")]
public JsonResult PostCreds(string login, string password)
{
int nFailedAuthDelay = 10000;
if (string.IsNullOrWhiteSpace(login) || string.IsNullOrWhiteSpace(password))
{
//Make a failed pw wait
System.Threading.Thread.Sleep(nFailedAuthDelay);
return Json(new { msg = "authentication failed", error = 1 });
}
var user = _context.User.SingleOrDefault(m => m.Login == login);
if (user == null)
{
//Make a failed pw wait
System.Threading.Thread.Sleep(nFailedAuthDelay);
return Json(new { msg = "authentication failed", error = 1 });
}
//TODO: do a test login from postman, login as john, then copy pw into db
//string pwnewJohn=Hasher.hash(user.Salt,"XXX");
//then login as Joyce and copy pw into db
// string pwnewJoyce=Hasher.hash(user.Salt,"XXX");
string hashed = Hasher.hash(user.Salt, password);
if (hashed == user.Password)
{
//get teh secret from appsettings.json
var secret = _configuration.GetSection("JWT").GetValue<string>("secret");
byte[] secretKey = System.Text.Encoding.ASCII.GetBytes(secret);
var iat = new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds();
//NOTE: pecklist is appropriate for a really long expiry
var exp = new DateTimeOffset(DateTime.Now.AddDays(365)).ToUnixTimeSeconds();
//Generate a download token and store it with the user account and return it for the client
Guid g = Guid.NewGuid();
string dlkey = Convert.ToBase64String(g.ToByteArray());
dlkey = dlkey.Replace("=", "");
dlkey = dlkey.Replace("+", "");
user.DlKey = dlkey;
user.DlKeyExp = exp;
_context.User.Update(user);
_context.SaveChanges();
var payload = new Dictionary<string, object>()
{
{ "iat", iat.ToString() },
{ "exp", exp.ToString() },
{ "iss", "GZTW_Pecklist" },
{ "id", user.Id.ToString() }
};
//NOTE: probably don't need Jose.JWT as am using Microsoft jwt stuff to validate routes so it should also be able to
//issue tokens as well, but it looked cmplex and this works so unless need to remove in future keeping it.
string token = Jose.JWT.Encode(payload, secretKey, Jose.JwsAlgorithm.HS256);
//string jsonDecoded = Jose.JWT.Decode(token, secretKey);
return Json(new
{
ok = 1,
issued = iat,
expires = exp,
token = token,
dlkey = dlkey,
name = user.Name,
id = user.Id
});
}
else
{
//Make a failed pw wait
System.Threading.Thread.Sleep(nFailedAuthDelay);
return Json(new { msg = "authentication failed", error = 1 });
}
}
//------------------------------------------------------
}//eoc
}//eons

View File

@@ -0,0 +1,456 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore;
using GZTW.Pecklist.Models;
using GZTW.Pecklist.Util;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Microsoft.Extensions.Logging;
namespace GZTW.Pecklist.Controllers
{
[Produces("application/json")]//forces all responses to be json
[Route("api/sync")]
[Authorize]
public class SyncController : Controller
{
private readonly PecklistContext _ct;
private readonly ILogger _logger;
public SyncController(PecklistContext context, ILogger<SyncController> logger)
{
_ct = context;
//guard clause
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
// POST: api/sync
[HttpPost]
public ActionResult PostList([FromBody] JArray jclient)
{
//TODO: Might need a lock flag in db here just in case of duelling updates
//Get list from server
ListData l = _ct.ListData.First(c=>c.Id == 1);
//client has no data, send them the latest data
if (jclient == null)
{
return Ok(l.TheList);
}
JArray jserver = JArray.Parse(l.TheList);
JArray jnew = doSync(jclient, jserver);
l.TheList = jnew.ToString();
_ct.ListData.Update(l);
_ct.SaveChanges();
return Ok(l.TheList);
}
////////////////////////////////////////////////////////////////////////
// SYNCHRONIZE
////////////////////////////////////////////////////////////////////////
/*
SYNC STRATEGY
=-=-=-=-=-=-=
NEW ITEMS:
client record is iterated looking for id that starts with the string "NEW_". If any are found the master rev is incremented and new items
are added with a proper ID.
DELETED ITEMS:
Server record is itereated looking for deleted items (items with priority -1), if any are
found master rev is incremented (unless it was already) then they are removed from the master list.
UPDATE
Compare client record to master record, compare list name for a change, then find all items that have the same ID but different user editable values,
if any found update master rev if not done already, make master changed items match client changed items, update master.
RETURN:
Finally, if master list has changed, it is saved to db. Master list is then returned returned to client.
*/
/////////////////////////////////////////////
// Synchronize the two lists, return the new
//
private JArray doSync(JArray jclient, JArray jserver)
{
_logger.LogInformation(LoggingEvents.SyncList, "Synchronize list");
//Iterate the client's lists looking for changes
foreach (JObject l in jclient)
{
//track if master v is incremented (non zero)
long newListVersion = 0;
var listId = (string)l["id"];
//Is this an entirely new list?
if (listId.StartsWith("NEW_"))
{
//yes this is a new list, add it and go to the next
addNewList(l, jserver);
continue;
}
//bugbug: this will bomb if client syncs with a list that was deleted (which should be entirely ignored)
ListHeaderData oldListHeader = getListHeaderData(listId, jserver);
if (oldListHeader.v == -999)
{
//list isn't at the server, was deleted so go to next list and ignore it
continue;
}
//Has this list been deleted?
long newListV = (long)l["v"];
if (l["deleted"] != null && newListV == oldListHeader.v)
{
removeList(listId, jserver);
continue;
}
//Has the name changed?
string newListName = (string)l["name"];
long newListNameV = (long)l["name_v"];
//if names differ but name_v are the same then the client changed the name
if (newListName != oldListHeader.name && oldListHeader.name_v == newListNameV)
{
if (0 == newListVersion)
newListVersion = incrementListVersion(listId, jserver);
renameList(listId, newListName, jserver);
}
//Existing list, lets see if there are any changes required
//iterate this client list's items
foreach (JObject citem in l["items"])
{
string itemId = (string)citem["id"];
int itemPriority = (int)citem["priority"];
string itemText = (string)citem["text"];
bool itemCompleted = (bool)citem["completed"];
//New item and not deleted?
if (itemId.StartsWith("NEW_") && itemPriority != -1)
{
if (0 == newListVersion)
newListVersion = incrementListVersion(listId, jserver);
addNewListItem(listId, citem, jserver);
continue;
}
//check if deleted
if (itemPriority == -1)
{
if (0 == newListVersion)
newListVersion = incrementListVersion(listId, jserver);
removeListItem(listId, itemId, jserver);
continue;
}
//check if changed or removed (sitem=null)
JObject sitem = getListItem(listId, itemId, jserver);
if (sitem != null && ((string)sitem["text"] != itemText || (int)sitem["priority"] != itemPriority || (bool)sitem["completed"] != itemCompleted))
{
//replace server item with client item
if (0 == newListVersion)
newListVersion = incrementListVersion(listId, jserver);
//make a replacement item
JObject ritem = new JObject();
ritem["id"] = itemId;
ritem["completed"] = itemCompleted;
ritem["text"] = itemText;
ritem["priority"] = itemPriority;
ritem["v"] = newListVersion;
//replace it
replaceListItem(listId, itemId, ritem, jserver);
continue;
}
}
}
//completed, return the synced list
return jserver;
}
////////////////////////////////////////////////////////////////////////
// SYNC UTILITIES
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////
// Increment a server list's version number
//
private long incrementListVersion(string listId, JArray jserver)
{
foreach (JObject slist in jserver)
{
if (((string)slist["id"]) == listId)
{
long listversion = (long)slist["v"];
listversion++;
slist["v"] = listversion;
return listversion;
}
}
throw new System.ArgumentNullException("listId[" + listId + "]", "SyncController::incrementListVersion -> List id not found in server docs lists");
}
////////////////////////////////////////////
// Get a list item from the server
//
private JObject getListItem(string listId, string itemId, JArray jserver)
{
foreach (JObject slist in jserver)
{
if (((string)slist["id"]) == listId)
{
foreach (JObject sitem in slist["items"])
{
if ((string)sitem["id"] == itemId)
{
return sitem;
}
}
}
}
return null;//not exceptional, client has item that other user deleted
//throw new System.ArgumentException("SyncController::getListItem -> List item[" + listId + "][" + itemId + "] not found in servers lists");
}
////////////////////////////////////////////
// Get a list "header" from the server
//
private ListHeaderData getListHeaderData(string listId, JArray jserver)
{
foreach (JObject slist in jserver)
{
if (((string)slist["id"]) == listId)
{
ListHeaderData lnd = new ListHeaderData();
lnd.name = (string)slist["name"];
lnd.name_v = (long)slist["name_v"];
lnd.v = (long)slist["v"];
return lnd;
}
}
//Doesn't exist, probably deleted previously and attempting to sync a deleted list
return new ListHeaderData() { v = -999 };
}
private class ListHeaderData
{
public long name_v { get; set; }
public string name { get; set; }
public long v { get; set; }
}
////////////////////////////////////////////
//Rename a list
//
private void renameList(string listId, string newName, JArray jserver)
{
_logger.LogInformation(LoggingEvents.RenameList, $"Rename list id {listId} to {newName}");
foreach (JObject slist in jserver)
{
if (((string)slist["id"]) == listId)
{
slist["name"] = newName;
slist["name_v"] = slist["v"];
}
}
}
////////////////////////////////////////////
// Clean up and ID a client list item
//
private JObject createServerReadyListItemFromClientListItem(JObject citem)
{
JObject sitem = new JObject();
sitem["id"] = Util.ShortId.Generate();
sitem["completed"] = (bool)citem["completed"];
sitem["text"] = (string)citem["text"];
sitem["v"] = 1;
sitem["priority"] = (int)citem["priority"];
return sitem;
}
////////////////////////////////////////////
//Add a new list item from the client
//
private void addNewListItem(string listId, JObject citem, JArray jserver)
{
_logger.LogInformation(LoggingEvents.InsertItem, "addNewListItem");
//NOTE: new list item should have starting version number equal to
//server list's master v value
JObject sitem = createServerReadyListItemFromClientListItem(citem);
foreach (JObject slist in jserver)
{
if (((string)slist["id"]) == listId)
{
((JArray)slist["items"]).Add(sitem);
}
}
}
////////////////////////////////////////////
// Replace a list item from the client
//
private void replaceListItem(string listId, string itemId, JObject citem, JArray jserver)
{
removeListItem(listId, itemId, jserver);
addNewListItem(listId, citem, jserver);
}
////////////////////////////////////////////
//Remove list item
//
private void removeListItem(string listId, string listItemId, JArray jserver)
{
_logger.LogInformation(LoggingEvents.DeleteItem, $"Remove list item - list id {listId} listItemId {listItemId}");
foreach (JObject slist in jserver)
{
if (((string)slist["id"]) == listId)
{
int nRemoveAt = -1;
JArray sitems = (JArray)slist["items"];
for (int i = 0; i < sitems.Count; i++)
{
if ((string)sitems[i]["id"] == listItemId)
{
nRemoveAt = i;
break;
}
}
if (nRemoveAt == -1)
{
_logger.LogWarning(LoggingEvents.DeleteItem, $"Remove list item NOT FOUND - list id {listId} listItemId {listItemId}");
}else{
sitems.RemoveAt(nRemoveAt);
}
return;
}
}
}
///////////////////////////////////////////
//Add a new list from the client
//
private void addNewList(JObject clist, JArray jserver)
{
_logger.LogInformation(LoggingEvents.AddList, "Add new list");
//NOTE: New lists start at version 1 for everything
JObject slist = new JObject();
//set list headers
slist["id"] = Util.ShortId.Generate();
slist["name"] = clist["name"];
slist["name_v"] = 1;
slist["v"] = 1;
//copy over the list data
JArray sitems = new JArray();
foreach (JObject citem in clist["items"])
{
sitems.Add(createServerReadyListItemFromClientListItem(citem));
}
slist["items"] = sitems;
jserver.Add(slist);
}
////////////////////////////////////////////
// Remove list
//
private void removeList(string listId, JArray jserver)
{
_logger.LogInformation(LoggingEvents.DeleteList, $"Remove list {listId}");
int nRemoveAt = -1;
for (int i = 0; i < jserver.Count; i++)
{
if ((string)jserver[i]["id"] == listId)
{
nRemoveAt = i;
break;
}
}
jserver.RemoveAt(nRemoveAt);
return;
}
//-----------------
#region LoggingEvents
public class LoggingEvents
{
public const int SyncList = 1000;
public const int RenameList = 1001;
public const int GetItem = 1002;
public const int InsertItem = 1003;
public const int UpdateItem = 1004;
public const int DeleteItem = 1005;
public const int AddList = 1006;
public const int DeleteList = 1007;
public const int GetItemNotFound = 4000;
public const int UpdateItemNotFound = 4001;
}
#endregion
//-------------
}
}

View File

@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
namespace GZTW.Pecklist.Controllers
{
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
[Authorize]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
// POST api/values
[HttpPost]
public void Post([FromBody]string value)
{
}
// PUT api/values/5
[HttpPut("{id}")]
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/values/5
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
}

26
Models/ApiError.cs Normal file
View File

@@ -0,0 +1,26 @@
using System;
namespace GZTW.Pecklist.Models
{
public class ApiError
{
public int code { get; set; }
public string message { get; set; }
//public string type { get; set; }
public string source { get; set; }
public string helpurl { get; set; }
public static ApiError generalError(string msg, string src)
{
return new ApiError { code = 100, helpurl = "peck.ayanova.com", message = msg, source = src };
}
}
}
/*
codes
100 = general error until we have more specific ones
*/

12
Models/ListData.cs Normal file
View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
namespace GZTW.Pecklist.Models
{
public partial class ListData
{
public long Id { get; set; }
public string TheList { get; set; }
}
}

67
Models/PecklistContext.cs Normal file
View File

@@ -0,0 +1,67 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
//using pecklist.Util;
using Microsoft.Extensions.Logging;
namespace GZTW.Pecklist.Models
{
public partial class PecklistContext : DbContext
{
public virtual DbSet<User> User { get; set; }
public virtual DbSet<ListData> ListData { get; set; }
public PecklistContext(DbContextOptions<PecklistContext> options) : base(options)
{ }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>(entity =>
{
entity.ToTable("user");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.Login)
.IsRequired()
.HasColumnName("login")
.HasColumnType("text");
entity.Property(e => e.Name)
.IsRequired()
.HasColumnName("name")
.HasColumnType("text");
entity.Property(e => e.DlKey)
.HasColumnName("dlkey")
.HasColumnType("text");
entity.Property(e => e.DlKeyExp)
.HasColumnName("dlkeyexp")
.HasColumnType("integer");
});
modelBuilder.Entity<ListData>(entity =>
{
entity.ToTable("listdata");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.TheList)
.IsRequired()
.HasColumnName("thelist")
.HasColumnType("text");
});
//-----------
}
}
}

17
Models/User.cs Normal file
View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
namespace GZTW.Pecklist.Models
{
public partial class User
{
public long Id { get; set; }
public string Name { get; set; }
public string Login { get; set; }
public string Password { get; set; }
public string Salt { get; set; }
public string DlKey { get; set; }
public long? DlKeyExp { get; set; }
}
}

31
Program.cs Normal file
View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace GZTW.Pecklist
{
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseUrls("http://*:3000")
.CaptureStartupErrors(true)
.UseSetting("detailedErrors", "true")
.UseKestrel()
.UseContentRoot(System.IO.Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
}
}

109
Startup.cs Normal file
View File

@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.EntityFrameworkCore;
using GZTW.Pecklist.Models;
using GZTW.Pecklist.Util;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Microsoft.AspNetCore.Authentication;
namespace GZTW.Pecklist
{
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<PecklistContext>(options => options.UseSqlite(Configuration.GetConnectionString("thedb")));
//Added this so that can access configuration from anywhere else
//See authcontroller for usage
services.AddSingleton<IConfiguration>(Configuration);
services.AddMvc();
//get the key from the appsettings.json file
var secretKey = Configuration.GetSection("JWT").GetValue<string>("secret");
var signingKey = new SymmetricSecurityKey(System.Text.Encoding.ASCII.GetBytes(secretKey));
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
// options.AutomaticAuthenticate = true;
// options.AutomaticChallenge = true;
options.TokenValidationParameters = new TokenValidationParameters
{
// Token signature will be verified using a private key.
ValidateIssuerSigningKey = true,
RequireSignedTokens = true,
IssuerSigningKey = signingKey,
ValidateIssuer = true,
ValidIssuer = "GZTW_Pecklist",
ValidateAudience = false,
// ValidAudience = "https://yourapplication.example.com",
// Token will only be valid if not expired yet, with 5 minutes clock skew.
// ValidateLifetime = true,
// RequireExpirationTime = true,
// ClockSkew = new TimeSpan(0, 5, 0),
};
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, PecklistContext dbContext)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseDefaultFiles();
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = context =>
{
if (context.File.Name == "index.html")
{
context.Context.Response.Headers.Add("Cache-Control", "no-cache, no-store");
context.Context.Response.Headers.Add("Expires", "-1");
}
}
});
app.UseAuthentication();
app.UseMvc();
//Check schema
Schema.CheckAndUpdate(dbContext);
}
}
}

View File

@@ -0,0 +1,10 @@
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning",
"System": "Warning",
"Microsoft": "Warning"
}
}
}

21
appsettings.json Normal file
View File

@@ -0,0 +1,21 @@
{
"Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Warning"
}
},
"Console": {
"LogLevel": {
"Default": "Warning"
}
}
},
"ConnectionStrings": {
"thedb": "Datasource=./db/pecklist.sqlite;"
},
"JWT": {
"secret": "-------Nothing8utTheRain!-------"
}
}

BIN
db/pecklist.sqlite Normal file

Binary file not shown.

107
notes/algorithm Normal file
View File

@@ -0,0 +1,107 @@
OLD Backend algorithm:
/*
SYNC STRATEGY
=-=-=-=-=-=-=
NEW ITEMS:
client record is iterated looking for non id or versioned items meaning newly added. If any are found the master rev is incremented and new items
are added.
DELETED ITEMS:
Server record is itereated looking for deleted items (not on the client's list and are older revision than the client version), if any are
found master rev is incremented (unless it was already) then they are removed from the master list.
UPDATE
Compare client record to master record, compare list name for a change, then find all items that have the same ID but different user editable values, if any found update master rev
if not done already, make master changed items match client changed items, update master.
RETURN:
Finally, if master list has changed, it is saved to db then it is returned to user otherwise {nochange:1} or something is returned.
*/
NEW ALGORITHM
Basically the same, except it will sync all lists on the device at once, so the routes need to take a list of lists of listitems as a chunk, not just a single list
OLD ROUTES
=-=-=-=-=-=
//POST - GET AN AUTHENTICATION TOKEN
router.route('/authenticate')
//GET USERS LISTS
// get all the lists for username specified
router.route('/mylists')
//GET A LIST OBJECT
// get the list object for id specified .
router.route('/list/:listid')
//POST - CREATE / SYNC A LIST
router.route('/list')
//DELETE - REMOVE A LIST
router.route('/list')
//DOWNLOAD - download all data
router.route('/mylists/download_all')
NEW ROUTES
=-=-=-=-=-
Since there is so little data overall in these lists, we are just going to assume all lists on all devices.
A Sync operation just sends the whole graph and gets the whole graph back, it's stored in a single field in sqlite table
If a user doesn't want to see a list maybe it's filtered out at the client view
Authenticate - POST
/user - post get
Gets and sets the users list subscriptions
/sync
POST -
client posts their entire subscribed list graph at once,
the return is the entire user's list graph after synced at server
new lists are also created via this mechanism (at client first)
If a client is just getting lists (for example in a first run scenario) they just post an empty list graph
GET - (no get? Post an empty list if you want to get all the user's lists)
NEW List data object graph:
{
lists:[
Name:listname,
id:ShortIdString,
name_v:integer,
v:integer,
items:[
id: ShortIdString or empty if new and not assigned an id yet,
completed: bool,
text:,
v:incrementing_long,
priority:[int 0-3]
]
]
}
https://github.com/tastejs/todomvc/blob/gh-pages/examples/jquery/js/app.js
https://github.com/tastejs/todomvc/blob/master/examples/jquery/index.html

63
notes/notes Normal file
View File

@@ -0,0 +1,63 @@
8750e825f27d77a16755a7b14a9622fe6d4653ceaab2dd5645bbc89b5702fa1d
*********************************************
Updating Microsoft CODE editor
Download the .deb file for 64 bit
execute it
sudo dpkg -i code_1.8.1-1482159060_i386.deb
HANDLEBARS
=-=-=-=-=-
Install:
sudo npm install -g handlebars
Build handlebars template:
handlebars -m wwwroot/js/templates/> wwwroot/js/templates/templates.js
3rd party Packages used:
JWT JSON web token support using jose-jwt found here:
https://jwt.io/#libraries
Mailkit (supposedly system.net.mail will be ported in v2.0 of .net core but for now mailkit is the shit):
<!-- NOTE: bouncycastle needed for license gen but mailkit brings a slightly different one in so there is a conflict but just removing bouncycastle works because the license code uses teh mailkit bouncycastle-->
<!--PackageReference Include="bouncycastle.netcore" Version="1.8.1.3" /-->
DEVICE METRICS LIST
https://material.io/devices/
John phone 411px wide (dp)
Joyce s7 360px wide (dp)
***********************
HOW TO DEPLOY TO IIS
https://stackify.com/how-to-deploy-asp-net-core-to-iis/
1) SET VERSION
RENAME ?plvx.x parameter in index.html to the new version so all files update on mobile
2) PUBLISH
publish command line from rockfishCore folder:
//this will build a release version which is what we use on the server now
dotnet publish -c Release -o ./../plpublish/
//This is what I have seen online, not sure why the -f is necessary or useful, the build is the exact same size as above, have a question in to stackoverflow about it
dotnet publish -f netcoreapp2.1 -c Release -o ./../plpublish/
//if need a debug version
dotnet publish -o ./../plpublish/
3) COPY
Copy over to production server, only need the .dll and the wwwroot folder contents,
remember not to delete the folders on the server only replace their contents because there are Windows file permissions set

2
notes/todo Normal file
View File

@@ -0,0 +1,2 @@

17
pecklist.csproj Normal file
View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" />
<!-- <PackageReference Include="Newtonsoft.Json" Version="10.0.3" /> -->
<PackageReference Include="jose-jwt" Version="2.4.0" />
</ItemGroup>
</Project>

86
util/FoodooImporter.cs Normal file
View File

@@ -0,0 +1,86 @@
using System;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using GZTW.Pecklist.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.IO;
namespace GZTW.Pecklist.Util
{
public static class FoodooImporter
{
public static void Import(PecklistContext ct)
{
//open and read in the GZListServerBackup.bak file into a json object
//https://www.newtonsoft.com/json/help/html/LINQtoJSON.htm
string sfoo = GetBackupFile();
JArray jin = JArray.Parse(sfoo);
JArray jout = new JArray();
foreach (JObject i in jin)
{
JObject o = new JObject();
o["name"] = (string)i["name"];
o["id"] = Util.ShortId.Generate();
o["name_v"] = (long)i["name_v"];
o["v"] = (long)i["v"];
JArray oitems = new JArray();
foreach (JObject l in i["items"])
{
JObject oitem = new JObject();
oitem["id"] = Util.ShortId.Generate();
oitem["completed"] = (bool)l["completed"];
oitem["text"] = (string)l["text"];
oitem["v"] = (long)l["v"];
var highlight = (bool?)l["highlight"];
if (highlight == null)
{
oitem["priority"] = 1;//default priority
}
else
{
oitem["priority"] = 3;//highest priority
}
oitems.Add(oitem);
}
o["items"] = oitems;
jout.Add(o);
}
//write it into the db
ListData ldat = new ListData();
//this is the most compact way to write the json out as text
ldat.TheList = JsonConvert.SerializeObject(jout);
ct.ListData.Add(ldat);
ct.SaveChanges();
}
private static string GetBackupFile()
{
try
{ // Open the text file using a stream reader.
using (StreamReader sr = new StreamReader("./db/GZListServerBackup.bak"))
{
// Read the stream to a string, and write the string to the console.
String line = sr.ReadToEnd();
return line;
}
}
catch
{
return null;
}
}
//--------
}
}

50
util/Hasher.cs Normal file
View File

@@ -0,0 +1,50 @@
using System;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
namespace GZTW.Pecklist.Util
{
//Authentication controller
public static class Hasher
{
public static string hash(string Salt, string Password)
{
//adapted from here:
//https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/consumer-apis/password-hashing
string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
password: Password,
salt: Base64StringToByteArray(Salt),
prf: KeyDerivationPrf.HMACSHA512,
iterationCount: 10000,
numBytesRequested: 512 / 8));
return hashed;
}
///////////////////////////////////
// convert the salt to a byte array
public static byte[] Base64StringToByteArray(string b64)
{
return Convert.FromBase64String(b64);
}
///////////////////////////
// Generate a random salt
//
public static string GenerateSalt()
{
// generate a 128-bit salt using a secure PRNG
byte[] salt = new byte[128 / 8];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(salt);
}
return Convert.ToBase64String(salt);
}
}//eoc
}//eons

141
util/Schema.cs Normal file
View File

@@ -0,0 +1,141 @@
using System;
using System.Text;
using Microsoft.EntityFrameworkCore;
using GZTW.Pecklist.Models;
namespace GZTW.Pecklist.Util
{
//Key generator controller
public static class Schema
{
private static PecklistContext ctx;
/////////////////////////////////////////////////////////////////
/////////// CHANGE THIS ON NEW SCHEMA UPDATE ////////////////////
public const int DESIRED_SCHEMA_LEVEL = 2;
/////////////////////////////////////////////////////////////////
static int startingSchema = -1;
static int currentSchema = -1;
//check and update schema
public static void CheckAndUpdate(PecklistContext context)
{
ctx = context;
bool AppSchemaExists = false;
// var ld=context.ListData;
//update schema here?
using (var command = ctx.Database.GetDbConnection().CreateCommand())
{
//first of all, do we have a schema table yet (v0?)
command.CommandText = "SELECT name FROM sqlite_master WHERE type='table' AND name='appschema';";
ctx.Database.OpenConnection();
using (var result = command.ExecuteReader())
{
if (result.HasRows)
{
AppSchemaExists = true;
}
ctx.Database.CloseConnection();
}
}
//Create schema table (v1)
if (!AppSchemaExists)
{
//nope, no schema table, add it now and set to v1
using (var cmCreateappschema = ctx.Database.GetDbConnection().CreateCommand())
{
context.Database.OpenConnection();
//first of all, do we have a schema table yet (v0?)
cmCreateappschema.CommandText = "CREATE TABLE appschema (id INTEGER PRIMARY KEY, schema INTEGER NOT NULL);";
cmCreateappschema.ExecuteNonQuery();
cmCreateappschema.CommandText = "insert into appschema (schema) values (1);";
cmCreateappschema.ExecuteNonQuery();
context.Database.CloseConnection();
startingSchema = 1;
currentSchema = 1;
}
}
else
{
//get current schema level
using (var cm = ctx.Database.GetDbConnection().CreateCommand())
{
cm.CommandText = "SELECT schema FROM appschema WHERE id=1;";
ctx.Database.OpenConnection();
using (var result = cm.ExecuteReader())
{
if (result.HasRows)
{
result.Read();
currentSchema = startingSchema = result.GetInt32(0);
ctx.Database.CloseConnection();
}
else
{
ctx.Database.CloseConnection();
throw new System.Exception("Pecklist->RfSchema->CheckAndUpdate: Error reading schema version");
}
}
}
}
//Bail early no update?
if (currentSchema == DESIRED_SCHEMA_LEVEL)
return;
//************* SCHEMA UPDATES ******************
//////////////////////////////////////////////////
//schema 2 import data from Foodoo
if (currentSchema < 2)
{
//empty out the listdata table
exec("delete from listdata");
//Trigger import of foodoo data
Util.FoodooImporter.Import(ctx);
//open and read in the GZListServerBackup.bak file into a json object
//turn it into pecklist format
//write it into the db
//$profit$
currentSchema = 2;
setSchemaLevel(currentSchema);
}
//*************************************************************************************
}//eofunction
private static void setSchemaLevel(int nCurrentSchema)
{
exec("UPDATE appschema SET schema=" + nCurrentSchema.ToString());
}
//execute command query
private static void exec(string q)
{
using (var cmCreateappschema = ctx.Database.GetDbConnection().CreateCommand())
{
ctx.Database.OpenConnection();
cmCreateappschema.CommandText = q;
cmCreateappschema.ExecuteNonQuery();
ctx.Database.CloseConnection();
}
}
//eoclass
}
//eons
}

20
util/ShortId.cs Normal file
View File

@@ -0,0 +1,20 @@
using System;
namespace GZTW.Pecklist.Util
{
//Authentication controller
public static class ShortId
{
/////////////////////////////////
// Generate a random shortId
//
// Based off this: https://stackoverflow.com/a/9279005/8939
public static string Generate()
{
return Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Replace("=","").Replace("/","").Replace("+","");
}
}//eoc
}//eons

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/img/mstile-150x150.png"/>
<TileColor>#00a300</TileColor>
</tile>
</msapplication>
</browserconfig>

74
wwwroot/css/app.css Normal file
View File

@@ -0,0 +1,74 @@
ul{
padding:0;
}
.gz-list, .list-unstyled{
list-style: none;
}
.gz-content{
margin-top:64px;
}
.gz-larger{
font-size: larger;
}
.gz-smaller{
font-size: smaller;
}
@media screen and (max-width: 576px){ /* bump up font on smaller screen */
html{font-size: 20px}
.gz-content{
margin-top:80px;
}
}
/*App specific styles*/
/* .peck-list{
max-width: 550px;
background-color: linen;
} */
.new-item{
width:100%;
}
.pl-toggle{
font-size:36px;
}
.pl-done > span{
text-decoration: line-through;
font-weight: lighter;
font-size:12px;
}
.pl-long-item{
font-size:smaller;
}
.pl-short-item{
font-size:larger;
}
/*
MAKE SCREEN SIZE AVAILABLE TO JS
Large is considered above 768px wide
and is the Bootstrap 4 breakpoint for medium screens and up
*/
body::after {
content: 'app-small-mode';
display: none;
}
@media screen and (min-width: 768px) {
body::after {
content: 'app-large-mode';
display: none;
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,40 @@
.alert.mdi::before {
margin: 0 3px 0 -3px;
}
.btn.mdi:not(:empty)::before {
margin: 0 3px 0 -3px;
}
.breadcrumb-item a.mdi::before, .breadcrumb-item span.mdi::before {
margin: 0 2px 0 -2px;
}
.dropdown-item.mdi::before {
margin: 0 8px 0 -10px;
}
.list-group-item.mdi::before {
margin: 0 6px 0 -6px;
}
.modal-title.mdi::before {
margin: 0 4px 0 0;
}
.nav-link.mdi::before {
margin: 0 4px 0 -4px;
}
.navbar-brand.mdi::before {
margin: 0 4px 0 0;
}
.popover-title.mdi::before {
margin: 0 4px 0 -4px;
}
.btn.mdi-chevron-up.collapsed::before {
content: '\f140';
}

Binary file not shown.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1021 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
wwwroot/img/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.7 KiB

74
wwwroot/index.html Normal file
View File

@@ -0,0 +1,74 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Pecklist loading....</title>
<!-- ICONS / MANIFEST -->
<link rel="apple-touch-icon" sizes="180x180" href="/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/img/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/img/favicon-16x16.png">
<link rel="manifest" href="manifest.json">
<link rel="mask-icon" href="/img/safari-pinned-tab.svg" color="#066910">
<link rel="shortcut icon" href="/img/favicon.ico">
<meta name="msapplication-config" content="browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<!-- 3rd party components fonts and icons -->
<link href="css/materialdesignicons.min.css?plv=1.3" media="all" rel="stylesheet" type="text/css" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M"
crossorigin="anonymous">
<link rel="stylesheet" href="css/mdi-bs4-compat.css?plv=1.3" type="text/css" />
<link rel="stylesheet" href="css/app.css?plv=1.3" type="text/css" />
<!-- third-party javascript -->
<script src="js/lib/jquery-3.2.1.min.js"></script>
<script src="js/lib/jquery.event.gevent.js?plv=1.3"></script>
<script src="js/lib/jquery.gzserialize.js?plv=1.3"></script>
<script src="js/lib/jquery.autocomplete.min.js?plv=1.3"></script>
<script src="js/lib/page.js?plv=1.3"></script>
<script src="js/lib/handlebars.runtime-v4.0.5.js?plv=1.3"></script>
<script src="js/lib/store.min.js?plv=1.3"></script>
<script src="js/lib/moment.min.js?plv=1.3"></script>
<script src="js/lib/lodash.min.js?plv=1.3"></script>
<!-- our javascript -->
<script src="js/index.js?plv=1.3"></script>
<script src="js/app.util.js?plv=1.3"></script>
<script src="js/app.api.js?plv=1.3"></script>
<script src="js/app.utilB.js?plv=1.3"></script>
<script src="js/app.nav.js?plv=1.3"></script>
<script src="js/app.shell.js?plv=1.3"></script>
<script src="js/app.fourohfour.js?plv=1.3"></script>
<script src="js/app.authenticate.js?plv=1.3"></script>
<script src="js/app.main.js?plv=1.3"></script>
<script src="js/app.settings.js?plv=1.3"></script>
<!-- handlebars templates -->
<script src="js/templates/templates.js?plv=1.3"></script>
<script>
$(function () {
app.initModule($('#app'));
});
</script>
</head>
<body class="gz-body">
<div id="app" class="container"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js" integrity="sha384-b/U6ypiBEHpOf/4+1nzFpr53nxSS+GLCkfwBdFNTxtclqqenISfwAzpKaMNFNmj4"
crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/js/bootstrap.min.js" integrity="sha384-h0AbiXch4ZDo7tp9hKZ4TsHbi047NrKGLO3SEJAg45jXxnGIfYzk4Si90RDIqNm1"
crossorigin="anonymous"></script>
</body>
</html>

529
wwwroot/js/app.api.js Normal file
View File

@@ -0,0 +1,529 @@
/*
* app.api.js
* Ajax api helper module
*/
/*jslint browser : true, continue : true,
devel : true, indent : 2, maxerr : 50,
newcap : true, nomen : true, plusplus : true,
regexp : true, sloppy : true, vars : false,
white : true
*/
/*global $, io, app */
app.api = (function () {
'use strict';
var
initModule, getAuthHeaderObject,
GZAppVersion,
get, remove, create, update, uploadFile,
doSync, getListFromStore, putListInStore,
goToNextList, getActiveList, replaceActiveList, renameActiveList, toggleDeleteActiveList,
getActiveListIndex, replaceRecordInStore, insertRecordInStore, getRecordFromStore,
getListItemCoordinates, setActiveListId, getActiveListId, newList
;
GZAppVersion = "1.3";
//////////////////////////////////////////////////////////////////////////////////////
// NOT AUTHORIZED ERROR HANDLER
$(document).ajaxError(function (event, jqxhr, settings, thrownError) {
//unauthorized? Trigger logout which will trigger login after clearing creds
if (jqxhr.status == 401) {
window.location.replace('#!/logout');
}
});
//////////////////////////////////////////////////////////////////////////////////////
// UTILITY
///////////////////////////////////////////////////////////
// Return the auth token header
//
//
getAuthHeaderObject = function () {
return {
"Authorization": "Bearer " + app.shell.stateMap.user.token
};
}
///////////////////////////////////////////////////////////
// Fetch the list from local storage
//
//
getListFromStore = function () {
return store.get('pecklist.list');
}
///////////////////////////////////////////////////////////
// Replace the list in local store
//
//
putListInStore = function (theList) {
store.set('pecklist.list', theList);
}
///////////////////////////////////////////////////////////
// Get the active list id or first if active not found
//
getActiveListId = function (theId) {
var ret = store.get('pecklist.activeListId');
//validate it's ok
var l = app.api.getListFromStore();
var firstListId = l[0].id;//get a default in case it's not found
var len = l.length, i = 0;
for (i; i < len; i++) {
if (l[i].id == ret) {
return ret;
}
}
//if we're here then it's not a valid id so use the first one instead and set it
store.set('pecklist.activeListId', firstListId);
return firstListId;
}
///////////////////////////////////////////////////////////
// Set the active list id
//
//
setActiveListId = function (theId) {
if (theId) {
store.set('pecklist.activeListId', theId);
}
}
///////////////////////////////////////////////////////////
// ADVANCE ACTIVE LIST ID TO NEXT LIST
//
//
goToNextList = function () {
var l = getListFromStore();
var allLength = l.length;
//is there more than one list?
if (allLength < 2) {
//Nope
return;
}
//figure out what the next list id should be
var currentIndex = getActiveListIndex();
var highestPossibleIndex = allLength - 1;
//are we already at the last list?
if (currentIndex == highestPossibleIndex) {
//go to first list (wrap)
setActiveListId(l[0].id);
return;
}
//advance to the next list
setActiveListId(l[currentIndex + 1].id);
return;
}
//////////////////////////////
// GET LIST INDEX
//
getActiveListIndex = function () {
var activeListId = getActiveListId();
var l = app.api.getListFromStore();
var len = l.length, i = 0;
for (i; i < len; i++) {
if (l[i].id == activeListId) {
return i;
}
}
//This should never happen, if it does something is very wrong:
throw new Error("Error: app.main.js::getActiveListIndex - active list ID not found in list store");
}
///////////////////////////
// GET ACTIVE LIST
//
getActiveList = function () {
var activeListId = getActiveListId();
var l = app.api.getListFromStore();
var len = l.length, i = 0;
for (i; i < len; i++) {
if (l[i].id == activeListId) {
return l[i];
}
}
//This should never happen, if it does something is very wrong:
throw new Error("Error: app.main.js::getActiveList - activeListId not found in list store");
}
///////////////////////////
// CREATE NEW LIST
//
newList = function () {
var l = app.api.getListFromStore();
var newListRecord={
id:"NEW_"+ _.now(),
name: "New list "+Date(),
v:1,
name_v:1,
items:[]
}
l.push(newListRecord);
app.api.putListInStore(l);
setActiveListId(newListRecord.id);
}
///////////////////////////
// RENAME ACTIVE LIST
//
renameActiveList = function (newName) {
var activeListId = getActiveListId();
var l = app.api.getListFromStore();
var len = l.length, i = 0;
for (i; i < len; i++) {
if (l[i].id == activeListId) {
l[i].name = newName;
putListInStore(l);
return;
}
}
//This should never happen, if it does something is very wrong:
throw new Error("Error: app.main.js::renameActiveList - activeListId not found in list store");
}
///////////////////////////
// DELETE ACTIVE LIST
//
toggleDeleteActiveList = function () {
var activeListId = getActiveListId();
var l = app.api.getListFromStore();
var len = l.length, i = 0;
for (i; i < len; i++) {
if (l[i].id == activeListId) {
if (l[i].deleted) {
delete l[i].deleted;
} else {
l[i].deleted = true;//tag it as deleted
}
putListInStore(l);
return;
}
}
//This should never happen, if it does something is very wrong:
throw new Error("Error: app.main.js::toggleDeleteActiveList - activeListId not found in list store");
}
///////////////////////////
// REPLACE ACTIVE LIST
//
replaceActiveList = function (replacementList) {
var activeListId = getActiveListId();
var l = app.api.getListFromStore();
var len = l.length, i = 0;
for (i; i < len; i++) {
if (l[i].id == activeListId) {
l[i] = replacementList;
putListInStore(l);
return;
}
}
//This should never happen, if it does something is very wrong:
throw new Error("Error: app.main.js::replaceActiveList - activeListId not found in list store");
}
//////////////////////////////
// GET LIST ITEM CO-ORDINATES
//
getListItemCoordinates = function (itemId) {
var allLists = app.api.getListFromStore();
var allLength = allLists.length, j = 0;
for (j; j < allLength; j++) {
//iterate the list and find it
var len = allLists[j].items.length, i = 0;
for (i; i < len; i++) {
if (allLists[j].items[i].id == itemId) {
return { listIndex: j, itemIndex: i };
}
}
}
//This should never happen, if it does something is very wrong:
throw new Error("Error: app.main.js::getListItemCoordinates - List item ID not found in list store");
}
///////////////////////////
// GET RECORD FROM STORE
//
getRecordFromStore = function (itemId) {
var co = getListItemCoordinates(itemId);
var allLists = app.api.getListFromStore();
return allLists[co.listIndex].items[co.itemIndex];
}
///////////////////////////
// REPLACE RECORD IN STORE
//
replaceRecordInStore = function (itemId, replacementRecord) {
var co = getListItemCoordinates(itemId);
var l = app.api.getListFromStore();
//Todo: is this the proper way to do it?
l[co.listIndex].items[co.itemIndex] = replacementRecord;
app.api.putListInStore(l);
}
///////////////////////////
// INSERT RECORD IN STORE
//
insertRecordInStore = function (newRecord) {
var activeListIndex = getActiveListIndex();
var l = app.api.getListFromStore();
l[activeListIndex].items.push(newRecord);
app.api.putListInStore(l);
}
//////////////////////////////////////////////////////////////////////////////////////
// PECKLIST SPECIFIC ROUTES
//////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////
//
// DOSYNC - Synchronize the stored data
//
doSync = function (callback) {
//get the list from the store
var theList = getListFromStore();
$.ajax({
method: "post",
dataType: "json",
url: app.shell.stateMap.apiUrl + 'sync',
headers: getAuthHeaderObject(),
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(theList),
success: function (ret, textStatus) {
//Put the new list in the store
putListInStore(JSON.parse(ret));
callback({ ok: 1 });
},
error: function (jqXHR, textStatus, errorThrown) {
callback({
error: 1,
msg: textStatus + '\n' + errorThrown,
error_detail: {}
});
}
});
}
////////////////////////////////////////////////////////////////////////////////////////////////////////
// STANDARD API CORE ROUTES
//
////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////
//Create
//Route app.post('/api/:obj_type/create', function (req, res) {
//
create = function (apiRoute, objData, callback) {
$.ajax({
method: "post",
dataType: "json",
url: app.shell.stateMap.apiUrl + apiRoute,
headers: getAuthHeaderObject(),
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(objData),
success: function (data, textStatus) {
callback(data);
},
error: function (jqXHR, textStatus, errorThrown) {
callback({
error: 1,
msg: textStatus + '\n' + errorThrown,
error_detail: {}
});
}
});
}
/////////////////
//Get - get anything, the caller provides the route, this should replace most legacy get
//
get = function (apiRoute, callback) {
$.ajax({
method: "GET",
dataType: "json",
url: app.shell.stateMap.apiUrl + apiRoute,
headers: getAuthHeaderObject(),
success: function (data, textStatus) {
callback(data);
},
error: function (jqXHR, textStatus, errorThrown) {
callback({
error: 1,
msg: textStatus + '\n' + errorThrown,
error_detail: {}
});
}
});
}
////////////////////
///////////////////////////////////////////////////////////
//Update
//route: app.post('/api/:obj_type/update/:id', function (req, res) {
//
update = function (objType, objData, callback) {
var theId;
if (!objData.id) {
return callback({
error: 1,
msg: 'app.api.js::update->Error: missing id field in update document',
error_detail: objData
});
}
theId = objData.id;
$.ajax({
method: "put",
dataType: "json",
url: app.shell.stateMap.apiUrl + objType + '/' + theId,
headers: getAuthHeaderObject(),
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(objData),
success: function (data, textStatus) {
if (data == null) {
data = { ok: 1 };
}
callback(data);
},
error: function (jqXHR, textStatus, errorThrown) {
callback({
error: 1,
msg: textStatus + '\n' + errorThrown,
error_detail: {}
});
}
});
}
///////////////////////////////////////////////////////////
//remove Item
remove = function (apiRoute, callback) {
$.ajax({
method: "DELETE",
dataType: "json",
url: app.shell.stateMap.apiUrl + apiRoute,
headers: getAuthHeaderObject(),
success: function (data, textStatus) {
callback(data);
},
error: function (jqXHR, textStatus, errorThrown) {
callback({
error: 1,
msg: textStatus + '\n' + errorThrown,
error_detail: {}
});
}
});
}
///////////////////////////////////////////////////////////
// uploadFile
// (ajax route to upload a file)
//
uploadFile = function (apiRoute, objData, callback) {
$.ajax({
method: "post",
dataType: "json",
url: app.shell.stateMap.apiUrl + apiRoute,
headers: getAuthHeaderObject(),
contentType: false,
processData: false,
data: objData,
success: function (data, textStatus) {
callback(data);
},
error: function (jqXHR, textStatus, errorThrown) {
callback({
error: 1,
msg: textStatus + '\n' + errorThrown,
error_detail: {}
});
}
});
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
initModule = function () { };
return {
initModule: initModule,
getAuthHeaderObject: getAuthHeaderObject,
GZAppVersion: GZAppVersion,
get: get,
remove: remove,
create: create,
update: update,
uploadFile: uploadFile,
doSync: doSync,
getListFromStore: getListFromStore,
putListInStore: putListInStore,
goToNextList: goToNextList,
getActiveList: getActiveList,
replaceActiveList: replaceActiveList,
getActiveListIndex: getActiveListIndex,
replaceRecordInStore: replaceRecordInStore,
insertRecordInStore: insertRecordInStore,
getRecordFromStore: getRecordFromStore,
getListItemCoordinates: getListItemCoordinates,
renameActiveList: renameActiveList,
toggleDeleteActiveList: toggleDeleteActiveList,
newList: newList
};
}());

View File

@@ -0,0 +1,104 @@
/*jslint browser : true, continue : true,
devel : true, indent : 2, maxerr : 50,
newcap : true, nomen : true, plusplus : true,
regexp : true, sloppy : true, vars : false,
white : true
*/
/*global $, app */
app.authenticate = (function() {
'use strict';
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
var
stateMap = {},
onSubmit,
configModule,
initModule;
//----------------- END MODULE SCOPE VARIABLES ---------------
//------------------- BEGIN UTILITY METHODS ------------------
//-------------------- END UTILITY METHODS -------------------
//------------------- BEGIN EVENT HANDLERS -------------------
onSubmit = function(event) {
event.preventDefault();
//get creds
var login = $('#login').val();
var password = $('#password').val();
$.ajax({
method: "post",
dataType: "json",
url: app.shell.stateMap.apiUrl.replace('/api/', '/authenticate'),
data: {
login: login,
password: password
},
success: function(data, textStatus, jqXHR) {
if (data.ok == 1) {
app.shell.stateMap.user.authenticated = true;
app.shell.stateMap.user.token = data.token;
app.shell.stateMap.user.name = data.name;
app.shell.stateMap.user.dlkey=data.dlkey;
app.shell.stateMap.user.id=data.id;
//token expiry date
app.shell.stateMap.user.expires = data.expires;
//tell the shell we've logged in successfully
$.gevent.publish('app-login', {
name: login
});
} else {
if (data.error) {
$.gevent.publish('app-show-error',data.error);
}
app.shell.stateMap.user.authenticated = false;
app.shell.stateMap.user.token = '';
app.shell.stateMap.user.name = 'please sign in';
$.gevent.publish('app-logout');
}
},
error: function(jqXHR, textStatus, errorThrown) {
app.shell.stateMap.user.authenticated = false;
app.shell.stateMap.user.token = '';
app.shell.stateMap.user.name = 'please sign in';
$.gevent.publish('app-logout');
$.gevent.publish('app-show-error',textStatus + " " + errorThrown);
}
});
return false; //prevent default?
};
//-------------------- END EVENT HANDLERS --------------------
//------------------- BEGIN PUBLIC METHODS -------------------
//CONFIGMODULE
//
configModule = function(context) {
stateMap.context = context.context;
if (stateMap.context.params.id) {
stateMap.id = stateMap.context.params.id;
}
};
//INITMODULE
//
initModule = function($container) {
if (typeof $container === 'undefined') {
$container = $('#app-shell-main-content');
}
$container.html(Handlebars.templates['app.authenticate']({}));
$('#btnSubmit').bind('click', onSubmit);
};
// return public methods
return {
configModule: configModule,
initModule: initModule
};
//------------------- END PUBLIC METHODS ---------------------
}());

View File

@@ -0,0 +1,56 @@
/*jslint browser : true, continue : true,
devel : true, indent : 2, maxerr : 50,
newcap : true, nomen : true, plusplus : true,
regexp : true, sloppy : true, vars : false,
white : true
*/
/*global $, app */
app.fourohfour = (function() {
'use strict';
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
var
stateMap = {},
configModule,
initModule;
//----------------- END MODULE SCOPE VARIABLES ---------------
//------------------- BEGIN UTILITY METHODS ------------------
//-------------------- END UTILITY METHODS -------------------
//------------------- BEGIN EVENT HANDLERS -------------------
//-------------------- END EVENT HANDLERS --------------------
//------------------- BEGIN PUBLIC METHODS -------------------
//CONFIGMODULE
//
configModule = function(context) {
stateMap.context = context.context;
if (stateMap.context.params.id) {
stateMap.id = stateMap.context.params.id;
}
};
//INITMODULE
//
initModule = function($container) {
if (typeof $container === 'undefined') {
$container = $('#app-shell-main-content');
}
$container.html(Handlebars.templates['app.fourohfour']({}));
};
//PUBLIC METHODS
//
return {
configModule: configModule,
initModule: initModule
};
//------------------- END PUBLIC METHODS ---------------------
}());

400
wwwroot/js/app.main.js Normal file
View File

@@ -0,0 +1,400 @@
/*
* app.main.js
* License key generator
*/
/*jslint browser : true, continue : true,
devel : true, indent : 2, maxerr : 50,
newcap : true, nomen : true, plusplus : true,
regexp : true, sloppy : true, vars : false,
white : true
*/
/*global $, app */
app.main = (function () {
'use strict';
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
var
stateMap = {},
configModule, initModule,
ENTER_KEY = 13, ESCAPE_KEY = 27,
render,
onSync, onNextList, onClearDone,
onInsert, onShowContextMenu, onShowListContextMenu, onNewPriority, onDoneChanged,
onNewText, onNewListName, onDeleteList;
//----------------- END MODULE SCOPE VARIABLES ---------------
//------------------- BEGIN UTILITY METHODS ------------------
///////////////////////////
// RENDER - DRAW THE LIST
//
render = function () {
var smallScreen = app.utilB.getMediaSize() == "app-small-mode";
var $l = $('#peck-list');
var $title = $('#peck-title');
var $listContextDiv = $('#list-context-div');
var $newItem =
$listContextDiv.empty();
$l.empty();
$title.text('');
var rawList = app.api.getActiveList();
if (rawList == null) {
$title.text('?? NO LIST ??');
$l.append('<li>NO LIST</li>');
return;
}
var deletedList = rawList.deleted;
var listDisplayName = rawList.name;
if (deletedList) {
listDisplayName = "[" + rawList.name + "] - DELETE NEXT SYNC ";
$('#new-item').hide();
$title.addClass('text-danger');
} else {
$title.removeClass('text-danger');
}
$title.text(listDisplayName);
// Sort by `user` in ascending order and by `age` in descending order.
var l = _.orderBy(rawList.items, ['priority', 'text'], ['desc', 'asc']);
//draw the list
//{"id":"FmcIT283XkSFxvoyBGCmw","completed":false,"text":"Paper towel","v":3706,"priority":2}
var len = l.length, i = 0;
for (i; i < len; i++) {
var o = l[i];
if (o.priority > -1) {
var itemTextSpanClass = '';
if (!o.text) {
o.text = "## EMPTY ITEM ###";
}
//3 = old highlight (orangey red) and 2 is default dark green, 1 is black and 0 is faded gray
switch (o.priority) {
case 3:
itemTextSpanClass = 'text-danger';
break;
case 2:
itemTextSpanClass = 'text-primary';
break;
case 1:
itemTextSpanClass = 'text-dark';
break;
default:
itemTextSpanClass = 'text-secondary';
}
if (deletedList) {
itemTextSpanClass = 'text-warning';
}
if (smallScreen && o.text.length > 30) {
itemTextSpanClass += ' pl-long-item';
} else {
itemTextSpanClass += ' pl-short-item';
}
var listItem;
if (o.completed || deletedList) {
listItem = '<li data-id="' + o.id + '" class="list-group-item text-right pl-done">' +
'<span id="b' + i + '" class="w-100 ' + itemTextSpanClass + '" >' + o.text + '</span>' +
'</li>'
} else {
listItem = '<li data-id="' + o.id + '" class="list-group-item">' +
'<span id="b' + i + '" class="w-100 font-weight-bold ' + itemTextSpanClass + '" >' + o.text + '</span>' +
'</li>'
}
$l.append(listItem);
if (!deletedList) {
$('#b' + i).bind('click', o, onShowContextMenu);
}
}
}
}
//-------------------- END UTILITY METHODS -------------------
//------------------- BEGIN EVENT HANDLERS -------------------
//////////////////////////////////////////////////
//
onSync = function () {
$.gevent.publish('app-clear-error');
app.api.doSync(function (res) {
if (res.error) {
$.gevent.publish('app-show-error', res.msg);
} else {
render();
//alert("Stub: onSync success ");
return false;
}
});
};
//////////////////////////////////////////////////
// ADVANCE TO NEXT LIST
//
onNextList = function (e) {
app.api.goToNextList();
render();
};
//////////////////////////////////////////////////
// Clear completed items
//
onClearDone = function (e) {
var l = app.api.getActiveList();
if (l == null) {
return;
}
var len = l.items.length, i = 0;
for (i; i < len; i++) {
if (l.items[i].completed) {
l.items[i].priority = -1;
}
}
app.api.replaceActiveList(l);
render();
};
//////////////////////////////////////////////////
//
onInsert = function (e) {
e.preventDefault();
var $input = $(e.target);
var val = $input.val().trim();
if (e.which !== ENTER_KEY || !val) {
return;
}
//{id: "3GtklnXES0Ox7QrOJpEFRQ", completed: false, text: "No need for lock feature if it's not used", v: 49, priority: 2}
var newRecord = {
id: "NEW_" + _.now(),
completed: false,
text: val,
v: 0,
priority: 1
};
app.api.insertRecordInStore(newRecord);
$input.val('');
render();
return false; //prevent default
};
//////////////////////////////////////////////////
// Show context menu
//
onShowContextMenu = function (e) {
e.preventDefault();
$('.pl-mnu').remove();
var li = $('li[data-id="' + e.data.id + '"]');
li.append(Handlebars.templates['app.main.context']({}));
var edTxt = $('#edtxt');
var btnToggleDone = $("#btn-toggle-done");
edTxt.val(e.data.text);
edTxt.keyup(e.data, onNewText);
btnToggleDone.focus();
if (e.data.completed) {
btnToggleDone.addClass("mdi-checkbox-blank-outline");
} else {
btnToggleDone.addClass("mdi-checkbox-marked");
}
btnToggleDone.click(e.data, onDoneChanged);
$('.ct-btn-priority').click(e.data, onNewPriority);
return false;
};
//////////////////////////////////////////////////
// Show LIST context menu
//
onShowListContextMenu = function (e) {
//rename or delete
e.preventDefault();
var listContextDiv = $('#list-context-div');
//is it already displayed?
if (listContextDiv.children().length > 0) {
//div has tags in it, so clear it and return, it's a dismissal
listContextDiv.empty();
return false;
}
listContextDiv.append(Handlebars.templates['app.main.list-context']({}));
var edTxt = $('#edtxt');
var btnDeleteList = $("#btn-delete-list");
var rawList = app.api.getActiveList();
var deletedList = rawList.deleted;
var $title = $('#peck-title');
if (deletedList) {
btnDeleteList.removeClass('btn-danger').addClass('btn-success');
btnDeleteList.text('UNDELETE LIST');
edTxt.hide();
} else {
edTxt.val($title.text());
edTxt.keyup(onNewListName);
edTxt.focus();
}
btnDeleteList.click(onDeleteList);
return false;
};
////////////////////////////////////////////////////////////////////////////////////////////////////
//CONTEXT FUNCTIONS HERE
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////
// CHANGE PRIORITY
//
onNewPriority = function (e) {
//List item id: e.data
//priority value: e.target.value
var rec = app.api.getRecordFromStore(e.data.id);
rec.priority = _.toNumber(e.target.value);
app.api.replaceRecordInStore(e.data.id, rec);
render();
return false;
};
//////////////////////////////////////////////////
// CHANGE TEXT
//
onNewText = function (e) {
var newText = e.target.value.trim();
if (e.which !== ENTER_KEY || !newText) {
return;
}
var rec = app.api.getRecordFromStore(e.data.id);
rec.text = newText;
app.api.replaceRecordInStore(e.data.id, rec);
render();
return false;
};
//////////////////////////////////////////////////
// CHANGE COMPLETED
//
onDoneChanged = function (e) {
//List item id: e.data
//priority value: e.target.value
var rec = app.api.getRecordFromStore(e.data.id);
rec.completed = !rec.completed;
app.api.replaceRecordInStore(e.data.id, rec);
render();
return false;
};
//////////////////////////////////////////////////
// CHANGE TEXT
//
onNewListName = function (e) {
var newText = e.target.value.trim();
if (e.which !== ENTER_KEY || !newText) {
return;
}
app.api.renameActiveList(newText);
render();
return false;
};
//////////////////////////////////////////////////
// CHANGE TEXT
//
onDeleteList = function (e) {
e.preventDefault();
app.api.toggleDeleteActiveList();
render();
return false;
};
//-------------------- END EVENT HANDLERS --------------------
//------------------- BEGIN PUBLIC METHODS -------------------
//CONFIGMODULE
//
configModule = function (context) {
stateMap.context = context.context;
if (stateMap.context.params.id) {
stateMap.id = stateMap.context.params.id;
}
};
//INITMODULE
//
initModule = function ($container) {
if (typeof $container === 'undefined') {
$container = $('#app-shell-main-content');
}
$container.html(Handlebars.templates['app.main']({}));
//Context menu
app.nav.contextClear();
////app.nav.setContextTitle("License");
//make context menu
//Context menu
app.nav.contextClear();
app.nav.contextAddButton('btn-sync', 'Sync', 'sync', onSync);
app.nav.contextAddButton('btn-clear-done', 'Clear done', 'nuke', onClearDone);
app.nav.contextAddButton('btn-next', 'List', 'skip-next', onNextList);
//bind event handlers
$('#new-item').keyup(onInsert);
$('#btn-test').click(onNewPriority);
$('#peck-title').bind('click', onShowListContextMenu);
//display the cached data
render();
};
// return public methods
return {
configModule: configModule,
initModule: initModule
};
//------------------- END PUBLIC METHODS ---------------------
}());

102
wwwroot/js/app.nav.js Normal file
View File

@@ -0,0 +1,102 @@
/*
* app.nav.js
* Handle dynamic navigation related operations
* toobar, menu, fab etc
*
*/
/*jslint browser : true, continue : true,
devel : true, indent : 2, maxerr : 50,
newcap : true, nomen : true, plusplus : true,
regexp : true, sloppy : true, vars : false,
white : true
*/
/*global $, app */
app.nav = (function () {
var contextClear, contextAddButton, contextAddLink, backUrl, backRemove, moreMenuInitialized,
setContextTitle, setSelectedMenuItem;
// ///////////////////////////////////////////
// //Set context menu title
// //
// var setContextTitle = function (title) {
// $("#gz-context-title").html(title + "...");
// }
////////////////////////////////////////////////
//Clear the contents of the context menu (reset it)
//
contextClear = function () {
$("#gz-context-group").empty();
$("#gz-context-group").addClass("invisible");
};
////////////////////////////////////////////////
//Add a non nav button item to the context menu
//
contextAddButton = function (id, title, icon, clickHandler) {
var $group=$("#gz-context-group");
$group.removeClass("invisible");
var iconClass = '';
if (icon) {
iconClass = "mdi mdi-" + icon;
}
$group.append(
'<button type="button" id="' + id + '" class="btn btn-outline-dark ' + iconClass + '" href="#">' + title + '</a>'
);
$('#' + id).bind('click', clickHandler);
};
// <button type="button" class="btn btn-outline-dark mdi mdi-key">Left</button>
////////////////////////////////////////////////
//Add an url item to the context menu handling
//phone vs larger sizes
//
contextAddLink = function (url, title, icon) {
var $group=$("#gz-context-group");
$group.removeClass("invisible");
var iconClass = '';
if (icon) {
iconClass = "mdi mdi-" + icon;
}
$group.append(
'<a class="btn btn-outline-dark ' + iconClass + '" href="#!/' + encodeURI(url) + '">' + title + '</a>'
);
};
////////////////////////////////////////////////
//Set the active class on the selected menu item
//
setSelectedMenuItem = function (selectedMenuItem) {
$(".nav-item").removeClass("active");
if (selectedMenuItem) {
$("#" + selectedMenuItem).addClass("active");
}
};
return {
contextClear: contextClear,
contextAddButton: contextAddButton,
contextAddLink: contextAddLink,
setContextTitle: setContextTitle,
setSelectedMenuItem: setSelectedMenuItem
};
}());

103
wwwroot/js/app.settings.js Normal file
View File

@@ -0,0 +1,103 @@
/*jslint browser : true, continue : true,
devel : true, indent : 2, maxerr : 50,
newcap : true, nomen : true, plusplus : true,
regexp : true, sloppy : true, vars : false,
white : true
*/
/*global $, app */
app.settings = (function () {
'use strict';
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
var
stateMap = {},
configModule,
onChangePassword, onNewList,
initModule;
//----------------- END MODULE SCOPE VARIABLES ---------------
//------------------- BEGIN UTILITY METHODS ------------------
//-------------------- END UTILITY METHODS -------------------
//------------------- BEGIN EVENT HANDLERS -------------------
///////////////////////////////
//ONUPDATE
//
onChangePassword = function (event) {
event.preventDefault();
$.gevent.publish('app-clear-error');
//get form data
var formData = $("form").serializeArray({
checkboxesAsBools: true
});
var submitData = app.utilB.objectifyFormDataArray(formData);
app.api.create('user/' + app.shell.stateMap.user.id + '/changepassword',
submitData, function (res) {
if (res.error) {
$.gevent.publish('app-show-error', res.msg);
} else {
page('#!/logout');
}
});
return false; //prevent default?
};
///////////////////////////////
// NEW LIST
//
onNewList = function (e) {
e.preventDefault();
app.api.newList();
page('#!/');
};
//-------------------- END EVENT HANDLERS --------------------
//------------------- BEGIN PUBLIC METHODS -------------------
//CONFIGMODULE
//
configModule = function (context) {
stateMap.context = context.context;
if (stateMap.context.params.id) {
stateMap.id = stateMap.context.params.id;
}
};
//INITMODULE
//
initModule = function ($container) {
if (typeof $container === 'undefined') {
$container = $('#app-shell-main-content');
}
$container.html(Handlebars.templates['app.settings']({}));
// bind actions
$('#btn-change-password').bind('click', onChangePassword);
$('#btn-new-list').bind('click', onNewList);
//Context menu
app.nav.contextClear();
////app.nav.setContextTitle("Search");
};
//PUBLIC METHODS
//
return {
configModule: configModule,
initModule: initModule
};
//------------------- END PUBLIC METHODS ---------------------
}());

310
wwwroot/js/app.shell.js Normal file
View File

@@ -0,0 +1,310 @@
/*
* app.shell.js
* Shell module for SPA
*/
/*jslint browser : true, continue : true,
devel : true, indent : 2, maxerr : 50,
newcap : true, nomen : true, plusplus : true,
regexp : true, sloppy : true, vars : false,
white : true
*/
/*global $, app */
app.shell = (function () {
'use strict';
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
var
stateMap = {
$container: undefined,
anchor_map: {},
user: {
authenticated: false,
token: '',
name: '',
id:0
},
apiUrl: '',
search_cache: {
has_cache: false
}
},
tokenExpired,
//onUPdate,
onLogin, onLogout, onShowError, onClearError,
initModule;
//----------------- END MODULE SCOPE VARIABLES ---------------
//------------------- BEGIN UTILITY METHODS ------------------
tokenExpired = function () {
//fetch stored creds if available
var creds = store.get('pecklist.usercreds');
if (creds) {
//check if token has expired
//is the date greater than the expires date
var rightNowUtc = new Date();
var expDate = new Date(creds.expires * 1000);
if (rightNowUtc > expDate) {
return true;
} else {
return false;
}
} else {
return true;
}
}
//-------------------- END UTILITY METHODS -------------------
//------------------- BEGIN EVENT HANDLERS -------------------
onLogin = function (event, login_user) {
//store creds
store.set('pecklist.usercreds', stateMap.user);
//New token needs to reset lastNav
stateMap.lastNav = new Date();
//Go to the home page
page('#!/main/');
return false;
};
onLogout = function (event) {
store.clear();
stateMap.user.authenticated = false;
stateMap.user.name = '';
stateMap.user.token = '';
stateMap.user.id=0;
page('#!/authenticate/');
return false;
};
// onUpdate = function (event) {
// window.location.reload(true);
// return false;
// };
onShowError = function (event, msg) {
$("#app-error-div").removeClass("d-none");
$('#app-error-message').text(msg);
return false;
};
onClearError = function (event) {
$("#app-error-div").addClass("d-none");
return false;
};
//-------------------- END EVENT HANDLERS --------------------
//------------------- BEGIN PUBLIC METHODS -------------------
initModule = function ($container) {
document.title = 'Pecklist ' + app.api.GZAppVersion;
//PRE FLIGHT CHECK
//================
//local storage required
if (!store.enabled) {
alert('Local storage is not supported by your browser. Please disable "Private Mode", or upgrade to a modern browser.');
return;
}
//wait indicator for ajax functions
$(document).ajaxStart(function () {
//disable all buttons
$('.app-frm-buttons button').prop('disabled', true).addClass('disabled');
//$('.app-frm-buttons button').attr('disabled', 'disabled');
$("body").css("cursor", "progress");
//add app-ajax-busy style to buttons div where user clicked
$('.app-frm-buttons').addClass('app-ajax-busy');
}).ajaxStop(function () {
//with delay in case it's too fast to see
setTimeout(function () {
$('.app-frm-buttons button').prop('disabled', false).removeClass('disabled');
//$('.app-frm-buttons button').removeAttr('disabled');
$("body").css("cursor", "default");
$('.app-frm-buttons').removeClass('app-ajax-busy');
}, 250);
});
// load HTML and map jQuery collections
stateMap.$container = $container;
$container.html(Handlebars.templates['app.shell']({}));
//auto hide navbar on click of nav link item
$('.nav-link').on('click',function() {
$('.navbar-collapse').collapse('hide');
});
//determine API url and save it
stateMap.apiUrl = app.utilB.getApiUrl();
//fetch stored creds if available
var creds = store.get('pecklist.usercreds');
//check if token has expired
if (tokenExpired()) {
stateMap.user.authenticated = false;
stateMap.user.name = '';
stateMap.user.token = '';
stateMap.user.id=0;
} else {
//Show the logout item in the menu
$(".app-mnu-logout").removeClass("app-hidden");
stateMap.user.authenticated = true;
stateMap.user.name = creds.name;
stateMap.user.token = creds.token;
stateMap.user.dlkey=creds.dlkey;
stateMap.user.id=creds.id;
}
//}
//EVENT SUBSCRIPTIONS
$.gevent.subscribe($container, 'app-login', onLogin);
$.gevent.subscribe($container, 'app-logout', onLogout);
//$.gevent.subscribe($container, 'gz-update', onUpdate);
$.gevent.subscribe($container, 'app-show-error', onShowError);
$.gevent.subscribe($container, 'app-clear-error', onClearError);
//ROUTES
//
page.base('/index.html');
page('*', beforeUrlChange);
page('/authenticate', authenticate);
page('/', main);
page('/logout', function () {
$.gevent.publish('app-logout');
});
page('/main', main);
page('/settings', settings);
page('*', notFound);
page({
hashbang: true
});
// /ROUTES
};
// End PUBLIC method /initModule/
var beforeUrlChange = function (ctx, next) {
$.gevent.publish('app-clear-error');
app.shell.stateMap.mediaSize = app.utilB.getMediaSize();
//bypass stuff below if about to logout
if (ctx.path == '/logout') {
return next();
}
//================================================================
//Check authentication token to see if expired, but only if it's been a few minutes since last navigation
if (stateMap.user.authenticated) {
//This first bit sets the last nav in cases where it's never been set before
//default to one hour ago in case it hasn't ever been set yet or isn't in statemap
if (!stateMap.lastNav) {
//Isn't this sketchy? Is this a date or a moment being set here
stateMap.lastNav = moment().subtract(3600, 's').toDate();//60 minutes ago
}
var mNow = moment(new Date()); //todays date
var mLastNav = moment(stateMap.lastNav); // another date
var duration = moment.duration(mNow.diff(mLastNav));
var secondsSinceLastNav = duration.asSeconds();
if (secondsSinceLastNav > 300)//have we checked in the last 5 minutes?
{
if (tokenExpired()) {
stateMap.user.authenticated = false;
}
}
stateMap.lastNav = new Date();
}
//===============================================================
//Not logged in and trying to go somewhere but authenticate?
if (!stateMap.user.authenticated) {
//hide nav
$("#gz-nav").hide({
duration: 200
});
//page nav to authenticate
if (ctx.path != '/authenticate/')
return page('#!/authenticate/');
} else {
//logged in so make sure to show toolbar here
$("#gz-nav").show({
duration: 200
});
}
next();
}
var authenticate = function (ctx) {
app.authenticate.configModule({
context: ctx
});
app.authenticate.initModule();
}
var main = function (ctx) {
app.nav.setSelectedMenuItem('main');
app.main.configModule({
context: ctx
});
app.main.initModule();
}
var settings = function (ctx) {
app.nav.setSelectedMenuItem('settings');
app.settings.configModule({
context: ctx
});
app.settings.initModule();
}
var notFound = function (ctx) {
app.fourohfour.configModule({
context: ctx
});
app.fourohfour.initModule();
}
return {
initModule: initModule,
stateMap: stateMap
};
//------------------- END PUBLIC METHODS ---------------------
}());

80
wwwroot/js/app.util.js Normal file
View File

@@ -0,0 +1,80 @@
/*
* app.util.js
* General JavaScript utilities
*
* Michael S. Mikowski - mmikowski at gmail dot com
* These are routines I have created, compiled, and updated
* since 1998, with inspiration from around the web.
*
* MIT License
*
*/
/*jslint browser : true, continue : true,
devel : true, indent : 2, maxerr : 50,
newcap : true, nomen : true, plusplus : true,
regexp : true, sloppy : true, vars : false,
white : true
*/
/*global $, app */
app.util = (function () {
var makeError, setConfigMap;
// Begin Public constructor /makeError/
// Purpose: a convenience wrapper to create an error object
// Arguments:
// * name_text - the error name
// * msg_text - long error message
// * data - optional data attached to error object
// Returns : newly constructed error object
// Throws : none
//
makeError = function ( name_text, msg_text, data ) {
var error = new Error();
error.name = name_text;
error.message = msg_text;
if ( data ){ error.data = data; }
return error;
};
// End Public constructor /makeError/
// Begin Public method /setConfigMap/
// Purpose: Common code to set configs in feature modules
// Arguments:
// * input_map - map of key-values to set in config
// * settable_map - map of allowable keys to set
// * config_map - map to apply settings to
// Returns: true
// Throws : Exception if input key not allowed
//
setConfigMap = function ( arg_map ){
var
input_map = arg_map.input_map,
settable_map = arg_map.settable_map,
config_map = arg_map.config_map,
key_name, error;
for ( key_name in input_map ){
if ( input_map.hasOwnProperty( key_name ) ){
if ( settable_map.hasOwnProperty( key_name ) ){
config_map[key_name] = input_map[key_name];
}
else {
error = makeError( 'Bad Input',
'Setting config key |' + key_name + '| is not supported'
);
throw error;
}
}
}
};
// End Public method /setConfigMap/
return {
makeError : makeError,
setConfigMap : setConfigMap
};
}());

334
wwwroot/js/app.utilB.js Normal file
View File

@@ -0,0 +1,334 @@
/**
* app.utilB.js
* JavaScript browser utilities
*
*/
/*jslint browser : true, continue : true,
devel : true, indent : 2, maxerr : 50,
newcap : true, nomen : true, plusplus : true,
regexp : true, sloppy : true, vars : false,
white : true
*/
/*global $, app, getComputedStyle */
app.utilB = (function () {
'use strict';
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
var
configMap = {
regex_encode_html: /[&"'><]/g,
regex_encode_noamp: /["'><]/g,
html_encode_map: {
'&': '&#38;',
'"': '&#34;',
"'": '&#39;',
'>': '&#62;',
'<': '&#60;'
}
},
decodeHtml, encodeHtml, getEmSize, getApiUrl, getUrlParams, formData, objectifyFormDataArray,
getMediaSize, prepareObjectForClient, fixDatesToStrings,
epochToShortDate, epochToLocalShortDate, epochToLocalShortDateTime, getCurrentDateTimeAsEpoch,
genListColumn,
genListColumnNoLink, autoComplete, prepareObjectDatesForServer,
fixStringToServerDate;
configMap.encode_noamp_map = $.extend({}, configMap.html_encode_map);
delete configMap.encode_noamp_map['&'];
//----------------- END MODULE SCOPE VARIABLES ---------------
//------------------- BEGIN UTILITY METHODS ------------------
// Begin decodeHtml
// Decodes HTML entities in a browser-friendly way
// See http://stackoverflow.com/questions/1912501/\
// unescape-html-entities-in-javascript
//
decodeHtml = function (str) {
return $('<div/>').html(str || '').text();
};
// End decodeHtml
// Begin encodeHtml
// This is single pass encoder for html entities and handles
// an arbitrary number of characters
//
encodeHtml = function (input_arg_str, exclude_amp) {
var
input_str = String(input_arg_str),
regex, lookup_map;
if (exclude_amp) {
lookup_map = configMap.encode_noamp_map;
regex = configMap.regex_encode_noamp;
} else {
lookup_map = configMap.html_encode_map;
regex = configMap.regex_encode_html;
}
return input_str.replace(regex,
function (match, name) {
return lookup_map[match] || '';
}
);
};
// End encodeHtml
// Begin getEmSize
// returns size of ems in pixels
//
getEmSize = function (elem) {
return Number(
getComputedStyle(elem, '').fontSize.match(/\d*\.?\d*/)[0]
);
};
// End getEmSize
//Begin getApiUrl
//returns url for api methods by parsing current window location url
//
getApiUrl = function () {
var u = window.location.href.replace(window.location.hash, "").replace('index.html', '') + "api/";
//is it a dev local url?
if (u.indexOf("localhost:8080") != -1) {
u = u.replace("8080", "8081");
}
// End getApiUrl
//fix for random recurrence of extraneous ? on iPhone when using api
//(Cannot post to http://gl-gztw.rhcloud.com/?api/list (404))
u = u.replace("?", "");
return u;
}
// Begin formData
// Get or set all form fields
//
formData = function (data) {
//fix dates into strings
prepareObjectForClient(data);
var inps = $(":input").get();
if (typeof data != "object") {
// return all data
data = {};
$.each(inps, function () {
if (this.name && (this.checked || /select|textarea/i.test(this.nodeName) || /text|hidden|password/i.test(this.type))) {
data[this.name] = $(this).val();
}
});
return data;
} else {
$.each(inps, function () {
if (this.name && data[this.name]) {
if (this.type == "checkbox" || this.type == "radio") {
$(this).prop("checked", (data[this.name]));
} else {
$(this).val(data[this.name]);
}
} else if (this.type == "checkbox") {
$(this).prop("checked", false);
}
});
return $(this);
}
};
// End formdata
// Begin getMediaSize
// Retrieves results of css media query in hidden pseudo element
// :after body element
//objectifyFormDataArray
getMediaSize = function () {
return window.getComputedStyle(document.querySelector('body'), ':after').getPropertyValue('content').replace(/\"/g, '');
};
// End getMediaSize
//Begin objectifyFormDataArray
//takes name value form input pairs in array and turns into a keyed object
//suitable for sending as json object
//
objectifyFormDataArray = function (arr) {
var rv = {};
for (var i = 0; i < arr.length; ++i)
if (arr[i] !== undefined) {
rv[arr[i].name] = arr[i].value.trim();//case 3205 added trim
}
return prepareObjectDatesForServer(rv);
}
//Prepare an object for server needed format before submission
//called by objectifyFormDataArray
prepareObjectDatesForServer = function (obj) {
if (Array.isArray(obj)) {
for (var i = 0; i < obj.length; i++) {
fixStringToServerDate(obj[i]);
}
} else {
fixStringToServerDate(obj);
}
return obj;
}
//
//Turn string date fields from client into server compatible format (Unix epoch like this: 1498262400 1499904000)
fixStringToServerDate = function (obj) {
var keys = Object.keys(obj);
keys.forEach(function (key) {
if (key.endsWith('Date') || key.startsWith('dt')) {
var value = obj[key];
if (value == null) {
obj[key] = '';
} else {
//this is the sample format we will see: 2017-07-13
//TODO: is this assuming UTC?
obj[key] = moment.utc(value, "YYYY-MM-DD").unix();
}
}
});
}
//This function exists to change the properties of the passed in object
//to values compatible with jquery form filling functions (mostly dates to strings for now)
//
prepareObjectForClient = function (obj) {
if (Array.isArray(obj)) {
for (var i = 0; i < obj.length; i++) {
fixDatesToStrings(obj[i]);
}
} else {
fixDatesToStrings(obj);
}
return obj;
}
//
//Turn date fields of object coming from db into stringified values for consumption by client
//turn null dates into empty strings and iso date values into strings in iso format
fixDatesToStrings = function (obj) {
var keys = Object.keys(obj);
keys.forEach(function (key) {
if (key.endsWith('Date') || key.startsWith('dt')) {
var value = obj[key];
if (value == null) {
obj[key] = '';
} else {
//Now with sqlite they come and go as unix epoch seconds
//needs to be yyyy-MM-dd
obj[key] = moment.utc(new Date(value * 1000)).format("YYYY-MM-DD");
}
}
});
}
//
//Turn date values coming in from server into displayable short date format
//used to display dates in various lists (where the source epoch is already localized)
epochToShortDate = function (epoch) {
if (epoch == null || epoch == 0) return '';
return moment.utc(new Date(epoch * 1000)).format("YYYY-MM-DD");
}
//
//LOCAL VERSION: Turn date values coming in from server into displayable short date format
//used to display dates in various lists where the source epoch is in UTC
epochToLocalShortDate = function (epoch) {
if (epoch == null || epoch == 0) return '';
var utdate = moment.utc(new Date(epoch * 1000));
var localdate = moment(utdate).local();
return localdate.format("YYYY-MM-DD");
}
//LOCAL VERSION: Turn date values coming in from server into displayable short date AND TIME format
//used to display dates in various lists where the source epoch is in UTC
epochToLocalShortDateTime = function (epoch) {
if (epoch == null || epoch == 0) return '';
var utdate = moment.utc(new Date(epoch * 1000));
var localdate = moment(utdate).local();
return localdate.format("YYYY-MM-DD LT");
}
//////////////////////////
//Get current date and time as a utc unix epoch
getCurrentDateTimeAsEpoch = function () {
return moment().utc().unix();
}
// Begin genListColumn
// This function is used to demarcate 'columns' of fields in basic list forms by wrapping each column field in html
//
genListColumn = function (val) {
return '<span class="gz-list-column">' + val + '</span>'
};
// End genListColumn
// Begin genListColumnNoLink
// This function is used to demarcate and style 'columns' of fields in basic list forms by wrapping each column field in html
// that are not link columns
genListColumnNoLink = function (val) {
return '<span class="gz-list-column-nolink">' + val + '</span>'
};
// End genListColumn
// Begin autoComplete
// This function is used to attach an autocomplete method to an input
//
autoComplete = function (controlId, acGetToken) {
$('#' + controlId).autocomplete({
serviceUrl: app.shell.stateMap.apiUrl + 'autocomplete',
params: {
acget: acGetToken
},
ajaxSettings: {
headers: app.api.getAuthHeaderObject()
}
});
};
// End autoComplete
// export methods
return {
decodeHtml: decodeHtml,
encodeHtml: encodeHtml,
getEmSize: getEmSize,
getApiUrl: getApiUrl,
getUrlParams: getUrlParams,
formData: formData,
getMediaSize: getMediaSize,
objectifyFormDataArray: objectifyFormDataArray,
epochToShortDate: epochToShortDate,
epochToLocalShortDate: epochToLocalShortDate,
epochToLocalShortDateTime: epochToLocalShortDateTime,
getCurrentDateTimeAsEpoch: getCurrentDateTimeAsEpoch,
genListColumn: genListColumn,
genListColumnNoLink: genListColumnNoLink,
autoComplete: autoComplete
};
//------------------- END PUBLIC METHODS ---------------------
}());

22
wwwroot/js/index.js Normal file
View File

@@ -0,0 +1,22 @@
/*
* app.js
* Root namespace module
*/
/*jslint browser : true, continue : true,
devel : true, indent : 2, maxerr : 50,
newcap : true, nomen : true, plusplus : true,
regexp : true, sloppy : true, vars : false,
white : true
*/
/*global $, app */
var app = (function () {
'use strict';
var initModule = function ( $container ) {
app.api.initModule();
app.shell.initModule( $container );
};
return { initModule: initModule };
}());

File diff suppressed because it is too large Load Diff

4
wwwroot/js/lib/jquery-3.2.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,154 @@
/*
* jQuery global custom event plugin (gevent)
*
* Copyright (c) 2013 Michael S. Mikowski
* (mike[dot]mikowski[at]gmail[dotcom])
*
* Dual licensed under the MIT or GPL Version 2
* http://jquery.org/license
*
* Versions
* 0.1.5 - initial release
* 0.1.6 - enhanced publishEvent (publish) method pass
* a non-array variable as the second argument
* to a subscribed function (the first argument
* is always the event object).
* 0.1.7-10, 0.2.0
* - documentation changes
* 1.0.2 - cleaned-up logic, bumped version
* 1.1.2 - added keywords
*
*/
/*jslint browser : true, continue : true,
devel : true, indent : 2, maxerr : 50,
newcap : true, nomen : true, plusplus : true,
regexp : true, sloppy : true, vars : false,
white : true
*/
/*global jQuery*/
(function ( $ ) {
'use strict';
$.gevent = (function () {
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
var
subscribeEvent, publishEvent, unsubscribeEvent,
$customSubMap = {}
;
//----------------- END MODULE SCOPE VARIABLES ---------------
//------------------- BEGIN PUBLIC METHODS -------------------
// BEGIN public method /publishEvent/
// Example :
// $.gevent.publish(
// 'spa-model-msg-receive',
// [ { user : 'fred', msg : 'Hi gang' } ]
// );
// Purpose :
// Publish an event with an optional list of arguments
// which a subscribed handler will receive after the event object.
// Arguments (positional)
// * 0 ( event_name ) - The global event name
// * 1 ( data ) - Optional data to be passed as argument(s)
// to subscribed functions after the event
// object. Provide an array for multiple
// arguments.
// Throws : none
// Returns : none
//
publishEvent = function () {
var arg_list = [],
arg_count, event_name,
event_obj, data, data_list;
arg_list = arg_list.slice.call( arguments, 0 );
arg_count = arg_list.length;
if ( arg_count === 0 ) { return false; }
event_name = arg_list.shift();
event_obj = $customSubMap[ event_name ];
if ( ! event_obj ) { return false; }
if ( arg_count > 1 ) {
data = arg_list.shift();
data_list = $.isArray( data ) ? data : [ data ];
}
else {
data_list = [];
}
event_obj.trigger( event_name, data_list );
return true;
};
// END public method /publishEvent/
// BEGIN public method /subscribeEvent/
// Example :
// $.gevent.subscribe(
// $( '#msg' ),
// 'spa-msg-receive',
// onModelMsgReceive
// );
// Purpose :
// Subscribe a function to a published event on a jQuery collection
// Arguments (positional)
// * 0 ( $collection ) - The jQuery collection on which to bind event
// * 1 ( event_name ) - The global event name
// * 2 ( fn ) - The function to bound to the event on the collection
// Throws : none
// Returns : none
//
subscribeEvent = function ( $collection, event_name, fn ) {
$collection.on( event_name, fn );
if ( $customSubMap[ event_name ] ) {
$customSubMap[ event_name ]
= $customSubMap[ event_name ].add( $collection );
}
else {
$customSubMap[ event_name ] = $collection;
}
};
// END public method /subscribeEvent/
// BEGIN public method /unsubscribeEvent/
// Example :
// $.gevent.unsubscribe(
// $( '#msg' ),
// 'spa-model-msg-receive'
// );
// Purpose :
// Remove a binding for the named event on a provided collection
// Arguments (positional)
// * 0 ( $collection ) - The jQuery collection on which to bind event
// * 1 ( event_name ) - The global event name
// Throws : none
// Returns : none
//
unsubscribeEvent = function ( $collection, event_name ) {
if ( ! $customSubMap[ event_name ] ){ return false; }
$customSubMap[ event_name ]
= $customSubMap[ event_name ].not( $collection );
if ( $customSubMap[ event_name ].length === 0 ){
delete $customSubMap[ event_name ];
}
return true;
};
// END public method /unsubscribeEvent/
//------------------- END PUBLIC METHODS ---------------------
// return public methods
return {
publish : publishEvent,
subscribe : subscribeEvent,
unsubscribe : unsubscribeEvent
};
}());
}( jQuery ));

View File

@@ -0,0 +1,783 @@
/*
* jQuery plugin for unified mouse and touch events
*
* Copyright (c) 2013 Michael S. Mikowski
* (mike[dot]mikowski[at]gmail[dotcom])
*
* Dual licensed under the MIT or GPL Version 2
* http://jquery.org/license
*
* Versions
* 1.2.0 - ignore_class => ignore_select, now defaults to ''
* 1.1.9 - Fixed ue-test.html demo to scale properly
* 1.1.8 - Removed prevent default from non-ue events
* 1.1.7 - Corrected desktop zoom motion description
* 1.1.0-5 - No code changes. Updated npm keywords. Fixed typos.
* Bumped version to represent maturity and stability.
* 0.6.1 - Change px_radius from 5 to 10 pixels
* 0.6.0 - Added px_tdelta_x and px_tdelta_y for deltas from start
* - Fixed onheld and drag conflicts
* 0.5.0 - Updated docs, removed cruft, updated for jslint,
* updated test page (zoom)
* 0.4.3 - Removed fatal execption possibility if originalEvent
* is not defined on event object
* 0.4.2 - Updated documentation
* 0.3.2 - Updated to jQuery 1.9.1.
* Confirmed 1.7.0-1.9.1 compatibility.
* 0.3.1 - Change for jQuery plugins site
* 0.3.0 - Initial jQuery plugin site release
* - Replaced scrollwheel zoom with drag motion.
* This resolved a conflict with scrollable areas.
*
*/
/*jslint browser : true, continue : true,
devel : true, indent : 2, maxerr : 50,
newcap : true, plusplus : true, regexp : true,
sloppy : true, vars : false, white : true
*/
/*global jQuery */
(function ( $ ) {
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
var
$Special = $.event.special, // Shortcut for special event
motionMapMap = {}, // Map of pointer motions by cursor
isMoveBound = false, // Flag if move handlers bound
pxPinchZoom = -1, // Distance between pinch-zoom points
optionKey = 'ue_bound', // Data key for storing options
doDisableMouse = false, // Flag to discard mouse input
defaultOptMap = { // Default option map
bound_ns_map : {}, // Map of bound namespaces e.g.
// bound_ns_map.utap.fred
px_radius : 10, // Tolerated distance before dragstart
ignore_select : '', // Selector of elements to ignore (e.g. :input)
max_tap_ms : 200, // Maximum time allowed for tap
min_held_ms : 300 // Minimum time require for long-press
},
callbackList = [], // global callback stack
zoomMouseNum = 1, // multiplier for mouse zoom
zoomTouchNum = 4, // multiplier for touch zoom
boundList, Ue,
motionDragId, motionHeldId, motionDzoomId,
motion1ZoomId, motion2ZoomId,
checkMatchVal, removeListVal, pushUniqVal, makeListPlus,
fnHeld, fnMotionStart, fnMotionMove,
fnMotionEnd, onMouse, onTouch
;
//----------------- END MODULE SCOPE VARIABLES ---------------
//------------------- BEGIN UTILITY METHODS ------------------
// Begin utiltity /makeListPlus/
// Returns an array with much desired methods:
// * remove_val(value) : remove element that matches
// the provided value. Returns number of elements
// removed.
// * match_val(value) : shows if a value exists
// * push_uniq(value) : pushes a value onto the stack
// iff it does not already exist there
// Note: the reason I need this is to compare objects to
// objects (perhaps jQuery has something similar?)
checkMatchVal = function ( data ) {
var match_count = 0, idx;
for ( idx = this.length; idx; 0 ) {
if ( this[--idx] === data ) { match_count++; }
}
return match_count;
};
removeListVal = function ( data ) {
var removed_count = 0, idx;
for ( idx = this.length; idx; 0 ) {
if ( this[--idx] === data ) {
this.splice(idx, 1);
removed_count++;
idx++;
}
}
return removed_count;
};
pushUniqVal = function ( data ) {
if ( checkMatchVal.call(this, data ) ) { return false; }
this.push( data );
return true;
};
// primary utility
makeListPlus = function ( input_list ) {
if ( input_list && $.isArray(input_list) ) {
if ( input_list.remove_val ) {
console.warn( 'The array appears to already have listPlus capabilities' );
return input_list;
}
}
else {
input_list = [];
}
input_list.remove_val = removeListVal;
input_list.match_val = checkMatchVal;
input_list.push_uniq = pushUniqVal;
return input_list;
};
// End utility /makeListPlus/
//-------------------- END UTILITY METHODS -------------------
//--------------- BEGIN JQUERY SPECIAL EVENTS ----------------
// Unique array for bound objects
boundList = makeListPlus();
// Begin define special event handlers
Ue = {
setup : function( data, name_list, bind_fn ) {
var
this_el = this,
$to_bind = $(this_el),
seen_map = {},
option_map, idx, namespace_key, ue_namespace_code, namespace_list
;
// jslint hack to allow unused arguments
if ( data && bind_fn ) { console.log( 'unused arguments' ); }
// if previous related event bound do not rebind, but do add to
// type of event bound to this element, if not already noted
if ( $.data( this, optionKey ) ) { return; }
option_map = {};
$.extend( true, option_map, defaultOptMap );
$.data( this_el, optionKey, option_map );
namespace_list = makeListPlus(name_list.slice(0));
if ( ! namespace_list.length
|| namespace_list[0] === ""
) { namespace_list = ["000"]; }
NSPACE_00:
for ( idx = 0; idx < namespace_list.length; idx++ ) {
namespace_key = namespace_list[idx];
if ( ! namespace_key ) { continue NSPACE_00; }
if ( seen_map.hasOwnProperty(namespace_key) ) { continue NSPACE_00; }
seen_map[namespace_key] = true;
ue_namespace_code = '.__ue' + namespace_key;
$to_bind.bind( 'mousedown' + ue_namespace_code, onMouse );
$to_bind.bind( 'touchstart' + ue_namespace_code, onTouch );
}
boundList.push_uniq( this_el ); // record as bound element
if ( ! isMoveBound ) {
// console.log('first element bound - adding global binds');
$(document).bind( 'mousemove.__ue', onMouse );
$(document).bind( 'touchmove.__ue', onTouch );
$(document).bind( 'mouseup.__ue' , onMouse );
$(document).bind( 'touchend.__ue' , onTouch );
$(document).bind( 'touchcancel.__ue', onTouch );
isMoveBound = true;
}
},
// arg_map.type = string - name of event to bind
// arg_map.data = poly - whatever (optional) data was passed when binding
// arg_map.namespace = string - A sorted, dot-delimited list of namespaces
// specified when binding the event
// arg_map.handler = fn - the event handler the developer wishes to be bound
// to the event. This function should be called whenever the event
// is triggered
// arg_map.guid = number - unique ID for event handler, provided by jQuery
// arg_map.selector = string - selector used by 'delegate' or 'live' jQuery
// methods. Only available when these methods are used.
//
// this - the element to which the event handler is being bound
// this always executes immediate after setup (if first binding)
add : function ( arg_map ) {
var
this_el = this,
option_map = $.data( this_el, optionKey ),
namespace_str = arg_map.namespace,
event_type = arg_map.type,
bound_ns_map, namespace_list, idx, namespace_key
;
if ( ! option_map ) { return; }
bound_ns_map = option_map.bound_ns_map;
if ( ! bound_ns_map[event_type] ) {
// this indicates a non-namespaced entry
bound_ns_map[event_type] = {};
}
if ( ! namespace_str ) { return; }
namespace_list = namespace_str.split('.');
for ( idx = 0; idx < namespace_list.length; idx++ ) {
namespace_key = namespace_list[idx];
bound_ns_map[event_type][namespace_key] = true;
}
},
remove : function ( arg_map ) {
var
elem_bound = this,
option_map = $.data( elem_bound, optionKey ),
bound_ns_map = option_map.bound_ns_map,
event_type = arg_map.type,
namespace_str = arg_map.namespace,
namespace_list, idx, namespace_key
;
if ( ! bound_ns_map[event_type] ) { return; }
// No namespace(s) provided:
// Remove complete record for custom event type (e.g. utap)
if ( ! namespace_str ) {
delete bound_ns_map[event_type];
return;
}
// Namespace(s) provided:
// Remove namespace flags from each custom event typei (e.g. utap)
// record. If all claimed namespaces are removed, remove
// complete record.
namespace_list = namespace_str.split('.');
for ( idx = 0; idx < namespace_list.length; idx++ ) {
namespace_key = namespace_list[idx];
if (bound_ns_map[event_type][namespace_key]) {
delete bound_ns_map[event_type][namespace_key];
}
}
if ( $.isEmptyObject( bound_ns_map[event_type] ) ) {
delete bound_ns_map[event_type];
}
},
teardown : function( name_list ) {
var
elem_bound = this,
$bound = $(elem_bound),
option_map = $.data( elem_bound, optionKey ),
bound_ns_map = option_map.bound_ns_map,
idx, namespace_key, ue_namespace_code, namespace_list
;
// do not tear down if related handlers are still bound
if ( ! $.isEmptyObject( bound_ns_map ) ) { return; }
namespace_list = makeListPlus(name_list);
namespace_list.push_uniq('000');
NSPACE_01:
for ( idx = 0; idx < namespace_list.length; idx++ ) {
namespace_key = namespace_list[idx];
if ( ! namespace_key ) { continue NSPACE_01; }
ue_namespace_code = '.__ue' + namespace_key;
$bound.unbind( 'mousedown' + ue_namespace_code );
$bound.unbind( 'touchstart' + ue_namespace_code );
$bound.unbind( 'mousewheel' + ue_namespace_code );
}
$.removeData( elem_bound, optionKey );
// Unbind document events only after last element element is removed
boundList.remove_val(this);
if ( boundList.length === 0 ) {
// console.log('last bound element removed - removing global binds');
$(document).unbind( 'mousemove.__ue');
$(document).unbind( 'touchmove.__ue');
$(document).unbind( 'mouseup.__ue');
$(document).unbind( 'touchend.__ue');
$(document).unbind( 'touchcancel.__ue');
isMoveBound = false;
}
}
};
// End define special event handlers
//--------------- BEGIN JQUERY SPECIAL EVENTS ----------------
//------------------ BEGIN MOTION CONTROLS -------------------
// Begin motion control /fnHeld/
fnHeld = function ( arg_map ) {
var
timestamp = +new Date(),
motion_id = arg_map.motion_id,
motion_map = arg_map.motion_map,
bound_ns_map = arg_map.bound_ns_map,
event_ue
;
delete motion_map.tapheld_toid;
if ( ! motion_map.do_allow_held ) { return; }
motion_map.px_end_x = motion_map.px_start_x;
motion_map.px_end_y = motion_map.px_start_y;
motion_map.ms_timestop = timestamp;
motion_map.ms_elapsed = timestamp - motion_map.ms_timestart;
if ( bound_ns_map.uheld ) {
event_ue = $.Event('uheld');
$.extend( event_ue, motion_map );
$(motion_map.elem_bound).trigger(event_ue);
}
// remove tracking, as we want no futher action on this motion
if ( bound_ns_map.uheldstart ) {
event_ue = $.Event('uheldstart');
$.extend( event_ue, motion_map );
$(motion_map.elem_bound).trigger(event_ue);
motionHeldId = motion_id;
}
else {
delete motionMapMap[motion_id];
}
};
// End motion control /fnHeld/
// Begin motion control /fnMotionStart/
fnMotionStart = function ( arg_map ) {
var
motion_id = arg_map.motion_id,
event_src = arg_map.event_src,
request_dzoom = arg_map.request_dzoom,
option_map = $.data( arg_map.elem, optionKey ),
bound_ns_map = option_map.bound_ns_map,
$target = $(event_src.target ),
do_zoomstart = false,
motion_map, cb_map, event_ue
;
// this should never happen, but it does
if ( motionMapMap[ motion_id ] ) { return; }
// ignore on zoom
if ( request_dzoom && ! bound_ns_map.uzoomstart ) { return; }
// :input selector includes text areas
if ( $target.is( option_map.ignore_select ) ) { return; }
// Prevent default only after confirming handling this event
event_src.preventDefault();
cb_map = callbackList.pop();
while ( cb_map ) {
if ( $target.is( cb_map.selector_str )
|| $( arg_map.elem ).is( cb_map.selector_str )
) {
if ( cb_map.callback_match ) {
cb_map.callback_match( arg_map );
}
}
else {
if ( cb_map.callback_nomatch ) {
cb_map.callback_nomatch( arg_map );
}
}
cb_map = callbackList.pop();
}
motion_map = {
do_allow_tap : bound_ns_map.utap ? true : false,
do_allow_held : ( bound_ns_map.uheld || bound_ns_map.uheldstart )
? true : false,
elem_bound : arg_map.elem,
elem_target : event_src.target,
ms_elapsed : 0,
ms_timestart : event_src.timeStamp,
ms_timestop : undefined,
option_map : option_map,
orig_target : event_src.target,
px_current_x : event_src.clientX,
px_current_y : event_src.clientY,
px_end_x : undefined,
px_end_y : undefined,
px_start_x : event_src.clientX,
px_start_y : event_src.clientY,
timeStamp : event_src.timeStamp
};
motionMapMap[ motion_id ] = motion_map;
if ( bound_ns_map.uzoomstart ) {
if ( request_dzoom ) {
motionDzoomId = motion_id;
}
else if ( ! motion1ZoomId ) {
motion1ZoomId = motion_id;
}
else if ( ! motion2ZoomId ) {
motion2ZoomId = motion_id;
event_ue = $.Event('uzoomstart');
do_zoomstart = true;
}
if ( do_zoomstart ) {
event_ue = $.Event( 'uzoomstart' );
motion_map.px_delta_zoom = 0;
$.extend( event_ue, motion_map );
$(motion_map.elem_bound).trigger(event_ue);
return;
}
}
if ( bound_ns_map.uheld || bound_ns_map.uheldstart ) {
motion_map.tapheld_toid = setTimeout(
function() {
fnHeld({
motion_id : motion_id,
motion_map : motion_map,
bound_ns_map : bound_ns_map
});
},
option_map.min_held_ms
);
}
};
// End motion control /fnMotionStart/
// Begin motion control /fnMotionMove/
fnMotionMove = function ( arg_map ) {
var
motion_id = arg_map.motion_id,
event_src = arg_map.event_src,
do_zoommove = false,
motion_map, option_map, bound_ns_map,
is_over_rad, event_ue, px_pinch_zoom,
px_delta_zoom, mzoom1_map, mzoom2_map
;
if ( ! motionMapMap[ motion_id ] ) { return; }
// Prevent default only after confirming handling this event
event_src.preventDefault();
motion_map = motionMapMap[motion_id];
option_map = motion_map.option_map;
bound_ns_map = option_map.bound_ns_map;
motion_map.timeStamp = event_src.timeStamp;
motion_map.elem_target = event_src.target;
motion_map.ms_elapsed = event_src.timeStamp - motion_map.ms_timestart;
motion_map.px_delta_x = event_src.clientX - motion_map.px_current_x;
motion_map.px_delta_y = event_src.clientY - motion_map.px_current_y;
motion_map.px_current_x = event_src.clientX;
motion_map.px_current_y = event_src.clientY;
motion_map.px_tdelta_x = motion_map.px_start_x - event_src.clientX;
motion_map.px_tdelta_y = motion_map.px_start_y - event_src.clientY;
is_over_rad = (
Math.abs( motion_map.px_tdelta_x ) > option_map.px_radius
|| Math.abs( motion_map.px_tdelta_y ) > option_map.px_radius
);
// native event object override
motion_map.timeStamp = event_src.timeStamp;
// disallow held or tap if outside of zone
if ( is_over_rad ) {
motion_map.do_allow_tap = false;
motion_map.do_allow_held = false;
}
// disallow tap if time has elapsed
if ( motion_map.ms_elapsed > option_map.max_tap_ms ) {
motion_map.do_allow_tap = false;
}
if ( motion1ZoomId && motion2ZoomId
&& ( motion_id === motion1ZoomId
|| motion_id === motion2ZoomId
)) {
motionMapMap[motion_id] = motion_map;
mzoom1_map = motionMapMap[motion1ZoomId];
mzoom2_map = motionMapMap[motion2ZoomId];
px_pinch_zoom = Math.floor(
Math.sqrt(
Math.pow((mzoom1_map.px_current_x - mzoom2_map.px_current_x),2)
+ Math.pow((mzoom1_map.px_current_y - mzoom2_map.px_current_y),2)
) +0.5
);
if ( pxPinchZoom === -1 ) { px_delta_zoom = 0; }
else { px_delta_zoom = ( px_pinch_zoom - pxPinchZoom ) * zoomTouchNum;}
// save value for next iteration delta comparison
pxPinchZoom = px_pinch_zoom;
do_zoommove = true;
}
else if ( motionDzoomId === motion_id ) {
if ( bound_ns_map.uzoommove ) {
px_delta_zoom = motion_map.px_delta_y * zoomMouseNum;
do_zoommove = true;
}
}
if ( do_zoommove ){
event_ue = $.Event('uzoommove');
motion_map.px_delta_zoom = px_delta_zoom;
$.extend( event_ue, motion_map );
$(motion_map.elem_bound).trigger(event_ue);
return;
}
if ( motionHeldId === motion_id ) {
if ( bound_ns_map.uheldmove ) {
event_ue = $.Event('uheldmove');
$.extend( event_ue, motion_map );
$(motion_map.elem_bound).trigger(event_ue);
}
return;
}
if ( motionDragId === motion_id ) {
if ( bound_ns_map.udragmove ) {
event_ue = $.Event('udragmove');
$.extend( event_ue, motion_map );
$(motion_map.elem_bound).trigger(event_ue);
}
return;
}
if ( bound_ns_map.udragstart
&& motion_map.do_allow_tap === false
&& motion_map.do_allow_held === false
&& !( motionDragId && motionHeldId )
) {
motionDragId = motion_id;
event_ue = $.Event('udragstart');
$.extend( event_ue, motion_map );
$(motion_map.elem_bound).trigger(event_ue);
if ( motion_map.tapheld_toid ) {
clearTimeout(motion_map.tapheld_toid);
delete motion_map.tapheld_toid;
}
}
};
// End motion control /fnMotionMove/
// Begin motion control /fnMotionEnd/
fnMotionEnd = function ( arg_map ) {
var
motion_id = arg_map.motion_id,
event_src = arg_map.event_src,
do_zoomend = false,
motion_map, option_map, bound_ns_map, event_ue
;
doDisableMouse = false;
if ( ! motionMapMap[motion_id] ) { return; }
motion_map = motionMapMap[motion_id];
option_map = motion_map.option_map;
bound_ns_map = option_map.bound_ns_map;
motion_map.elem_target = event_src.target;
motion_map.ms_elapsed = event_src.timeStamp - motion_map.ms_timestart;
motion_map.ms_timestop = event_src.timeStamp;
if ( motion_map.px_current_x ) {
motion_map.px_delta_x = event_src.clientX - motion_map.px_current_x;
motion_map.px_delta_y = event_src.clientY - motion_map.px_current_y;
}
motion_map.px_current_x = event_src.clientX;
motion_map.px_current_y = event_src.clientY;
motion_map.px_end_x = event_src.clientX;
motion_map.px_end_y = event_src.clientY;
motion_map.px_tdelta_x = motion_map.px_start_x - motion_map.px_end_x;
motion_map.px_tdelta_y = motion_map.px_start_y - motion_map.px_end_y;
// native event object override
motion_map.timeStamp = event_src.timeStamp
;
// clear-out any long-hold tap timer
if ( motion_map.tapheld_toid ) {
clearTimeout(motion_map.tapheld_toid);
delete motion_map.tapheld_toid;
}
// trigger utap
if ( bound_ns_map.utap
&& motion_map.ms_elapsed <= option_map.max_tap_ms
&& motion_map.do_allow_tap
) {
event_ue = $.Event('utap');
$.extend( event_ue, motion_map );
$(motion_map.elem_bound).trigger(event_ue);
}
// trigger udragend
if ( motion_id === motionDragId ) {
if ( bound_ns_map.udragend ) {
event_ue = $.Event('udragend');
$.extend( event_ue, motion_map );
$(motion_map.elem_bound).trigger(event_ue);
}
motionDragId = undefined;
}
// trigger heldend
if ( motion_id === motionHeldId ) {
if ( bound_ns_map.uheldend ) {
event_ue = $.Event('uheldend');
$.extend( event_ue, motion_map );
$(motion_map.elem_bound).trigger(event_ue);
}
motionHeldId = undefined;
}
// trigger uzoomend
if ( motion_id === motionDzoomId ) {
do_zoomend = true;
motionDzoomId = undefined;
}
// cleanup zoom info
else if ( motion_id === motion1ZoomId ) {
if ( motion2ZoomId ) {
motion1ZoomId = motion2ZoomId;
motion2ZoomId = undefined;
do_zoomend = true;
}
else { motion1ZoomId = undefined; }
pxPinchZoom = -1;
}
if ( motion_id === motion2ZoomId ) {
motion2ZoomId = undefined;
pxPinchZoom = -1;
do_zoomend = true;
}
if ( do_zoomend && bound_ns_map.uzoomend ) {
event_ue = $.Event('uzoomend');
motion_map.px_delta_zoom = 0;
$.extend( event_ue, motion_map );
$(motion_map.elem_bound).trigger(event_ue);
}
// remove pointer from consideration
delete motionMapMap[motion_id];
};
// End motion control /fnMotionEnd/
//------------------ END MOTION CONTROLS -------------------
//------------------- BEGIN EVENT HANDLERS -------------------
// Begin event handler /onTouch/ for all touch events.
// We use the 'type' attribute to dispatch to motion control
onTouch = function ( event ) {
var
this_el = this,
timestamp = +new Date(),
o_event = event.originalEvent,
touch_list = o_event ? o_event.changedTouches || [] : [],
touch_count = touch_list.length,
idx, touch_event, motion_id, handler_fn
;
doDisableMouse = true;
event.timeStamp = timestamp;
switch ( event.type ) {
case 'touchstart' : handler_fn = fnMotionStart; break;
case 'touchmove' : handler_fn = fnMotionMove; break;
case 'touchend' :
case 'touchcancel' : handler_fn = fnMotionEnd; break;
default : handler_fn = null;
}
if ( ! handler_fn ) { return; }
for ( idx = 0; idx < touch_count; idx++ ) {
touch_event = touch_list[idx];
motion_id = 'touch' + String(touch_event.identifier);
event.clientX = touch_event.clientX;
event.clientY = touch_event.clientY;
handler_fn({
elem : this_el,
motion_id : motion_id,
event_src : event
});
}
};
// End event handler /onTouch/
// Begin event handler /onMouse/ for all mouse events
// We use the 'type' attribute to dispatch to motion control
onMouse = function ( event ) {
var
this_el = this,
motion_id = 'mouse' + String(event.button),
request_dzoom = false,
handler_fn
;
if ( doDisableMouse ) {
event.stopImmediatePropagation();
return;
}
if ( event.shiftKey ) { request_dzoom = true; }
// skip left or middle clicks
if ( event.type !== 'mousemove' ) {
if ( event.button !== 0 ) { return true; }
}
switch ( event.type ) {
case 'mousedown' : handler_fn = fnMotionStart; break;
case 'mouseup' : handler_fn = fnMotionEnd; break;
case 'mousemove' : handler_fn = fnMotionMove; break;
default : handler_fn = null;
}
if ( ! handler_fn ) { return; }
handler_fn({
elem : this_el,
event_src : event,
request_dzoom : request_dzoom,
motion_id : motion_id
});
};
// End event handler /onMouse/
//-------------------- END EVENT HANDLERS --------------------
// Export special events through jQuery API
$Special.ue
= $Special.utap = $Special.uheld
= $Special.uzoomstart = $Special.uzoommove = $Special.uzoomend
= $Special.udragstart = $Special.udragmove = $Special.udragend
= $Special.uheldstart = $Special.uheldmove = $Special.uheldend
= Ue
;
$.ueSetGlobalCb = function ( selector_str, callback_match, callback_nomatch ) {
callbackList.push( {
selector_str : selector_str || '',
callback_match : callback_match || null,
callback_nomatch : callback_nomatch || null
});
};
}(jQuery));

View File

@@ -0,0 +1,87 @@
(function($) {
$.fn.serialize = function(options) {
return $.param(this.serializeArray(options));
};
$.fn.serializeArray = function(options) {
var o = $.extend({
checkboxesAsBools: false
}, options || {});
var rselectTextarea = /select|textarea/i;
var rinput = /text|hidden|password|date|search/i;
return this.map(function() {
return this.elements ? $.makeArray(this.elements) : this;
})
.filter(function() {
return this.name && !this.disabled &&
(this.checked || (o.checkboxesAsBools && this.type === 'checkbox') || rselectTextarea.test(this.nodeName) || rinput.test(this.type));
})
.map(function(i, elem) {
var val = $(this).val();
//this block changed here by me to break out the overly tight but obscure code
if (val == null) return null;
if ($.isArray(val)) {
//Array return
return $.map(val, function(val, i) {
return {
name: elem.name,
value: val
};
})
} else {
if (o.checkboxesAsBools && this.type === 'checkbox') {
return {
name: elem.name,
value: (this.checked ? 'true' : 'false')
}
}
//for now dates are handled at the backend
//by convention of field name ending in "Date" (sentDate, readDate etc)
// if (this.type === 'date') {
// return {
// name: elem.name,
// value: (val === '' ? '' : val) //empty dates sb null for mongo db backend
// }
// }
//default all other types
return {
name: elem.name,
value: val
}
}
// /changed by me
//ORIGINAL BLOCK:
// return val == null ?
// null :
// $.isArray(val) ?
// $.map(val, function (val, i) {
// return { name: elem.name, value: val };
// }) :
// {
// name: elem.name,
// value: (o.checkboxesAsBools && this.type === 'checkbox') ? //moar ternaries!
// (this.checked ? 'true' : 'false') :
// val
// };
}).get();
};
})(jQuery);

136
wwwroot/js/lib/lodash.min.js vendored Normal file
View File

@@ -0,0 +1,136 @@
/**
* @license
* Lodash lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE
*/
;(function(){function n(n,t){return n.set(t[0],t[1]),n}function t(n,t){return n.add(t),n}function r(n,t,r){switch(r.length){case 0:return n.call(t);case 1:return n.call(t,r[0]);case 2:return n.call(t,r[0],r[1]);case 3:return n.call(t,r[0],r[1],r[2])}return n.apply(t,r)}function e(n,t,r,e){for(var u=-1,i=null==n?0:n.length;++u<i;){var o=n[u];t(e,o,r(o),n)}return e}function u(n,t){for(var r=-1,e=null==n?0:n.length;++r<e&&false!==t(n[r],r,n););return n}function i(n,t){for(var r=null==n?0:n.length;r--&&false!==t(n[r],r,n););
return n}function o(n,t){for(var r=-1,e=null==n?0:n.length;++r<e;)if(!t(n[r],r,n))return false;return true}function f(n,t){for(var r=-1,e=null==n?0:n.length,u=0,i=[];++r<e;){var o=n[r];t(o,r,n)&&(i[u++]=o)}return i}function c(n,t){return!(null==n||!n.length)&&-1<d(n,t,0)}function a(n,t,r){for(var e=-1,u=null==n?0:n.length;++e<u;)if(r(t,n[e]))return true;return false}function l(n,t){for(var r=-1,e=null==n?0:n.length,u=Array(e);++r<e;)u[r]=t(n[r],r,n);return u}function s(n,t){for(var r=-1,e=t.length,u=n.length;++r<e;)n[u+r]=t[r];
return n}function h(n,t,r,e){var u=-1,i=null==n?0:n.length;for(e&&i&&(r=n[++u]);++u<i;)r=t(r,n[u],u,n);return r}function p(n,t,r,e){var u=null==n?0:n.length;for(e&&u&&(r=n[--u]);u--;)r=t(r,n[u],u,n);return r}function _(n,t){for(var r=-1,e=null==n?0:n.length;++r<e;)if(t(n[r],r,n))return true;return false}function v(n,t,r){var e;return r(n,function(n,r,u){if(t(n,r,u))return e=r,false}),e}function g(n,t,r,e){var u=n.length;for(r+=e?1:-1;e?r--:++r<u;)if(t(n[r],r,n))return r;return-1}function d(n,t,r){if(t===t)n:{
--r;for(var e=n.length;++r<e;)if(n[r]===t){n=r;break n}n=-1}else n=g(n,b,r);return n}function y(n,t,r,e){--r;for(var u=n.length;++r<u;)if(e(n[r],t))return r;return-1}function b(n){return n!==n}function x(n,t){var r=null==n?0:n.length;return r?k(n,t)/r:P}function j(n){return function(t){return null==t?F:t[n]}}function w(n){return function(t){return null==n?F:n[t]}}function m(n,t,r,e,u){return u(n,function(n,u,i){r=e?(e=false,n):t(r,n,u,i)}),r}function A(n,t){var r=n.length;for(n.sort(t);r--;)n[r]=n[r].c;
return n}function k(n,t){for(var r,e=-1,u=n.length;++e<u;){var i=t(n[e]);i!==F&&(r=r===F?i:r+i)}return r}function E(n,t){for(var r=-1,e=Array(n);++r<n;)e[r]=t(r);return e}function O(n,t){return l(t,function(t){return[t,n[t]]})}function S(n){return function(t){return n(t)}}function I(n,t){return l(t,function(t){return n[t]})}function R(n,t){return n.has(t)}function z(n,t){for(var r=-1,e=n.length;++r<e&&-1<d(t,n[r],0););return r}function W(n,t){for(var r=n.length;r--&&-1<d(t,n[r],0););return r}function B(n){
return"\\"+Tn[n]}function L(n){var t=-1,r=Array(n.size);return n.forEach(function(n,e){r[++t]=[e,n]}),r}function U(n,t){return function(r){return n(t(r))}}function C(n,t){for(var r=-1,e=n.length,u=0,i=[];++r<e;){var o=n[r];o!==t&&"__lodash_placeholder__"!==o||(n[r]="__lodash_placeholder__",i[u++]=r)}return i}function D(n){var t=-1,r=Array(n.size);return n.forEach(function(n){r[++t]=n}),r}function M(n){var t=-1,r=Array(n.size);return n.forEach(function(n){r[++t]=[n,n]}),r}function T(n){if(Bn.test(n)){
for(var t=zn.lastIndex=0;zn.test(n);)++t;n=t}else n=tt(n);return n}function $(n){return Bn.test(n)?n.match(zn)||[]:n.split("")}var F,N=1/0,P=NaN,Z=[["ary",128],["bind",1],["bindKey",2],["curry",8],["curryRight",16],["flip",512],["partial",32],["partialRight",64],["rearg",256]],q=/\b__p\+='';/g,V=/\b(__p\+=)''\+/g,K=/(__e\(.*?\)|\b__t\))\+'';/g,G=/&(?:amp|lt|gt|quot|#39);/g,H=/[&<>"']/g,J=RegExp(G.source),Y=RegExp(H.source),Q=/<%-([\s\S]+?)%>/g,X=/<%([\s\S]+?)%>/g,nn=/<%=([\s\S]+?)%>/g,tn=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,rn=/^\w*$/,en=/^\./,un=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,on=/[\\^$.*+?()[\]{}|]/g,fn=RegExp(on.source),cn=/^\s+|\s+$/g,an=/^\s+/,ln=/\s+$/,sn=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,hn=/\{\n\/\* \[wrapped with (.+)\] \*/,pn=/,? & /,_n=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,vn=/\\(\\)?/g,gn=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,dn=/\w*$/,yn=/^[-+]0x[0-9a-f]+$/i,bn=/^0b[01]+$/i,xn=/^\[object .+?Constructor\]$/,jn=/^0o[0-7]+$/i,wn=/^(?:0|[1-9]\d*)$/,mn=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,An=/($^)/,kn=/['\n\r\u2028\u2029\\]/g,En="[\\ufe0e\\ufe0f]?(?:[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|\\ud83c[\\udffb-\\udfff])?(?:\\u200d(?:[^\\ud800-\\udfff]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff])[\\ufe0e\\ufe0f]?(?:[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|\\ud83c[\\udffb-\\udfff])?)*",On="(?:[\\u2700-\\u27bf]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff])"+En,Sn="(?:[^\\ud800-\\udfff][\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]?|[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff]|[\\ud800-\\udfff])",In=RegExp("['\u2019]","g"),Rn=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]","g"),zn=RegExp("\\ud83c[\\udffb-\\udfff](?=\\ud83c[\\udffb-\\udfff])|"+Sn+En,"g"),Wn=RegExp(["[A-Z\\xc0-\\xd6\\xd8-\\xde]?[a-z\\xdf-\\xf6\\xf8-\\xff]+(?:['\u2019](?:d|ll|m|re|s|t|ve))?(?=[\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000]|[A-Z\\xc0-\\xd6\\xd8-\\xde]|$)|(?:[A-Z\\xc0-\\xd6\\xd8-\\xde]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])+(?:['\u2019](?:D|LL|M|RE|S|T|VE))?(?=[\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000]|[A-Z\\xc0-\\xd6\\xd8-\\xde](?:[a-z\\xdf-\\xf6\\xf8-\\xff]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])|$)|[A-Z\\xc0-\\xd6\\xd8-\\xde]?(?:[a-z\\xdf-\\xf6\\xf8-\\xff]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])+(?:['\u2019](?:d|ll|m|re|s|t|ve))?|[A-Z\\xc0-\\xd6\\xd8-\\xde]+(?:['\u2019](?:D|LL|M|RE|S|T|VE))?|\\d*(?:(?:1ST|2ND|3RD|(?![123])\\dTH)\\b)|\\d*(?:(?:1st|2nd|3rd|(?![123])\\dth)\\b)|\\d+",On].join("|"),"g"),Bn=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\ufe0e\\ufe0f]"),Ln=/[a-z][A-Z]|[A-Z]{2,}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,Un="Array Buffer DataView Date Error Float32Array Float64Array Function Int8Array Int16Array Int32Array Map Math Object Promise RegExp Set String Symbol TypeError Uint8Array Uint8ClampedArray Uint16Array Uint32Array WeakMap _ clearTimeout isFinite parseInt setTimeout".split(" "),Cn={};
Cn["[object Float32Array]"]=Cn["[object Float64Array]"]=Cn["[object Int8Array]"]=Cn["[object Int16Array]"]=Cn["[object Int32Array]"]=Cn["[object Uint8Array]"]=Cn["[object Uint8ClampedArray]"]=Cn["[object Uint16Array]"]=Cn["[object Uint32Array]"]=true,Cn["[object Arguments]"]=Cn["[object Array]"]=Cn["[object ArrayBuffer]"]=Cn["[object Boolean]"]=Cn["[object DataView]"]=Cn["[object Date]"]=Cn["[object Error]"]=Cn["[object Function]"]=Cn["[object Map]"]=Cn["[object Number]"]=Cn["[object Object]"]=Cn["[object RegExp]"]=Cn["[object Set]"]=Cn["[object String]"]=Cn["[object WeakMap]"]=false;
var Dn={};Dn["[object Arguments]"]=Dn["[object Array]"]=Dn["[object ArrayBuffer]"]=Dn["[object DataView]"]=Dn["[object Boolean]"]=Dn["[object Date]"]=Dn["[object Float32Array]"]=Dn["[object Float64Array]"]=Dn["[object Int8Array]"]=Dn["[object Int16Array]"]=Dn["[object Int32Array]"]=Dn["[object Map]"]=Dn["[object Number]"]=Dn["[object Object]"]=Dn["[object RegExp]"]=Dn["[object Set]"]=Dn["[object String]"]=Dn["[object Symbol]"]=Dn["[object Uint8Array]"]=Dn["[object Uint8ClampedArray]"]=Dn["[object Uint16Array]"]=Dn["[object Uint32Array]"]=true,
Dn["[object Error]"]=Dn["[object Function]"]=Dn["[object WeakMap]"]=false;var Mn,Tn={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},$n=parseFloat,Fn=parseInt,Nn=typeof global=="object"&&global&&global.Object===Object&&global,Pn=typeof self=="object"&&self&&self.Object===Object&&self,Zn=Nn||Pn||Function("return this")(),qn=typeof exports=="object"&&exports&&!exports.nodeType&&exports,Vn=qn&&typeof module=="object"&&module&&!module.nodeType&&module,Kn=Vn&&Vn.exports===qn,Gn=Kn&&Nn.process;
n:{try{Mn=Gn&&Gn.binding&&Gn.binding("util");break n}catch(n){}Mn=void 0}var Hn=Mn&&Mn.isArrayBuffer,Jn=Mn&&Mn.isDate,Yn=Mn&&Mn.isMap,Qn=Mn&&Mn.isRegExp,Xn=Mn&&Mn.isSet,nt=Mn&&Mn.isTypedArray,tt=j("length"),rt=w({"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I",
"\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y","\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss","\u0100":"A","\u0102":"A","\u0104":"A","\u0101":"a","\u0103":"a","\u0105":"a","\u0106":"C","\u0108":"C","\u010a":"C",
"\u010c":"C","\u0107":"c","\u0109":"c","\u010b":"c","\u010d":"c","\u010e":"D","\u0110":"D","\u010f":"d","\u0111":"d","\u0112":"E","\u0114":"E","\u0116":"E","\u0118":"E","\u011a":"E","\u0113":"e","\u0115":"e","\u0117":"e","\u0119":"e","\u011b":"e","\u011c":"G","\u011e":"G","\u0120":"G","\u0122":"G","\u011d":"g","\u011f":"g","\u0121":"g","\u0123":"g","\u0124":"H","\u0126":"H","\u0125":"h","\u0127":"h","\u0128":"I","\u012a":"I","\u012c":"I","\u012e":"I","\u0130":"I","\u0129":"i","\u012b":"i","\u012d":"i",
"\u012f":"i","\u0131":"i","\u0134":"J","\u0135":"j","\u0136":"K","\u0137":"k","\u0138":"k","\u0139":"L","\u013b":"L","\u013d":"L","\u013f":"L","\u0141":"L","\u013a":"l","\u013c":"l","\u013e":"l","\u0140":"l","\u0142":"l","\u0143":"N","\u0145":"N","\u0147":"N","\u014a":"N","\u0144":"n","\u0146":"n","\u0148":"n","\u014b":"n","\u014c":"O","\u014e":"O","\u0150":"O","\u014d":"o","\u014f":"o","\u0151":"o","\u0154":"R","\u0156":"R","\u0158":"R","\u0155":"r","\u0157":"r","\u0159":"r","\u015a":"S","\u015c":"S",
"\u015e":"S","\u0160":"S","\u015b":"s","\u015d":"s","\u015f":"s","\u0161":"s","\u0162":"T","\u0164":"T","\u0166":"T","\u0163":"t","\u0165":"t","\u0167":"t","\u0168":"U","\u016a":"U","\u016c":"U","\u016e":"U","\u0170":"U","\u0172":"U","\u0169":"u","\u016b":"u","\u016d":"u","\u016f":"u","\u0171":"u","\u0173":"u","\u0174":"W","\u0175":"w","\u0176":"Y","\u0177":"y","\u0178":"Y","\u0179":"Z","\u017b":"Z","\u017d":"Z","\u017a":"z","\u017c":"z","\u017e":"z","\u0132":"IJ","\u0133":"ij","\u0152":"Oe","\u0153":"oe",
"\u0149":"'n","\u017f":"s"}),et=w({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}),ut=w({"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"',"&#39;":"'"}),it=function w(En){function On(n){if(xu(n)&&!af(n)&&!(n instanceof Mn)){if(n instanceof zn)return n;if(ci.call(n,"__wrapped__"))return Pe(n)}return new zn(n)}function Sn(){}function zn(n,t){this.__wrapped__=n,this.__actions__=[],this.__chain__=!!t,this.__index__=0,this.__values__=F}function Mn(n){this.__wrapped__=n,this.__actions__=[],this.__dir__=1,
this.__filtered__=false,this.__iteratees__=[],this.__takeCount__=4294967295,this.__views__=[]}function Tn(n){var t=-1,r=null==n?0:n.length;for(this.clear();++t<r;){var e=n[t];this.set(e[0],e[1])}}function Nn(n){var t=-1,r=null==n?0:n.length;for(this.clear();++t<r;){var e=n[t];this.set(e[0],e[1])}}function Pn(n){var t=-1,r=null==n?0:n.length;for(this.clear();++t<r;){var e=n[t];this.set(e[0],e[1])}}function qn(n){var t=-1,r=null==n?0:n.length;for(this.__data__=new Pn;++t<r;)this.add(n[t])}function Vn(n){
this.size=(this.__data__=new Nn(n)).size}function Gn(n,t){var r,e=af(n),u=!e&&cf(n),i=!e&&!u&&sf(n),o=!e&&!u&&!i&&gf(n),u=(e=e||u||i||o)?E(n.length,ri):[],f=u.length;for(r in n)!t&&!ci.call(n,r)||e&&("length"==r||i&&("offset"==r||"parent"==r)||o&&("buffer"==r||"byteLength"==r||"byteOffset"==r)||Re(r,f))||u.push(r);return u}function tt(n){var t=n.length;return t?n[cr(0,t-1)]:F}function ot(n,t){return Te(Mr(n),gt(t,0,n.length))}function ft(n){return Te(Mr(n))}function ct(n,t,r){(r===F||hu(n[t],r))&&(r!==F||t in n)||_t(n,t,r);
}function at(n,t,r){var e=n[t];ci.call(n,t)&&hu(e,r)&&(r!==F||t in n)||_t(n,t,r)}function lt(n,t){for(var r=n.length;r--;)if(hu(n[r][0],t))return r;return-1}function st(n,t,r,e){return oo(n,function(n,u,i){t(e,n,r(n),i)}),e}function ht(n,t){return n&&Tr(t,Lu(t),n)}function pt(n,t){return n&&Tr(t,Uu(t),n)}function _t(n,t,r){"__proto__"==t&&Ei?Ei(n,t,{configurable:true,enumerable:true,value:r,writable:true}):n[t]=r}function vt(n,t){for(var r=-1,e=t.length,u=Hu(e),i=null==n;++r<e;)u[r]=i?F:Wu(n,t[r]);return u;
}function gt(n,t,r){return n===n&&(r!==F&&(n=n<=r?n:r),t!==F&&(n=n>=t?n:t)),n}function dt(n,t,r,e,i,o){var f,c=1&t,a=2&t,l=4&t;if(r&&(f=i?r(n,e,i,o):r(n)),f!==F)return f;if(!bu(n))return n;if(e=af(n)){if(f=Ee(n),!c)return Mr(n,f)}else{var s=yo(n),h="[object Function]"==s||"[object GeneratorFunction]"==s;if(sf(n))return Wr(n,c);if("[object Object]"==s||"[object Arguments]"==s||h&&!i){if(f=a||h?{}:Oe(n),!c)return a?Fr(n,pt(f,n)):$r(n,ht(f,n))}else{if(!Dn[s])return i?n:{};f=Se(n,s,dt,c)}}if(o||(o=new Vn),
i=o.get(n))return i;o.set(n,f);var a=l?a?ye:de:a?Uu:Lu,p=e?F:a(n);return u(p||n,function(e,u){p&&(u=e,e=n[u]),at(f,u,dt(e,t,r,u,n,o))}),f}function yt(n){var t=Lu(n);return function(r){return bt(r,n,t)}}function bt(n,t,r){var e=r.length;if(null==n)return!e;for(n=ni(n);e--;){var u=r[e],i=t[u],o=n[u];if(o===F&&!(u in n)||!i(o))return false}return true}function xt(n,t,r){if(typeof n!="function")throw new ei("Expected a function");return jo(function(){n.apply(F,r)},t)}function jt(n,t,r,e){var u=-1,i=c,o=true,f=n.length,s=[],h=t.length;
if(!f)return s;r&&(t=l(t,S(r))),e?(i=a,o=false):200<=t.length&&(i=R,o=false,t=new qn(t));n:for(;++u<f;){var p=n[u],_=null==r?p:r(p),p=e||0!==p?p:0;if(o&&_===_){for(var v=h;v--;)if(t[v]===_)continue n;s.push(p)}else i(t,_,e)||s.push(p)}return s}function wt(n,t){var r=true;return oo(n,function(n,e,u){return r=!!t(n,e,u)}),r}function mt(n,t,r){for(var e=-1,u=n.length;++e<u;){var i=n[e],o=t(i);if(null!=o&&(f===F?o===o&&!Au(o):r(o,f)))var f=o,c=i}return c}function At(n,t){var r=[];return oo(n,function(n,e,u){
t(n,e,u)&&r.push(n)}),r}function kt(n,t,r,e,u){var i=-1,o=n.length;for(r||(r=Ie),u||(u=[]);++i<o;){var f=n[i];0<t&&r(f)?1<t?kt(f,t-1,r,e,u):s(u,f):e||(u[u.length]=f)}return u}function Et(n,t){return n&&co(n,t,Lu)}function Ot(n,t){return n&&ao(n,t,Lu)}function St(n,t){return f(t,function(t){return gu(n[t])})}function It(n,t){t=Rr(t,n);for(var r=0,e=t.length;null!=n&&r<e;)n=n[$e(t[r++])];return r&&r==e?n:F}function Rt(n,t,r){return t=t(n),af(n)?t:s(t,r(n))}function zt(n){if(null==n)n=n===F?"[object Undefined]":"[object Null]";else if(ki&&ki in ni(n)){
var t=ci.call(n,ki),r=n[ki];try{n[ki]=F;var e=true}catch(n){}var u=si.call(n);e&&(t?n[ki]=r:delete n[ki]),n=u}else n=si.call(n);return n}function Wt(n,t){return n>t}function Bt(n,t){return null!=n&&ci.call(n,t)}function Lt(n,t){return null!=n&&t in ni(n)}function Ut(n,t,r){for(var e=r?a:c,u=n[0].length,i=n.length,o=i,f=Hu(i),s=1/0,h=[];o--;){var p=n[o];o&&t&&(p=l(p,S(t))),s=Mi(p.length,s),f[o]=!r&&(t||120<=u&&120<=p.length)?new qn(o&&p):F}var p=n[0],_=-1,v=f[0];n:for(;++_<u&&h.length<s;){var g=p[_],d=t?t(g):g,g=r||0!==g?g:0;
if(v?!R(v,d):!e(h,d,r)){for(o=i;--o;){var y=f[o];if(y?!R(y,d):!e(n[o],d,r))continue n}v&&v.push(d),h.push(g)}}return h}function Ct(n,t,r){var e={};return Et(n,function(n,u,i){t(e,r(n),u,i)}),e}function Dt(n,t,e){return t=Rr(t,n),n=2>t.length?n:It(n,vr(t,0,-1)),t=null==n?n:n[$e(Ge(t))],null==t?F:r(t,n,e)}function Mt(n){return xu(n)&&"[object Arguments]"==zt(n)}function Tt(n){return xu(n)&&"[object ArrayBuffer]"==zt(n)}function $t(n){return xu(n)&&"[object Date]"==zt(n)}function Ft(n,t,r,e,u){if(n===t)t=true;else if(null==n||null==t||!xu(n)&&!xu(t))t=n!==n&&t!==t;else n:{
var i=af(n),o=af(t),f=i?"[object Array]":yo(n),c=o?"[object Array]":yo(t),f="[object Arguments]"==f?"[object Object]":f,c="[object Arguments]"==c?"[object Object]":c,a="[object Object]"==f,o="[object Object]"==c;if((c=f==c)&&sf(n)){if(!sf(t)){t=false;break n}i=true,a=false}if(c&&!a)u||(u=new Vn),t=i||gf(n)?_e(n,t,r,e,Ft,u):ve(n,t,f,r,e,Ft,u);else{if(!(1&r)&&(i=a&&ci.call(n,"__wrapped__"),f=o&&ci.call(t,"__wrapped__"),i||f)){n=i?n.value():n,t=f?t.value():t,u||(u=new Vn),t=Ft(n,t,r,e,u);break n}if(c)t:if(u||(u=new Vn),
i=1&r,f=de(n),o=f.length,c=de(t).length,o==c||i){for(a=o;a--;){var l=f[a];if(!(i?l in t:ci.call(t,l))){t=false;break t}}if((c=u.get(n))&&u.get(t))t=c==t;else{c=true,u.set(n,t),u.set(t,n);for(var s=i;++a<o;){var l=f[a],h=n[l],p=t[l];if(e)var _=i?e(p,h,l,t,n,u):e(h,p,l,n,t,u);if(_===F?h!==p&&!Ft(h,p,r,e,u):!_){c=false;break}s||(s="constructor"==l)}c&&!s&&(r=n.constructor,e=t.constructor,r!=e&&"constructor"in n&&"constructor"in t&&!(typeof r=="function"&&r instanceof r&&typeof e=="function"&&e instanceof e)&&(c=false)),
u.delete(n),u.delete(t),t=c}}else t=false;else t=false}}return t}function Nt(n){return xu(n)&&"[object Map]"==yo(n)}function Pt(n,t,r,e){var u=r.length,i=u,o=!e;if(null==n)return!i;for(n=ni(n);u--;){var f=r[u];if(o&&f[2]?f[1]!==n[f[0]]:!(f[0]in n))return false}for(;++u<i;){var f=r[u],c=f[0],a=n[c],l=f[1];if(o&&f[2]){if(a===F&&!(c in n))return false}else{if(f=new Vn,e)var s=e(a,l,c,n,t,f);if(s===F?!Ft(l,a,3,e,f):!s)return false}}return true}function Zt(n){return!(!bu(n)||li&&li in n)&&(gu(n)?_i:xn).test(Fe(n))}function qt(n){
return xu(n)&&"[object RegExp]"==zt(n)}function Vt(n){return xu(n)&&"[object Set]"==yo(n)}function Kt(n){return xu(n)&&yu(n.length)&&!!Cn[zt(n)]}function Gt(n){return typeof n=="function"?n:null==n?Nu:typeof n=="object"?af(n)?Xt(n[0],n[1]):Qt(n):Vu(n)}function Ht(n){if(!Le(n))return Ci(n);var t,r=[];for(t in ni(n))ci.call(n,t)&&"constructor"!=t&&r.push(t);return r}function Jt(n,t){return n<t}function Yt(n,t){var r=-1,e=pu(n)?Hu(n.length):[];return oo(n,function(n,u,i){e[++r]=t(n,u,i)}),e}function Qt(n){
var t=me(n);return 1==t.length&&t[0][2]?Ue(t[0][0],t[0][1]):function(r){return r===n||Pt(r,n,t)}}function Xt(n,t){return We(n)&&t===t&&!bu(t)?Ue($e(n),t):function(r){var e=Wu(r,n);return e===F&&e===t?Bu(r,n):Ft(t,e,3)}}function nr(n,t,r,e,u){n!==t&&co(t,function(i,o){if(bu(i)){u||(u=new Vn);var f=u,c=n[o],a=t[o],l=f.get(a);if(l)ct(n,o,l);else{var l=e?e(c,a,o+"",n,t,f):F,s=l===F;if(s){var h=af(a),p=!h&&sf(a),_=!h&&!p&&gf(a),l=a;h||p||_?af(c)?l=c:_u(c)?l=Mr(c):p?(s=false,l=Wr(a,true)):_?(s=false,l=Lr(a,true)):l=[]:wu(a)||cf(a)?(l=c,
cf(c)?l=Ru(c):(!bu(c)||r&&gu(c))&&(l=Oe(a))):s=false}s&&(f.set(a,l),nr(l,a,r,e,f),f.delete(a)),ct(n,o,l)}}else f=e?e(n[o],i,o+"",n,t,u):F,f===F&&(f=i),ct(n,o,f)},Uu)}function tr(n,t){var r=n.length;if(r)return t+=0>t?r:0,Re(t,r)?n[t]:F}function rr(n,t,r){var e=-1;return t=l(t.length?t:[Nu],S(je())),n=Yt(n,function(n){return{a:l(t,function(t){return t(n)}),b:++e,c:n}}),A(n,function(n,t){var e;n:{e=-1;for(var u=n.a,i=t.a,o=u.length,f=r.length;++e<o;){var c=Ur(u[e],i[e]);if(c){e=e>=f?c:c*("desc"==r[e]?-1:1);
break n}}e=n.b-t.b}return e})}function er(n,t){return ur(n,t,function(t,r){return Bu(n,r)})}function ur(n,t,r){for(var e=-1,u=t.length,i={};++e<u;){var o=t[e],f=It(n,o);r(f,o)&&pr(i,Rr(o,n),f)}return i}function ir(n){return function(t){return It(t,n)}}function or(n,t,r,e){var u=e?y:d,i=-1,o=t.length,f=n;for(n===t&&(t=Mr(t)),r&&(f=l(n,S(r)));++i<o;)for(var c=0,a=t[i],a=r?r(a):a;-1<(c=u(f,a,c,e));)f!==n&&wi.call(f,c,1),wi.call(n,c,1);return n}function fr(n,t){for(var r=n?t.length:0,e=r-1;r--;){var u=t[r];
if(r==e||u!==i){var i=u;Re(u)?wi.call(n,u,1):mr(n,u)}}}function cr(n,t){return n+zi(Fi()*(t-n+1))}function ar(n,t){var r="";if(!n||1>t||9007199254740991<t)return r;do t%2&&(r+=n),(t=zi(t/2))&&(n+=n);while(t);return r}function lr(n,t){return wo(Ce(n,t,Nu),n+"")}function sr(n){return tt(Du(n))}function hr(n,t){var r=Du(n);return Te(r,gt(t,0,r.length))}function pr(n,t,r,e){if(!bu(n))return n;t=Rr(t,n);for(var u=-1,i=t.length,o=i-1,f=n;null!=f&&++u<i;){var c=$e(t[u]),a=r;if(u!=o){var l=f[c],a=e?e(l,c,f):F;
a===F&&(a=bu(l)?l:Re(t[u+1])?[]:{})}at(f,c,a),f=f[c]}return n}function _r(n){return Te(Du(n))}function vr(n,t,r){var e=-1,u=n.length;for(0>t&&(t=-t>u?0:u+t),r=r>u?u:r,0>r&&(r+=u),u=t>r?0:r-t>>>0,t>>>=0,r=Hu(u);++e<u;)r[e]=n[e+t];return r}function gr(n,t){var r;return oo(n,function(n,e,u){return r=t(n,e,u),!r}),!!r}function dr(n,t,r){var e=0,u=null==n?e:n.length;if(typeof t=="number"&&t===t&&2147483647>=u){for(;e<u;){var i=e+u>>>1,o=n[i];null!==o&&!Au(o)&&(r?o<=t:o<t)?e=i+1:u=i}return u}return yr(n,t,Nu,r);
}function yr(n,t,r,e){t=r(t);for(var u=0,i=null==n?0:n.length,o=t!==t,f=null===t,c=Au(t),a=t===F;u<i;){var l=zi((u+i)/2),s=r(n[l]),h=s!==F,p=null===s,_=s===s,v=Au(s);(o?e||_:a?_&&(e||h):f?_&&h&&(e||!p):c?_&&h&&!p&&(e||!v):p||v?0:e?s<=t:s<t)?u=l+1:i=l}return Mi(i,4294967294)}function br(n,t){for(var r=-1,e=n.length,u=0,i=[];++r<e;){var o=n[r],f=t?t(o):o;if(!r||!hu(f,c)){var c=f;i[u++]=0===o?0:o}}return i}function xr(n){return typeof n=="number"?n:Au(n)?P:+n}function jr(n){if(typeof n=="string")return n;
if(af(n))return l(n,jr)+"";if(Au(n))return uo?uo.call(n):"";var t=n+"";return"0"==t&&1/n==-N?"-0":t}function wr(n,t,r){var e=-1,u=c,i=n.length,o=true,f=[],l=f;if(r)o=false,u=a;else if(200<=i){if(u=t?null:po(n))return D(u);o=false,u=R,l=new qn}else l=t?[]:f;n:for(;++e<i;){var s=n[e],h=t?t(s):s,s=r||0!==s?s:0;if(o&&h===h){for(var p=l.length;p--;)if(l[p]===h)continue n;t&&l.push(h),f.push(s)}else u(l,h,r)||(l!==f&&l.push(h),f.push(s))}return f}function mr(n,t){return t=Rr(t,n),n=2>t.length?n:It(n,vr(t,0,-1)),
null==n||delete n[$e(Ge(t))]}function Ar(n,t,r,e){for(var u=n.length,i=e?u:-1;(e?i--:++i<u)&&t(n[i],i,n););return r?vr(n,e?0:i,e?i+1:u):vr(n,e?i+1:0,e?u:i)}function kr(n,t){var r=n;return r instanceof Mn&&(r=r.value()),h(t,function(n,t){return t.func.apply(t.thisArg,s([n],t.args))},r)}function Er(n,t,r){var e=n.length;if(2>e)return e?wr(n[0]):[];for(var u=-1,i=Hu(e);++u<e;)for(var o=n[u],f=-1;++f<e;)f!=u&&(i[u]=jt(i[u]||o,n[f],t,r));return wr(kt(i,1),t,r)}function Or(n,t,r){for(var e=-1,u=n.length,i=t.length,o={};++e<u;)r(o,n[e],e<i?t[e]:F);
return o}function Sr(n){return _u(n)?n:[]}function Ir(n){return typeof n=="function"?n:Nu}function Rr(n,t){return af(n)?n:We(n,t)?[n]:mo(zu(n))}function zr(n,t,r){var e=n.length;return r=r===F?e:r,!t&&r>=e?n:vr(n,t,r)}function Wr(n,t){if(t)return n.slice();var r=n.length,r=yi?yi(r):new n.constructor(r);return n.copy(r),r}function Br(n){var t=new n.constructor(n.byteLength);return new di(t).set(new di(n)),t}function Lr(n,t){return new n.constructor(t?Br(n.buffer):n.buffer,n.byteOffset,n.length)}function Ur(n,t){
if(n!==t){var r=n!==F,e=null===n,u=n===n,i=Au(n),o=t!==F,f=null===t,c=t===t,a=Au(t);if(!f&&!a&&!i&&n>t||i&&o&&c&&!f&&!a||e&&o&&c||!r&&c||!u)return 1;if(!e&&!i&&!a&&n<t||a&&r&&u&&!e&&!i||f&&r&&u||!o&&u||!c)return-1}return 0}function Cr(n,t,r,e){var u=-1,i=n.length,o=r.length,f=-1,c=t.length,a=Di(i-o,0),l=Hu(c+a);for(e=!e;++f<c;)l[f]=t[f];for(;++u<o;)(e||u<i)&&(l[r[u]]=n[u]);for(;a--;)l[f++]=n[u++];return l}function Dr(n,t,r,e){var u=-1,i=n.length,o=-1,f=r.length,c=-1,a=t.length,l=Di(i-f,0),s=Hu(l+a);
for(e=!e;++u<l;)s[u]=n[u];for(l=u;++c<a;)s[l+c]=t[c];for(;++o<f;)(e||u<i)&&(s[l+r[o]]=n[u++]);return s}function Mr(n,t){var r=-1,e=n.length;for(t||(t=Hu(e));++r<e;)t[r]=n[r];return t}function Tr(n,t,r,e){var u=!r;r||(r={});for(var i=-1,o=t.length;++i<o;){var f=t[i],c=e?e(r[f],n[f],f,r,n):F;c===F&&(c=n[f]),u?_t(r,f,c):at(r,f,c)}return r}function $r(n,t){return Tr(n,vo(n),t)}function Fr(n,t){return Tr(n,go(n),t)}function Nr(n,t){return function(r,u){var i=af(r)?e:st,o=t?t():{};return i(r,n,je(u,2),o);
}}function Pr(n){return lr(function(t,r){var e=-1,u=r.length,i=1<u?r[u-1]:F,o=2<u?r[2]:F,i=3<n.length&&typeof i=="function"?(u--,i):F;for(o&&ze(r[0],r[1],o)&&(i=3>u?F:i,u=1),t=ni(t);++e<u;)(o=r[e])&&n(t,o,e,i);return t})}function Zr(n,t){return function(r,e){if(null==r)return r;if(!pu(r))return n(r,e);for(var u=r.length,i=t?u:-1,o=ni(r);(t?i--:++i<u)&&false!==e(o[i],i,o););return r}}function qr(n){return function(t,r,e){var u=-1,i=ni(t);e=e(t);for(var o=e.length;o--;){var f=e[n?o:++u];if(false===r(i[f],f,i))break;
}return t}}function Vr(n,t,r){function e(){return(this&&this!==Zn&&this instanceof e?i:n).apply(u?r:this,arguments)}var u=1&t,i=Hr(n);return e}function Kr(n){return function(t){t=zu(t);var r=Bn.test(t)?$(t):F,e=r?r[0]:t.charAt(0);return t=r?zr(r,1).join(""):t.slice(1),e[n]()+t}}function Gr(n){return function(t){return h($u(Tu(t).replace(In,"")),n,"")}}function Hr(n){return function(){var t=arguments;switch(t.length){case 0:return new n;case 1:return new n(t[0]);case 2:return new n(t[0],t[1]);case 3:
return new n(t[0],t[1],t[2]);case 4:return new n(t[0],t[1],t[2],t[3]);case 5:return new n(t[0],t[1],t[2],t[3],t[4]);case 6:return new n(t[0],t[1],t[2],t[3],t[4],t[5]);case 7:return new n(t[0],t[1],t[2],t[3],t[4],t[5],t[6])}var r=io(n.prototype),t=n.apply(r,t);return bu(t)?t:r}}function Jr(n,t,e){function u(){for(var o=arguments.length,f=Hu(o),c=o,a=xe(u);c--;)f[c]=arguments[c];return c=3>o&&f[0]!==a&&f[o-1]!==a?[]:C(f,a),o-=c.length,o<e?fe(n,t,Xr,u.placeholder,F,f,c,F,F,e-o):r(this&&this!==Zn&&this instanceof u?i:n,this,f);
}var i=Hr(n);return u}function Yr(n){return function(t,r,e){var u=ni(t);if(!pu(t)){var i=je(r,3);t=Lu(t),r=function(n){return i(u[n],n,u)}}return r=n(t,r,e),-1<r?u[i?t[r]:r]:F}}function Qr(n){return ge(function(t){var r=t.length,e=r,u=zn.prototype.thru;for(n&&t.reverse();e--;){var i=t[e];if(typeof i!="function")throw new ei("Expected a function");if(u&&!o&&"wrapper"==be(i))var o=new zn([],true)}for(e=o?e:r;++e<r;)var i=t[e],u=be(i),f="wrapper"==u?_o(i):F,o=f&&Be(f[0])&&424==f[1]&&!f[4].length&&1==f[9]?o[be(f[0])].apply(o,f[3]):1==i.length&&Be(i)?o[u]():o.thru(i);
return function(){var n=arguments,e=n[0];if(o&&1==n.length&&af(e))return o.plant(e).value();for(var u=0,n=r?t[u].apply(this,n):e;++u<r;)n=t[u].call(this,n);return n}})}function Xr(n,t,r,e,u,i,o,f,c,a){function l(){for(var d=arguments.length,y=Hu(d),b=d;b--;)y[b]=arguments[b];if(_){var x,j=xe(l),b=y.length;for(x=0;b--;)y[b]===j&&++x}if(e&&(y=Cr(y,e,u,_)),i&&(y=Dr(y,i,o,_)),d-=x,_&&d<a)return j=C(y,j),fe(n,t,Xr,l.placeholder,r,y,j,f,c,a-d);if(j=h?r:this,b=p?j[n]:n,d=y.length,f){x=y.length;for(var w=Mi(f.length,x),m=Mr(y);w--;){
var A=f[w];y[w]=Re(A,x)?m[A]:F}}else v&&1<d&&y.reverse();return s&&c<d&&(y.length=c),this&&this!==Zn&&this instanceof l&&(b=g||Hr(b)),b.apply(j,y)}var s=128&t,h=1&t,p=2&t,_=24&t,v=512&t,g=p?F:Hr(n);return l}function ne(n,t){return function(r,e){return Ct(r,n,t(e))}}function te(n,t){return function(r,e){var u;if(r===F&&e===F)return t;if(r!==F&&(u=r),e!==F){if(u===F)return e;typeof r=="string"||typeof e=="string"?(r=jr(r),e=jr(e)):(r=xr(r),e=xr(e)),u=n(r,e)}return u}}function re(n){return ge(function(t){
return t=l(t,S(je())),lr(function(e){var u=this;return n(t,function(n){return r(n,u,e)})})})}function ee(n,t){t=t===F?" ":jr(t);var r=t.length;return 2>r?r?ar(t,n):t:(r=ar(t,Ri(n/T(t))),Bn.test(t)?zr($(r),0,n).join(""):r.slice(0,n))}function ue(n,t,e,u){function i(){for(var t=-1,c=arguments.length,a=-1,l=u.length,s=Hu(l+c),h=this&&this!==Zn&&this instanceof i?f:n;++a<l;)s[a]=u[a];for(;c--;)s[a++]=arguments[++t];return r(h,o?e:this,s)}var o=1&t,f=Hr(n);return i}function ie(n){return function(t,r,e){
e&&typeof e!="number"&&ze(t,r,e)&&(r=e=F),t=Eu(t),r===F?(r=t,t=0):r=Eu(r),e=e===F?t<r?1:-1:Eu(e);var u=-1;r=Di(Ri((r-t)/(e||1)),0);for(var i=Hu(r);r--;)i[n?r:++u]=t,t+=e;return i}}function oe(n){return function(t,r){return typeof t=="string"&&typeof r=="string"||(t=Iu(t),r=Iu(r)),n(t,r)}}function fe(n,t,r,e,u,i,o,f,c,a){var l=8&t,s=l?o:F;o=l?F:o;var h=l?i:F;return i=l?F:i,t=(t|(l?32:64))&~(l?64:32),4&t||(t&=-4),u=[n,t,u,h,s,i,o,f,c,a],r=r.apply(F,u),Be(n)&&xo(r,u),r.placeholder=e,De(r,n,t)}function ce(n){
var t=Xu[n];return function(n,r){if(n=Iu(n),r=null==r?0:Mi(Ou(r),292)){var e=(zu(n)+"e").split("e"),e=t(e[0]+"e"+(+e[1]+r)),e=(zu(e)+"e").split("e");return+(e[0]+"e"+(+e[1]-r))}return t(n)}}function ae(n){return function(t){var r=yo(t);return"[object Map]"==r?L(t):"[object Set]"==r?M(t):O(t,n(t))}}function le(n,t,r,e,u,i,o,f){var c=2&t;if(!c&&typeof n!="function")throw new ei("Expected a function");var a=e?e.length:0;if(a||(t&=-97,e=u=F),o=o===F?o:Di(Ou(o),0),f=f===F?f:Ou(f),a-=u?u.length:0,64&t){
var l=e,s=u;e=u=F}var h=c?F:_o(n);return i=[n,t,r,e,u,l,s,i,o,f],h&&(r=i[1],n=h[1],t=r|n,e=128==n&&8==r||128==n&&256==r&&i[7].length<=h[8]||384==n&&h[7].length<=h[8]&&8==r,131>t||e)&&(1&n&&(i[2]=h[2],t|=1&r?0:4),(r=h[3])&&(e=i[3],i[3]=e?Cr(e,r,h[4]):r,i[4]=e?C(i[3],"__lodash_placeholder__"):h[4]),(r=h[5])&&(e=i[5],i[5]=e?Dr(e,r,h[6]):r,i[6]=e?C(i[5],"__lodash_placeholder__"):h[6]),(r=h[7])&&(i[7]=r),128&n&&(i[8]=null==i[8]?h[8]:Mi(i[8],h[8])),null==i[9]&&(i[9]=h[9]),i[0]=h[0],i[1]=t),n=i[0],t=i[1],
r=i[2],e=i[3],u=i[4],f=i[9]=i[9]===F?c?0:n.length:Di(i[9]-a,0),!f&&24&t&&(t&=-25),De((h?lo:xo)(t&&1!=t?8==t||16==t?Jr(n,t,f):32!=t&&33!=t||u.length?Xr.apply(F,i):ue(n,t,r,e):Vr(n,t,r),i),n,t)}function se(n,t,r,e){return n===F||hu(n,ii[r])&&!ci.call(e,r)?t:n}function he(n,t,r,e,u,i){return bu(n)&&bu(t)&&(i.set(t,n),nr(n,t,F,he,i),i.delete(t)),n}function pe(n){return wu(n)?F:n}function _e(n,t,r,e,u,i){var o=1&r,f=n.length,c=t.length;if(f!=c&&!(o&&c>f))return false;if((c=i.get(n))&&i.get(t))return c==t;var c=-1,a=true,l=2&r?new qn:F;
for(i.set(n,t),i.set(t,n);++c<f;){var s=n[c],h=t[c];if(e)var p=o?e(h,s,c,t,n,i):e(s,h,c,n,t,i);if(p!==F){if(p)continue;a=false;break}if(l){if(!_(t,function(n,t){if(!R(l,t)&&(s===n||u(s,n,r,e,i)))return l.push(t)})){a=false;break}}else if(s!==h&&!u(s,h,r,e,i)){a=false;break}}return i.delete(n),i.delete(t),a}function ve(n,t,r,e,u,i,o){switch(r){case"[object DataView]":if(n.byteLength!=t.byteLength||n.byteOffset!=t.byteOffset)break;n=n.buffer,t=t.buffer;case"[object ArrayBuffer]":if(n.byteLength!=t.byteLength||!i(new di(n),new di(t)))break;
return true;case"[object Boolean]":case"[object Date]":case"[object Number]":return hu(+n,+t);case"[object Error]":return n.name==t.name&&n.message==t.message;case"[object RegExp]":case"[object String]":return n==t+"";case"[object Map]":var f=L;case"[object Set]":if(f||(f=D),n.size!=t.size&&!(1&e))break;return(r=o.get(n))?r==t:(e|=2,o.set(n,t),t=_e(f(n),f(t),e,u,i,o),o.delete(n),t);case"[object Symbol]":if(eo)return eo.call(n)==eo.call(t)}return false}function ge(n){return wo(Ce(n,F,Ve),n+"")}function de(n){
return Rt(n,Lu,vo)}function ye(n){return Rt(n,Uu,go)}function be(n){for(var t=n.name+"",r=Ji[t],e=ci.call(Ji,t)?r.length:0;e--;){var u=r[e],i=u.func;if(null==i||i==n)return u.name}return t}function xe(n){return(ci.call(On,"placeholder")?On:n).placeholder}function je(){var n=On.iteratee||Pu,n=n===Pu?Gt:n;return arguments.length?n(arguments[0],arguments[1]):n}function we(n,t){var r=n.__data__,e=typeof t;return("string"==e||"number"==e||"symbol"==e||"boolean"==e?"__proto__"!==t:null===t)?r[typeof t=="string"?"string":"hash"]:r.map;
}function me(n){for(var t=Lu(n),r=t.length;r--;){var e=t[r],u=n[e];t[r]=[e,u,u===u&&!bu(u)]}return t}function Ae(n,t){var r=null==n?F:n[t];return Zt(r)?r:F}function ke(n,t,r){t=Rr(t,n);for(var e=-1,u=t.length,i=false;++e<u;){var o=$e(t[e]);if(!(i=null!=n&&r(n,o)))break;n=n[o]}return i||++e!=u?i:(u=null==n?0:n.length,!!u&&yu(u)&&Re(o,u)&&(af(n)||cf(n)))}function Ee(n){var t=n.length,r=n.constructor(t);return t&&"string"==typeof n[0]&&ci.call(n,"index")&&(r.index=n.index,r.input=n.input),r}function Oe(n){
return typeof n.constructor!="function"||Le(n)?{}:io(bi(n))}function Se(r,e,u,i){var o=r.constructor;switch(e){case"[object ArrayBuffer]":return Br(r);case"[object Boolean]":case"[object Date]":return new o(+r);case"[object DataView]":return e=i?Br(r.buffer):r.buffer,new r.constructor(e,r.byteOffset,r.byteLength);case"[object Float32Array]":case"[object Float64Array]":case"[object Int8Array]":case"[object Int16Array]":case"[object Int32Array]":case"[object Uint8Array]":case"[object Uint8ClampedArray]":
case"[object Uint16Array]":case"[object Uint32Array]":return Lr(r,i);case"[object Map]":return e=i?u(L(r),1):L(r),h(e,n,new r.constructor);case"[object Number]":case"[object String]":return new o(r);case"[object RegExp]":return e=new r.constructor(r.source,dn.exec(r)),e.lastIndex=r.lastIndex,e;case"[object Set]":return e=i?u(D(r),1):D(r),h(e,t,new r.constructor);case"[object Symbol]":return eo?ni(eo.call(r)):{}}}function Ie(n){return af(n)||cf(n)||!!(mi&&n&&n[mi])}function Re(n,t){return t=null==t?9007199254740991:t,
!!t&&(typeof n=="number"||wn.test(n))&&-1<n&&0==n%1&&n<t}function ze(n,t,r){if(!bu(r))return false;var e=typeof t;return!!("number"==e?pu(r)&&Re(t,r.length):"string"==e&&t in r)&&hu(r[t],n)}function We(n,t){if(af(n))return false;var r=typeof n;return!("number"!=r&&"symbol"!=r&&"boolean"!=r&&null!=n&&!Au(n))||(rn.test(n)||!tn.test(n)||null!=t&&n in ni(t))}function Be(n){var t=be(n),r=On[t];return typeof r=="function"&&t in Mn.prototype&&(n===r||(t=_o(r),!!t&&n===t[0]))}function Le(n){var t=n&&n.constructor;
return n===(typeof t=="function"&&t.prototype||ii)}function Ue(n,t){return function(r){return null!=r&&(r[n]===t&&(t!==F||n in ni(r)))}}function Ce(n,t,e){return t=Di(t===F?n.length-1:t,0),function(){for(var u=arguments,i=-1,o=Di(u.length-t,0),f=Hu(o);++i<o;)f[i]=u[t+i];for(i=-1,o=Hu(t+1);++i<t;)o[i]=u[i];return o[t]=e(f),r(n,this,o)}}function De(n,t,r){var e=t+"";t=wo;var u,i=Ne;return u=(u=e.match(hn))?u[1].split(pn):[],r=i(u,r),(i=r.length)&&(u=i-1,r[u]=(1<i?"& ":"")+r[u],r=r.join(2<i?", ":" "),
e=e.replace(sn,"{\n/* [wrapped with "+r+"] */\n")),t(n,e)}function Me(n){var t=0,r=0;return function(){var e=Ti(),u=16-(e-r);if(r=e,0<u){if(800<=++t)return arguments[0]}else t=0;return n.apply(F,arguments)}}function Te(n,t){var r=-1,e=n.length,u=e-1;for(t=t===F?e:t;++r<t;){var e=cr(r,u),i=n[e];n[e]=n[r],n[r]=i}return n.length=t,n}function $e(n){if(typeof n=="string"||Au(n))return n;var t=n+"";return"0"==t&&1/n==-N?"-0":t}function Fe(n){if(null!=n){try{return fi.call(n)}catch(n){}return n+""}return"";
}function Ne(n,t){return u(Z,function(r){var e="_."+r[0];t&r[1]&&!c(n,e)&&n.push(e)}),n.sort()}function Pe(n){if(n instanceof Mn)return n.clone();var t=new zn(n.__wrapped__,n.__chain__);return t.__actions__=Mr(n.__actions__),t.__index__=n.__index__,t.__values__=n.__values__,t}function Ze(n,t,r){var e=null==n?0:n.length;return e?(r=null==r?0:Ou(r),0>r&&(r=Di(e+r,0)),g(n,je(t,3),r)):-1}function qe(n,t,r){var e=null==n?0:n.length;if(!e)return-1;var u=e-1;return r!==F&&(u=Ou(r),u=0>r?Di(e+u,0):Mi(u,e-1)),
g(n,je(t,3),u,true)}function Ve(n){return(null==n?0:n.length)?kt(n,1):[]}function Ke(n){return n&&n.length?n[0]:F}function Ge(n){var t=null==n?0:n.length;return t?n[t-1]:F}function He(n,t){return n&&n.length&&t&&t.length?or(n,t):n}function Je(n){return null==n?n:Ni.call(n)}function Ye(n){if(!n||!n.length)return[];var t=0;return n=f(n,function(n){if(_u(n))return t=Di(n.length,t),true}),E(t,function(t){return l(n,j(t))})}function Qe(n,t){if(!n||!n.length)return[];var e=Ye(n);return null==t?e:l(e,function(n){
return r(t,F,n)})}function Xe(n){return n=On(n),n.__chain__=true,n}function nu(n,t){return t(n)}function tu(){return this}function ru(n,t){return(af(n)?u:oo)(n,je(t,3))}function eu(n,t){return(af(n)?i:fo)(n,je(t,3))}function uu(n,t){return(af(n)?l:Yt)(n,je(t,3))}function iu(n,t,r){return t=r?F:t,t=n&&null==t?n.length:t,le(n,128,F,F,F,F,t)}function ou(n,t){var r;if(typeof t!="function")throw new ei("Expected a function");return n=Ou(n),function(){return 0<--n&&(r=t.apply(this,arguments)),1>=n&&(t=F),
r}}function fu(n,t,r){return t=r?F:t,n=le(n,8,F,F,F,F,F,t),n.placeholder=fu.placeholder,n}function cu(n,t,r){return t=r?F:t,n=le(n,16,F,F,F,F,F,t),n.placeholder=cu.placeholder,n}function au(n,t,r){function e(t){var r=c,e=a;return c=a=F,_=t,s=n.apply(e,r)}function u(n){var r=n-p;return n-=_,p===F||r>=t||0>r||g&&n>=l}function i(){var n=Jo();if(u(n))return o(n);var r,e=jo;r=n-_,n=t-(n-p),r=g?Mi(n,l-r):n,h=e(i,r)}function o(n){return h=F,d&&c?e(n):(c=a=F,s)}function f(){var n=Jo(),r=u(n);if(c=arguments,
a=this,p=n,r){if(h===F)return _=n=p,h=jo(i,t),v?e(n):s;if(g)return h=jo(i,t),e(p)}return h===F&&(h=jo(i,t)),s}var c,a,l,s,h,p,_=0,v=false,g=false,d=true;if(typeof n!="function")throw new ei("Expected a function");return t=Iu(t)||0,bu(r)&&(v=!!r.leading,l=(g="maxWait"in r)?Di(Iu(r.maxWait)||0,t):l,d="trailing"in r?!!r.trailing:d),f.cancel=function(){h!==F&&ho(h),_=0,c=p=a=h=F},f.flush=function(){return h===F?s:o(Jo())},f}function lu(n,t){function r(){var e=arguments,u=t?t.apply(this,e):e[0],i=r.cache;return i.has(u)?i.get(u):(e=n.apply(this,e),
r.cache=i.set(u,e)||i,e)}if(typeof n!="function"||null!=t&&typeof t!="function")throw new ei("Expected a function");return r.cache=new(lu.Cache||Pn),r}function su(n){if(typeof n!="function")throw new ei("Expected a function");return function(){var t=arguments;switch(t.length){case 0:return!n.call(this);case 1:return!n.call(this,t[0]);case 2:return!n.call(this,t[0],t[1]);case 3:return!n.call(this,t[0],t[1],t[2])}return!n.apply(this,t)}}function hu(n,t){return n===t||n!==n&&t!==t}function pu(n){return null!=n&&yu(n.length)&&!gu(n);
}function _u(n){return xu(n)&&pu(n)}function vu(n){if(!xu(n))return false;var t=zt(n);return"[object Error]"==t||"[object DOMException]"==t||typeof n.message=="string"&&typeof n.name=="string"&&!wu(n)}function gu(n){return!!bu(n)&&(n=zt(n),"[object Function]"==n||"[object GeneratorFunction]"==n||"[object AsyncFunction]"==n||"[object Proxy]"==n)}function du(n){return typeof n=="number"&&n==Ou(n)}function yu(n){return typeof n=="number"&&-1<n&&0==n%1&&9007199254740991>=n}function bu(n){var t=typeof n;return null!=n&&("object"==t||"function"==t);
}function xu(n){return null!=n&&typeof n=="object"}function ju(n){return typeof n=="number"||xu(n)&&"[object Number]"==zt(n)}function wu(n){return!(!xu(n)||"[object Object]"!=zt(n))&&(n=bi(n),null===n||(n=ci.call(n,"constructor")&&n.constructor,typeof n=="function"&&n instanceof n&&fi.call(n)==hi))}function mu(n){return typeof n=="string"||!af(n)&&xu(n)&&"[object String]"==zt(n)}function Au(n){return typeof n=="symbol"||xu(n)&&"[object Symbol]"==zt(n)}function ku(n){if(!n)return[];if(pu(n))return mu(n)?$(n):Mr(n);
if(Ai&&n[Ai]){n=n[Ai]();for(var t,r=[];!(t=n.next()).done;)r.push(t.value);return r}return t=yo(n),("[object Map]"==t?L:"[object Set]"==t?D:Du)(n)}function Eu(n){return n?(n=Iu(n),n===N||n===-N?1.7976931348623157e308*(0>n?-1:1):n===n?n:0):0===n?n:0}function Ou(n){n=Eu(n);var t=n%1;return n===n?t?n-t:n:0}function Su(n){return n?gt(Ou(n),0,4294967295):0}function Iu(n){if(typeof n=="number")return n;if(Au(n))return P;if(bu(n)&&(n=typeof n.valueOf=="function"?n.valueOf():n,n=bu(n)?n+"":n),typeof n!="string")return 0===n?n:+n;
n=n.replace(cn,"");var t=bn.test(n);return t||jn.test(n)?Fn(n.slice(2),t?2:8):yn.test(n)?P:+n}function Ru(n){return Tr(n,Uu(n))}function zu(n){return null==n?"":jr(n)}function Wu(n,t,r){return n=null==n?F:It(n,t),n===F?r:n}function Bu(n,t){return null!=n&&ke(n,t,Lt)}function Lu(n){return pu(n)?Gn(n):Ht(n)}function Uu(n){if(pu(n))n=Gn(n,true);else if(bu(n)){var t,r=Le(n),e=[];for(t in n)("constructor"!=t||!r&&ci.call(n,t))&&e.push(t);n=e}else{if(t=[],null!=n)for(r in ni(n))t.push(r);n=t}return n}function Cu(n,t){
if(null==n)return{};var r=l(ye(n),function(n){return[n]});return t=je(t),ur(n,r,function(n,r){return t(n,r[0])})}function Du(n){return null==n?[]:I(n,Lu(n))}function Mu(n){return Nf(zu(n).toLowerCase())}function Tu(n){return(n=zu(n))&&n.replace(mn,rt).replace(Rn,"")}function $u(n,t,r){return n=zu(n),t=r?F:t,t===F?Ln.test(n)?n.match(Wn)||[]:n.match(_n)||[]:n.match(t)||[]}function Fu(n){return function(){return n}}function Nu(n){return n}function Pu(n){return Gt(typeof n=="function"?n:dt(n,1))}function Zu(n,t,r){
var e=Lu(t),i=St(t,e);null!=r||bu(t)&&(i.length||!e.length)||(r=t,t=n,n=this,i=St(t,Lu(t)));var o=!(bu(r)&&"chain"in r&&!r.chain),f=gu(n);return u(i,function(r){var e=t[r];n[r]=e,f&&(n.prototype[r]=function(){var t=this.__chain__;if(o||t){var r=n(this.__wrapped__);return(r.__actions__=Mr(this.__actions__)).push({func:e,args:arguments,thisArg:n}),r.__chain__=t,r}return e.apply(n,s([this.value()],arguments))})}),n}function qu(){}function Vu(n){return We(n)?j($e(n)):ir(n)}function Ku(){return[]}function Gu(){
return false}En=null==En?Zn:it.defaults(Zn.Object(),En,it.pick(Zn,Un));var Hu=En.Array,Ju=En.Date,Yu=En.Error,Qu=En.Function,Xu=En.Math,ni=En.Object,ti=En.RegExp,ri=En.String,ei=En.TypeError,ui=Hu.prototype,ii=ni.prototype,oi=En["__core-js_shared__"],fi=Qu.prototype.toString,ci=ii.hasOwnProperty,ai=0,li=function(){var n=/[^.]+$/.exec(oi&&oi.keys&&oi.keys.IE_PROTO||"");return n?"Symbol(src)_1."+n:""}(),si=ii.toString,hi=fi.call(ni),pi=Zn._,_i=ti("^"+fi.call(ci).replace(on,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),vi=Kn?En.Buffer:F,gi=En.Symbol,di=En.Uint8Array,yi=vi?vi.f:F,bi=U(ni.getPrototypeOf,ni),xi=ni.create,ji=ii.propertyIsEnumerable,wi=ui.splice,mi=gi?gi.isConcatSpreadable:F,Ai=gi?gi.iterator:F,ki=gi?gi.toStringTag:F,Ei=function(){
try{var n=Ae(ni,"defineProperty");return n({},"",{}),n}catch(n){}}(),Oi=En.clearTimeout!==Zn.clearTimeout&&En.clearTimeout,Si=Ju&&Ju.now!==Zn.Date.now&&Ju.now,Ii=En.setTimeout!==Zn.setTimeout&&En.setTimeout,Ri=Xu.ceil,zi=Xu.floor,Wi=ni.getOwnPropertySymbols,Bi=vi?vi.isBuffer:F,Li=En.isFinite,Ui=ui.join,Ci=U(ni.keys,ni),Di=Xu.max,Mi=Xu.min,Ti=Ju.now,$i=En.parseInt,Fi=Xu.random,Ni=ui.reverse,Pi=Ae(En,"DataView"),Zi=Ae(En,"Map"),qi=Ae(En,"Promise"),Vi=Ae(En,"Set"),Ki=Ae(En,"WeakMap"),Gi=Ae(ni,"create"),Hi=Ki&&new Ki,Ji={},Yi=Fe(Pi),Qi=Fe(Zi),Xi=Fe(qi),no=Fe(Vi),to=Fe(Ki),ro=gi?gi.prototype:F,eo=ro?ro.valueOf:F,uo=ro?ro.toString:F,io=function(){
function n(){}return function(t){return bu(t)?xi?xi(t):(n.prototype=t,t=new n,n.prototype=F,t):{}}}();On.templateSettings={escape:Q,evaluate:X,interpolate:nn,variable:"",imports:{_:On}},On.prototype=Sn.prototype,On.prototype.constructor=On,zn.prototype=io(Sn.prototype),zn.prototype.constructor=zn,Mn.prototype=io(Sn.prototype),Mn.prototype.constructor=Mn,Tn.prototype.clear=function(){this.__data__=Gi?Gi(null):{},this.size=0},Tn.prototype.delete=function(n){return n=this.has(n)&&delete this.__data__[n],
this.size-=n?1:0,n},Tn.prototype.get=function(n){var t=this.__data__;return Gi?(n=t[n],"__lodash_hash_undefined__"===n?F:n):ci.call(t,n)?t[n]:F},Tn.prototype.has=function(n){var t=this.__data__;return Gi?t[n]!==F:ci.call(t,n)},Tn.prototype.set=function(n,t){var r=this.__data__;return this.size+=this.has(n)?0:1,r[n]=Gi&&t===F?"__lodash_hash_undefined__":t,this},Nn.prototype.clear=function(){this.__data__=[],this.size=0},Nn.prototype.delete=function(n){var t=this.__data__;return n=lt(t,n),!(0>n)&&(n==t.length-1?t.pop():wi.call(t,n,1),
--this.size,true)},Nn.prototype.get=function(n){var t=this.__data__;return n=lt(t,n),0>n?F:t[n][1]},Nn.prototype.has=function(n){return-1<lt(this.__data__,n)},Nn.prototype.set=function(n,t){var r=this.__data__,e=lt(r,n);return 0>e?(++this.size,r.push([n,t])):r[e][1]=t,this},Pn.prototype.clear=function(){this.size=0,this.__data__={hash:new Tn,map:new(Zi||Nn),string:new Tn}},Pn.prototype.delete=function(n){return n=we(this,n).delete(n),this.size-=n?1:0,n},Pn.prototype.get=function(n){return we(this,n).get(n);
},Pn.prototype.has=function(n){return we(this,n).has(n)},Pn.prototype.set=function(n,t){var r=we(this,n),e=r.size;return r.set(n,t),this.size+=r.size==e?0:1,this},qn.prototype.add=qn.prototype.push=function(n){return this.__data__.set(n,"__lodash_hash_undefined__"),this},qn.prototype.has=function(n){return this.__data__.has(n)},Vn.prototype.clear=function(){this.__data__=new Nn,this.size=0},Vn.prototype.delete=function(n){var t=this.__data__;return n=t.delete(n),this.size=t.size,n},Vn.prototype.get=function(n){
return this.__data__.get(n)},Vn.prototype.has=function(n){return this.__data__.has(n)},Vn.prototype.set=function(n,t){var r=this.__data__;if(r instanceof Nn){var e=r.__data__;if(!Zi||199>e.length)return e.push([n,t]),this.size=++r.size,this;r=this.__data__=new Pn(e)}return r.set(n,t),this.size=r.size,this};var oo=Zr(Et),fo=Zr(Ot,true),co=qr(),ao=qr(true),lo=Hi?function(n,t){return Hi.set(n,t),n}:Nu,so=Ei?function(n,t){return Ei(n,"toString",{configurable:true,enumerable:false,value:Fu(t),writable:true})}:Nu,ho=Oi||function(n){
return Zn.clearTimeout(n)},po=Vi&&1/D(new Vi([,-0]))[1]==N?function(n){return new Vi(n)}:qu,_o=Hi?function(n){return Hi.get(n)}:qu,vo=Wi?function(n){return null==n?[]:(n=ni(n),f(Wi(n),function(t){return ji.call(n,t)}))}:Ku,go=Wi?function(n){for(var t=[];n;)s(t,vo(n)),n=bi(n);return t}:Ku,yo=zt;(Pi&&"[object DataView]"!=yo(new Pi(new ArrayBuffer(1)))||Zi&&"[object Map]"!=yo(new Zi)||qi&&"[object Promise]"!=yo(qi.resolve())||Vi&&"[object Set]"!=yo(new Vi)||Ki&&"[object WeakMap]"!=yo(new Ki))&&(yo=function(n){
var t=zt(n);if(n=(n="[object Object]"==t?n.constructor:F)?Fe(n):"")switch(n){case Yi:return"[object DataView]";case Qi:return"[object Map]";case Xi:return"[object Promise]";case no:return"[object Set]";case to:return"[object WeakMap]"}return t});var bo=oi?gu:Gu,xo=Me(lo),jo=Ii||function(n,t){return Zn.setTimeout(n,t)},wo=Me(so),mo=function(n){n=lu(n,function(n){return 500===t.size&&t.clear(),n});var t=n.cache;return n}(function(n){var t=[];return en.test(n)&&t.push(""),n.replace(un,function(n,r,e,u){
t.push(e?u.replace(vn,"$1"):r||n)}),t}),Ao=lr(function(n,t){return _u(n)?jt(n,kt(t,1,_u,true)):[]}),ko=lr(function(n,t){var r=Ge(t);return _u(r)&&(r=F),_u(n)?jt(n,kt(t,1,_u,true),je(r,2)):[]}),Eo=lr(function(n,t){var r=Ge(t);return _u(r)&&(r=F),_u(n)?jt(n,kt(t,1,_u,true),F,r):[]}),Oo=lr(function(n){var t=l(n,Sr);return t.length&&t[0]===n[0]?Ut(t):[]}),So=lr(function(n){var t=Ge(n),r=l(n,Sr);return t===Ge(r)?t=F:r.pop(),r.length&&r[0]===n[0]?Ut(r,je(t,2)):[]}),Io=lr(function(n){var t=Ge(n),r=l(n,Sr);return(t=typeof t=="function"?t:F)&&r.pop(),
r.length&&r[0]===n[0]?Ut(r,F,t):[]}),Ro=lr(He),zo=ge(function(n,t){var r=null==n?0:n.length,e=vt(n,t);return fr(n,l(t,function(n){return Re(n,r)?+n:n}).sort(Ur)),e}),Wo=lr(function(n){return wr(kt(n,1,_u,true))}),Bo=lr(function(n){var t=Ge(n);return _u(t)&&(t=F),wr(kt(n,1,_u,true),je(t,2))}),Lo=lr(function(n){var t=Ge(n),t=typeof t=="function"?t:F;return wr(kt(n,1,_u,true),F,t)}),Uo=lr(function(n,t){return _u(n)?jt(n,t):[]}),Co=lr(function(n){return Er(f(n,_u))}),Do=lr(function(n){var t=Ge(n);return _u(t)&&(t=F),
Er(f(n,_u),je(t,2))}),Mo=lr(function(n){var t=Ge(n),t=typeof t=="function"?t:F;return Er(f(n,_u),F,t)}),To=lr(Ye),$o=lr(function(n){var t=n.length,t=1<t?n[t-1]:F,t=typeof t=="function"?(n.pop(),t):F;return Qe(n,t)}),Fo=ge(function(n){function t(t){return vt(t,n)}var r=n.length,e=r?n[0]:0,u=this.__wrapped__;return!(1<r||this.__actions__.length)&&u instanceof Mn&&Re(e)?(u=u.slice(e,+e+(r?1:0)),u.__actions__.push({func:nu,args:[t],thisArg:F}),new zn(u,this.__chain__).thru(function(n){return r&&!n.length&&n.push(F),
n})):this.thru(t)}),No=Nr(function(n,t,r){ci.call(n,r)?++n[r]:_t(n,r,1)}),Po=Yr(Ze),Zo=Yr(qe),qo=Nr(function(n,t,r){ci.call(n,r)?n[r].push(t):_t(n,r,[t])}),Vo=lr(function(n,t,e){var u=-1,i=typeof t=="function",o=pu(n)?Hu(n.length):[];return oo(n,function(n){o[++u]=i?r(t,n,e):Dt(n,t,e)}),o}),Ko=Nr(function(n,t,r){_t(n,r,t)}),Go=Nr(function(n,t,r){n[r?0:1].push(t)},function(){return[[],[]]}),Ho=lr(function(n,t){if(null==n)return[];var r=t.length;return 1<r&&ze(n,t[0],t[1])?t=[]:2<r&&ze(t[0],t[1],t[2])&&(t=[t[0]]),
rr(n,kt(t,1),[])}),Jo=Si||function(){return Zn.Date.now()},Yo=lr(function(n,t,r){var e=1;if(r.length)var u=C(r,xe(Yo)),e=32|e;return le(n,e,t,r,u)}),Qo=lr(function(n,t,r){var e=3;if(r.length)var u=C(r,xe(Qo)),e=32|e;return le(t,e,n,r,u)}),Xo=lr(function(n,t){return xt(n,1,t)}),nf=lr(function(n,t,r){return xt(n,Iu(t)||0,r)});lu.Cache=Pn;var tf=lr(function(n,t){t=1==t.length&&af(t[0])?l(t[0],S(je())):l(kt(t,1),S(je()));var e=t.length;return lr(function(u){for(var i=-1,o=Mi(u.length,e);++i<o;)u[i]=t[i].call(this,u[i]);
return r(n,this,u)})}),rf=lr(function(n,t){return le(n,32,F,t,C(t,xe(rf)))}),ef=lr(function(n,t){return le(n,64,F,t,C(t,xe(ef)))}),uf=ge(function(n,t){return le(n,256,F,F,F,t)}),of=oe(Wt),ff=oe(function(n,t){return n>=t}),cf=Mt(function(){return arguments}())?Mt:function(n){return xu(n)&&ci.call(n,"callee")&&!ji.call(n,"callee")},af=Hu.isArray,lf=Hn?S(Hn):Tt,sf=Bi||Gu,hf=Jn?S(Jn):$t,pf=Yn?S(Yn):Nt,_f=Qn?S(Qn):qt,vf=Xn?S(Xn):Vt,gf=nt?S(nt):Kt,df=oe(Jt),yf=oe(function(n,t){return n<=t}),bf=Pr(function(n,t){
if(Le(t)||pu(t))Tr(t,Lu(t),n);else for(var r in t)ci.call(t,r)&&at(n,r,t[r])}),xf=Pr(function(n,t){Tr(t,Uu(t),n)}),jf=Pr(function(n,t,r,e){Tr(t,Uu(t),n,e)}),wf=Pr(function(n,t,r,e){Tr(t,Lu(t),n,e)}),mf=ge(vt),Af=lr(function(n){return n.push(F,se),r(jf,F,n)}),kf=lr(function(n){return n.push(F,he),r(Rf,F,n)}),Ef=ne(function(n,t,r){n[t]=r},Fu(Nu)),Of=ne(function(n,t,r){ci.call(n,t)?n[t].push(r):n[t]=[r]},je),Sf=lr(Dt),If=Pr(function(n,t,r){nr(n,t,r)}),Rf=Pr(function(n,t,r,e){nr(n,t,r,e)}),zf=ge(function(n,t){
var r={};if(null==n)return r;var e=false;t=l(t,function(t){return t=Rr(t,n),e||(e=1<t.length),t}),Tr(n,ye(n),r),e&&(r=dt(r,7,pe));for(var u=t.length;u--;)mr(r,t[u]);return r}),Wf=ge(function(n,t){return null==n?{}:er(n,t)}),Bf=ae(Lu),Lf=ae(Uu),Uf=Gr(function(n,t,r){return t=t.toLowerCase(),n+(r?Mu(t):t)}),Cf=Gr(function(n,t,r){return n+(r?"-":"")+t.toLowerCase()}),Df=Gr(function(n,t,r){return n+(r?" ":"")+t.toLowerCase()}),Mf=Kr("toLowerCase"),Tf=Gr(function(n,t,r){return n+(r?"_":"")+t.toLowerCase();
}),$f=Gr(function(n,t,r){return n+(r?" ":"")+Nf(t)}),Ff=Gr(function(n,t,r){return n+(r?" ":"")+t.toUpperCase()}),Nf=Kr("toUpperCase"),Pf=lr(function(n,t){try{return r(n,F,t)}catch(n){return vu(n)?n:new Yu(n)}}),Zf=ge(function(n,t){return u(t,function(t){t=$e(t),_t(n,t,Yo(n[t],n))}),n}),qf=Qr(),Vf=Qr(true),Kf=lr(function(n,t){return function(r){return Dt(r,n,t)}}),Gf=lr(function(n,t){return function(r){return Dt(n,r,t)}}),Hf=re(l),Jf=re(o),Yf=re(_),Qf=ie(),Xf=ie(true),nc=te(function(n,t){return n+t},0),tc=ce("ceil"),rc=te(function(n,t){
return n/t},1),ec=ce("floor"),uc=te(function(n,t){return n*t},1),ic=ce("round"),oc=te(function(n,t){return n-t},0);return On.after=function(n,t){if(typeof t!="function")throw new ei("Expected a function");return n=Ou(n),function(){if(1>--n)return t.apply(this,arguments)}},On.ary=iu,On.assign=bf,On.assignIn=xf,On.assignInWith=jf,On.assignWith=wf,On.at=mf,On.before=ou,On.bind=Yo,On.bindAll=Zf,On.bindKey=Qo,On.castArray=function(){if(!arguments.length)return[];var n=arguments[0];return af(n)?n:[n]},
On.chain=Xe,On.chunk=function(n,t,r){if(t=(r?ze(n,t,r):t===F)?1:Di(Ou(t),0),r=null==n?0:n.length,!r||1>t)return[];for(var e=0,u=0,i=Hu(Ri(r/t));e<r;)i[u++]=vr(n,e,e+=t);return i},On.compact=function(n){for(var t=-1,r=null==n?0:n.length,e=0,u=[];++t<r;){var i=n[t];i&&(u[e++]=i)}return u},On.concat=function(){var n=arguments.length;if(!n)return[];for(var t=Hu(n-1),r=arguments[0];n--;)t[n-1]=arguments[n];return s(af(r)?Mr(r):[r],kt(t,1))},On.cond=function(n){var t=null==n?0:n.length,e=je();return n=t?l(n,function(n){
if("function"!=typeof n[1])throw new ei("Expected a function");return[e(n[0]),n[1]]}):[],lr(function(e){for(var u=-1;++u<t;){var i=n[u];if(r(i[0],this,e))return r(i[1],this,e)}})},On.conforms=function(n){return yt(dt(n,1))},On.constant=Fu,On.countBy=No,On.create=function(n,t){var r=io(n);return null==t?r:ht(r,t)},On.curry=fu,On.curryRight=cu,On.debounce=au,On.defaults=Af,On.defaultsDeep=kf,On.defer=Xo,On.delay=nf,On.difference=Ao,On.differenceBy=ko,On.differenceWith=Eo,On.drop=function(n,t,r){var e=null==n?0:n.length;
return e?(t=r||t===F?1:Ou(t),vr(n,0>t?0:t,e)):[]},On.dropRight=function(n,t,r){var e=null==n?0:n.length;return e?(t=r||t===F?1:Ou(t),t=e-t,vr(n,0,0>t?0:t)):[]},On.dropRightWhile=function(n,t){return n&&n.length?Ar(n,je(t,3),true,true):[]},On.dropWhile=function(n,t){return n&&n.length?Ar(n,je(t,3),true):[]},On.fill=function(n,t,r,e){var u=null==n?0:n.length;if(!u)return[];for(r&&typeof r!="number"&&ze(n,t,r)&&(r=0,e=u),u=n.length,r=Ou(r),0>r&&(r=-r>u?0:u+r),e=e===F||e>u?u:Ou(e),0>e&&(e+=u),e=r>e?0:Su(e);r<e;)n[r++]=t;
return n},On.filter=function(n,t){return(af(n)?f:At)(n,je(t,3))},On.flatMap=function(n,t){return kt(uu(n,t),1)},On.flatMapDeep=function(n,t){return kt(uu(n,t),N)},On.flatMapDepth=function(n,t,r){return r=r===F?1:Ou(r),kt(uu(n,t),r)},On.flatten=Ve,On.flattenDeep=function(n){return(null==n?0:n.length)?kt(n,N):[]},On.flattenDepth=function(n,t){return null!=n&&n.length?(t=t===F?1:Ou(t),kt(n,t)):[]},On.flip=function(n){return le(n,512)},On.flow=qf,On.flowRight=Vf,On.fromPairs=function(n){for(var t=-1,r=null==n?0:n.length,e={};++t<r;){
var u=n[t];e[u[0]]=u[1]}return e},On.functions=function(n){return null==n?[]:St(n,Lu(n))},On.functionsIn=function(n){return null==n?[]:St(n,Uu(n))},On.groupBy=qo,On.initial=function(n){return(null==n?0:n.length)?vr(n,0,-1):[]},On.intersection=Oo,On.intersectionBy=So,On.intersectionWith=Io,On.invert=Ef,On.invertBy=Of,On.invokeMap=Vo,On.iteratee=Pu,On.keyBy=Ko,On.keys=Lu,On.keysIn=Uu,On.map=uu,On.mapKeys=function(n,t){var r={};return t=je(t,3),Et(n,function(n,e,u){_t(r,t(n,e,u),n)}),r},On.mapValues=function(n,t){
var r={};return t=je(t,3),Et(n,function(n,e,u){_t(r,e,t(n,e,u))}),r},On.matches=function(n){return Qt(dt(n,1))},On.matchesProperty=function(n,t){return Xt(n,dt(t,1))},On.memoize=lu,On.merge=If,On.mergeWith=Rf,On.method=Kf,On.methodOf=Gf,On.mixin=Zu,On.negate=su,On.nthArg=function(n){return n=Ou(n),lr(function(t){return tr(t,n)})},On.omit=zf,On.omitBy=function(n,t){return Cu(n,su(je(t)))},On.once=function(n){return ou(2,n)},On.orderBy=function(n,t,r,e){return null==n?[]:(af(t)||(t=null==t?[]:[t]),
r=e?F:r,af(r)||(r=null==r?[]:[r]),rr(n,t,r))},On.over=Hf,On.overArgs=tf,On.overEvery=Jf,On.overSome=Yf,On.partial=rf,On.partialRight=ef,On.partition=Go,On.pick=Wf,On.pickBy=Cu,On.property=Vu,On.propertyOf=function(n){return function(t){return null==n?F:It(n,t)}},On.pull=Ro,On.pullAll=He,On.pullAllBy=function(n,t,r){return n&&n.length&&t&&t.length?or(n,t,je(r,2)):n},On.pullAllWith=function(n,t,r){return n&&n.length&&t&&t.length?or(n,t,F,r):n},On.pullAt=zo,On.range=Qf,On.rangeRight=Xf,On.rearg=uf,On.reject=function(n,t){
return(af(n)?f:At)(n,su(je(t,3)))},On.remove=function(n,t){var r=[];if(!n||!n.length)return r;var e=-1,u=[],i=n.length;for(t=je(t,3);++e<i;){var o=n[e];t(o,e,n)&&(r.push(o),u.push(e))}return fr(n,u),r},On.rest=function(n,t){if(typeof n!="function")throw new ei("Expected a function");return t=t===F?t:Ou(t),lr(n,t)},On.reverse=Je,On.sampleSize=function(n,t,r){return t=(r?ze(n,t,r):t===F)?1:Ou(t),(af(n)?ot:hr)(n,t)},On.set=function(n,t,r){return null==n?n:pr(n,t,r)},On.setWith=function(n,t,r,e){return e=typeof e=="function"?e:F,
null==n?n:pr(n,t,r,e)},On.shuffle=function(n){return(af(n)?ft:_r)(n)},On.slice=function(n,t,r){var e=null==n?0:n.length;return e?(r&&typeof r!="number"&&ze(n,t,r)?(t=0,r=e):(t=null==t?0:Ou(t),r=r===F?e:Ou(r)),vr(n,t,r)):[]},On.sortBy=Ho,On.sortedUniq=function(n){return n&&n.length?br(n):[]},On.sortedUniqBy=function(n,t){return n&&n.length?br(n,je(t,2)):[]},On.split=function(n,t,r){return r&&typeof r!="number"&&ze(n,t,r)&&(t=r=F),r=r===F?4294967295:r>>>0,r?(n=zu(n))&&(typeof t=="string"||null!=t&&!_f(t))&&(t=jr(t),
!t&&Bn.test(n))?zr($(n),0,r):n.split(t,r):[]},On.spread=function(n,t){if(typeof n!="function")throw new ei("Expected a function");return t=null==t?0:Di(Ou(t),0),lr(function(e){var u=e[t];return e=zr(e,0,t),u&&s(e,u),r(n,this,e)})},On.tail=function(n){var t=null==n?0:n.length;return t?vr(n,1,t):[]},On.take=function(n,t,r){return n&&n.length?(t=r||t===F?1:Ou(t),vr(n,0,0>t?0:t)):[]},On.takeRight=function(n,t,r){var e=null==n?0:n.length;return e?(t=r||t===F?1:Ou(t),t=e-t,vr(n,0>t?0:t,e)):[]},On.takeRightWhile=function(n,t){
return n&&n.length?Ar(n,je(t,3),false,true):[]},On.takeWhile=function(n,t){return n&&n.length?Ar(n,je(t,3)):[]},On.tap=function(n,t){return t(n),n},On.throttle=function(n,t,r){var e=true,u=true;if(typeof n!="function")throw new ei("Expected a function");return bu(r)&&(e="leading"in r?!!r.leading:e,u="trailing"in r?!!r.trailing:u),au(n,t,{leading:e,maxWait:t,trailing:u})},On.thru=nu,On.toArray=ku,On.toPairs=Bf,On.toPairsIn=Lf,On.toPath=function(n){return af(n)?l(n,$e):Au(n)?[n]:Mr(mo(zu(n)))},On.toPlainObject=Ru,
On.transform=function(n,t,r){var e=af(n),i=e||sf(n)||gf(n);if(t=je(t,4),null==r){var o=n&&n.constructor;r=i?e?new o:[]:bu(n)&&gu(o)?io(bi(n)):{}}return(i?u:Et)(n,function(n,e,u){return t(r,n,e,u)}),r},On.unary=function(n){return iu(n,1)},On.union=Wo,On.unionBy=Bo,On.unionWith=Lo,On.uniq=function(n){return n&&n.length?wr(n):[]},On.uniqBy=function(n,t){return n&&n.length?wr(n,je(t,2)):[]},On.uniqWith=function(n,t){return t=typeof t=="function"?t:F,n&&n.length?wr(n,F,t):[]},On.unset=function(n,t){return null==n||mr(n,t);
},On.unzip=Ye,On.unzipWith=Qe,On.update=function(n,t,r){return null==n?n:pr(n,t,Ir(r)(It(n,t)),void 0)},On.updateWith=function(n,t,r,e){return e=typeof e=="function"?e:F,null!=n&&(n=pr(n,t,Ir(r)(It(n,t)),e)),n},On.values=Du,On.valuesIn=function(n){return null==n?[]:I(n,Uu(n))},On.without=Uo,On.words=$u,On.wrap=function(n,t){return rf(Ir(t),n)},On.xor=Co,On.xorBy=Do,On.xorWith=Mo,On.zip=To,On.zipObject=function(n,t){return Or(n||[],t||[],at)},On.zipObjectDeep=function(n,t){return Or(n||[],t||[],pr);
},On.zipWith=$o,On.entries=Bf,On.entriesIn=Lf,On.extend=xf,On.extendWith=jf,Zu(On,On),On.add=nc,On.attempt=Pf,On.camelCase=Uf,On.capitalize=Mu,On.ceil=tc,On.clamp=function(n,t,r){return r===F&&(r=t,t=F),r!==F&&(r=Iu(r),r=r===r?r:0),t!==F&&(t=Iu(t),t=t===t?t:0),gt(Iu(n),t,r)},On.clone=function(n){return dt(n,4)},On.cloneDeep=function(n){return dt(n,5)},On.cloneDeepWith=function(n,t){return t=typeof t=="function"?t:F,dt(n,5,t)},On.cloneWith=function(n,t){return t=typeof t=="function"?t:F,dt(n,4,t)},
On.conformsTo=function(n,t){return null==t||bt(n,t,Lu(t))},On.deburr=Tu,On.defaultTo=function(n,t){return null==n||n!==n?t:n},On.divide=rc,On.endsWith=function(n,t,r){n=zu(n),t=jr(t);var e=n.length,e=r=r===F?e:gt(Ou(r),0,e);return r-=t.length,0<=r&&n.slice(r,e)==t},On.eq=hu,On.escape=function(n){return(n=zu(n))&&Y.test(n)?n.replace(H,et):n},On.escapeRegExp=function(n){return(n=zu(n))&&fn.test(n)?n.replace(on,"\\$&"):n},On.every=function(n,t,r){var e=af(n)?o:wt;return r&&ze(n,t,r)&&(t=F),e(n,je(t,3));
},On.find=Po,On.findIndex=Ze,On.findKey=function(n,t){return v(n,je(t,3),Et)},On.findLast=Zo,On.findLastIndex=qe,On.findLastKey=function(n,t){return v(n,je(t,3),Ot)},On.floor=ec,On.forEach=ru,On.forEachRight=eu,On.forIn=function(n,t){return null==n?n:co(n,je(t,3),Uu)},On.forInRight=function(n,t){return null==n?n:ao(n,je(t,3),Uu)},On.forOwn=function(n,t){return n&&Et(n,je(t,3))},On.forOwnRight=function(n,t){return n&&Ot(n,je(t,3))},On.get=Wu,On.gt=of,On.gte=ff,On.has=function(n,t){return null!=n&&ke(n,t,Bt);
},On.hasIn=Bu,On.head=Ke,On.identity=Nu,On.includes=function(n,t,r,e){return n=pu(n)?n:Du(n),r=r&&!e?Ou(r):0,e=n.length,0>r&&(r=Di(e+r,0)),mu(n)?r<=e&&-1<n.indexOf(t,r):!!e&&-1<d(n,t,r)},On.indexOf=function(n,t,r){var e=null==n?0:n.length;return e?(r=null==r?0:Ou(r),0>r&&(r=Di(e+r,0)),d(n,t,r)):-1},On.inRange=function(n,t,r){return t=Eu(t),r===F?(r=t,t=0):r=Eu(r),n=Iu(n),n>=Mi(t,r)&&n<Di(t,r)},On.invoke=Sf,On.isArguments=cf,On.isArray=af,On.isArrayBuffer=lf,On.isArrayLike=pu,On.isArrayLikeObject=_u,
On.isBoolean=function(n){return true===n||false===n||xu(n)&&"[object Boolean]"==zt(n)},On.isBuffer=sf,On.isDate=hf,On.isElement=function(n){return xu(n)&&1===n.nodeType&&!wu(n)},On.isEmpty=function(n){if(null==n)return true;if(pu(n)&&(af(n)||typeof n=="string"||typeof n.splice=="function"||sf(n)||gf(n)||cf(n)))return!n.length;var t=yo(n);if("[object Map]"==t||"[object Set]"==t)return!n.size;if(Le(n))return!Ht(n).length;for(var r in n)if(ci.call(n,r))return false;return true},On.isEqual=function(n,t){return Ft(n,t);
},On.isEqualWith=function(n,t,r){var e=(r=typeof r=="function"?r:F)?r(n,t):F;return e===F?Ft(n,t,F,r):!!e},On.isError=vu,On.isFinite=function(n){return typeof n=="number"&&Li(n)},On.isFunction=gu,On.isInteger=du,On.isLength=yu,On.isMap=pf,On.isMatch=function(n,t){return n===t||Pt(n,t,me(t))},On.isMatchWith=function(n,t,r){return r=typeof r=="function"?r:F,Pt(n,t,me(t),r)},On.isNaN=function(n){return ju(n)&&n!=+n},On.isNative=function(n){if(bo(n))throw new Yu("Unsupported core-js use. Try https://npms.io/search?q=ponyfill.");
return Zt(n)},On.isNil=function(n){return null==n},On.isNull=function(n){return null===n},On.isNumber=ju,On.isObject=bu,On.isObjectLike=xu,On.isPlainObject=wu,On.isRegExp=_f,On.isSafeInteger=function(n){return du(n)&&-9007199254740991<=n&&9007199254740991>=n},On.isSet=vf,On.isString=mu,On.isSymbol=Au,On.isTypedArray=gf,On.isUndefined=function(n){return n===F},On.isWeakMap=function(n){return xu(n)&&"[object WeakMap]"==yo(n)},On.isWeakSet=function(n){return xu(n)&&"[object WeakSet]"==zt(n)},On.join=function(n,t){
return null==n?"":Ui.call(n,t)},On.kebabCase=Cf,On.last=Ge,On.lastIndexOf=function(n,t,r){var e=null==n?0:n.length;if(!e)return-1;var u=e;if(r!==F&&(u=Ou(r),u=0>u?Di(e+u,0):Mi(u,e-1)),t===t){for(r=u+1;r--&&n[r]!==t;);n=r}else n=g(n,b,u,true);return n},On.lowerCase=Df,On.lowerFirst=Mf,On.lt=df,On.lte=yf,On.max=function(n){return n&&n.length?mt(n,Nu,Wt):F},On.maxBy=function(n,t){return n&&n.length?mt(n,je(t,2),Wt):F},On.mean=function(n){return x(n,Nu)},On.meanBy=function(n,t){return x(n,je(t,2))},On.min=function(n){
return n&&n.length?mt(n,Nu,Jt):F},On.minBy=function(n,t){return n&&n.length?mt(n,je(t,2),Jt):F},On.stubArray=Ku,On.stubFalse=Gu,On.stubObject=function(){return{}},On.stubString=function(){return""},On.stubTrue=function(){return true},On.multiply=uc,On.nth=function(n,t){return n&&n.length?tr(n,Ou(t)):F},On.noConflict=function(){return Zn._===this&&(Zn._=pi),this},On.noop=qu,On.now=Jo,On.pad=function(n,t,r){n=zu(n);var e=(t=Ou(t))?T(n):0;return!t||e>=t?n:(t=(t-e)/2,ee(zi(t),r)+n+ee(Ri(t),r))},On.padEnd=function(n,t,r){
n=zu(n);var e=(t=Ou(t))?T(n):0;return t&&e<t?n+ee(t-e,r):n},On.padStart=function(n,t,r){n=zu(n);var e=(t=Ou(t))?T(n):0;return t&&e<t?ee(t-e,r)+n:n},On.parseInt=function(n,t,r){return r||null==t?t=0:t&&(t=+t),$i(zu(n).replace(an,""),t||0)},On.random=function(n,t,r){if(r&&typeof r!="boolean"&&ze(n,t,r)&&(t=r=F),r===F&&(typeof t=="boolean"?(r=t,t=F):typeof n=="boolean"&&(r=n,n=F)),n===F&&t===F?(n=0,t=1):(n=Eu(n),t===F?(t=n,n=0):t=Eu(t)),n>t){var e=n;n=t,t=e}return r||n%1||t%1?(r=Fi(),Mi(n+r*(t-n+$n("1e-"+((r+"").length-1))),t)):cr(n,t);
},On.reduce=function(n,t,r){var e=af(n)?h:m,u=3>arguments.length;return e(n,je(t,4),r,u,oo)},On.reduceRight=function(n,t,r){var e=af(n)?p:m,u=3>arguments.length;return e(n,je(t,4),r,u,fo)},On.repeat=function(n,t,r){return t=(r?ze(n,t,r):t===F)?1:Ou(t),ar(zu(n),t)},On.replace=function(){var n=arguments,t=zu(n[0]);return 3>n.length?t:t.replace(n[1],n[2])},On.result=function(n,t,r){t=Rr(t,n);var e=-1,u=t.length;for(u||(u=1,n=F);++e<u;){var i=null==n?F:n[$e(t[e])];i===F&&(e=u,i=r),n=gu(i)?i.call(n):i;
}return n},On.round=ic,On.runInContext=w,On.sample=function(n){return(af(n)?tt:sr)(n)},On.size=function(n){if(null==n)return 0;if(pu(n))return mu(n)?T(n):n.length;var t=yo(n);return"[object Map]"==t||"[object Set]"==t?n.size:Ht(n).length},On.snakeCase=Tf,On.some=function(n,t,r){var e=af(n)?_:gr;return r&&ze(n,t,r)&&(t=F),e(n,je(t,3))},On.sortedIndex=function(n,t){return dr(n,t)},On.sortedIndexBy=function(n,t,r){return yr(n,t,je(r,2))},On.sortedIndexOf=function(n,t){var r=null==n?0:n.length;if(r){
var e=dr(n,t);if(e<r&&hu(n[e],t))return e}return-1},On.sortedLastIndex=function(n,t){return dr(n,t,true)},On.sortedLastIndexBy=function(n,t,r){return yr(n,t,je(r,2),true)},On.sortedLastIndexOf=function(n,t){if(null==n?0:n.length){var r=dr(n,t,true)-1;if(hu(n[r],t))return r}return-1},On.startCase=$f,On.startsWith=function(n,t,r){return n=zu(n),r=null==r?0:gt(Ou(r),0,n.length),t=jr(t),n.slice(r,r+t.length)==t},On.subtract=oc,On.sum=function(n){return n&&n.length?k(n,Nu):0},On.sumBy=function(n,t){return n&&n.length?k(n,je(t,2)):0;
},On.template=function(n,t,r){var e=On.templateSettings;r&&ze(n,t,r)&&(t=F),n=zu(n),t=jf({},t,e,se),r=jf({},t.imports,e.imports,se);var u,i,o=Lu(r),f=I(r,o),c=0;r=t.interpolate||An;var a="__p+='";r=ti((t.escape||An).source+"|"+r.source+"|"+(r===nn?gn:An).source+"|"+(t.evaluate||An).source+"|$","g");var l="sourceURL"in t?"//# sourceURL="+t.sourceURL+"\n":"";if(n.replace(r,function(t,r,e,o,f,l){return e||(e=o),a+=n.slice(c,l).replace(kn,B),r&&(u=true,a+="'+__e("+r+")+'"),f&&(i=true,a+="';"+f+";\n__p+='"),
e&&(a+="'+((__t=("+e+"))==null?'':__t)+'"),c=l+t.length,t}),a+="';",(t=t.variable)||(a="with(obj){"+a+"}"),a=(i?a.replace(q,""):a).replace(V,"$1").replace(K,"$1;"),a="function("+(t||"obj")+"){"+(t?"":"obj||(obj={});")+"var __t,__p=''"+(u?",__e=_.escape":"")+(i?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+a+"return __p}",t=Pf(function(){return Qu(o,l+"return "+a).apply(F,f)}),t.source=a,vu(t))throw t;return t},On.times=function(n,t){if(n=Ou(n),1>n||9007199254740991<n)return[];
var r=4294967295,e=Mi(n,4294967295);for(t=je(t),n-=4294967295,e=E(e,t);++r<n;)t(r);return e},On.toFinite=Eu,On.toInteger=Ou,On.toLength=Su,On.toLower=function(n){return zu(n).toLowerCase()},On.toNumber=Iu,On.toSafeInteger=function(n){return n?gt(Ou(n),-9007199254740991,9007199254740991):0===n?n:0},On.toString=zu,On.toUpper=function(n){return zu(n).toUpperCase()},On.trim=function(n,t,r){return(n=zu(n))&&(r||t===F)?n.replace(cn,""):n&&(t=jr(t))?(n=$(n),r=$(t),t=z(n,r),r=W(n,r)+1,zr(n,t,r).join("")):n;
},On.trimEnd=function(n,t,r){return(n=zu(n))&&(r||t===F)?n.replace(ln,""):n&&(t=jr(t))?(n=$(n),t=W(n,$(t))+1,zr(n,0,t).join("")):n},On.trimStart=function(n,t,r){return(n=zu(n))&&(r||t===F)?n.replace(an,""):n&&(t=jr(t))?(n=$(n),t=z(n,$(t)),zr(n,t).join("")):n},On.truncate=function(n,t){var r=30,e="...";if(bu(t))var u="separator"in t?t.separator:u,r="length"in t?Ou(t.length):r,e="omission"in t?jr(t.omission):e;n=zu(n);var i=n.length;if(Bn.test(n))var o=$(n),i=o.length;if(r>=i)return n;if(i=r-T(e),1>i)return e;
if(r=o?zr(o,0,i).join(""):n.slice(0,i),u===F)return r+e;if(o&&(i+=r.length-i),_f(u)){if(n.slice(i).search(u)){var f=r;for(u.global||(u=ti(u.source,zu(dn.exec(u))+"g")),u.lastIndex=0;o=u.exec(f);)var c=o.index;r=r.slice(0,c===F?i:c)}}else n.indexOf(jr(u),i)!=i&&(u=r.lastIndexOf(u),-1<u&&(r=r.slice(0,u)));return r+e},On.unescape=function(n){return(n=zu(n))&&J.test(n)?n.replace(G,ut):n},On.uniqueId=function(n){var t=++ai;return zu(n)+t},On.upperCase=Ff,On.upperFirst=Nf,On.each=ru,On.eachRight=eu,On.first=Ke,
Zu(On,function(){var n={};return Et(On,function(t,r){ci.call(On.prototype,r)||(n[r]=t)}),n}(),{chain:false}),On.VERSION="4.17.4",u("bind bindKey curry curryRight partial partialRight".split(" "),function(n){On[n].placeholder=On}),u(["drop","take"],function(n,t){Mn.prototype[n]=function(r){r=r===F?1:Di(Ou(r),0);var e=this.__filtered__&&!t?new Mn(this):this.clone();return e.__filtered__?e.__takeCount__=Mi(r,e.__takeCount__):e.__views__.push({size:Mi(r,4294967295),type:n+(0>e.__dir__?"Right":"")}),e},Mn.prototype[n+"Right"]=function(t){
return this.reverse()[n](t).reverse()}}),u(["filter","map","takeWhile"],function(n,t){var r=t+1,e=1==r||3==r;Mn.prototype[n]=function(n){var t=this.clone();return t.__iteratees__.push({iteratee:je(n,3),type:r}),t.__filtered__=t.__filtered__||e,t}}),u(["head","last"],function(n,t){var r="take"+(t?"Right":"");Mn.prototype[n]=function(){return this[r](1).value()[0]}}),u(["initial","tail"],function(n,t){var r="drop"+(t?"":"Right");Mn.prototype[n]=function(){return this.__filtered__?new Mn(this):this[r](1);
}}),Mn.prototype.compact=function(){return this.filter(Nu)},Mn.prototype.find=function(n){return this.filter(n).head()},Mn.prototype.findLast=function(n){return this.reverse().find(n)},Mn.prototype.invokeMap=lr(function(n,t){return typeof n=="function"?new Mn(this):this.map(function(r){return Dt(r,n,t)})}),Mn.prototype.reject=function(n){return this.filter(su(je(n)))},Mn.prototype.slice=function(n,t){n=Ou(n);var r=this;return r.__filtered__&&(0<n||0>t)?new Mn(r):(0>n?r=r.takeRight(-n):n&&(r=r.drop(n)),
t!==F&&(t=Ou(t),r=0>t?r.dropRight(-t):r.take(t-n)),r)},Mn.prototype.takeRightWhile=function(n){return this.reverse().takeWhile(n).reverse()},Mn.prototype.toArray=function(){return this.take(4294967295)},Et(Mn.prototype,function(n,t){var r=/^(?:filter|find|map|reject)|While$/.test(t),e=/^(?:head|last)$/.test(t),u=On[e?"take"+("last"==t?"Right":""):t],i=e||/^find/.test(t);u&&(On.prototype[t]=function(){function t(n){return n=u.apply(On,s([n],f)),e&&h?n[0]:n}var o=this.__wrapped__,f=e?[1]:arguments,c=o instanceof Mn,a=f[0],l=c||af(o);
l&&r&&typeof a=="function"&&1!=a.length&&(c=l=false);var h=this.__chain__,p=!!this.__actions__.length,a=i&&!h,c=c&&!p;return!i&&l?(o=c?o:new Mn(this),o=n.apply(o,f),o.__actions__.push({func:nu,args:[t],thisArg:F}),new zn(o,h)):a&&c?n.apply(this,f):(o=this.thru(t),a?e?o.value()[0]:o.value():o)})}),u("pop push shift sort splice unshift".split(" "),function(n){var t=ui[n],r=/^(?:push|sort|unshift)$/.test(n)?"tap":"thru",e=/^(?:pop|shift)$/.test(n);On.prototype[n]=function(){var n=arguments;if(e&&!this.__chain__){
var u=this.value();return t.apply(af(u)?u:[],n)}return this[r](function(r){return t.apply(af(r)?r:[],n)})}}),Et(Mn.prototype,function(n,t){var r=On[t];if(r){var e=r.name+"";(Ji[e]||(Ji[e]=[])).push({name:t,func:r})}}),Ji[Xr(F,2).name]=[{name:"wrapper",func:F}],Mn.prototype.clone=function(){var n=new Mn(this.__wrapped__);return n.__actions__=Mr(this.__actions__),n.__dir__=this.__dir__,n.__filtered__=this.__filtered__,n.__iteratees__=Mr(this.__iteratees__),n.__takeCount__=this.__takeCount__,n.__views__=Mr(this.__views__),
n},Mn.prototype.reverse=function(){if(this.__filtered__){var n=new Mn(this);n.__dir__=-1,n.__filtered__=true}else n=this.clone(),n.__dir__*=-1;return n},Mn.prototype.value=function(){var n,t=this.__wrapped__.value(),r=this.__dir__,e=af(t),u=0>r,i=e?t.length:0;n=i;for(var o=this.__views__,f=0,c=-1,a=o.length;++c<a;){var l=o[c],s=l.size;switch(l.type){case"drop":f+=s;break;case"dropRight":n-=s;break;case"take":n=Mi(n,f+s);break;case"takeRight":f=Di(f,n-s)}}if(n={start:f,end:n},o=n.start,f=n.end,n=f-o,
o=u?f:o-1,f=this.__iteratees__,c=f.length,a=0,l=Mi(n,this.__takeCount__),!e||!u&&i==n&&l==n)return kr(t,this.__actions__);e=[];n:for(;n--&&a<l;){for(o+=r,u=-1,i=t[o];++u<c;){var h=f[u],s=h.type,h=(0,h.iteratee)(i);if(2==s)i=h;else if(!h){if(1==s)continue n;break n}}e[a++]=i}return e},On.prototype.at=Fo,On.prototype.chain=function(){return Xe(this)},On.prototype.commit=function(){return new zn(this.value(),this.__chain__)},On.prototype.next=function(){this.__values__===F&&(this.__values__=ku(this.value()));
var n=this.__index__>=this.__values__.length;return{done:n,value:n?F:this.__values__[this.__index__++]}},On.prototype.plant=function(n){for(var t,r=this;r instanceof Sn;){var e=Pe(r);e.__index__=0,e.__values__=F,t?u.__wrapped__=e:t=e;var u=e,r=r.__wrapped__}return u.__wrapped__=n,t},On.prototype.reverse=function(){var n=this.__wrapped__;return n instanceof Mn?(this.__actions__.length&&(n=new Mn(this)),n=n.reverse(),n.__actions__.push({func:nu,args:[Je],thisArg:F}),new zn(n,this.__chain__)):this.thru(Je);
},On.prototype.toJSON=On.prototype.valueOf=On.prototype.value=function(){return kr(this.__wrapped__,this.__actions__)},On.prototype.first=On.prototype.head,Ai&&(On.prototype[Ai]=tu),On}();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(Zn._=it, define(function(){return it})):Vn?((Vn.exports=it)._=it,qn._=it):Zn._=it}).call(this);

7
wwwroot/js/lib/moment.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1113
wwwroot/js/lib/page.js Normal file

File diff suppressed because it is too large Load Diff

7
wwwroot/js/lib/store.min.js vendored Normal file
View File

@@ -0,0 +1,7 @@
/* Copyright (c) 2010-2016 Marcus Westin */
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.store = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
(function (global){
"use strict";module.exports=function(){function e(){try{return o in n&&n[o]}catch(e){return!1}}var t,r={},n="undefined"!=typeof window?window:global,i=n.document,o="localStorage",a="script";if(r.disabled=!1,r.version="1.3.20",r.set=function(e,t){},r.get=function(e,t){},r.has=function(e){return void 0!==r.get(e)},r.remove=function(e){},r.clear=function(){},r.transact=function(e,t,n){null==n&&(n=t,t=null),null==t&&(t={});var i=r.get(e,t);n(i),r.set(e,i)},r.getAll=function(){},r.forEach=function(){},r.serialize=function(e){return JSON.stringify(e)},r.deserialize=function(e){if("string"==typeof e)try{return JSON.parse(e)}catch(t){return e||void 0}},e())t=n[o],r.set=function(e,n){return void 0===n?r.remove(e):(t.setItem(e,r.serialize(n)),n)},r.get=function(e,n){var i=r.deserialize(t.getItem(e));return void 0===i?n:i},r.remove=function(e){t.removeItem(e)},r.clear=function(){t.clear()},r.getAll=function(){var e={};return r.forEach(function(t,r){e[t]=r}),e},r.forEach=function(e){for(var n=0;n<t.length;n++){var i=t.key(n);e(i,r.get(i))}};else if(i&&i.documentElement.addBehavior){var c,u;try{u=new ActiveXObject("htmlfile"),u.open(),u.write("<"+a+">document.w=window</"+a+'><iframe src="/favicon.ico"></iframe>'),u.close(),c=u.w.frames[0].document,t=c.createElement("div")}catch(l){t=i.createElement("div"),c=i.body}var f=function(e){return function(){var n=Array.prototype.slice.call(arguments,0);n.unshift(t),c.appendChild(t),t.addBehavior("#default#userData"),t.load(o);var i=e.apply(r,n);return c.removeChild(t),i}},d=new RegExp("[!\"#$%&'()*+,/\\\\:;<=>?@[\\]^`{|}~]","g"),s=function(e){return e.replace(/^d/,"___$&").replace(d,"___")};r.set=f(function(e,t,n){return t=s(t),void 0===n?r.remove(t):(e.setAttribute(t,r.serialize(n)),e.save(o),n)}),r.get=f(function(e,t,n){t=s(t);var i=r.deserialize(e.getAttribute(t));return void 0===i?n:i}),r.remove=f(function(e,t){t=s(t),e.removeAttribute(t),e.save(o)}),r.clear=f(function(e){var t=e.XMLDocument.documentElement.attributes;e.load(o);for(var r=t.length-1;r>=0;r--)e.removeAttribute(t[r].name);e.save(o)}),r.getAll=function(e){var t={};return r.forEach(function(e,r){t[e]=r}),t},r.forEach=f(function(e,t){for(var n,i=e.XMLDocument.documentElement.attributes,o=0;n=i[o];++o)t(n.name,r.deserialize(e.getAttribute(n.name)))})}try{var v="__storejs__";r.set(v,v),r.get(v)!=v&&(r.disabled=!0),r.remove(v)}catch(l){r.disabled=!0}return r.enabled=!r.disabled,r}();
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}]},{},[1])(1)
});

View File

@@ -0,0 +1,9 @@
<div>
<img src="img/android-chrome-192x192.png" alt="Pecklist logo" >
<h2>Login</h2>
<form method="post" action="index.html">
<p><input type="text" id="login" value="" placeholder="Username" autocapitalize="none"></p>
<p><input type="password" id="password" value="" placeholder="Password"></p>
<p class="submit"><input type="submit" id="btnSubmit" value="Login"></p>
</form>
</div>

View File

@@ -0,0 +1,3 @@
<div>
<h2>404 NOT FOUND 404</h2>
</div>

View File

@@ -0,0 +1,16 @@
<div class="card pl-mnu">
<div class="card-body">
<div class="mb-2">
<button id="btn-toggle-done" type="button" class="pl-toggle mdi btn btn-small btn-success btn-block"></button>
</div>
<div class="mb-2">
<input class="form-control font-weight-bold pl-short-item" type="text" id="edtxt" name="edtxt">
</div>
<div class="w-100 btn-group btn-group-lg">
<button data-val="0" value="0" type="button" class="ct-btn-priority btn btn-small btn-secondary">0</button>
<button data-val="1" value="1" type="button" class="ct-btn-priority btn btn-small btn-dark">1</button>
<button data-val="2" value="2" type="button" class="ct-btn-priority btn btn-small btn-primary">2</button>
<button data-val="3" value="3" type="button" class="ct-btn-priority btn btn-small btn-danger">3</button>
</div>
</div>
</div>

View File

@@ -0,0 +1,8 @@
<header class="header">
<h3 class="text-truncate" id="peck-title"></h3>
<div id="list-context-div"></div>
<input id="new-item" type="text" class="new-item" placeholder="New item" autofocus>
</header>
<div>
<ul id="peck-list" class="peck-list list-group"></ul>
</div>

View File

@@ -0,0 +1,13 @@
<div class="card pl-mnu">
<div class="card-body">
<div class="mb-2">
<input class="form-control font-weight-bold pl-short-item" type="text" id="edtxt" name="edtxt">
</div>
<div class="mb-2">
<button id="btn-delete-list" type="button" class="btn btn-danger">DELETE LIST</button>
</div>
</div>
</div>

View File

@@ -0,0 +1,20 @@
<div>
<form id="frm" method="post" action="index.html">
<div class="form-group mb-5">
<div class="app-frm-buttons">
<button id="btn-new-list" class="mdi mdi-plus">New list</button>
</div>
</div>
<div class="form-group">
<label for="oldpassword">Change password</label>
<input class="form-control" type="text" id="oldpassword" name="oldpassword" placeholder="current password" value="">
<input class="form-control" type="text" id="newpassword" name="newpassword" placeholder="new password" value="">
<div class="app-frm-buttons">
<button id="btn-change-password">Update</button>
</div>
</div>
</form>
</div>

View File

@@ -0,0 +1,40 @@
<nav id="gz-nav" class="navbar fixed-top navbar-expand-lg navbar-dark bg-success">
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li id="main" class="nav-item">
<a class="nav-link mdi mdi-view-list" href="#!/main">Main </a>
</li>
<li id="settings" class="nav-item">
<a class="nav-link mdi mdi-settings" href="#!/settings">Settings </a>
</li>
<li class="nav-item">
<a class="nav-link mdi mdi-logout" href="#!/logout">Log off </a>
</li>
</ul>
</div>
</nav>
<div class="gz-content mx-2 ">
<div id="app-error-div" class="alert alert-danger d-none" role="alert">
<p id="app-error-message"></p>
</div>
<div>
<div id="gz-context-group" class="btn-group my-3" role="group">
</div>
</div>
<main id="app-shell-main-content">
</main>
</div>

18
wwwroot/manifest.json Normal file
View File

@@ -0,0 +1,18 @@
{
"name": "Pecklist",
"icons": [
{
"src": "/img/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/img/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}