add qr-codes, share button, and pwa config toggle

This commit is contained in:
Joey Kimsey
2025-02-05 19:39:54 -05:00
parent a38d132e61
commit a6b241c7f0
10 changed files with 168 additions and 66 deletions

1
.gitignore vendored
View File

@ -65,3 +65,4 @@ vendor/canary/logs/*
components/* components/*
mailhog.log mailhog.log
uploads/* uploads/*
images/qr-codes/

View File

@ -76,25 +76,6 @@ class Images extends AdminController {
Views::view( 'admin.images.upload' ); Views::view( 'admin.images.upload' );
} }
private function getFolderObject( $folder, $subdirs = '' ) { private function getFolderObject( $folder, $subdirs = '' ) {
$names = explode( DIRECTORY_SEPARATOR, $folder ); $names = explode( DIRECTORY_SEPARATOR, $folder );
$folderName = array_pop( $names ); $folderName = array_pop( $names );
@ -155,28 +136,6 @@ class Images extends AdminController {
return $dirs; return $dirs;
} }
public function __construct() { public function __construct() {
parent::__construct(); parent::__construct();
self::$title = 'Admin - Images'; self::$title = 'Admin - Images';
@ -204,25 +163,6 @@ class Images extends AdminController {
Debug::error( 'There was an error with your upload.'); Debug::error( 'There was an error with your upload.');
Issues::add( 'error', [ 'There was an error with your upload.' => Check::userErrors() ] ); Issues::add( 'error', [ 'There was an error with your upload.' => Check::userErrors() ] );
} }
// if ( self::$token->create(
// Input::post( 'name' ),
// Input::post( 'notes' ),
// Input::post( 'token_type' )
// ) ) {
// Session::flash( 'success', 'Token Created' );
// Redirect::to( 'admin/images' );
// }
} }
Views::view( 'admin.images.create' ); Views::view( 'admin.images.create' );
} }
@ -261,7 +201,6 @@ class Images extends AdminController {
} }
public function rename() { public function rename() {
if ( ! Input::exists( 'fileLocation' ) ) { if ( ! Input::exists( 'fileLocation' ) ) {
Session::flash( 'warning', 'Unknown image.' ); Session::flash( 'warning', 'Unknown image.' );
Redirect::to( 'admin/images' ); Redirect::to( 'admin/images' );
@ -274,7 +213,6 @@ class Images extends AdminController {
Issues::add( 'error', [ 'There was an error renaming the image.' => Check::userErrors() ] ); Issues::add( 'error', [ 'There was an error renaming the image.' => Check::userErrors() ] );
} else { } else {
$result = $this->renameFile( Input::post( 'filelocation' ), Input::post( 'newname' ) ); $result = $this->renameFile( Input::post( 'filelocation' ), Input::post( 'newname' ) );
if ( ! empty( $result ) ) { if ( ! empty( $result ) ) {
Session::flash( 'success', 'Image has been renamed.' ); Session::flash( 'success', 'Image has been renamed.' );
Redirect::to( 'admin/images' ); Redirect::to( 'admin/images' );

View File

@ -8,6 +8,61 @@
* @link https://TheTempusProject.com * @link https://TheTempusProject.com
* @license https://opensource.org/licenses/MIT [MIT LICENSE] * @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/ */
let deferredPrompt;
const installPrompt = document.getElementById("install-prompt");
const installButton = document.getElementById("install-button");
const dismissButton = document.querySelector("#install-prompt .btn-close");
// Check if the user previously dismissed the prompt
if (!localStorage.getItem("pwaInstallDismissed")) {
window.addEventListener("beforeinstallprompt", (event) => {
event.preventDefault();
deferredPrompt = event;
installPrompt.classList.add("show"); // Show the prompt
});
}
// Handle Install Button Click
if ( installButton ) {
installButton.addEventListener("click", async () => {
if (deferredPrompt) {
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
if (outcome === "dismissed") {
setInstallDismissed(); // Store that the user dismissed the prompt
}
deferredPrompt = null; // Reset prompt
installPrompt.classList.remove("show"); // Hide the prompt
}
});
}
// Handle Close Button Click
if ( dismissButton ) {
dismissButton.addEventListener("click", () => {
setInstallDismissed(); // Store that the user dismissed the prompt
});
}
// Function to remember user choice for 7 days
function setInstallDismissed() {
localStorage.setItem("pwaInstallDismissed", Date.now() + 7 * 24 * 60 * 60 * 1000);
installPrompt.classList.remove("show"); // Hide the prompt
}
// Check if the 7-day period has passed
if (localStorage.getItem("pwaInstallDismissed")) {
const dismissUntil = parseInt(localStorage.getItem("pwaInstallDismissed"), 10);
if (Date.now() < dismissUntil) {
installPrompt.classList.remove("show"); // Ensure it's hidden
} else {
localStorage.removeItem("pwaInstallDismissed"); // Reset after 7 days
}
}
/** /**
* Automatically selects/de-selects all check boxes associated with that field * Automatically selects/de-selects all check boxes associated with that field
**/ **/

