More joining
Former-commit-id: 690f0bf7b00ecfb89043bbcea77616e8d9913bc4
This commit is contained in:
@@ -1,43 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>LegoLog: About</title>
|
||||
<meta name="viewport">
|
||||
<link rel="icon" type="image/svg+xml" href="/res/favicon.svg">
|
||||
<link rel="stylesheet" type="text/css" href="../global.css">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Londrina+Solid&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Josefin+Sans&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Auth0 - a library for authentication -->
|
||||
<script src="https://cdn.auth0.com/js/auth0-spa-js/1.13/auth0-spa-js.production.js"></script>
|
||||
|
||||
<!-- Flickity - a library to make carousells easier to implement -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/flickity@2/dist/flickity.min.css">
|
||||
<script src="https://unpkg.com/flickity@2/dist/flickity.pkgd.min.js"></script>
|
||||
|
||||
<!-- Components used on this page - they must be included to work -->
|
||||
<script type="module" src="/components/components.mjs"></script>
|
||||
<script type="module" src="/components/navbar.mjs"></script>
|
||||
<script type="module" src="/components/search.mjs"></script>
|
||||
<script type="module" src="/components/basket.mjs"></script>
|
||||
<script type="module" src="/components/basket-popout.mjs"></script>
|
||||
<script type="module" src="/components/notificationbar.mjs"></script>
|
||||
<script type="module" src="/components/storefront.mjs"></script>
|
||||
<script type="module" src="/components/tag.mjs"></script>
|
||||
<script type="module" src="/components/product-list.mjs"></script>
|
||||
<script type="module" src="/components/super-compact-listing.mjs"></script>
|
||||
<script type="module" src="/components/compact-listing.mjs"></script>
|
||||
<script type="module" src="/components/product-listing.mjs"></script>
|
||||
|
||||
<script type="module" src="index.mjs"></script>
|
||||
</head>
|
||||
<body>
|
||||
<notificationbar-component></notificationbar-component>
|
||||
<navbar-component></navbar-component>
|
||||
<limited-margin>
|
||||
|
||||
</limited-margin>
|
||||
</body>
|
||||
@@ -139,7 +139,8 @@ class BasketPopout extends Component {
|
||||
<super-compact-listing-component class="sc-listing"
|
||||
id="${key.split('~')[0]}"
|
||||
type="${item.type}"
|
||||
quantity="${item.quantity}">
|
||||
quantity="${item.quantity}"
|
||||
modifier="${key.split('~')[1] || ''}">
|
||||
</super-compact-listing-component>
|
||||
</div>
|
||||
`;
|
||||
@@ -224,6 +225,7 @@ class BasketPopout extends Component {
|
||||
}
|
||||
|
||||
.popup-content-item {
|
||||
background-color: #F5F6F6;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
@@ -237,6 +239,7 @@ class BasketPopout extends Component {
|
||||
}
|
||||
|
||||
.sc-listing {
|
||||
flex-basis: 100%;
|
||||
flex-grow: 3;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,17 @@ class CompactProductListing extends Component {
|
||||
super(CompactProductListing);
|
||||
}
|
||||
|
||||
OnMount() {
|
||||
console.log(this.state);
|
||||
if (this.state.tags) {
|
||||
const tags = JSON.parse(this.state.tags);
|
||||
this.setState({
|
||||
...this.getState,
|
||||
tags,
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
|
||||
Render() {
|
||||
return {
|
||||
template: /* html */`
|
||||
@@ -20,6 +31,9 @@ class CompactProductListing extends Component {
|
||||
<div class="product-listing-info">
|
||||
<div class="product-listing-name">{this.state.name} {this.state.id}</div>
|
||||
</a>
|
||||
${this.state.tags
|
||||
? this.state.tags.map(tag => `<tag-component name="${tag}"></tag-component>`).join('')
|
||||
: ''}
|
||||
${this.state.discount
|
||||
? `<span class="product-listing-price-full">£${parseFloat(this.state.price).toFixed(2)}</span><span class="product-listing-price-new">£${parseFloat(this.state.discount).toFixed(2)}</span>`
|
||||
: `<span class="product-listing-price">£${parseFloat(this.state.price).toFixed(2)}</span>`}
|
||||
|
||||
@@ -38,9 +38,12 @@ class ProductList extends Component {
|
||||
this.keepLoading = true;
|
||||
this.loadingBar = `
|
||||
<!--Infinite Loading-->
|
||||
<div class="product-list-loader-container-container">
|
||||
<div class="product-list-loader-container">
|
||||
<div class="product-list-loader">
|
||||
<!-- https://loading.io/css/ -->
|
||||
<div class="lds-ellipsis"><div></div><div></div><div></div><div></div></div>
|
||||
<img src="/res/loading.gif" height="100" alt="Loading...">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -55,6 +58,7 @@ class ProductList extends Component {
|
||||
listing="${product.listing}"
|
||||
price="${product.price}"
|
||||
type="${product.type}"
|
||||
tags="${JSON.stringify(product.tags).replace(/"/g, '"')}"
|
||||
discount="${product.discount || ''}"></compact-listing-component>
|
||||
`;
|
||||
}).join('')}
|
||||
@@ -86,66 +90,49 @@ class ProductList extends Component {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.product-list-loader {
|
||||
.product-list-loader-container-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.lds-ellipsis {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
z-index: 0;
|
||||
@keyframes grow-shrink {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
.lds-ellipsis div {
|
||||
position: absolute;
|
||||
top: 33px;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
50% {
|
||||
transform: scale(1.4);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shrink-grow {
|
||||
0% {
|
||||
transform: scale(1.4);
|
||||
}
|
||||
50% {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1.4);
|
||||
}
|
||||
}
|
||||
|
||||
.product-list-loader-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100px;
|
||||
border-radius: 50%;
|
||||
background: #7F5CFF;
|
||||
animation-timing-function: cubic-bezier(0, 1, 1, 0);
|
||||
}
|
||||
.lds-ellipsis div:nth-child(1) {
|
||||
left: 8px;
|
||||
animation: lds-ellipsis1 0.6s infinite;
|
||||
}
|
||||
.lds-ellipsis div:nth-child(2) {
|
||||
left: 8px;
|
||||
animation: lds-ellipsis2 0.6s infinite;
|
||||
}
|
||||
.lds-ellipsis div:nth-child(3) {
|
||||
left: 32px;
|
||||
animation: lds-ellipsis2 0.6s infinite;
|
||||
}
|
||||
.lds-ellipsis div:nth-child(4) {
|
||||
left: 56px;
|
||||
animation: lds-ellipsis3 0.6s infinite;
|
||||
}
|
||||
@keyframes lds-ellipsis1 {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes lds-ellipsis3 {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0);
|
||||
}
|
||||
}
|
||||
@keyframes lds-ellipsis2 {
|
||||
0% {
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
100% {
|
||||
transform: translate(24px, 0);
|
||||
background-color: #D7C2FF;
|
||||
/* grow and shrink sine wave */
|
||||
animation-timing-function: ease-in-out;
|
||||
animation: grow-shrink 1s infinite;
|
||||
}
|
||||
|
||||
.product-list-loader {
|
||||
animation-timing-function: ease-in-out;
|
||||
animation: shrink-grow 1s infinite;
|
||||
}
|
||||
|
||||
@media (pointer:none), (pointer:coarse), screen and (max-width: 900px) {
|
||||
|
||||
@@ -103,7 +103,7 @@ class Search extends Component {
|
||||
const res = /* html */`
|
||||
<super-compact-listing-component class="sc-listing" id="${result.id}"
|
||||
name="${result.name}"
|
||||
tag="${result.tag}"
|
||||
tags="${JSON.stringify(result.tags).replace(/"/g, '"')}"
|
||||
type="${result.type}"
|
||||
price="${result.discount || result.price}">
|
||||
</super-compact-listing-component>
|
||||
|
||||
@@ -19,10 +19,10 @@ class StoreFront extends Component {
|
||||
</div>
|
||||
</div>
|
||||
<div class="carousel-cell">
|
||||
<img class="carousel-image" src="res/warehouse.png" alt="">
|
||||
<img class="carousel-image" src="res/technic.png" alt="">
|
||||
<div class="carousel-caption">
|
||||
<h1>Our state of the art warehouse ensures your speedy delivery</h1>
|
||||
<a href="/about/"><button>Find Out More</button></a>
|
||||
<h1>Check out our LEGO® Technic range</h1>
|
||||
<a href="/search/?q=technic"><button>Show Technic Now</button></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="carousel-cell">
|
||||
|
||||
@@ -13,18 +13,39 @@ class SuperCompactProductListing extends Component {
|
||||
if (!this.state.name || !this.state.price) {
|
||||
const product = (await fetch(`/api/${this.state.type}/${this.state.id}`).then(res => res.json())).data;
|
||||
const name = product.name;
|
||||
const price = (product.discount || product.price) * this.state.quantity || 1;
|
||||
const price = product.discount || product.price;
|
||||
const tag = product.tag;
|
||||
const tags = product.tags;
|
||||
const colours = product.colours;
|
||||
|
||||
this.setState({
|
||||
...this.getState,
|
||||
name,
|
||||
price,
|
||||
tag,
|
||||
tags,
|
||||
colours,
|
||||
quantity: product.quantity,
|
||||
}, false);
|
||||
} else if (this.state.tags) {
|
||||
const tags = JSON.parse(this.state.tags);
|
||||
this.setState({
|
||||
...this.getState,
|
||||
tags,
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
|
||||
Render() {
|
||||
let modifierPreview = '';
|
||||
if (this.state.modifier) {
|
||||
if (this.state.modifier !== '0') {
|
||||
modifierPreview = /* html */`
|
||||
<span class="brick-colour-demonstrator" style="background-color: #${this.state.colours[this.state.modifier].hexrgb}"></span>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
template: /* html */`
|
||||
<span class="product-listing">
|
||||
@@ -34,10 +55,15 @@ class SuperCompactProductListing extends Component {
|
||||
alt="Image of {this.state.name}"
|
||||
src="/api/cdn/${this.state.id}-thumb.png">
|
||||
</span>
|
||||
${modifierPreview}
|
||||
<span class="product-listing-info">
|
||||
<span class="product-listing-name">{this.state.name}</span>
|
||||
<div class="product-listing-modifier">${this.state.modifier || ''}</div>
|
||||
<tag-component name="{this.state.tag}"></tag-component>
|
||||
<div class="product-listing-modifier">${this.state.modifier ? `Colour: ${this.state.colours[this.state.modifier].name}` : ''}</div>
|
||||
<span class="product-listing-tags">
|
||||
${this.state.tags
|
||||
? this.state.tags.map(tag => `<tag-component name="${tag}"></tag-component>`).join('')
|
||||
: `<tag-component name="${this.state.tag}"></tag-component>`}
|
||||
</span>
|
||||
</span>
|
||||
<span class="product-pricing">
|
||||
£${parseFloat(this.state.price).toFixed(2)}
|
||||
@@ -69,6 +95,18 @@ class SuperCompactProductListing extends Component {
|
||||
flex-grow: 1
|
||||
}
|
||||
|
||||
.brick-colour-demonstrator {
|
||||
position: absolute;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 7px;
|
||||
align-self: flex-start;
|
||||
left: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-right: 0.5em;
|
||||
border: #1A1A1A solid 1px;
|
||||
}
|
||||
|
||||
.product-image {
|
||||
object-fit: scale-down;
|
||||
object-position: center;
|
||||
|
||||
BIN
client/public/res/technic.png
Normal file
BIN
client/public/res/technic.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 568 KiB |
2
src/controllers/bigram.js
Normal file
2
src/controllers/bigram.js
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
|
||||
@@ -94,6 +94,7 @@ async function GetBulkBricks(bricksArr) {
|
||||
|
||||
async function GetBrick(brickId) {
|
||||
await Database.Query('BEGIN TRANSACTION;');
|
||||
|
||||
const dbres = await Database.Query(`
|
||||
SELECT lego_brick.id, lego_brick.name, tag.name AS "tag",
|
||||
inv.price, inv.new_price AS "discount", inv.stock,
|
||||
|
||||
@@ -55,7 +55,7 @@ function LevenshteinDistance(s, t) {
|
||||
|
||||
function SanatiseQuery(query) {
|
||||
query = query.trim();
|
||||
query = query.replace(/[^a-zA-Z0-9\s]/g, '');
|
||||
query = query.replace(/[^a-zA-Z0-9,/\s]/g, '');
|
||||
query = escape(query);
|
||||
query = query.toLowerCase();
|
||||
return query;
|
||||
|
||||
@@ -22,7 +22,7 @@ async function Search(fuzzyString) {
|
||||
}
|
||||
|
||||
// order by levenshtine distance
|
||||
const sets = dbres.rows;
|
||||
let sets = dbres.rows;
|
||||
sets.sort((a, b) => {
|
||||
const aName = a.name.toLowerCase();
|
||||
const bName = b.name.toLowerCase();
|
||||
@@ -54,9 +54,19 @@ async function Search(fuzzyString) {
|
||||
// combine tags into a single array
|
||||
for (const set of sets) {
|
||||
set.type = 'set';
|
||||
set.tags = set.tag.split(',');
|
||||
set.tags = [];
|
||||
}
|
||||
|
||||
// combine (joined) rows into a single array
|
||||
sets = sets.reduce((arr, current) => {
|
||||
if (!arr.some(item => item.id === current.id)) {
|
||||
arr.push(current);
|
||||
}
|
||||
|
||||
arr.find(item => item.id === current.id).tags.push(current.tag);
|
||||
return arr;
|
||||
}, []);
|
||||
|
||||
return sets;
|
||||
}
|
||||
|
||||
@@ -109,9 +119,11 @@ async function GetSets(page, resPerPage) {
|
||||
const total = parseInt(countRes.rows[0].count);
|
||||
const dbres = await Database.Query(`
|
||||
SELECT
|
||||
id, name, price, new_price AS "discount"
|
||||
lego_set.id, lego_set.name, price, new_price AS "discount", tag.name AS "tag"
|
||||
FROM lego_set
|
||||
LEFT JOIN lego_set_inventory as inv ON lego_set.id = inv.set_id
|
||||
LEFT JOIN lego_set_tag AS tags ON tags.set_id = lego_set.id
|
||||
LEFT JOIN tag AS tag ON tags.tag = tag.id
|
||||
ORDER BY id ASC
|
||||
LIMIT $1
|
||||
OFFSET $2;`,
|
||||
@@ -126,12 +138,23 @@ async function GetSets(page, resPerPage) {
|
||||
};
|
||||
}
|
||||
|
||||
const sets = dbres.rows;
|
||||
let sets = dbres.rows;
|
||||
|
||||
for (const set of sets) {
|
||||
set.type = 'set';
|
||||
set.tags = [];
|
||||
}
|
||||
|
||||
// combine (joined) rows into a single array
|
||||
sets = sets.reduce((arr, current) => {
|
||||
if (!arr.some(item => item.id === current.id)) {
|
||||
arr.push(current);
|
||||
}
|
||||
|
||||
arr.find(item => item.id === current.id).tags.push(current.tag);
|
||||
return arr;
|
||||
}, []);
|
||||
|
||||
return { total, sets };
|
||||
}
|
||||
|
||||
|
||||
@@ -50,8 +50,14 @@ async function Query(query, params, callback) {
|
||||
|
||||
// debug moment
|
||||
Logger.Database(`PSQL Query: ${query.substring(0, 500).trim()}...`);
|
||||
try {
|
||||
const result = await connection.query(query, params, callback);
|
||||
return result;
|
||||
} catch (err) {
|
||||
Logger.Database(`PSQL Query Error: ${err.message}`);
|
||||
connection.query('ROLLBACK TRANSACTION;');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function Destroy() {
|
||||
|
||||
@@ -16,6 +16,9 @@ function Get(req, res) {
|
||||
id = id.replace('-thumb', '');
|
||||
}
|
||||
|
||||
// TODO: bricks with a modifier should show the modified colour in the thumbnail
|
||||
// I HAVE NO IDEA HOW TO DO THIS WITHOUT A LOT OF WORK
|
||||
|
||||
// this very randomly fails sometimes
|
||||
try {
|
||||
// work out hash from id
|
||||
|
||||
Reference in New Issue
Block a user