Fix #1429 - Use basic-devtools module (#1430)

This MR brings in `$`, `$$`, and `$x` browsers devtools' utilities to our code so we can use these whenever we find it convenient.
This commit is contained in:
Andrea Giammarchi
2023-05-02 15:12:28 +02:00
committed by GitHub
parent 3a66be585f
commit 82613d016a
11 changed files with 47 additions and 27 deletions

View File

@@ -7,6 +7,9 @@
"": {
"name": "pyscript",
"version": "0.0.1",
"dependencies": {
"basic-devtools": "^0.1.6"
},
"devDependencies": {
"@codemirror/commands": "^6.2.2",
"@codemirror/lang-python": "^6.1.2",
@@ -2346,6 +2349,11 @@
"integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==",
"dev": true
},
"node_modules/basic-devtools": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/basic-devtools/-/basic-devtools-0.1.6.tgz",
"integrity": "sha512-g9zJ63GmdUesS3/Fwv0B5SYX6nR56TQXmGr+wE5PRTNCnGQMYWhUx/nZB/mMWnQJVLPPAp89oxDNlasdtNkW5Q=="
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -7743,6 +7751,11 @@
"integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==",
"dev": true
},
"basic-devtools": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/basic-devtools/-/basic-devtools-0.1.6.tgz",
"integrity": "sha512-g9zJ63GmdUesS3/Fwv0B5SYX6nR56TQXmGr+wE5PRTNCnGQMYWhUx/nZB/mMWnQJVLPPAp89oxDNlasdtNkW5Q=="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",

View File

@@ -38,5 +38,8 @@
"synclink": "0.2.4",
"ts-jest": "29.0.3",
"typescript": "5.0.4"
},
"dependencies": {
"basic-devtools": "^0.1.6"
}
}

View File