View File

@ -19,6 +19,15 @@ use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Bedrock\Classes\Config; use TheTempusProject\Bedrock\Classes\Config;
use TheTempusProject\Bedrock\Functions\Input; use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\TheTempusProject as App; use TheTempusProject\TheTempusProject as App;
use Endroid\QrCode\Builder\Builder;
use Endroid\QrCode\Encoding\Encoding;
use Endroid\QrCode\ErrorCorrectionLevel;
use Endroid\QrCode\Label\LabelAlignment;
use Endroid\QrCode\Label\Font\OpenSans;
use Endroid\QrCode\RoundBlockSizeMode;
use Endroid\QrCode\Writer\PngWriter;
use TheTempusProject\Hermes\Functions\Route as Routes;
use TheTempusProject\Canary\Bin\Canary as Debug;
class DefaultLoader extends Loader { class DefaultLoader extends Loader {
private static $loaded = false; private static $loaded = false;
@ -41,12 +50,61 @@ class DefaultLoader extends Loader {
} }
$this->addJs( '<script language="JavaScript" crossorigin="anonymous" type="text/javascript" src="{ROOT_URL}app/js/main.js"></script>' ); $this->addJs( '<script language="JavaScript" crossorigin="anonymous" type="text/javascript" src="{ROOT_URL}app/js/main.js"></script>' );
Components::setIfNull( 'LOGO', Config::getValue( 'main/logo' ) ?? TP_DEFAULT_LOGO ); Components::setIfNull( 'LOGO', Config::getValue( 'main/logo' ) ?? TP_DEFAULT_LOGO );
if ( ! empty( Config::getValue( 'share/enabled' ) ) ) {
$currentUrl = Routes::getAddress() . Input::get( 'url' );
$folder = IMAGE_DIRECTORY . 'qr-codes' . DIRECTORY_SEPARATOR ;
$filename = md5( $currentUrl ) . '.png';
if ( ! file_exists( $folder ) ) {
Debug::Info( 'Creating Directory because it does not exist' );
mkdir( $folder, 0777, true );
}
if ( ! empty( Config::getValue( 'share/qr' ) ) ) {
if ( ! file_exists( $folder . $filename ) ) {
Debug::Info( 'Creating qr-image because it does not exist' );
$builder = new Builder(
writer: new PngWriter(),
writerOptions: [],
validateResult: false,
data: Routes::getAddress() . Input::get( 'url' ),
encoding: new Encoding('UTF-8'),
errorCorrectionLevel: ErrorCorrectionLevel::High,
size: 200,
margin: 10,
roundBlockSizeMode: RoundBlockSizeMode::Margin,
logoPath: APP_ROOT_DIRECTORY . DIRECTORY_SEPARATOR . Config::getValue( 'main/logo' ),
logoResizeToWidth: 30,
logoPunchoutBackground: true,
labelText: Config::getValue( 'main/name' ),
labelFont: new OpenSans(14),
labelAlignment: LabelAlignment::Center
);
$result = $builder->build();
$result->saveToFile( $folder . $filename );
}
Components::set( 'QR_CODE','<img src="{ROOT_URL}images/qr-codes/' . $filename . '" alt="QR Code" class="img-fluid mb-2">' );
} else {
Components::setIfNull( 'QR_CODE', '' );
}
Components::setIfNull( 'SHARE_IMAGE', Views::simpleView( 'footer.share' ) );
} else {
Components::setIfNull( 'SHARE_IMAGE', '' );
}
if ( ! empty( Config::getValue( 'main/pwa' ) ) ) {
Components::setIfNull( 'PWA', Views::simpleView( 'pwa') );
} else {
Components::setIfNull( 'PWA', '' );
}
Components::setIfNull( 'COPY', Views::simpleView( 'footer.copy') ); Components::setIfNull( 'COPY', Views::simpleView( 'footer.copy') );
Components::setIfNull( 'SOCIAL', Views::simpleView( 'footer.social') ); Components::setIfNull( 'SOCIAL', Views::simpleView( 'footer.social') );
Components::prepend( 'FOOTER_LEFT', Views::simpleView( 'footer.left', Navigation::getMenuLinks( App::CONTACT_FOOTER_MENU_NAME ) ) ); Components::prepend( 'FOOTER_LEFT', Views::simpleView( 'footer.left', Navigation::getMenuLinks( App::CONTACT_FOOTER_MENU_NAME ) ) );
Components::prepend( 'FOOTER_CENTER', Views::simpleView( 'footer.center', Navigation::getMenuLinks( App::INFO_FOOTER_MENU_NAME ) ) ); Components::prepend( 'FOOTER_CENTER', Views::simpleView( 'footer.center', Navigation::getMenuLinks( App::INFO_FOOTER_MENU_NAME ) ) );
Components::prepend( 'FOOTER_RIGHT', Views::simpleView( 'footer.right') ); Components::prepend( 'FOOTER_RIGHT', Views::simpleView( 'footer.right') );
Components::setIfNull( 'FOOT', Views::simpleView( 'footer.container') ); Components::setIfNull( 'FOOT', Views::simpleView( 'footer.container') );
/** /**
* Top-Nav * Top-Nav
*/ */

View File

@ -91,6 +91,7 @@
</div> </div>
</div> </div>
{/ISSUES} {/ISSUES}
{PWA}
<!-- Main Page Content --> <!-- Main Page Content -->
{CONTENT} {CONTENT}
</div> </div>

View File

@ -1,4 +1,5 @@
<div class="col-12 col-sm-6 col-md-3 col-lg-2 mb-3 text-center"> <div class="col-12 col-sm-6 col-md-3 col-lg-2 mb-3 text-center">
{SHARE_IMAGE}
<h5>Dark Mode</h5> <h5>Dark Mode</h5>
<div class="material-switch px-4 mt-2"> <div class="material-switch px-4 mt-2">
<input name="dark-mode-toggle" type="checkbox" id="dark-mode-toggle" class="form-check-input"> <input name="dark-mode-toggle" type="checkbox" id="dark-mode-toggle" class="form-check-input">

View File

@ -0,0 +1,17 @@
<div class="text-center mb-3">
<h5 class="mb-3">Share</h5>
<div class="px-4 mt-2">
<!-- Share Button (visible only on medium+ screens) -->
<button type="button" class="btn btn-outline-primary"
data-bs-toggle="popover" data-bs-html="true" title="Share"
data-bs-content='
{QR_CODE}
<div class="d-flex justify-content-between">
<a href="https://www.reddit.com/submit?type=LINK&url={CURRENT_URL_SAFE}" target="_blank" class="mx-1 btn btn-sm btn-outline-danger"><i class="fa-brands fa-fw fa-reddit"></i></a>
<a href="https://www.facebook.com/sharer/sharer.php?u={CURRENT_URL_SAFE}" target="_blank" class="mx-1 btn btn-sm btn-outline-primary"><i class="fa-brands fa-fw fa-facebook"></i></a>
<a href="https://twitter.com/intent/tweet?url={CURRENT_URL_SAFE}" target="_blank" class="mx-1 btn btn-sm btn-outline-info"><i class="fa-brands fa-fw fa-x"></i></a>
</div>'>
Share
</button>
</div>
</div>

11
app/views/pwa.html Normal file
View File

@ -0,0 +1,11 @@
<div class="container pt-4 fade show" id="install-prompt">
<div class="row">
<div class="alert alert-success alert-dismissible w-100" role="alert">
<div class="d-flex justify-content-between align-items-center">
{SITENAME} is now available as a Progressive Web App, click the button to install now.
<button class="btn btn-md btn-outline-primary me-3" id="install-button">Install App</button>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
</div>
</div>

View File

@ -317,7 +317,13 @@ class TheTempusProject extends Bedrock {
"pretty" => "Maximum Login session length. (in seconds)", "pretty" => "Maximum Login session length. (in seconds)",
"default" => 604800, // 60 * 60 * 24 * 7 "default" => 604800, // 60 * 60 * 24 * 7
"value" => 604800, // 60 * 60 * 24 * 7 "value" => 604800, // 60 * 60 * 24 * 7
] ],
"pwa" => [
"type" => "radio",
"pretty" => "Enable PWA banner for installs",
"default" => false,
"value" => false,
],
], ],
"maintenance" => [ "maintenance" => [
"enabled" => [ "enabled" => [
@ -333,6 +339,20 @@ class TheTempusProject extends Bedrock {
"value" => "Currently the site is undergoing maintenance. Only administrators will be able to sign in.", "value" => "Currently the site is undergoing maintenance. Only administrators will be able to sign in.",
] ]
], ],
"share" => [
"enabled" => [
"type" => "radio",
"pretty" => "Enables the share popover.",
"default" => false,
"value" => false,
],
"qr"=> [
"type" => "radio",
"pretty" => "Enables a custom qr-code in the share popover.",
"default" => false,
"value" => false,
]
],
"uploads" => [ "uploads" => [
"images" => [ "images" => [
"type" => "radio", "type" => "radio",

View File

@ -194,12 +194,12 @@ class Install extends Controller {
*/ */
public function configure() { public function configure() {
if ( Forms::Check( 'installConfigure' ) ) { if ( Forms::Check( 'installConfigure' ) ) {
$logo = $baseConfig['main']['template']['default']; TheTempusProject::$activeConfig->load( BEDROCK_CONFIG_JSON );
$baseConfig = TheTempusProject::$configMatrix;
$logo = $baseConfig['main']['logo']['default'];
if ( Input::exists( 'logo' ) && Upload::image( 'logo', 'System' ) ) { if ( Input::exists( 'logo' ) && Upload::image( 'logo', 'System' ) ) {
$logo = 'Uploads/Images/System/' . Upload::last(); $logo = 'Uploads/Images/System/' . Upload::last();
} }
TheTempusProject::$activeConfig->load( BEDROCK_CONFIG_JSON );
$baseConfig = TheTempusProject::$configMatrix;
$baseConfig['main']['logo']['value'] = $logo; $baseConfig['main']['logo']['value'] = $logo;
$baseConfig['main']['name']['value'] = Input::postNull( 'siteName' ); $baseConfig['main']['name']['value'] = Input::postNull( 'siteName' );
$baseConfig['database']['dbEnabled']['value'] = $baseConfig['database']['dbEnabled']['default']; $baseConfig['database']['dbEnabled']['value'] = $baseConfig['database']['dbEnabled']['default'];