Added support for custom themes

This commit is contained in:
Julian Prieber 2022-05-18 21:08:58 +02:00
parent 8f15795a5c
commit 592dde4b2a
13 changed files with 898 additions and 207 deletions

View File

@ -7,11 +7,13 @@ use Illuminate\Support\Facades\Hash;
use Auth;
use DB;
use ZipArchive;
use App\Models\User;
use App\Models\Button;
use App\Models\Link;
//Function tests if string starts with certain string (used to test for illegal strings)
function stringStartsWith($haystack,$needle,$case=true) {
if ($case){
@ -56,8 +58,8 @@ class UserController extends Controller
return abort(404);
}
$userinfo = User::select('name', 'littlelink_name', 'littlelink_description')->where('id', $id)->first();
$information = User::select('name', 'littlelink_name', 'littlelink_description')->where('id', $id)->get();
$userinfo = User::select('name', 'littlelink_name', 'littlelink_description', 'theme')->where('id', $id)->first();
$information = User::select('name', 'littlelink_name', 'littlelink_description', 'theme')->where('id', $id)->get();
$links = DB::table('links')->join('buttons', 'buttons.id', '=', 'links.button_id')->select('links.link', 'links.id', 'links.button_id', 'links.title', 'links.custom_css', 'links.custom_icon', 'buttons.name')->where('user_id', $id)->orderBy('up_link', 'asc')->orderBy('order', 'asc')->get();
@ -74,8 +76,8 @@ class UserController extends Controller
return abort(404);
}
$userinfo = User::select('name', 'littlelink_name', 'littlelink_description')->where('id', $id)->first();
$information = User::select('name', 'littlelink_name', 'littlelink_description')->where('id', $id)->get();
$userinfo = User::select('name', 'littlelink_name', 'littlelink_description', 'theme')->where('id', $id)->first();
$information = User::select('name', 'littlelink_name', 'littlelink_description', 'theme')->where('id', $id)->get();
$links = DB::table('links')->join('buttons', 'buttons.id', '=', 'links.button_id')->select('links.link', 'links.id', 'links.button_id', 'links.title', 'links.custom_css', 'links.custom_icon', 'buttons.name')->where('user_id', $id)->orderBy('up_link', 'asc')->orderBy('order', 'asc')->get();
@ -301,8 +303,47 @@ class UserController extends Controller
return Redirect('/studio/page');
}
//Show custom theme
public function showTheme(request $request)
{
$userId = Auth::user()->id;
$data['pages'] = User::where('id', $userId)->select('littlelink_name', 'theme')->get();
return view('/studio/theme', $data);
}
//Save custom theme
public function editTheme(request $request)
{
$request->validate([
'zip' => 'sometimes|mimes:zip',
]);
$userId = Auth::user()->id;
$zipfile = $request->file('zip');
$theme = $request->theme;
User::where('id', $userId)->update(['theme' => $theme]);
if(!empty($zipfile)){
$zipfile->move(base_path('/themes'), "temp.zip");
$zip = new ZipArchive;
$zip->open(base_path() . '/themes/temp.zip');
$zip->extractTo(base_path() . '/themes');
$zip->close();
unlink(base_path() . '/themes/temp.zip');
}
return Redirect('/studio/theme');
}
//Show user (name, email, password)
public function showProfile()
public function showProfile(request $request)
{
$userId = Auth::user()->id;

View File

@ -25,6 +25,7 @@ class CreateUsersTable extends Migration
$table->enum('block', ['yes', 'no'])->default('no');
$table->rememberToken();
$table->timestamps();
$table->string('theme')->nullable();
});
}

View File

