diff --git a/i18n/english.js b/i18n/english.js
index 85a4f833..9d8adf11 100644
--- a/i18n/english.js
+++ b/i18n/english.js
@@ -218,7 +218,9 @@ const ui = {
childOf: "child of",
parentOf: "parent of",
unlocked: "unlocked",
- locked: "locked"
+ locked: "locked",
+ switchPayload: "Switch payload",
+ removeFromCache: "Remove from cache"
},
search: {
packagesCache: "Packages available in the cache",
diff --git a/i18n/french.js b/i18n/french.js
index 419ca2f3..904b082b 100644
--- a/i18n/french.js
+++ b/i18n/french.js
@@ -218,7 +218,9 @@ const ui = {
childOf: "enfant de",
parentOf: "parent de",
unlocked: "Déverrouillé",
- locked: "Verrouillé"
+ locked: "Verrouillé",
+ switchPayload: "Changer de payload",
+ removeFromCache: "Retirer du cache"
},
search: {
packagesCache: "Packages disponibles dans le cache",
diff --git a/public/components/navigation/navigation.css b/public/components/navigation/navigation.css
index 80134c97..e9979ff4 100644
--- a/public/components/navigation/navigation.css
+++ b/public/components/navigation/navigation.css
@@ -76,23 +76,3 @@ nav#aside>ul li.bottom-nav {
margin-top: auto;
}
-#search-nav {
- z-index: 30;
- display: flex;
- justify-content: center;
- align-items: center;
- position: absolute;
- height: 30px;
- left: 50px;
- padding-left: 20px;
- max-width: calc(100vw - 70px);
- box-sizing: border-box;
- background: var(--primary);
- transform: skewX(-20deg);
- box-shadow: 2px 1px 10px #26107f7a;
-}
-
-body.dark #search-nav {
- background: var(--dark-theme-primary-color);
-}
-
diff --git a/public/components/drill-breadcrumb/drill-breadcrumb.js b/public/components/network-breadcrumb/network-breadcrumb.js
similarity index 51%
rename from public/components/drill-breadcrumb/drill-breadcrumb.js
rename to public/components/network-breadcrumb/network-breadcrumb.js
index 1b538cb7..24e48156 100644
--- a/public/components/drill-breadcrumb/drill-breadcrumb.js
+++ b/public/components/network-breadcrumb/network-breadcrumb.js
@@ -3,18 +3,28 @@ import { LitElement, html, css, nothing } from "lit";
// Import Internal Dependencies
import { EVENTS } from "../../core/events.js";
+import { currentLang } from "../../common/utils.js";
-class DrillBreadcrumb extends LitElement {
+class NetworkBreadcrumb extends LitElement {
static styles = css`
:host {
+ --breadcrumb-bg: var(--primary);
+ --breadcrumb-shadow: 2px 1px 10px rgb(38 16 127 / 48%);
+ --breadcrumb-dropdown-bg: var(--primary-darker);
+ --breadcrumb-dropdown-border: rgb(255 255 255 / 20%);
+ --breadcrumb-dropdown-hover: rgb(255 255 255 / 15%);
+ --breadcrumb-header-color: rgb(255 255 255 / 50%);
+ --breadcrumb-separator-bg: rgb(255 255 255 / 15%);
+
position: absolute;
- top: 38px;
+ top: 8px;
left: 10px;
z-index: 20;
display: flex;
align-items: center;
gap: 4px;
- background: rgb(10 10 20 / 72%);
+ background: var(--breadcrumb-bg);
+ box-shadow: var(--breadcrumb-shadow);
border-radius: 6px;
padding: 4px 10px;
font-family: mononoki, monospace;
@@ -22,6 +32,16 @@ class DrillBreadcrumb extends LitElement {
color: #fff;
}
+ :host-context(body.dark) {
+ --breadcrumb-bg: rgb(10 10 20 / 72%);
+ --breadcrumb-shadow: none;
+ --breadcrumb-dropdown-bg: rgb(10 10 20 / 95%);
+ --breadcrumb-dropdown-border: rgb(255 255 255 / 15%);
+ --breadcrumb-dropdown-hover: rgb(255 255 255 / 10%);
+ --breadcrumb-header-color: rgb(255 255 255 / 40%);
+ --breadcrumb-separator-bg: rgb(255 255 255 / 10%);
+ }
+
:host([hidden]) {
display: none !important;
}
@@ -72,14 +92,11 @@ class DrillBreadcrumb extends LitElement {
.dropdown {
position: absolute;
top: calc(100% + 6px);
- left: 50%;
- transform: translateX(-50%);
- background: rgb(10 10 20 / 95%);
- border: 1px solid rgb(255 255 255 / 15%);
+ left: 0;
+ background: var(--breadcrumb-dropdown-bg);
+ border: 1px solid var(--breadcrumb-dropdown-border);
border-radius: 6px;
padding: 4px 0;
- min-width: 180px;
- max-height: 260px;
overflow-y: auto;
z-index: 30;
box-shadow: 0 4px 16px rgb(0 0 0 / 50%);
@@ -97,17 +114,73 @@ class DrillBreadcrumb extends LitElement {
}
.dropdown button:hover {
- background: rgb(255 255 255 / 10%);
+ background: var(--breadcrumb-dropdown-hover);
color: #fff;
text-decoration: none;
}
+
+ .dropdown-header {
+ display: block;
+ padding: 4px 12px 3px;
+ font-size: 10px;
+ color: var(--breadcrumb-header-color);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ cursor: default;
+ }
+
+ .dropdown-separator {
+ height: 1px;
+ background: var(--breadcrumb-separator-bg);
+ margin: 3px 0 4px;
+ }
+
+ .root-switcher-wrapper {
+ position: static;
+ }
+
+ .root-switcher {
+ opacity: 0.55;
+ font-size: 11px;
+ padding: 0 3px;
+ }
+
+ .root-switcher:hover {
+ opacity: 1;
+ text-decoration: none;
+ }
+
+ .root-entry {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+ }
+
+ .root-label {
+ color: rgb(255 255 255 / 85%);
+ }
+
+ .remove-btn {
+ opacity: 0.3;
+ font-size: 14px;
+ line-height: 1;
+ padding: 0 1px;
+ }
+
+ .remove-btn:hover {
+ opacity: 1;
+ color: #ff6b6b;
+ text-decoration: none;
+ }
`;
static properties = {
root: { type: Object },
stack: { type: Array },
siblings: { type: Array },
- _openDropdown: { state: true }
+ packages: { type: Array },
+ _openDropdown: { state: true },
+ _rootSwitcherOpen: { state: true }
};
constructor() {
@@ -115,7 +188,9 @@ class DrillBreadcrumb extends LitElement {
this.root = null;
this.stack = [];
this.siblings = [];
+ this.packages = [];
this._openDropdown = null;
+ this._rootSwitcherOpen = false;
}
connectedCallback() {
@@ -129,13 +204,17 @@ class DrillBreadcrumb extends LitElement {
}
updated() {
- this.hidden = this.stack.length === 0 || this.root === null;
+ this.hidden = this.root === null;
}
#handleDocumentClick = () => {
if (this._openDropdown !== null) {
this._openDropdown = null;
}
+
+ if (this._rootSwitcherOpen) {
+ this._rootSwitcherOpen = false;
+ }
};
#handleReset() {
@@ -170,13 +249,68 @@ class DrillBreadcrumb extends LitElement {
}));
}
+ #toggleRootSwitcher(event) {
+ event.stopPropagation();
+
+ this._rootSwitcherOpen = !this._rootSwitcherOpen;
+ }
+
+ #handleRootSwitch(spec, event) {
+ event.stopPropagation();
+
+ this._rootSwitcherOpen = false;
+ this.dispatchEvent(new CustomEvent(EVENTS.ROOT_SWITCH, {
+ detail: { spec },
+ bubbles: true,
+ composed: true
+ }));
+ }
+
+ #handleRootRemove(event) {
+ event.stopPropagation();
+
+ this.dispatchEvent(new CustomEvent(EVENTS.ROOT_REMOVE, {
+ bubbles: true,
+ composed: true
+ }));
+ }
+
render() {
- if (this.stack.length === 0 || this.root === null) {
+ if (this.root === null) {
return nothing;
}
+ const otherPackages = this.packages ?? [];
+ const isInDrill = this.stack.length > 0;
+ const i18n = window.i18n[currentLang()];
+
return html`
-
+ ${otherPackages.length > 0 ? html`
+
+
+ ${this._rootSwitcherOpen ? html`
+
+
+
+ ${otherPackages.map((pkg) => html`
+
+ `)}
+
+ ` : nothing}
+
+ ` : nothing}
+
+ ${isInDrill
+ ? html``
+ : html`${this.root.name}@${this.root.version}`
+ }
+
+
${this.stack.map((entry, stackIndex) => {
const siblingList = this.siblings?.[stackIndex] ?? [];
const hasSiblings = siblingList.length > 0;
@@ -212,4 +346,4 @@ class DrillBreadcrumb extends LitElement {
}
}
-customElements.define("drill-breadcrumb", DrillBreadcrumb);
+customElements.define("network-breadcrumb", NetworkBreadcrumb);
diff --git a/public/components/package-navigation/package-navigation.js b/public/components/package-navigation/package-navigation.js
deleted file mode 100644
index a6ac733c..00000000
--- a/public/components/package-navigation/package-navigation.js
+++ /dev/null
@@ -1,260 +0,0 @@
-// Import Third-party Dependencies
-import { LitElement, html, css, nothing } from "lit";
-import { repeat } from "lit/directives/repeat.js";
-
-// Import Internal Dependencies
-import * as utils from "../../common/utils.js";
-import "../icon/icon.js";
-
-/**
- * @typedef {Object} PackageMetadata
- * @property {string} spec - Package spec (e.g. "package@1.0.0")
- * @property {string} scanType - Type of scan ("cwd" for local)
- * @property {string} locationOnDisk - Path to the package on disk
- * @property {number} lastUsedAt - Timestamp of last usage
- * @property {string | null} integrity - Package integrity hash
- */
-
-class PackageNavigation extends LitElement {
- static styles = css`
- b,p {
- margin: 0;
- padding: 0;
- border: 0;
- font: inherit;
- font-size: 100%;
- }
-
- :host {
- z-index: 30;
- display: flex;
- justify-content: center;
- align-items: center;
- height: 30px;
- left: 50px;
- padding-left: 20px;
- max-width: calc(100vw - 70px);
- box-sizing: border-box;
- background: var(--primary);
- box-shadow: 2px 1px 10px #26107f7a;
- }
-
- :host-context(body.dark) {
- background: var(--dark-theme-primary-color);
- }
-
- .packages {
- height: 30px;
- display: flex;
- background: var(--primary);
- }
-
- .packages > .package {
- height: 30px;
- font-family: mononoki;
- display: flex;
- align-items: center;
- background: linear-gradient(to right, rgb(55 34 175 / 100%) 0%, rgb(87 74 173 / 100%) 50%, rgb(59 110 205) 100%);
- padding: 0 10px;
- border-right: 2px solid #0f041a;
- text-shadow: 1px 1px 10px #000;
- color: #def7ff;
- }
-
- :host-context(body.dark) .packages > .package {
- background: linear-gradient(to right, rgb(11 3 31 / 100%) 0%, rgb(11 3 31 / 80%) 50%, rgb(11 3 31 / 60%) 100%);
- }
-
- .packages > .package > * {
- transform: skewX(20deg);
- }
-
- .packages > .package:first-child {
- padding-left: 10px;
- }
-
- .packages > .package:not(.active):hover {
- background: linear-gradient(to right, rgb(55 34 175 / 100%) 1%, rgb(68 121 218) 100%);
- color: #defff9;
- cursor: pointer;
- }
-
- :host-context(body.dark) .packages > .package:not(.active):hover {
- background: linear-gradient(to right, rgb(11 3 31 / 70%) 1%, rgb(11 3 31 / 50%) 100%);
- }
-
- .packages > .package.active {
- background: linear-gradient(to right, rgb(55 34 175 / 100%) 0%, rgb(87 74 173 / 100%) 50%, rgb(59 110 205) 100%);
- }
-
- .packages > .package.active > b {
- background: var(--secondary);
- }
-
- .packages > .package.active > .remove {
- display: block;
- }
-
- .packages > .package > b:last-of-type:not(:first-of-type) {
- background: #f57c00;
- }
-
- .packages > .package > b {
- font-weight: bold;
- font-size: 12px;
- margin-left: 5px;
- background: var(--secondary-darker);
- padding: 3px 5px;
- border-radius: 2px;
- font-family: Roboto;
- letter-spacing: 1px;
- }
-
- .add {
- height: 30px;
- font-size: 20px;
- border: none;
- background: var(--secondary-darker);
- cursor: pointer;
- padding: 0 7px;
- transition: 0.2s all ease;
- color: #def7ff;
- }
-
- .add:hover {
- background: var(--secondary);
- cursor: pointer;
- }
-
- .add > i {
- transform: skewX(20deg);
- }
-
- button.remove {
- display: none;
- border: none;
- position: relative;
- cursor: pointer;
- color: #fff5dc;
- background: #ff3434e2;
- margin-left: 10px;
- border-radius: 50%;
- line-height: 16px;
- text-shadow: 1px 1px 10px #000;
- font-weight: bold;
- width: 20px;
- }
-
- button.remove:hover {
- cursor: pointer;
- background: #ff5353e2;
- }
- `;
-
- static properties = {
- /**
- * Array of package metadata objects
- * @type {PackageMetadata[]}
- */
- metadata: { type: Array },
- /**
- * Currently active package spec
- * @type {string}
- */
- activePackage: { type: String }
- };
-
- constructor() {
- super();
- /** @type {PackageMetadata[]} */
- this.metadata = [];
- /** @type {string} */
- this.activePackage = "";
- }
-
- /**
- * Check if there are at least 2 packages
- * @returns {boolean}
- */
- get #hasAtLeast2Packages() {
- return this.metadata.length > 1;
- }
-
- /**
- * Handle click on a package to select it
- * @param {string} spec
- */
- #handlePackageClick(spec) {
- if (this.activePackage !== spec) {
- window.socket.commands.search(spec);
- }
- }
-
- /**
- * Handle click on remove button
- * @param {Event} event
- * @param {string} packageName
- */
- #handleRemoveClick(event, packageName) {
- event.stopPropagation();
- window.socket.commands.remove(packageName);
- }
-
- #handleAddClick() {
- window.navigation.setNavByName("search--view");
- }
-
- /**
- * Render a single package element
- * @param {PackageMetadata} param0
- * @returns {import("lit").TemplateResult}
- */
- #renderPackage({ spec, scanType }) {
- const isLocal = scanType === "cwd";
- const { name, version } = utils.parseNpmSpec(spec);
- const isActive = spec === this.activePackage;
-
- return html`
-
this.#handlePackageClick(spec)}"
- >
-
${name}
-
v${version}
- ${isLocal ? html`
local` : nothing}
- ${this.#hasAtLeast2Packages
- ? html`
-
- `
- : nothing}
-
- `;
- }
-
- render() {
- if (this.metadata.length === 0) {
- return nothing;
- }
-
- return html`
-
- ${repeat(
- this.metadata,
- (pkg) => pkg.spec,
- (pkg) => this.#renderPackage(pkg)
- )}
-
-
- `;
- }
-}
-
-customElements.define("package-navigation", PackageNavigation);
diff --git a/public/core/events.js b/public/core/events.js
index c1f7c421..04421754 100644
--- a/public/core/events.js
+++ b/public/core/events.js
@@ -13,5 +13,7 @@ export const EVENTS = {
SEARCH_COMMAND_INIT: "search-command-init",
DRILL_RESET: "drill-reset",
DRILL_BACK: "drill-back",
- DRILL_SWITCH: "drill-switch"
+ DRILL_SWITCH: "drill-switch",
+ ROOT_SWITCH: "root-switch",
+ ROOT_REMOVE: "root-remove"
};
diff --git a/public/core/search-nav.js b/public/core/search-nav.js
deleted file mode 100644
index 7468e3bc..00000000
--- a/public/core/search-nav.js
+++ /dev/null
@@ -1,42 +0,0 @@
-// Import Internal Dependencies
-import { createDOMElement } from "../common/utils.js";
-import "../components/package-navigation/package-navigation.js";
-
-// CONSTANTS
-const kSearchShortcut = navigator.userAgent.includes("Mac") ? "⌘K" : "Ctrl+K";
-
-export function initSearchNav(
- data,
- options = {}
-) {
- const {
- initFromZero = true
- } = options;
-
- const searchNavElement = document.getElementById("search-nav");
- if (!searchNavElement) {
- throw new Error("Unable to found search navigation");
- }
-
- if (initFromZero) {
- searchNavElement.innerHTML = "";
- const element = document.createElement("package-navigation");
- searchNavElement.appendChild(
- element
- );
- element.metadata = data;
- element.activePackage = data.length > 0 ? data[0].spec : "";
- }
-
- if (document.getElementById("search-shortcut-hint") === null) {
- document.body.appendChild(
- createDOMElement("div", {
- classList: ["search-shortcut-hint"],
- attributes: { id: "search-shortcut-hint" },
- childs: [
- createDOMElement("kbd", { text: kSearchShortcut })
- ]
- })
- );
- }
-}
diff --git a/public/css/light.css b/public/css/light.css
index c6cda34c..85646711 100644
--- a/public/css/light.css
+++ b/public/css/light.css
@@ -15,6 +15,7 @@
--dark-theme-accent-darker: #261c66;
--dark-theme-accent-color: #8d7afc;
--dark-theme-gray: #191a38;
+
}
body {
@@ -28,4 +29,5 @@ body {
body.dark {
color: white;
background: var(--dark-theme-gray);
+
}
diff --git a/public/main.js b/public/main.js
index 6ffc12f0..92e3d5b2 100644
--- a/public/main.js
+++ b/public/main.js
@@ -13,14 +13,16 @@ import "./components/search-command/search-command.js";
import { Settings } from "./components/views/settings/settings.js";
import { HomeView } from "./components/views/home/home.js";
import "./components/views/search/search.js";
-import "./components/drill-breadcrumb/drill-breadcrumb.js";
+import "./components/network-breadcrumb/network-breadcrumb.js";
import { NetworkNavigation } from "./core/network-navigation.js";
import { i18n } from "./core/i18n.js";
-import { initSearchNav } from "./core/search-nav.js";
import * as utils from "./common/utils.js";
import { EVENTS } from "./core/events.js";
import { WebSocketClient } from "./websocket.js";
+// CONSTANTS
+const kSearchShortcut = navigator.userAgent.includes("Mac") ? "⌘K" : "Ctrl+K";
+
let secureDataSet;
let nsn;
let homeView;
@@ -42,7 +44,17 @@ document.addEventListener("DOMContentLoaded", async() => {
// update searchview after window.i18n is set
searchview.requestUpdate();
- drillBreadcrumb = document.querySelector("drill-breadcrumb");
+ document.body.appendChild(
+ utils.createDOMElement("div", {
+ classList: ["search-shortcut-hint"],
+ attributes: { id: "search-shortcut-hint" },
+ childs: [
+ utils.createDOMElement("kbd", { text: kSearchShortcut })
+ ]
+ })
+ );
+
+ drillBreadcrumb = document.querySelector("network-breadcrumb");
drillBreadcrumb.addEventListener(EVENTS.DRILL_RESET, resetDrill);
drillBreadcrumb.addEventListener(EVENTS.DRILL_BACK, function handleDrillBack(event) {
drillBackTo(event.detail.index);
@@ -52,6 +64,22 @@ document.addEventListener("DOMContentLoaded", async() => {
drillStack.length = stackIndex;
drillInto(nodeId);
});
+ drillBreadcrumb.addEventListener(EVENTS.ROOT_SWITCH, function handleRootSwitch(event) {
+ window.socket.commands.search(event.detail.spec);
+ });
+ drillBreadcrumb.addEventListener(EVENTS.ROOT_REMOVE, function handleRootRemove() {
+ const specToRemove = window.activePackage;
+ const nextPackage = drillBreadcrumb.packages[0];
+ if (nextPackage) {
+ window.socket.commands.search(nextPackage.spec);
+ }
+ else {
+ window.navigation.hideMenu("network--view");
+ window.navigation.hideMenu("home--view");
+ window.navigation.setNavByName("search--view");
+ }
+ window.socket.commands.remove(specToRemove);
+ });
await init();
window.dispatchEvent(
@@ -85,7 +113,6 @@ async function onSocketPayload(event) {
window.activePackage = name + "@" + version;
await init({ navigateToNetworkView: true });
- initSearchNav(payload, { initFromZero: false });
dispatchSearchCommandInit();
}
@@ -112,11 +139,8 @@ async function onSocketInitOrReload(event) {
await init();
}
- const navCache = cache.slice(0, 3);
- const overflowCache = cache.slice(3);
-
- initSearchNav(navCache);
- searchview.cachedSpecs = overflowCache;
+ drillBreadcrumb.packages = cache.filter((pkg) => pkg.spec !== window.activePackage);
+ searchview.cachedSpecs = cache;
searchview.reset();
dispatchSearchCommandInit();
}
@@ -222,6 +246,9 @@ function updateDrillBreadcrumb() {
name: rootEntry.name,
version: rootEntry.version
};
+ drillBreadcrumb.packages = window.cachedSpecs.filter(
+ (pkg) => pkg.spec !== window.activePackage
+ );
drillBreadcrumb.stack = drillStack.map((nodeId) => {
const entry = secureDataSet.linker.get(nodeId);
@@ -297,17 +324,6 @@ async function init(options = {}) {
window.navigation.setNavByName("network--view");
}
- // update search nav
- const pkgs = document.querySelectorAll("#search-nav .packages > .package");
- for (const pkg of pkgs) {
- if (pkg.dataset.name.startsWith(window.activePackage)) {
- pkg.classList.add("active");
- }
- else {
- pkg.classList.remove("active");
- }
- }
-
PackageInfo.close();
console.log("[INFO] Node-Secure is ready!");
diff --git a/views/index.html b/views/index.html
index b16cb4d4..84e35e22 100644
--- a/views/index.html
+++ b/views/index.html
@@ -64,8 +64,6 @@
-
@@ -80,7 +78,7 @@
[[=z.token('please_wait')]]
-
+
[[=z.token('network.unlocked')]]