Cartella con file per la frontend, un makefile per costruire tutto e lista utenti con fuzzy search

main-old
Antonio De Lucreziis 3 years ago
parent 91998e8924
commit 58db3e3901

7
.gitignore vendored

@ -6,3 +6,10 @@ tags
# Local files # Local files
*.local* *.local*
# NodeJS
dist/
node_modules/
# Don't version generated files
public/js/*.min.js

@ -0,0 +1,36 @@
JS_SOURCES = $(wildcard frontend/src/*.js)
JS_OUTPUTS = $(patsubst frontend/src/%.js, public/js/%.min.js, $(JS_SOURCES))
SERVER_EXECUTABLE = phc-website-server
.PHONY: all
all: js go
#
# Build Frontend
#
.PHONY: js
js: $(JS_OUTPUTS)
@echo "Compiled Frontend"
public/js/%.min.js: frontend/src/%.js
cd frontend; rollup -c rollup.config.js
cp $(patsubst frontend/src/%.js, frontend/dist/%.min.js, $<) $@
#
# Build Server
#
.PHONY: go
go: $(SERVER_EXECUTABLE)
@echo "Compiled Server"
$(SERVER_EXECUTABLE):
go build -o $(SERVER_EXECUTABLE) .
.PHONY: debug
debug:
@echo "JS_SOURCES = $(JS_SOURCES)"
@echo "JS_OUTPUTS = $(JS_OUTPUTS)"
@echo "SERVER_EXECUTABLE = $(SERVER_EXECUTABLE)"

File diff suppressed because it is too large Load Diff

@ -0,0 +1,21 @@
{
"name": "frontend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@rollup/plugin-node-resolve": "^13.3.0",
"rollup": "^2.75.3",
"rollup-plugin-terser": "^7.0.2"
},
"dependencies": {
"alpinejs": "^3.10.2",
"fuse.js": "^6.6.2"
}
}

@ -0,0 +1,15 @@
import { defineConfig } from 'rollup'
import { nodeResolve } from '@rollup/plugin-node-resolve'
import { terser } from 'rollup-plugin-terser'
export default defineConfig([
{
input: 'src/utenti.js',
output: {
file: 'dist/utenti.min.js',
format: 'es',
},
plugins: [terser(), nodeResolve()],
},
])

@ -0,0 +1,82 @@
import Alpine from 'alpinejs'
import Fuse from 'fuse.js'
const SHOW_MORE_INCREMENT = 15
const FUSE_OPTIONS = {
includeScore: true,
keys: [
'nome',
'cognome',
{ name: 'nomeCompleto', getFn: user => `${user.nome} ${user.cognome}` },
],
}
const SORT_MODES = {
chronological: () => 0,
name: (a, b) => (a.nome < b.nome ? -1 : 1),
surname: (a, b) => (a.cognome < b.cognome ? -1 : 1),
}
function getSortedUserList(original, mode) {
return [...original].sort(SORT_MODES[mode])
}
Alpine.data('utenti', () => ({
searchField: '', // two-way binding for the search input field
sortMode: 'chronological', // two-way binding for the sorting mode
fetchedUsers: [], // hold complete user list
sortedUserBuffer: [], // Yet another buffer of the user list for the sort mode
fuse: new Fuse([], FUSE_OPTIONS), // current fuse instance, used to filter the list above
searchResultsBuffer: [], // stores the full current search
searchResults: [], // list to renderer on screen with a subset of the whole search results buffer
async init() {
// Get user list from server
const response = await fetch('/api/utenti')
this.fetchedUsers = await response.json()
// This will call the function "showMore()" when the user is near the end of the list
new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log('Near the bottom of the page')
this.showMore()
}
})
}).observe(this.$refs.spinner)
// Initialize with an empty query
this.updateSortMode()
this.updateSearch()
},
showMore() {
// setTimeout(() => {
// Updates the final "searchResults" list with more items from the previous buffer
const newCount = this.searchResults.length + SHOW_MORE_INCREMENT
this.searchResults = this.searchResultsBuffer.slice(0, newCount)
// }, 250) // For fun
},
setResults(list) {
this.searchResultsBuffer = list.filter(
entry => entry.score === undefined || entry.score <= 0.25
)
this.searchResults = this.searchResultsBuffer.slice(0, SHOW_MORE_INCREMENT)
},
updateSortMode() {
this.sortedUserBuffer = getSortedUserList(this.fetchedUsers, this.sortMode)
this.fuse.setCollection(this.sortedUserBuffer)
this.updateSearch()
},
updateSearch() {
console.time('search')
if (this.searchField.trim().length === 0) {
// Reset the result list
this.setResults(this.sortedUserBuffer.map(user => ({ item: user })))
} else {
// Update the result list with the new results
this.setResults(this.fuse.search(this.searchField))
}
console.timeEnd('search')
},
}))
Alpine.start()

@ -1,3 +0,0 @@
// window.addEventListener('DOMContentLoaded', () => {
// })

@ -713,11 +713,34 @@ form .field-set input {
} }
.page-utenti .user-item .icon { .page-utenti .user-item .icon {
width: 1.5rem; width: 1.75rem;
display: grid; display: grid;
place-content: center; place-content: center;
} }
.page-utenti .spinner {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 5rem;
color: var(--fg);
animation: rotate 1s linear infinite;
}
@keyframes rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.search { .search {
margin: 2rem 0; margin: 2rem 0;
display: flex; display: flex;

@ -6,7 +6,7 @@ import (
) )
type UserInfo struct { type UserInfo struct {
Uid string `string:"uid"` Uid string `json:"uid"`
Nome string `json:"nome"` Nome string `json:"nome"`
Cognome string `json:"cognome"` Cognome string `json:"cognome"`
IsMacchinista bool `json:"macchinista,omitempty"` IsMacchinista bool `json:"macchinista,omitempty"`

@ -1,5 +1,5 @@
{{template "base" .}} {{define "title"}}Utenti &bull; PHC{{end}} {{define "body"}} {{template "base" .}} {{define "title"}}Utenti &bull; PHC{{end}} {{define "body"}}
<section> <section x-data="utenti">
<h2> <h2>
<i class="fas fa-users"></i> <i class="fas fa-users"></i>
Lista degli Utenti Lista degli Utenti
@ -11,43 +11,47 @@
</p> </p>
<div class="search"> <div class="search">
<div class="compound padded"> <div class="compound padded">
<div class="icon"> <div class="icon" title="Ordina per">
<i class="fas fa-sort"></i> <i class="fas fa-sort"></i>
</div> </div>
<div class="divider"></div> <div class="divider"></div>
<select> <select x-model="sortMode" @click="updateSortMode()">
<option value="name">Cronologico</option> <option value="chronological">Cronologico</option>
<option value="name">Nome</option> <option value="name">Nome</option>
<option value="name">Cognome</option> <option value="surname">Cognome</option>
</select> </select>
</div> </div>
<div class="compound"> <div class="compound">
<input type="text" id="search-field" placeholder="Cerca..." autocomplete="off" /> <input type="text" x-model="searchField" @input="updateSearch()" placeholder="Cerca..." autocomplete="off" />
<button class="icon"> <button class="icon" title="In realtà questo tasto è finto">
<i class="fas fa-search"></i> <i class="fas fa-search"></i>
</button> </button>
</div> </div>
</div> </div>
<div class="user-list card-list"> <div class="user-list card-list">
{{ range .Utenti }} <template x-for="entry in searchResults" :key="entry.item.uid">
<div class="user-item card"> <div class="user-item card">
<a class="full-name" href="{{ $.UserPagesBaseUrl }}{{ .Uid }}"> <a class="full-name" :href="'{{ $.Config.UserPagesBaseUrl }}' + entry.item.uid" x-text="
{{ .Nome }} {{ .Cognome }} sortMode === 'surname' ?
</a> entry.item.cognome + ' ' + entry.item.nome :
{{ if .IsMacchinista }} entry.item.nome + ' ' + entry.item.cognome
<div class="icon" title="Macchinista"> "></a>
<i class="fas fa-wrench"></i> <template x-if="entry.item.macchinista">
<div class="icon" title="Macchinista">
<i class="fas fa-wrench"></i>
</div>
</template>
<!-- <template x-if="entry.score">
<span x-text="entry.score"></span>
</template> -->
</div> </div>
{{ end }} </template>
<div class="spinner" x-ref="spinner" x-show="searchResults.length < searchResultsBuffer.length">
<i class="fas fa-hourglass"></i>
</div> </div>
{{ end }}
</div> </div>
</section> </section>
<!-- <script type="module" src="/public/js/utenti.min.js"></script>
<script src="//unpkg.com/alpinejs" defer></script>
<script src="https://cdn.jsdelivr.net/npm/fuse.js/dist/fuse.js" defer></script>
<script type="module" src="/public/js/utenti.js"></script>
-->
{{end}} {{end}}

Loading…
Cancel
Save