add infifny scrolling
This commit is contained in:
parent
a5f0bcb522
commit
29354c6ba0
|
@ -0,0 +1,380 @@
|
||||||
|
<script context="module">
|
||||||
|
|
||||||
|
const THROTTLE_LIMIT = 50;
|
||||||
|
const LOOP_CHECK_TIMEOUT = 1000;
|
||||||
|
const LOOP_CHECK_MAX_CALLS = 10;
|
||||||
|
|
||||||
|
const ERROR_INFINITE_LOOP = [
|
||||||
|
`executed the callback function more than ${LOOP_CHECK_MAX_CALLS} times for a short time, it looks like searched a wrong scroll wrapper that doest not has fixed height or maximum height, please check it. If you want to force to set a element as scroll wrapper rather than automatic searching, you can do this:`,
|
||||||
|
'<!-- add a special attribute for the real scroll wrapper (can also be data-infinite-wrapper) -->',
|
||||||
|
'<div infinite-wrapper>',
|
||||||
|
' ...',
|
||||||
|
' <!-- set forceUseInfiniteWrapper -->',
|
||||||
|
' <InfiniteLoading forceUseInfiniteWrapper>',
|
||||||
|
'</div>',
|
||||||
|
'or',
|
||||||
|
'<div class="infinite-wrapper">',
|
||||||
|
' ...',
|
||||||
|
' <!-- set forceUseInfiniteWrapper as css selector of the real scroll wrapper -->',
|
||||||
|
' <InfiniteLoading forceUseInfiniteWrapper=".infinite-wrapper" />',
|
||||||
|
'</div>',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const thirdEventArg = (() => {
|
||||||
|
let supportsPassive = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const opts = Object.defineProperty({}, 'passive', {
|
||||||
|
get() {
|
||||||
|
supportsPassive = { passive: true };
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('testPassive', null, opts);
|
||||||
|
window.removeEventListener('testPassive', null, opts);
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
return supportsPassive;
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
const throttler = {
|
||||||
|
timers: [],
|
||||||
|
caches: [],
|
||||||
|
|
||||||
|
throttle(fn) {
|
||||||
|
if (this.caches.indexOf(fn) === -1) {
|
||||||
|
// cache current handler
|
||||||
|
this.caches.push(fn);
|
||||||
|
|
||||||
|
// save timer for current handler
|
||||||
|
this.timers.push(setTimeout(() => {
|
||||||
|
fn();
|
||||||
|
|
||||||
|
// empty cache and timer
|
||||||
|
this.caches.splice(this.caches.indexOf(fn), 1);
|
||||||
|
this.timers.shift();
|
||||||
|
}, THROTTLE_LIMIT));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
// reset all timers
|
||||||
|
this.timers.forEach((timer) => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
});
|
||||||
|
this.timers.length = 0;
|
||||||
|
|
||||||
|
// empty caches
|
||||||
|
this.caches = [];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const loopTracker = {
|
||||||
|
isChecked: false,
|
||||||
|
timer: null,
|
||||||
|
times: 0,
|
||||||
|
|
||||||
|
track() {
|
||||||
|
// record track times
|
||||||
|
this.times += 1;
|
||||||
|
|
||||||
|
// try to mark check status
|
||||||
|
clearTimeout(this.timer);
|
||||||
|
this.timer = setTimeout(() => {
|
||||||
|
this.isChecked = true;
|
||||||
|
}, LOOP_CHECK_TIMEOUT);
|
||||||
|
|
||||||
|
// throw warning if the times of continuous calls large than the maximum times
|
||||||
|
if (this.times > LOOP_CHECK_MAX_CALLS) {
|
||||||
|
console.error(ERROR_INFINITE_LOOP);
|
||||||
|
this.isChecked = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const scrollBarStorage = {
|
||||||
|
key: '_infiniteScrollHeight',
|
||||||
|
|
||||||
|
getScrollElement(element) {
|
||||||
|
return element === window ? document.documentElement : element;
|
||||||
|
},
|
||||||
|
|
||||||
|
save(element) {
|
||||||
|
const target = this.getScrollElement(element);
|
||||||
|
|
||||||
|
// save scroll height on the scroll parent
|
||||||
|
target[this.key] = target.scrollHeight;
|
||||||
|
},
|
||||||
|
|
||||||
|
restore(element) {
|
||||||
|
const target = this.getScrollElement(element);
|
||||||
|
|
||||||
|
/* istanbul ignore else */
|
||||||
|
if (typeof target[this.key] === 'number') {
|
||||||
|
target.scrollTop = target.scrollHeight - target[this.key] + target.scrollTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.remove(target);
|
||||||
|
},
|
||||||
|
|
||||||
|
remove(element) {
|
||||||
|
if (element[this.key] !== undefined) {
|
||||||
|
// remove scroll height
|
||||||
|
delete element[this.key]; // eslint-disable-line no-param-reassign
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function isVisible(element) {
|
||||||
|
return element && (element.offsetWidth + element.offsetHeight) > 0;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { onMount, onDestroy, tick, createEventDispatcher } from 'svelte';
|
||||||
|
import Spinner from './Spinner.svelte';
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
const STATUS = {
|
||||||
|
READY: 0,
|
||||||
|
LOADING: 1,
|
||||||
|
COMPLETE: 2,
|
||||||
|
ERROR: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
export let distance = 100;
|
||||||
|
export let spinner = 'default';
|
||||||
|
export let direction = 'bottom';
|
||||||
|
export let forceUseInfiniteWrapper = false;
|
||||||
|
export let identifier = +new Date();
|
||||||
|
|
||||||
|
let isFirstLoad = true; // save the current loading whether it is the first loading
|
||||||
|
let status = STATUS.READY;
|
||||||
|
let mounted = false;
|
||||||
|
let thisElement;
|
||||||
|
let scrollParent;
|
||||||
|
|
||||||
|
$: showSpinner = status === STATUS.LOADING;
|
||||||
|
$: showError = status === STATUS.ERROR;
|
||||||
|
$: showNoResults = status === STATUS.COMPLETE && isFirstLoad;
|
||||||
|
$: showNoMore = status === STATUS.COMPLETE && !isFirstLoad;
|
||||||
|
|
||||||
|
const stateChanger = {
|
||||||
|
loaded: async () => {
|
||||||
|
isFirstLoad = false;
|
||||||
|
|
||||||
|
if (direction === 'top') {
|
||||||
|
// wait for DOM updated
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
scrollBarStorage.restore(scrollParent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === STATUS.LOADING) {
|
||||||
|
await tick();
|
||||||
|
await attemptLoad(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
complete: async () => {
|
||||||
|
status = STATUS.COMPLETE;
|
||||||
|
|
||||||
|
// force re-complation computed properties to fix the problem of get slot text delay
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
scrollParent.removeEventListener('scroll', scrollHandler, thirdEventArg);
|
||||||
|
},
|
||||||
|
|
||||||
|
reset: () => {
|
||||||
|
status = STATUS.READY;
|
||||||
|
isFirstLoad = true;
|
||||||
|
|
||||||
|
scrollBarStorage.remove(scrollParent);
|
||||||
|
|
||||||
|
scrollParent.addEventListener('scroll', scrollHandler, thirdEventArg);
|
||||||
|
|
||||||
|
// wait for list to be empty and the empty action may trigger a scroll event
|
||||||
|
setTimeout(() => {
|
||||||
|
throttler.reset();
|
||||||
|
scrollHandler();
|
||||||
|
}, 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
error: () => {
|
||||||
|
status = STATUS.ERROR;
|
||||||
|
throttler.reset();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function scrollHandler(event) {
|
||||||
|
if (status === STATUS.READY) {
|
||||||
|
if (event && event.constructor === Event && isVisible(thisElement)) {
|
||||||
|
throttler.throttle(attemptLoad);
|
||||||
|
} else {
|
||||||
|
attemptLoad();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to trigger load
|
||||||
|
async function attemptLoad(isContinuousCall) {
|
||||||
|
if (status !== STATUS.COMPLETE && isVisible(thisElement) && getCurrentDistance() <= distance) {
|
||||||
|
status = STATUS.LOADING;
|
||||||
|
|
||||||
|
if (direction === 'top') {
|
||||||
|
// wait for spinner display
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
scrollBarStorage.save(scrollParent);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch('infinite', stateChanger);
|
||||||
|
|
||||||
|
if (isContinuousCall && !forceUseInfiniteWrapper && !loopTracker.isChecked) {
|
||||||
|
// check this component whether be in an infinite loop if it is not checked
|
||||||
|
loopTracker.track();
|
||||||
|
}
|
||||||
|
} else if (status === STATUS.LOADING) {
|
||||||
|
status = STATUS.READY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current distance from the specified direction
|
||||||
|
function getCurrentDistance() {
|
||||||
|
let distance;
|
||||||
|
|
||||||
|
if (direction === 'top') {
|
||||||
|
distance = typeof scrollParent.scrollTop === 'number' ? scrollParent.scrollTop : scrollParent.pageYOffset;
|
||||||
|
} else {
|
||||||
|
const infiniteElementOffsetTopFromBottom = thisElement.getBoundingClientRect().top;
|
||||||
|
const scrollElementOffsetTopFromBottom = scrollParent === window ? window.innerHeight : scrollParent.getBoundingClientRect().bottom;
|
||||||
|
|
||||||
|
distance = infiniteElementOffsetTopFromBottom - scrollElementOffsetTopFromBottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the first scroll parent of an element
|
||||||
|
function getScrollParent(element = thisElement) {
|
||||||
|
let result;
|
||||||
|
|
||||||
|
if (typeof forceUseInfiniteWrapper === 'string') {
|
||||||
|
result = document.querySelector(forceUseInfiniteWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
if (element.tagName === 'BODY') {
|
||||||
|
result = window;
|
||||||
|
} else if (!forceUseInfiniteWrapper && ['scroll', 'auto'].indexOf(getComputedStyle(element).overflowY) > -1) {
|
||||||
|
result = element;
|
||||||
|
} else if (element.hasAttribute('infinite-wrapper') || element.hasAttribute('data-infinite-wrapper')) {
|
||||||
|
result = element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result || getScrollParent(element.parentNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateScrollParent() {
|
||||||
|
if (mounted) scrollParent = getScrollParent();
|
||||||
|
}
|
||||||
|
|
||||||
|
function identifierUpdated() {
|
||||||
|
if (mounted) stateChanger.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch forceUseInfiniteWrapper and mounted
|
||||||
|
$: forceUseInfiniteWrapper, mounted, updateScrollParent();
|
||||||
|
|
||||||
|
// Watch identifier and mounted
|
||||||
|
$: identifier, mounted, identifierUpdated();
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
mounted = true;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
scrollHandler();
|
||||||
|
scrollParent.addEventListener('scroll', scrollHandler, thirdEventArg);
|
||||||
|
}, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
if (mounted && status !== STATUS.COMPLETE) {
|
||||||
|
throttler.reset();
|
||||||
|
scrollBarStorage.remove(scrollParent);
|
||||||
|
scrollParent.removeEventListener('scroll', scrollHandler, thirdEventArg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="infinite-loading-container" bind:this={thisElement}>
|
||||||
|
{#if showSpinner}
|
||||||
|
<div class="infinite-status-prompt">
|
||||||
|
<slot name="spinner" {isFirstLoad}>
|
||||||
|
<Spinner {spinner} />
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if showNoResults}
|
||||||
|
<div class="infinite-status-prompt">
|
||||||
|
<slot name="noResults">
|
||||||
|
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if showNoMore}
|
||||||
|
<div class="infinite-status-prompt">
|
||||||
|
<slot name="noMore">
|
||||||
|
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if showError}
|
||||||
|
<div class="infinite-status-prompt">
|
||||||
|
<slot name="error" {attemptLoad}>
|
||||||
|
---
|
||||||
|
<br>
|
||||||
|
<button class="btn-try-infinite" on:click={attemptLoad}>
|
||||||
|
Retry
|
||||||
|
</button>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.infinite-loading-container {
|
||||||
|
clear: both;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-try-infinite {
|
||||||
|
margin-top: 5px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 3px;
|
||||||
|
outline: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-try-infinite:not(:active):hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,467 @@
|
||||||
|
<script>
|
||||||
|
export let spinner = '';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if spinner === 'bubbles'}
|
||||||
|
|
||||||
|
<!-- BUBBLES -->
|
||||||
|
<span class="loading-bubbles">
|
||||||
|
<span class="bubble-item"></span>
|
||||||
|
<span class="bubble-item"></span>
|
||||||
|
<span class="bubble-item"></span>
|
||||||
|
<span class="bubble-item"></span>
|
||||||
|
<span class="bubble-item"></span>
|
||||||
|
<span class="bubble-item"></span>
|
||||||
|
<span class="bubble-item"></span>
|
||||||
|
<span class="bubble-item"></span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{:else if spinner === 'circles'}
|
||||||
|
|
||||||
|
<!-- CIRCLES -->
|
||||||
|
<span class="loading-circles">
|
||||||
|
<span class="circle-item"></span>
|
||||||
|
<span class="circle-item"></span>
|
||||||
|
<span class="circle-item"></span>
|
||||||
|
<span class="circle-item"></span>
|
||||||
|
<span class="circle-item"></span>
|
||||||
|
<span class="circle-item"></span>
|
||||||
|
<span class="circle-item"></span>
|
||||||
|
<span class="circle-item"></span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{:else if spinner === 'spiral'}
|
||||||
|
|
||||||
|
<!-- SPIRAL -->
|
||||||
|
<i class="loading-spiral"></i>
|
||||||
|
|
||||||
|
{:else if spinner === 'wavedots'}
|
||||||
|
|
||||||
|
<!-- WAVEDOTS -->
|
||||||
|
<span class="loading-wave-dots">
|
||||||
|
<span class="wave-item"></span>
|
||||||
|
<span class="wave-item"></span>
|
||||||
|
<span class="wave-item"></span>
|
||||||
|
<span class="wave-item"></span>
|
||||||
|
<span class="wave-item"></span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{:else}
|
||||||
|
|
||||||
|
<!-- DEFAULT -->
|
||||||
|
<i class="loading-default"></i>
|
||||||
|
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* THIS IS THE COMPILED spinner.less STYLESHEET FROM vue-infinite-loading */
|
||||||
|
/* COMPILED BECAUSE OTHERWISE THE USER WOULD HAVE TO ADD AN less PREPROCESSOR */
|
||||||
|
|
||||||
|
|
||||||
|
.loading-wave-dots {
|
||||||
|
/*
|
||||||
|
$size: 8px;
|
||||||
|
$wave: -6px;
|
||||||
|
$delay: .14s;
|
||||||
|
*/
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-wave-dots .wave-item {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: -4px; /* = -$size / 2 */
|
||||||
|
width: 8px; /* = $size */
|
||||||
|
height: 8px; /* = $size */
|
||||||
|
border-radius: 50%;
|
||||||
|
-webkit-animation: loading-wave-dots linear 2.8s infinite;
|
||||||
|
animation: loading-wave-dots linear 2.8s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-wave-dots .wave-item:first-child {
|
||||||
|
margin-left: -36px; /* = -$size/2 + -$size * 4 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-wave-dots .wave-item:nth-child(2) {
|
||||||
|
margin-left: -20px; /* = -$size/2 + -$size * 2 */
|
||||||
|
-webkit-animation-delay: 0.14s; /* = $delay */
|
||||||
|
animation-delay: 0.14s; /* = $delay */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-wave-dots .wave-item:nth-child(3) {
|
||||||
|
margin-left: -4px; /* = -$size/2 */
|
||||||
|
-webkit-animation-delay: 0.28s; /* = $delay * 2 */
|
||||||
|
animation-delay: 0.28s; /* = $delay * 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-wave-dots .wave-item:nth-child(4) {
|
||||||
|
margin-left: 12px; /* = -$size/2 + $size * 2 */
|
||||||
|
-webkit-animation-delay: 0.42s; /* = $delay * 3 */
|
||||||
|
animation-delay: 0.42s; /* = $delay * 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-wave-dots .wave-item:last-child {
|
||||||
|
margin-left: 28px; /* = -$size/2 + $size * 4 */
|
||||||
|
-webkit-animation-delay: 0.56s; /* = $delay * 4 */
|
||||||
|
animation-delay: 0.56s; /* = $delay * 4 */
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes loading-wave-dots {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: translateY(0);
|
||||||
|
transform: translateY(0);
|
||||||
|
background: #bbb;
|
||||||
|
}
|
||||||
|
10% {
|
||||||
|
-webkit-transform: translateY(-6px); /* = translateY($wave) */
|
||||||
|
transform: translateY(-6px); /* = translateY($wave) */
|
||||||
|
background: #999;
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
-webkit-transform: translateY(0);
|
||||||
|
transform: translateY(0);
|
||||||
|
background: #bbb;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
-webkit-transform: translateY(0);
|
||||||
|
transform: translateY(0);
|
||||||
|
background: #bbb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loading-wave-dots {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: translateY(0);
|
||||||
|
transform: translateY(0);
|
||||||
|
background: #bbb;
|
||||||
|
}
|
||||||
|
10% {
|
||||||
|
-webkit-transform: translateY(-6px); /* = translateY($wave) */
|
||||||
|
transform: translateY(-6px); /* = translateY($wave) */
|
||||||
|
background: #999;
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
-webkit-transform: translateY(0);
|
||||||
|
transform: translateY(0);
|
||||||
|
background: #bbb;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
-webkit-transform: translateY(0);
|
||||||
|
transform: translateY(0);
|
||||||
|
background: #bbb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
.loading-circles {
|
||||||
|
$size: 5px;
|
||||||
|
$radius: 12px;
|
||||||
|
$shallow: 56%;
|
||||||
|
$c-basic: #505050;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
.loading-circles .circle-item {
|
||||||
|
width: 5px; /* = $size */
|
||||||
|
height: 5px; /* = $size */
|
||||||
|
-webkit-animation: loading-circles linear .75s infinite;
|
||||||
|
animation: loading-circles linear .75s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-circles .circle-item:first-child {
|
||||||
|
margin-top: -14.5px; /* = -$size/2 + -$radius */
|
||||||
|
margin-left: -2.5px; /* = -$size/2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-circles .circle-item:nth-child(2) {
|
||||||
|
margin-top: -11.26px; /* = -$size/2 + -$radius * .73 */
|
||||||
|
margin-left: 6.26px; /* = -$size/2 + $radius * .73 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-circles .circle-item:nth-child(3) {
|
||||||
|
margin-top: -2.5px; /* = -$size/2 */
|
||||||
|
margin-left: 9.5px; /* = -$size/2 + $radius */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-circles .circle-item:nth-child(4) {
|
||||||
|
margin-top: 6.26px; /* = -$size/2 + $radius * .73 */
|
||||||
|
margin-left: 6.26px; /* = -$size/2 + $radius * .73 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-circles .circle-item:nth-child(5) {
|
||||||
|
margin-top: 9.5px; /* = -$size/2 + $radius */
|
||||||
|
margin-left: -2.5px; /* = -$size/2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-circles .circle-item:nth-child(6) {
|
||||||
|
margin-top: 6.26px; /* = -$size/2 + $radius * .73 */
|
||||||
|
margin-left: -11.26px; /* = -$size/2 + -$radius * .73 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-circles .circle-item:nth-child(7) {
|
||||||
|
margin-top: -2.5px; /* = -$size/2 */
|
||||||
|
margin-left: -14.5px; /* = -$size/2 + -$radius */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-circles .circle-item:last-child {
|
||||||
|
margin-top: -11.26px; /* = -$size/2 + -$radius * .73 */
|
||||||
|
margin-left: -11.26px; /* = -$size/2 + -$radius * .73 */
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes loading-circles {
|
||||||
|
0% {
|
||||||
|
background: #dfdfdf; /* = lighten($c-basic, $shallow) */
|
||||||
|
}
|
||||||
|
90% {
|
||||||
|
background: #505050; /* = $c-basic */
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background: #dfdfdf; /* = lighten($c-basic, $shallow) */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loading-circles {
|
||||||
|
0% {
|
||||||
|
background: #dfdfdf; /* = lighten($c-basic, $shallow) */
|
||||||
|
}
|
||||||
|
90% {
|
||||||
|
background: #505050; /* = $c-basic */
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background: #dfdfdf; /* = lighten($c-basic, $shallow) */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
.loading-bubbles {
|
||||||
|
$size: 1px;
|
||||||
|
$radius: 12px;
|
||||||
|
$shallow: 3px;
|
||||||
|
$c-basic: #666;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
.loading-bubbles .bubble-item {
|
||||||
|
background: #666; /* = $c-basic */
|
||||||
|
-webkit-animation: loading-bubbles linear .75s infinite;
|
||||||
|
animation: loading-bubbles linear .75s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-bubbles .bubble-item:first-child {
|
||||||
|
margin-top: -12.5px; /* = -$size/2 + -$radius */
|
||||||
|
margin-left: -0.5px; /* = -$size/2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-bubbles .bubble-item:nth-child(2) {
|
||||||
|
margin-top: -9.26px; /* = -$size/2 + -$radius * .73 */
|
||||||
|
margin-left: 8.26px; /* = -$size/2 + $radius * .73 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-bubbles .bubble-item:nth-child(3) {
|
||||||
|
margin-top: -0.5px; /* = -$size/2 */
|
||||||
|
margin-left: 11.5px; /* = -$size/2 + $radius */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-bubbles .bubble-item:nth-child(4) {
|
||||||
|
margin-top: 8.26px; /* = -$size/2 + $radius * .73 */
|
||||||
|
margin-left: 8.26px; /* = -$size/2 + $radius * .73 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-bubbles .bubble-item:nth-child(5) {
|
||||||
|
margin-top: 11.5px; /* = -$size/2 + $radius */
|
||||||
|
margin-left: -0.5px; /* = -$size/2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-bubbles .bubble-item:nth-child(6) {
|
||||||
|
margin-top: 8.26px; /* = -$size/2 + $radius * .73 */
|
||||||
|
margin-left: -9.26px; /* = -$size/2 + -$radius * .73 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-bubbles .bubble-item:nth-child(7) {
|
||||||
|
margin-top: -0.5px; /* = -$size/2 */
|
||||||
|
margin-left: -12.5px; /* = -$size/2 + -$radius */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-bubbles .bubble-item:last-child {
|
||||||
|
margin-top: -9.26px; /* = -$size/2 + -$radius * .73 */
|
||||||
|
margin-left: -9.26px; /* = -$size/2 + -$radius * .73 */
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes loading-bubbles {
|
||||||
|
0% {
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
box-shadow: 0 0 0 3px #666; /* = 0 0 0 $shallow $c-basic */
|
||||||
|
}
|
||||||
|
90% {
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
box-shadow: 0 0 0 0 #666; /* = 0 0 0 0 $c-basic */
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
box-shadow: 0 0 0 3px #666; /* = 0 0 0 $shallow $c-basic */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loading-bubbles {
|
||||||
|
0% {
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
box-shadow: 0 0 0 3px #666; /* = 0 0 0 $shallow $c-basic */
|
||||||
|
}
|
||||||
|
90% {
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
box-shadow: 0 0 0 0 #666; /* = 0 0 0 0 $c-basic */
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
box-shadow: 0 0 0 3px #666; /* = 0 0 0 $shallow $c-basic */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* default adjust-huener */
|
||||||
|
.loading-default {
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid #999;
|
||||||
|
-webkit-animation: loading-rotating ease 1.5s infinite;
|
||||||
|
animation: loading-rotating ease 1.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-default:before {
|
||||||
|
/*
|
||||||
|
$size: 6px
|
||||||
|
*/
|
||||||
|
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
margin-top: -3px; /* = -$size/2 */
|
||||||
|
margin-left: -3px; /* = -$size/2 */
|
||||||
|
width: 6px; /* = $size */
|
||||||
|
height: 6px; /* = $size */
|
||||||
|
background-color: #999;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* spiral adjust-huener */
|
||||||
|
.loading-spiral {
|
||||||
|
border: 2px solid #777;
|
||||||
|
border-right-color: transparent;
|
||||||
|
-webkit-animation: loading-rotating linear .85s infinite;
|
||||||
|
animation: loading-rotating linear .85s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes loading-rotating {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: rotate(0);
|
||||||
|
transform: rotate(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
-webkit-transform: rotate(360deg);
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loading-rotating {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: rotate(0);
|
||||||
|
transform: rotate(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
-webkit-transform: rotate(360deg);
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* common styles for the bubble adjust-huener and circle adjust-huener */
|
||||||
|
.loading-bubbles,
|
||||||
|
.loading-circles {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-circles .circle-item,
|
||||||
|
.loading-bubbles .bubble-item {
|
||||||
|
/*
|
||||||
|
$delay: .093s
|
||||||
|
*/
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-circles .circle-item:nth-child(2),
|
||||||
|
.loading-bubbles .bubble-item:nth-child(2) {
|
||||||
|
-webkit-animation-delay: 0.093s; /* = $delay */
|
||||||
|
animation-delay: 0.093s; /* = $delay */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-circles .circle-item:nth-child(3),
|
||||||
|
.loading-bubbles .bubble-item:nth-child(3) {
|
||||||
|
-webkit-animation-delay: 0.186s; /* = $delay * 2 */
|
||||||
|
animation-delay: 0.186s; /* = $delay * 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-circles .circle-item:nth-child(4),
|
||||||
|
.loading-bubbles .bubble-item:nth-child(4) {
|
||||||
|
-webkit-animation-delay: 0.279s; /* = $delay * 3 */
|
||||||
|
animation-delay: 0.279s; /* = $delay * 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-circles .circle-item:nth-child(5),
|
||||||
|
.loading-bubbles .bubble-item:nth-child(5) {
|
||||||
|
-webkit-animation-delay: 0.372s; /* = $delay * 4 */
|
||||||
|
animation-delay: 0.372s; /* = $delay * 4 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-circles .circle-item:nth-child(6),
|
||||||
|
.loading-bubbles .bubble-item:nth-child(6) {
|
||||||
|
-webkit-animation-delay: 0.465s; /* = $delay * 5 */
|
||||||
|
animation-delay: 0.465s; /* = $delay * 5 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-circles .circle-item:nth-child(7),
|
||||||
|
.loading-bubbles .bubble-item:nth-child(7) {
|
||||||
|
-webkit-animation-delay: 0.558s; /* = $delay * 6 */
|
||||||
|
animation-delay: 0.558s; /* = $delay * 6 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-circles .circle-item:last-child,
|
||||||
|
.loading-bubbles .bubble-item:last-child {
|
||||||
|
-webkit-animation-delay: 0.651s; /* = $delay * 7 */
|
||||||
|
animation-delay: 0.651s; /* = $delay * 7 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-bubbles,
|
||||||
|
.loading-circles,
|
||||||
|
.loading-spiral,
|
||||||
|
.loading-wave-dots,
|
||||||
|
.loading-default {
|
||||||
|
/*
|
||||||
|
$size: 28px
|
||||||
|
*/
|
||||||
|
|
||||||
|
display: inline-block;
|
||||||
|
margin: 5px 0;
|
||||||
|
width: 28px; /* = $size */
|
||||||
|
height: 28px; /* = $size */
|
||||||
|
font-size: 28px; /* = $size */
|
||||||
|
line-height: 28px; /* = $size */
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,33 +1,64 @@
|
||||||
<script>
|
<script>
|
||||||
|
// import { onMount } from "svelte";
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
let serverurl = 'http://127.0.0.1:3000';
|
import InfiniteLoading from '/src/lib/InfiniteLoading.svelte';
|
||||||
let recipes_p = axios.get(serverurl+"/recipes?page=59");
|
|
||||||
|
let serverurl = 'http://192.168.0.105:3000';
|
||||||
|
let last_page = 1;
|
||||||
|
let recipes_list = [];
|
||||||
|
|
||||||
|
// First loading
|
||||||
|
let recipes_p = axios.get(serverurl+"/recipes?page=" + last_page);
|
||||||
recipes_p.then(function (response) {
|
recipes_p.then(function (response) {
|
||||||
console.log(response);
|
recipes_list = response.data.recipes;
|
||||||
})
|
})
|
||||||
.catch(function (error) {
|
.catch(function (error) {
|
||||||
// handle error
|
// handle error
|
||||||
console.log(error);
|
console.log(error);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Inf loading
|
||||||
|
function infiniteHandler({ detail: { loaded, complete } }) {
|
||||||
|
last_page += 1;
|
||||||
|
axios.get(serverurl+"/recipes?page=" + last_page).then(
|
||||||
|
function (response) {
|
||||||
|
if (response.data.recipes.length > 0) {
|
||||||
|
recipes_list = [...recipes_list, ...response.data.recipes];
|
||||||
|
loaded();
|
||||||
|
} else {
|
||||||
|
complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="bg-white">
|
<div class="bg-white">
|
||||||
<div class="mx-auto max-w-2xl px-4 py-16 sm:px-6 sm:py-24 lg:max-w-7xl lg:px-8">
|
<div class="mx-auto max-w-2xl px-4 py-16 sm:px-6 sm:py-24 lg:max-w-7xl lg:px-8">
|
||||||
<h2 class="sr-only">Products</h2>
|
<h2 class="sr-only">Рецепты</h2>
|
||||||
<div class="grid grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8">
|
{#await recipes_p}
|
||||||
{#await recipes_p}
|
<div class="text-center flex items-center justify-center h-screen">
|
||||||
waiting...
|
<div role="status">
|
||||||
{:then data}
|
<svg aria-hidden="true" class="inline w-8 h-8 mr-2 text-gray-200 animate-spin dark:text-gray-200 fill-blue-600" viewBox="0 0 100 101" fill="none">
|
||||||
{#each data.data.recipes as recipe (recipe.id)}
|
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
|
||||||
<a href="/recipe/{recipe.id}" class="group transition duration-300 hover:scale-110">
|
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
|
||||||
<div class="aspect-h-1 aspect-w-1 w-full overflow-hidden rounded-lg bg-gray-200 xl:aspect-h-8 xl:aspect-w-7">
|
</svg>
|
||||||
<img src="{serverurl}/recipe_image?filename={recipe.img}" alt="{recipe.title}" class="h-full w-full object-cover object-center transition duration-700 group-hover:opacity-90">
|
<span class="sr-only">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="mt-3 text-base text-gray-700">{recipe.ctime} / {recipe.cal}</h3>
|
</div>
|
||||||
<p class="mt-0 text-lg font-medium text-gray-900">{recipe.title}</p>
|
{:then data}
|
||||||
</a>
|
<div class="grid grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8">
|
||||||
{/each}
|
{#each recipes_list as recipe}
|
||||||
{/await}
|
<a href="/recipe/{recipe.id}" class="group transition duration-300 hover:scale-110">
|
||||||
</div>
|
<div class="aspect-h-1 aspect-w-1 w-full overflow-hidden rounded-lg bg-gray-200 xl:aspect-h-8 xl:aspect-w-7">
|
||||||
|
<img src="{serverurl}/recipe_image?filename={recipe.img}" alt="{recipe.title}" class="h-full w-full object-cover object-center transition duration-700 group-hover:opacity-90">
|
||||||
|
</div>
|
||||||
|
<h3 class="mt-3 text-base text-gray-700">{recipe.ctime} / {recipe.cal}</h3>
|
||||||
|
<p class="mt-0 text-lg font-medium text-gray-900">{recipe.title}</p>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<InfiniteLoading on:infinite={infiniteHandler} />
|
||||||
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -1,6 +1,12 @@
|
||||||
<!-- <script>
|
<script>
|
||||||
import logo from '$lib/assets/logo1.png';
|
let mobileMenuOpen = false
|
||||||
</script> -->
|
|
||||||
|
$: console.log(mobileMenuOpen)
|
||||||
|
|
||||||
|
function changeMenuState() {
|
||||||
|
mobileMenuOpen = !mobileMenuOpen
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<header class="bg-slate-50">
|
<header class="bg-slate-50">
|
||||||
<nav class="mx-auto flex max-w-7xl items-center justify-between p-6 lg:px-8" aria-label="Global">
|
<nav class="mx-auto flex max-w-7xl items-center justify-between p-6 lg:px-8" aria-label="Global">
|
||||||
|
@ -11,7 +17,7 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex lg:hidden">
|
<div class="flex lg:hidden">
|
||||||
<button type="button" class="-m-2.5 inline-flex items-center justify-center rounded-md p-2.5 text-gray-700">
|
<button type="button" on:click={changeMenuState} class="-m-2.5 inline-flex items-center justify-center rounded-md p-2.5 text-gray-700">
|
||||||
<span class="sr-only">Open main menu</span>
|
<span class="sr-only">Open main menu</span>
|
||||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||||
|
@ -30,14 +36,15 @@
|
||||||
<!-- Mobile menu, show/hide based on menu open state. -->
|
<!-- Mobile menu, show/hide based on menu open state. -->
|
||||||
<div class="lg:hidden" role="dialog" aria-modal="true">
|
<div class="lg:hidden" role="dialog" aria-modal="true">
|
||||||
<!-- Background backdrop, show/hide based on slide-over state. -->
|
<!-- Background backdrop, show/hide based on slide-over state. -->
|
||||||
|
{#if mobileMenuOpen}
|
||||||
<div class="fixed inset-0 z-10"></div>
|
<div class="fixed inset-0 z-10"></div>
|
||||||
<div class="fixed inset-y-0 right-0 z-10 w-full overflow-y-auto bg-white px-6 py-6 sm:max-w-sm sm:ring-1 sm:ring-gray-900/10">
|
<div class="fixed inset-y-0 right-0 z-10 w-full overflow-y-auto bg-white px-6 py-6 sm:max-w-sm sm:ring-1 sm:ring-gray-900/10">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<a href="#" class="-m-1.5 p-1.5">
|
<a href="/" class="-m-1.5 p-1.5">
|
||||||
<span class="sr-only">Your Company</span>
|
<span class="sr-only">Your Company</span>
|
||||||
<img class="h-8 w-auto" src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=600" alt="">
|
<img class="h-10 w-auto" src="/logo1.png" alt="">
|
||||||
</a>
|
</a>
|
||||||
<button type="button" class="-m-2.5 rounded-md p-2.5 text-gray-700">
|
<button type="button" on:click={changeMenuState} class="-m-2.5 rounded-md p-2.5 text-gray-700">
|
||||||
<span class="sr-only">Close menu</span>
|
<span class="sr-only">Close menu</span>
|
||||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
@ -47,22 +54,23 @@
|
||||||
<div class="mt-6 flow-root">
|
<div class="mt-6 flow-root">
|
||||||
<div class="-my-6 divide-y divide-gray-500/10">
|
<div class="-my-6 divide-y divide-gray-500/10">
|
||||||
<div class="space-y-2 py-6">
|
<div class="space-y-2 py-6">
|
||||||
<a href="#" class="-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-50">Features</a>
|
<a href="/" on:click={changeMenuState} class="-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-50">Все рецепты</a>
|
||||||
<a href="#" class="-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-50">Marketplace</a>
|
<a href="/categories" on:click={changeMenuState} class="-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-50">Категории</a>
|
||||||
<a href="#" class="-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-50">Company</a>
|
<a href="/donate" on:click={changeMenuState} class="-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-50">Поддержать сайт</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|
||||||
<!-- todo:
|
<!-- todo:
|
||||||
категории
|
при переходе по категории перекидывать на страницу категории
|
||||||
закрытие и открытие меню для телефона
|
дозагрузка страниц в категориях
|
||||||
|
отредактировать категории
|
||||||
мобильная вёрстка
|
мобильная вёрстка
|
||||||
дозагрузка страниц на главной странице
|
|
||||||
донат
|
донат
|
||||||
поиск
|
поиск
|
||||||
-->
|
-->
|
|
@ -1,17 +1,54 @@
|
||||||
<script>
|
<script>
|
||||||
let category_list = ["Завтраки", "Коктейли", "Закуски", "Салаты", "Соусы", "Десерты", "Выпечка", "Основные блюда", "Супы"]
|
let category_list = [
|
||||||
let serverurl = 'http://127.0.0.1:3000';
|
{
|
||||||
|
title: "На сковороде",
|
||||||
|
img: "1b0dc44c-5dc3-4a74-9bb8-1a079999caa9.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Коктейли",
|
||||||
|
img: "0334e507-d2c2-4ca5-9aea-c144fbcafb76.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Закуски",
|
||||||
|
img: "00651793-d00c-4f39-aa07-3d5d5911aa8c.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Салаты",
|
||||||
|
img: "039b0932-56fc-47da-83c9-62c95f227412.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Соусы",
|
||||||
|
img: "06b85ba2-0665-40b7-9e3e-b2473dde97f2.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Десерты",
|
||||||
|
img: "07bcfe9b-d44a-47b1-adfb-3afa0694eda2.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Выпечка",
|
||||||
|
img: "06f68dda-2949-41e2-a307-efaaf5eecbbf.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Основные блюда",
|
||||||
|
img: "d6960f87-4936-439a-86fe-54d5508f06f8.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Супы",
|
||||||
|
img: "08c99a7f-66ef-4651-b26b-c2e48e901294.jpg"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
let serverurl = 'http://192.168.0.105:3000';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="bg-white">
|
<div class="bg-white">
|
||||||
<div class="mx-auto max-w-2xl px-4 py-16 sm:px-6 sm:py-24 lg:max-w-7xl lg:px-8">
|
<div class="mx-auto max-w-2xl px-4 py-16 sm:px-6 sm:py-24 lg:max-w-7xl lg:px-8">
|
||||||
<h2 class="sr-only">Products</h2>
|
<h2 class="sr-only">Products</h2>
|
||||||
<div class="grid grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8">
|
<div class="grid grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3 xl:gap-x-8">
|
||||||
{#each category_list as category}
|
{#each category_list as category}
|
||||||
<a href="/categories/{category}" class="relative group transition duration-300 hover:scale-110">
|
<a href="/categories/{category.title}" class="relative group transition duration-300 hover:scale-110">
|
||||||
<div class="aspect-h-1 aspect-w-1 w-full h-full overflow-hidden rounded-lg bg-gray-200 xl:aspect-h-8 xl:aspect-w-7">
|
<div id="category" class="aspect-h-1 aspect-w-1 w-full h-full overflow-hidden rounded-lg bg-gray-200 xl:aspect-h-8 xl:aspect-w-7">
|
||||||
<img src="{serverurl}/recipe_image?filename=1b0dc44c-5dc3-4a74-9bb8-1a079999caa9.jpg" alt="{category}" class="brightness-30 h-full w-full object-cover object-center transition duration-700 group-hover:brightness-50">
|
<img src="{serverurl}/recipe_image?filename={category.img}" alt="{category.title}" class="brightness-30 h-full w-full object-cover object-center transition duration-700 group-hover:brightness-50">
|
||||||
<p class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-lg font-bold text-slate-200 group-hover:text-white duration-700">{category}</p>
|
<p class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-center font-bold text-slate-200 group-hover:text-white duration-700">{category.title}</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -24,4 +61,22 @@
|
||||||
--tw-brightness: brightness(.3);
|
--tw-brightness: brightness(.3);
|
||||||
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'RobotoRegular';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url('/fonts/Roboto-Regular.ttf');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'AlegreyaSansSC';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 800;
|
||||||
|
src: url('/fonts/Alegreyasansscextrabold.ttf');
|
||||||
|
}
|
||||||
|
|
||||||
|
#category {
|
||||||
|
font-size: 3dvh;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
|
@ -1,9 +1,50 @@
|
||||||
<script>
|
<script>
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
console.log($page.params.category)
|
console.log($page.params.category)
|
||||||
|
|
||||||
|
let serverurl = 'http://192.168.0.105:3000';
|
||||||
|
|
||||||
|
let last_page = 1
|
||||||
|
|
||||||
|
let recipes = axios.get(serverurl+"/category_recipes?page=" + last_page + "&category=" + $page.params.category);
|
||||||
|
recipes.then(function (response) {
|
||||||
|
console.log(response);
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
// handle error
|
||||||
|
console.log(error);
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
hello, {$page.params.category}
|
<div class="mx-auto max-w-2xl px-4 py-16 sm:px-6 sm:py-24 lg:max-w-7xl lg:px-8">
|
||||||
|
<h2 class="sr-only">Категории</h2>
|
||||||
|
{#await recipes}
|
||||||
|
<div class="text-center flex items-center justify-center h-screen">
|
||||||
|
<div role="status">
|
||||||
|
<svg aria-hidden="true" class="inline w-8 h-8 mr-2 text-gray-200 animate-spin dark:text-gray-200 fill-blue-600" viewBox="0 0 100 101" fill="none">
|
||||||
|
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
|
||||||
|
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
|
||||||
|
</svg>
|
||||||
|
<span class="sr-only">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:then data}
|
||||||
|
<div class="grid grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8">
|
||||||
|
{#each data.data.recipes as recipe (recipe.id)}
|
||||||
|
<a href="/recipe/{recipe.id}" class="group transition duration-300 hover:scale-110">
|
||||||
|
<div class="aspect-h-1 aspect-w-1 w-full overflow-hidden rounded-lg bg-gray-200 xl:aspect-h-8 xl:aspect-w-7">
|
||||||
|
<img src="{serverurl}/recipe_image?filename={recipe.img}" alt="{recipe.title}" class="h-full w-full object-cover object-center transition duration-700 group-hover:opacity-90">
|
||||||
|
</div>
|
||||||
|
<h3 class="mt-3 text-base text-gray-700">{recipe.ctime} / {recipe.cal}</h3>
|
||||||
|
<p class="mt-0 text-lg font-medium text-gray-900">{recipe.title}</p>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{:catch}
|
||||||
|
Ошибка
|
||||||
|
{/await}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -3,7 +3,7 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
// create request
|
// create request
|
||||||
let serverurl = 'http://127.0.0.1:3000';
|
let serverurl = 'http://192.168.0.105:3000';
|
||||||
let recipe_p = axios.get(serverurl+"/recipe?r_id="+$page.params.recipeid)
|
let recipe_p = axios.get(serverurl+"/recipe?r_id="+$page.params.recipeid)
|
||||||
recipe_p.then(function (response) {
|
recipe_p.then(function (response) {
|
||||||
console.log(response);
|
console.log(response);
|
||||||
|
|
Loading…
Reference in New Issue