@@ -1,3 +1,5 @@
import { $, $$ } from 'basic-devtools';
import { basicSetup, EditorView } from 'codemirror';
import { python } from '@codemirror/lang-python';
import { indentUnit } from '@codemirror/language';
@@ -78,7 +80,7 @@ export function make_PyRepl(interpreter: InterpreterClient, app: PyScriptApp) {
if (!response.ok) {
return;
}
const cmcontentElement = this.querySelector("div[class='cm-content']");
const cmcontentElement = $('div[class="cm-content"]', this);
const { lastElementChild } = cmcontentElement;
cmcontentElement.replaceChildren(lastElementChild);
lastElementChild.textContent = await response.text();
@@ -191,7 +193,7 @@ export function make_PyRepl(interpreter: InterpreterClient, app: PyScriptApp) {
// should be the default.
autogenerateMaybe(): void {
if (this.hasAttribute('auto-generate')) {
const allPyRepls = document.querySelectorAll(`py-repl[root='${this.getAttribute('root')}'][exec-id]`);
const allPyRepls = $$(`py-repl[root='${this.getAttribute('root')}'][exec-id]`, document);
const lastRepl = allPyRepls[allPyRepls.length - 1];
const lastExecId = lastRepl.getAttribute('exec-id');
const nextExecId = parseInt(lastExecId) + 1;

View File

@@ -1,3 +1,5 @@
import { $$, $x } from 'basic-devtools';
import { ltrim, htmlDecode, ensureUniqueId, createDeprecationWarning } from '../utils';
import { getLogger } from '../logger';
import { pyExec, displayPyException } from '../pyexec';
@@ -113,7 +115,7 @@ export function make_PyScript(interpreter: InterpreterClient, app: PyScriptApp)
if ((node as PyScriptElement).matches(pyScriptCSS)) {
bootstrap(node as PyScriptElement);
}
for (const child of (node as PyScriptElement).querySelectorAll(pyScriptCSS)) {
for (const child of $$(pyScriptCSS, node as PyScriptElement)) {
bootstrap(child as PyScriptElement);
}
}
@@ -139,7 +141,7 @@ export function make_PyScript(interpreter: InterpreterClient, app: PyScriptApp)
});
// bootstrap all already live py <script> tags
callback([{ addedNodes: document.querySelectorAll(pyScriptCSS) } as unknown] as MutationRecord[], null);
callback([{ addedNodes: $$(pyScriptCSS, document) } as unknown] as MutationRecord[], null);
// once all tags have been initialized, observe new possible tags added later on
// this is to save a few ticks within the callback as each <script> already adds a companion node
@@ -149,20 +151,6 @@ export function make_PyScript(interpreter: InterpreterClient, app: PyScriptApp)
return PyScript;
}
// Differently from CSS selectors, XPath can crawl attributes by name and select
// directly attribute nodes. This allows us to look for literally any `py-*` attribute.
// TODO: could we just depend on basic-devtools module?
// @see https://github.com/WebReflection/basic-devtools
const $x = (path: string, root: Document | HTMLElement = document): (Node | Attr)[] => {
const expression = new XPathEvaluator().createExpression(path);
const xpath = expression.evaluate(root, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
const result = [];
for (let i = 0, { snapshotLength } = xpath; i < snapshotLength; i++) {
result.push(xpath.snapshotItem(i));
}
return result;
};
/** A weak relation between an element and current interpreter */
const elementInterpreter: WeakMap<Element, InterpreterClient> = new WeakMap();
@@ -199,7 +187,7 @@ function createElementsWithEventListeners(interpreter: InterpreterClient, el: El
/** Mount all elements with attribute py-mount into the Python namespace */
export async function mountElements(interpreter: InterpreterClient) {
const matches: NodeListOf<HTMLElement> = document.querySelectorAll('[py-mount]');
const matches = $$('[py-mount]', document);
logger.info(`py-mount: found ${matches.length} elements`);
if (matches.length > 0) {

View File

@@ -1,3 +1,5 @@
import { $$ } from 'basic-devtools';
import './styles/pyscript_base.css';
import { loadConfigFromElement } from './pyconfig';
@@ -150,7 +152,7 @@ export class PyScriptApp {
// XXX: we should actively complain if there are multiple <py-config>
// and show a big error. PRs welcome :)
logger.info('searching for <py-config>');
const elements = document.getElementsByTagName('py-config');
const elements = $$('py-config', document);
let el: Element | null = null;
if (elements.length > 0) el = elements[0];
if (elements.length >= 2) {

View File

@@ -1,3 +1,5 @@
import { $$ } from 'basic-devtools';
import { showWarning } from '../utils';
import { Plugin } from '../plugin';
import { getLogger } from '../logger';
@@ -21,7 +23,7 @@ export class ImportmapPlugin extends Plugin {
// await the module to be fully registered before executing the code
// inside py-script. It's also unclear whether we want to wait or not
// (or maybe only wait only if we do an actual 'import'?)
for (const node of document.querySelectorAll("script[type='importmap']")) {
for (const node of $$("script[type='importmap']", document)) {
const importmap: ImportMapType = (() => {
try {
return JSON.parse(node.textContent) as ImportMapType;

View File

@@ -1,3 +1,5 @@
import { $ } from 'basic-devtools';
import type { PyScriptApp } from '../main';
import type { AppConfig } from '../pyconfig';
import { Plugin, validateConfigParameterFromArray } from '../plugin';
@@ -42,7 +44,7 @@ export class PyTerminalPlugin extends Plugin {
const { terminal: t, docked: d } = config;
const auto = t === true || t === 'auto';
const docked = d === true || d === 'docked';
if (auto && document.querySelector('py-terminal') === null) {
if (auto && $('py-terminal', document) === null) {
logger.info('No <py-terminal> found, adding one');
const termElem = document.createElement('py-terminal');
if (auto) termElem.setAttribute('auto', '');

View File

@@ -1,3 +1,5 @@
import { $ } from 'basic-devtools';
import type { AppConfig } from '../pyconfig';
import type { UserError } from '../exceptions';
import { showWarning } from '../utils';
@@ -92,8 +94,8 @@ export class PySplashscreen extends HTMLElement {
</div>
</div>`;
this.mount_name = this.id.split('-').join('_');
this.operation = document.getElementById('pyscript-operation');
this.details = document.getElementById('pyscript-operation-details');
this.operation = $('#pyscript-operation', document) as HTMLElement;
this.details = $('#pyscript-operation-details', document) as HTMLElement;
}
log(msg: string) {

View File

@@ -1,3 +1,5 @@
import { $ } from 'basic-devtools';
import { Plugin } from '../plugin';
import { TargetedStdio, StdioMultiplexer } from '../stdio';
import type { InterpreterClient } from '../interpreter_client';
@@ -107,7 +109,7 @@ export class StdioDirector extends Plugin {
if (outputId) {
// 'output' attribute also used as location to send
// result of REPL
if (document.getElementById(outputId)) {
if ($('#' + outputId, document)) {
await pyDisplay(options.interpreter, options.result, { target: outputId });
} else {
//no matching element on page

View File

@@ -1,3 +1,5 @@
import { $ } from 'basic-devtools';
import { createSingularWarning, escape } from './utils';
export interface Stdio {
@@ -67,7 +69,7 @@ export class TargetedStdio implements Stdio {
*/
writeline_by_attribute(msg: string) {
const target_id = this.source_element.getAttribute(this.source_attribute);
const target = document.getElementById(target_id);
const target = $('#' + target_id, document);
if (target === null) {
// No matching ID
createSingularWarning(

View File

@@ -1,3 +1,5 @@
import { $$ } from 'basic-devtools';
import { _createAlertBanner } from './exceptions';
export function addClasses(element: HTMLElement, classes: string[]) {
@@ -101,7 +103,7 @@ export function createDeprecationWarning(msg: string, elementName: string): void
* If null, the full text of 'msg' is used instead.
*/
export function createSingularWarning(msg: string, sentinelText: string | null = null): void {
const banners = document.getElementsByClassName('alert-banner py-warning');
const banners = $$('.alert-banner, .py-warning', document);
let bannerCount = 0;
for (const banner of banners) {
if (banner.innerHTML.includes(sentinelText ? sentinelText : msg)) {