diff --git a/packages/client/src/components/form/range.vue b/packages/client/src/components/form/range.vue index d46174acc1648501a0b659b263319e5ca229dc9a..387ad26f3d3b6f46c5266c5f9609d4d78c027380 100644 --- a/packages/client/src/components/form/range.vue +++ b/packages/client/src/components/form/range.vue @@ -6,7 +6,7 @@ <div class="track"> <div class="highlight" :style="{ width: (steppedRawValue * 100) + '%' }"></div> </div> - <div v-if="steps" class="ticks"> + <div v-if="steps && showTicks" class="ticks"> <div v-for="i in (steps + 1)" class="tick" :style="{ left: (((i - 1) / steps) * 100) + '%' }"></div> </div> <div ref="thumbEl" v-tooltip="textConverter(finalValue)" class="thumb" :style="{ left: thumbPosition + 'px' }" @mousedown="onMousedown" @touchstart="onMousedown"></div> @@ -27,6 +27,7 @@ const props = withDefaults(defineProps<{ max: number; step?: number; textConverter?: (value: number) => string, + showTicks?: boolean; }>(), { step: 1, textConverter: (v) => v.toString(), diff --git a/packages/client/src/components/marquee.vue b/packages/client/src/components/marquee.vue index 2fd76a54f0407398f2028304c1aadf3980ada434..4685033517048bb9eeec0888f85c1e723953bf7f 100644 --- a/packages/client/src/components/marquee.vue +++ b/packages/client/src/components/marquee.vue @@ -1,5 +1,5 @@ <script lang="ts"> -import { h, onMounted, onUnmounted, ref } from 'vue'; +import { h, onMounted, onUnmounted, ref, watch } from 'vue'; export default { name: 'MarqueeText', @@ -32,6 +32,8 @@ export default { contentEl.value.style.animationDuration = `${duration}s`; } + watch(() => props.duration, calc); + onMounted(() => { calc(); }); diff --git a/packages/client/src/scripts/use-interval.ts b/packages/client/src/scripts/use-interval.ts index eb6e44338d55b812f5c0024a24a4b73d8e472bdd..201ba417ef2fe344ba7a5a3c51e00ed9bff19b0f 100644 --- a/packages/client/src/scripts/use-interval.ts +++ b/packages/client/src/scripts/use-interval.ts @@ -4,6 +4,8 @@ export function useInterval(fn: () => void, interval: number, options: { immediate: boolean; afterMounted: boolean; }): void { + if (Number.isNaN(interval)) return; + let intervalId: number | null = null; if (options.afterMounted) { diff --git a/packages/client/src/widgets/rss-marquee.vue b/packages/client/src/widgets/rss-marquee.vue index 2f92c09f382d9b7c772d0c421532689f8f07b26d..c20954c1e024b0b9655ed91ec677a177b3becc41 100644 --- a/packages/client/src/widgets/rss-marquee.vue +++ b/packages/client/src/widgets/rss-marquee.vue @@ -6,11 +6,13 @@ <div class="ekmkgxbk"> <MkLoading v-if="fetching"/> <div v-else class="feed"> - <MarqueeText :key="key" :duration="widgetProps.speed" :reverse="widgetProps.reverse"> - <span v-for="item in items" class="item"> - <a class="link" :href="item.link" rel="nofollow noopener" target="_blank" :title="item.title">{{ item.title }}</a><span class="divider"></span> - </span> - </MarqueeText> + <transition name="change" mode="default"> + <MarqueeText :key="key" :duration="widgetProps.duration" :reverse="widgetProps.reverse"> + <span v-for="item in items" class="item"> + <a class="link" :href="item.link" rel="nofollow noopener" target="_blank" :title="item.title">{{ item.title }}</a><span class="divider"></span> + </span> + </MarqueeText> + </transition> </div> </div> </MkContainer> @@ -32,30 +34,26 @@ const widgetPropsDef = { type: 'string' as const, default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews', }, - showHeader: { + refreshIntervalSec: { + type: 'number' as const, + default: 60, + }, + duration: { + type: 'range' as const, + default: 70, + step: 1, + min: 5, + max: 200, + }, + reverse: { type: 'boolean' as const, default: false, }, - transparent: { + showHeader: { type: 'boolean' as const, default: false, }, - speed: { - type: 'radio' as const, - default: 70, - options: [{ - value: 170, label: 'very slow', - }, { - value: 100, label: 'slow', - }, { - value: 70, label: 'medium', - }, { - value: 40, label: 'fast', - }, { - value: 20, label: 'very fast', - }], - }, - reverse: { + transparent: { type: 'boolean' as const, default: false, }, @@ -91,7 +89,7 @@ const tick = () => { watch(() => widgetProps.url, tick); -useInterval(tick, 60000, { +useInterval(tick, Math.max(10000, widgetProps.refreshIntervalSec * 1000), { immediate: true, afterMounted: true, }); @@ -104,17 +102,32 @@ defineExpose<WidgetComponentExpose>({ </script> <style lang="scss" scoped> +.change-enter-active, .change-leave-active { + position: absolute; + top: 0; + transition: all 1s ease; +} +.change-enter-from { + opacity: 0; + transform: translateY(-100%); +} +.change-leave-to { + opacity: 0; + transform: translateY(100%); +} + .ekmkgxbk { > .feed { padding: 0; font-size: 0.9em; + line-height: 42px; + height: 42px; ::v-deep(.item) { display: inline-flex; align-items: center; vertical-align: bottom; color: var(--fg); - margin: 12px 0; > .divider { display: inline-block;