chore(renamed): removed froxy prefix on folders

This commit is contained in:
Maieul BOYER 2026-02-08 18:13:20 +01:00
parent e57bb34a35
commit 6960959794
Signed by: maix
SSH key fingerprint: SHA256:iqCzqFFF5KjRixmDExqbAltCIj9ndlBWIGJf3t9Ln9g
29 changed files with 32 additions and 12 deletions

30
templates/src/friends.rs Normal file
View file

@ -0,0 +1,30 @@
pub use crate::templates::FRIENDS;
pub fn add_to_context(env: &mut minijinja::Environment) {
if env.get_template("template.html").is_err() {
crate::meta_template::add_to_context(env);
}
if env.get_template("open_modal.html").is_err() {
crate::open_modal::add_to_context(env);
}
env.add_template("friends.html", FRIENDS).unwrap();
}
pub fn render(env: &minijinja::Environment, data: FriendsData) -> Result<String, minijinja::Error> {
let template = env.get_template("friends.html")?;
template.render(data)
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Friend {
pub name: smol_str::SmolStr,
pub image: String,
pub position: Option<smol_str::SmolStr>,
pub last_active: Option<smol_str::SmolStr>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct FriendsData {
pub friends: Vec<Friend>,
}

121
templates/src/index.rs Normal file
View file

@ -0,0 +1,121 @@
use std::collections::HashMap;
use smol_str::SmolStr;
pub use crate::templates::INDEX;
pub fn add_to_context(env: &mut minijinja::Environment) {
if env.get_template("template.html").is_err() {
crate::meta_template::add_to_context(env);
}
if env.get_template("open_modal.html").is_err() {
crate::open_modal::add_to_context(env);
}
env.add_template("index.html", INDEX).unwrap();
env.add_function("max", |a: i32, b: i32| -> i32 { a.max(b) });
}
pub fn render(env: &minijinja::Environment, data: IndexData) -> Result<String, minijinja::Error> {
let template = env.get_template("index.html")?;
template.render(data)
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LocationData {
pub login: SmolStr,
pub image: String,
pub me: bool,
pub friend: bool,
pub close_friend: bool,
pub pooled: bool,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ClusterData {
pub users: u32,
pub dead_pc: u32,
pub max_pc: u32,
pub silent: bool,
pub piscine: bool,
pub map: Map,
pub issues: HashMap<SmolStr, PcIssue>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(try_from = "SmolStr", into = "SmolStr")]
pub enum PcIssue {
#[default]
Dead,
Attention,
}
impl TryFrom<SmolStr> for PcIssue {
type Error = &'static str;
fn try_from(value: SmolStr) -> Result<Self, &'static str> {
Ok(match value.as_str() {
"dead" => Self::Dead,
"attention" => Self::Attention,
_ => return Err("Invalid string"),
})
}
}
impl From<PcIssue> for SmolStr {
fn from(val: PcIssue) -> Self {
match val {
PcIssue::Dead => SmolStr::new_static("dead"),
PcIssue::Attention => SmolStr::new_static("attention"),
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(from = "SmolStr", into = "SmolStr")]
pub enum MapPos {
Wall,
Flex,
Empty,
Door,
Pc(SmolStr),
}
impl From<SmolStr> for MapPos {
fn from(value: SmolStr) -> Self {
match value.as_str() {
"x" => Self::Wall,
"f" => Self::Flex,
"" => Self::Empty,
"d" => Self::Door,
_ => Self::Pc(value),
}
}
}
impl From<MapPos> for SmolStr {
fn from(val: MapPos) -> Self {
match val {
MapPos::Wall => SmolStr::new_static("x"),
MapPos::Flex => SmolStr::new_static("f"),
MapPos::Door => SmolStr::new_static("d"),
MapPos::Empty => SmolStr::new_static(""),
MapPos::Pc(s) => s,
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MapRow {
pub range: SmolStr,
pub col: Vec<MapPos>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Map {
pub rows: Vec<MapRow>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct IndexData {
pub locations: HashMap<SmolStr, LocationData>,
pub clusters: HashMap<SmolStr, ClusterData>,
pub current_cluster: SmolStr,
}

9
templates/src/lib.rs Normal file
View file

@ -0,0 +1,9 @@
#![allow(dead_code)]
mod templates;
pub mod friends;
pub mod index;
pub mod meta_template;
pub mod open_modal;
pub mod profile;

View file

@ -0,0 +1,5 @@
pub use crate::templates::TEMPLATE;
pub fn add_to_context(env: &mut minijinja::Environment) {
env.add_template("template.html", TEMPLATE).unwrap();
}

View file

@ -0,0 +1,5 @@
pub use crate::templates::OPEN_MODAL as TEMPLATE;
pub fn add_to_context(env: &mut minijinja::Environment) {
env.add_template("open_modal.html", TEMPLATE).unwrap();
}

41
templates/src/profile.rs Normal file
View file

@ -0,0 +1,41 @@
use smol_str::SmolStr;
pub use crate::templates::PROFILE as TEMPLATE;
pub fn add_to_context(env: &mut minijinja::Environment) {
if env.get_template("template.html").is_err() {
crate::meta_template::add_to_context(env);
}
if env.get_template("open_modal.html").is_err() {
crate::open_modal::add_to_context(env);
}
env.add_template("profile.html", TEMPLATE).unwrap();
}
pub fn render(env: &minijinja::Environment, data: ProfileData) -> Result<String, minijinja::Error> {
let template = env.get_template("profile.html")?;
template.render(data)
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ProfileUser {
pub position: Option<SmolStr>,
pub last_active: Option<SmolStr>,
pub image: String,
pub name: SmolStr,
pub pool: Option<SmolStr>,
pub is_friend: bool,
pub github: Option<String>,
pub website: Option<String>,
pub discord: Option<String>,
/// profile description
pub recit: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ProfileData {
pub user: ProfileUser,
}

View file

@ -0,0 +1,145 @@
{% extends 'template.html' %}
{% block css %}
<link href="/static/css/friends.css?v={{ version }}" rel="stylesheet">
{% endblock %}
{% block content %}
<div class="modal fade" id="addFriendModal" tabindex="-1" aria-labelledby="addFriendLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="addFriendLabel">Ajouter un ami</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="input-group mb-3">
<span class="input-group-text" id="loginAddon">Login</span>
<input autofocus list="suggestions" type="text" class="form-control" id="addFriendInput"
aria-label="Login42" aria-describedby="loginAddon" placeholder="ami1, ami2, ami3">
<datalist id="suggestions"></datalist>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
<button type="button" class="btn btn-primary" id="addFriendButton">
<i class="fa-solid fa-user-plus"></i> <i hidden class="spinner-border spinner-border-sm"></i>
Ajouter en ami
</button>
</div>
</div>
</div>
</div>
{% include 'open_modal.html' %}
<div class="container">
<div class="row text-center justify-content-center">
{% for friend in friends %}
<div id="case-{{ friend.name }}" class="card shadow m-2 pl-fix card-size grow"
onclick="openFriend('{{ friend.name }}', true)">
<img src="{{ friend.image | safe }}" class="m-1 card-img-top card-img-size"
alt="{{ friend.name }}'s image">
<div class="card-body">
<h5 class="card-title">
<i class="fa-solid {{ "fa-2xs fa-circle online" if friend.position else "fa-xs fa-person-walking offline" }}"></i> {{ friend.name }}
</h5>
{%- set position = "Absent" -%}
{% if friend.position %}
{%- set position = friend.position -%}
{% endif%}
{%- set last_active = "" -%}
{% if friend.last_active %}
{%- set last_active = friend.last_active -%}
{% endif%}
<p class="card-text">{{ position }} {{ last_active }}
{% if friend.position %}
<a class="fa-solid fa-users-viewfinder" href="/goto/{{ friend.position }}"></a>
{% endif %}
</p>
</div>
</div>
{% endfor %}
<div class="card p-0 m-2 shadow card-size grow" onclick="openAddFriend();">
<div class="m-1 w-100 card-img-top text-center card-not-img-size">
<i class="fa-solid fa-plus fa-5x"></i>
</div>
<div class="card-body">
<h5 class="card-title">Ajouter un ami</h5>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
let openAddFriendModal;
let openFriendModal;
let addFriendInput = document.getElementById('addFriendInput');
let addFriendButton = document.getElementById('addFriendButton');
function fillSuggestions(element, suggestions) {
let list = document.querySelector(element);
list.innerHTML = ''
suggestions.forEach(function (item) {
if (item['type'] !== "user") return;
let option = document.createElement('option');
option.value = item['v'];
list.appendChild(option);
});
}
function openAddFriend() {
const modal = new bootstrap.Modal('#addFriendModal', {});
openAddFriendModal = modal;
modal.show();
setTimeout(() => {
addFriendInput.focus();
addFriendInput.select();
}, 500)
}
async function deleteLocalFriend() {
let friend_name = document.getElementById('openFriendLabel').innerText.trim();
let resp = await deleteFriend(friend_name, "#deleteFriend")
if (resp === 200) {
openFriendModal.hide();
document.getElementById('case-' + friend_name).remove();
}
}
addFriendInput.addEventListener('keyup', function (key) {
let name = addFriendInput.value.trim().toLowerCase();
if (key.key === 'Enter') {
if (name.length <= 3) {
addFriendInput.focus();
return;
}
addFriend(name, '#addFriendButton', true);
fillSuggestions('#suggestions', []);
}
if (name.trim() === '') return fillSuggestions('#suggestions', []);
if (name.length < 3) return;
setTimeout(() => {
if (name !== addFriendInput.value) return;
fetch('/search/' + encodeURIComponent(name) + "/1").then((response) => {
response.json().then((json) => {
fillSuggestions('#suggestions', json)
})
})
}, 200)
})
addFriendButton.addEventListener('click', function () {
let val = addFriendInput.value.trim().toLowerCase();
if (val.length <= 3) {
addFriendInput.focus();
return;
}
addFriend(val, '#addFriendButton', true)
})
</script>
{% endblock %}

View file

@ -0,0 +1,238 @@
{% extends 'template.html' %}
{% block css %}
<link href="/static/css/friends.css?v={{ version }}" rel="stylesheet">
<link href="/static/css/index.css?v={{ version }}" rel="stylesheet">
<style>
@media (max-width: 800px) {
.btn-width {
width: 100%;
}
}
.btn-width {
overflow-x: auto;
}
.range-no-color {
min-width: 0 !important;
}
.btn-check:checked+.btn-outline-warning > .text-muted {
color: black!important;
}
</style>
{% endblock %}
{% block content %}
{% include 'open_modal.html' %}
<div class="modal fade" id="issueModal" tabindex="-1" aria-labelledby="issueModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="issueModalLabel">Signalement dumps</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close report"></button>
</div>
<div class="modal-body">
Le dump <span id="dumpIdIssueModal"></span> ne fonctionne pas comme prévu ? Signalez le problème
pour avertir que la place n'est pas
libre
<div class="form-check" id="issueForm">
<input class="form-check-input" type="radio" data-id="1" name="issueRadios" id="issueRadios1">
<label class="form-check-label" for="issueRadios1">
Le dump est inutilisable
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" data-id="2" name="issueRadios" id="issueRadios2">
<label class="form-check-label" for="issueRadios2">
Le dump fonctionne, mais l'écran ou le clavier est dégradé
</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
<button type="button" class="btn btn-primary" id="submitIssue">Valider</button>
</div>
</div>
</div>
</div>
<div class="text-center">
<div class="btn-group mb-1 btn-width" role="group" aria-label="Change cluster">
{% for name in clusters %}
<input type="radio" class="btn-check cluster_radios" name="btn-radio"
data-cluster="{{ name }}" id="radio{{ name }}"
autocomplete="off">
{% set cluster = clusters[name] %}
{% set piscine = "" %}
{% set silent = "" %}
{% set spacer = "" %}
{%- if cluster.piscine -%}
{% set piscine = '<i class="fa-solid fa-person-swimming text-info"></i>' %}
{%- endif -%}
{%- if cluster.silent -%}
{% set piscine = '<i class="fa-solid fa-volume-xmark text-secondary"></i>' %}
{%- endif -%}
{%- if cluster.piscine and cluster.silent -%}
{% set spacer = " " %}
{%- endif -%}
{% set u = cluster.users %}
{% set m = cluster.max_pc - cluster.dead_pc %}
{%- if m < 0 -%}
{% set m = 0 %}
{%- endif -%}
{% set percent = 0 %}
{%- if m > 0 -%}
{% set percent = (u * 100) / m %}
{%- endif -%}
{% set fill_level = 'primary' %}
{%- if percent > 75 -%}
{% set fill_level = 'danger' %}
{%- elif percent > 65 -%}
{% set fill_level = 'warning' %}
{% endif%}
<label title="{{ percent | int }}%" class="btn btn-outline-{{ fill_level }} m-t" for="radio{{ name }}">
{{ piscine | safe }}{{ spacer | safe }}{{ silent | safe }} {{ name }}
<span class="text-muted">{{ u }}/{{ m }}</span></label>
{%- endfor -%}
</div>
</div>
<div class="container-fluid mt-2 scroll p-0">
<table class="grid text-center">
<tbody>
{% set map = clusters[current_cluster].map %}
{% set cluster = clusters[current_cluster] %}
{% for row in map.rows %}
<tr>
{%- set countB = 0 -%}
<td class="range">{{ row.range }}</td>
{%- for col in row.col -%}
{%- if col == 'x' -%}
<td class="range">&nbsp;</td>
{%- elif col == '|' -%}
<td class="range-no-color"></td>
{%- elif col == 'h' -%}
<td></td>
{%- elif col == 'd' -%}
<td><i class="fa-solid fa-person-walking fa-2xl"></i></td>
{%- elif col == 'f' -%}
<td><i class="fa-solid fa-laptop fa-xl"></i></td>
{%- elif col == '' -%}
<td class="small-colors"></td>
{%- else -%}
{%- set text_color = '' -%}
{%- set img = '<i class="fa-solid fa-user"></i>' -%}
{%- if col in locations -%}
{%- set img = '<img loading="lazy" class="profile-pic2" alt="" src="' + locations[col].image + '">' -%}
{%- endif -%}
{%- set relation = '' -%}
{%- if focus and focus == col %}
{%- set relation = 'focus' -%}
{%- elif col in locations and locations[col].me -%}
{%- set relation = 'me' -%}
{%- elif col in locations and locations[col].close_friend -%}
{%- set relation = 'close_friend' -%}
{%- elif col in locations and locations[col].friend -%}
{%- set relation = 'friend' -%}
{%- elif col in cluster.issues -%}
{%- set relation = cluster.issues[col] -%}
{%- elif col in locations and locations[col]['pool'] and not cluster.piscine -%}
{%- set relation = 'pooled' -%}
{%- endif -%}
{% set loc_name = col|replace(current_cluster, "") %}
<td data-pos="{{ col }}"
data-login="{{ locations[col].login if col in locations else '' }}"
class="sm-t case {{ relation }}">
{%- if countB % 2 == 0 -%}
{{ img|safe }}<br>{{ loc_name }}
{%- else -%}
{{ loc_name }}<br>{{ img|safe }}
{%- endif -%}
</td>
{%- endif -%}
{%- set countB = (countB + 1) % 2 -%}
{%- endfor -%}
<td class="range">{{ row.range }}</td>
</tr>
{%- endfor -%}
</tbody>
</table>
<div id="tooltip" hidden class="rounded bg-white zindex9999">
<img class="profile-pic rounded no-shadow" alt="pp">
<div class="fluid-container text-center text-black name">name</div>
</div>
</div>
<script src="/static/js/popper.min.js?v={{ version }}" defer></script>
{% endblock %}
{% block scripts %}
<script>
let tooltip = document.getElementById('tooltip')
let cases = document.querySelectorAll('.case');
cases.forEach(e => {
e.addEventListener('click', () => {
let login = e.dataset.login
let dump = e.dataset.pos
if (login.length === 0) {
const modal = new bootstrap.Modal('#issueModal', {});
document.getElementById('submitIssue').onclick = async function () {
let checked = document.querySelector('input[name="issueRadios"]:checked');
if (!checked) {
document.getElementById('issueRadios2').focus()
return;
}
let resp = checked.dataset.id;
fetch(`/addissue/${dump}/${resp}`).then(resp => {
if (resp.status === 200) {
triggerToast('Merci de votre signalement ! :)', true, false);
modal.hide();
} else {
triggerToast("Une erreur s'est produite lors du signalement", false, false);
}
})
}
document.getElementById('dumpIdIssueModal').innerText = dump;
modal.show();
return;
}
openFriend(login, true)
})
if (!e.dataset.login)
return;
e.addEventListener('mouseover', () => {
if (window.isTouchDevice && isTouchDevice()) return;
if (!window.Popper) return;
tooltip.hidden = false;
tooltip.querySelector('img').src = e.querySelector('img').src
tooltip.querySelector('.name').innerText = e.dataset.login
Popper.createPopper(e, tooltip, {
placement: 'right'
})
})
e.addEventListener('mouseleave', () => {
tooltip.hidden = true;
});
});
// Change cluster buttons
let radio_clusters = document.querySelectorAll('.cluster_radios')
radio_clusters.forEach(e => {
e.addEventListener('click', () => {
let cluster = e.dataset.cluster
location.href = "/?cluster=" + cluster
})
})
</script>
{% endblock %}

View file

@ -0,0 +1,11 @@
macro_rules! template {
($name:ident, $path:literal) => {
pub static $name: &'static str = include_str!($path);
};
}
template!(FRIENDS, "./friends.html");
template!(INDEX, "./index.html");
template!(OPEN_MODAL, "./open_modal.html");
template!(PROFILE, "./profile.html");
template!(TEMPLATE, "./template.html");

View file

@ -0,0 +1,76 @@
<div class="modal fade" id="openFriendModal" tabindex="-1" aria-labelledby="openFriendLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5 me-2" id="openFriendLabel"></h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<!-- PP - Name - Pool -->
<div class="modal-body">
<div class="mb-3 text-center">
<img alt="" class="rounded-pill profile-pic" style="height:9rem" id="openFriendModalPic" src="">
<div class="display-inline-grid ms-3">
<span id="openFriendModalName">
<h5 class="name display-5 fw-bold"></h5>
<span id="addCloseFriend">
<i class="fa-regular fa-star text-warning"></i>
<i hidden class="spinner-border spinner-border-sm"></i>
</span>
<span id="removeCloseFriend">
<i class="fa-solid fa-star text-warning"></i>
<i hidden class="spinner-border spinner-border-sm"></i>
</span>
<span class="fs-6 text-muted pool">Piscine de</span>
</span>
</div>
</div>
<!-- Bio & Socials -->
<div hidden class="rounded bg-dark-subtle m-2 p-1" id="FP-bio">
<div hidden class="row text-center" id="FP-socials-row">
<div hidden class="col no-wrap" id="FP-github">
<i class="fa-brands fa-github icon-length"></i>
<a class="{{ 'no-click' if kiosk else '' }}" href=""></a>
</div>
<div hidden class="col no-wrap" id="FP-website">
<i class="fa-solid fa-globe icon-length"></i>
<a class="{{ 'no-click' if kiosk else '' }}" href=""></a>
</div>
<div hidden class="col no-wrap" id="FP-discord">
<i class="fa-brands fa-discord icon-length"></i> <a></a>
</div>
</div>
<div hidden class="div text-center m-1 justify" style="text-align: justify" id="FP-text"></div>
</div>
<!-- Buttons -->
<div class="row text-center">
<div class="col-md mb-1">
<button type="button" class="btn btn-sm btn-primary" id="openFriendShowCluster">
<i class="fa-solid fa-magnifying-glass"></i> Afficher dans le Cluster
</button>
</div>
<div class="col-md mb-1">
<a id="openFriendProfile">
<button type="button" class="btn btn-sm btn-secondary">
<i class="fa-solid fa-circle-info"></i> Voir l'intra de l'utilisateur
</button>
</a>
</div>
<div class="col-md mb-1">
<button hidden type="button" class="btn btn-sm btn-danger" id="openFriendLabelDeleteFriend">
<i class="fa-solid fa-user-minus"></i>
<i hidden class="spinner-border spinner-border-sm"></i>
Supprimer des contacts
</button>
<button type="button" class="btn btn-sm btn-success" id="openFriendLabelAddFriend">
<i class="fa-solid fa-user-plus"></i><i hidden class="spinner-border spinner-border-sm"></i>
Ajouter au contacts
</button>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,109 @@
{% extends 'template.html' %}
{% block css %}
<style>
.align-vertical {
vertical-align: middle;
}
.badge-sm {
font-size: 1rem;
}
</style>
<link href="/static/css/friends.css?v={{ version }}" rel="stylesheet">
<link href="/static/css/profile.css?v={{ version }}" rel="stylesheet">
{% endblock %}
{% block content %}
{% include 'open_modal.html' %}
<div class="container mt-2">
<div class="p-5 mb-4 bg-light-subtle rounded-3 shadow-sm">
<div class="container-fluid py-5">
<div class="mb-4 text-center">
<img class="rounded-pill profile-pic" style="height:9rem" src="{{ user.image }}" alt="{{ user.name }}'s pic">
<div class="display-inline-grid ms-3">
<h1 class="display-5 fw-bold">
{{ user.name }}
{% if user.pool %}<span class="fs-6 text-muted ">Piscine de {{ user.pool }}</span>{% endif %}
</h1>
<span class="text-left">
<i class="fa-solid {{ "fa-2=xs fa-circle online" if user.position else "fa-xs fa-person-walking offline" }}"></i>
{{ user.position if user.position else "Absent" }} {{ user.last_active if user.last_active or ""}}
{% if user.position %}<a class="fa-solid fa-users-viewfinder" href="/goto/{{ user.position }}"></a>{% endif %}
</span>
<span class="text-left">
<button {{ 'hidden' if user.is_friend else '' }}
type="button"
class="btn btn-sm btn-outline-success online"
id="addLocalFriend">
<i class="fa-solid fa-user-plus"></i>
<i hidden class="spinner-border spinner-border-sm"></i>
Ajouter {{ user.name }} en ami
</button>
<button {{ 'hidden' if not user.is_friend else '' }}
type="button"
class="btn btn-sm btn-outline-danger"
id="removeLocalFriend">
<i class="fa-solid fa-user-minus"></i>
<i hidden class="spinner-border spinner-border-sm"></i>
Retirer {{ user.name }} des amis
</button>
<button type="button"
onclick="newTab('https://profile.intra.42.fr/users/{{ user.name }}');"
class="btn btn-sm btn-outline-secondary">
<i class="fa-solid fa-circle-info"></i>
Profil intra de {{ user.name }}
</button>
</span>
</div>
</div>
<div class="fs-5">
<div class="row">
<div class="col-md-3">
{%- if user.github or user.website or user.discord -%}
<p class="mb-0">Contacts</p>
<ul>
{%- if 'github' in user and user.github -%}
<li>
<i class="fa-brands fa-github icon-length"></i> <a href="{{ user.github }}">{{ user.github |replace("https://github.com/", "") }}</a>
</li>
{%- endif -%}
{%- if 'website' in user and user.website -%}
<li>
<i class="fa-solid fa-globe icon-length"></i> <a href="{{ user.website }}">{{ user.website |replace("https://", "")|replace("http://", "") }}</a>
</li>
{%- endif -%}
{%- if 'discord' and user.discord -%}
<li>
<i class="fa-brands fa-discord icon-length"></i> <a>{{ user.discord }}</a>
</li>
{%- endif -%}
</ul>
{% endif %}
</div>
<div class="col-md-8" style="text-align: justify;">{{ user.recit if user.recit and user.recit else '' }}</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
let addLocalFriend = document.getElementById('addLocalFriend');
let removeLocalFriend = document.getElementById('removeLocalFriend');
if (addLocalFriend)
addLocalFriend.addEventListener('click', async () => {
await addFriend('{{ user.name }}', '#addLocalFriend');
addLocalFriend.hidden = true;
removeLocalFriend.hidden = false;
})
if (removeLocalFriend)
removeLocalFriend.addEventListener('click', async () => {
await deleteFriend('{{ user.name }}', '#removeLocalFriend');
addLocalFriend.hidden = false;
removeLocalFriend.hidden = true;
})
</script>
{% endblock %}

View file

@ -0,0 +1,81 @@
{%- set version='9' %}
<!doctype html>
<html lang="fr" data-bs-theme="dark">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>FFT - Maix.me</title>
<link href="/static/css/bootstrap.min.css?v={{ version }}" rel="stylesheet">
<link href="/static/css/common.css?v={{ version }}" rel="stylesheet">
<link rel="manifest" href="/static/manifest.json?v={{ version }}">
<link rel="shortcut icon" type="image/x-icon" href="/static/img/favicon.ico">
<link rel="apple-touch-icon" href="/static/img/apple-touch-icon.png"/>
<meta name="theme-color" content="rgb(43, 48, 53)"/>
{% block css %}{% endblock %}
</head>
<body>
<nav class="navbar navbar-expand-lg bg-body-tertiary mb-2 shadow">
<div class="container-fluid">
<a class="navbar-brand me-2 position-relative " href="/">
<img src="/static/img/android-chrome-192x192.png" alt="Logo" width="24" height="24" class="d-inline-block align-text-top">
FFT<span class="text-muted text-beta"></span>
</a>
<button class="btn btn-secondary hide-navbar" hidden id="qc-friends">
<i class="fa-solid fa-user-group"></i>
</button>
<button class="btn btn-secondary hide-navbar" hidden id="qc-cluster">
<i class="fa-solid fa-layer-group"></i>
</button>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/">Clusters</a>
</li>
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/friends/">Amis</a>
</li>
</ul>
<div class="d-flex" role="search">
<button type="button" class="btn btn-outline-secondary me-1" onclick="newTab('https://github.com/maix0/fft');" aria-label="Github">
<i class="fa-brands fa-github"></i> <i hidden class="spinner-border spinner-border-sm"></i>
</button>
<input class="form-control" list="global_suggestions" id="globalSearch" type="search" placeholder="Rechercher..." aria-label="Search">
<datalist id="global_suggestions"></datalist>
<button type="button" id="globalSearchButton" aria-label="Search" class="btn btn-outline-light mx-1">
<i class="fa-solid fa-magnifying-glass"></i>
</button>
<button type="button" class="btn btn-outline-light" aria-label="Settings"
onclick="location.href = '/settings/';">
<i class="fa-solid fa-gear"></i>
</button>
</div>
</div>
</div>
</nav>
<div class="toast-container position-fixed top-0 end-0 p-3">
<div id="liveToast" class="toast align-items-center text-bg-danger border-0" role="alert" aria-live="assertive"
aria-atomic="true">
<div class="d-flex">
<div class="toast-body" id="toast_body">
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"
aria-label="Close"></button>
</div>
</div>
</div>
{% block content %}{% endblock %}
<script async src="/static/js/bootstrap.min.js?v={{ version }}"></script>
<script src="/static/js/common.js?v={{ version }}"></script>
{% block scripts %}{% endblock %}
<link href="/static/fontawesome/css/fontawesome.min.css?v={{ version }}" rel="stylesheet">
<link href="/static/fontawesome/css/solid.min.css?v={{ version }}" rel="stylesheet">
<link href="/static/fontawesome/css/regular.min.css?v={{ version }}" rel="stylesheet">
<link href="/static/fontawesome/css/brands.min.css?v={{ version }}" rel="stylesheet">
</body>
</html>