Repository navigation
Expand file tree
/
Copy pathIndex.cshtml
More file actions
140 lines (125 loc) · 5.52 KB
/
Copy pathIndex.cshtml
File metadata and controls
140 lines (125 loc) · 5.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
@page "/"
@attribute [RenderStatic]
@{
ViewData["Title"] = "Search";
}
@await Html.PartialAsync("SrcLink","https://github.com/NetCoreApps/Xkcd/blob/master/Xkcd/Pages/Index.cshtml")
<div class="py-8">
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div id="app">
<div class="flex flex-1 flex-col overflow-hidden">
<div class="mb-8 mx-auto w-96">
<h2 class="text-center mb-4 max-w-4xl font-display text-5xl font-bold tracking-tight text-slate-800 dark:text-slate-200">
search xkcd
</h2>
<text-input v-cloak class="w-full w-prose w-100" type="search" v-model="searchTerm" placeholder="search xkcd comic titles"></text-input>
</div>
<div v-cloak>
<div v-if="!loading && hasInit" class="w-full pb-4 bg-white dark:bg-black border border-black flex flex-wrap">
<div v-if="comics.length" class="border-2 border-slate-700 ml-4 mt-4 p-4 flex justify-center items-center hover:shadow-lg hover:bg-slate-50 dark:hover:bg-slate-900 max-w-[48%]" v-for="comic in comics">
<div v-on:click="showModal(comic)" class="cursor-pointer">
<h2 class="mb-4 text-lg font-semibold text-gray-900 dark:text-gray-100 text-center">{{ comic.title }}</h2>
<img :src="comic.imageUrl" :width="comic.width" :height="comic.height" class="h-48 object-cover" :aria-description="comic.explanation" :alt="comic.transcript" loading="lazy">
</div>
</div>
<div v-else class="w-full">
<h4 class="text-center text-lg pt-8 pb-4">query returned no results</h4>
</div>
</div>
<div v-else class="flex justify-center items-center pt-8">
<loading>searching xkcd...</loading>
</div>
</div>
<modal-comic v-if="selected" :comic="selected" v-on:done="done"></modal-comic>
</div>
</div>
</div>
</div>
<script type="module">
import { ref, watch, onMounted } from "vue"
import { mount } from "app.mjs"
import { QueryXkcdComics } from "dtos.mjs"
import { useClient, useUtils } from "@@servicestack/vue"
import { queryString } from "@@servicestack/client"
const App = {
setup(props) {
const comics = ref(props.comics || [])
const qs = queryString(location.search)
const searchTerm = ref(qs.q || '')
const client = useClient()
const { pushState } = useUtils()
const selected = ref()
const loading = ref(false)
const hasInit = ref(false)
onMounted(async () => {
if (qs.q) {
let api = await client.api(new QueryXkcdComics({ titleContains:searchTerm.value, orderByDesc:'id' }))
comics.value = api.response.results
} else {
await init()
}
if (qs.id) {
showModal(comics.value.find(x => x.id == qs.id))
}
hasInit.value = true
})
async function init() {
loading.value = true;
let randomIds = generateRandomNumbers(1,2630,12)
if (qs.id) randomIds.unshift(parseInt(qs.id))
let results = await client.api(new QueryXkcdComics({ ids:randomIds }))
comics.value = results.response.results
loading.value = false
}
const searchApi = createDebounce(async (query) => {
if (!query || query.length === 0) {
pushState({ q: undefined })
let randomIds = generateRandomNumbers(1,2630,12);
let api = await client.api(new QueryXkcdComics({ ids:randomIds }))
if (api.succeeded) {
comics.value = api.response.results
}
} else {
pushState({ q: searchTerm.value })
await (async (titleContains) => {
let api = await client.api(new QueryXkcdComics({ titleContains, orderByDesc:'id' }))
// discard any invalidated api responses
if (titleContains === searchTerm.value) {
comics.value = api.response.results
}
})(searchTerm.value)
}
loading.value = false
},250)
watch(searchTerm, async(newValue,oldValue) => {
loading.value = true
searchApi(newValue)
})
function createDebounce(fnc, delayMs) {
let timeout = null;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => {
fnc(...args);
}, delayMs || 500);
};
}
function showModal(comic) {
selected.value = comic;
pushState({ id:comic?.id || undefined })
}
function done() {
showModal(null)
}
function generateRandomNumbers(low, high, count) {
const result = [];
for (let i = 0; i < count; i++) {
result.push(Math.floor(Math.random() * (high - low + 1) + low));
}
return result;
}
return { comics, searchTerm, hasInit, loading, selected, showModal, done }
},
}
mount('#app', App)
</script>