Skip to content

Commit

Permalink
feat: Add button icons to the project selector (#918)
Browse files Browse the repository at this point in the history
  • Loading branch information
jespertheend authored Mar 27, 2024
1 parent 476dd99 commit c31e2f8
Show file tree
Hide file tree
Showing 30 changed files with 197 additions and 127 deletions.
4 changes: 1 addition & 3 deletions studio/src/Studio.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { ProjectManager } from "./projectSelector/ProjectManager.js";
import { BuiltInDefaultAssetLinksManager } from "./assets/BuiltInDefaultAssetLinksManager.js";
import { BuiltInAssetManager } from "./assets/BuiltInAssetManager.js";
import { DragManager } from "./misc/DragManager.js";
import { ColorizerFilterManager } from "./util/colorizerFilters/ColorizerFilterManager.js";
import { ServiceWorkerManager } from "./misc/ServiceWorkerManager.js";
import { IS_DEV_BUILD } from "./studioDefines.js";
import { DevSocketManager } from "./network/DevSocketManager.js";
Expand Down Expand Up @@ -50,8 +49,7 @@ export class Studio {
this.preferencesManager.addLocation(new GlobalPreferencesLocation(this.indexedDb));

this.selectionManager = new SelectionManager();
this.colorizerFilterManager = new ColorizerFilterManager();
this.popoverManager = new PopoverManager(this.colorizerFilterManager);
this.popoverManager = new PopoverManager();
this.gestureInProgressManager = new GestureInProgressManager();

/** @type {KeyboardShortcutManager<typeof autoRegisterShortcutCommands>} */
Expand Down
111 changes: 77 additions & 34 deletions studio/src/projectSelector/ProjectSelector.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { RENDA_VERSION_STRING } from "../../../src/engineDefines.js";
import { IndexedDbUtil } from "../../../src/util/IndexedDbUtil.js";
import { PromiseWaitHelper } from "../../../src/util/PromiseWaitHelper.js";
import { createSpinner } from "../ui/spinner.js";
import { ColorizerFilterManager } from "../util/colorizerFilters/ColorizerFilterManager.js";
import { IndexedDbStudioFileSystem } from "../util/fileSystems/IndexedDbStudioFileSystem.js";

export class ProjectSelector {
Expand Down Expand Up @@ -68,31 +69,43 @@ export class ProjectSelector {
*/
this.allowOpeningNew = true;

this.createAction("New Project", async () => {
if (this.allowOpeningNew) {
this.willOpenProjectAfterLoad();
const studio = await this.waitForStudio();
studio.projectManager.openNewDbProject(true);
}
this.setVisibility(false);
this.createAction({
text: "New Project",
iconUrl: "static/icons/newDatabase.svg",
onClick: async () => {
if (this.allowOpeningNew) {
this.willOpenProjectAfterLoad();
const studio = await this.waitForStudio();
studio.projectManager.openNewDbProject(true);
}
this.setVisibility(false);
},
});

const { buttonEl: openProjectButton } = this.createAction("Open Project", async () => {
this.willOpenProjectAfterLoad();
const studio = await this.waitForStudio();
studio.projectManager.openProjectFromLocalDirectory();
this.setVisibility(false);
const { buttonEl: openProjectButton } = this.createAction({
text: "Open Project",
iconUrl: "static/icons/folder.svg",
onClick: async () => {
this.willOpenProjectAfterLoad();
const studio = await this.waitForStudio();
studio.projectManager.openProjectFromLocalDirectory();
this.setVisibility(false);
},
});
if (!("showDirectoryPicker" in globalThis)) {
openProjectButton.disabled = true;
openProjectButton.title = "Opening local projects is not supported by your browser.";
}

this.createAction("Connect Remote Project", async () => {
this.willOpenProjectAfterLoad();
const studio = await this.waitForStudio();
studio.projectManager.openNewRemoteProject(true);
this.setVisibility(false);
this.createAction({
text: "Connect to Remote",
iconUrl: "static/icons/remoteSignal.svg",
onClick: async () => {
this.willOpenProjectAfterLoad();
const studio = await this.waitForStudio();
studio.projectManager.openNewRemoteProject(true);
this.setVisibility(false);
},
});

/** @type {StoredProjectEntryAny[]?} */
Expand Down Expand Up @@ -130,9 +143,13 @@ export class ProjectSelector {
} else if (/linux/i.test(ua)) {
text = "Get the Linux App";
}
const { listItemEl } = this.createAction(text, async () => {
await event.prompt();
listItemEl.remove();
const { listItemEl } = this.createAction({
text,
iconUrl: "static/icons/download.svg",
onClick: async () => {
await event.prompt();
listItemEl.remove();
},
});
});
}
Expand All @@ -158,26 +175,44 @@ export class ProjectSelector {
}

/**
* @param {string} name
* @param {() => void} onClick
* @param {CreateListButtonOptions} options
*/
createAction(name, onClick) {
return this.createListButton(this.actionsListEl, name, onClick);
createAction(options) {
return this.createListButton(this.actionsListEl, options);
}

/**
* @typedef CreateListButtonOptions
* @property {string} text
* @property {string} iconUrl
* @property {() => void} onClick
*/

/**
* @param {HTMLUListElement} listEl
* @param {string} name
* @param {() => void} onClick
* @param {CreateListButtonOptions} options
*/
createListButton(listEl, name, onClick) {
createListButton(listEl, { iconUrl, text, onClick }) {
const item = document.createElement("li");
listEl.appendChild(item);
const button = document.createElement("button");
item.appendChild(button);
button.classList.add("project-selector-button");
button.textContent = name;
button.addEventListener("click", onClick);

const buttonWrap = document.createElement("span");
buttonWrap.classList.add("button-wrap");
button.append(buttonWrap);

const iconEl = document.createElement("div");
iconEl.classList.add("project-selector-button-icon");
iconEl.style.backgroundImage = `url(${iconUrl})`;
ColorizerFilterManager.instance().applyFilter(iconEl, "var(--default-button-text-color)");
buttonWrap.append(iconEl);

const textEl = document.createElement("span");
textEl.textContent = text;
buttonWrap.append(textEl);
return {
listItemEl: item,
buttonEl: button,
Expand Down Expand Up @@ -240,25 +275,33 @@ export class ProjectSelector {
if (entry.alias) {
text = entry.alias;
}
const { buttonEl } = this.createListButton(this.recentListEl, text, async () => {
this.willOpenProjectAfterLoad();
const studio = await this.waitForStudio();
studio.projectManager.openExistingProject(entry, true);
this.setVisibility(false);
});
let icon = "";
let tooltip = "";
if (entry.fileSystemType == "fsa") {
icon = "folder";
tooltip = "File System on Disk";
} else if (entry.fileSystemType == "db") {
icon = "database";
tooltip = "Stored in Cookies";
} else if (entry.fileSystemType == "remote") {
icon = "remoteSignal";
tooltip = "Remote File System";
if (entry.remoteProjectConnectionType == "internal") {
tooltip += " (Internal Connection)";
} else if (entry.remoteProjectConnectionType == "webRtc") {
tooltip += " (WebRTC Connection)";
}
}
const { buttonEl } = this.createListButton(this.recentListEl, {
text,
iconUrl: `static/icons/${icon}.svg`,
onClick: async () => {
this.willOpenProjectAfterLoad();
const studio = await this.waitForStudio();
studio.projectManager.openExistingProject(entry, true);
this.setVisibility(false);
},
});
buttonEl.title = tooltip;
buttonEl.addEventListener("contextmenu", (e) => {
if (this.loadedStudio) {
Expand Down
21 changes: 19 additions & 2 deletions studio/src/projectSelector/projectSelector.css
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@
}

.project-selector-list > li {
margin: 4px 0px;
margin: 2px 0px;
}

.project-selector-button {
Expand All @@ -153,15 +153,32 @@
font-size: var(--default-font-size);
font-family: var(--default-font-family);
}

button.project-selector-button[disabled] {
color: var(--default-text-color-disabled-context-menu);
}

.project-selector-button:hover:not([disabled]) {
color: var(--default-button-text-color);
}

.project-selector-button:hover:not([disabled]):not(:active) {
background-color: var(--default-button-color);
color: var(--default-button-text-color);
}

.project-selector-button:active {
background-color: var(--default-button-active-color);
}

.project-selector-button .button-wrap {
display: flex;
gap: 5px;
}

.project-selector-button-icon {
width: 1lh;
height: 1lh;
background-repeat: no-repeat;
background-position: center;
display: inline-block;
}
9 changes: 4 additions & 5 deletions studio/src/ui/Button.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { ColorizerFilterManager } from "../util/colorizerFilters/ColorizerFilterManager.js";

/**
* @template TCallbacksContext
* @typedef {object} ButtonGuiOptionsType
* @property {string} [text = ""] The text to show on the button.
* @property {string} [icon = ""] The icon to show on the button.
* @property {string} [tooltip = ""] The text to show when hovering over the button.
* @property {import("../util/colorizerFilters/ColorizerFilterManager.js").ColorizerFilterManager?} [colorizerFilterManager = null] The colorizer filter manager if you want theme support for icons to work.
* @property {boolean} [hasDownArrow = false] Whether the button should show a down arrow.
* @property {((ctx: TCallbacksContext) => any)?} [onClick = null] The function to call when the button is clicked.
* @property {boolean} [draggable = false] Whether the button should be draggable.
Expand Down Expand Up @@ -38,7 +39,6 @@ export class Button {
text = "",
icon = "",
tooltip = "",
colorizerFilterManager = null,
hasDownArrow = false,
onClick = null,
draggable = false,
Expand All @@ -47,7 +47,6 @@ export class Button {
} = {}) {
this.iconUrl = icon;
this.#hasDownArrow = hasDownArrow;
this.colorizerFilterManager = colorizerFilterManager;
this.currentText = text;
const { el, iconEl, textEl } = this.createButtonEl();
this.el = el;
Expand Down Expand Up @@ -180,8 +179,8 @@ export class Button {
#applyIconToEl(el, iconUrl) {
el.style.backgroundImage = `url(${iconUrl})`;
el.style.display = iconUrl ? "" : "none";
if (iconUrl && this.colorizerFilterManager) {
this.colorizerFilterManager.applyFilter(el, "var(--default-button-text-color)");
if (iconUrl) {
ColorizerFilterManager.instance().applyFilter(el, "var(--default-button-text-color)");
}
}

Expand Down
4 changes: 2 additions & 2 deletions studio/src/ui/PathGui.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getStudioInstance } from "../studioInstance.js";
import { ColorizerFilterManager } from "../util/colorizerFilters/ColorizerFilterManager.js";

/**
* @typedef {import("./propertiesTreeView/types.ts").GuiOptionsBase} PathGuiOptions
Expand Down Expand Up @@ -103,7 +103,7 @@ export class PathGui {
if (!isLast) {
const arrow = document.createElement("span");
arrow.textContent = "/";
getStudioInstance().colorizerFilterManager.applyFilter(arrow, "var(--text-color-level0)");
ColorizerFilterManager.instance().applyFilter(arrow, "var(--text-color-level0)");
arrow.classList.add("path-arrow");
this.el.appendChild(arrow);
}
Expand Down
27 changes: 10 additions & 17 deletions studio/src/ui/TreeView.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { getMaybeStudioInstance, getStudioInstance } from "../studioInstance.js";
import { parseMimeType } from "../util/util.js";
import { clamp, generateUuid, iLerp } from "../../../src/util/mod.js";
import { ColorizerFilterManager } from "../util/colorizerFilters/ColorizerFilterManager.js";

/**
* @typedef TreeViewInitData
Expand Down Expand Up @@ -434,11 +435,7 @@ export class TreeView {
iconEl.classList.add("tree-view-icon");
iconEl.style.backgroundImage = `url(${iconUrl})`;

const colorizerFilterManager = getMaybeStudioInstance()?.colorizerFilterManager;
// TreeViews are sometimes used in tests without a mocked colorizerFilterManager
if (colorizerFilterManager) {
colorizerFilterManager.applyFilter(iconEl, "var(--default-button-text-color)");
}
ColorizerFilterManager.instance().applyFilter(iconEl, "var(--default-button-text-color)");

this.addedIcons.push(iconEl);
this.afterEl.appendChild(iconEl);
Expand Down Expand Up @@ -1385,18 +1382,14 @@ export class TreeView {
}
if (this.#focusSelectedShortcutCondition) this.#focusSelectedShortcutCondition.setValue(focusSelected);

const colorizerFilterManager = getMaybeStudioInstance()?.colorizerFilterManager;
// TreeViews are sometimes used in tests without a mocked colorizerFilterManager
if (colorizerFilterManager) {
let color;
if (focusSelected) {
color = "var(--selected-text-color)";
} else {
color = "var(--default-button-text-color)";
}
for (const iconEl of this.addedIcons) {
colorizerFilterManager.applyFilter(iconEl, color);
}
let color;
if (focusSelected) {
color = "var(--selected-text-color)";
} else {
color = "var(--default-button-text-color)";
}
for (const iconEl of this.addedIcons) {
ColorizerFilterManager.instance().applyFilter(iconEl, color);
}
}

Expand Down
10 changes: 4 additions & 6 deletions studio/src/ui/popoverMenus/PopoverManager.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { waitForEventLoop } from "../../../../src/util/util.js";
import { ColorizerFilterManager } from "../../util/colorizerFilters/ColorizerFilterManager.js";
import { ContextMenu } from "./ContextMenu.js";
import { Popover } from "./Popover.js";

Expand All @@ -8,15 +9,12 @@ export class PopoverManager {
*/
#activePopovers = [];

/**
* @param {import("../../util/colorizerFilters/ColorizerFilterManager.js").ColorizerFilterManager} colorizerFilterManager
*/
constructor(colorizerFilterManager) {
constructor() {
this.curtainEl = document.createElement("div");
this.curtainEl.classList.add("popover-curtain");

const iconDefaultColorFilter = colorizerFilterManager.getFilter("var(--text-color-level0)");
const iconHoverColorFilter = colorizerFilterManager.getFilter("var(--selected-text-color)");
const iconDefaultColorFilter = ColorizerFilterManager.instance().getFilter("var(--text-color-level0)");
const iconHoverColorFilter = ColorizerFilterManager.instance().getFilter("var(--selected-text-color)");

// References are kept around to ensure the filters don't get garbage collected.
this.iconDefaultColorFilterRef = iconDefaultColorFilter.getUsageReference();
Expand Down
6 changes: 6 additions & 0 deletions studio/src/util/colorizerFilters/ColorizerFilter.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { ColorizerFilterUsageReference } from "./ColorizerFilterUsageReference.js";

export class ColorizerFilter {
#cssColor;
get cssColor() {
return this.#cssColor;
}

/**
* @param {string} cssColor
* @param {HTMLElement} containerEl
*/
constructor(cssColor, containerEl) {
this.#cssColor = cssColor;
const ns = "http://www.w3.org/2000/svg";
this.svgEl = document.createElementNS(ns, "svg");
this.svgEl.setAttribute("xmlns", ns);
Expand Down
Loading

0 comments on commit c31e2f8

Please sign in to comment.