tooltip, char bar

This commit is contained in:
Sky Johnson 2024-10-03 16:25:25 -05:00
parent e32803b7f9
commit 95cee55d0c
17 changed files with 314 additions and 37 deletions

Binary file not shown.

Binary file not shown.

98
docs/tooltip.md Normal file
View File

@ -0,0 +1,98 @@
# Tooltip
The tooltip library is Isotip, taken and modified by Sharkk! Here is the breakdown of the API.
Configuring a specific tooltip is done via data attributes on an element.
### **`data-tooltip-classname`**
If you'd like to add a classname to the root tooltip element, set it here.
### **`data-tooltip-content`**
This sets the main body content of the tooltip into a `<p>` tag by default with a class of `tooltip-content`. Content is interpreted as plain text by default. To insert html, set the data-tooltip-html attribute to true.
### **`data-tooltip-title`**
This sets the title of the tooltip into a `<p>` tag by default with a class of `tooltip-title`.
### **`data-tooltip-html`**
Setting this to true will force Isotip to try and interpret the content as HTML. If it fails, it will interpret the content as plain text.
### **`data-tooltip-placement`**
This sets the position of the tooltip. Options are `top`, `right`, `bottom`, and `left`. By default, `top` is used for all tooltips.
### **`data-tooltip-container`**
This sets the element that the tooltip will be prepended to. By default, this is the `<body>` element.
Alternatively, programattic creation and destruction of tooltips is available.
### **`data-tooltip-scrollContainer`**
This sets the element that will have a scroll event bound to it. If your tooltip is inside a scrolling element (`overflow:scroll`), you need to add this!.
### **`data-tooltip-autoclose`**
If set to false, the tooltip will *not* close unless you do so programmatically with `isotip.close()`. Normal tooltips will not open until the open one has been closed!
### **`init( config )`**
The init method provides automatic event binding for tooltips. It sets up delegated event listeners for `.tooltip-click`, `.tooltip-hover`, and `.tooltip-focus` for click, mouseover, and focus events respectively. You can pass in an optional config object to overwrite any of the default options.
```javascript
var options = {
html: false, // set to true to always interpret content as HTML
placement: 'top', // default placement of tooltips
container: 'body', // default containing element for the tooltip
scrollContainer: '.scroll-container', // default container for scroll watching
template: '<div class="tooltip" data-tooltip-target="tooltip"></div>', // default template for the tooltip shell
removalDelay: 200, // default number of ms before the tooltip is removed
tooltipOffset: 10, // default number of px the tooltip is offset from the trigger element
windowPadding: { // window bounds for tooltip repositioning
top: 10,
right: 10,
bottom: 10,
left: 10
}
};
Tooltip.init( config );
```
### **`open( trigger, config )`**
The open method will create the tooltip, insert it into the DOM, and position it in relation to it's trigger. The trigger can be an element or a CSS selector. The object to be passed in will serve as a replacement for the data attributes on the trigger.
```javascript
var config = {
className: 'specific-class', // set to add a class to the tooltip
html: false, // set to true to interpret content as HTML
placement: 'top', // where to place the tooltip in relation to the trigger
content: 'Tooltip content', // the content to go into the tooltip,
title: 'Tooltip title', // the text to go in the title, if any
container: document.querySelector('.container'), // the container to append the tooltip to
scrollContainer: document.querySelector('.scroll-container'), // the container to bind the scroll event to
autoClose: false // set to false if you only want to close the tooltip programmatically. Normal tooltips won't open until the open one has been closed!
};
Tooltip.open( '.tooltip', config );
```
### **`close( tooltip )`**
The close method will remove a tooltip from the DOM. The tooltip to remove should be passed in and can be an element or a CSS selector.
```javascript
Tooltip.close( '.tooltip' );
```
### **`positionTooltip( tooltip, trigger, placement )`**
The positionTooltip method will re-evaluate the position of a tooltip in relation to it's trigger element. Only the tooltip and trigger need to be passed in, and placement will default to what's been configured by `init()`. Tooltip and trigger can be either an element or a CSS selector.
```javascript
Tooltip.positionTooltip( '.tooltip', '.tooltip-click', 'left' );
```

View File