@ -48,21 +48,21 @@
text-align: center;
margin: 0 auto;
box-sizing: border-box; }
.column {
.column {
position: center;
width: 100%;
float: center;
box-sizing: border-box; }
/* For devices larger than 400px */
@media (min-width: 400px) {
/* For devices larger than 400px */
@media (min-width: 400px) {
.container {
width: 85%;
padding: 0; }
}
}
/* For devices larger than 550px */
@media (min-width: 550px) {
/* For devices larger than 550px */
@media (min-width: 550px) {
.container {
width: 80%; }
.column,
@ -72,16 +72,16 @@
.columns:first-child {
margin-left: 0; }
}
}
/* Base Styles
*/
/* NOTE
html is set to 62.5% so that all the REM measurements throughout Skeleton
are based on 10px sizing. So basically 1.5rem = 15px :) */
/* Base Styles
*/
/* NOTE
html is set to 62.5% so that all the REM measurements throughout Skeleton
are based on 10px sizing. So basically 1.5rem = 15px :) */
html {
html {
font-size: 100%; }
@media (prefers-color-scheme: dark) {
@ -107,40 +107,40 @@
}
/* Typography
*/
h1 {
/* Typography
*/
h1 {
margin-top: 0;
margin-bottom: 16px;
font-weight: 800; }
h1 { font-size:24px; line-height: 64px; letter-spacing: 0;}
h1 { font-size:24px; line-height: 64px; letter-spacing: 0;}
/* Larger than phablet */
@media (min-width: 550px) {
/* Larger than phablet */
@media (min-width: 550px) {
h1 { font-size: 48px; line-height: 96px;}
}
}
p {
p {
margin-top: 0; }
/* Links
*/
a {
/* Links
*/
a {
color: #0085FF;
text-decoration: none;
}
a:hover {
}
a:hover {
color: #0085FF; }
.spacing {
.spacing {
padding: 0 10px;
}
}
/* Code
*/
code {
/* Code
*/
code {
padding: .2rem .5rem;
margin: 0 .2rem;
font-size: 90%;
@ -148,54 +148,54 @@
background: #F1F1F1;
border: 1px solid #E1E1E1;
border-radius: 4px; }
pre > code {
pre > code {
display: block;
padding: 1rem 1.5rem;
white-space: pre; }
/* Spacing
*/
button,
.button {
/* Spacing
*/
button,
.button {
margin-bottom: 1rem; }
input,
textarea,
select,
fieldset {
input,
textarea,
select,
fieldset {
margin-bottom: 1.5rem; }
pre,
blockquote,
dl,
figure,
p,
ol {
pre,
blockquote,
dl,
figure,
p,
ol {
margin-bottom: 2.5rem; }
/* Utilities
*/
.u-full-width {
/* Utilities
*/
.u-full-width {
width: 100%;
box-sizing: border-box; }
.u-max-full-width {
.u-max-full-width {
max-width: 100%;
box-sizing: border-box; }
.u-pull-right {
.u-pull-right {
float: right; }
.u-pull-left {
.u-pull-left {
float: left; }
/* Misc
*/
hr {
/* Misc
*/
hr {
margin-top: 3rem;
margin-bottom: 3.5rem;
border-width: 0;
border-top: 1px solid #E1E1E1; }
@media (prefers-color-scheme: dark) {
/* ===== Scrollbar CSS ===== */
/* ===== Scrollbar CSS ===== */
/* Firefox */
* {
scrollbar-width: thin;
@ -217,10 +217,10 @@
border: 3px none #ffffff;
}
}
}
@media (prefers-color-scheme: light) {
/* ===== Scrollbar CSS ===== */
/* ===== Scrollbar CSS ===== */
/* Firefox */
* {
scrollbar-width: thin;
@ -242,23 +242,23 @@
border: 3px none #ffffff;
}
}
}
/* Credit footer
*/
.credit-txt {
font-weight: 700;
font-size: 15px;
text-decoration: none;
font-weight: 700;
font-size: 15px;
text-decoration: none;
}
@media (prefers-color-scheme: dark) {
.credit-txt-clr{
.credit-txt-clr{
color: #FFF !important;
}
}
}
@media (prefers-color-scheme: light) {
.credit-txt-clr{
.credit-txt-clr{
color: #100a26 !important;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -140,6 +140,9 @@ if ($url1sb->successful() or $url2sb->successful()) {
<a href="{{ url('/studio/page') }}">Page</a>
</li>
<li>
<a href="{{ url('/studio/theme') }}">Themes</a>
</li>
<li>
<a href="{{ url('/studio/profile') }}">Profile</a>
</li>
<form action="{{ route('logout') }}" method="post">

View File

@ -0,0 +1,65 @@
@extends('layouts.sidebar')
@section('content')
@foreach($pages as $page)
<h2 class="mb-4"><i class="bi bi-brush"> Select a theme</i></h2>
<form action="{{ route('editTheme') }}" enctype="multipart/form-data" method="post">
@csrf
<br><br><div class="form-group col-lg-8">
<h3>Current theme</h3>
@if(empty($page->theme))
<input type="text" class="form-control" value="default" readonly>
@else
<input type="text" class="form-control" value="{{ $page->theme }}" readonly>
@endif
</div><br>
<div id="result" style="left: 1%; position: relative; background-color:#2c2d3a; border-radius: 25px; min-width:300px; max-width:950px; box-shadow: 0 10px 20px -10px rgba(0,0,0, 0.6);">
<div style="padding:5%5%;">
<h3 align="center" style="color:white">Preview:</h3>
<center><img style="width:95%;max-width:700px;argin-left:1rem!important;" src="@if(file_exists(base_path() . '/themes/' . $page->theme . '/preview.png')){{url('/themes/' . $page->theme . '/preview.png')}}@elseif($page->theme === 'default' or empty($page->theme)){{url('/littlelink/images/themes/default.png')}}@else{{url('/littlelink/images/themes/no-preview.png')}}@endif"></img></center>
</div></div><br>
<div class="form-group col-lg-8">
<h3>Select a theme</h3>
<select class="form-control" name="theme">
<?php if ($handle = opendir('themes')) {
while (false !== ($entry = readdir($handle))) {
if ($entry != "." && $entry != "..") {
echo '<option>'; print_r($entry); echo '</option>'; }}} ?>
<option>default</option>
</select>
</div>
<button type="submit" class="mt-3 ml-3 btn btn-info">Update theme</button>
</form>
</details>
@if(auth()->user()->role == 'admin')
<br><br><br>
<form action="{{ route('editTheme') }}" enctype="multipart/form-data" method="post">
@csrf
<h3>Upload themes</h3>
<div style="display: none;" class="form-group col-lg-8">
<select class="form-control" name="theme">
<option>{{ $page->theme }}</option>
</select>
<br>
</div>
<div class="form-group col-lg-8">
<label>Upload theme</label>
<input type="file" accept=".zip" class="form-control-file" name="zip">
</div>
<button type="submit" class="mt-3 ml-3 btn btn-info">Upload theme</button>
</form>
</details>
@endif
@endforeach
@endsection

View File

@ -50,6 +50,8 @@ Route::get('/studio/index', [UserController::class, 'index'])->name('studioIndex
Route::get('/studio/add-link', [UserController::class, 'showButtons'])->name('showButtons');
Route::post('/studio/add-link', [UserController::class, 'addLink'])->name('addLink');
Route::get('/studio/links', [UserController::class, 'showLinks'])->name('showLinks');
Route::get('/studio/theme', [UserController::class, 'showTheme'])->name('showTheme');
Route::post('/studio/theme', [UserController::class, 'editTheme'])->name('editTheme');
Route::get('/deleteLink/{id}', [UserController::class, 'deleteLink'])->name('deleteLink');
Route::get('/upLink/{up}/{id}', [UserController::class, 'upLink'])->name('upLink');
Route::get('/studio/edit-link/{id}', [UserController::class, 'showLink'])->name('showLink');

288
themes/brands.css vendored Normal file
View File

@ -0,0 +1,288 @@
/* Table of contents
- Rounded user avatars
- Buttons
- Brand Styles
*/
@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap');
body{
color: white !important;
background-color: #202124 !important;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
display: flex;
flex-direction: column;
border-left: 0;
border-right: 0;
font-family: 'Roboto Mono', monospace !important;
}
/* Buttons
*/
.button,
button {
display: inline-block;
margin-bottom: 20px;
padding: 17px;
font-size: 1rem;
font-weight: 500;
border-radius: 5px;
border: dashed white 2px;
text-decoration: none;
color: white !important;
word-wrap: break-word;
width: 300px;
}
button:hover,
.button:focus {
color: #333;
border-color: #888;
outline: 0; }
.button.button-primary {
color: #FFF;
filter: brightness(90%) }
.button.button-primary:hover,
.button.button-primary:focus {
color: #FFF;
filter: brightness(90%) }
/* Brand Icons
*/
.icon {
padding: 0px 8px 3.5px 0px;
vertical-align: middle;
width: 20px;
height: 20px;
-webkit-filter: grayscale(100%);
-moz-filter: grayscale(100%);
filter: grayscale(100%);
}
/* Brand Styles
*/
/* Added custom link button*/
.button.button-custom:hover,
.button.button-custom:focus {
filter: brightness(90%) }
/* Default (this is great for your own brand color!) */
.button.button-default:hover,
.button.button-default:focus {
filter: brightness(90%) }
/* VRChat */
.button.button-vrchat:hover,
.button.button-vrchat:focus {
filter: brightness(90%);
}
/* Discord */
.button.button-discord:hover,
.button.button-discord:focus {
filter: brightness(90%) }
/* Facebook */
.button.button-facebook:hover,
.button.button-facebook:focus {
filter: brightness(90%) }
/* Facebook Messenger */
.button.button-messenger:hover,
.button.button-messenger:focus {
filter: brightness(90%) }
/* Figma */
.button.button-figma:hover,
.button.button-figma:focus {
filter: brightness(90%) }
/* Github */
.button.button-github:hover,
.button.button-github:focus {
filter: brightness(90%) }
/* Goodreads */
.button.button-goodreads:hover,
.button.button-goodreads:focus {
filter: brightness(90%) }
/* Instagram */
.button.button-instagram:hover,
.button.button-instagram:focus {
filter: brightness(90%) }
/* Kit */
.button.button-kit:hover,
.button.button-kit:focus {
filter: brightness(90%) }
/* LinkedIn */
.button.button-linkedin:hover,
.button.button-linkedin:focus {
filter: brightness(90%) }
/* Medium */
.button.button-medium:hover,
.button.button-medium:focus {
filter: brightness(90%) }
/* Pinterest */
.button.button-pinterest:hover,
.button.button-pinterest:focus {
filter: brightness(90%) }
/* Producthunt */
.button.button-producthunt:focus {
filter: brightness(90%) }
/* Reddit */
.button.button-reddit:hover,
.button.button-reddit:focus {
filter: brightness(90%) }
/* Skoob */
.button.button-skoob:hover,
.button.button-skoob:focus {
filter: brightness(90%) }
/* Snapchat */
.button.button-snapchat:hover,
.button.button-snapchat:focus {
filter: brightness(90%) }
/* SoundCloud */
.button.button-soundcloud:hover,
.button.button-soundcloud:focus {
filter: brightness(90%) }
/* Spotify */
.button.button-spotify:hover,
.button.button-spotify:focus {
filter: brightness(90%) }
/* Steam */
.button.button-steam:hover,
.button.button-steam:focus {
filter: brightness(90%) }
/* Telegram */
.button.button-telegram:hover,
.button.button-telegram:focus {
filter: brightness(90%) }
/* TikTok */
.button.button-tiktok:hover,
.button.button-tiktok:focus {
filter: brightness(90%) }
/* Tumblr */
.button.button-tumblr:hover,
.button.button-tumblr:focus {
filter: brightness(90%) }
/* Twitch */
.button.button-twitch:hover,
.button.button-twitch:focus {
filter: brightness(90%) }
/* Twitter */
.button.button-twitter:hover,
.button.button-twitter:focus {
filter: brightness(90%) }
/* Vimeo */
.button.button-vimeo:hover,
.button.button-vimeo:focus {
filter: brightness(90%) }
/* YouTube */
.button.button-youtube:hover,
.button.button-youtube:focus {
filter: brightness(90%) }
/* Wordpress */
.button.button-wordpress:hover,
.button.button-wordpress:focus {
filter: brightness(90%) }
/* Bandcamp */
.button.button-bandcamp:hover,
.button.button-bandcamp:focus {
filter: brightness(90%);
}
/* Patreon */
.button.button-patreon:hover,
.button.button-patreon:focus {
filter: brightness(90%);
}
/* Signal */
.button.button-signal:hover,
.button.button-signal:focus {
filter: brightness(90%);
}
/* Venmo */
.button.button-venmo:hover,
.button.button-venmo:focus {
filter: brightness(90%);
}
/* Cash App */
.button.button-cashapp:hover,
.button.button-cashapp:focus {
filter: brightness(90%);
}
/* Gitlab */
.button.button-gitlab:hover,
.button.button-gitlab:focus {
filter: brightness(90%);
}
/* Mastodon */
.button.button-mastodon:hover,
.button.button-mastodon:focus {
filter: brightness(90%);
}
/* PayPal */
.button.button-paypal:hover,
.button.button-paypal:focus {
filter: brightness(90%);
}
/* WhatsApp */
.button.button-whatsapp:hover,
.button.button-whatsapp:focus {
filter: brightness(90%);
}
/* Xing */
.button.button-xing:hover,
.button.button-xing:focus {
filter: brightness(90%);
}
/* Buy Me a Coffee */
.button.button-coffee:hover,
.button.button-coffee:focus {
filter: brightness(90%);
}
/* Custom Website */
.button.button-web:hover,
.button.button-web:focus {
filter: brightness(90%);
}

BIN
themes/preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

18
themes/readme.md Normal file
View File

@ -0,0 +1,18 @@
# A LittleLink Custom Theme
Find more themes: https://github.com/JulianPrieber/llc-themes
* Theme Name: Mono
* Theme Version: 1.0
* Theme Date: 2022-05-18
* Theme Author: JulianPrieber
* Theme Author URI: https://github.com/JulianPrieber
* Theme License: GPLv3
### Used assets:
* Built using:
* https://github.com/dhg/Skeleton
* License: MIT
* https://github.com/JulianPrieber/littlelink-mono
* License: MIT

97
themes/share.button.css vendored Normal file
View File

@ -0,0 +1,97 @@
.sharediv {
position:relative;
top: 30px;
right: 30px;
padding-bottom: 40px;
}
.toastdiv {
display: -webkit-flex;
display: flex;
-webkit-align-items: center;
align-items: center;
-webkit-justify-content: center;
justify-content: center;
}
.toastbox {
width: 280px;
padding: 10px;
background-color: rgba(0, 0, 0, 0.7);
color: white;
border-radius: 4px;
position: fixed;
top: 105%;
-webkit-transition: transform 0.3s linear;
transition: transform 0.3s linear;
z-index: 2;
text-align: center;
}
.toastbox.toast-tox--active {
-webkit-transform: translateY(-150px);
transform: translateY(-150px);
}
.sharebutton,
sharebutton {
display: inline-block;
text-decoration: none;
height: 48px;
text-align: center;
vertical-align: middle;
font-size: 18px;
width: 48px;
font-weight: 700;
line-height: 48px;
letter-spacing: 0.1px;
white-space: wrap;
border-radius: 8px;
cursor: pointer;
color: #000000;
background-color: #efefef
}
@media screen and (min-width: 600px) {
.sharebutton,
sharebutton {
display: inline-block;
text-decoration: none;
height: 48px;
text-align: center;
vertical-align: middle;
font-size: 18px;
width: 150px;
font-weight: 700;
line-height: 48px;
letter-spacing: 0.1px;
white-space: wrap;
border-radius: 8px;
cursor: pointer;
color: #000000;
background-color: #efefef
}
}
sharebutton:hover,
.sharebutton:hover {
color: #000000;
opacity: 0.85;
filter: alpha(opacity=40);
border-color: #888;
outline: 0; }
.sharebutton.sharebutton-primary {
color: #FFFFFF;
filter: brightness(90%) }
.sharebutton.sharebutton-primary:hover,
.sharebutton.sharebutton-primary:focus {
color: #FFFFFF;
filter: brightness(90%) }
@media screen and (max-width: 600px) {
.sharebutton-mb {
display: none;
}
.sharebutton-img {
position: relative;
left: 3px;
margin-left: auto;
margin-right: auto;
}
}

176
themes/skeleton-auto.css vendored Normal file
View File

@ -0,0 +1,176 @@
/* Table of contents
- Grid
- Base Styles
- Typography
- Links
- Code
- Spacing
- Utilities
*/
/* Grid
*/
.container {
position: relative;
width: 100%;
max-width: 600px;
text-align: center;
margin: 0 auto;
padding: 0 20px;
box-sizing: border-box;
}
.column {
position: center;
width: 100%;
float: center;
box-sizing: border-box;
}
/* For devices larger than 400px */
@media (min-width: 400px) {
.container {
width: 85%;
padding: 0;
}
}
/* For devices larger than 550px */
@media (min-width: 550px) {
.container {
width: 80%;
}
.column,
.columns {
margin-left: 0;
}
.column:first-child,
.columns:first-child {
margin-left: 0;
}
}
/* Base Styles
*/
/* NOTE
html is set to 62.5% so that all the REM measurements throughout Skeleton
are based on 10px sizing. So basically 1.5rem = 15px :) */
html {
font-size: 100%;
color-scheme: light dark;
}
body {
font-size: 18px;
line-height: 24px;
font-weight: 400;
}
/* Typography
*/
h1 {
margin-top: 0;
margin-bottom: 16px;
font-weight: 800;
}
h1 {
font-size: 24px;
line-height: 64px;
letter-spacing: 0;
}
/* Larger than phablet */
@media (min-width: 550px) {
h1 {
font-size: 48px;
line-height: 96px;
}
}
p {
margin-top: 0;
}
/* Links
*/
a {
color: #0085ff;
}
a:hover {
color: #0085ff;
}
/* Code
*/
code {
padding: 0.2rem 0.5rem;
margin: 0 0.2rem;
font-size: 90%;
white-space: nowrap;
background: #f1f1f1;
border: 1px solid #e1e1e1;
border-radius: 4px;
}
pre > code {
display: block;
padding: 1rem 1.5rem;
white-space: pre;
}
/* Spacing
*/
button,
.button {
margin-bottom: 1rem;
}
input,
textarea,
select,
fieldset {
margin-bottom: 1.5rem;
}
pre,
blockquote,
dl,
figure,
p,
ol {
margin-bottom: 2.5rem;
}
/* Utilities
*/
.u-full-width {
width: 100%;
box-sizing: border-box;
}
.u-max-full-width {
max-width: 100%;
box-sizing: border-box;
}
.u-pull-right {
float: right;
}
.u-pull-left {
float: left;
}
/* Misc
*/
hr {
margin-top: 3rem;
margin-bottom: 3.5rem;
border-width: 0;
border-top: 1px solid #e1e1e1;
}
.credit-icon {
display: none;
}
.credit-text {
color: white !important;
}