Skip to content

Commit

Permalink
Merge pull request #23 from ubq-testing/feat/repo-select
Browse files Browse the repository at this point in the history
Feat/repo select
  • Loading branch information
rndquu authored Dec 5, 2024
2 parents b52f2db + 4672fac commit ae1a823
Show file tree
Hide file tree
Showing 10 changed files with 191 additions and 41 deletions.
23 changes: 10 additions & 13 deletions static/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,20 @@ export async function mainModule() {
try {
const ubiquityOrgsToFetchOfficialConfigFrom = ["ubiquity-os"];
const fetcher = new ManifestFetcher(ubiquityOrgsToFetchOfficialConfigFrom, auth.octokit);
const cache = fetcher.checkManifestCache();

if (auth.isActiveSession()) {
const userOrgs = await auth.getGitHubUserOrgs();

if (Object.keys(cache).length === 0) {
renderer.manifestGuiBody.dataset.loading = "true";
const killNotification = toastNotification("Fetching manifest data...", { type: "info", shouldAutoDismiss: true });
renderOrgPicker(renderer, []);

await fetcher.fetchMarketplaceManifests();
await fetcher.fetchOfficialPluginConfig();
killNotification();
renderer.manifestGuiBody.dataset.loading = "false";
}
renderer.manifestGuiBody.dataset.loading = "true";
const killNotification = toastNotification("Fetching manifest data...", { type: "info", shouldAutoDismiss: true });

const userOrgs = await auth.getGitHubUserOrgs();
const userOrgRepos = await auth.getGitHubUserOrgRepos(userOrgs);
localStorage.setItem("orgRepos", JSON.stringify(userOrgRepos));
renderOrgPicker(renderer, userOrgs);

await fetcher.fetchMarketplaceManifests();
await fetcher.fetchOfficialPluginConfig();
renderer.manifestGuiBody.dataset.loading = "false";
killNotification();
} else {
renderOrgPicker(renderer, []);
}
Expand Down
10 changes: 10 additions & 0 deletions static/scripts/authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@ export class AuthService {
return response.data.map((org: { login: string }) => org.login);
}

public async getGitHubUserOrgRepos(orgs: string[]): Promise<Record<string, string[]>> {
const octokit = await this.getOctokit();
const orgRepos: Record<string, string[]> = {};
for (const org of orgs) {
const response = await octokit.rest.repos.listForOrg({ org });
orgRepos[org] = response.data.map((repo: { name: string }) => repo.name);
}
return orgRepos;
}

public async getOctokit(): Promise<Octokit> {
if (this.octokit) return this.octokit;
const token = await this.getSessionToken();
Expand Down
18 changes: 12 additions & 6 deletions static/scripts/config-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ export class ConfigParser {
}

async fetchUserInstalledConfig(org: string, octokit: Octokit, repo = CONFIG_ORG_REPO, path = CONFIG_FULL_PATH) {
if (org === repo) {
repo = CONFIG_ORG_REPO;
}

if (repo === CONFIG_ORG_REPO) {
await this.configRepoExistenceCheck(org, repo, octokit);
}
Expand Down Expand Up @@ -106,20 +110,22 @@ export class ConfigParser {
}

parseConfig(config?: string | null): PluginConfig {
if (config) {
if (config && typeof config === "string") {
return YAML.parse(config);
} else {
return YAML.parse(this.loadConfig());
}
if (!this.newConfigYml) {
this.loadConfig();
}
return YAML.parse(`${this.newConfigYml}`);
}

async updateConfig(org: string, octokit: Octokit, path = CONFIG_FULL_PATH, repo = CONFIG_ORG_REPO) {
async updateConfig(org: string, octokit: Octokit, repo = CONFIG_ORG_REPO, path = CONFIG_FULL_PATH) {
return this.createOrUpdateFileContents(org, repo, path, octokit);
}

async createOrUpdateFileContents(org: string, repo: string, path: string, octokit: Octokit) {
if (org === repo) {
repo = CONFIG_ORG_REPO;
}

const recentSha = await octokit.repos.getContent({
owner: org,
repo: repo,
Expand Down
6 changes: 3 additions & 3 deletions static/scripts/render-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class ManifestRenderer {
private _configDefaults: { [key: string]: { type: string; value: string; items: { type: string } | null } } = {};
private _auth: AuthService;
private _backButton: HTMLButtonElement;
private _currentStep: "orgPicker" | "pluginSelector" | "configEditor" = "orgPicker";
private _currentStep: "orgPicker" | "repoPicker" | "pluginSelector" | "configEditor" = "orgPicker";
private _orgs: string[] = [];

constructor(auth: AuthService) {
Expand Down Expand Up @@ -45,11 +45,11 @@ export class ManifestRenderer {
this._orgs = orgs;
}

get currentStep(): "orgPicker" | "pluginSelector" | "configEditor" {
get currentStep(): "orgPicker" | "repoPicker" | "pluginSelector" | "configEditor" {
return this._currentStep;
}

set currentStep(step: "orgPicker" | "pluginSelector" | "configEditor") {
set currentStep(step: "orgPicker" | "repoPicker" | "pluginSelector" | "configEditor") {
this._currentStep = step;
}

Expand Down
8 changes: 5 additions & 3 deletions static/scripts/rendering/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createElement } from "../../utils/element-helpers";
import { ManifestRenderer } from "../render-manifest";
import { renderOrgPicker } from "./org-select";
import { renderPluginSelector } from "./plugin-select";
import { renderRepoPicker } from "./repo-select";

export function createBackButton(renderer: ManifestRenderer): HTMLButtonElement {
const backButton = createElement("button", {
Expand All @@ -21,11 +22,12 @@ function handleBackButtonClick(renderer: ManifestRenderer): void {
if (readmeContainer) {
readmeContainer.remove();
}

// "pluginSelector" | "configEditor"
const step = renderer.currentStep;

if (step === "pluginSelector" || step === "orgPicker") {
if (step === "repoPicker" || step === "orgPicker") {
renderOrgPicker(renderer, renderer.orgs);
} else if (step === "pluginSelector") {
renderRepoPicker(renderer, JSON.parse(localStorage.getItem("orgRepos") || "{}"));
} else if (step === "configEditor") {
renderPluginSelector(renderer);
}
Expand Down
17 changes: 3 additions & 14 deletions static/scripts/rendering/org-select.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { createElement } from "../../utils/element-helpers";
import { STRINGS } from "../../utils/strings";
import { toastNotification } from "../../utils/toaster";
import { ManifestRenderer } from "../render-manifest";
import { controlButtons } from "./control-buttons";
import { renderPluginSelector } from "./plugin-select";
import { renderRepoPicker } from "./repo-select";
import { closeAllSelect, updateGuiTitle } from "./utils";

/**
Expand Down Expand Up @@ -85,16 +84,6 @@ function handleOrgSelection(renderer: ManifestRenderer, org: string): void {
throw new Error("No org selected");
}
localStorage.setItem("selectedOrg", org);
fetchOrgConfig(renderer, org).catch(console.error);
}

async function fetchOrgConfig(renderer: ManifestRenderer, org: string): Promise<void> {
const kill = toastNotification("Fetching organization config...", { type: "info", shouldAutoDismiss: true });
const octokit = renderer.auth.octokit;
if (!octokit) {
throw new Error("No org or octokit found");
}
await renderer.configParser.fetchUserInstalledConfig(org, octokit);
renderPluginSelector(renderer);
kill();
const repos = JSON.parse(localStorage.getItem("orgRepos") || "{}");
renderRepoPicker(renderer, repos);
}
130 changes: 130 additions & 0 deletions static/scripts/rendering/repo-select.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { createElement } from "../../utils/element-helpers";
import { STRINGS } from "../../utils/strings";
import { toastNotification } from "../../utils/toaster";
import { ManifestRenderer } from "../render-manifest";
import { controlButtons } from "./control-buttons";
import { renderPluginSelector } from "./plugin-select";
import { updateGuiTitle } from "./utils";

export function renderRepoPicker(renderer: ManifestRenderer, repos: Record<string, string[]>): void {
renderer.currentStep = "repoPicker";
controlButtons({ hide: true });
renderer.backButton.style.display = "block";
renderer.manifestGui?.classList.add("rendering");
renderer.manifestGuiBody.innerHTML = null;

if (!Reflect.ownKeys(repos).length) {
updateGuiTitle("No repositories found");
renderer.manifestGuiBody.appendChild(document.createElement("tr"));
renderer.manifestGui?.classList.add("rendered");
return;
}

localStorage.setItem("orgRepos", JSON.stringify(repos));

const selectedOrg = localStorage.getItem("selectedOrg");

if (!selectedOrg) {
throw new Error(`No selected org found in local storage`);
}

const topLevelRow = document.createElement("tr");
const topLevelCell = document.createElement("td");
topLevelCell.colSpan = 4;
topLevelCell.className = STRINGS.TDV_CENTERED;

const useOrgConfigButton = createElement("button", {
id: "use-org-config-button",
textContent: `Use ${selectedOrg} config`,
});

useOrgConfigButton.style.width = "100%";
useOrgConfigButton.style.textAlign = "left";

useOrgConfigButton.addEventListener("click", () => {
localStorage.setItem("selectedRepo", selectedOrg);
fetchOrgConfig(renderer, selectedOrg, selectedOrg)
.then(() => {
renderPluginSelector(renderer);
})
.catch((error) => {
console.error(error);
toastNotification("Error fetching org config", { type: "error" });
});
});

topLevelCell.appendChild(useOrgConfigButton);
topLevelRow.appendChild(topLevelCell);
renderer.manifestGuiBody.appendChild(topLevelRow);

const pickerRow = document.createElement("tr");
const pickerCell = document.createElement("td");
pickerCell.colSpan = 4;
pickerCell.className = STRINGS.TDV_CENTERED;

updateGuiTitle("Select a Repository");

const repoSelect = createElement("select", {
id: "repo-picker-select",
class: STRINGS.PICKER_SELECT,
style: "width: 100%",
});

const defaultOption = createElement("option", {
value: null,
textContent: "Or select a repository...",
});
repoSelect.appendChild(defaultOption);

const orgRepos = repos[selectedOrg];

if (!orgRepos) {
throw new Error("No org repos found");
}

orgRepos.forEach((repo) => {
const option = createElement("option", {
value: repo,
textContent: repo,
});
repoSelect.appendChild(option);
});

repoSelect.addEventListener("change", (event) => handleRepoSelection(event, renderer));
pickerCell.appendChild(repoSelect);
pickerRow.appendChild(pickerCell);
renderer.manifestGuiBody.appendChild(pickerRow);

renderer.manifestGui?.classList.add("rendered");
}

async function fetchOrgConfig(renderer: ManifestRenderer, org: string, repo: string): Promise<void> {
const kill = toastNotification(`Fetching ${org} config...`, { type: "info", shouldAutoDismiss: true });
const octokit = renderer.auth.octokit;
if (!octokit) {
throw new Error("No org or octokit found");
}
await renderer.configParser.fetchUserInstalledConfig(org, octokit, repo);
kill();
}

function handleRepoSelection(event: Event, renderer: ManifestRenderer): void {
const selectElement = event.target as HTMLSelectElement;
const selectedRepo = selectElement.value;
if (selectedRepo) {
localStorage.setItem("selectedRepo", selectedRepo);
const selectedOrg = localStorage.getItem("selectedOrg");
if (!selectedOrg) {
throw new Error("No selected org found");
}

fetchOrgConfig(renderer, selectedOrg, selectedRepo)
.then(() => {
renderPluginSelector(renderer);
})
.catch((error) => {
console.error(error);
toastNotification("Error fetching org config", { type: "error" });
});
}
}
7 changes: 6 additions & 1 deletion static/scripts/rendering/write-add-remove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,12 @@ async function notificationConfigPush(renderer: ManifestRenderer) {
}

try {
await renderer.configParser.updateConfig(org, octokit);
const selectedRepo = localStorage.getItem("selectedRepo");
if (!selectedRepo) {
throw new Error("No selected repo found");
}

await renderer.configParser.updateConfig(org, octokit, selectedRepo);
} catch (error) {
console.error("Error pushing config to GitHub:", error);
toastNotification("An error occurred while pushing the configuration to GitHub.", {
Expand Down
1 change: 1 addition & 0 deletions static/utils/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export const STRINGS = {
SELECT_SELECTED: ".select-selected",
SELECT_HIDE: "select-hide",
SELECT_ARROW_ACTIVE: "select-arrow-active",
PICKER_SELECT: "picker-select",
};
12 changes: 11 additions & 1 deletion static/utils/toaster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export function toastNotification(
action?: () => void;
shouldAutoDismiss?: boolean;
duration?: number;
killAll?: boolean;
} = {}
): () => void {
const { type = "info", actionText, action, shouldAutoDismiss = false, duration = 5000 } = options;
Expand Down Expand Up @@ -38,7 +39,16 @@ export function toastNotification(
class: "toast-action",
textContent: actionText,
});
actionButton.addEventListener("click", action);

actionButton.addEventListener("click", async () => {
action();
setTimeout(() => {
document.querySelectorAll(".toast").forEach((toast) => {
toast.classList.remove("show");
setTimeout(() => toast.remove(), 250);
});
}, 5000);
});
toastElement.appendChild(actionButton);
}

Expand Down

0 comments on commit ae1a823

Please sign in to comment.