Skip to content

Commit e81a394

Browse files
committed
Improved loading time on themes page
Now lazy loading theme updater with Ajax
1 parent 452d94c commit e81a394

File tree

3 files changed

+387
-354
lines changed

3 files changed

+387
-354
lines changed
Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
@if(auth()->user()->role == 'admin')
2+
@if(env('ENABLE_THEME_UPDATER') == 'true')
3+
<style>
4+
details {
5+
width: 65%;
6+
margin-left: 15px;
7+
8+
{
9+
{
10+
-- max-width: calc(100% - 20rem);
11+
--
12+
}
13+
}
14+
15+
position: relative;
16+
border: 1px solid #78909C;
17+
border-radius: 6px;
18+
background-color: #ECEFF1;
19+
color: #263238;
20+
transition: background-color .15s;
21+
22+
> :last-child {
23+
margin-bottom: 1rem;
24+
}
25+
26+
&::before {
27+
width: 100%;
28+
height: 100%;
29+
content: '';
30+
position: absolute;
31+
top: 0;
32+
left: 0;
33+
border-radius: inherit;
34+
opacity: .15;
35+
box-shadow: 0 .25em .5em #263238;
36+
pointer-events: none;
37+
transition: opacity .2s;
38+
z-index: -1;
39+
}
40+
41+
&[open] {
42+
background-color: #FFF;
43+
44+
&::before {
45+
opacity: .6;
46+
}
47+
}
48+
}
49+
50+
summary {
51+
padding: 0.375rem 0.75rem;
52+
width: 100%;
53+
display: block;
54+
position: relative;
55+
font-size: 1.33em;
56+
font-weight: bold;
57+
cursor: pointer;
58+
59+
&::before,
60+
&::after {
61+
width: .75em;
62+
height: 2px;
63+
position: absolute;
64+
top: 50%;
65+
right: 0;
66+
content: '';
67+
background-color: currentColor;
68+
text-align: right;
69+
transform: translateY(-50%);
70+
transition: transform .2s ease-in-out;
71+
}
72+
73+
&::after {
74+
transform: translateY(-50%) rotate(90deg);
75+
76+
[open] & {
77+
transform: translateY(-50%) rotate(180deg);
78+
}
79+
}
80+
81+
&::-webkit-details-marker {
82+
display: none;
83+
}
84+
}
85+
86+
table,
87+
th,
88+
td {
89+
border: 1px solid black;
90+
}
91+
92+
.updatespin {
93+
animation: upspin 1s linear infinite;
94+
display: inline-block;
95+
}
96+
97+
@keyframes upspin {
98+
100% {
99+
transform: rotate(360deg)
100+
}
101+
}
102+
103+
</style>
104+
<br><br><br>
105+
<details>
106+
<summary><i class="bi bi-caret-down-fill"></i> Theme updater </summary>
107+
<div class="content" style="padding:10px;">
108+
<table>
109+
<tr>
110+
<th style="width:85%;">Theme name:</th>
111+
<th style="width: 15%;">Update status:</th>
112+
<th>Version:&nbsp;</th>
113+
</tr>
114+
<?php
115+
116+
if ($handle = opendir('themes')) {
117+
while (false !== ($entry = readdir($handle))) {
118+
119+
if(file_exists(base_path('themes') . '/' . $entry . '/readme.md')){
120+
$text = file_get_contents(base_path('themes') . '/' . $entry . '/readme.md');
121+
$pattern = '/Theme Version:.*/';
122+
preg_match($pattern, $text, $matches, PREG_OFFSET_CAPTURE);
123+
if(sizeof($matches) > 0) {
124+
$verNr = substr($matches[0][0],15);
125+
}
126+
}
127+
128+
$themeVe = NULL;
129+
if(!isset($verNr)){$verNr = "error";};
130+
131+
if ($entry != "." && $entry != "..") {
132+
echo '<tr>';
133+
echo '<th>'; print_r(ucfirst($entry));
134+
echo '</th>';
135+
echo '<th><center>';
136+
if(file_exists(base_path('themes') . '/' . $entry . '/readme.md')){
137+
if(!strpos(file_get_contents(base_path('themes') . '/' . $entry . '/readme.md'), 'Source code:')){$hasSource = false;}else{
138+
$hasSource = true;
139+
140+
$text = file_get_contents(base_path('themes') . '/' . $entry . '/readme.md');
141+
$pattern = '/Source code:.*/';
142+
preg_match($pattern, $text, $matches, PREG_OFFSET_CAPTURE);
143+
$sourceURL = substr($matches[0][0],13);
144+
145+
$replaced = str_replace("https://github.com/", "https://raw.githubusercontent.com/", trim($sourceURL));
146+
$replaced = $replaced . "/main/readme.md";
147+
148+
if (strpos($sourceURL, 'github.com')){
149+
150+
ini_set('user_agent', 'Mozilla/4.0 (compatible; MSIE 6.0)');
151+
try{
152+
$textGit = file_get_contents($replaced);
153+
$patternGit = '/Theme Version:.*/';
154+
preg_match($patternGit, $textGit, $matches, PREG_OFFSET_CAPTURE);
155+
$sourceURLGit = substr($matches[0][0],15);
156+
$Vgitt = 'v' . $sourceURLGit;
157+
$verNrv = 'v' . $verNr;
158+
}catch(Exception $ex){
159+
$themeVe = "error";
160+
$Vgitt = NULL;
161+
$verNrv = NULL;
162+
}
163+
164+
if(trim($Vgitt) > trim($verNrv)){
165+
$updateAv = true;
166+
$GLOBALS['updateAv'] = true;
167+
} else {
168+
$updateAv = false;
169+
}
170+
} else {$themeVe = "error";}
171+
172+
}
173+
}
174+
175+
if ($themeVe == "error") {
176+
echo '<img style="scale:0.9" src="https://img.llc.ovh/static/v1?label=&message=Error!&color=red">';
177+
} elseif ($hasSource == false) {
178+
echo '<a href="https://littlelink-custom.com/themes.php" target="_blank"><img style="scale:0.9" src="https://img.llc.ovh/static/v1?label=&message=Update manually&color=red"></a>';
179+
} elseif($updateAv == true) {
180+
echo '<img style="scale:0.9" src="https://img.llc.ovh/static/v1?label=&message=Update available&color=yellow">';
181+
} else {
182+
echo '<img style="scale:0.9" src="https://img.llc.ovh/static/v1?label=&message=Up to date&color=green">';
183+
}
184+
echo '</center></th>';
185+
echo '<th>' . $verNr . '</th>';
186+
echo '</tr>';}
187+
}} ?>
188+
</table>
189+
</div>
190+
<a href="{{url('update/theme')}}" onclick="updateicon()" class="mt-3 ml-3 btn btn-info row"><span id="updateicon" class=""><i class="bi bi-arrow-repeat"></i></span> Update all themes</a><br><br>
191+
<script>
192+
function updateicon() {
193+
var element = document.getElementById("updateicon");
194+
element.classList.add("updatespin");
195+
}
196+
197+
</script>
198+
</details>
199+
200+
<?php
201+
try{ if($GLOBALS['updateAv'] == true) echo '<img style="padding-left:40px; padding-top:15px; scale: 1.5;" src="https://img.llc.ovh/static/v1?label=&message=A theme needs updating&color=brightgreen">';
202+
}catch(Exception $ex){}
203+
?>
204+
205+
<script>
206+
$(function() {
207+
$('select[name=theme]').on('change', function() {
208+
var s = $(this).data('base-url') + "?t=" + $(this).val();
209+
$("#frPreview").prop('src', s);
210+
})
211+
});
212+
213+
class Accordion {
214+
constructor(el) {
215+
// Store the <details> element
216+
this.el = el;
217+
// Store the <summary> element
218+
this.summary = el.querySelector('summary');
219+
// Store the <div class="content"> element
220+
this.content = el.querySelector('.content');
221+
222+
// Store the animation object (so we can cancel it if needed)
223+
this.animation = null;
224+
// Store if the element is closing
225+
this.isClosing = false;
226+
// Store if the element is expanding
227+
this.isExpanding = false;
228+
// Detect user clicks on the summary element
229+
this.summary.addEventListener('click', (e) => this.onClick(e));
230+
}
231+
232+
onClick(e) {
233+
// Stop default behaviour from the browser
234+
e.preventDefault();
235+
// Add an overflow on the <details> to avoid content overflowing
236+
this.el.style.overflow = 'hidden';
237+
// Check if the element is being closed or is already closed
238+
if (this.isClosing || !this.el.open) {
239+
this.open();
240+
// Check if the element is being openned or is already open
241+
} else if (this.isExpanding || this.el.open) {
242+
this.shrink();
243+
}
244+
}
245+
246+
shrink() {
247+
// Set the element as "being closed"
248+
this.isClosing = true;
249+
250+
// Store the current height of the element
251+
const startHeight = `${this.el.offsetHeight}px`;
252+
// Calculate the height of the summary
253+
const endHeight = `${this.summary.offsetHeight}px`;
254+
255+
// If there is already an animation running
256+
if (this.animation) {
257+
// Cancel the current animation
258+
this.animation.cancel();
259+
}
260+
261+
// Start a WAAPI animation
262+
this.animation = this.el.animate({
263+
// Set the keyframes from the startHeight to endHeight
264+
height: [startHeight, endHeight]
265+
}, {
266+
duration: 400
267+
, easing: 'ease-out'
268+
});
269+
270+
// When the animation is complete, call onAnimationFinish()
271+
this.animation.onfinish = () => this.onAnimationFinish(false);
272+
// If the animation is cancelled, isClosing variable is set to false
273+
this.animation.oncancel = () => this.isClosing = false;
274+
}
275+
276+
open() {
277+
// Apply a fixed height on the element
278+
this.el.style.height = `${this.el.offsetHeight}px`;
279+
// Force the [open] attribute on the details element
280+
this.el.open = true;
281+
// Wait for the next frame to call the expand function
282+
window.requestAnimationFrame(() => this.expand());
283+
}
284+
285+
expand() {
286+
// Set the element as "being expanding"
287+
this.isExpanding = true;
288+
// Get the current fixed height of the element
289+
const startHeight = `${this.el.offsetHeight}px`;
290+
// Calculate the open height of the element (summary height + content height)
291+
const endHeight = `${this.summary.offsetHeight + this.content.offsetHeight}px`;
292+
293+
// If there is already an animation running
294+
if (this.animation) {
295+
// Cancel the current animation
296+
this.animation.cancel();
297+
}
298+
299+
// Start a WAAPI animation
300+
this.animation = this.el.animate({
301+
// Set the keyframes from the startHeight to endHeight
302+
height: [startHeight, endHeight]
303+
}, {
304+
duration: 400
305+
, easing: 'ease-out'
306+
});
307+
// When the animation is complete, call onAnimationFinish()
308+
this.animation.onfinish = () => this.onAnimationFinish(true);
309+
// If the animation is cancelled, isExpanding variable is set to false
310+
this.animation.oncancel = () => this.isExpanding = false;
311+
}
312+
313+
onAnimationFinish(open) {
314+
// Set the open attribute based on the parameter
315+
this.el.open = open;
316+
// Clear the stored animation
317+
this.animation = null;
318+
// Reset isClosing & isExpanding
319+
this.isClosing = false;
320+
this.isExpanding = false;
321+
// Remove the overflow hidden and the fixed height
322+
this.el.style.height = this.el.style.overflow = '';
323+
}
324+
}
325+
326+
document.querySelectorAll('details').forEach((el) => {
327+
new Accordion(el);
328+
});
329+
330+
</script>
331+
332+
@endif
333+
@endif

0 commit comments

Comments
 (0)