1
0
mirror of https://github.com/amir20/dozzle.git synced 2025-12-21 21:33:18 +01:00

Changes scroll progress to behave differently when fetching scroll back content. (#640)

This commit is contained in:
Amir Raminfar
2020-08-19 12:47:28 -07:00
committed by GitHub
parent 0c2e2430fd
commit b148367618
10 changed files with 158 additions and 56 deletions

View File

@@ -13,12 +13,14 @@
<router-view></router-view> <router-view></router-view>
</pane> </pane>
<pane v-for="other in activeContainers" :key="other.id" v-if="!isMobile"> <pane v-for="other in activeContainers" :key="other.id" v-if="!isMobile">
<scrollable-view> <log-container
<template v-slot:header> :id="other.id"
<container-title :value="other.name" closable @close="removeActiveContainer(other)"></container-title> :title="other.name"
</template> show-title
<log-viewer-with-source :id="other.id"></log-viewer-with-source> scrollable
</scrollable-view> closable
@close="removeActiveContainer(other)"
></log-container>
</pane> </pane>
</splitpanes> </splitpanes>
</pane> </pane>
@@ -41,26 +43,22 @@
import { mapActions, mapGetters, mapState } from "vuex"; import { mapActions, mapGetters, mapState } from "vuex";
import { Splitpanes, Pane } from "splitpanes"; import { Splitpanes, Pane } from "splitpanes";
import LogViewerWithSource from "./components/LogViewerWithSource"; import LogContainer from "./components/LogContainer";
import ScrollableView from "./components/ScrollableView";
import SideMenu from "./components/SideMenu"; import SideMenu from "./components/SideMenu";
import MobileMenu from "./components/MobileMenu"; import MobileMenu from "./components/MobileMenu";
import Search from "./components/Search"; import Search from "./components/Search";
import ContainerTitle from "./components/ContainerTitle";
import Icon from "./components/Icon"; import Icon from "./components/Icon";
export default { export default {
name: "App", name: "App",
components: { components: {
Icon, Icon,
LogViewerWithSource,
SideMenu, SideMenu,
LogContainer,
MobileMenu, MobileMenu,
ScrollableView,
Splitpanes, Splitpanes,
Pane, Pane,
Search, Search,
ContainerTitle,
}, },
data() { data() {
return { return {

View File

@@ -1,14 +1,12 @@
<template> <template>
<div ref="observer" class="control" :class="{ 'is-loading': isLoading }"></div> <div ref="observer" class="control"></div>
</template> </template>
<script> <script>
export default { export default {
name: "InfiniteLoader", name: "InfiniteLoader",
data() { data() {
return { return {};
isLoading: false,
};
}, },
props: { props: {
onLoadMore: Function, onLoadMore: Function,
@@ -21,9 +19,7 @@ export default {
if (this.onLoadMore && this.enabled) { if (this.onLoadMore && this.enabled) {
const scrollingParent = this.$el.closest("[data-scrolling]") || document.documentElement; const scrollingParent = this.$el.closest("[data-scrolling]") || document.documentElement;
const previousHeight = scrollingParent.scrollHeight; const previousHeight = scrollingParent.scrollHeight;
this.isLoading = true;
await this.onLoadMore(); await this.onLoadMore();
this.isLoading = false;
this.$nextTick(() => (scrollingParent.scrollTop += scrollingParent.scrollHeight - previousHeight)); this.$nextTick(() => (scrollingParent.scrollTop += scrollingParent.scrollHeight - previousHeight));
} }
}, },

View File

@@ -0,0 +1,45 @@
<template>
<scrollable-view :scrollable="scrollable">
<template v-slot:header v-if="showTitle">
<container-title :value="title" :closable="closable" @close="$emit('close')"></container-title>
</template>
<template v-slot="{ setLoading }">
<log-viewer-with-source :id="id" @loading-more="setLoading($event)"></log-viewer-with-source>
</template>
</scrollable-view>
</template>
<script>
import LogViewerWithSource from "./LogViewerWithSource";
import ScrollableView from "./ScrollableView";
import ContainerTitle from "./ContainerTitle";
export default {
props: {
id: {
type: String,
},
title: {
type: String,
},
showTitle: {
type: Boolean,
default: false,
},
scrollable: {
type: Boolean,
default: false,
},
closable: {
type: Boolean,
default: false,
},
},
name: "LogContainer",
components: {
LogViewerWithSource,
ScrollableView,
ContainerTitle,
},
};
</script>

View File

@@ -60,6 +60,7 @@ export default {
async loadOlderLogs() { async loadOlderLogs() {
if (this.messages.length < 300) return; if (this.messages.length < 300) return;
this.$emit("loading-more", true);
const to = this.messages[0].date; const to = this.messages[0].date;
const last = this.messages[299].date; const last = this.messages[299].date;
const delta = to - last; const delta = to - last;
@@ -74,6 +75,7 @@ export default {
.map((line) => this.parseMessage(line)); .map((line) => this.parseMessage(line));
this.messages.unshift(...newMessages); this.messages.unshift(...newMessages);
} }
this.$emit("loading-more", false);
}, },
parseMessage(data) { parseMessage(data) {
let i = data.indexOf(" "); let i = data.indexOf(" ");

View File

@@ -1,5 +1,5 @@
<template> <template>
<log-event-source :id="id" v-slot="eventSource"> <log-event-source :id="id" v-slot="eventSource" @loading-more="$emit('loading-more', $event)">
<log-viewer :messages="eventSource.messages"></log-viewer> <log-viewer :messages="eventSource.messages"></log-viewer>
</log-event-source> </log-event-source>
</template> </template>

View File

@@ -8,9 +8,11 @@
> >
<span></span> <span></span> <span></span> <span></span> <span></span> <span></span>
</a> </a>
<svg class="logo"> <router-link :to="{ name: 'default' }">
<use href="#logo"></use> <svg class="logo">
</svg> <use href="#logo"></use>
</svg>
</router-link>
<p class="menu-label is-hidden-mobile" :class="{ 'is-active': showNav }">Containers</p> <p class="menu-label is-hidden-mobile" :class="{ 'is-active': showNav }">Containers</p>
<ul class="menu-list is-hidden-mobile" :class="{ 'is-active': showNav }"> <ul class="menu-list is-hidden-mobile" :class="{ 'is-active': showNav }">
<li v-for="item in visibleContainers" :key="item.id"> <li v-for="item in visibleContainers" :key="item.id">

View File

@@ -1,15 +1,22 @@
<template> <template>
<div class="scroll-progress"> <div class="scroll-progress">
<svg width="100" height="100" viewBox="0 0 100 100"> <svg width="100" height="100" viewBox="0 0 100 100" :class="{ indeterminate }">
<circle r="44" cx="50" cy="50" :style="{ '--progress': scrollProgress }" /> <circle r="44" cx="50" cy="50" :style="{ '--progress': scrollProgress }" />
</svg> </svg>
<div class="is-overlay columns is-vcentered is-centered has-text-weight-light"> <div class="is-overlay columns is-vcentered is-centered has-text-weight-light">
<span class="column is-narrow is-paddingless is-size-2"> <template v-if="indeterminate">
{{ Math.ceil(scrollProgress * 100) }} <div class="column is-narrow is-paddingless is-size-2">
</span> &#8734;
<span class="column is-narrow is-paddingless"> </div>
% </template>
</span> <template v-else>
<span class="column is-narrow is-paddingless is-size-2">
{{ Math.ceil(scrollProgress * 100) }}
</span>
<span class="column is-narrow is-paddingless">
%
</span>
</template>
</div> </div>
</div> </div>
</template> </template>
@@ -20,6 +27,16 @@ import throttle from "lodash.throttle";
export default { export default {
name: "ScrollProgress", name: "ScrollProgress",
props: {
indeterminate: {
default: false,
type: Boolean,
},
autoHide: {
default: true,
type: Boolean,
},
},
data() { data() {
return { return {
scrollProgress: 0, scrollProgress: 0,
@@ -39,6 +56,9 @@ export default {
this.detachEvents(); this.detachEvents();
this.attachEvents(); this.attachEvents();
}, },
indeterminate() {
this.$nextTick(() => this.onScroll());
},
}, },
computed: { computed: {
...mapState(["activeContainers"]), ...mapState(["activeContainers"]),
@@ -54,16 +74,18 @@ export default {
onScroll() { onScroll() {
const p = this.parentElement == document ? document.documentElement : this.parentElement; const p = this.parentElement == document ? document.documentElement : this.parentElement;
this.scrollProgress = p.scrollTop / (p.scrollHeight - p.clientHeight); this.scrollProgress = p.scrollTop / (p.scrollHeight - p.clientHeight);
this.animation.cancel(); if (this.autoHide) {
this.animation = this.$el.animate( this.animation.cancel();
{ opacity: [1, 0] }, this.animation = this.$el.animate(
{ { opacity: [1, 0] },
duration: 500, {
delay: 2000, duration: 500,
fill: "both", delay: 2000,
easing: "ease-out", fill: "both",
} easing: "ease-out",
); }
);
}
}, },
}, },
}; };
@@ -79,10 +101,46 @@ export default {
transform: rotate(-90deg); transform: rotate(-90deg);
transform-origin: 50% 50%; transform-origin: 50% 50%;
stroke: var(--primary-color); stroke: var(--primary-color);
stroke-dashoffset: calc(276.32px - var(--progress) * 276.32px); stroke-dashoffset: calc(276.32 - var(--progress) * 276.32);
stroke-dasharray: 276.32px 276.32px; stroke-dasharray: 276.32px 276.32px;
stroke-linecap: round;
stroke-width: 3; stroke-width: 3;
will-change: stroke-dashoffset; will-change: stroke-dashoffset;
} }
svg.indeterminate {
animation: 2s linear infinite svg-animation;
circle {
animation: 1.4s ease-in-out infinite both circle-animation;
}
}
}
@keyframes svg-animation {
0% {
transform: rotateZ(0deg);
}
100% {
transform: rotateZ(360deg);
}
}
@keyframes circle-animation {
0%,
25% {
stroke-dashoffset: 275;
transform: rotate(0);
}
50%,
75% {
stroke-dashoffset: 70;
transform: rotate(45deg);
}
100% {
stroke-dashoffset: 275;
transform: rotate(360deg);
}
} }
</style> </style>

View File

@@ -5,9 +5,9 @@
</header> </header>
<main ref="content" :data-scrolling="scrollable"> <main ref="content" :data-scrolling="scrollable">
<div class="is-scrollbar-progress is-hidden-mobile"> <div class="is-scrollbar-progress is-hidden-mobile">
<scroll-progress v-show="paused"></scroll-progress> <scroll-progress v-show="paused" :indeterminate="loading" :auto-hide="!loading"></scroll-progress>
</div> </div>
<slot></slot> <slot :setLoading="setLoading"></slot>
<div ref="scrollObserver" class="is-scroll-observer"></div> <div ref="scrollObserver" class="is-scroll-observer"></div>
</main> </main>
@@ -41,6 +41,7 @@ export default {
return { return {
paused: false, paused: false,
hasMore: false, hasMore: false,
loading: false,
}; };
}, },
mounted() { mounted() {
@@ -73,6 +74,9 @@ export default {
this.$refs.scrollObserver.scrollIntoView({ behavior }); this.$refs.scrollObserver.scrollIntoView({ behavior });
this.hasMore = false; this.hasMore = false;
}, },
setLoading(loading) {
this.loading = loading;
},
}, },
}; };
</script> </script>

View File

@@ -1,27 +1,24 @@
<template> <template>
<scrollable-view :scrollable="activeContainers.length > 0"> <log-container
<template v-slot:header v-if="activeContainers.length > 0"> :id="id"
<container-title :value="allContainersById[id].name"></container-title> :title="title"
</template> :show-title="activeContainers.length > 0"
<log-viewer-with-source :id="id"></log-viewer-with-source> :scrollable="activeContainers.length > 0"
</scrollable-view> >
</log-container>
</template> </template>
<script> <script>
import { mapActions, mapGetters, mapState } from "vuex"; import { mapActions, mapGetters, mapState } from "vuex";
import LogViewerWithSource from "../components/LogViewerWithSource"; import LogContainer from "../components/LogContainer";
import ScrollableView from "../components/ScrollableView";
import ContainerTitle from "../components/ContainerTitle";
import store from "../store"; import store from "../store";
export default { export default {
props: ["id", "name"], props: ["id"],
name: "Container", name: "Container",
components: { components: {
LogViewerWithSource, LogContainer,
ScrollableView,
ContainerTitle,
}, },
data() { data() {
return { return {

View File

@@ -51,7 +51,7 @@
</span> </span>
</p> </p>
</div> </div>
<p class="panel-tabs"> <p class="panel-tabs" v-if="!search">
<a :class="{ 'is-active': sort === 'running' }" @click="sort = 'running'">Running</a> <a :class="{ 'is-active': sort === 'running' }" @click="sort = 'running'">Running</a>
<a :class="{ 'is-active': sort === 'all' }" @click="sort = 'all'">All</a> <a :class="{ 'is-active': sort === 'all' }" @click="sort = 'all'">All</a>
</p> </p>