Skip to content

Commit 84f488d

Browse files
authored
Merge pull request #24 from alkrauss48/issue-23/feat/looping
Implements front-end looping via query param
2 parents 7279c83 + c241279 commit 84f488d

File tree

7 files changed

+145
-7
lines changed

7 files changed

+145
-7
lines changed

Diff for: app/Http/Middleware/HandleInertiaRequests.php

+2
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,13 @@ public function share(Request $request): array
3232
{
3333
$index = intval(request()->input('index'));
3434
$progress = request()->input('progress');
35+
$loop = intval(request()->input('loop'));
3536

3637
return [
3738
...parent::share($request),
3839
'index' => $index,
3940
'progress' => $progress,
41+
'loop' => $loop,
4042
'auth' => [
4143
'user' => $request->user(),
4244
],

Diff for: resources/js/Components/SlideView.vue

+35-5
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ import Keys from '@/constants/keys.ts';
1111
import dataStore from '@/store/dataStore.ts'
1212
import slideStore from '@/store/slideStore.ts'
1313
14-
const content = computed(() => {
15-
return dataStore.data[slideStore.index];
16-
});
17-
14+
let loopInterval: null | ReturnType<typeof setInterval> = null;
1815
let fontLoadInterval: null | ReturnType<typeof setInterval> = null;
1916
const fontLoaded = ref(false);
2017
const FONT = '16px Montserrat';
2118
19+
const content = computed(() => {
20+
return dataStore.data[slideStore.index];
21+
});
22+
2223
const showProgressLabel = computed<boolean>(() => {
2324
return slideStore.progress === ProgressType.Label;
2425
});
@@ -35,6 +36,14 @@ const buildQueryParams = () : QueryParams => {
3536
return query;
3637
};
3738
39+
const checkAndClearLoopInterval = () : void => {
40+
if (!loopInterval) {
41+
return;
42+
}
43+
44+
clearInterval(Number(loopInterval));
45+
};
46+
3847
const incrementContent = (count: number) : void => {
3948
slideStore.increment(count);
4049
@@ -58,6 +67,10 @@ const bindKeyDown = (event: KeyboardEvent): void => {
5867
}
5968
}
6069
70+
if (Keys.ALL_APP_KEYS.includes(key)) {
71+
checkAndClearLoopInterval();
72+
}
73+
6174
if (Keys.INCREMENTORS.includes(key)) {
6275
incrementContent(1);
6376
} else if (Keys.DECREMENTORS.includes(key)) {
@@ -86,8 +99,25 @@ onMounted(() => {
8699
fontLoaded.value = true;
87100
clearInterval(Number(fontLoadInterval));
88101
}, 50);
102+
103+
if (!slideStore.canLoop()) {
104+
return;
105+
}
106+
107+
loopInterval = setInterval(() => {
108+
if (slideStore.isEnd()) {
109+
incrementContent(-1 * dataStore.data.length);
110+
return;
111+
}
112+
113+
incrementContent(1)
114+
}, slideStore.loop * 1000);
115+
});
116+
117+
onUnmounted(() => {
118+
window.removeEventListener('keydown', bindKeyDown);
119+
checkAndClearLoopInterval();
89120
});
90-
onUnmounted(() => window.removeEventListener('keydown', bindKeyDown));
91121
</script>
92122

93123
<template>

Diff for: resources/js/Pages/Slides.vue

+2
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ import slideStore from '@/store/slideStore.ts'
1212
1313
const props = defineProps<{
1414
index?: number,
15+
loop?: number,
1516
progress?: ProgressType,
1617
slides?: string
1718
content?: string
1819
}>();
1920
2021
const processQueryParams = (): void => {
2122
slideStore.index = props.index ?? 0;
23+
slideStore.loop = props.loop ?? 0;
2224
slideStore.progress = props.progress ?? ProgressType.Bar;
2325
};
2426

Diff for: resources/js/constants/keys.ts

+10
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ export const LARGE_DECREMENTORS = [
3434
'B',
3535
];
3636

37+
export const ALL_APP_KEYS = [
38+
...INCREMENTORS,
39+
...DECREMENTORS,
40+
...LARGE_INCREMENTORS,
41+
...LARGE_DECREMENTORS,
42+
DOLLAR_SIGN,
43+
ZERO,
44+
];
45+
3746
export default {
3847
ENTER,
3948
SPACE,
@@ -43,4 +52,5 @@ export default {
4352
DECREMENTORS,
4453
LARGE_INCREMENTORS,
4554
LARGE_DECREMENTORS,
55+
ALL_APP_KEYS
4656
};

Diff for: resources/js/store/slideStore.ts

+13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import dataStore from './dataStore.ts'
55

66
const slideStore = reactive({
77
index: 0,
8+
loop: 0,
89
progress: ProgressType.Bar,
910

1011
getNewIndex(count: number) : number {
@@ -29,6 +30,18 @@ const slideStore = reactive({
2930
slideStore.index = newIndex;
3031
},
3132

33+
isEnd() : boolean {
34+
return this.index === dataStore.data.length - 1;
35+
},
36+
37+
canLoop() : boolean {
38+
if (this.loop < 2) {
39+
return false;
40+
}
41+
42+
return true;
43+
},
44+
3245
reset() {
3346
this.index = 0;
3447
this.progress = ProgressType.Bar;

Diff for: resources/js/test/components/SlideView.loop.test.ts

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { mount, VueWrapper } from '@vue/test-utils'
2+
3+
import SlideView from '@/Components/SlideView.vue'
4+
import Keys from '@/constants/keys.ts';
5+
import ProgressType from '@/enums/progressType.ts'
6+
import dataStore from '@/store/dataStore.ts'
7+
import slideStore from '@/store/slideStore.ts'
8+
9+
// Incrementors
10+
11+
const mountWrapper = () : VueWrapper<any> => {
12+
dataStore.data = ['foo', 'bar', 'baz', 'foo', 'bar', 'baz', 'foo', 'bar'];
13+
slideStore.index = 1;
14+
slideStore.loop = 5;
15+
slideStore.progress = ProgressType.Bar;
16+
17+
return mount(SlideView);
18+
};
19+
20+
test('loopInterval is not null with valid loop set', async () => {
21+
const wrapper = mountWrapper();
22+
23+
expect(wrapper.vm.loopInterval).not.toBe(null);
24+
});
25+
26+
test('loopInterval is cleared with valid loop set', async () => {
27+
const wrapper = mountWrapper();
28+
const spy = vi.spyOn(global, 'clearInterval');
29+
30+
wrapper.vm.checkAndClearLoopInterval();
31+
32+
expect(spy).toHaveBeenCalledTimes(1);
33+
expect(spy.mock.lastCall?.[0]).toBe(Number(wrapper.vm.loopInterval));
34+
});
35+
36+
test('loopInterval is cleared on valid key press', async () => {
37+
const wrapper = mountWrapper();
38+
const spy = vi.spyOn(global, 'clearInterval');
39+
40+
wrapper.vm.bindKeyDown({ key: Keys.ENTER });
41+
42+
expect(spy).toHaveBeenCalledTimes(1);
43+
expect(spy.mock.lastCall?.[0]).toBe(Number(wrapper.vm.loopInterval));
44+
});
45+
46+
test('loopInterval is not cleared on invalid key press', async () => {
47+
const wrapper = mountWrapper();
48+
const spy = vi.spyOn(global, 'clearInterval');
49+
50+
wrapper.vm.bindKeyDown({ key: 'x' });
51+
52+
expect(spy).toHaveBeenCalledTimes(0);
53+
});

Diff for: resources/js/test/store/slideStore.test.ts

+30-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import ProgressType from '@/enums/progressType.ts'
2+
import dataStore from '@/store/dataStore.ts'
23
import slideStore from '@/store/slideStore.ts'
34

45
afterEach(() => {
5-
slideStore.reset()
6-
})
6+
dataStore.reset();
7+
slideStore.reset();
8+
});
79

810
test('gets the right initial values', async () => {
911
expect(slideStore.index).toBe(0);
@@ -30,3 +32,29 @@ test('will not change slide store if no new index', async () => {
3032

3133
expect(slideStore.index).toBe(0);
3234
});
35+
36+
test('returns true for isEnd if slide index is at the end', async () => {
37+
dataStore.data = ['a', 'b', 'c'];
38+
slideStore.index = 2;
39+
40+
expect(slideStore.isEnd()).toBe(true);
41+
});
42+
43+
test('returns false for isEnd if slide index is not at the end', async () => {
44+
dataStore.data = ['a', 'b', 'c'];
45+
slideStore.index = 1;
46+
47+
expect(slideStore.isEnd()).toBe(false);
48+
});
49+
50+
test('returns true for canLoop if loop value is valid', async () => {
51+
slideStore.loop = 5;
52+
53+
expect(slideStore.canLoop()).toBe(true);
54+
});
55+
56+
test('returns false for canLoop if loop value is invalid', async () => {
57+
slideStore.loop = 1;
58+
59+
expect(slideStore.canLoop()).toBe(false);
60+
});

0 commit comments

Comments
 (0)