diff --git a/.gitignore b/.gitignore index b4c3555..9b497c7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,11 +6,8 @@ yarn-debug.log* yarn-error.log* # Webpack -dist - # Babel lib -dist # Runtime data pids diff --git a/README.md b/README.md index 6f56c83..ebfba85 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,14 @@ # meeting-pane Bring together schedule, people, materials, discussion around a meeting. + +## Genertive AI usage +The SolidOS team is using GitHub Copilot integrated in Visual Studio Code. +We have added comments in the code to make it explicit which parts are 100% written by AI. +Example: +* Some code was generated by the GPT-5.3-Codex model in GitHub Copilot based on the following prompt: +* I have these classes meetingToolIcon, meetingToolIcon-visible, meetingToolIconAddButton, meetingToolIcon-disabled, meetingToolIcon-yellow. I want to toggle the visibility of the icons by adding or removing the class meetingToolIcon-visible. Write a function that takes a boolean showIcons and toggles the class on all the icons in iconArray accordingly. +* the icons are way too big and they are lined up vertically +instead of horizontally. Can you fix that? +* also icon sizing - the icons should be about an inch big + diff --git a/declarations.d.ts b/declarations.d.ts index 2c131cd..e344443 100644 --- a/declarations.d.ts +++ b/declarations.d.ts @@ -1,4 +1,17 @@ declare module '*.ttl' { const content: string; export default content; -} \ No newline at end of file +} + +export interface PaneDefinition { + icon: string + name: string + audience: unknown[] + label: (subject: unknown, context: unknown) => string | null + render: (subject: unknown, context: { dom: Document }) => HTMLElement + mintClass?: unknown + mintNew?: (context: unknown, options: unknown) => Promise +} + +declare const meetingPane: PaneDefinition +export default meetingPane \ No newline at end of file diff --git a/dev/dev-global.css b/dev/dev-global.css new file mode 100644 index 0000000..b7ad4fd --- /dev/null +++ b/dev/dev-global.css @@ -0,0 +1,388 @@ +/* ---ONLY FOR LOCAL DEV--- */ +/* ---final version is in mashlib--- */ + +/* Global CSS: base styles, variables, and resets */ +/* Accessible color palette */ + +:root { + /* Primary/Accent colors (profile-pane specific) */ + --color-primary: #7C4DFF; /* Vivid Purple */ + --color-secondary: #0077B6; /* Accessible Blue */ + --color-accent: #FFD600; /* Bright Yellow */ + --color-error: #B00020; /* Accessible Red */ + --color-success: #00C853; /* Accessible Green */ + + /* Card/Section backgrounds */ + --color-background: #FFFFFF; /* White */ + --color-card-bg: #FFFFFF; /* White for inner cards */ + --color-section-bg: #F5F5F5; /* Light grey for outer sections */ + + --color-text: #1A1A1A; /* Near-black */ + --color-text-secondary: #666; /* Added for repeated usage */ + --color-text-muted: #444; /* Added for repeated usage */ + --color-border-pale: #eee; /* Added for repeated borders */ + --border-radius-full: 1em; /* Matches module usage */ + --border-radius-base: 0.5em; + --box-shadow: 0 2px 8px rgba(124,77,255,0.08); /* Matches module usage */ + --box-shadow-sm: 0 1px 4px rgba(124,77,255,0.12); + --spacing-xs: 0.5em; + --spacing-sm: 0.75em; + --spacing-md: 1em; + --spacing-lg: 1.5em; + --spacing-xl: 2em; + --font-family-base: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + --font-size-base: 1em; /* 16px default */ + --font-size-sm: 0.875em; /* 14px */ + --font-size-lg: 1.125em; /* 18px */ + --font-size-xl: 1.25em; /* 20px */ + --line-height-base: 1.5; /* WCAG recommended */ + --line-height-tight: 1.4; + --line-height-loose: 1.6; + --letter-spacing-wide: 0.025em; + + /* Minimum font sizes for accessibility */ + --min-font-size: 14px; + --min-line-height: 1.4; + + /* Accessibility improvements */ + --min-touch-target: 44px; /* WCAG minimum touch target */ + --focus-ring-width: 2px; + --animation-duration: 0.2s; /* Reduced motion friendly */ + + /* Additional accessibility variables */ + --focus-indicator-width: 3px; + --animation-duration-slow: 0.3s; + --high-contrast-ratio: 7:1; /* WCAG AAA standard */ +} + + +/* Improve text rendering */ +html, body { + margin: 0; + padding: 0; + font-family: var(--font-family-base); + font-size: var(--font-size-base); + line-height: var(--line-height-base); + background: var(--color-background); + color: var(--color-text); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; +} + +/* Improved heading hierarchy */ +h1, h2, h3, h4, h5, h6 { + color: var(--color-primary); + font-weight: 600; + line-height: var(--line-height-tight); + margin-top: 0; + margin-bottom: var(--spacing-sm); +} + +h1 { font-size: 2em; } /* 32px */ +h2 { font-size: 1.5em; } /* 24px */ +h3 { font-size: 1.25em; } /* 20px */ +h4 { font-size: 1.125em; }/* 18px */ +h5, h6 { font-size: 1em; }/* 16px */ + +/* Better paragraph spacing */ +p { + margin-bottom: var(--spacing-md); + line-height: var(--line-height-base); + max-width: 65ch; /* Optimal reading width */ +} + +/* Improved link accessibility */ +a { + color: var(--color-primary); + text-decoration: underline; + text-underline-offset: 0.125em; + text-decoration-thickness: 0.0625em; +} + +a:hover, a:focus { + text-decoration-thickness: 0.125em; +} + +/* Ensure minimum font sizes are respected */ +@media screen and (max-width: 768px) { + html { + font-size: max(16px, 1rem); /* Never smaller than 16px on mobile */ + } +} + +/* Support for larger text preferences */ +@media (prefers-reduced-motion: no-preference) { + html { + scroll-behavior: smooth; + } +} + +/* Accessibility: focus styles */ +:focus { + outline: 2px solid var(--color-primary); + outline-offset: 1px; + box-shadow: 0 0 0 1px var(--color-background); +} + +/* Accessibility: Respect user motion preferences */ +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} + +/* Accessibility: High contrast mode support */ +@media (prefers-contrast: high) { + :root { + --color-border-pale: #000; + --box-shadow: 0 2px 4px rgba(0,0,0,0.5); + --box-shadow-sm: 0 1px 2px rgba(0,0,0,0.3); + } +} + +/* Accessibility: Improved focus management */ +:focus-visible { + outline: var(--focus-ring-width) solid var(--color-primary); + outline-offset: 2px; + box-shadow: 0 0 0 1px var(--color-background); +} + +:focus:not(:focus-visible) { + outline: none; +} + +/* Skip link for screen readers */ +.skip-link { + position: absolute; + top: -40px; + left: 6px; + background: var(--color-primary); + color: white; + padding: var(--spacing-sm) var(--spacing-md); + text-decoration: none; + border-radius: var(--border-radius-base); + z-index: 1000; + font-weight: 600; + font-size: var(--font-size-base); + line-height: 1; +} + +.skip-link:focus { + top: 6px; + outline: 2px solid white; + outline-offset: 2px; +} + +/* Semantic HTML5 improvements */ +article, aside, section { + display: block; +} + +header { + margin-bottom: var(--spacing-md); +} + +nav { + display: block; +} + +nav ul { + list-style: none; + padding: 0; + margin: 0; +} + +/* Enhanced keyboard navigation */ +*:focus-visible { + outline: var(--focus-indicator-width) solid var(--color-primary); + outline-offset: 2px; + box-shadow: 0 0 0 1px var(--color-background), 0 0 0 4px rgba(124, 77, 255, 0.2); + border-radius: 2px; + transition: none; /* Remove transitions on focus for immediate feedback */ +} + +/* Improve focus management for interactive elements */ +[role="button"]:focus, +[role="link"]:focus, +button:focus, +a:focus { + outline: 2px solid var(--color-primary); + outline-offset: 2px; + box-shadow: 0 0 0 1px var(--color-background); +} + +/* Enhanced error message accessibility */ +[role="alert"] { + padding: var(--spacing-md); + border: 2px solid var(--color-error); + border-radius: var(--border-radius-base); + background-color: rgba(176, 0, 32, 0.1); + margin: var(--spacing-md) 0; +} + +/* Success message styling */ +[role="status"] { + padding: var(--spacing-md); + border: 2px solid var(--color-success); + border-radius: var(--border-radius-base); + background-color: rgba(0, 200, 83, 0.1); + margin: var(--spacing-md) 0; +} + +/* Enhanced table accessibility */ +table { + border-collapse: collapse; + width: 100%; +} + +th { + background-color: var(--color-section-bg); + font-weight: 600; + text-align: left; + padding: var(--spacing-sm); +} + +td { + padding: var(--spacing-sm); +} + +/* Focus trap for modals */ +.focus-trap { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 9999; + background: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; +} + +/* Enhanced button accessibility */ +button, [role="button"] { + cursor: pointer; + border: none; + border-radius: var(--border-radius-base); + padding: var(--spacing-sm) var(--spacing-md); + min-height: var(--min-touch-target); + min-width: var(--min-touch-target); + font-size: var(--font-size-base); + font-weight: 600; + transition: all var(--animation-duration) ease; + position: relative; + border: 1px solid grey; +} + +button:disabled, [role="button"][aria-disabled="true"] { + opacity: 0.6; + cursor: not-allowed; + pointer-events: none; +} + +/* Loading indicator accessibility */ +.loading-spinner { + width: 40px; + height: 40px; + border: 3px solid var(--color-border-pale); + border-top: 3px solid var(--color-primary); + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +@media (prefers-reduced-motion: reduce) { + .loading-spinner { + animation: none; + border-top-color: var(--color-primary); + } +} + +/* Utility classes */ +.u-flex { + display: flex; +} +.u-grid { + display: grid; +} +.u-center { + justify-content: center; + align-items: center; +} +.u-gap { + gap: 1em; +} + +/* Common card component - used across all modules */ +.module-card { + background: var(--color-card-bg); + border-radius: var(--border-radius-full); + box-shadow: var(--box-shadow); + padding: var(--spacing-lg); + margin-bottom: var(--spacing-lg); + width: 100%; + max-width: 100%; + box-sizing: border-box; +} + +/* Common header styles */ +.module-header { + text-align: center; + margin-bottom: var(--spacing-md); +} + +/* Common flex patterns */ +.flex-center { + display: flex; + justify-content: center; + align-items: center; +} + +.flex-column { + display: flex; + flex-direction: column; +} + +.flex-column-center { + display: flex; + flex-direction: column; + align-items: center; +} + +/* Text utilities */ +.text-center { + text-align: center; +} + +.text-secondary { + color: var(--color-text-secondary); +} + +.text-muted { + color: var(--color-text-muted); +} + +/* Override solid-ui error message close button styling */ +.errorMessageBlock .close, +.errorMessageBlock button[type="button"], +.errorMessageBlock .button { + background: var(--color-border-pale) !important; + color: var(--color-text) !important; + border: 1px solid var(--color-border-pale) !important; +} + +.errorMessageBlock .close:hover, +.errorMessageBlock button[type="button"]:hover, +.errorMessageBlock .button:hover { + background: var(--color-text-secondary) !important; + color: var(--color-background) !important; +} diff --git a/dev/index.js b/dev/index.js index eaa7d0f..45af2d0 100644 --- a/dev/index.js +++ b/dev/index.js @@ -2,6 +2,7 @@ import * as logic from 'solid-logic' import MeetingPane from '../src/meetingPane' import * as $rdf from 'rdflib' import * as UI from 'solid-ui' +import './dev-global.css' async function appendMeetingPane (dom, uri) { const subject = $rdf.sym(uri) diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..67507ff --- /dev/null +++ b/index.d.ts @@ -0,0 +1,2 @@ +export { default } from './declarations' +export type { PaneDefinition } from './declarations' diff --git a/package-lock.json b/package-lock.json index cf3e096..db01b1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,8 @@ "babel-loader": "^10.0.0", "babel-plugin-inline-import": "^3.0.0", "buffer": "^6.0.3", + "copy-webpack-plugin": "^14.0.0", + "css-loader": "^7.1.2", "eslint": "^9.39.2", "globals": "^17.1.0", "html-webpack-plugin": "^5.6.6", @@ -21,6 +23,8 @@ "rdflib": "^2.3.6", "solid-logic": "^4.0.6", "solid-ui": "^3.0.5", + "style-loader": "^4.0.0", + "terser-webpack-plugin": "^5.3.6", "typescript": "^6.0.2", "webpack": "^5.104.1", "webpack-cli": "^7.0.1", @@ -1401,9 +1405,9 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.2.tgz", - "integrity": "sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.0.tgz", + "integrity": "sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==", "dev": true, "license": "MIT", "dependencies": { @@ -4268,6 +4272,40 @@ "dev": true, "license": "MIT" }, + "node_modules/copy-webpack-plugin": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-14.0.0.tgz", + "integrity": "sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-parent": "^6.0.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^7.0.3", + "tinyglobby": "^0.2.12" + }, + "engines": { + "node": ">= 20.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/serialize-javascript": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.4.tgz", + "integrity": "sha512-DuGdB+Po43Q5Jxwpzt1lhyFSYKryqoNjQSA9M92tyw0lyHIOur+XCalOUe0KTJpyqzT8+fQ5A0Jf7vCx/NKmIg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/core-js": { "version": "3.47.0", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz", @@ -4414,6 +4452,55 @@ "dev": true, "license": "MIT" }, + "node_modules/css-loader": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.4.tgz", + "integrity": "sha512-vv3J9tlOl04WjiMvHQI/9tmIrCxVrj6PFbHemBB1iihpeRbi/I4h033eoFIhwxBBqLhI0KYFS7yvynBFhIZfTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.40", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.6.3" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-loader/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/css-select": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", @@ -4444,6 +4531,19 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -5715,9 +5815,9 @@ } }, "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, "license": "ISC" }, @@ -6396,6 +6496,19 @@ "node": ">=0.10.0" } }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -7721,6 +7834,25 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -8772,6 +8904,119 @@ "node": ">= 0.4" } }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -10073,6 +10318,16 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -10298,6 +10553,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", + "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.27.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", diff --git a/package.json b/package.json index a85b9a3..e383ff1 100644 --- a/package.json +++ b/package.json @@ -2,14 +2,18 @@ "name": "meeting-pane", "version": "3.0.2", "description": "Solid-compatible Panes: meeting collaborative tool", - "main": "dist/meetingPane.js", + "main": "lib/meeting-pane.js", + "types": "lib/index.d.ts", + "sideEffects": [ + "**/*.css" + ], "files": [ - "dist/", + "lib/", "README.md", "LICENSE" ], "scripts": { - "clean": "rm -rf dist", + "clean": "rm -rf lib", "build": "npm run clean && npm run build-dist", "build-dist": "webpack --progress", "lint": "eslint", @@ -53,6 +57,8 @@ "babel-loader": "^10.0.0", "babel-plugin-inline-import": "^3.0.0", "buffer": "^6.0.3", + "copy-webpack-plugin": "^14.0.0", + "css-loader": "^7.1.2", "eslint": "^9.39.2", "globals": "^17.1.0", "html-webpack-plugin": "^5.6.6", @@ -61,6 +67,8 @@ "rdflib": "^2.3.6", "solid-logic": "^4.0.6", "solid-ui": "^3.0.5", + "style-loader": "^4.0.0", + "terser-webpack-plugin": "^5.3.6", "typescript": "^6.0.2", "webpack": "^5.104.1", "webpack-cli": "^7.0.1", diff --git a/src/meetingPane.js b/src/meetingPane.js index 1bebc0b..1ad9181 100644 --- a/src/meetingPane.js +++ b/src/meetingPane.js @@ -6,6 +6,8 @@ import * as UI from 'solid-ui' import * as $rdf from 'rdflib' import { meetingDetailsFormText } from './meetingDetailsForm.ttl' import { solidLogicSingleton, authn } from 'solid-logic' +import './styles/meetingPane.css' +import './styles/utilities.css' const { typeIndex } = solidLogicSingleton const { loadTypeIndexesFor } = typeIndex @@ -91,7 +93,7 @@ export default { const complain = function complain (message, color) { console.log(message) const pre = dom.createElement('pre') - pre.setAttribute('style', 'background-color: ' + color || '#eed' + ';') + pre.setAttribute('style', 'background-color: ' + (color || '#eed') + ';') div.appendChild(pre) pre.appendChild(dom.createTextNode(message)) } @@ -104,18 +106,19 @@ export default { const meetingDoc = subject.doc() const meetingBase = subject.dir().uri const div = dom.createElement('div') + div.classList.add('meetingPane') const table = div.appendChild(dom.createElement('table')) - table.style = 'width: 100%; height: 100%; margin:0;' + table.classList.add('meetingPaneTable') const topTR = table.appendChild(dom.createElement('tr')) + topTR.classList.add('meetingPaneHeader') topTR.appendChild(dom.createElement('div')) // topDiv const mainTR = table.appendChild(dom.createElement('tr')) + mainTR.classList.add('meetingPaneMainRow') const toolBar0 = table.appendChild(dom.createElement('td')) const toolBar1 = toolBar0.appendChild(dom.createElement('table')) const toolBar = toolBar1.appendChild(dom.createElement('tr')) - topTR.setAttribute('style', 'height: 2em;') // spacer if notthing else - let me = null // @@ Put code to find out logged in person const saveBackMeetingDoc = function () { @@ -842,31 +845,45 @@ export default { 'text/turtle' ) // Load form directly - const iconStyle = 'padding: 1em; width: 3em; height: 3em;' const iconCell = toolBar.appendChild(dom.createElement('td')) + iconCell.classList.add('meetingToolBarIcons') + const iconRow = iconCell.appendChild(dom.createElement('div')) + iconRow.classList.add('meetingToolIconRow') const parameterCell = toolBar.appendChild(dom.createElement('td')) - const star = iconCell.appendChild(dom.createElement('img')) + const star = iconRow.appendChild(dom.createElement('img')) + star.classList.add( + 'meetingToolIcon', + 'meetingToolIcon-visible', + 'meetingToolIconAddButton' + ) let visible = false // the inividual tools tools star.setAttribute('src', UI.icons.iconBase + 'noun_19460_green.svg') // noun_272948.svg - star.setAttribute('style', iconStyle + 'opacity: 50%;') star.setAttribute('title', 'Add another tool to the meeting') + // This code was generated by Generative AI (GPT-5.3-Codex in GitHub Copilot) based on the following prompt: + // I have these classes meetingToolIcon, meetingToolIcon-visible, meetingToolIconAddButton, + // meetingToolIcon-disabled, meetingToolIcon-yellow. I want to toggle the visibility of + // the icons by adding or removing the class meetingToolIcon-visible. Write a function + // that takes a boolean showIcons and toggles the class on all the icons in iconArray accordingly. + const toggleIconsVisibility = function (showIcons) { + for (let i = 0; i < iconArray.length; i++) { + iconArray[i].classList.toggle('meetingToolIcon-visible', showIcons) + } + } + const selectNewTool = function (_event) { visible = !visible - star.setAttribute( - 'style', - iconStyle + (visible ? 'background-color: yellow;' : '') - ) - styleTheIcons(visible ? '' : 'display: none;') + star.classList.toggle('meetingToolIcon-highlighted', visible) + toggleIconsVisibility(visible) } + // end ai generated code let loginOutButton authn.checkUser().then(webId => { if (webId) { me = webId star.addEventListener('click', selectNewTool) - star.setAttribute('style', iconStyle) return } @@ -874,9 +891,7 @@ export default { if (webIdUri) { me = kb.sym(webIdUri) parameterCell.removeChild(loginOutButton) - // loginOutButton.setAttribute('',iconStyle) // make it match the icons star.addEventListener('click', selectNewTool) - star.setAttribute('style', iconStyle) } else { console.log('(Logged out)') me = null @@ -890,9 +905,12 @@ export default { for (let i = 0; i < toolIcons.length; i++) { const foo = function () { const toolObject = toolIcons[i] - const icon = iconCell.appendChild(dom.createElement('img')) + const icon = iconRow.appendChild(dom.createElement('img')) + icon.classList.add('meetingToolIcon') icon.setAttribute('src', UI.icons.iconBase + toolObject.icon) - icon.setAttribute('style', iconStyle + 'display: none;') + if (toolObject.disabled) { + icon.classList.add('meetingToolIcon-disabled') + } iconArray.push(icon) icon.tool = toolObject const maker = toolObject.maker @@ -906,23 +924,20 @@ export default { foo() } - const styleTheIcons = function (style) { - for (let i = 0; i < iconArray.length; i++) { - let st = iconStyle + style - if (toolIcons[i].disabled) { - st += 'opacity: 0.3;' - } - iconArray[i].setAttribute('style', st) // eg 'background-color: #ccc;' - } - } const resetTools = function () { - styleTheIcons('display: none;') - star.setAttribute('style', iconStyle) + visible = false + toggleIconsVisibility(false) + star.classList.remove('meetingToolIcon-highlighted') } const selectTool = function (icon) { - styleTheIcons('display: none;') // 'background-color: #ccc;' - icon.setAttribute('style', iconStyle + 'background-color: yellow;') + visible = false + toggleIconsVisibility(false) + for (let i = 0; i < iconArray.length; i++) { + iconArray[i].classList.remove('meetingToolIcon-highlighted') + } + icon.classList.add('meetingToolIcon-visible', 'meetingToolIcon-highlighted') + star.classList.remove('meetingToolIcon-highlighted') } // ////////////////////////////// @@ -933,8 +948,8 @@ export default { let label = kb.any(item, ns.rdfs('label')) label = label ? label.value : UI.utils.label(target) const s = div.appendChild(dom.createElement('div')) + s.classList.add('meetingToolLabel') s.textContent = label - s.setAttribute('style', 'margin-left: 0.7em') const icon = kb.any(item, ns.meeting('icon')) if (icon) { // Make sure the icon is cleanly on the left of the label @@ -942,11 +957,9 @@ export default { const tr = table.appendChild(dom.createElement('tr')) const left = tr.appendChild(dom.createElement('td')) const right = tr.appendChild(dom.createElement('td')) - // var img = div.appendChild(dom.createElement('img')) const img = left.appendChild(dom.createElement('img')) + img.classList.add('meetingToolIconImg') img.setAttribute('src', icon.uri) - // img.setAttribute('style', 'max-width: 1.5em; max-height: 1.5em;') // @@ SVG shrinks to 0 - img.setAttribute('style', 'width: 1.5em; height: 1.5em;') // @ img.setAttribute('title', label) right.appendChild(s) } else { @@ -960,7 +973,7 @@ export default { const tipDiv = function (text) { const d = dom.createElement('div') const p = d.appendChild(dom.createElement('p')) - p.setAttribute('style', 'margin: 0em; padding:3em; color: #888;') + p.classList.add('meetingToolTip') p.textContent = 'Tip: ' + text return d } @@ -1004,10 +1017,7 @@ export default { saveBackMeetingDoc() } ) - delButton.setAttribute('style', 'width: 1.5em; height: 1.5em;') - // delButton.setAttribute('class', '') - // delButton.setAttribute('style', 'height: 2em; width: 2em; margin: 1em; border-radius: 0.5em; padding: 1em; font-size: 120%; background-color: red; color: white;') - // delButton.textContent = 'Delete this tab' + delButton.classList.add('meetingPaneDeleteButton') } else { containerDiv.appendChild(dom.createElement('h4')).textContent = '(No adjustments available)' @@ -1031,17 +1041,8 @@ export default { // See https://stackoverflow.com/questions/325273/make-iframe-to-fit-100-of-containers-remaining-height // Set the container position (sic) so it becaomes a 100% reference for the size of the iframe height 100% /* For now at least , leave the container style as set by the tab system. 20200115b - containerDiv.setAttribute( - 'style', - 'position: relative; top: 0px; left:0px; right:0px; resize: both; overflow:scroll; min-width: 30em; min-height: 30em;' - ) - */ - // iframe.setAttribute('style', 'height: 350px; border: 0; margin: 0; padding: 0; resize:both; overflow:scroll; width: 100%;') - // iframe.setAttribute('style', 'border: none; margin: 0; padding: 0; height: 100%; width: 100%; resize: both; overflow:scroll;') - iframe.setAttribute( - 'style', - 'border: none; margin: 0; padding: 0; height: 100%; width: 100%;' - ) +*/ + iframe.classList.add('meetingPaneIframe') // Following https://dev.chromium.org/Home/chromium-security/deprecating-permissions-in-cross-origin-iframes : iframe.setAttribute('allow', 'microphone camera') // Allow iframe to request camera and mic // containerDiv.style.resize = 'none' // Remove scroll bars on outer div - don't seem to work so well @@ -1139,22 +1140,21 @@ export default { ) // "Make a new meeting" button - const imageStyle = 'height: 2em; width: 2em; margin:0.5em;' const detailsBottom = containerDiv.appendChild(dom.createElement('div')) const spawn = detailsBottom.appendChild(dom.createElement('img')) + spawn.classList.add('meetingToolIconSpawn') spawn.setAttribute('src', UI.icons.iconBase + 'noun_145978.svg') spawn.setAttribute('title', 'Make a fresh new meeting') spawn.addEventListener('click', makeNewMeeting) - spawn.setAttribute('style', imageStyle) // "Fork me on Github" button const forka = detailsBottom.appendChild(dom.createElement('a')) forka.setAttribute('href', 'https://github.com/solid/solid-panes') // @@ Move when code moves forka.setAttribute('target', '_blank') const fork = forka.appendChild(dom.createElement('img')) + fork.classList.add('meetingToolIconForkGithub') fork.setAttribute('src', UI.icons.iconBase + 'noun_368567.svg') fork.setAttribute('title', 'Fork me on github') - fork.setAttribute('style', imageStyle + 'opacity: 50%;') } if (kb.holds(subject, ns.rdf('type'), ns.meeting('Tool'))) { @@ -1176,7 +1176,7 @@ export default { ? dataBrowserContext.session.paneRegistry.byName(view) : null table = containerDiv.appendChild(dom.createElement('table')) - table.style.width = '100%' + table.classList.add('meetingPaneTableMain') dataBrowserContext .getOutliner(dom) .GotoSubject(target, true, pane, false, undefined, table) diff --git a/src/styles/meetingPane.css b/src/styles/meetingPane.css new file mode 100644 index 0000000..4013b54 --- /dev/null +++ b/src/styles/meetingPane.css @@ -0,0 +1,115 @@ +/* Some of the CSS code below was generated by Generative AI (GPT-5.3-Codex in GitHub Copilot) based on the following prompt: +the icons are way too big and they are lined up vertically +instead of horizontally. Can you fix that? */ +/* also icon sizing - the icons should be about an inch big */ +.meetingPaneTable { + border-collapse: collapse; + width: 100%; + height: 100%; + margin: 0; +} +.meetingPaneHeader { + height: 2em; +} + +.meetingPane { + padding: 5em; + box-sizing: border-box; +} + +.meetingPane .meetingToolBarIcons { + width: 1%; + white-space: nowrap !important; + vertical-align: middle; +} + +.meetingPane .meetingToolIconRow { + display: flex !important; + flex-direction: row !important; + flex-wrap: nowrap !important; + align-items: center !important; + justify-content: flex-start !important; + gap: var(--spacing-md, 1em); +} + +.meetingPane .meetingToolIcon { + box-sizing: border-box; + padding: 0 !important; + width: var(--icon-xl, 3.1em); + height: var(--icon-xl, 3.1em); + min-width: var(--icon-xl, 3.1em); + min-height: var(--icon-xl, 3.1em); + margin: var(--spacing-xs, 0.5em) !important; + opacity: 50%; + display: none !important; + vertical-align: middle; + object-fit: contain; + flex: 0 0 auto !important; +} + +.meetingPane .meetingToolIcon-visible { + display: inline-block !important; + opacity: 100% !important; +} +.meetingPane .meetingToolIcon-highlighted { + background-color: yellow; +} + +.meetingPane .meetingToolIcon-disabled { + opacity: 30%; +} + +.meetingPane .meetingToolIconAddButton { + padding: 0 !important; + margin: var(--spacing-xs, 0.5em) !important; + width: var(--icon-xl, 3.1em) !important; + height: var(--icon-xl, 3.1em) !important; + max-width: var(--icon-xl, 3.1em) !important; + max-height: var(--icon-xl, 3.1em) !important; + min-width: var(--icon-xl, 3.1em) !important; + min-height: var(--icon-xl, 3.1em) !important; +} + +.meetingToolLabel { + margin-left: 0.7em; +} +.meetingToolIconImg { + width: var(--icon-xs, 1.5em); + height: var(--icon-xs, 1.5em); +} + +.meetingToolTip { + margin: 0em; + padding:3em; + color: #888; +} +.meetingPaneDeleteButton { + width: var(--icon-xs, 1.5em); + height: var(--icon-xs, 1.5em); +} +.meetingPaneIframe { + border: none; + margin: 0; + padding: 0; + height: 100%; + width: 100%; +} +.meetingToolIconSpawn { + height: var(--icon-base, 2em); + width: var(--icon-base, 2em); + margin: var(--spacing-xs, 0.5em); +} +.meetingToolIconForkGithub { + height: var(--icon-base, 2em); + width: var(--icon-base, 2em); + margin: var(--spacing-xs, 0.5em); + opacity: 50%; +} +.meetingPaneTableMain { + border-collapse: collapse; + width: 100%; +} +/* ai generated below */ +.meetingPaneMainRow { + height: calc(100% - 50px); +} \ No newline at end of file diff --git a/src/styles/utilities.css b/src/styles/utilities.css new file mode 100644 index 0000000..6c860a4 --- /dev/null +++ b/src/styles/utilities.css @@ -0,0 +1,5 @@ +:root { + --icon-xs: 1.5em; + --icon-base: 2em; + --icon-xl: 3.1em; +} \ No newline at end of file diff --git a/webpack.config.mjs b/webpack.config.mjs index 4a8eeb0..f3ececb 100644 --- a/webpack.config.mjs +++ b/webpack.config.mjs @@ -1,46 +1,113 @@ import path from 'path' import NodePolyfillPlugin from 'node-polyfill-webpack-plugin' +import { moduleRules } from './webpack.module.rules.mjs' +import { createRequire } from 'module' +import TerserPlugin from 'terser-webpack-plugin' +import CopyPlugin from 'copy-webpack-plugin' -export default [ - { - mode: 'production', - entry: './src/meetingPane.js', - output: { - path: path.resolve(process.cwd(), 'dist'), - filename: 'meetingPane.js', - library: { - name: 'MeetingPane', - type: 'umd' - }, - globalObject: 'this', - clean: false +const require = createRequire(import.meta.url) + +const common = { + mode: 'production', + entry: './src/meetingPane.js', + output: { + path: path.resolve(process.cwd(), 'lib'), + filename: 'meetingPane.js', + library: { + name: 'MeetingPane', + type: 'umd' }, - plugins: [ - new NodePolyfillPlugin() - ], - module: { - rules: [ + globalObject: 'this', + clean: false + }, + plugins: [ + new NodePolyfillPlugin() + ], + module: { + rules: moduleRules, + }, + externals: { + 'solid-ui': 'UI', + 'solid-logic': 'SolidLogic', + rdflib: '$rdf', + }, + resolve: { + extensions: ['.js', '.ts'], + fallback: { + path: require.resolve('path-browserify') + }, + }, + devtool: 'source-map', +} + +const normalConfig = { + ...common, + mode: 'production', + output: { + path: path.resolve(process.cwd(), 'lib'), + filename: 'meeting-pane.js', + library: { + type: 'umd', + name: 'MeetingPane', + export: 'default', + }, + globalObject: 'this', + clean: true, + }, + plugins: [ + ...(common.plugins || []), + new CopyPlugin({ + patterns: [ { - test: /\.(js|ts)$/, - exclude: /node_modules/, - use: ['babel-loader'], + from: path.resolve('src/styles'), + to: path.resolve('lib/styles'), }, + ], + }), + ], + optimization: { + minimize: false, + } +} +const minConfig = { + ...common, + mode: 'production', + output: { + path: path.resolve(process.cwd(), 'lib'), + filename: 'meeting-pane.min.js', + library: { + type: 'umd', + name: 'MeetingPane', + export: 'default', + }, + globalObject: 'this', + clean: false, + }, + plugins: [ + ...(common.plugins || []), + new CopyPlugin({ + patterns: [ { - test: /\.ttl$/, // Target text files - type: 'asset/source', // Load the file's content as a string + from: path.resolve('src/styles'), + to: path.resolve('lib/styles'), }, - ], - }, - externals: { - 'solid-ui': 'UI', - 'solid-logic': 'SolidLogic', - rdflib: '$rdf', - }, - resolve: { - extensions: ['.js'], - }, - devtool: false, + }), + ], + optimization: { + minimize: true, + minimizer: [ + new TerserPlugin({ + terserOptions: { + format: { + comments: false, + }, + }, + extractComments: false, + }) + ], } -] +} + +export default [normalConfig, minConfig] diff --git a/webpack.dev.config.mjs b/webpack.dev.config.mjs index 4b721f4..acdc6f0 100644 --- a/webpack.dev.config.mjs +++ b/webpack.dev.config.mjs @@ -1,5 +1,6 @@ import HtmlWebpackPlugin from 'html-webpack-plugin' import NodePolyfillPlugin from 'node-polyfill-webpack-plugin' +import { moduleRules } from './webpack.module.rules.mjs' export default [ { @@ -10,19 +11,7 @@ export default [ new NodePolyfillPlugin() ], module: { - rules: [ - { - test: /\.(js|ts)$/, - exclude: /node_modules/, - use: ['babel-loader'], - }, - - { - test: /\.ttl$/, // Target text files - type: 'asset/source', // Load the file's content as a string - }, - - ], + rules: moduleRules, }, resolve: { extensions: ['.js', '.ts'], diff --git a/webpack.module.rules.mjs b/webpack.module.rules.mjs new file mode 100644 index 0000000..d361c54 --- /dev/null +++ b/webpack.module.rules.mjs @@ -0,0 +1,32 @@ +export const getModuleRules = function (styleLoader = 'style-loader') { + return [ + { + test: /\.(js|ts)$/, + exclude: /node_modules/, + use: ['babel-loader'], + }, + { + test: /\.css$/, + exclude: /\.module\.css$/, + use: [styleLoader, 'css-loader'], + }, + { + test: /\.module\.css$/, + use: [ + styleLoader, + { + loader: 'css-loader', + options: { + modules: true + } + } + ] + }, + { + test: /\.ttl$/, + type: 'asset/source', + } + ] +} + +export const moduleRules = getModuleRules()