Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • applications/etuc/hubl
  • applications/hubl
  • decentral1se/hubl
  • rngadam/hubl
  • jvtrudel/hubl
  • 3wc/hubl
6 results
Show changes
div.content-box__info
solid-link(class='backlink right', bind-resources, next='project-edit') Retour
h1 Modifier l'image du projet
solid-picture(
bind-resources
upload-src=`${endpoints.uploads || endpoints.post.uploads}`
upload-id="solid-project-edit-picture"
nested-fields='customer'
fields='logo'
next='project-edit'
additional='name'
)
solid-router(default-route='project-profile', hidden)
solid-route(name='project-profile')
solid-route(name='project-edit')
#project-profile(hidden)
include ../../templates/hubl-captain.pug
include ../../templates/hubl-circle-team.pug
include ../../templates/hubl-project-team.pug
.content-box__info.flex
.flex.space-between.with-padding.mobile-vertical-align
solid-display.mobile-margin__bottom(
bind-resources
fields='creationDateSet(title, creationDate)'
class-title='word-spacing-right'
value-title='Date de création : '
widget-creationDate='solid-display-date'
)
solid-ac-checker(permission='acl:Append', bind-resources, nested-field='members')
solid-link(class='button mobile-full-width text-bold text-uppercase reversed button-primary bordered with-icon icon-pencil' next='project-edit' bind-resources) Modifier et ajouter un membre
solid-display(
class='flex desktop-button__end'
bind-resources
nested-field='members'
fields='relation'
action-relation='relation'
widget-relation='hubl-project-leave-button'
search-fields='user'
search-widget-user='solid-form-hidden'
search-value-user=""
hubl-inherit-user-id="search-value-user"
)
.flex.space-between
solid-display(
bind-resources
fields='label-captain, captain'
value-label-captain='Capitaine :'
class-label-captain='h2-like'
widget-captain='hubl-captain'
)
solid-display(
class='customer-logo'
bind-resources
fields='customer.logo'
widget-customer.logo='solid-display-img'
)
h2 Equipe :
solid-display.block(
bind-resources
fields='members'
multiple-members
widget-members='hubl-project-team'
)
solid-widget(name='hubl-project-leave-button')
template
solid-delete(
class='button desktop-btn-margin__left text-bold text-uppercase reversed button-secondary bordered with-icon icon-close'
data-src="${src}"
data-label='Quitter le groupe'
)
#project-edit.content-box__height(hidden)
include page-project-edit.pug
import { resolve } from "node:path";
import { createHtmlPlugin } from 'vite-plugin-html'
import { defineConfig } from "vite";
import { VitePWA } from "vite-plugin-pwa";
import Handlebars from "handlebars";
import handlebars from "vite-plugin-handlebars";
import config from "./vite/generateConfig.mjs";
// Workaround for https://github.com/alexlafroscia/vite-plugin-handlebars/issues/192
function handlebarsOverride(options) {
const plugin = handlebars(options);
plugin.handleHotUpdate = async ({ server, file }) => {
if (file.endsWith(".html") || file.endsWith(".hbs"))
server.ws.send({
type: "full-reload",
});
};
return plugin;
}
export default defineConfig({
build: {
rollupOptions: {
input: {
app: resolve(__dirname, "index.html"),
},
},
},
css: {
preprocessorOptions: {
scss: {
quietDeps: true,
api: "modern-compiler"
},
},
},
define: config,
plugins: [
handlebarsOverride({
helpers: {
json: (value) => JSON.stringify(value),
year: () => new Date().getFullYear(),
isArray: (value) => Array.isArray(value),
mergeAttributes: (attributes) => {
let result = "";
for (const key in attributes) {
if (attributes.hasOwnProperty(key) && attributes[key] !== null) {
result += ` ${key}="${attributes[key]}"`;
}
}
return new Handlebars.SafeString(result);
},
hasComponent: (...component) =>
component.slice(0, -1).some((e) => config.componentSet.includes(e)),
hasComponentAll: (...component) =>
component.slice(0, -1).every((e) => config.componentSet.includes(e)),
hasNpmPackage: (...packages) =>
packages.slice(0, -1).every((e) => config.npm.filter(n => n.package === e).length > 0),
getComponent: (component) =>
config.components.find((c) => c.type === component),
getComponentFromRoute: (route) =>
config.components.find((c) => c.route === route),
getDefaultRoute: config.helpers.getDefaultRoute,
getRoute: config.helpers.getRoute,
get_legacy_view: (...name) => `legacy/${name.slice(0, -1).join('')}`,
is: (cond1, cond2) => cond1 === cond2,
any_are: (value, ...cond) => cond.slice(0, -1)?.includes(value),
includes: (arr, cond) => arr?.includes(cond),
},
partialDirectory: [resolve(__dirname, "src/partials")],
context: config,
}),
VitePWA({
devOptions: {
enabled: false,
},
injectRegister: "auto",
sourcemap: true,
manifest: {
lang: config.client.i18n.lang,
name: config.client.name,
short_name: config.client.shortName || config.client.name,
...config.client.pwa,
},
workbox: {
maximumFileSizeToCacheInBytes: 5000000,
runtimeCaching: [
{
urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
handler: "CacheFirst",
options: {
cacheName: "google-fonts-cache",
expiration: {
maxEntries: 10,
maxAgeSeconds: 60 * 60 * 24 * 365,
},
cacheableResponse: {
statuses: [0, 200],
},
},
},
{
urlPattern: /^https:\/\/fonts\.gstatic\.com\/.*/i,
handler: "CacheFirst",
options: {
cacheName: "gstatic-fonts-cache",
expiration: {
maxEntries: 10,
maxAgeSeconds: 60 * 60 * 24 * 365,
},
cacheableResponse: {
statuses: [0, 200],
},
},
},
],
},
}),
createHtmlPlugin({
minify: true,
}),
],
resolve: {
alias: {
"@helpers": "/src/helpers",
"@partials": "/src/partials",
"@styles": "/src/styles",
},
},
});
{
"client": {
"name": "Sample of a functional Orbit",
"description": "",
"logo": "https://cdn.startinblox.com/logos/webp/startinblox.webp",
"server": "http://localhost:8000",
"favicon": "/pwa/favicon.ico",
"css": false,
"defaultAvatar": "/images/alien.webp",
"i18n": {
"lang": "en",
"force": false
},
"pwa": {
"dir": "ltr",
"icons": [
{
"src": "/pwa/pwa-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/pwa/pwa-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/pwa/pwa-maskable-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/pwa/pwa-maskable-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"start_url": "/",
"display": "standalone",
"orientation": "portrait",
"background_color": "#fff",
"theme_color": "#FFFFFF"
}
},
"components": [
{
"type": "routing",
"route": false
},
{
"type": "menu",
"route": false
},
{
"type": "menu-top",
"parameters": {
"isBeta": false
},
"route": false
}
],
"npm": []
}
{
"@startinblox/core": {
"version": "0.19",
"path": "https://cdn.jsdelivr.net/npm/@startinblox/core@0.19/dist/index.js"
},
"@startinblox/router": {
"version": "0.12",
"path": "https://cdn.jsdelivr.net/npm/@startinblox/router@0.12/+esm"
},
"@startinblox/oidc": {
"version": "0.16",
"path": "https://cdn.jsdelivr.net/npm/@startinblox/oidc@0.16/+esm",
"requiredBy": ["autoLogin", "registering"]
},
"@startinblox/component-chat": {
"version": "7.1.5",
"requiredBy": ["chat", "circles", "projects", "spaces"]
},
"@startinblox/component-communities": {
"version": "2.0.4",
"requiredBy": ["communities"]
},
"@startinblox/component-dashboard": {
"requiredBy": ["dashboard"]
},
"@startinblox/component-directory": {
"version": "8.0.2",
"requiredBy": ["directory"]
},
"@startinblox/component-event": {
"version": "5.0.7",
"requiredBy": ["events"]
},
"@startinblox/component-conversation": {
"version": "1.0.2",
"requiredBy": ["events", "polls", "resources"]
},
"@startinblox/component-invoicing": {
"requiredBy": ["invoices"]
},
"@startinblox/component-job-board": {
"requiredBy": ["job-board"]
},
"@startinblox/component-notifications": {
"version": "1.0.2",
"requiredBy": ["notification"]
},
"@startinblox/component-poll": {
"requiredBy": ["polls"]
},
"@startinblox/component-spaces": {
"requiredBy": ["spaces"]
},
"@startinblox/component-sales": {
"requiredBy": ["spaces"]
},
"@startinblox/component-resource": {
"version": "5.0.5",
"requiredBy": ["resources"]
},
"@startinblox/component-babelfish": {
"requiredBy": ["babelfish"]
},
"@startinblox/ontochain-directory": {
"requiredBy": ["ontochain-directory"]
},
"@startinblox/component-piswap": {
"requiredBy": ["piswap"]
},
"@startinblox/component-tamis": {
"requiredBy": [
"tamis-profile",
"tamis-commande",
"tamis-prestation",
"tamis-asset"
]
},
"@startinblox/components-tems": {
"requiredBy": ["tems-catalog"]
},
"moralis": {
"path": "https://unpkg.com/moralis/dist/moralis.js",
"requiredBy": ["piswap"]
}
}
import { defaultComposer } from "default-composer";
import { readdirSync, statSync } from "node:fs";
import { join } from "node:path";
import defaultNpm from "./default.npm.json";
import defaultConfig from "./default.config.json";
import convertStringToBoolean from "../src/helpers/convertStringToBoolean.js";
import generateUniq from "../src/helpers/generateUniq.js";
import generateUrl from "../src/helpers/generateUrl.js";
import rewriteServer from "../src/helpers/rewriteServer.js";
/*
Generate the client config from a combination of client.json, default.client.json, default.npm.json
Provide local federations and collision-free routes to each component.
*/
let userConfig;
try {
userConfig = (await import("../config.json")).default;
} catch {
userConfig = {};
}
try {
userConfig.context = (await import("../src/context.json")).default;
} catch {
userConfig.context = {};
}
const config = defaultComposer(defaultConfig, userConfig);
const componentSet = new Set(
[...defaultConfig.components, ...config.components].map((c) => c.type),
);
config.components.map((c) => {
if (c.extensions) {
for (const e of c.extensions) {
componentSet.add(e.type);
}
}
});
const defaultPackage = {
package: "",
version: "latest",
path: false,
};
// Those attributes will not be serialized on a component
const ignoredAttributes = ["replacement", "menu"];
const requiredPackages = Object.keys(defaultNpm)
.map((mod) => {
const replaced = config.npm.findIndex((e) => e.package === mod);
if (replaced === -1) {
const p = defaultNpm[mod];
if (!p.requiredBy || p.requiredBy?.some((dep) => componentSet.has(dep))) {
return defaultComposer(defaultPackage, {
package: mod,
version: p.version,
// path: p.path || `https://cdn.jsdelivr.net/npm/${mod}@${p.version || "latest"}`,
path: p.path,
});
}
} else {
return config.npm.splice(replaced, 1)[0];
}
return false;
})
.filter((p) => p);
const routes = new Set();
const federations = {};
const components = config.components.map((component) => {
if (typeof component.route === "undefined") {
component.route = component.type;
}
component.uniq = generateUniq();
if (component.route) {
let route = component.route;
if (routes.has(component.route)) {
route += `-${component.uniq}`;
}
routes.add(route);
component.route = route;
}
if (component.extensions) {
for (const extension of component.extensions) {
if (typeof extension.route === "undefined") {
extension.route = extension.type;
}
extension.uniq = generateUniq();
if (extension.route) {
let route = extension.route;
if (routes.has(extension.route)) {
route += `-${extension.uniq}`;
}
routes.add(route);
extension.route = route;
}
}
}
if (component.parameters) {
const federation = new Set();
if (config.client.server) {
federation.add(config.client.server);
}
if (config.client.servers) {
for (const server of config.client.servers) {
federation.add(server);
}
}
if (component.federation) {
for (const target of component.federation) {
federation.add(target);
}
}
component.federation = [...federation];
for (const [attribute, path] of Object.entries(component.parameters)) {
if (typeof path === "string") {
if (path.startsWith("federation://")) {
const contains = generateUrl(federation, path);
if (contains.length > 1) {
federations[`store://local.${component.uniq}/${attribute}/`] = {
"@cache": "false",
"@context": "https://cdn.startinblox.com/owl/context.jsonld",
"@type": "ldp:Container",
"@id": `store://local.${component.uniq}/${attribute}/`,
"ldp:contains": contains,
permissions: [{ mode: { "@type": "view" } }],
};
component.parameters[attribute] =
`store://local.${component.uniq}/${attribute}/`;
} else {
component.parameters[attribute] =
federation.values().next().value +
path.replace(/federation:\//, "");
}
}
}
component.parameters[attribute] = rewriteServer(component.parameters[attribute], {client: {server: config.client.server}});
}
/* Rewrite every parameters to kebab-case */
const rewriteParameters = {};
for (const [attribute, value] of Object.entries(component.parameters)) {
const attributeName = attribute.replace(/((?<=[a-z\d])[A-Z]|(?<=[A-Z\d])[A-Z](?=[a-z]))/g, "-$1").toLowerCase();
if(!ignoredAttributes.includes(attributeName)) {
rewriteParameters[attributeName] = value;
}
}
component.attributes = rewriteParameters;
component.attributes.route = component.route;
component.attributes.uniq = component.uniq;
}
if (component.extensions) {
component.extensions = component.extensions.map((extension) => {
if (extension.parameters) {
const federation = new Set();
if (config.client.server) {
federation.add(config.client.server);
}
if (config.client.servers) {
for (const server of config.client.servers) {
federation.add(server);
}
}
if (component.federation) {
for (const target of component.federation) {
federation.add(target);
}
}
if (extension.federation) {
for (const target of extension.federation) {
federation.add(target);
}
}
extension.federation = [...federation];
for (const [attribute, path] of Object.entries(extension.parameters)) {
if (typeof path === "string") {
if (path.startsWith("federation://")) {
const contains = generateUrl(federation, path);
if (contains.length > 1) {
federations[`store://local.${extension.uniq}/${attribute}/`] = {
"@cache": "false",
"@context": "https://cdn.startinblox.com/owl/context.jsonld",
"@type": "ldp:Container",
"@id": `store://local.${extension.uniq}/${attribute}/`,
"ldp:contains": contains,
permissions: [{ mode: { "@type": "view" } }],
};
extension.parameters[attribute] =
`store://local.${extension.uniq}/${attribute}/`;
} else {
component.parameters[attribute] =
federation.values().next().value +
path.replace(/federation:\//, "");
}
}
}
component.parameters[attribute] = rewriteServer(component.parameters[attribute], {client: {server: config.client.server}});
}
/* Rewrite every parameters to kebab-case */
const rewriteParameters = {};
for (const [attribute, value] of Object.entries(extension.parameters)) {
const attributeName = attribute.replace(/((?<=[a-z\d])[A-Z]|(?<=[A-Z\d])[A-Z](?=[a-z]))/g, "-$1").toLowerCase();
if(!ignoredAttributes.includes(attributeName)) {
rewriteParameters[attributeName] = value;
}
}
extension.attributes = rewriteParameters;
extension.attributes.uniq = extension.uniq;
}
return extension;
});
}
// Legacy: component.experimental to component.integration
if (component.experimental) {
component.integration = component.experimental;
}
return component;
});
const getRoute = (type, returnFirst = false, ignoreError = false) => {
const availables = components.filter(
(c) => c.type === type || c.uniq === type,
);
for (const c of components) {
if (c.extensions) {
for (const e of c.extensions) {
if (e.type === type || e.uniq === type) {
availables.push(e);
}
}
}
}
if (availables.length > 1) {
if (returnFirst) {
return availables[0].route;
}
return availables[availables.length - 1].route;
}
if (availables.length < 1) {
if (!ignoreError && import.meta.env.DEV)
console.error(`No component found for route ${type}`);
} else {
return availables[0].route;
}
return false;
};
const getDefaultRoute = () => {
const defaultComponent = components.filter(
(e) => e.defaultRoute !== undefined,
);
let defaultRoute = "dashboard";
if (defaultComponent.length === 1) {
defaultRoute = defaultComponent[0].route;
}
return defaultRoute;
};
const listFiles = (directory, subfolder) => {
try {
const files = readdirSync(directory);
const fileNames = files.filter((filePath) =>
statSync(join(directory, filePath)).isFile(),
);
return fileNames.map(
(file) => () => `${subfolder}/${file.replace(".hbs", "")}`,
);
} catch (err) {
console.error(err);
return [];
}
};
const notifications = listFiles(
"./src/partials/notifications",
"notifications",
);
const widgets = listFiles("./src/partials/widgets", "widgets");
const mandatoryComponents = defaultConfig.components.filter(
(component) =>
!config.components.some((c) => component["type"] === c["type"]),
);
const definitiveConfig = {
client: Object.fromEntries(
Object.entries(config.client).map(([k, c]) => [
k,
convertStringToBoolean(rewriteServer(c, config)),
]),
),
components: [...mandatoryComponents, ...config.components],
// Both handlebars and vite does not handle Set properly
componentSet: [...componentSet],
npm: requiredPackages.concat(config.npm),
federations,
files: {
notifications: notifications,
widgets: widgets,
},
helpers: {
getDefaultRoute,
getRoute,
listFiles,
},
};
export default definitiveConfig;