From 729c4ca0389db273146d1ee70f70ea6c7d391290 Mon Sep 17 00:00:00 2001 From: Cintia Sanchez Garcia Date: Tue, 9 Apr 2024 23:47:42 +0200 Subject: [PATCH] Allow setting web application base path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Cintia Sanchez Garcia Signed-off-by: Sergio Castaño Arteaga Co-authored-by: Cintia Sanchez Garcia Co-authored-by: Sergio Castaño Arteaga --- docs/config/settings.yml | 12 ++- embed/embed.html | 2 +- embed/package.json | 3 +- .../iframeResizer.contentWindow-v4.3.9.min.js | 0 embed/src/App.tsx | 6 +- embed/src/types.ts | 1 + embed/src/utils/getUrl.tsx | 7 +- embed/vite.config.ts | 15 +-- embed/yarn.lock | 95 +------------------ src/build/datasets.rs | 7 +- src/build/settings.rs | 34 +++++++ src/new/template/settings.yml | 30 +++--- web/package.json | 3 +- web/{src/assets/js => public/assets}/gtm.js | 0 web/src/App.tsx | 21 ++-- web/src/data.ts | 10 ++ web/src/layout/common/Image.tsx | 2 +- web/src/layout/explore/index.tsx | 7 +- web/src/layout/guide/index.tsx | 4 +- web/src/layout/navigation/EmbedModal.tsx | 25 ++--- web/src/layout/navigation/Header.tsx | 28 +++--- web/src/layout/navigation/MobileDropdown.tsx | 7 +- web/src/layout/navigation/MobileHeader.tsx | 8 +- web/src/layout/notFound/index.tsx | 3 +- web/src/layout/stores/groupActive.tsx | 11 +-- web/src/types.ts | 1 + web/src/utils/getBasePath.ts | 5 + web/src/utils/isExploreSection.ts | 7 ++ web/src/window.d.ts | 1 + web/vite.config.ts | 11 +-- web/yarn.lock | 42 +------- 31 files changed, 182 insertions(+), 226 deletions(-) rename embed/{src/assets/js => public/assets}/iframeResizer.contentWindow-v4.3.9.min.js (100%) rename web/{src/assets/js => public/assets}/gtm.js (100%) create mode 100644 web/src/utils/getBasePath.ts create mode 100644 web/src/utils/isExploreSection.ts diff --git a/docs/config/settings.yml b/docs/config/settings.yml index feff137f..88589661 100644 --- a/docs/config/settings.yml +++ b/docs/config/settings.yml @@ -28,6 +28,14 @@ url: https://landscape.cncf.io # gtm: # Google Tag Manager configuration # container_id: # Landscape web application container ID +# Base path (optional) +# +# Base path where the landscape will be hosted. By default the generated +# landscape is prepared to be hosted at the root of the domain. However, if the +# landscape will be hosted in a subpath, this value must be set accordingly. +# +# base_path: / + # Categories (optional) # # Categories information is read from the `landscape.yml` data file. The way @@ -303,8 +311,8 @@ tags: - category: "Wasm" subcategories: - "Packaging, Registries & Application Delivery" - contributor-strategy: [] - environmental-sustainability: [] + contributor-strategy: [ ] + environmental-sustainability: [ ] network: - category: "Orchestration & Management" subcategories: diff --git a/embed/embed.html b/embed/embed.html index f0845ace..c9588371 100644 --- a/embed/embed.html +++ b/embed/embed.html @@ -14,7 +14,7 @@ const resizer = urlParams.get('iframe-resizer'); if (typeof resizer !== null && resizer === 'true') { const script = document.createElement('script'); - script.setAttribute('src', '/embed/assets/iframeResizer.contentWindow-v4.3.9.min.js'); + script.setAttribute('src', './embed/assets/iframeResizer.contentWindow-v4.3.9.min.js'); document.body.appendChild(script); } })(); diff --git a/embed/package.json b/embed/package.json index 3e34c5ef..7cffc0d2 100644 --- a/embed/package.json +++ b/embed/package.json @@ -24,7 +24,6 @@ "eslint-plugin-solid": "^0.13.1", "typescript": "^5.3.3", "vite": "^5.1.4", - "vite-plugin-solid": "^2.10.1", - "vite-plugin-static-copy": "^1.0.1" + "vite-plugin-solid": "^2.10.1" } } diff --git a/embed/src/assets/js/iframeResizer.contentWindow-v4.3.9.min.js b/embed/public/assets/iframeResizer.contentWindow-v4.3.9.min.js similarity index 100% rename from embed/src/assets/js/iframeResizer.contentWindow-v4.3.9.min.js rename to embed/public/assets/iframeResizer.contentWindow-v4.3.9.min.js diff --git a/embed/src/App.tsx b/embed/src/App.tsx index d61fb51e..51fbde72 100644 --- a/embed/src/App.tsx +++ b/embed/src/App.tsx @@ -7,6 +7,7 @@ import NoData from './common/NoData'; import StyleView from './common/StyleView'; import { Alignment, + BASE_PATH_PARAM, BaseItem, Data, DEFAULT_DISPLAY_CATEGORY_HEADER, @@ -116,6 +117,7 @@ const SubcategoryTitle = styled('div')` `; const App = () => { + const [basePath, setBasePath] = createSignal(''); const [key, setKey] = createSignal(); const [data, setData] = createSignal(); const [displayHeader, setDisplayHeader] = createSignal(DEFAULT_DISPLAY_HEADER); @@ -139,6 +141,7 @@ const App = () => { onMount(() => { const urlParams = new URLSearchParams(window.location.search); + const basePathParam = urlParams.get(BASE_PATH_PARAM); const keyParam = urlParams.get(KEY_PARAM); const displayHeaderParam = urlParams.get(DISPLAY_HEADER_PARAM); const styleParam = urlParams.get(ITEMS_STYLE_PARAM); @@ -223,6 +226,7 @@ const App = () => { } // When size and style are not valid, we don´t save the key if (isValidSize && isValidStyle) { + setBasePath(basePathParam || ''); setKey(keyParam); } else { setData(null); @@ -240,7 +244,7 @@ const App = () => { fetch( import.meta.env.MODE === 'development' ? `http://localhost:8000/data/embed_${key()}.json` - : `../data/embed_${key()}.json` + : `${basePath()}/data/embed_${key()}.json` ) .then((res) => { if (res.ok) { diff --git a/embed/src/types.ts b/embed/src/types.ts index 3c438776..fd1619c8 100644 --- a/embed/src/types.ts +++ b/embed/src/types.ts @@ -14,6 +14,7 @@ export const ITEMS_ALIGNMENT_PARAM = 'items-alignment'; export const ITEMS_SPACING_PARAM = 'items-spacing'; export const TITLE_BGCOLOR_PARAM = 'bg-color'; export const TITLE_FGCOLOR_PARAM = 'fg-color'; +export const BASE_PATH_PARAM = 'base-path'; export interface Data { category: Category; diff --git a/embed/src/utils/getUrl.tsx b/embed/src/utils/getUrl.tsx index 7574201b..d7d388d7 100644 --- a/embed/src/utils/getUrl.tsx +++ b/embed/src/utils/getUrl.tsx @@ -1,6 +1,9 @@ +import { BASE_PATH_PARAM } from '../types'; + const getUrl = (): string => { - const url = new URL(document.location.href); - return url.origin; + const urlParams = new URLSearchParams(location.search); + const basePathParam = urlParams.get(BASE_PATH_PARAM); + return `${location.origin}${basePathParam || ''}`; }; export default getUrl; diff --git a/embed/vite.config.ts b/embed/vite.config.ts index fbd4a6a4..64b1fa3f 100644 --- a/embed/vite.config.ts +++ b/embed/vite.config.ts @@ -1,10 +1,8 @@ import { defineConfig } from 'vite'; import solid from 'vite-plugin-solid'; -import { viteStaticCopy } from 'vite-plugin-static-copy'; - export default defineConfig({ - base: '/embed', + base: '', build: { rollupOptions: { input: { @@ -12,14 +10,5 @@ export default defineConfig({ }, }, }, - plugins: [solid(), - viteStaticCopy({ - targets: [ - { - src: './src/assets/js/iframeResizer.contentWindow-v4.3.9.min.js', - dest: 'assets' - } - ] - }) - ] + plugins: [solid()] }); diff --git a/embed/yarn.lock b/embed/yarn.lock index 23c28c26..3ae4d173 100644 --- a/embed/yarn.lock +++ b/embed/yarn.lock @@ -728,14 +728,6 @@ ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -anymatch@~3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" @@ -769,11 +761,6 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -789,7 +776,7 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^3.0.2, braces@~3.0.2: +braces@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -833,21 +820,6 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chokidar@^3.5.3: - version "3.6.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" - integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -1095,7 +1067,7 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== -fast-glob@^3.2.11, fast-glob@^3.2.9: +fast-glob@^3.2.9: version "3.3.2" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== @@ -1159,15 +1131,6 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== -fs-extra@^11.1.0: - version "11.2.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b" - integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -1183,7 +1146,7 @@ gensync@^1.0.0-beta.2: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== -glob-parent@^5.1.2, glob-parent@~5.1.2: +glob-parent@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -1238,11 +1201,6 @@ goober@^2.1.10: resolved "https://registry.yarnpkg.com/goober/-/goober-2.1.14.tgz#4a5c94fc34dc086a8e6035360ae1800005135acd" integrity sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg== -graceful-fs@^4.1.6, graceful-fs@^4.2.0: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - graphemer@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" @@ -1304,19 +1262,12 @@ inline-style-parser@0.1.1: resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -1387,15 +1338,6 @@ json5@^2.2.3: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== -jsonfile@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" - integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== - dependencies: - universalify "^2.0.0" - optionalDependencies: - graceful-fs "^4.1.6" - kebab-case@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/kebab-case/-/kebab-case-1.0.2.tgz#5eac97d5d220acf606d40e3c0ecfea21f1f9e1eb" @@ -1501,11 +1443,6 @@ node-releases@^2.0.14: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -1571,7 +1508,7 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: +picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== @@ -1607,13 +1544,6 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -1815,11 +1745,6 @@ typescript@^5.3.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== -universalify@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" - integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== - update-browserslist-db@^1.0.13: version "1.0.13" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" @@ -1852,16 +1777,6 @@ vite-plugin-solid@^2.10.1: solid-refresh "^0.6.3" vitefu "^0.2.5" -vite-plugin-static-copy@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/vite-plugin-static-copy/-/vite-plugin-static-copy-1.0.1.tgz#c8aa9871d920b0de9c8583caae5510669546cf8e" - integrity sha512-3eGL4mdZoPJMDBT68pv/XKIHR4MgVolStIxxv1gIBP4R8TpHn9C9EnaU0hesqlseJ4ycLGUxckFTu/jpuJXQlA== - dependencies: - chokidar "^3.5.3" - fast-glob "^3.2.11" - fs-extra "^11.1.0" - picocolors "^1.0.0" - vite@^5.1.4: version "5.1.4" resolved "https://registry.yarnpkg.com/vite/-/vite-5.1.4.tgz#14e9d3e7a6e488f36284ef13cebe149f060bcfb6" diff --git a/src/build/datasets.rs b/src/build/datasets.rs index 76e98e24..8c0670a9 100644 --- a/src/build/datasets.rs +++ b/src/build/datasets.rs @@ -69,11 +69,15 @@ mod base { /// Base dataset information. #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] + #[allow(clippy::struct_field_names)] pub(crate) struct Base { pub finances_available: bool, pub foundation: String, pub qr_code: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub base_path: Option, + #[serde(skip_serializing_if = "Vec::is_empty")] pub categories: Vec, @@ -123,7 +127,7 @@ mod base { finances_available: false, foundation: settings.foundation.clone(), qr_code: qr_code.to_string(), - images: settings.images.clone(), + base_path: settings.base_path.clone(), categories: landscape_data.categories.clone(), categories_overridden: vec![], colors: settings.colors.clone(), @@ -132,6 +136,7 @@ mod base { groups: settings.groups.clone().unwrap_or_default(), guide_summary: BTreeMap::new(), header: settings.header.clone(), + images: settings.images.clone(), items: vec![], members_category: settings.members_category.clone(), upcoming_event: settings.upcoming_event.clone(), diff --git a/src/build/settings.rs b/src/build/settings.rs index 97f7888c..dd4b73b7 100644 --- a/src/build/settings.rs +++ b/src/build/settings.rs @@ -27,6 +27,9 @@ pub(crate) struct LandscapeSettings { #[serde(skip_serializing_if = "Option::is_none")] pub analytics: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub base_path: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub categories: Option>, @@ -112,8 +115,10 @@ impl LandscapeSettings { /// Create a new landscape settings instance from the raw data provided. fn new_from_raw_data(raw_data: &str) -> Result { let mut settings: LandscapeSettings = serde_yaml::from_str(raw_data)?; + settings.validate().context("the landscape settings file provided is not valid")?; settings.footer_text_to_html().context("error converting footer md text to html")?; + settings.remove_base_path_trailing_slash(); settings.set_groups_normalized_name(); Ok(settings) @@ -131,6 +136,15 @@ impl LandscapeSettings { Ok(()) } + /// Remove base_path trailing slash if present. + fn remove_base_path_trailing_slash(&mut self) { + if let Some(base_path) = &mut self.base_path { + if let Some(base_path_updated) = base_path.strip_suffix('/') { + *base_path = base_path_updated.to_string(); + } + } + } + /// Set the normalized name field of the provided groups. fn set_groups_normalized_name(&mut self) { if let Some(groups) = self.groups.as_mut() { @@ -150,6 +164,7 @@ impl LandscapeSettings { // Check url is valid validate_url("landscape", &Some(self.url.clone()))?; + self.validate_base_path()?; self.validate_categories()?; self.validate_colors()?; self.validate_featured_items()?; @@ -165,6 +180,25 @@ impl LandscapeSettings { Ok(()) } + /// Check base path is valid. + fn validate_base_path(&self) -> Result<()> { + let Some(base_path) = &self.base_path else { + return Ok(()); + }; + + // Check base path is not empty + if base_path.is_empty() { + bail!("base_path cannot be empty"); + } + + // Check base path starts with a slash + if !base_path.starts_with('/') { + bail!("base_path must start with a slash"); + } + + Ok(()) + } + /// Check categories are valid. fn validate_categories(&self) -> Result<()> { if let Some(categories) = &self.categories { diff --git a/src/new/template/settings.yml b/src/new/template/settings.yml index 0fefc3b6..703dbade 100644 --- a/src/new/template/settings.yml +++ b/src/new/template/settings.yml @@ -28,9 +28,17 @@ url: http://127.0.0.1:8000 # gtm: # Google Tag Manager configuration # container_id: # Landscape web application container ID +# Base path (optional) +# +# Base path where the landscape will be hosted. By default the generated +# landscape is prepared to be hosted at the root of the domain. However, if the +# landscape will be hosted in a subpath, this value must be set accordingly. +# +# base_path: / + # Categories (optional) # -# Categories information is read from the `landscape.yml` data file. The way +# Categories information is read from the `landscape.yml` data file. The way # categories are displayed in the web application is computed dynamically based # on the number of categories and subcategories, as well as the number of items # on each. Sometimes, however, we may want subcategories to be displayed in a @@ -56,11 +64,11 @@ url: http://127.0.0.1:8000 # # colors: # color1: # Buttons, groups, links -# color2: # Some highlighted items like filters button, search icon -# color3: # Participation stats bars, spinners, modal titles -# color4: # Categories titles in filters, fieldset in filters modal -# color4: # Categories and subcategories frames (odd) -# color5: # Categories and subcategories frames (even) +# color2: # Some highlighted items like filters button, search icon +# color3: # Participation stats bars, spinners, modal titles +# color4: # Categories titles in filters, fieldset in filters modal +# color4: # Categories and subcategories frames (odd) +# color5: # Categories and subcategories frames (even) # colors: color1: "rgba(0, 107, 204, 1)" @@ -138,7 +146,7 @@ footer: # # Defines the preferred size of the landscape items in the grid mode. When the # landscape contains many items, it is recommended to use the `small` size. -# However, if there aren't many items, choosing `medium` or `large` may make +# However, if there aren't many items, choosing `medium` or `large` may make # the landscape look nicer. Users will still be able to adjust the items size # from the UI using the zoom controls. # @@ -185,7 +193,7 @@ header: # Images (optional) # -# Urls of some images used in the landscape UI. +# Urls of some images used in the landscape UI. # # images: # favicon: @@ -199,7 +207,7 @@ header: # but it is important that we define it here as there are some special # operations that depend on it. # -# members_category: +# members_category: # # Osano (optional) @@ -233,7 +241,7 @@ screenshot_width: 1500 # (by using the `tag` field in the `extra` item's section). However, sometimes # this information is not available at the item level. This configuration # section provides a mechanism to automatically asign a TAG to projects items -# based on the categories and subcategories they belong to. +# based on the categories and subcategories they belong to. # # For example, we can define that all projects in the category are # owned by . When the items are processed, the corresponding TAG will be @@ -265,4 +273,4 @@ screenshot_width: 1500 # start: # Start date: (required, format: YYYY-MM-DD) # end: # End date: (required, format: YYYY-MM-DD) # banner_url: # Event banner image url (required, recommended dimensions: 2400x300) -# details_url: # Event details URL (required) +# details_url: # Event details URL (required) diff --git a/web/package.json b/web/package.json index 003d9fbf..aecc0d6e 100644 --- a/web/package.json +++ b/web/package.json @@ -37,8 +37,7 @@ "typescript": "^5.3.3", "vite": "^5.1.5", "vite-plugin-ejs": "^1.7.0", - "vite-plugin-solid": "^2.10.1", - "vite-plugin-static-copy": "^1.0.1" + "vite-plugin-solid": "^2.10.1" }, "description": "Landscape2 is a tool that generates interactive landscapes websites.", "license": "Apache-2.0" diff --git a/web/src/assets/js/gtm.js b/web/public/assets/gtm.js similarity index 100% rename from web/src/assets/js/gtm.js rename to web/public/assets/gtm.js diff --git a/web/src/App.tsx b/web/src/App.tsx index d894a734..0cde832d 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -3,6 +3,15 @@ import isUndefined from 'lodash/isUndefined'; import range from 'lodash/range'; import { createSignal, onMount } from 'solid-js'; +import { + EMBED_SETUP_PATH, + EXPLORE_PATH, + FINANCES_PATH, + GUIDE_PATH, + LOGOS_PREVIEW_PATH, + SCREENSHOTS_PATH, + STATS_PATH, +} from './data'; import Layout from './layout'; import Explore from './layout/explore'; import Finances from './layout/finances'; @@ -69,13 +78,13 @@ const App = () => { return ( - } /> - - - - } /> - + } /> + + + + + } /> ); }; diff --git a/web/src/data.ts b/web/src/data.ts index 7bd72dd5..1116b2e5 100644 --- a/web/src/data.ts +++ b/web/src/data.ts @@ -13,8 +13,18 @@ import { ViewMode, ZoomLevelsPerSize, } from './types'; +import getBasePath from './utils/getBasePath'; import getFoundationNameLabel from './utils/getFoundationNameLabel'; +export const BASE_PATH = getBasePath(); +export const EXPLORE_PATH = BASE_PATH === '' ? '/' : `${BASE_PATH}/`; +export const EMBED_SETUP_PATH = `${BASE_PATH}/embed-setup`; +export const STATS_PATH = `${BASE_PATH}/stats`; +export const GUIDE_PATH = `${BASE_PATH}/guide`; +export const FINANCES_PATH = `${BASE_PATH}/finances`; +export const LOGOS_PREVIEW_PATH = `${BASE_PATH}/logos-preview`; +export const SCREENSHOTS_PATH = '/screenshot'; + export const TAB_PARAM = 'tab'; export const VIEW_MODE_PARAM = 'view-mode'; export const GROUP_PARAM = 'group'; diff --git a/web/src/layout/common/Image.tsx b/web/src/layout/common/Image.tsx index 73749ce1..d31f15c5 100644 --- a/web/src/layout/common/Image.tsx +++ b/web/src/layout/common/Image.tsx @@ -22,7 +22,7 @@ const Image = (props: Props) => { {`${props.name} setError(true)} loading={!isUndefined(props.enableLazyLoad) && props.enableLazyLoad ? 'lazy' : undefined} width="auto" diff --git a/web/src/layout/explore/index.tsx b/web/src/layout/explore/index.tsx index cf19c16f..455119c7 100644 --- a/web/src/layout/explore/index.tsx +++ b/web/src/layout/explore/index.tsx @@ -9,6 +9,7 @@ import { batch, createEffect, createSignal, For, Match, on, onCleanup, onMount, import { ALL_OPTION, + BASE_PATH, CLASSIFY_PARAM, DEFAULT_CLASSIFY, DEFAULT_SORT, @@ -274,7 +275,7 @@ const Explore = (props: Props) => { } } - navigate(`${location.pathname}?${updatedSearchParams.toString()}${getHash()}`, { + navigate(`${BASE_PATH}/?${updatedSearchParams.toString()}${getHash()}`, { state: location.state, replace: true, scroll: true, // default @@ -336,7 +337,7 @@ const Explore = (props: Props) => { const query = params.toString(); - navigate(`${location.pathname}${query === '' ? '' : `?${query}`}${getHash()}`, { + navigate(`${BASE_PATH}/${query === '' ? '' : `?${query}`}${getHash()}`, { state: location.state, replace: true, scroll: true, // default @@ -366,7 +367,7 @@ const Explore = (props: Props) => { }); if (viewMode() === ViewMode.Card) { - navigate(`${location.pathname}${location.search}${location.hash !== '' ? location.hash : getHash()}`, { + navigate(`${BASE_PATH}/${location.search}${location.hash !== '' ? location.hash : getHash()}`, { state: location.state, replace: true, scroll: true, // default diff --git a/web/src/layout/guide/index.tsx b/web/src/layout/guide/index.tsx index 01bd8e96..ea0c5d25 100644 --- a/web/src/layout/guide/index.tsx +++ b/web/src/layout/guide/index.tsx @@ -2,7 +2,7 @@ import { useLocation, useNavigate } from '@solidjs/router'; import isUndefined from 'lodash/isUndefined'; import { createEffect, createMemo, createSignal, For, on, onMount, Show } from 'solid-js'; -import { SMALL_DEVICES_BREAKPOINTS } from '../../data'; +import { GUIDE_PATH, SMALL_DEVICES_BREAKPOINTS } from '../../data'; import useBreakpointDetect from '../../hooks/useBreakpointDetect'; import { CategoryGuide, Guide, StateContent, SubcategoryGuide, SVGIconKind, ToCTitle } from '../../types'; import getNormalizedName from '../../utils/getNormalizedName'; @@ -133,7 +133,7 @@ const GuideIndex = () => { ); const updateRoute = (title: string) => { - navigate(`${location.pathname}${location.search}#${title}`, { + navigate(`${GUIDE_PATH}${location.search}#${title}`, { replace: true, scroll: false, state: { fromMenu: true }, diff --git a/web/src/layout/navigation/EmbedModal.tsx b/web/src/layout/navigation/EmbedModal.tsx index 25093427..d5b2bb20 100644 --- a/web/src/layout/navigation/EmbedModal.tsx +++ b/web/src/layout/navigation/EmbedModal.tsx @@ -1,7 +1,7 @@ import { useLocation, useNavigate } from '@solidjs/router'; import isUndefined from 'lodash/isUndefined'; import sortBy from 'lodash/sortBy'; -import { batch, createEffect, createSignal, For, on, onMount, Show } from 'solid-js'; +import { batch, createEffect, createMemo, createSignal, For, on, onMount, Show } from 'solid-js'; import { Alignment, @@ -40,10 +40,11 @@ import { TITLE_SIZE_PARAM, UPPERCASE_TITLE_PARAM, } from '../../../../embed/src/types'; -import { SMALL_DEVICES_BREAKPOINTS } from '../../data'; +import { BASE_PATH, EMBED_SETUP_PATH, SMALL_DEVICES_BREAKPOINTS } from '../../data'; import useBreakpointDetect from '../../hooks/useBreakpointDetect'; import { Category, Subcategory, SVGIconKind } from '../../types'; import capitalizeFirstLetter from '../../utils/capitalizeFirstLetter'; +import isExploreSection from '../../utils/isExploreSection'; import rgba2hex from '../../utils/rgba2hex'; import CheckBox from '../common/Checkbox'; import CodeBlock from '../common/CodeBlock'; @@ -91,8 +92,8 @@ const EmbedModal = () => { ? rgba2hex(window.baseDS.colors.color5) : DEFAULT_TITLE_BG_COLOR; // Icon is only visible when Explore section is loaded - const isVisible = () => ['/', '/embed-setup'].includes(location.pathname); - const isEmbedSetupActive = () => location.pathname === '/embed-setup'; + const isVisible = createMemo(() => isExploreSection(location.pathname)); + const isEmbedSetupActive = () => location.pathname === EMBED_SETUP_PATH; const [visibleModal, setVisibleModal] = createSignal(isEmbedSetupActive()); const categoriesList = () => sortBy(window.baseDS.categories, ['name']); const [subcategoriesList, setSubcategoriesList] = createSignal( @@ -127,10 +128,10 @@ const EmbedModal = () => { const [prevHash, setPrevHash] = createSignal(''); const [prevSearch, setPrevSearch] = createSignal(''); - const getUrl = () => { + const getIFrameUrl = () => { return `${ import.meta.env.MODE === 'development' ? 'http://localhost:8000' : window.location.origin - }/embed/embed.html?${KEY_PARAM}=${key() || categoriesList()[0].normalized_name}&${DISPLAY_HEADER_PARAM}=${ + }${BASE_PATH}/embed/embed.html?${KEY_PARAM}=${key() || categoriesList()[0].normalized_name}&${DISPLAY_HEADER_PARAM}=${ displayHeader() ? 'true' : 'false' }&${DISPLAY_HEADER_CATEGORY_PARAM}=${ displayCategoryTitle() ? 'true' : 'false' @@ -144,7 +145,7 @@ const EmbedModal = () => { itemsSpacingType() === SpacingType.Custom && !isUndefined(itemsSpacing()) ? `&${ITEMS_SPACING_PARAM}=${itemsSpacing()}` : '' - }&${TITLE_BGCOLOR_PARAM}=${encodeURIComponent(bgColor())}&${TITLE_FGCOLOR_PARAM}=${encodeURIComponent(fgColor())}`; + }&${TITLE_BGCOLOR_PARAM}=${encodeURIComponent(bgColor())}&${TITLE_FGCOLOR_PARAM}=${encodeURIComponent(fgColor())}${!isUndefined(window.baseDS.base_path) ? `&base-path=${encodeURIComponent(window.baseDS.base_path)}` : ''}`; }; const onUpdateSpacingType = (type: SpacingType) => { @@ -224,7 +225,7 @@ const EmbedModal = () => { }; const onClose = () => { - navigate(`/${prevSearch() !== '' ? prevSearch() : ''}${prevHash()}`, { + navigate(`${BASE_PATH}/${prevSearch() !== '' ? prevSearch() : ''}${prevHash()}`, { replace: true, }); setVisibleModal(false); @@ -256,11 +257,11 @@ const EmbedModal = () => { }; onMount(() => { - setUrl(getUrl()); + setUrl(getIFrameUrl()); }); createEffect(() => { - setUrl(getUrl()); + setUrl(getIFrameUrl()); }); createEffect( @@ -276,7 +277,7 @@ const EmbedModal = () => { if (visibleModal()) { setPrevSearch(location.search); setPrevHash(location.hash); - setUrl(getUrl()); + setUrl(getIFrameUrl()); } }) ); @@ -291,7 +292,7 @@ const EmbedModal = () => { e.preventDefault(); e.stopPropagation(); setVisibleModal(true); - navigate('embed-setup', { + navigate(EMBED_SETUP_PATH, { replace: true, }); }} diff --git a/web/src/layout/navigation/Header.tsx b/web/src/layout/navigation/Header.tsx index 494a9572..c815169f 100644 --- a/web/src/layout/navigation/Header.tsx +++ b/web/src/layout/navigation/Header.tsx @@ -1,10 +1,11 @@ import { useLocation, useNavigate } from '@solidjs/router'; import isEmpty from 'lodash/isEmpty'; import isUndefined from 'lodash/isUndefined'; -import { Show } from 'solid-js'; +import { createMemo, Show } from 'solid-js'; -import { ALL_OPTION } from '../../data'; +import { ALL_OPTION, EXPLORE_PATH, GUIDE_PATH, SCREENSHOTS_PATH, STATS_PATH } from '../../data'; import { SVGIconKind, ViewMode } from '../../types'; +import isExploreSection from '../../utils/isExploreSection'; import scrollToTop from '../../utils/scrollToTop'; import DownloadDropdown from '../common/DownloadDropdown'; import ExternalLink from '../common/ExternalLink'; @@ -21,6 +22,7 @@ const Header = () => { const logo = () => (window.baseDS.header ? window.baseDS.header!.logo : undefined); const setViewMode = useSetViewMode(); const setSelectedGroup = useSetGroupActive(); + const isExploreActive = createMemo(() => isExploreSection(location.pathname)); const isActive = (path: string) => { return path === location.pathname; @@ -41,7 +43,7 @@ const Header = () => { class="btn btn-link p-0 pe-3 me-2 me-xl-5" onClick={() => { resetDefaultExploreValues(); - navigate('/', { + navigate(EXPLORE_PATH, { state: { from: 'logo-header' }, }); scrollToTop(false); @@ -59,7 +61,7 @@ const Header = () => { {