Septième et dernier article sur LoFiDroX: les composants svelte.
Composant Svelte: structure et fonctionnement#
Un composant Svelte est divisé en 3 parties obligatoires (qui peuvent rester vides):
- style: on y définit les styles CSS, soit directement soit en les incluant par
@import
. - script: dans cette section, on ajoute toute la logique de l’écran.
- enfin on ajoute le code HTML qui sera affiché par le composant.
Note: les composants sont autonomes et n’ont pas connaissance des états des autres composants.
Composant cmpLfdError.svelte#
Ce composant affiche une page d’erreur et permet de revenir à la page de login.
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
| <style>
@import "../../frameworks/bootstrap/css/bootstrap.min.css";
@import "../css/lofidrox.css";
</style>
<script>
import GtgUtilsWeb from '../../frameworks/gtg-svelte/gtg-utils-web.js';
import LfdUrls from '../js/lfdUrlManager.js';
function goLogin() {
GtgUtilsWeb.route(LfdUrls.spa_user_login);
};
</script>
<main class="container">
<section class="h-100 lfd-gradient-form">
<div class="container py-2 h-100">
<div class="row d-flex justify-content-center align-items-center h-100">
<div class="col-xl-10">
<div class="card rounded-3 text-black">
<div class="row g-0">
<div class="col-md-6">
<div class="card-body p-md-5 mx-md-4">
<div class="text-center">
<img src="/lofidrox/img/lofidrox.png" alt="LoFiDroX" class="lfd-logo">
<h4 class="mt-1 mb-5 pb-1"></h4>
</div>
<form>
<p class="bg-danger text-white px-3 py-4">
An error occured. You cannot access this page. Please continue to <a class="text-white" on:click="{goLogin}">login page</a>.
</p>
</form>
</div>
</div>
<div class="col-md-6 d-flex align-items-center lfd-gradient-colors">
<img src="/lofidrox/img/error_error.png" class="lfd-image-illu" alt="Error">
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</main>
|
Composant cmpLfdFileInbox.svelte#
Ce composant affiche les fichiers reçus par l’utilisateur et permet leur téléchargement. La difficulté est de télécharger les fichiers un par un (la plupart des autres logiciels du même type imposent un téléchargement groupé).
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
| <style>
@import "../../frameworks/bootstrap/css/bootstrap.min.css";
@import "../css/lofidrox.css";
</style>
<script>
import GtgUtilsTools from '../../frameworks/gtg-svelte/gtg-utils-tooling.js';
import GtgUtilsDates from '../../frameworks/gtg-svelte/gtg-utils-dates.js';
import GtgUtilsWeb from '../../frameworks/gtg-svelte/gtg-utils-web.js';
import LfdUrls from '../js/lfdUrlManager.js';
let hasFiles = false;
let files = [ ];
let selectedFiles = [ ];
let hasSelectionDown = true;
let hasSelectionDel = true;
function processNextFile(index) {
let dataToSend;
if (index < selectedFiles.length) {
dataToSend = { id: selectedFiles[index] };
GtgUtilsWeb.postJson(LfdUrls.crud_file_down, dataToSend, function(data) {
if (!GtgUtilsTools.isNull(data)) {
GtgUtilsWeb.download(data.data, data.filename, data.type);
processNextFile(++index);
}
}, function(error) {
GtgUtilsWeb.route(LfdUrls.spa_error);
} );
} else {
// erase selection
selectedFiles = [ ];
hasSelectionDown = true;
// refresh list
doRefresh();
}
};
function doDownload() {
hasSelectionDown = ((null != selectedFiles) && (0 < selectedFiles.length));
if (hasSelectionDown) {
processNextFile(0);
}
};
function doRefresh() {
fetchFiles();
};
function doDelete() {
let dataToSend = { files: null };
hasSelectionDel = ((null != selectedFiles) && (0 < selectedFiles.length));
if (hasSelectionDel) {
dataToSend.files = selectedFiles;
GtgUtilsWeb.deleteJson(LfdUrls.crud_file_del, dataToSend, function(data) {
if (!GtgUtilsTools.isNull(data)) {
fetchFiles();
}
}, function(error) {
GtgUtilsWeb.route(LfdUrls.spa_error);
} );
}
};
function fetchFiles() {
files = [ ];
GtgUtilsWeb.postJson(LfdUrls.crud_file_list, null, function(data) {
if (!GtgUtilsTools.isNull(data)) {
files = data.files;
hasFiles = (!GtgUtilsTools.isNull(data.files) && (0 < data.files.length));
clearSelection();
}
}, function(error) {
GtgUtilsWeb.route(LfdUrls.spa_error);
} );
};
function clearSelection() {
selectedFiles = [ ];
hasSelectionDown = true;
hasSelectionDel = true;
};
fetchFiles();
</script>
<main>
<div class="row">
<div class="col mx-1 my-2">
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<span class="navbar-brand text-warning lfd-title">Inbox</span>
<div>
<a class="btn btn-sm btn-outline-danger" on:click={doDelete}><i class="mdi mdi-delete-outline"></i></a>
<span class="text-secondary"> </span><a class="btn btn-sm btn-outline-info" on:click={doRefresh}>Refresh <i class="mdi mdi-refresh"></i></a>
<span class="text-secondary"> </span><a class="btn btn-sm btn-outline-primary" on:click={doDownload}>Download <i class="mdi mdi-download"></i></a>
</div>
</div>
</nav>
</div>
</div>
<div class="row">
<div class="col mx-1 my-2">
<div class="lfd-files-list">
{#if !hasFiles }
<p class="bg-warning text-black px-1 py-1">You have no file to download</p>
{/if}
{#if !hasSelectionDown }
<p class="bg-danger text-white px-1 py-1">You must choose as least one file to download</p>
{/if}
{#if !hasSelectionDel }
<p class="bg-danger text-white px-1 py-1">You must choose as least one file to delete</p>
{/if}
<div class="lfd-card-list-content">
<div class="content">
<div class="row lfd-files-header">
<div class="col-sm-8 text-warning">Filename</div>
<div class="col-sm-4 text-warning">Sent by / on</div>
</div>
{#if files}
<div class="row lfd-files-list-data">
{#each files as file, idx}
<div class="col-sm-8 lfd-row-color-{idx % 2} text-white">
<input type="checkbox" name="user" value="{file.id}" bind:group={selectedFiles} />
{#if file.isNew}<i class="mdi mdi-alert-decagram text-danger"></i>{/if}
{file.filename}
</div>
<div class="col-sm-4 lfd-row-color-{idx % 2} text-white">{file.sentBy}, {file.sentOn}</div>
{/each}
</div>
{/if}
</div>
</div>
</div>
</div>
</div>
</main>
|
Composant cmpLfdFileUpload.svelte#
Ce composant permet d’envoyer un ou plusieurs fichiers à un ou plusieurs utilisateurs. Comme lors du téléchargement, l’envoi au serveur est réalisé fichier par fichier.
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
| <style>
@import "../../frameworks/bootstrap/css/bootstrap.min.css";
@import "../css/lofidrox.css";
</style>
<script>
import GtgUtilsTools from '../../frameworks/gtg-svelte/gtg-utils-tooling.js';
import GtgUtilsWeb from '../../frameworks/gtg-svelte/gtg-utils-web.js';
import LfdUrls from '../js/lfdUrlManager.js';
let files = null;
let filesDsp = null;
let users = null;
let selectedUsers = [ ];
let hasUsers = true;
let hasFiles = true;
function doProcess() {
hasUsers = (0 !== selectedUsers.length);
hasFiles = (GtgUtilsTools.isArray(filesDsp) && (0 !== filesDsp.length));
if (hasUsers && hasFiles) {
processNextFile(0);
}
};
function handleFileUploadChange() {
filesDsp = [];
if (0 < files.length) {
for (let pos = 0; pos < files.length; pos++) {
filesDsp = [...filesDsp, { name: files[pos].name, uploaded: false, uploading: false } ];
}
}
};
function processNextFile(index) {
let fileRead;
if (index < files.length) {
filesDsp[index] = { name: filesDsp[index].name, uploaded: false, uploading: true };
fileRead = new FileReader();
fileRead.addEventListener("load", function(evt) {
let dataToSend = {
filename: files[index].name,
users: selectedUsers,
data: fileRead.result
};
GtgUtilsWeb.postJson(LfdUrls.crud_file_send, dataToSend, function(data) {
if (!GtgUtilsTools.isNull(data) && data.written) {
filesDsp[index] = { name: filesDsp[index].name, uploaded: true, uploading: false };
processNextFile(++index);
}
}, function(error) {
GtgUtilsWeb.route(LfdUrls.spa_error);
} );
}, false);
fileRead.readAsDataURL(files[index]);
}
};
function fetchUsers() {
GtgUtilsWeb.postJson(LfdUrls.crud_user_list, null, function(data) {
if (!GtgUtilsTools.isNull(data)) {
users = data.users;
}
}, function(error) {
GtgUtilsWeb.route(LfdUrls.spa_error);
} );
};
function doClearFiles() {
filesDsp = [ ];
hasFiles = true;
};
function doClearUsers() {
selectedUsers = [ ];
hasUsers = true;
};
fetchUsers();
</script>
<main>
<div class="row">
<div class="col mx-1 my-2">
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<span class="navbar-brand text-warning lfd-title">Upload</span>
</div>
</nav>
</div>
</div>
<div class="row">
<div class="col-lg-4">
<div class="row">
<div class="col mx-1 my-2">
<nav class="navbar navbar-dark bg-dark lfd-files-header">
<div class="container-fluid">
<span class="navbar-brand text-warning">Users available</span>
<div>
<div>
<a class="btn btn-sm btn-outline-secondary float-end" on:click={doClearUsers}>Clear</a>
</div>
</div>
</div>
</nav>
</div>
</div>
<div class="row">
<div class="col mx-1 my-2">
<div class="lfd-files-list">
{#if !hasUsers }
<p class="bg-danger text-white px-1 py-1">You must choose as least one user</p>
{/if}
<div class="lfd-files-list-content">
<div class="content">
{#if users}
{#each users as name}
<div class="is-size-7">
<input type="checkbox" name="user" value="{name}" bind:group={selectedUsers} />
<span class="text-white">{name}</span>
</div>
{/each}
{/if}
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-8">
<div class="row">
<div class="col mx-1 my-2">
<nav class="navbar navbar-dark bg-dark lfd-files-header">
<div class="container-fluid">
<span class="navbar-brand text-warning">Files to send</span>
<div>
<div>
<a class="btn btn-sm btn-outline-secondary" on:click={doClearFiles}>Clear</a>
<span class="text-secondary"> </span>
<a class="btn btn-sm btn-outline-primary float-end" on:click={doProcess}>Send <i class="mdi mdi-file-send"></i></a>
</div>
</div>
</div>
</nav>
</div>
</div>
<div class="row">
<div class="col mx-1 my-2">
<div class="lfd-files-list">
{#if !hasFiles }
<p class="bg-danger text-white px-1 py-1">You must choose as least one file</p>
{/if}
<div class="lfd-files-list-content">
<div class="content">
<input id="fileUpload" type="file" multiple bind:files on:change={handleFileUploadChange} />
{#if filesDsp}
{#each filesDsp as send}
<div class="is-size-7">
{#if !send.uploading && !send.uploaded}
<span class="icon text-danger"><i class="mdi mdi-timer-sand"></i></span>
{/if}
{#if send.uploading}
<span class="icon text-primary"><i class="mdi mdi-progress-clock"></i></span>
{/if}
{#if send.uploaded}
<span class="icon text-success"><i class="mdi mdi-check-circle-outline"></i></span>
{/if}
<span class="text-white">{send.name}</span>
</div>
{/each}
{/if}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
|
Composant cmpLfdHome.svelte#
Ce composant affiche une page d’accueil proposant d’accéder aux fichiers reçus, à l’envoi de fichiers ainsi qu’à l’écran de déconnexion.
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
| <style>
@import "../../frameworks/bootstrap/css/bootstrap.min.css";
@import "../css/lofidrox.css";
</style>
<script>
import GtgUtilsWeb from '../../frameworks/gtg-svelte/gtg-utils-web.js';
import LfdUrls from '../js/lfdUrlManager.js';
function goInbox() {
GtgUtilsWeb.route(LfdUrls.spa_file_inbox);
};
function goUpload() {
GtgUtilsWeb.route(LfdUrls.spa_file_upload);
};
function goLogout() {
GtgUtilsWeb.route(LfdUrls.spa_user_logout);
};
</script>
<main>
<div class="row">
<div class="col-lg-12 text-center"> </div>
</div>
<div class="row">
<div class="col-lg-12 text-center">
<h3 class="text-white">
Welcome on <span class="lfd-title text-warning">LoFiDroX</span>
</h3>
</div>
</div>
<div class="row">
<div class="col-lg-12 text-center"> </div>
</div>
<div class="row gx-5 text-center">
<div class="col-lg-4 d-flex align-items-center justify-content-center">
<div class="card" style="width: 18rem;">
<img src="/lofidrox/img/home_inbox.png" class="card-img-top" alt="Inbox">
<div class="card-body">
<h5 class="card-title">Inbox</h5>
<p class="card-text">To consult files you received</p>
<a class="btn btn-sm btn-outline-primary" on:click={goInbox} >Inbox</a>
</div>
</div>
</div>
<div class="col-lg-4 d-flex align-items-center justify-content-center">
<div class="card" style="width: 18rem;">
<img src="/lofidrox/img/home_upload.png" class="card-img-top" alt="Upload">
<div class="card-body">
<h5 class="card-title">Upload</h5>
<p class="card-text">To send files to other users</p>
<a class="btn btn-sm btn-outline-primary" on:click={goUpload}>Upload</a>
</div>
</div>
</div>
<div class="col-lg-4 d-flex align-items-center justify-content-center">
<div class="card" style="width: 18rem;">
<img src="/lofidrox/img/home_logout.png" class="card-img-top" alt="Logout">
<div class="card-body">
<h5 class="card-title">Logout</h5>
<p class="card-text">To quit <span class="lfd-app-name">LoFiDroX</span></p>
<a class="text-danger btn btn-sm btn-outline-danger" on:click={goLogout} >Logout</a>
</div>
</div>
</div>
</div>
</main>
|
Composant cmpLfdTitle.svelte#
Il s’agit du bandeau de titre avec les boutons permettant d’accéder aux fonctions principales (fichiers reçus, envoi de fichiers, déconnexion).
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
| <style>
@import "../../frameworks/bootstrap/css/bootstrap.min.css";
@import "../css/lofidrox.css";
</style>
<script>
import { lfdStore } from '../js/lfdStore.js';
import GtgUtilsWeb from '../../frameworks/gtg-svelte/gtg-utils-web.js';
import LfdUrls from '../js/lfdUrlManager.js';
let loggedIn = false;
$: loggedIn = $lfdStore.loggedIn;
function goHome() {
GtgUtilsWeb.route(LfdUrls.spa_home);
};
function goInbox() {
GtgUtilsWeb.route(LfdUrls.spa_file_inbox);
};
function goUpload() {
GtgUtilsWeb.route(LfdUrls.spa_file_upload);
};
function goLogout() {
GtgUtilsWeb.route(LfdUrls.spa_user_logout);
};
</script>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark lfd-nav-bar">
<div class="container-fluid">
{#if loggedIn}
<a on:click={goHome} ><img src="/lofidrox/img/lofidrox_small.png"/></a>
{:else}
<img src="/lofidrox/img/lofidrox_small.png"/>
{/if}
<span class="navbar-brand lfd-title">LoFiDroX</span>
<div>
<div>
{#if loggedIn}
<a class="text-secondary btn btn-sm btn-outline-secondary" on:click={goHome} >Home</a>
<span class="text-secondary"> </span>
<a class="text-white btn btn-sm btn-outline-light" on:click={goInbox} >Inbox</a>
<span class="text-secondary"> </span>
<a class="text-white btn btn-sm btn-outline-light" on:click={goUpload} >Upload</a>
<span class="text-secondary"> </span>
<a class="text-danger btn btn-sm btn-outline-danger" on:click={goLogout} >Logout</a>
{/if}
</div>
</div>
</div>
</nav>
|
Composant cmpLfdUserLogin.svelte#
Ce composant est l’écran de connexion. Il intègre le contrôle des champs via le framework Yup.
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
| <style>
@import "../../frameworks/bootstrap/css/bootstrap.min.css";
@import "../css/lofidrox.css";
</style>
<script>
import { navigate } from "svelte-routing";
import { onMount } from 'svelte';
import * as yup from 'yup';
import GtgUtilsTools from '../../frameworks/gtg-svelte/gtg-utils-tooling.js';
import GtgUtilsPassword from '../../frameworks/gtg-svelte/gtg-utils-password.js';
import GtgUtilsWeb from '../../frameworks/gtg-svelte/gtg-utils-web.js';
import LfdUrls from '../js/lfdUrlManager.js';
import LfdEvents from '../js/lfdEventsManager.js';
import LfdLocalStore from '../js/lfdLocalStore.js';
import { lfdStore } from '../js/lfdStore.js';
let fields = { fieldUsr: null, fieldPwd: null };
let values = { username: null, password: null };
let validation = { login: true };
let errorMessages = { username: null, pwd: null };
let schemaValidation = yup.object().shape( {
username: yup.string().nullable().required('You must enter a username').min(4, 'Username must contains at least ${min} letters'),
pwd: yup.string().nullable().required('You must enter a password').
min(10, 'Password must contains at least ${min} caracters').
max(256, 'Passqord must contains up to ${max} caracters long').
test('pwd-strength', 'Password must contains uppercase letter, lowercase letter, digit, special caracter',
function(value) {
return GtgUtilsPassword.checkStrongPwd(value);
} )
} );
async function login() {
try {
validation.login = true;
errorMessages = { username: null, pwd: null };
await schemaValidation.validate( { 'username': values.username, 'pwd': values.password }, { abortEarly: false } );
GtgUtilsTools.setLocalValue(LfdLocalStore.field_username, values.username);
GtgUtilsWeb.postJson(LfdUrls.crud_user_login, { 'username': values.username, 'pwd': values.password }, function(data) {
if (!GtgUtilsTools.isNull(data) && !GtgUtilsTools.isNull(data.logged) && data.logged) {
$lfdStore.loggedIn = true;
GtgUtilsWeb.route(LfdUrls.spa_home);
}
}, function(error) {
if (!GtgUtilsTools.isNull(error) && (GtgUtilsWeb.HTTP_CODES.Unauthorized === error)) {
validation.login = false;
} else {
GtgUtilsWeb.route(LfdUrls.spa_error);
}
} );
} catch (errors) {
errorMessages = GtgUtilsTools.turnYupErrorsToArray(errors);
}
};
function goRegister() {
GtgUtilsWeb.route(LfdUrls.spa_user_register);
};
values.username = window.localStorage.getItem(LfdLocalStore.field_username);
onMount(() => {
if (!GtgUtilsTools.isNull(values.username)) {
fields.fieldPwd.focus();
} else {
fields.fieldUsr.focus();
}
} );
</script>
<main class="container">
<section class="h-100 lfd-gradient-form">
<div class="container py-2 h-100">
<div class="row d-flex justify-content-center align-items-center h-100">
<div class="col-xl-10">
<div class="card rounded-3 text-black">
<div class="row g-0">
<div class="col-md-6">
<div class="card-body p-md-5 mx-md-4">
<div class="text-center">
<img src="/lofidrox/img/lofidrox.png" alt="LoFiDroX" class="lfd-logo">
<h4 class="mt-1 mb-5 pb-1"></h4>
</div>
<form on:submit|preventDefault|stopPropagation={(e)=>{login();}}>
<p>Please login to your account</p>
<div class="form-outline mb-4">
<input class="input form-control form-control-sm" tabindex="1" type="text" name="fieldUsr" placeholder="Enter your username" bind:value={values.username} bind:this={fields.fieldUsr}>
{#if errorMessages.username }
<p class="bg-danger text-white px-1 py-1">{errorMessages.username}</p>
{/if}
</div>
<div class="form-outline mb-4">
<input class="input form-control form-control-sm" tabindex="2" type="password" name="fieldPwd" placeholder="Enter your password" bind:value={values.password} bind:this={fields.fieldPwd}>
{#if errorMessages.pwd }
<p class="bg-danger text-white px-1 py-1">{errorMessages.pwd}</p>
{/if}
</div>
{#if !validation.login }
<div class="form-outline mb-4">
<p class="bg-danger text-white px-1 py-1">
You cannot login. Please check your login and password.
</p>
</div>
{/if}
<div class="text-center pt-1 mb-5 pb-1">
<button class="btn btn-primary btn-block fa-lg lfd-gradient-colors mb-3" tabindex="3" type="submit">Log in <i class="mdi mdi-login" aria-hidden="true"></i></button>
</div>
<div class="d-flex align-items-center justify-content-center pb-4">
<p class="mb-0 me-2">Don't have an account?</p>
<a type="button" class="btn btn-outline-primary" on:click="{goRegister}" tabindex="4">Create <i class="mdi mdi-account-plus"></i></a>
</div>
</form>
</div>
</div>
<div class="col-md-6 d-flex align-items-center lfd-gradient-colors">
{#if validation.login }
<div class="text-white px-3 py-4 p-md-5 mx-md-4">
<p class="small mb-0"><span class="lfd-app-name">LoFiDroX</span> let you share files over a Local Area Network.
<span class="lfd-app-name">LoFiDroX</span> don't use cloud to synchronize content: files are stored on you local
<span class="lfd-app-name">LoFiDroX</span> instance.
</p>
</div>
{:else}
<img src="/lofidrox/img/user_login_error.png" class="lfd-image-illu" alt="Error">
{/if}
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</main>
|
Composant cmpLfdUserLogout.svelte#
Ce composant affiche l’écran de déconnexion.
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
| <style>
@import "../../frameworks/bootstrap/css/bootstrap.min.css";
@import "../css/lofidrox.css";
</style>
<script>
import { navigate } from "svelte-routing";
import GtgUtilsTools from '../../frameworks/gtg-svelte/gtg-utils-tooling.js';
import GtgUtilsWeb from '../../frameworks/gtg-svelte/gtg-utils-web.js';
import LfdUrls from '../js/lfdUrlManager.js';
import LfdLocalStore from '../js/lfdLocalStore.js';
import { lfdStore } from '../js/lfdStore.js';
function logout() {
GtgUtilsWeb.deleteJson(LfdUrls.crud_user_logout, { 'username': GtgUtilsTools.getLocalValue(LfdLocalStore.field_username) }, function(data) {
if (!GtgUtilsTools.isNull(data) && !GtgUtilsTools.isNull(data.exited) && data.exited) {
$lfdStore.loggedIn = false;
GtgUtilsWeb.route(LfdUrls.spa_user_login);
}
}, function(error) {
GtgUtilsWeb.route(LfdUrls.spa_error);
} );
};
function goBack() {
GtgUtilsWeb.route(LfdUrls.spa_home);
};
</script>
<main class="container">
<section class="h-100 lfd-gradient-form">
<div class="container py-2 h-100">
<div class="row d-flex justify-content-center align-items-center h-100">
<div class="col-xl-10">
<div class="card rounded-3 text-black">
<div class="row g-0">
<div class="col-md-6">
<div class="card-body p-md-5 mx-md-4">
<div class="text-center">
<img src="/lofidrox/img/lofidrox.png" alt="LoFiDroX" class="lfd-logo">
<h4 class="mt-1 mb-5 pb-1"></h4>
</div>
<form>
<p class="bg-danger text-white px-3 py-4">Are you sure you want to exit <span class="lfd-app-name">LoFiDroX</span>?</p>
<div class="text-center pt-1 mb-5 pb-1">
<a class="btn btn-danger btn-block fa-lg mb-3" type="button" on:click="{logout}">Logout <i class="mdi mdi-logout"></i></a>
<a class="btn btn-outline-dark fa-lg mb-3" on:click="{goBack}">Cancel <i class="mdi mdi-close-circle-outline"></i></a>
</div>
</form>
</div>
</div>
<div class="col-md-6 d-flex align-items-center lfd-gradient-colors">
<img src="/lofidrox/img/user_logout_logout.png" class="lfd-image-illu" alt="Logout">
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</main>
|
Composant cmpLfdUserRegister.svelte#
Ce composant est l’écran de création de compte. Il est très similaire à l’écran de connexion.
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
| <style>
@import "../../frameworks/bootstrap/css/bootstrap.min.css";
@import "../css/lofidrox.css";
</style>
<script>
import { onMount } from 'svelte';
import * as yup from 'yup';
import LfdUrls from '../js/lfdUrlManager.js';
import LfdStore from '../js/lfdLocalStore.js';
import GtgUtilsTools from '../../frameworks/gtg-svelte/gtg-utils-tooling.js';
import GtgUtilsPassword from '../../frameworks/gtg-svelte/gtg-utils-password.js';
import GtgUtilsWeb from '../../frameworks/gtg-svelte/gtg-utils-web.js';
let fields = { fUsername: null };
let values = { username: null, password: null, passwordConf: null };
let validation = { registered: true };
let errorMessages = { username: null, pwd: null, pwdCnf: null };
let schemaValidation = yup.object().shape( {
username: yup.string().nullable().required('You must enter a username').min(4, 'Username must contains at least ${min} letters'),
pwd: yup.string().nullable().required('You must enter a password').
min(10, 'Password must contains at least ${min} caracters').
max(256, 'Passqord must contains up to ${max} caracters long').
test('pwd-strength', 'Password must contains uppercase letter, lowercase letter, digit, special caracter',
function(value) {
return GtgUtilsPassword.checkStrongPwd(value);
} ),
pwdCnf: yup.string().nullable().required('You must confirm password').
min(10, 'Password must contains at least ${min} caracters').
max(256, 'Passqord must contains up to ${max} caracters long').
test('pwd-egality', 'Passwords are not identical',
function(value) {
return GtgUtilsPassword.checkEquality(values.password, value);
} )
} );
function goLogin() {
GtgUtilsWeb.route(LfdUrls.spa_user_login);
};
async function register() {
try {
validation.registered = true;
errorMessages = { username: null, pwd: null, pwdCnf: null };
await schemaValidation.validate( { 'username': values.username, 'pwd': values.password, 'pwdCnf': values.passwordConf }, { abortEarly: false } );
GtgUtilsWeb.postJson(LfdUrls.crud_user_register, { username: values.username, pwd: values.password }, function(data) {
if ((null != data) && data.registered) {
window.localStorage.setItem(LfdStore.field_username, data.username);
GtgUtilsWeb.route(LfdUrls.spa_user_login);
}
}, function(error) {
if (!GtgUtilsTools.isNull(error) && (GtgUtilsWeb.HTTP_CODES.NotAcceptable === error)) {
validation.registered = false;
} else {
GtgUtilsWeb.route(LfdUrls.spa_error);
}
} );
} catch (errors) {
errorMessages = GtgUtilsTools.turnYupErrorsToArray(errors);
}
};
onMount(() => {
fields.fUsername.focus();
} );
</script>
<main class="container">
<section class="h-100 lfd-gradient-form">
<div class="container py-2 h-100">
<div class="row d-flex justify-content-center align-items-center h-100">
<div class="col-xl-10">
<div class="card rounded-3 text-black">
<div class="row g-0">
<div class="col-md-6">
<div class="card-body p-md-5 mx-md-4">
<div class="text-center">
<img src="/lofidrox/img/lofidrox.png" alt="LoFiDroX" class="lfd-logo">
<h4 class="mt-1 mb-5 pb-1"></h4>
</div>
<form on:submit|preventDefault|stopPropagation={(e)=>{register();}}>
<p>Please create your account</p>
<div class="form-outline mb-4">
<input class="input form-control form-control-sm" tabindex="1" type="text" placeholder="Enter your name" id="fUsername" bind:value={values.username} bind:this={fields.fUsername}>
{#if errorMessages.username }
<p class="bg-danger text-white px-1 py-1">{errorMessages.username}</p>
{/if}
</div>
<div class="form-outline mb-4">
<input class="input form-control form-control-sm" tabindex="2" type="password" placeholder="Enter your password" id="fPassword" bind:value="{values.password}">
{#if errorMessages.pwd}
<p class="bg-danger text-white px-1 py-1">{errorMessages.pwd}</p>
{/if}
</div>
<div class="form-outline mb-4">
<input class="input form-control form-control-sm" tabindex="3" type="password" placeholder="Confirm your password" id="fPasswordCnf" bind:value="{values.passwordConf}">
{#if errorMessages.pwdCnf}
<p class="bg-danger text-white px-1 py-1">{errorMessages.pwdCnf}</p>
{/if}
</div>
{#if !validation.registered }
<div class="form-outline mb-4">
<p class="bg-danger text-white px-1 py-1">
You cannot register this account. Please use another username.
</p>
</div>
{/if}
<div class="text-center pt-1 mb-5 pb-1">
<button class="btn btn-primary btn-block fa-lg mb-3" tabindex="4" type="submit">Register <i class="mdi mdi-account-plus"></i></button>
<a class="btn btn-outline-dark fa-lg mb-3" tabindex="5" type="button" on:click="{goLogin}">Cancel <i class="mdi mdi-close"></i></a>
</div>
</form>
</div>
</div>
<div class="col-md-6 d-flex align-items-center lfd-gradient-colors">
{#if validation.registered }
<img src="/lofidrox/img/user_register_register.png" class="lfd-image-illu" alt="Register">
{:else}
<img src="/lofidrox/img/user_register_error.png" class="lfd-image-illu" alt="Error">
{/if}
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</main>
|
Composant cmpLoFiDroX.svelte#
Enfin le composant principal: son rôle est d’afficher le composant demandé (on parle alors de routage).
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
| <style>
@import "../../frameworks/bootstrap/css/bootstrap.min.css";
@import "../css/lofidrox.css";
</style>
<script>
import { Router, Link, Route } from "svelte-routing";
import GtgUtilsTools from '../../frameworks/gtg-svelte/gtg-utils-tooling.js';
import GtgUtilsWeb from '../../frameworks/gtg-svelte/gtg-utils-web.js';
import LfdUrls from '../js/lfdUrlManager.js';
import LfdLocalStore from '../js/lfdLocalStore.js';
import { lfdStore } from '../js/lfdStore.js';
import CmpLfdTitle from './cmpLfdTitle.svelte';
import CmpLfdHome from './cmpLfdHome.svelte';
import CmpLfdUserLogin from './cmpLfdUserLogin.svelte';
import CmpLfdUserRegister from './cmpLfdUserRegister.svelte';
import CmpLfdUserLogout from './cmpLfdUserLogout.svelte';
import CmpLfdFileUpload from './cmpLfdFileUpload.svelte';
import CmpLfdFileInbox from './cmpLfdFileInbox.svelte';
import CmpLfdError from './cmpLfdError.svelte';
let container = { };
// fetch query string parameters
container.urlParams = new URLSearchParams(window.location.search);
// fetch redirect page
container.page = container.urlParams.get('page');
// check if user session is still valid
container.username = window.localStorage.getItem(LfdLocalStore.field_username);
// define default route
container.urlSpa = LfdUrls.spa_user_login;
// skip user session check if no username found
if (!GtgUtilsTools.isNullOrEmpty(container.username)) {
// send check request
GtgUtilsWeb.postJson(LfdUrls.crud_user_check, { username: container.username }, function(data) {
if (!GtgUtilsTools.isNull(data) && data.session) {
// user's session is still valid, goto to homepage
$lfdStore.loggedIn = true;
GtgUtilsWeb.route(LfdUrls.spa_home);
} else {
// session is now invalid. If requested page is error, show it otherwise show login page
GtgUtilsWeb.route((LfdUrls.spa_error !== container.page? LfdUrls.spa_user_login: container.page));
}
}, function(error) {
// an error occured, goto to login page
if (!GtgUtilsTools.isNull(error) && (GtgUtilsWeb.HTTP_CODES.Unauthorized === error)) {
GtgUtilsWeb.route(LfdUrls.spa_user_login);
} else {
GtgUtilsWeb.route(LfdUrls.spa_error);
}
} );
};
// define default route
//export let urlSpa = LfdUrls.spa_user_login;
</script>
<main class="hero is-fullheight lfd-scroller-hori-no lfd-scroller-vert-no">
<div class="hero-head">
<CmpLfdTitle />
</div>
<div class="hero-body">
<div class="container lfd-container-main">
<Router url="{container.urlSpa}">
<Route path="{LfdUrls.spa_error}"><CmpLfdError /></Route>
<Route path="{LfdUrls.spa_home}"><CmpLfdHome /></Route>
<Route path="{LfdUrls.spa_user_login}"><CmpLfdUserLogin /></Route>
<Route path="{LfdUrls.spa_user_register}"><CmpLfdUserRegister /></Route>
<Route path="{LfdUrls.spa_user_logout}"><CmpLfdUserLogout /></Route>
<Route path="{LfdUrls.spa_file_upload}"><CmpLfdFileUpload /></Route>
<Route path="{LfdUrls.spa_file_inbox}"><CmpLfdFileInbox /></Route>
</Router>
</div>
</div>
</main>
|
Conclusion#
Svelte propose une structure simple et efficace pour réaliser des applications de type “Single Page” (SPA).
C’était le dernier article expliquant les rouages de l’application LoFiDroX. Si tu as des questions, poses les moi par mail.
Code source#
Comme annoncé dans l’article Fuyez GitHub, le code n’est plus disponible sur GitHub. L’intégralité du code est disponible sur mon CodeBerg: https://codeberg.org/GoboTheGeek/LoFiDroX