feat(planning): grille hebdomadaire complète avec API et filtres
- Connexion API via proxy Angular (résolution CORS, base path /api) - Import CSS ng-zorro global pour les modales et composants - Filtres Camion/Show câblés sur l'affichage de la grille - Camions affichés via TrucksService (linkés au show du même créneau) - Panneau de détails : spectacles + camions du jour sélectionné - Modale de création de spectacle stylisée avec fond et centrage - Positionnement précis des events à la minute dans leur créneau - Auto-scroll vers l'heure courante au chargement - Ligne "maintenant" sur la colonne du jour actuel - Régénération des services OpenAPI (nouveaux noms de types) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+71
-69
@@ -20,10 +20,10 @@ export class UriTemplate {
|
||||
}
|
||||
}
|
||||
get variableNames() {
|
||||
return this.parts.flatMap(part => (typeof part === 'string' ? [] : part.names));
|
||||
return this.parts.flatMap((part) => typeof part === 'string' ? [] : part.names);
|
||||
}
|
||||
constructor(template) {
|
||||
UriTemplate.validateLength(template, MAX_TEMPLATE_LENGTH, 'Template');
|
||||
UriTemplate.validateLength(template, MAX_TEMPLATE_LENGTH, "Template");
|
||||
this.template = template;
|
||||
this.parts = this.parse(template);
|
||||
}
|
||||
@@ -32,30 +32,30 @@ export class UriTemplate {
|
||||
}
|
||||
parse(template) {
|
||||
const parts = [];
|
||||
let currentText = '';
|
||||
let currentText = "";
|
||||
let i = 0;
|
||||
let expressionCount = 0;
|
||||
while (i < template.length) {
|
||||
if (template[i] === '{') {
|
||||
if (template[i] === "{") {
|
||||
if (currentText) {
|
||||
parts.push(currentText);
|
||||
currentText = '';
|
||||
currentText = "";
|
||||
}
|
||||
const end = template.indexOf('}', i);
|
||||
const end = template.indexOf("}", i);
|
||||
if (end === -1)
|
||||
throw new Error('Unclosed template expression');
|
||||
throw new Error("Unclosed template expression");
|
||||
expressionCount++;
|
||||
if (expressionCount > MAX_TEMPLATE_EXPRESSIONS) {
|
||||
throw new Error(`Template contains too many expressions (max ${MAX_TEMPLATE_EXPRESSIONS})`);
|
||||
}
|
||||
const expr = template.slice(i + 1, end);
|
||||
const operator = this.getOperator(expr);
|
||||
const exploded = expr.includes('*');
|
||||
const exploded = expr.includes("*");
|
||||
const names = this.getNames(expr);
|
||||
const name = names[0];
|
||||
// Validate variable name length
|
||||
for (const name of names) {
|
||||
UriTemplate.validateLength(name, MAX_VARIABLE_LENGTH, 'Variable name');
|
||||
UriTemplate.validateLength(name, MAX_VARIABLE_LENGTH, "Variable name");
|
||||
}
|
||||
parts.push({ name, operator, names, exploded });
|
||||
i = end + 1;
|
||||
@@ -71,73 +71,75 @@ export class UriTemplate {
|
||||
return parts;
|
||||
}
|
||||
getOperator(expr) {
|
||||
const operators = ['+', '#', '.', '/', '?', '&'];
|
||||
return operators.find(op => expr.startsWith(op)) || '';
|
||||
const operators = ["+", "#", ".", "/", "?", "&"];
|
||||
return operators.find((op) => expr.startsWith(op)) || "";
|
||||
}
|
||||
getNames(expr) {
|
||||
const operator = this.getOperator(expr);
|
||||
return expr
|
||||
.slice(operator.length)
|
||||
.split(',')
|
||||
.map(name => name.replace('*', '').trim())
|
||||
.filter(name => name.length > 0);
|
||||
.split(",")
|
||||
.map((name) => name.replace("*", "").trim())
|
||||
.filter((name) => name.length > 0);
|
||||
}
|
||||
encodeValue(value, operator) {
|
||||
UriTemplate.validateLength(value, MAX_VARIABLE_LENGTH, 'Variable value');
|
||||
if (operator === '+' || operator === '#') {
|
||||
UriTemplate.validateLength(value, MAX_VARIABLE_LENGTH, "Variable value");
|
||||
if (operator === "+" || operator === "#") {
|
||||
return encodeURI(value);
|
||||
}
|
||||
return encodeURIComponent(value);
|
||||
}
|
||||
expandPart(part, variables) {
|
||||
if (part.operator === '?' || part.operator === '&') {
|
||||
if (part.operator === "?" || part.operator === "&") {
|
||||
const pairs = part.names
|
||||
.map(name => {
|
||||
.map((name) => {
|
||||
const value = variables[name];
|
||||
if (value === undefined)
|
||||
return '';
|
||||
return "";
|
||||
const encoded = Array.isArray(value)
|
||||
? value.map(v => this.encodeValue(v, part.operator)).join(',')
|
||||
? value.map((v) => this.encodeValue(v, part.operator)).join(",")
|
||||
: this.encodeValue(value.toString(), part.operator);
|
||||
return `${name}=${encoded}`;
|
||||
})
|
||||
.filter(pair => pair.length > 0);
|
||||
.filter((pair) => pair.length > 0);
|
||||
if (pairs.length === 0)
|
||||
return '';
|
||||
const separator = part.operator === '?' ? '?' : '&';
|
||||
return separator + pairs.join('&');
|
||||
return "";
|
||||
const separator = part.operator === "?" ? "?" : "&";
|
||||
return separator + pairs.join("&");
|
||||
}
|
||||
if (part.names.length > 1) {
|
||||
const values = part.names.map(name => variables[name]).filter(v => v !== undefined);
|
||||
const values = part.names
|
||||
.map((name) => variables[name])
|
||||
.filter((v) => v !== undefined);
|
||||
if (values.length === 0)
|
||||
return '';
|
||||
return values.map(v => (Array.isArray(v) ? v[0] : v)).join(',');
|
||||
return "";
|
||||
return values.map((v) => (Array.isArray(v) ? v[0] : v)).join(",");
|
||||
}
|
||||
const value = variables[part.name];
|
||||
if (value === undefined)
|
||||
return '';
|
||||
return "";
|
||||
const values = Array.isArray(value) ? value : [value];
|
||||
const encoded = values.map(v => this.encodeValue(v, part.operator));
|
||||
const encoded = values.map((v) => this.encodeValue(v, part.operator));
|
||||
switch (part.operator) {
|
||||
case '':
|
||||
return encoded.join(',');
|
||||
case '+':
|
||||
return encoded.join(',');
|
||||
case '#':
|
||||
return '#' + encoded.join(',');
|
||||
case '.':
|
||||
return '.' + encoded.join('.');
|
||||
case '/':
|
||||
return '/' + encoded.join('/');
|
||||
case "":
|
||||
return encoded.join(",");
|
||||
case "+":
|
||||
return encoded.join(",");
|
||||
case "#":
|
||||
return "#" + encoded.join(",");
|
||||
case ".":
|
||||
return "." + encoded.join(".");
|
||||
case "/":
|
||||
return "/" + encoded.join("/");
|
||||
default:
|
||||
return encoded.join(',');
|
||||
return encoded.join(",");
|
||||
}
|
||||
}
|
||||
expand(variables) {
|
||||
let result = '';
|
||||
let result = "";
|
||||
let hasQueryParam = false;
|
||||
for (const part of this.parts) {
|
||||
if (typeof part === 'string') {
|
||||
if (typeof part === "string") {
|
||||
result += part;
|
||||
continue;
|
||||
}
|
||||
@@ -145,34 +147,34 @@ export class UriTemplate {
|
||||
if (!expanded)
|
||||
continue;
|
||||
// Convert ? to & if we already have a query parameter
|
||||
if ((part.operator === '?' || part.operator === '&') && hasQueryParam) {
|
||||
result += expanded.replace('?', '&');
|
||||
if ((part.operator === "?" || part.operator === "&") && hasQueryParam) {
|
||||
result += expanded.replace("?", "&");
|
||||
}
|
||||
else {
|
||||
result += expanded;
|
||||
}
|
||||
if (part.operator === '?' || part.operator === '&') {
|
||||
if (part.operator === "?" || part.operator === "&") {
|
||||
hasQueryParam = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
escapeRegExp(str) {
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
partToRegExp(part) {
|
||||
const patterns = [];
|
||||
// Validate variable name length for matching
|
||||
for (const name of part.names) {
|
||||
UriTemplate.validateLength(name, MAX_VARIABLE_LENGTH, 'Variable name');
|
||||
UriTemplate.validateLength(name, MAX_VARIABLE_LENGTH, "Variable name");
|
||||
}
|
||||
if (part.operator === '?' || part.operator === '&') {
|
||||
if (part.operator === "?" || part.operator === "&") {
|
||||
for (let i = 0; i < part.names.length; i++) {
|
||||
const name = part.names[i];
|
||||
const prefix = i === 0 ? '\\' + part.operator : '&';
|
||||
const prefix = i === 0 ? "\\" + part.operator : "&";
|
||||
patterns.push({
|
||||
pattern: prefix + this.escapeRegExp(name) + '=([^&]+)',
|
||||
name
|
||||
pattern: prefix + this.escapeRegExp(name) + "=([^&]+)",
|
||||
name,
|
||||
});
|
||||
}
|
||||
return patterns;
|
||||
@@ -180,31 +182,31 @@ export class UriTemplate {
|
||||
let pattern;
|
||||
const name = part.name;
|
||||
switch (part.operator) {
|
||||
case '':
|
||||
pattern = part.exploded ? '([^/,]+(?:,[^/,]+)*)' : '([^/,]+)';
|
||||
case "":
|
||||
pattern = part.exploded ? "([^/]+(?:,[^/]+)*)" : "([^/,]+)";
|
||||
break;
|
||||
case '+':
|
||||
case '#':
|
||||
pattern = '(.+)';
|
||||
case "+":
|
||||
case "#":
|
||||
pattern = "(.+)";
|
||||
break;
|
||||
case '.':
|
||||
pattern = '\\.([^/,]+)';
|
||||
case ".":
|
||||
pattern = "\\.([^/,]+)";
|
||||
break;
|
||||
case '/':
|
||||
pattern = '/' + (part.exploded ? '([^/,]+(?:,[^/,]+)*)' : '([^/,]+)');
|
||||
case "/":
|
||||
pattern = "/" + (part.exploded ? "([^/]+(?:,[^/]+)*)" : "([^/,]+)");
|
||||
break;
|
||||
default:
|
||||
pattern = '([^/]+)';
|
||||
pattern = "([^/]+)";
|
||||
}
|
||||
patterns.push({ pattern, name });
|
||||
return patterns;
|
||||
}
|
||||
match(uri) {
|
||||
UriTemplate.validateLength(uri, MAX_TEMPLATE_LENGTH, 'URI');
|
||||
let pattern = '^';
|
||||
UriTemplate.validateLength(uri, MAX_TEMPLATE_LENGTH, "URI");
|
||||
let pattern = "^";
|
||||
const names = [];
|
||||
for (const part of this.parts) {
|
||||
if (typeof part === 'string') {
|
||||
if (typeof part === "string") {
|
||||
pattern += this.escapeRegExp(part);
|
||||
}
|
||||
else {
|
||||
@@ -215,8 +217,8 @@ export class UriTemplate {
|
||||
}
|
||||
}
|
||||
}
|
||||
pattern += '$';
|
||||
UriTemplate.validateLength(pattern, MAX_REGEX_LENGTH, 'Generated regex pattern');
|
||||
pattern += "$";
|
||||
UriTemplate.validateLength(pattern, MAX_REGEX_LENGTH, "Generated regex pattern");
|
||||
const regex = new RegExp(pattern);
|
||||
const match = uri.match(regex);
|
||||
if (!match)
|
||||
@@ -225,9 +227,9 @@ export class UriTemplate {
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
const { name, exploded } = names[i];
|
||||
const value = match[i + 1];
|
||||
const cleanName = name.replace('*', '');
|
||||
if (exploded && value.includes(',')) {
|
||||
result[cleanName] = value.split(',');
|
||||
const cleanName = name.replace("*", "");
|
||||
if (exploded && value.includes(",")) {
|
||||
result[cleanName] = value.split(",");
|
||||
}
|
||||
else {
|
||||
result[cleanName] = value;
|
||||
|
||||
Reference in New Issue
Block a user