Move rendering into private templates
Add an explicit template renderer with HTML views and partials for the app, bootstrap, package, and catalog pages. Move shared reporting setup into config/reporting.php and relocate stylesheet assets under css/.
This commit is contained in:
@@ -24,11 +24,9 @@ require_once __DIR__ . '/private/languagestore.php';
|
||||
require_once __DIR__ . '/private/nexttoken.php';
|
||||
require_once __DIR__ . '/private/promptstore.php';
|
||||
require_once __DIR__ . '/private/usersettings.php';
|
||||
require_once __DIR__ . '/private/viewdata.php';
|
||||
|
||||
ini_set('display_errors', '1');
|
||||
ini_set('display_startup_errors', '1');
|
||||
ini_set('log_errors', '1');
|
||||
error_reporting(E_ALL);
|
||||
require_once __DIR__ . '/config/reporting.php';
|
||||
|
||||
$DB_FILE = __DIR__ . '/data/racket-sandbox.sqlite';
|
||||
|
||||
@@ -57,11 +55,11 @@ function get_value($name, $default = '')
|
||||
return $_GET[$name] ?? $default;
|
||||
}
|
||||
|
||||
function t($key, $fallback = null)
|
||||
function t($key, $fallback = null, $values = array())
|
||||
{
|
||||
global $languageStore, $language;
|
||||
|
||||
return $languageStore->translate($key, $language, $fallback);
|
||||
return $languageStore->translateFormat($key, $language, $values, $fallback);
|
||||
}
|
||||
|
||||
function current_scheme()
|
||||
@@ -154,64 +152,7 @@ $language = resolve_user_language(
|
||||
$languageStore->supportedLanguages()
|
||||
);
|
||||
|
||||
$languageStore->seedDefaults(array(
|
||||
'app.title' => array('en' => 'Racket sandbox', 'nl' => 'Racket sandbox'),
|
||||
'app.manage_prompts' => array('en' => 'Manage prompts', 'nl' => 'Prompts beheren'),
|
||||
'app.logout' => array('en' => 'Logout', 'nl' => 'Uitloggen'),
|
||||
'app.language' => array('en' => 'Language', 'nl' => 'Taal'),
|
||||
'app.logged_in_as' => array('en' => 'Logged in as:', 'nl' => 'Ingelogd als:'),
|
||||
'app.admin' => array('en' => 'admin', 'nl' => 'admin'),
|
||||
'app.bootstrap_link' => array('en' => 'Bootstrap link', 'nl' => 'Bootstraplink'),
|
||||
'app.generate_bootstrap_link' => array('en' => 'Generate bootstrap link', 'nl' => 'Bootstraplink genereren'),
|
||||
'app.ttl_minutes' => array('en' => 'TTL in minutes', 'nl' => 'TTL in minuten'),
|
||||
'app.ttl_range_help' => array('en' => 'Allowed range: 30 minutes to 8 hours.', 'nl' => 'Toegestaan bereik: 30 minuten tot 8 uur.'),
|
||||
'app.generated_link' => array('en' => 'Generated link', 'nl' => 'Gegenereerde link'),
|
||||
'app.copy' => array('en' => 'Copy', 'nl' => 'Kopieer'),
|
||||
'app.copied' => array('en' => 'Copied', 'nl' => 'Gekopieerd'),
|
||||
'app.generated_link_help' => array(
|
||||
'en' => 'Give this link to the AI agent. The agent should start from this link and then only follow links from the generated HTML pages.',
|
||||
'nl' => 'Geef deze link aan de AI-agent. De agent moet vanaf deze link starten en daarna alleen links volgen vanuit de gegenereerde HTML-paginas.'
|
||||
),
|
||||
'app.select_prompt' => array('en' => 'Select prompt', 'nl' => 'Prompt selecteren'),
|
||||
'app.copy_full_prompt' => array('en' => 'Copy full prompt', 'nl' => 'Volledige prompt kopieren'),
|
||||
'app.bootstrap_prompt_help' => array(
|
||||
'en' => 'Choose one of your prompts. The placeholder {{bootstrap-racket-link}} is replaced by the generated bootstrap link.',
|
||||
'nl' => 'Kies een van je prompts. De placeholder {{bootstrap-racket-link}} wordt vervangen door de gegenereerde bootstraplink.'
|
||||
),
|
||||
'app.no_bootstrap_prompts' => array(
|
||||
'en' => 'No personal prompts are available for this language. Copy a default prompt first from prompt management.',
|
||||
'nl' => 'Er zijn geen persoonlijke prompts beschikbaar voor deze taal. Kopieer eerst een standaardprompt vanuit promptbeheer.'
|
||||
),
|
||||
'app.user_management' => array('en' => 'User management', 'nl' => 'Gebruikersbeheer'),
|
||||
'app.configuration' => array('en' => 'Configuration', 'nl' => 'Configuratie'),
|
||||
'app.user_management_help' => array(
|
||||
'en' => 'Users are registered manually by email address, full name and password. This page only manages existing users.',
|
||||
'nl' => 'Gebruikers worden handmatig geregistreerd met e-mailadres, volledige naam en wachtwoord. Deze pagina beheert alleen bestaande gebruikers.'
|
||||
),
|
||||
'app.id' => array('en' => 'ID', 'nl' => 'ID'),
|
||||
'app.full_name' => array('en' => 'Full name', 'nl' => 'Volledige naam'),
|
||||
'app.email' => array('en' => 'Email', 'nl' => 'E-mail'),
|
||||
'app.enabled' => array('en' => 'Enabled', 'nl' => 'Ingeschakeld'),
|
||||
'app.created' => array('en' => 'Created', 'nl' => 'Gemaakt'),
|
||||
'app.last_login' => array('en' => 'Last login', 'nl' => 'Laatste login'),
|
||||
'app.actions' => array('en' => 'Actions', 'nl' => 'Acties'),
|
||||
'app.yes' => array('en' => 'yes', 'nl' => 'ja'),
|
||||
'app.no' => array('en' => 'no', 'nl' => 'nee'),
|
||||
'app.save_flags' => array('en' => 'Save flags', 'nl' => 'Vlaggen opslaan'),
|
||||
'app.new_password' => array('en' => 'New password', 'nl' => 'Nieuw wachtwoord'),
|
||||
'app.change_password' => array('en' => 'Change password', 'nl' => 'Wachtwoord wijzigen'),
|
||||
'app.delete_user' => array('en' => 'Delete user', 'nl' => 'Gebruiker verwijderen'),
|
||||
'app.delete_user_confirm' => array('en' => 'Delete user', 'nl' => 'Gebruiker verwijderen'),
|
||||
'app.cannot_delete_self' => array('en' => 'You cannot delete your own account.', 'nl' => 'Je kunt je eigen account niet verwijderen.'),
|
||||
'app.bootstrap_link_issued' => array('en' => 'Bootstrap link issued.', 'nl' => 'Bootstraplink aangemaakt.'),
|
||||
'app.password_changed_for' => array('en' => 'Password changed for:', 'nl' => 'Wachtwoord gewijzigd voor:'),
|
||||
'app.user_flags_updated' => array('en' => 'User flags updated.', 'nl' => 'Gebruikersvlaggen bijgewerkt.'),
|
||||
'app.user_deleted' => array('en' => 'User deleted.', 'nl' => 'Gebruiker verwijderd.'),
|
||||
'app.admin_rights_required' => array('en' => 'Admin rights required.', 'nl' => 'Adminrechten vereist.'),
|
||||
'app.cannot_remove_own_admin' => array('en' => 'You cannot remove your own admin rights.', 'nl' => 'Je kunt je eigen adminrechten niet verwijderen.'),
|
||||
'app.cannot_disable_self' => array('en' => 'You cannot disable your own account.', 'nl' => 'Je kunt je eigen account niet uitschakelen.'),
|
||||
'app.unknown_action' => array('en' => 'Unknown action:', 'nl' => 'Onbekende actie:'),
|
||||
));
|
||||
seed_template_translations($languageStore, 'index.html');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$action = post_value('action');
|
||||
@@ -260,7 +201,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$password = (string)post_value('password');
|
||||
|
||||
$auth->setPassword($email, $password);
|
||||
$message = t('app.password_changed_for', 'Password changed for:') . ' ' . $email;
|
||||
$message = t('app.password_changed_for', 'Password changed for: {{email}}', array('email' => $email));
|
||||
} elseif ($action === 'set_flags') {
|
||||
$userId = (int)post_value('user_id');
|
||||
$isAdmin = post_bool('is_admin');
|
||||
@@ -281,13 +222,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$userId = (int)post_value('user_id');
|
||||
|
||||
if ($userId === $currentUser->id()) {
|
||||
throw new Exception('You cannot delete your own account.');
|
||||
throw new Exception(t('app.cannot_delete_self', 'You cannot delete your own account.'));
|
||||
}
|
||||
|
||||
$auth->deleteUser($userId);
|
||||
$message = t('app.user_deleted', 'User deleted.');
|
||||
} elseif ($action !== '') {
|
||||
throw new Exception(t('app.unknown_action', 'Unknown action:') . ' ' . $action);
|
||||
throw new Exception(t('app.unknown_action', 'Unknown action: {{action}}', array('action' => $action)));
|
||||
}
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
@@ -345,20 +286,59 @@ if ($currentUser->isAdmin()) {
|
||||
}
|
||||
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="<?= h($language) ?>">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title><?= h(t('app.title', 'Racket sandbox')) ?></title>
|
||||
<link rel="stylesheet" href="/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
$generatedLinkHtml = '';
|
||||
|
||||
<div class="page">
|
||||
if ($issuedLink !== '') {
|
||||
$generatedLinkHtml = RacketSandboxTemplate::renderFile('partials/index-generated-link.html', array(
|
||||
'generated_link_label' => t('app.generated_link', 'Generated link'),
|
||||
'issued_link' => $issuedLink,
|
||||
'copy_label' => t('app.copy', 'Copy'),
|
||||
'copied_label' => t('app.copied', 'Copied'),
|
||||
));
|
||||
}
|
||||
|
||||
<?php
|
||||
render_app_header(array(
|
||||
$promptPanelHtml = '';
|
||||
|
||||
if ($issuedLink !== '') {
|
||||
$promptToolHtml = '';
|
||||
|
||||
if (count($bootstrapPrompts) > 0) {
|
||||
$promptOptionsHtml = '';
|
||||
|
||||
foreach ($bootstrapPrompts as $prompt) {
|
||||
$promptOptionsHtml .= RacketSandboxTemplate::renderFile('partials/select-option.html', array(
|
||||
'value' => $prompt['id'],
|
||||
'selected' => '',
|
||||
'label' => $prompt['name'],
|
||||
)) . "\n";
|
||||
}
|
||||
|
||||
$promptToolHtml = RacketSandboxTemplate::renderFile('partials/index-prompt-select.html', array(
|
||||
'select_prompt_label' => t('app.select_prompt', 'Select prompt'),
|
||||
'prompt_options_html' => $promptOptionsHtml,
|
||||
'copy_label' => t('app.copy', 'Copy'),
|
||||
'copied_label' => t('app.copied', 'Copied'),
|
||||
'copy_full_prompt_label' => t('app.copy_full_prompt', 'Copy full prompt'),
|
||||
'bootstrap_prompt_json' => json_encode(array(
|
||||
'link' => $issuedLink,
|
||||
'prompts' => $bootstrapPrompts,
|
||||
), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT),
|
||||
));
|
||||
} else {
|
||||
$promptToolHtml = RacketSandboxTemplate::renderFile('partials/paragraph.html', array(
|
||||
'class' => 'small',
|
||||
'text' => t('app.no_bootstrap_prompts', 'No personal prompts are available for this language. Copy a default prompt first from prompt management.'),
|
||||
));
|
||||
}
|
||||
|
||||
$promptPanelHtml = RacketSandboxTemplate::renderFile('partials/index-prompt-tool.html', array(
|
||||
'copy_full_prompt_label' => t('app.copy_full_prompt', 'Copy full prompt'),
|
||||
'bootstrap_prompt_help' => t('app.bootstrap_prompt_help', 'Choose one of your prompts. The placeholder {{bootstrap-racket-link}} is replaced by the generated bootstrap link.'),
|
||||
'prompt_tool_html' => $promptToolHtml,
|
||||
));
|
||||
}
|
||||
|
||||
$headerHtml = app_header_html(array(
|
||||
'title' => t('app.title', 'Racket sandbox'),
|
||||
'nav_items' => $headerNavItems,
|
||||
'user' => $currentUser,
|
||||
@@ -373,98 +353,20 @@ render_app_header(array(
|
||||
'message' => $message,
|
||||
'error' => $error,
|
||||
));
|
||||
?>
|
||||
|
||||
<main class="page-main dashboard-main">
|
||||
|
||||
<section class="panel">
|
||||
<h2><?= h(t('app.bootstrap_link', 'Bootstrap link')) ?></h2>
|
||||
|
||||
<fieldset>
|
||||
<legend><?= h(t('app.generate_bootstrap_link', 'Generate bootstrap link')) ?></legend>
|
||||
|
||||
<div class="bootstrap-result-grid <?= $issuedLink !== '' ? 'has-prompt' : '' ?>">
|
||||
<div>
|
||||
<form method="post" action="/?lang=<?= h($language) ?>">
|
||||
<input type="hidden" name="action" value="issue_bootstrap">
|
||||
|
||||
<label>
|
||||
<?= h(t('app.ttl_minutes', 'TTL in minutes')) ?><br>
|
||||
<input type="number"
|
||||
name="ttl_minutes"
|
||||
value="<?= h($bootstrapTtlMinutes) ?>"
|
||||
min="<?= h(BOOTSTRAP_TTL_MIN_MINUTES) ?>"
|
||||
max="<?= h(BOOTSTRAP_TTL_MAX_MINUTES) ?>">
|
||||
</label>
|
||||
<p class="small"><?= h(t('app.ttl_range_help', 'Allowed range: 30 minutes to 8 hours.')) ?></p>
|
||||
|
||||
<button type="submit"><?= h(t('app.generate_bootstrap_link', 'Generate bootstrap link')) ?></button>
|
||||
</form>
|
||||
|
||||
<?php if ($issuedLink !== ''): ?>
|
||||
<h3><?= h(t('app.generated_link', 'Generated link')) ?></h3>
|
||||
<div class="generated-link-row">
|
||||
<a href="<?= h($issuedLink) ?>" target="_blank" rel="noopener noreferrer"><?= h($issuedLink) ?></a>
|
||||
<button type="button"
|
||||
class="js-copy-button"
|
||||
data-copy-text="<?= h($issuedLink) ?>"
|
||||
data-copy-label="<?= h(t('app.copy', 'Copy')) ?>"
|
||||
data-copied-label="<?= h(t('app.copied', 'Copied')) ?>">
|
||||
<?= h(t('app.copy', 'Copy')) ?>
|
||||
</button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($issuedLink !== ''): ?>
|
||||
<div>
|
||||
<h3><?= h(t('app.copy_full_prompt', 'Copy full prompt')) ?></h3>
|
||||
<p>
|
||||
<?= h(t('app.bootstrap_prompt_help', 'Choose one of your prompts. The placeholder {{bootstrap-racket-link}} is replaced by the generated bootstrap link.')) ?>
|
||||
</p>
|
||||
|
||||
<?php if (count($bootstrapPrompts) > 0): ?>
|
||||
<div class="bootstrap-prompt-tool">
|
||||
<label>
|
||||
<?= h(t('app.select_prompt', 'Select prompt')) ?><br>
|
||||
<select id="bootstrapPromptSelect">
|
||||
<?php foreach ($bootstrapPrompts as $prompt): ?>
|
||||
<option value="<?= h($prompt['id']) ?>"><?= h($prompt['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<textarea id="bootstrapPromptOutput" readonly rows="12"></textarea>
|
||||
|
||||
<button type="button"
|
||||
class="js-copy-button"
|
||||
data-copy-target="bootstrapPromptOutput"
|
||||
data-copy-label="<?= h(t('app.copy', 'Copy')) ?>"
|
||||
data-copied-label="<?= h(t('app.copied', 'Copied')) ?>">
|
||||
<?= h(t('app.copy_full_prompt', 'Copy full prompt')) ?>
|
||||
</button>
|
||||
</div>
|
||||
<script type="application/json" id="bootstrapPromptData">
|
||||
<?= json_encode(array(
|
||||
'link' => $issuedLink,
|
||||
'prompts' => $bootstrapPrompts,
|
||||
), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>
|
||||
</script>
|
||||
<?php else: ?>
|
||||
<p class="small"><?= h(t('app.no_bootstrap_prompts', 'No personal prompts are available for this language. Copy a default prompt first from prompt management.')) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/js/clipboard.js" defer></script>
|
||||
<script src="/js/bootstrap-prompt.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
echo RacketSandboxTemplate::renderFile('index.html', array(
|
||||
'language' => $language,
|
||||
'language_url' => rawurlencode($language),
|
||||
'title' => t('app.title', 'Racket sandbox'),
|
||||
'header_html' => $headerHtml,
|
||||
'bootstrap_link_label' => t('app.bootstrap_link', 'Bootstrap link'),
|
||||
'generate_bootstrap_link_label' => t('app.generate_bootstrap_link', 'Generate bootstrap link'),
|
||||
'bootstrap_result_class' => $issuedLink !== '' ? 'has-prompt' : '',
|
||||
'ttl_minutes_label' => t('app.ttl_minutes', 'TTL in minutes'),
|
||||
'bootstrap_ttl_minutes' => $bootstrapTtlMinutes,
|
||||
'bootstrap_ttl_min_minutes' => BOOTSTRAP_TTL_MIN_MINUTES,
|
||||
'bootstrap_ttl_max_minutes' => BOOTSTRAP_TTL_MAX_MINUTES,
|
||||
'ttl_range_help' => t('app.ttl_range_help', 'Allowed range: 30 minutes to 8 hours.'),
|
||||
'generated_link_html' => $generatedLinkHtml,
|
||||
'prompt_panel_html' => $promptPanelHtml,
|
||||
));
|
||||
|
||||
Reference in New Issue
Block a user