@ -25,12 +25,11 @@ body {
border: none; border: none;
font-size: 1rem; font-size: 1rem;
background: #f7f8fa linear-gradient(rgba(255, 255, 255, 0), rgba(0, 0, 0, 0.1)); background: #f7f8fa linear-gradient(rgba(255, 255, 255, 0), rgba(0, 0, 0, 0.1));
box-shadow: 0 1px 0 1px rgba(255, 255, 255, 0.3) inset, 0 0 0 1px #adb2bb inset;
color: #111111; color: #111111;
margin: 0rem 0.25rem 0rem 0rem;
padding: 0.5rem 1rem 0.5rem; padding: 0.5rem 1rem 0.5rem;
text-align: center; text-align: center;
border-radius: 3px; border-radius: 3px;
box-shadow: 0 1px 0 1px rgba(255, 255, 255, 0.3) inset, 0 0 0 1px #adb2bb inset;
user-select: none; user-select: none;
text-decoration: none; text-decoration: none;
transition: opacity 0.1s ease, background-color 0.1s ease, color 0.1s ease, background 0.1s ease; transition: opacity 0.1s ease, background-color 0.1s ease, color 0.1s ease, background 0.1s ease;
@ -43,6 +42,11 @@ body {
color: rgba(0, 0, 0, 0.8); color: rgba(0, 0, 0, 0.8);
} }
&.badge {
font-size: 10px;
padding: 0.1rem 0.25rem;
}
&.primary { &.primary {
background-color: #f4cc67; background-color: #f4cc67;
background-image: linear-gradient(rgba(255, 255, 255, 0.15), rgba(0, 0, 0, 0.1)); background-image: linear-gradient(rgba(255, 255, 255, 0.15), rgba(0, 0, 0, 0.1));
@ -98,6 +102,31 @@ header {
main { main {
padding: 1rem; padding: 1rem;
display: grid;
grid-template-columns: 1fr 8fr 1fr;
gap: 2rem;
}
aside#left {
.box {
background-color: rgba(0, 0, 0, 0.2);
border-radius: 0.15rem;
&#nav > a {
display: block;
width: 100%;
padding: 0.5rem 1rem;
border-radius: 0.15rem;
text-decoration: none;
color: black;
transition: color, background-color 0.2s ease;
&:hover, &.active {
color: white;
background-color: black;
}
}
}
} }
footer { footer {
@ -116,11 +145,11 @@ footer {
#char-bar { #char-bar {
display: flex; display: flex;
justify-content: space-between;
align-items: center; align-items: center;
padding: 0 1rem; padding: 0 1rem;
height: 34px; height: 34px;
color: white; color: white;
gap: 1rem;
background-image: url('/assets/img/deco-bar2.jpg'); background-image: url('/assets/img/deco-bar2.jpg');
& > div { & > div {
@ -172,3 +201,60 @@ span.badge {
width: 960px; width: 960px;
margin: 0 auto; margin: 0 auto;
} }
.char-meter {
background-color: black;
height: 16px;
min-width: 100px;
border-radius: 0.1rem;
position: relative;
& > div {
height: 100%;
border-radius: 0.1rem;
overflow: hidden;
&.hp {
background-color: #e57373;
background-image: linear-gradient(rgba(255, 255, 255, 0.15), rgba(139, 0, 0, 0.1));
box-shadow: 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset;
border: 1px solid;
border-color: #d32f2f #c62828 #b71c1c;
}
&.mp {
background-color: #5a9bd4;
background-image: linear-gradient(rgba(255, 255, 255, 0.15), rgba(60, 100, 150, 0.1));
box-shadow: 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset;
border: 1px solid;
border-color: #4a8ab0 #3a7a9c #2a6a88;
}
&.tp {
background-color: #f4cc67;
background-image: linear-gradient(rgba(255, 255, 255, 0.15), rgba(0, 0, 0, 0.1));
box-shadow: 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset;
border: 1px solid;
border-color: #C59F43 #AA8326 #957321;
}
}
}
.tooltip {
background-color: black;
color: white;
border: 1px solid #666;
font-size: 14px;
padding: 0.5rem;
box-shadow: 0 0 0.5rem 0.1rem rgba(0, 0, 0, 0.2);
border-radius: 0.1rem;
text-align: center;
}
.tooltip-trigger {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -13,7 +13,7 @@ $r = [];
*/ */
router_get($r, '/', function () { router_get($r, '/', function () {
if (user()) must_have_character(); if (user()) must_have_character();
echo render('layouts/basic', ['view' => 'pages/home']); echo render('layouts/basic', ['view' => 'pages/home', 'activeTab' => nav_tabs['home']]);
}); });
/* /*
@ -28,6 +28,7 @@ router_post($r, '/auth/logout', 'auth_controller_logout_post');
/* /*
Characters Characters
*/ */
router_get($r, '/characters', 'char_controller_select_get');
router_post($r, '/character/create', 'char_controller_create_post'); router_post($r, '/character/create', 'char_controller_create_post');
router_post($r, '/character/select', 'auth_controller_change_character_post'); router_post($r, '/character/select', 'auth_controller_change_character_post');
router_get($r, '/character/create-first', 'char_controller_create_first_get'); router_get($r, '/character/create-first', 'char_controller_create_first_get');

View File

@ -61,13 +61,9 @@ function guest_only(): void
*/ */
function must_have_character(): void function must_have_character(): void
{ {
// If there is a character selected, and the data exists, return early.
if ($_SESSION['user']['char_id'] !== 0 && !empty($_SESSION['char'])) return;
// If there is a character selected, make sure the session is up to date. // If there is a character selected, make sure the session is up to date.
if ($_SESSION['user']['char_id'] !== 0 && empty($_SESSION['char'])) { if ($_SESSION['user']['char_id'] !== 0) {
$char = db_query(db_live(), 'SELECT * FROM characters WHERE id = :c', [':c' => $_SESSION['user']['char_id']]); $char = db_query(db_live(), 'SELECT * FROM characters WHERE id = :c', [':c' => $_SESSION['user']['char_id']])->fetchArray(SQLITE3_ASSOC);
$char = $char = $char->fetchArray(SQLITE3_ASSOC);
$_SESSION['char'] = $char; $_SESSION['char'] = $char;
return; return;
} }
@ -77,10 +73,7 @@ function must_have_character(): void
// if no character selected, select the first one // if no character selected, select the first one
if ($_SESSION['user']['char_id'] === 0) { if ($_SESSION['user']['char_id'] === 0) {
$char = db_query(db_live(), 'SELECT * FROM characters WHERE user_id = :u ORDER BY id ASC LIMIT 1', [':u' => user('id')]); $char = db_query(db_live(), 'SELECT * FROM characters WHERE user_id = :u ORDER BY id ASC LIMIT 1', [':u' => user('id')])->fetchArray(SQLITE3_ASSOC);
$char = $char->fetchArray(SQLITE3_ASSOC); change_user_character($char['id']);
$_SESSION['user']['char_id'] = $char['id'];
db_query(db_auth(), 'UPDATE users SET char_id = :c WHERE id = :u', [':c' => $char['id'], ':u' => user('id')]);
$_SESSION['char'] = $char;
} }
} }

View File

@ -1,5 +1,10 @@
<?php <?php
const nav_tabs = [
'home' => 0,
'chars' => 1,
];
/** /**
* Render the logout button's form. * Render the logout button's form.
*/ */
@ -17,3 +22,11 @@ function c_char_bar(): string
if (!char()) return ''; if (!char()) return '';
return render('components/char_bar', ['char' => char()]); return render('components/char_bar', ['char' => char()]);
} }
/**
* Render the left sidebar navigation menu. Provide the active tab to highlight it.
*/
function c_left_nav(int $activeTab): string
{
return render('components/left_nav', ['activeTab' => $activeTab]);
}

View File

@ -1,5 +1,18 @@
<?php <?php
/**
* Display a list of characters for the currently logged in user.
*/
function char_controller_select_get(): void
{
auth_only();
must_have_character();
$chars = char_list(user('id'));
echo render('layouts/basic', ['view' => 'pages/chars/select', 'chars' => $chars, 'activeTab' => nav_tabs['chars']]);
}
/** /**
* Form to create your first character. * Form to create your first character.
*/ */
@ -10,7 +23,7 @@ function char_controller_create_first_get(): void
// If the user already has a character, redirect them to the main page. // If the user already has a character, redirect them to the main page.
if (char_count(user('id')) > 0) redirect('/'); if (char_count(user('id')) > 0) redirect('/');
echo render('layouts/basic', ['view' => 'pages/chars/first']); echo render('layouts/basic', ['view' => 'pages/chars/first', 'activeTab' => nav_tabs['chars']]);
} }
/** /**

View File

@ -147,3 +147,13 @@ function change_user_character(int $char_id): void
db_query(db_auth(), "UPDATE users SET char_id = :c WHERE id = :u", [':c' => $char_id, ':u' => user('id')]); db_query(db_auth(), "UPDATE users SET char_id = :c WHERE id = :u", [':c' => $char_id, ':u' => user('id')]);
$_SESSION['char'] = char_find($char_id); $_SESSION['char'] = char_find($char_id);
} }
/**
* Get a percent between two ints, rounded to the nearest whole number or return 0.
*/
function percent(int $num, int $denom, int $precision = 4): int
{
if ($denom === 0) return 0;
$p = ($num / $denom) * 100;
return $p < 0 ? 0 : round($p, $precision);
}

View File

@ -1,6 +1,30 @@
<div id="char-bar"> <div id="char-bar">
<div> <div>
<img class="icon" src="/assets/img/icons/user1.png" alt="User"> <img class="icon" src="/assets/img/icons/user1.png" alt="User">
<?= $char['name'] ?> <span class="badge ml-2"><?= $char['level'] ?></span> <?= $char['name'] ?> <span class="badge ml-2 tooltip-hover" data-tooltip-content="Level"><?= $char['level'] ?></span>
<?php if ($char['attrib_points'] > 0): ?>
<span class="ui button primary badge ml-2 tooltip-hover" data-tooltip-content="Attribute Points"><?= $char['attrib_points'] ?></span>
<?php endif; ?>
</div>
<div>
<div class="char-meter">
<div class="hp" style="width: <?= percent($char['current_hp'], $char['max_hp']) ?>%"></div>
<div class="tooltip-trigger tooltip-hover" data-tooltip-content="Health<br><?= $char['current_hp'] ?> / <?= $char['max_hp'] ?>"></div>
</div>
</div>
<div>
<div class="char-meter">
<div class="mp" style="width: <?= percent($char['current_mp'], $char['max_mp']) ?>%"></div>
<div class="tooltip-trigger tooltip-hover" data-tooltip-content="Mana<br><?= $char['current_mp'] ?> / <?= $char['max_mp'] ?>"></div>
</div>
</div>
<div>
<div class="char-meter">
<div class="tp" style="width: <?= percent($char['current_tp'], $char['max_tp']) ?>%"></div>
<div class="tooltip-trigger tooltip-hover" data-tooltip-content="Travel Points<br><?= $char['current_tp'] ?> / <?= $char['max_tp'] ?>"></div>
</div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,4 @@
<div id="nav" class="box">
<a href="/" class="<?= $activeTab === 0 ? 'active' : '' ?>">Home</a>
<a href="/characters" class="<?= $activeTab === 1 ? 'active' : '' ?>">Characters</a>
</div>

View File

View File

@ -19,14 +19,28 @@
<?php else: ?> <?php else: ?>
<a class="ui button primary" href="/auth/login">Login</a> <a class="ui button primary" href="/auth/login">Login</a>
<a class="ui button secondary" href="/auth/register">Register</a> <a class="ui button secondary" href="/auth/register">Register</a>
<?php endif ?> <?php endif; ?>
</div> </div>
</header> </header>
<?= c_char_bar(user('char_id')) ?> <?= c_char_bar(user('char_id')) ?>
<main> <main>
<?= render($view, $data) ?> <aside id="left">
<?php if (user()): ?>
<?= c_left_nav($activeTab ?? 0) ?>
<?php endif; ?>
</aside>
<div id="center">
<?= render($view, $data) ?>
</div>
<aside id="right">
<?php if (user()): ?>
// right nav
<?php endif; ?>
</aside>
</main> </main>
<footer> <footer>
@ -34,5 +48,10 @@
<p>q<?= $GLOBALS['queries'] ?></p> <p>q<?= $GLOBALS['queries'] ?></p>
<p>v<?= env('version') ?></p> <p>v<?= env('version') ?></p>
</footer> </footer>
<script type="module">
import Tooltip from '/assets/scripts/tooltip.js';
Tooltip.init();
</script>
</body> </body>
</html> </html>

View File

@ -0,0 +1,16 @@
<h1>Characters</h1>
<?php
$list = char_list(user('id'));
if (count($list) > 0): ?>
<form action="/character/select" method="post">
<input type="hidden" name="csrf" value="<?= csrf() ?>">
<?php foreach ($list as $id => $char): ?>
<input type="radio" name="char_id" value="<?= $id ?>" id="char_<?= $id ?>">
<label for="char_<?= $id ?>"><?= $char['name'] ?> (Level <?= $char['level'] ?>)</label><br>
<?php endforeach; ?>
<input type="submit" value="Select Character">
</form>
<?php else: ?>
<!-- Should never see this particular message. If you have, there's a bug. -->
<p>You have no characters.</p>
<?php endif; ?>

View File

@ -1,21 +1,8 @@
<?php if (!user()): ?> <?php if (!user()): ?>
<h2>Welcome!</h2> <h1>Welcome!</h1>
<a href="/auth/login" class="ui button primary">Login</a>
<a href="/auth/register" class="ui button secondary">Register</a>
<?php else: ?> <?php else: ?>
<?php if (char_count(user('id')) > 0): ?> <h1 class="tooltip-click" data-tooltip-content="Hover-based tooltip">Home</h1>
<h3>Characters</h3> <?= print_r(char()) ?>
<form action="/character/select" method="post">
<input type="hidden" name="csrf" value="<?= csrf() ?>">
<?php foreach (char_list(user('id')) as $id => $char): ?>
<input type="radio" name="char_id" value="<?= $id ?>" id="char_<?= $id ?>">
<label for="char_<?= $id ?>"><?= $char['name'] ?> (Level <?= $char['level'] ?>)</label><br>
<?php endforeach; ?>
<input type="submit" value="Select Character">
</form>
<?php endif; ?>
<form action="/character/create" method="post">
<input type="hidden" name="csrf" value="<?= csrf() ?>">
<input type="text" name="name" placeholder="Character Name">
<input type="submit" value="Create Character">
</form>
<?php endif; ?> <?php endif; ?>