Skip to content

Commit

Permalink
refactor: add perceptor tab and layout & relevant feature injection (#…
Browse files Browse the repository at this point in the history
…589)

* feat: add perceptor tab and layout

* fix failed to select perceptor tab

* move update tab highlight from additional listener to init

* after discussion at the moment we don't introduce the addtionalListeners mechanism

remove additionalListeners

* extract the svg path into a seperate file to make the code look more clean

* remove unnecessary code

* I think this line is redundant

* better code comment

* chore

* the React.FC type should be no longer used

---------

Co-authored-by: Lam Tang <tangyenan@gmail.com>
  • Loading branch information
wxharry and tyn1998 authored Mar 8, 2023
1 parent 85fa29b commit 99563fb
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 3 deletions.
4 changes: 2 additions & 2 deletions src/feature-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,11 @@ const add = async (
*/
await sleep(10); // 10ms seems enough
}
// if a feature doesn't exisit in DOM, try loading it since it might be expected in current page
// if a feature doesn't exist in DOM, try loading it since it might be expected in current page
if (!exists(`#${id}`)) {
setupPageLoad(id, details);
} else {
// if already exisits, either it's not removed from DOM after a turbo:visit or the
// if already exists, either it's not removed from DOM after a turbo:visit or the
// current visit is a restoration visit. For the second case, we should handle.
if (restore && isRestorationVisit()) {
restore();
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/should-feature-run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default async function shouldFeatureRun(
} = props;
return (
(await Promise.all(asLongAs.map((c) => c())).then((flags) =>
flags.some((flag) => flag === true)
flags.every((flag) => flag === true)
)) &&
(await Promise.all(include.map((c) => c())).then((flags) =>
flags.some((flag) => flag === true)
Expand Down
39 changes: 39 additions & 0 deletions src/pages/ContentScripts/features/perceptor-layout/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';
import $ from 'jquery';
import elementReady from 'element-ready';
import { render, Container } from 'react-dom';

import features from '../../../../feature-manager';
import { isPerceptor } from '../../../../utils/utils';
import View from './view';

const featureId = features.getFeatureID(import.meta.url);

const renderTo = (container: Container) => {
render(<View />, container);
};

const init = async (): Promise<void> => {
// remove the original container
const parentContainer = await elementReady('#repo-content-turbo-frame');
$('h1.sr-only', parentContainer).text('Perceptor');

const perceptorLayoutContainer = $(
'div.clearfix.container-xl:first',
parentContainer
);
perceptorLayoutContainer.children('div').remove();

// create the new one: the percepter container
const percepterContainer = document.createElement('div');
percepterContainer.setAttribute('id', 'perceptor-layout');

renderTo(percepterContainer);
perceptorLayoutContainer.append(percepterContainer);
};

features.add(featureId, {
asLongAs: [isPerceptor],
awaitDomReady: false,
init,
});
15 changes: 15 additions & 0 deletions src/pages/ContentScripts/features/perceptor-layout/view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';

const View = (): JSX.Element => {
return (
<div
data-view-component="true"
className="Layout Layout--flowRow-until-md Layout--sidebarPosition-start Layout--sidebarPosition-flowRow-start Layout--sidebar-narrow"
>
<div data-view-component="true" className="Layout-sidebar"></div>
<div data-view-component="true" className="Layout-main"></div>
</div>
);
};

export default View;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default '<path xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" d="M 14.253906 0 C 13.292969 0 12.507812 0.78125 12.507812 1.746094 C 12.507812 2.046875 12.585938 2.328125 12.71875 2.578125 L 9.859375 5.4375 C 9.464844 5.128906 8.972656 4.945312 8.4375 4.945312 C 7.898438 4.945312 7.40625 5.128906 7.011719 5.4375 L 4.78125 3.207031 C 4.882812 3.03125 4.945312 2.832031 4.945312 2.617188 C 4.945312 1.976562 4.421875 1.453125 3.78125 1.453125 C 3.140625 1.453125 2.617188 1.976562 2.617188 2.617188 C 2.617188 3.261719 3.140625 3.78125 3.78125 3.78125 C 3.996094 3.78125 4.195312 3.71875 4.371094 3.617188 L 6.601562 5.847656 C 6.292969 6.242188 6.109375 6.734375 6.109375 7.273438 C 6.109375 7.808594 6.292969 8.304688 6.601562 8.699219 L 2.917969 12.382812 C 2.605469 12.101562 2.195312 11.925781 1.746094 11.925781 C 0.78125 11.925781 0 12.710938 0 13.671875 C 0 14.636719 0.78125 15.417969 1.746094 15.417969 C 2.707031 15.417969 3.492188 14.636719 3.492188 13.671875 C 3.492188 13.371094 3.414062 13.089844 3.28125 12.839844 L 7.011719 9.109375 C 7.332031 9.359375 7.722656 9.527344 8.144531 9.578125 L 8.144531 12.535156 C 7.320312 12.675781 6.691406 13.390625 6.691406 14.253906 C 6.691406 15.21875 7.472656 16 8.4375 16 C 9.398438 16 10.183594 15.21875 10.183594 14.253906 C 10.183594 13.390625 9.550781 12.675781 8.726562 12.535156 L 8.726562 9.578125 C 9.152344 9.527344 9.539062 9.359375 9.863281 9.109375 L 12.09375 11.339844 C 11.988281 11.511719 11.925781 11.710938 11.925781 11.925781 C 11.925781 12.570312 12.449219 13.089844 13.089844 13.089844 C 13.734375 13.089844 14.253906 12.570312 14.253906 11.925781 C 14.253906 11.285156 13.734375 10.761719 13.089844 10.761719 C 12.875 10.761719 12.675781 10.828125 12.503906 10.929688 L 10.273438 8.699219 C 10.578125 8.304688 10.761719 7.808594 10.761719 7.273438 C 10.761719 6.734375 10.578125 6.242188 10.273438 5.847656 L 13.085938 3.035156 C 13.394531 3.316406 13.804688 3.492188 14.253906 3.492188 C 15.21875 3.492188 16 2.707031 16 1.746094 C 16 0.78125 15.21875 0 14.253906 0 Z M 3.199219 2.617188 C 3.199219 2.296875 3.460938 2.035156 3.78125 2.035156 C 4.101562 2.035156 4.363281 2.296875 4.363281 2.617188 C 4.363281 2.9375 4.101562 3.199219 3.78125 3.199219 C 3.460938 3.199219 3.199219 2.9375 3.199219 2.617188 Z M 1.746094 14.835938 C 1.105469 14.835938 0.582031 14.316406 0.582031 13.671875 C 0.582031 13.03125 1.105469 12.507812 1.746094 12.507812 C 2.386719 12.507812 2.910156 13.03125 2.910156 13.671875 C 2.910156 14.316406 2.386719 14.835938 1.746094 14.835938 Z M 9.601562 14.253906 C 9.601562 14.894531 9.078125 15.417969 8.4375 15.417969 C 7.792969 15.417969 7.273438 14.894531 7.273438 14.253906 C 7.273438 13.613281 7.792969 13.089844 8.4375 13.089844 C 9.078125 13.089844 9.601562 13.613281 9.601562 14.253906 Z M 8.4375 9.019531 C 7.472656 9.019531 6.691406 8.234375 6.691406 7.273438 C 6.691406 6.308594 7.472656 5.527344 8.4375 5.527344 C 9.398438 5.527344 10.183594 6.308594 10.183594 7.273438 C 10.183594 8.234375 9.398438 9.019531 8.4375 9.019531 Z M 13.671875 11.925781 C 13.671875 12.25 13.410156 12.507812 13.089844 12.507812 C 12.769531 12.507812 12.507812 12.25 12.507812 11.925781 C 12.507812 11.605469 12.769531 11.34375 13.089844 11.34375 C 13.410156 11.34375 13.671875 11.605469 13.671875 11.925781 Z M 14.253906 2.910156 C 13.613281 2.910156 13.089844 2.386719 13.089844 1.746094 C 13.089844 1.105469 13.613281 0.582031 14.253906 0.582031 C 14.894531 0.582031 15.417969 1.105469 15.417969 1.746094 C 15.417969 2.386719 14.894531 2.910156 14.253906 2.910156 Z M 14.253906 2.910156 "/>';
97 changes: 97 additions & 0 deletions src/pages/ContentScripts/features/perceptor-tab/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import $ from 'jquery';
import elementReady from 'element-ready';

import iconSvgPath from './icon-svg-path';
import features from '../../../../feature-manager';
import { isPerceptor, isPublicRepo } from '../../../../utils/utils';
import sleep from '../../../../helpers/sleep';

const featureId = features.getFeatureID(import.meta.url);

const addPerceptorTab = async (): Promise<void | false> => {
// the creation of the Perceptor tab is based on the Insights tab
const insightsTab = await elementReady(
'a.UnderlineNav-item[id="insights-tab"]',
{ waitForChildren: false }
);
if (!insightsTab) {
// if the selector failed to find the Insights tab
return false;
}
const perceptorTab = insightsTab.cloneNode(true) as HTMLAnchorElement;
delete perceptorTab.dataset.selectedLinks;
const perceptorHref = `${insightsTab.href}?redirect=perceptor`;
perceptorTab.href = perceptorHref;
perceptorTab.id = featureId;
perceptorTab.setAttribute('data-tab-item', featureId);

const perceptorTitle = $('[data-content]', perceptorTab);
perceptorTitle.text('Perceptor').attr('data-content', 'Perceptor');

// slot for any future counter function
const perceptorCounter = $('[class=Counter]', perceptorTab);
perceptorCounter.attr('id', `${featureId}-count`);

// replace with the perceptor Icon
$('svg.octicon', perceptorTab).html(iconSvgPath);

// add the Perceptor tab to the tabs list
if (!insightsTab.parentElement) {
return false;
}
const tabContainer = document.createElement('li');
tabContainer.appendChild(perceptorTab);
tabContainer.setAttribute('data-view-component', 'true');
tabContainer.className = 'd-inline-flex';
insightsTab.parentElement.after(tabContainer);

// add to drop down menu (when the window is narrow enough some tabs are hidden into "···" menu)
const repoNavigationDropdown = await elementReady('.UnderlineNav-actions ul');
if (!repoNavigationDropdown) {
return false;
}
const insightsTabDataItem = $(
'li[data-menu-item$="insights-tab"]',
repoNavigationDropdown
);
const perceptorTabDataItem = insightsTabDataItem.clone(true);
perceptorTabDataItem.attr('data-menu-item', featureId);
perceptorTabDataItem.children('a').text('Perceptor').attr({
'data-selected-links': perceptorHref,
href: perceptorHref,
});
insightsTabDataItem.after(perceptorTabDataItem);

// Trigger a reflow to push the right-most tab into the overflow dropdown
window.dispatchEvent(new Event('resize'));
};

const updatePerceptorTabHighlighting = async (): Promise<void> => {
const insightsTab = $('#insights-tab');
const perceptorTab = $(`#${featureId}`);
// no operation needed
if (!isPerceptor()) return;
// if perceptor tab
const insightsTabSeletedLinks = insightsTab.attr('data-selected-links');
insightsTab.removeAttr('data-selected-links');
perceptorTab.attr('data-selected-links', 'pulse');
// should wait a short time for the host code to update the tab highlighting first
await sleep(10);
if (!insightsTabSeletedLinks) return;
insightsTab.attr('data-selected-links', insightsTabSeletedLinks);
perceptorTab.removeAttr('data-selected-links');
};

const init = async (): Promise<void> => {
await addPerceptorTab();
// add event listener to update tab highlighting at each turbo:load event
document.addEventListener('turbo:load', async () => {
await updatePerceptorTabHighlighting();
});
};

features.add(featureId, {
asLongAs: [isPublicRepo],
awaitDomReady: false,
init,
});
2 changes: 2 additions & 0 deletions src/pages/ContentScripts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import './features/repo-activity-openrank-trends';
import './features/developer-activity-openrank-trends';
import './features/repo-fork-tooltip';
import './features/repo-star-tooltip';
import './features/perceptor-tab';
import './features/perceptor-layout';

/**
* I infer that GitHub uses hotwired/turbo to speedup its SPA.
Expand Down

0 comments on commit 99563fb

Please sign in to comment.