Implemented groundwork for search configuration.
This commit is contained in:
parent
3129e5e564
commit
2719142538
@ -15,7 +15,7 @@ namespace Props.Shop.Adafruit.Api
|
||||
private Dictionary<string, List<ProductListing>> listings = new Dictionary<string, List<ProductListing>>();
|
||||
private bool requested = false;
|
||||
public DateTime TimeOfLastRequest { get; private set; }
|
||||
public bool RequestReady => DateTime.Now - TimeOfLastRequest > TimeSpan.FromMinutes(minutesPerRequest);
|
||||
public bool RequestReady => !requested || DateTime.Now - TimeOfLastRequest > TimeSpan.FromMinutes(minutesPerRequest);
|
||||
|
||||
public ProductListingManager(int requestsPerMinute = 5)
|
||||
{
|
||||
@ -27,10 +27,10 @@ namespace Props.Shop.Adafruit.Api
|
||||
requested = true;
|
||||
TimeOfLastRequest = DateTime.Now;
|
||||
HttpResponseMessage response = await http.GetAsync("/products");
|
||||
SetListings(await response.Content.ReadAsStringAsync());
|
||||
SetListingsData(await response.Content.ReadAsStringAsync());
|
||||
}
|
||||
|
||||
public void SetListings(string data)
|
||||
public void SetListingsData(string data)
|
||||
{
|
||||
ListingsParser listingsParser = new ListingsParser(data);
|
||||
foreach (ProductListing listing in listingsParser.ProductListings)
|
||||
|
@ -25,7 +25,7 @@ namespace Props.Shop.Framework
|
||||
public int MinPurchases { get; set; }
|
||||
public bool KeepUnknownPurchaseCount { get; set; } = true;
|
||||
public int MinReviews { get; set; }
|
||||
public bool KeepUnknownRatingCount { get; set; } = true;
|
||||
public bool KeepUnknownReviewCount { get; set; } = true;
|
||||
public bool EnableMaxShippingFee { get; set; }
|
||||
private int maxShippingFee;
|
||||
|
||||
@ -59,7 +59,7 @@ namespace Props.Shop.Framework
|
||||
MinPurchases == other.MinPurchases &&
|
||||
KeepUnknownPurchaseCount == other.KeepUnknownPurchaseCount &&
|
||||
MinReviews == other.MinReviews &&
|
||||
KeepUnknownRatingCount == other.KeepUnknownRatingCount &&
|
||||
KeepUnknownReviewCount == other.KeepUnknownReviewCount &&
|
||||
EnableMaxShippingFee == other.EnableMaxShippingFee &&
|
||||
MaxShippingFee == other.MaxShippingFee &&
|
||||
KeepUnknownShipping == other.KeepUnknownShipping;
|
||||
@ -81,5 +81,16 @@ namespace Props.Shop.Framework
|
||||
{
|
||||
return (Filters)this.MemberwiseClone();
|
||||
}
|
||||
|
||||
public bool Validate(ProductListing listing)
|
||||
{
|
||||
if (listing.Shipping == null && !KeepUnknownShipping || (EnableMaxShippingFee && listing.Shipping > MaxShippingFee)) return false;
|
||||
float shippingDifference = listing.Shipping != null ? listing.Shipping.Value : 0;
|
||||
if (!(listing.LowerPrice + shippingDifference >= LowerPrice && (!EnableUpperPrice || listing.UpperPrice + shippingDifference <= UpperPrice))) return false;
|
||||
if ((listing.Rating == null && !KeepUnrated) && MinRating > (listing.Rating == null ? 0 : listing.Rating)) return false;
|
||||
if ((listing.PurchaseCount == null && !KeepUnknownPurchaseCount) || MinPurchases > (listing.PurchaseCount == null ? 0 : listing.PurchaseCount)) return false;
|
||||
if ((listing.ReviewCount == null && !KeepUnknownReviewCount) || MinReviews > (listing.ReviewCount == null ? 0 : listing.ReviewCount)) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ namespace Props.Shop.Adafruit.Tests.Api
|
||||
public async Task TestSearch()
|
||||
{
|
||||
ProductListingManager mockProductListingManager = new ProductListingManager();
|
||||
mockProductListingManager.SetListings(File.ReadAllText("./Assets/products.json"));
|
||||
mockProductListingManager.SetListingsData(File.ReadAllText("./Assets/products.json"));
|
||||
List<ProductListing> results = new List<ProductListing>();
|
||||
await foreach (ProductListing item in mockProductListingManager.Search("arduino", 0.5f))
|
||||
{
|
||||
|
@ -5,7 +5,7 @@ module.exports = {
|
||||
"node": true,
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"parser": "babel-eslint",
|
||||
"parser": "@babel/eslint-parser",
|
||||
"rules": {
|
||||
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||
@ -22,6 +22,10 @@ module.exports = {
|
||||
"always"
|
||||
],
|
||||
"comma-dangle": ["error", "only-multiline"],
|
||||
"space-before-function-paren": ["error", "never"]
|
||||
"space-before-function-paren": ["error", {
|
||||
"anonymous": "always",
|
||||
"named": "never",
|
||||
"asyncArrow": "always"
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
@ -6,8 +6,9 @@
|
||||
}
|
||||
|
||||
<div class="flex-grow-1 d-flex flex-column justify-content-center">
|
||||
<div class="jumbotron border-top border-bottom">
|
||||
<h1 class="mx-auto mt-3 mb-4 text-center">@ViewData["Title"]</h1>
|
||||
<div class="jumbotron sole d-flex flex-column align-content-center">
|
||||
<img alt="Props logo" src="~/images/logo-simplified.svg" class="img-fluid" style="max-height: 180px;" asp-append-version="true" />
|
||||
<h1 class="mt-3 mb-4 text-center">@ViewData["Title"]</h1>
|
||||
<div class="my-3 row justify-content-md-center">
|
||||
<div class="col-md-4">
|
||||
<form id="account" method="post">
|
||||
|
@ -13,3 +13,5 @@
|
||||
<li class="nav-item"><a class="nav-link @ManageNavPages.TwoFactorAuthenticationNavClass(ViewContext)" id="two-factor" asp-page="./TwoFactorAuthentication">Two-factor authentication</a></li>
|
||||
<li class="nav-item"><a class="nav-link @ManageNavPages.PersonalDataNavClass(ViewContext)" id="personal-data" asp-page="./PersonalData">Personal data</a></li>
|
||||
</ul>
|
||||
|
||||
@* TODO: Finish styling this page. *@
|
@ -5,8 +5,9 @@
|
||||
}
|
||||
|
||||
<div class="flex-grow-1 d-flex flex-column justify-content-center">
|
||||
<div class="jumbotron border-top border-bottom">
|
||||
<h1 class="mx-auto mt-3 mb-4 text-center">@ViewData["Title"]</h1>
|
||||
<div class="jumbotron sole d-flex flex-column align-content-center">
|
||||
<img alt="Props logo" src="~/images/logo-simplified.svg" class="img-fluid" style="max-height: 180px;" asp-append-version="true" />
|
||||
<h1 class="mt-3 mb-4 text-center">@ViewData["Title"]</h1>
|
||||
<div class="my-3 row justify-content-md-center">
|
||||
<div class="col-md-4">
|
||||
<form asp-route-returnUrl="@Model.ReturnUrl" method="post">
|
||||
|
@ -20,4 +20,4 @@
|
||||
</p>
|
||||
}
|
||||
}
|
||||
@* TODO: https://aka.ms/aspaccountconf *@
|
||||
@* TODO: Do something about this. *@
|
41
Props/Controllers/SearchController.cs
Normal file
41
Props/Controllers/SearchController.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Props.Models.Search;
|
||||
using Props.Services.Modules;
|
||||
|
||||
namespace Props.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[Controller]")]
|
||||
public class SearchController : ControllerBase
|
||||
{
|
||||
private SearchOutline defaultOutline = new SearchOutline();
|
||||
IShopManager shopManager;
|
||||
|
||||
public SearchController(IShopManager shopManager)
|
||||
{
|
||||
this.shopManager = shopManager;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("Shops/Available")]
|
||||
public IActionResult GetAvailableShops()
|
||||
{
|
||||
return Ok(shopManager.AvailableShops());
|
||||
}
|
||||
|
||||
|
||||
[HttpGet]
|
||||
[Route("Default/Outline/Filters")]
|
||||
public IActionResult GetDefaultFilters()
|
||||
{
|
||||
return Ok(defaultOutline.Filters);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("Default/Outline/DisabledShops")]
|
||||
public IActionResult GetDefaultDisabledShops()
|
||||
{
|
||||
return Ok(defaultOutline.Disabled);
|
||||
}
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
||||
using Props.Models;
|
||||
using Props.Models.Search;
|
||||
using Props.Models.User;
|
||||
using Props.Shop.Framework;
|
||||
|
||||
@ -15,6 +16,8 @@ namespace Props.Data
|
||||
{
|
||||
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
|
||||
{
|
||||
DbSet<SearchOutline> SearchOutlines { get; set; }
|
||||
DbSet<ProductListingInfo> TrackedListings { get; set; }
|
||||
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
|
||||
: base(options)
|
||||
{
|
||||
@ -37,11 +40,11 @@ namespace Props.Data
|
||||
);
|
||||
|
||||
modelBuilder.Entity<SearchOutline>()
|
||||
.Property(e => e.ShopStates)
|
||||
.Property(e => e.Disabled)
|
||||
.HasConversion(
|
||||
v => JsonSerializer.Serialize(v, null),
|
||||
v => JsonSerializer.Deserialize<SearchOutline.ShopToggler>(v, null),
|
||||
new ValueComparer<SearchOutline.ShopToggler>(
|
||||
v => JsonSerializer.Deserialize<SearchOutline.ShopsDisabled>(v, null),
|
||||
new ValueComparer<SearchOutline.ShopsDisabled>(
|
||||
(a, b) => a.Equals(b),
|
||||
c => c.GetHashCode(),
|
||||
c => c.Copy()
|
||||
|
@ -9,7 +9,7 @@ using Props.Data;
|
||||
namespace Props.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20210721064503_InitialCreate")]
|
||||
[Migration("20210722180024_InitialCreate")]
|
||||
partial class InitialCreate
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
@ -150,7 +150,80 @@ namespace Props.Data.Migrations
|
||||
b.ToTable("AspNetUserTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.ApplicationUser", b =>
|
||||
modelBuilder.Entity("Props.Models.ResultsPreferences", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ApplicationUserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Order")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProfileName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ResultsPreferences");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.Search.ProductListingInfo", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("Hits")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("LastUpdated")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProductName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProductUrl")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("TrackedListings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.Search.SearchOutline", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ApplicationUserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Disabled")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Filters")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId");
|
||||
|
||||
b.ToTable("SearchOutlines");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.User.ApplicationUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
@ -214,56 +287,6 @@ namespace Props.Data.Migrations
|
||||
b.ToTable("AspNetUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.ResultsPreferences", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ApplicationUserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Order")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ResultsPreferences");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.SearchOutline", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ApplicationUserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Filters")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("MaxResults")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ShopStates")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("SearchOutline");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Shared.Models.User.ApplicationPreferences", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@ -299,7 +322,7 @@ namespace Props.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
b.HasOne("Props.Models.User.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@ -308,7 +331,7 @@ namespace Props.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
b.HasOne("Props.Models.User.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@ -323,7 +346,7 @@ namespace Props.Data.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
b.HasOne("Props.Models.User.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@ -332,7 +355,7 @@ namespace Props.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
b.HasOne("Props.Models.User.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@ -341,32 +364,38 @@ namespace Props.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Props.Models.ResultsPreferences", b =>
|
||||
{
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
b.HasOne("Props.Models.User.ApplicationUser", "ApplicationUser")
|
||||
.WithOne("ResultsPreferences")
|
||||
.HasForeignKey("Props.Models.ResultsPreferences", "ApplicationUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ApplicationUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.SearchOutline", b =>
|
||||
modelBuilder.Entity("Props.Models.Search.SearchOutline", b =>
|
||||
{
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
.WithOne("SearchOutline")
|
||||
.HasForeignKey("Props.Models.SearchOutline", "ApplicationUserId")
|
||||
b.HasOne("Props.Models.User.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("SearchOutlines")
|
||||
.HasForeignKey("ApplicationUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ApplicationUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Shared.Models.User.ApplicationPreferences", b =>
|
||||
{
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
b.HasOne("Props.Models.User.ApplicationUser", "ApplicationUser")
|
||||
.WithOne("ApplicationPreferences")
|
||||
.HasForeignKey("Props.Shared.Models.User.ApplicationPreferences", "ApplicationUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ApplicationUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.ApplicationUser", b =>
|
||||
modelBuilder.Entity("Props.Models.User.ApplicationUser", b =>
|
||||
{
|
||||
b.Navigation("ApplicationPreferences")
|
||||
.IsRequired();
|
||||
@ -374,8 +403,7 @@ namespace Props.Data.Migrations
|
||||
b.Navigation("ResultsPreferences")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("SearchOutline")
|
||||
.IsRequired();
|
||||
b.Navigation("SearchOutlines");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
@ -46,6 +46,22 @@ namespace Props.Data.Migrations
|
||||
table.PrimaryKey("PK_AspNetUsers", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "TrackedListings",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Hits = table.Column<uint>(type: "INTEGER", nullable: false),
|
||||
LastUpdated = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
ProductUrl = table.Column<string>(type: "TEXT", nullable: true),
|
||||
ProductName = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_TrackedListings", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetRoleClaims",
|
||||
columns: table => new
|
||||
@ -180,7 +196,8 @@ namespace Props.Data.Migrations
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
ApplicationUserId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Order = table.Column<string>(type: "TEXT", nullable: false)
|
||||
Order = table.Column<string>(type: "TEXT", nullable: false),
|
||||
ProfileName = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@ -194,21 +211,20 @@ namespace Props.Data.Migrations
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SearchOutline",
|
||||
name: "SearchOutlines",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
ApplicationUserId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Filters = table.Column<string>(type: "TEXT", nullable: true),
|
||||
MaxResults = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
ShopStates = table.Column<string>(type: "TEXT", nullable: false)
|
||||
Disabled = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SearchOutline", x => x.Id);
|
||||
table.PrimaryKey("PK_SearchOutlines", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_SearchOutline_AspNetUsers_ApplicationUserId",
|
||||
name: "FK_SearchOutlines_AspNetUsers_ApplicationUserId",
|
||||
column: x => x.ApplicationUserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
@ -265,10 +281,9 @@ namespace Props.Data.Migrations
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SearchOutline_ApplicationUserId",
|
||||
table: "SearchOutline",
|
||||
column: "ApplicationUserId",
|
||||
unique: true);
|
||||
name: "IX_SearchOutlines_ApplicationUserId",
|
||||
table: "SearchOutlines",
|
||||
column: "ApplicationUserId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
@ -295,7 +310,10 @@ namespace Props.Data.Migrations
|
||||
name: "ResultsPreferences");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "SearchOutline");
|
||||
name: "SearchOutlines");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "TrackedListings");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetRoles");
|
@ -148,7 +148,80 @@ namespace Props.Data.Migrations
|
||||
b.ToTable("AspNetUserTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.ApplicationUser", b =>
|
||||
modelBuilder.Entity("Props.Models.ResultsPreferences", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ApplicationUserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Order")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProfileName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ResultsPreferences");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.Search.ProductListingInfo", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("Hits")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("LastUpdated")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProductName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProductUrl")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("TrackedListings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.Search.SearchOutline", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ApplicationUserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Disabled")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Filters")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId");
|
||||
|
||||
b.ToTable("SearchOutlines");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.User.ApplicationUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
@ -212,56 +285,6 @@ namespace Props.Data.Migrations
|
||||
b.ToTable("AspNetUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.ResultsPreferences", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ApplicationUserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Order")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ResultsPreferences");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.SearchOutline", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ApplicationUserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Filters")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("MaxResults")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ShopStates")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("SearchOutline");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Shared.Models.User.ApplicationPreferences", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@ -297,7 +320,7 @@ namespace Props.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
b.HasOne("Props.Models.User.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@ -306,7 +329,7 @@ namespace Props.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
b.HasOne("Props.Models.User.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@ -321,7 +344,7 @@ namespace Props.Data.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
b.HasOne("Props.Models.User.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@ -330,7 +353,7 @@ namespace Props.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
b.HasOne("Props.Models.User.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@ -339,32 +362,38 @@ namespace Props.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Props.Models.ResultsPreferences", b =>
|
||||
{
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
b.HasOne("Props.Models.User.ApplicationUser", "ApplicationUser")
|
||||
.WithOne("ResultsPreferences")
|
||||
.HasForeignKey("Props.Models.ResultsPreferences", "ApplicationUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ApplicationUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.SearchOutline", b =>
|
||||
modelBuilder.Entity("Props.Models.Search.SearchOutline", b =>
|
||||
{
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
.WithOne("SearchOutline")
|
||||
.HasForeignKey("Props.Models.SearchOutline", "ApplicationUserId")
|
||||
b.HasOne("Props.Models.User.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("SearchOutlines")
|
||||
.HasForeignKey("ApplicationUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ApplicationUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Shared.Models.User.ApplicationPreferences", b =>
|
||||
{
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
b.HasOne("Props.Models.User.ApplicationUser", "ApplicationUser")
|
||||
.WithOne("ApplicationPreferences")
|
||||
.HasForeignKey("Props.Shared.Models.User.ApplicationPreferences", "ApplicationUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ApplicationUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.ApplicationUser", b =>
|
||||
modelBuilder.Entity("Props.Models.User.ApplicationUser", b =>
|
||||
{
|
||||
b.Navigation("ApplicationPreferences")
|
||||
.IsRequired();
|
||||
@ -372,8 +401,7 @@ namespace Props.Data.Migrations
|
||||
b.Navigation("ResultsPreferences")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("SearchOutline")
|
||||
.IsRequired();
|
||||
b.Navigation("SearchOutlines");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
|
18
Props/Models/Search/ProductListingInfo.cs
Normal file
18
Props/Models/Search/ProductListingInfo.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using Props.Shop.Framework;
|
||||
|
||||
namespace Props.Models.Search
|
||||
{
|
||||
public class ProductListingInfo
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public uint Hits { get; set; }
|
||||
|
||||
public DateTime LastUpdated { get; set; }
|
||||
|
||||
public string ProductUrl { get; set; }
|
||||
|
||||
public string ProductName { get; set; }
|
||||
}
|
||||
}
|
@ -2,23 +2,27 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using Props.Models.User;
|
||||
using Props.Shop.Framework;
|
||||
|
||||
namespace Props.Models
|
||||
namespace Props.Models.Search
|
||||
{
|
||||
public class SearchOutline
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public string ApplicationUserId { get; set; }
|
||||
|
||||
public Filters Filters { get; set; }
|
||||
public int MaxResults { get; set; } = 100;
|
||||
[Required]
|
||||
public virtual ApplicationUser ApplicationUser { get; set; }
|
||||
|
||||
public Filters Filters { get; set; } = new Filters();
|
||||
|
||||
[Required]
|
||||
public ShopToggler ShopStates { get; set; } = new ShopToggler();
|
||||
public ShopsDisabled Disabled { get; set; } = new ShopsDisabled();
|
||||
|
||||
public sealed class ShopToggler : HashSet<string>
|
||||
public sealed class ShopsDisabled : HashSet<string>
|
||||
{
|
||||
public int TotalShops { get; set; }
|
||||
public bool this[string name]
|
||||
@ -41,9 +45,9 @@ namespace Props.Models
|
||||
}
|
||||
}
|
||||
|
||||
public ShopToggler Copy()
|
||||
public ShopsDisabled Copy()
|
||||
{
|
||||
ShopToggler copy = new ShopToggler();
|
||||
ShopsDisabled copy = new ShopsDisabled();
|
||||
copy.Union(this);
|
||||
return copy;
|
||||
}
|
||||
@ -63,9 +67,8 @@ namespace Props.Models
|
||||
SearchOutline other = (SearchOutline)obj;
|
||||
return
|
||||
Id == other.Id &&
|
||||
MaxResults == other.MaxResults &&
|
||||
Filters.Equals(other.Filters) &&
|
||||
ShopStates.Equals(other.ShopStates);
|
||||
Disabled.Equals(other.Disabled);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
@ -1,4 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Props.Models.User;
|
||||
|
||||
namespace Props.Shared.Models.User
|
||||
{
|
||||
@ -9,6 +10,9 @@ namespace Props.Shared.Models.User
|
||||
[Required]
|
||||
public string ApplicationUserId { get; set; }
|
||||
|
||||
[Required]
|
||||
public virtual ApplicationUser ApplicationUser { get; set; }
|
||||
|
||||
public bool EnableSearchHistory { get; set; } = true;
|
||||
public bool DarkMode { get; set; } = false;
|
||||
}
|
||||
|
@ -4,30 +4,30 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Props.Models.Search;
|
||||
using Props.Shared.Models.User;
|
||||
|
||||
namespace Props.Models.User
|
||||
{
|
||||
public class ApplicationUser : IdentityUser
|
||||
{
|
||||
[Required]
|
||||
public virtual SearchOutline SearchOutline { get; private set; }
|
||||
public virtual ISet<SearchOutline> SearchOutlines { get; set; }
|
||||
|
||||
[Required]
|
||||
public virtual ResultsPreferences ResultsPreferences { get; private set; }
|
||||
|
||||
[Required]
|
||||
public virtual ApplicationPreferences ApplicationPreferences { get; private set; }
|
||||
|
||||
// TODO: Write project system.
|
||||
public ApplicationUser()
|
||||
{
|
||||
SearchOutline = new SearchOutline();
|
||||
ResultsPreferences = new ResultsPreferences();
|
||||
ApplicationPreferences = new ApplicationPreferences();
|
||||
}
|
||||
|
||||
public ApplicationUser(SearchOutline searchOutline, ResultsPreferences resultsPreferences, ApplicationPreferences applicationPreferences)
|
||||
public ApplicationUser(ResultsPreferences resultsPreferences, ApplicationPreferences applicationPreferences)
|
||||
{
|
||||
this.SearchOutline = searchOutline;
|
||||
this.ResultsPreferences = resultsPreferences;
|
||||
this.ApplicationPreferences = applicationPreferences;
|
||||
}
|
||||
|
@ -3,18 +3,27 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Props.Models.User;
|
||||
|
||||
namespace Props.Models
|
||||
{
|
||||
public class ResultsPreferences
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public string ApplicationUserId { get; set; }
|
||||
|
||||
[Required]
|
||||
public virtual ApplicationUser ApplicationUser { get; set; }
|
||||
|
||||
|
||||
[Required]
|
||||
public IList<Category> Order { get; set; }
|
||||
|
||||
[Required]
|
||||
public string ProfileName { get; set; }
|
||||
|
||||
public ResultsPreferences()
|
||||
{
|
||||
Order = new List<Category>(Enum.GetValues<Category>().Length);
|
||||
|
11
Props/Options/ModulesOptions.cs
Normal file
11
Props/Options/ModulesOptions.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Props.Options
|
||||
{
|
||||
public class ModulesOptions
|
||||
{
|
||||
public const string Modules = "Modules";
|
||||
public string ShopsDir { get; set; }
|
||||
public bool RecursiveLoad { get; set; }
|
||||
public int MaxResults { get; set; }
|
||||
public string ShopRegex { get; set; }
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@
|
||||
|
||||
<section class="jumbotron d-flex flex-column align-items-center">
|
||||
<div>
|
||||
<img alt="Props logo" src="./images/logo.svg" class="img-fluid" style="max-height: 540px;" />
|
||||
<img alt="Props logo" src="~/images/logo.svg" class="img-fluid" style="max-height: 540px;" asp-append-version="true" />
|
||||
</div>
|
||||
<div class="text-center px-3 my-2 concise">
|
||||
<h1 class="my-2">Props</h1>
|
||||
|
@ -4,13 +4,107 @@
|
||||
ViewData["Specific"] = "Search";
|
||||
}
|
||||
|
||||
<div class="container d-flex flex-column align-items-center">
|
||||
<form class="my-4" style="width: 720px;">
|
||||
<div class="container">
|
||||
<div class="my-4 less-concise mx-auto">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="What are you looking for?" aria-label="Search" aria-describedby="search-btn">
|
||||
<input type="text" class="form-control" placeholder="What are you looking for?" aria-label="Search" aria-describedby="search-btn" id="search-bar">
|
||||
<input type="checkbox" class="btn-check" id="config-check-toggle" autocomplete="off">
|
||||
<label class="btn btn-outline-secondary" for="config-check-toggle"><i class="bi bi-sliders"></i></label>
|
||||
<button class="btn btn-outline-primary" type="button" id="search-btn">Search</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tear d-none" id="configuration">
|
||||
<div class="container invisible">
|
||||
<h2 class="my-2">Configuration</h2>
|
||||
<div class="row justify-content-md-center">
|
||||
<section class="col-lg">
|
||||
<h3>Price</h3>
|
||||
<div class="mb-3">
|
||||
<label for="max-price" class="form-label">Maximum Price</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">
|
||||
<input class="form-check-input mt-0" type="checkbox" id="max-price-enabled">
|
||||
</div>
|
||||
<span class="input-group-text">$</span>
|
||||
<input type="number" class="form-control" min="0" id="max-price">
|
||||
<span class="input-group-text">.00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="min-price" class="form-label">Minimum Price</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">$</span>
|
||||
<input type="number" class="form-control" min="0" id="min-price">
|
||||
<span class="input-group-text">.00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="max-shipping" class="form-label">Maximum Shipping Fee</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">
|
||||
<input class="form-check-input mt-0" type="checkbox" id="max-shipping-enabled">
|
||||
</div>
|
||||
<span class="input-group-text">$</span>
|
||||
<input type="number" class="form-control" min="0" id="max-shipping">
|
||||
<span class="input-group-text">.00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="keep-unknown-shipping">
|
||||
<label class="form-check-label" for="keep-unknown-shipping">Keep Unknown Shipping</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="col-lg">
|
||||
<h3>Metrics</h3>
|
||||
<div class="mb-3">
|
||||
<label for="min-purchases" class="form-label">Minimum Purchases</label>
|
||||
<div class="input-group">
|
||||
<input type="number" class="form-control" min="0" id="min-purchases">
|
||||
<span class="input-group-text">Purchases</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="keep-unknown-purchases">
|
||||
<label class="form-check-label" for="keep-unknown-purchases">Keep Unknown Purchases</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="min-reviews" class="form-label">Minimum Reviews</label>
|
||||
<div class="input-group">
|
||||
<input type="number" class="form-control" min="0" id="min-reviews">
|
||||
<span class="input-group-text">Reviews</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="keep-unknown-reviews">
|
||||
<label class="form-check-label" for="keep-unknown-reviews">Keep Unknown Number of Reviews</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<label for="min-rating" class="form-label">Minimum Rating</label>
|
||||
<input type="range" class="form-range" id="min-rating" min="0" max="100" step="1">
|
||||
<div id="min-rating-display" class="form-text"></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="keep-unrated">
|
||||
<label class="form-check-label" for="keep-unrated">Keep Minimum Rating</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="col-md">
|
||||
<h3>Shops Enabled</h3>
|
||||
<div class="mb-3 px-3" id="shop-checkboxes">
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@* TODO: Add results display and default results display *@
|
@ -4,6 +4,6 @@ namespace Props.Pages
|
||||
{
|
||||
public class SearchModel : PageModel
|
||||
{
|
||||
|
||||
// TODO: Complete the search model.
|
||||
}
|
||||
}
|
@ -11,10 +11,6 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@ViewData["Title"] - Props</title>
|
||||
<script src="~/js/site.js" asp-append-version="true"></script>
|
||||
@if (!string.IsNullOrEmpty((ViewData["Specific"] as string)))
|
||||
{
|
||||
<script src="@($"~/js/specific/{(ViewData["Specific"])}.js")" asp-append-version="true"></script>
|
||||
}
|
||||
</head>
|
||||
|
||||
<body class="theme-light">
|
||||
@ -67,6 +63,10 @@
|
||||
© 2021 - Props - <a asp-area="" asp-page="/Privacy">Privacy</a>
|
||||
</footer>
|
||||
|
||||
@if (!string.IsNullOrEmpty((ViewData["Specific"] as string)))
|
||||
{
|
||||
<script src="@($"~/js/specific/{(ViewData["Specific"])}.js")" asp-append-version="true"></script>
|
||||
}
|
||||
@await RenderSectionAsync("Scripts", required: false)
|
||||
</body>
|
||||
|
||||
|
@ -3,13 +3,13 @@ using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Props.Services.Content
|
||||
{
|
||||
public class ContentManager<Page> : IContentManager<Page>
|
||||
public class CachedContentManager<TPage> : IContentManager<TPage>
|
||||
{
|
||||
private dynamic data;
|
||||
private readonly string directory;
|
||||
private readonly string fileName;
|
||||
|
||||
dynamic IContentManager<Page>.Json
|
||||
dynamic IContentManager<TPage>.Json
|
||||
{
|
||||
get
|
||||
{
|
||||
@ -18,10 +18,10 @@ namespace Props.Services.Content
|
||||
}
|
||||
}
|
||||
|
||||
public ContentManager(string directory = "content")
|
||||
public CachedContentManager(string directory = "content")
|
||||
{
|
||||
this.directory = directory;
|
||||
this.fileName = typeof(Page).Name.Replace("Model", "") + ".json";
|
||||
this.fileName = typeof(TPage).Name.Replace("Model", "") + ".json";
|
||||
}
|
||||
}
|
||||
}
|
14
Props/Services/Modules/IShopManager.cs
Normal file
14
Props/Services/Modules/IShopManager.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Props.Models.Search;
|
||||
using Props.Shop.Framework;
|
||||
|
||||
namespace Props.Services.Modules
|
||||
{
|
||||
public interface IShopManager
|
||||
{
|
||||
public IEnumerable<string> AvailableShops();
|
||||
public Task<IList<ProductListing>> Search(string query, SearchOutline searchOutline);
|
||||
}
|
||||
}
|
107
Props/Services/Modules/LocalShopManager.cs
Normal file
107
Props/Services/Modules/LocalShopManager.cs
Normal file
@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Props.Models.Search;
|
||||
using Props.Options;
|
||||
using Props.Shop.Framework;
|
||||
|
||||
namespace Props.Services.Modules
|
||||
{
|
||||
public class LocalShopManager : IShopManager
|
||||
{
|
||||
private ILogger<LocalShopManager> logger;
|
||||
private Dictionary<string, IShop> shops;
|
||||
private ModulesOptions options;
|
||||
private IConfiguration configuration;
|
||||
public LocalShopManager(IConfiguration configuration, ILogger<LocalShopManager> logger)
|
||||
{
|
||||
this.configuration = configuration;
|
||||
this.logger = logger;
|
||||
options = configuration.GetSection(ModulesOptions.Modules).Get<ModulesOptions>();
|
||||
|
||||
shops = new Dictionary<string, IShop>();
|
||||
foreach (IShop shop in LoadShops(options.ShopsDir, options.ShopRegex, options.RecursiveLoad))
|
||||
{
|
||||
if (!shops.TryAdd(shop.ShopName, shop))
|
||||
{
|
||||
logger.LogWarning("Duplicate shop {0} detected. Ignoring the latter.", shop.ShopName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> AvailableShops()
|
||||
{
|
||||
return shops.Keys;
|
||||
}
|
||||
|
||||
public async Task<IList<ProductListing>> Search(string query, SearchOutline searchOutline)
|
||||
{
|
||||
List<ProductListing> results = new List<ProductListing>();
|
||||
foreach (string shopName in shops.Keys)
|
||||
{
|
||||
if (!searchOutline.Disabled[shopName])
|
||||
{
|
||||
int amount = 0;
|
||||
await foreach (ProductListing product in shops[shopName].Search(query, searchOutline.Filters))
|
||||
{
|
||||
if (searchOutline.Filters.Validate(product))
|
||||
{
|
||||
amount += 1;
|
||||
results.Add(product);
|
||||
}
|
||||
if (amount >= options.MaxResults) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private IEnumerable<IShop> LoadShops(string shopsDir, string shopRegex, bool recursiveLoad)
|
||||
{
|
||||
Stack<string> directories = new Stack<string>();
|
||||
directories.Push(shopsDir);
|
||||
string currentDirectory = null;
|
||||
while (directories.TryPop(out currentDirectory))
|
||||
{
|
||||
if (recursiveLoad)
|
||||
{
|
||||
foreach (string dir in Directory.EnumerateDirectories(currentDirectory))
|
||||
{
|
||||
directories.Push(dir);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (string file in Directory.EnumerateFiles(currentDirectory))
|
||||
{
|
||||
if (Path.GetExtension(file).Equals(".dll") && Regex.IsMatch(file, shopRegex))
|
||||
{
|
||||
ShopAssemblyLoadContext context = new ShopAssemblyLoadContext(file);
|
||||
Assembly assembly = context.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(file)));
|
||||
int success = 0;
|
||||
foreach (Type type in assembly.GetTypes())
|
||||
{
|
||||
if (typeof(IShop).IsAssignableFrom(type))
|
||||
{
|
||||
IShop shop = Activator.CreateInstance(type) as IShop;
|
||||
if (shop != null)
|
||||
{
|
||||
success += 1;
|
||||
yield return shop;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (success == 0)
|
||||
{
|
||||
logger.LogWarning("There were no shops found within the assembly at path \"{0}\".", file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using Microsoft.Extensions.DependencyModel;
|
||||
using Props.Shop.Framework;
|
||||
|
||||
namespace Props.Services.Modules
|
||||
{
|
||||
@ -16,6 +17,7 @@ namespace Props.Services.Modules
|
||||
|
||||
protected override Assembly Load(AssemblyName assemblyName)
|
||||
{
|
||||
if (assemblyName.FullName.Equals(typeof(IShop).Assembly.FullName)) return null;
|
||||
string assemblyPath = resolver.ResolveAssemblyToPath(assemblyName);
|
||||
return assemblyPath != null ? LoadFromAssemblyPath(assemblyPath) : null;
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ using Microsoft.Extensions.Hosting;
|
||||
using Props.Data;
|
||||
using Props.Models.User;
|
||||
using Props.Services.Content;
|
||||
using Props.Services.Modules;
|
||||
|
||||
namespace Props
|
||||
{
|
||||
@ -53,7 +54,8 @@ namespace Props
|
||||
.AddEntityFrameworkStores<ApplicationDbContext>();
|
||||
services.AddRazorPages();
|
||||
|
||||
services.AddSingleton(typeof(IContentManager<>), typeof(ContentManager<>));
|
||||
services.AddSingleton(typeof(IContentManager<>), typeof(CachedContentManager<>));
|
||||
services.AddSingleton<IShopManager, LocalShopManager>();
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
@ -75,6 +77,7 @@ namespace Props
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
endpoints.MapRazorPages();
|
||||
});
|
||||
}
|
||||
|
@ -9,8 +9,11 @@
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
"Shops": {
|
||||
"Path": "./shops"
|
||||
"Modules": {
|
||||
"ShopsDir": "./shops",
|
||||
"RecursiveLoad": "false",
|
||||
"MaxResults": "100",
|
||||
"ShopRegex": "Props\\.Shop\\.."
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
232
Props/assets/images/logo-simplified.svg
Normal file
232
Props/assets/images/logo-simplified.svg
Normal file
@ -0,0 +1,232 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="130.02815mm"
|
||||
height="183.73235mm"
|
||||
viewBox="0 0 130.02815 183.73235"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
sodipodi:docname="logo-simplified.svg"
|
||||
inkscape:export-filename="C:\Users\yunya\Documents\Props\Props\client\src\assets\images\logo.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:snap-global="true"
|
||||
inkscape:zoom="0.4734482"
|
||||
inkscape:cx="11.616899"
|
||||
inkscape:cy="186.92647"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1027"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-40.717182,-65.583996)">
|
||||
<rect
|
||||
style="fill:#51b6bf;fill-opacity:1;stroke:none;stroke-width:0.394384"
|
||||
id="rect846"
|
||||
width="130.02815"
|
||||
height="180.11604"
|
||||
x="40.717182"
|
||||
y="69.200294"
|
||||
ry="7.2278728" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.355205"
|
||||
id="rect6139"
|
||||
width="114.73653"
|
||||
height="165.58026"
|
||||
x="48.362995"
|
||||
y="76.468185"
|
||||
ry="6.6445661" />
|
||||
<g
|
||||
id="g20855"
|
||||
transform="translate(0,-4.5861139)">
|
||||
<rect
|
||||
style="fill:none;fill-opacity:1;stroke:#3b3485;stroke-width:1.465;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect4822-1"
|
||||
width="8.1061144"
|
||||
height="8.1061144"
|
||||
x="56.92775"
|
||||
y="113.97274"
|
||||
ry="1.3718038" />
|
||||
<rect
|
||||
style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:1.86051;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5136-7"
|
||||
width="42.169209"
|
||||
height="3.4690557"
|
||||
x="69.150803"
|
||||
y="116.29127"
|
||||
ry="1.7345278" />
|
||||
</g>
|
||||
<g
|
||||
id="g20859"
|
||||
transform="translate(0,-7.0555611)">
|
||||
<rect
|
||||
style="fill:none;fill-opacity:1;stroke:#3b3485;stroke-width:1.465;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect4822-14"
|
||||
width="8.1061144"
|
||||
height="8.1061144"
|
||||
x="56.92775"
|
||||
y="127.76641"
|
||||
ry="1.3718038" />
|
||||
<rect
|
||||
style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:1.43107;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5136-7-2"
|
||||
width="21.986813"
|
||||
height="3.9363635"
|
||||
x="68.953468"
|
||||
y="129.85129"
|
||||
ry="1.9681817" />
|
||||
</g>
|
||||
<g
|
||||
id="g20845"
|
||||
transform="translate(0,-2.1166667)">
|
||||
<rect
|
||||
style="fill:none;fill-opacity:1;stroke:#3b3485;stroke-width:1.465;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect4822"
|
||||
width="8.1061144"
|
||||
height="8.1061144"
|
||||
x="56.92775"
|
||||
y="100.17907"
|
||||
ry="1.3718038" />
|
||||
<rect
|
||||
style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:1.59414;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5136"
|
||||
width="28.571259"
|
||||
height="3.7589138"
|
||||
x="69.028404"
|
||||
y="102.35267"
|
||||
ry="1.8794569" />
|
||||
<rect
|
||||
style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:1.50408;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5136-5"
|
||||
width="24.788029"
|
||||
height="3.8569105"
|
||||
x="102.89133"
|
||||
y="102.30367"
|
||||
ry="1.9284552" />
|
||||
</g>
|
||||
<g
|
||||
id="g20863"
|
||||
transform="translate(0,-9.5250005)">
|
||||
<rect
|
||||
style="fill:none;fill-opacity:1;stroke:#3b3485;stroke-width:1.465;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect4822-1-4"
|
||||
width="8.1061144"
|
||||
height="8.1061144"
|
||||
x="56.92775"
|
||||
y="141.56007"
|
||||
ry="1.3718038" />
|
||||
<rect
|
||||
style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:1.57172;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5728"
|
||||
width="27.594185"
|
||||
height="3.7833092"
|
||||
x="69.018089"
|
||||
y="143.72148"
|
||||
ry="1.8916546" />
|
||||
</g>
|
||||
<rect
|
||||
style="fill:#202042;fill-opacity:1;stroke:none;stroke-width:0.23824"
|
||||
id="rect1392"
|
||||
width="53.152565"
|
||||
height="18.233921"
|
||||
x="-132.30754"
|
||||
y="-83.817917"
|
||||
ry="3.4788733"
|
||||
transform="scale(-1)" />
|
||||
<rect
|
||||
style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:2.22336;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6412"
|
||||
width="73.657707"
|
||||
height="2.8362327"
|
||||
x="56.336582"
|
||||
y="201.33577"
|
||||
ry="1.4181163" />
|
||||
<rect
|
||||
style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:2.23876;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6414"
|
||||
width="69.712616"
|
||||
height="3.0383809"
|
||||
x="56.344284"
|
||||
y="209.24466"
|
||||
ry="1.5191904" />
|
||||
<rect
|
||||
style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:2.42357;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6416"
|
||||
width="86.988022"
|
||||
height="2.8535743"
|
||||
x="56.436687"
|
||||
y="217.23825"
|
||||
ry="1.4267871" />
|
||||
<rect
|
||||
style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:2.38882;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6418"
|
||||
width="83.495445"
|
||||
height="2.8883159"
|
||||
x="56.419315"
|
||||
y="225.12207"
|
||||
ry="1.444158" />
|
||||
<rect
|
||||
style="fill:#2f3898;fill-opacity:1;stroke:none;stroke-width:3.0459;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6295-8"
|
||||
width="57.490276"
|
||||
height="6.8199105"
|
||||
x="76.986122"
|
||||
y="87.608482"
|
||||
ry="3.4099553" />
|
||||
<rect
|
||||
style="fill:#2f3898;fill-opacity:1;stroke:none;stroke-width:2.58155;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6490"
|
||||
width="41.297459"
|
||||
height="6.8199105"
|
||||
x="85.082527"
|
||||
y="188.09093"
|
||||
ry="3.4099553" />
|
||||
<rect
|
||||
style="fill:#2f3898;fill-opacity:1;stroke:none;stroke-width:1.87563;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6490-3"
|
||||
width="21.8001"
|
||||
height="6.8199105"
|
||||
x="94.831207"
|
||||
y="151.63762"
|
||||
ry="3.4099553" />
|
||||
<rect
|
||||
style="opacity:1;fill:#2bc8d7;fill-opacity:1;stroke:none;stroke-width:1.63579;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6492"
|
||||
width="16.931116"
|
||||
height="13.580167"
|
||||
x="132.27409"
|
||||
y="201.23309"
|
||||
ry="2.6454868" />
|
||||
<rect
|
||||
style="opacity:1;fill:#dcf8f6;fill-opacity:1;stroke:none;stroke-width:1.465;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6932"
|
||||
width="79.717339"
|
||||
height="19.929335"
|
||||
x="65.872589"
|
||||
y="161.02196"
|
||||
ry="5.6437054" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 7.6 KiB |
@ -1,2 +1,2 @@
|
||||
import "../../node_modules/bootstrap/js/dist/collapse";
|
||||
import "~/node_modules/bootstrap/js/dist/collapse";
|
||||
import "simplebar";
|
||||
|
@ -1 +1,115 @@
|
||||
console.log("abc");
|
||||
import { apiHttp } from "~/assets/js/services/http.js";
|
||||
|
||||
// All input fields.
|
||||
let inputs = {
|
||||
maxPriceEnabled: document.getElementById("max-price-enabled"),
|
||||
maxShippingEnabled: document.getElementById("max-shipping-enabled"),
|
||||
minRating: document.getElementById("min-rating"),
|
||||
maxPrice: document.getElementById("max-price"),
|
||||
maxShipping: document.getElementById("max-shipping"),
|
||||
keepUnknownPurchases: document.getElementById("keep-unknown-purchases"),
|
||||
keepUnknownReviews: document.getElementById("keep-unknown-reviews"),
|
||||
keepUnknownShipping: document.getElementById("keep-unknown-shipping"),
|
||||
keepUnrated: document.getElementById("keep-unrated"),
|
||||
minPrice: document.getElementById("min-price"),
|
||||
minPurchases: document.getElementById("min-purchases"),
|
||||
minReviews: document.getElementById("min-reviews"),
|
||||
shopToggles: {}
|
||||
};
|
||||
|
||||
async function main() {
|
||||
setupInteractiveBehavior();
|
||||
await setupInitialValues((await apiHttp.get("/Search/Default/Outline/Filters")).data);
|
||||
await setupShopToggles((await apiHttp.get("/Search/Shops/Available")).data);
|
||||
|
||||
document.getElementById("configuration").querySelector(".invisible").classList.remove("invisible"); // Load completed, show the UI.
|
||||
}
|
||||
|
||||
function setupInteractiveBehavior() {
|
||||
document.getElementById("config-check-toggle").addEventListener("change", function () {
|
||||
let configElem = document.getElementById("configuration");
|
||||
if (this.checked) {
|
||||
configElem.classList.remove("d-none");
|
||||
} else {
|
||||
configElem.classList.add("d-none");
|
||||
}
|
||||
});
|
||||
|
||||
inputs.maxPriceEnabled.addEventListener("change", function () {
|
||||
inputs.maxPrice.disabled = !this.checked;
|
||||
});
|
||||
|
||||
inputs.maxShippingEnabled.addEventListener("change", function () {
|
||||
inputs.maxShipping.disabled = !this.checked;
|
||||
});
|
||||
|
||||
inputs.minRating.addEventListener("input", function () {
|
||||
document.getElementById("min-rating-display").innerHTML = `Minimum rating: ${this.value}%`;
|
||||
});
|
||||
}
|
||||
|
||||
async function setupInitialValues(filters) {
|
||||
inputs.maxShippingEnabled.checked = filters.enableMaxShippingFee;
|
||||
inputs.maxShippingEnabled.dispatchEvent(new Event("change"));
|
||||
|
||||
inputs.maxPriceEnabled.checked = filters.enableUpperPrice;
|
||||
inputs.maxPriceEnabled.dispatchEvent(new Event("change"));
|
||||
|
||||
inputs.keepUnknownPurchases.checked = filters.keepUnknownPurchaseCount;
|
||||
inputs.keepUnknownPurchases.dispatchEvent(new Event("change"));
|
||||
|
||||
inputs.keepUnknownReviews.checked = filters.keepUnknownReviewCount;
|
||||
inputs.keepUnknownReviews.dispatchEvent(new Event("change"));
|
||||
|
||||
inputs.keepUnknownShipping.checked = filters.keepUnknownShipping;
|
||||
inputs.keepUnknownShipping.dispatchEvent(new Event("change"));
|
||||
|
||||
inputs.keepUnrated.checked = filters.keepUnrated;
|
||||
inputs.keepUnrated.dispatchEvent(new Event("change"));
|
||||
|
||||
inputs.minPrice.value = filters.lowerPrice;
|
||||
inputs.minPrice.dispatchEvent(new Event("change"));
|
||||
|
||||
inputs.maxShipping.value = filters.maxShippingFee;
|
||||
inputs.maxShipping.dispatchEvent(new Event("change"));
|
||||
|
||||
inputs.minPurchases.value = filters.minPurchases;
|
||||
inputs.minPurchases.dispatchEvent(new Event("change"));
|
||||
|
||||
inputs.minRating.value = filters.minRating * 100;
|
||||
inputs.minRating.dispatchEvent(new Event("input"));
|
||||
|
||||
inputs.minReviews.value = filters.minReviews;
|
||||
inputs.minReviews.dispatchEvent(new Event("change"));
|
||||
|
||||
inputs.maxPrice.value = filters.upperPrice;
|
||||
inputs.maxPrice.dispatchEvent(new Event("change"));
|
||||
}
|
||||
|
||||
async function setupShopToggles(availableShops) {
|
||||
let disabledShops = (await apiHttp.get("/Search/Default/Outline/DisabledShops")).data;
|
||||
let shopsElem = document.getElementById("shop-checkboxes");
|
||||
availableShops.forEach(shopName => {
|
||||
let id = `${shopName}-enabled`;
|
||||
let shopLabelElem = document.createElement("label");
|
||||
shopLabelElem.classList.add("form-check-label");
|
||||
shopLabelElem.htmlFor = id;
|
||||
shopLabelElem.innerHTML = `Enable ${shopName}`;
|
||||
|
||||
let shopCheckboxElem = document.createElement("input");
|
||||
shopCheckboxElem.classList.add("form-check-input");
|
||||
shopCheckboxElem.type = "checkbox";
|
||||
shopCheckboxElem.id = id;
|
||||
shopCheckboxElem.checked = !disabledShops.includes(shopName);
|
||||
inputs.shopToggles[shopName] = shopCheckboxElem;
|
||||
|
||||
let shopToggleElem = document.createElement("div");
|
||||
shopToggleElem.classList.add("form-check");
|
||||
shopToggleElem.appendChild(shopCheckboxElem);
|
||||
shopToggleElem.appendChild(shopLabelElem);
|
||||
shopsElem.appendChild(shopToggleElem);
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
// TODO: Animate configuration toggle.
|
||||
|
@ -1,3 +1,12 @@
|
||||
$themes: (
|
||||
"light": ("background": #f4f4f4, "navbar": #FFF8F8, "main": #BDF2D5, "footer": #F2F2F2,"sub": #F4FCFC, "bold": #647b9b, "text": #1A1A1A, "muted": #797a7e),
|
||||
"light": (
|
||||
"background": #f4f4f4,
|
||||
"navbar": #FFF8F8,
|
||||
"main": #BDF2D5,
|
||||
"footer": #F2F2F2,
|
||||
"sub": #F4FCFC,
|
||||
"bold": #647b9b,
|
||||
"text": #1A1A1A,
|
||||
"muted": #797a7e,
|
||||
),
|
||||
);
|
||||
|
@ -1,7 +1,6 @@
|
||||
@use "themer";
|
||||
@use "~/node_modules/bootstrap/scss/bootstrap";
|
||||
@import "~/node_modules/bootstrap-icons/font/bootstrap-icons.css";
|
||||
@import "~/node_modules/simplebar/dist/simplebar.min.css";
|
||||
@use "sass:color";
|
||||
|
||||
header > nav {
|
||||
@extend .navbar-expand-lg;
|
||||
@ -33,6 +32,19 @@ main {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.tear {
|
||||
@extend .p-3;
|
||||
border-top: 1px;
|
||||
border-top-style: solid;
|
||||
border-bottom: 1px;
|
||||
border-bottom-style: solid;
|
||||
@include themer.themed {
|
||||
$tear: themer.color-of("background");
|
||||
border-color: adjust-color($color: $tear, $lightness: -20%, $alpha: 1.0);
|
||||
background-color: adjust-color($color: $tear, $lightness: -5%, $alpha: 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
@ -65,6 +77,16 @@ footer {
|
||||
background-color: themer.color-of("sub");
|
||||
}
|
||||
}
|
||||
|
||||
&.sole {
|
||||
@include themer.themed {
|
||||
background-color: themer.color-of("main");
|
||||
border-color: themer.color-of("sub");
|
||||
border-top-style: solid;
|
||||
border-bottom-style: solid;
|
||||
border-width: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.concise {
|
||||
@ -112,4 +134,7 @@ body {
|
||||
background-color: themer.color-of("background");
|
||||
color: themer.color-of("text");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@import "~/node_modules/bootstrap-icons/font/bootstrap-icons.css";
|
||||
@import "~/node_modules/simplebar/dist/simplebar.min.css";
|
||||
|
11
Props/babel.config.js
Normal file
11
Props/babel.config.js
Normal file
@ -0,0 +1,11 @@
|
||||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
|
||||
const presets = [];
|
||||
const plugins = [];
|
||||
|
||||
return {
|
||||
presets,
|
||||
plugins
|
||||
};
|
||||
};
|
54
Props/package-lock.json
generated
54
Props/package-lock.json
generated
@ -63,6 +63,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/eslint-parser": {
|
||||
"version": "7.14.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.14.7.tgz",
|
||||
"integrity": "sha512-6WPwZqO5priAGIwV6msJcdc9TsEPzYeYdS/Xuoap+/ihkgN6dzHp2bcAAwyWZ5bLzk0vvjDmKvRwkqNaiJ8BiQ==",
|
||||
"requires": {
|
||||
"eslint-scope": "^5.1.1",
|
||||
"eslint-visitor-keys": "^2.1.0",
|
||||
"semver": "^6.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.14.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.8.tgz",
|
||||
@ -950,6 +967,28 @@
|
||||
"@babel/helper-plugin-utils": "^7.14.5"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-runtime": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.14.5.tgz",
|
||||
"integrity": "sha512-fPMBhh1AV8ZyneiCIA+wYYUH1arzlXR1UMcApjvchDhfKxhy2r2lReJv8uHEyihi4IFIGlr1Pdx7S5fkESDQsg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-module-imports": "^7.14.5",
|
||||
"@babel/helper-plugin-utils": "^7.14.5",
|
||||
"babel-plugin-polyfill-corejs2": "^0.2.2",
|
||||
"babel-plugin-polyfill-corejs3": "^0.2.2",
|
||||
"babel-plugin-polyfill-regenerator": "^0.2.2",
|
||||
"semver": "^6.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-shorthand-properties": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.14.5.tgz",
|
||||
@ -1121,7 +1160,6 @@
|
||||
"version": "7.14.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.8.tgz",
|
||||
"integrity": "sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
@ -2136,7 +2174,6 @@
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esrecurse": "^4.3.0",
|
||||
"estraverse": "^4.1.1"
|
||||
@ -2162,8 +2199,7 @@
|
||||
"eslint-visitor-keys": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
|
||||
"integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw=="
|
||||
},
|
||||
"espree": {
|
||||
"version": "7.3.1",
|
||||
@ -2217,7 +2253,6 @@
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
|
||||
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"estraverse": "^5.2.0"
|
||||
},
|
||||
@ -2225,16 +2260,14 @@
|
||||
"estraverse": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
|
||||
"integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"estraverse": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
|
||||
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="
|
||||
},
|
||||
"esutils": {
|
||||
"version": "2.0.3",
|
||||
@ -3227,8 +3260,7 @@
|
||||
"regenerator-runtime": {
|
||||
"version": "0.13.7",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
|
||||
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
|
||||
"dev": true
|
||||
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
|
||||
},
|
||||
"regenerator-transform": {
|
||||
"version": "0.14.5",
|
||||
|
@ -9,6 +9,7 @@
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.8",
|
||||
"@babel/plugin-transform-runtime": "^7.14.5",
|
||||
"@babel/preset-env": "^7.14.8",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
@ -23,6 +24,8 @@
|
||||
"webpack-cli": "^4.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/eslint-parser": "^7.14.7",
|
||||
"@babel/runtime": "^7.14.8",
|
||||
"axios": "^0.21.1",
|
||||
"bootstrap": "^5.0.2",
|
||||
"bootstrap-icons": "^1.5.0",
|
||||
|
BIN
Props/shops/FuzzySharp.dll
Normal file
BIN
Props/shops/FuzzySharp.dll
Normal file
Binary file not shown.
BIN
Props/shops/Newtonsoft.Json.dll
Normal file
BIN
Props/shops/Newtonsoft.Json.dll
Normal file
Binary file not shown.
BIN
Props/shops/Props.Shop.Adafruit.dll
Normal file
BIN
Props/shops/Props.Shop.Adafruit.dll
Normal file
Binary file not shown.
@ -31,7 +31,8 @@ let config = {
|
||||
use: {
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
presets: ["@babel/preset-env"]
|
||||
presets: ["@babel/preset-env"],
|
||||
plugins: ["@babel/plugin-transform-runtime"]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user