Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node
{
"name": "Node.js",
"image": "mcr.microsoft.com/devcontainers/javascript-node:20",
"image": "mcr.microsoft.com/devcontainers/typescript-node:20",
"containerEnv": {
"HOST": "0.0.0.0" // Let Nuxt listen on all interfaces, otherwise container port forwarding doesn't work.
},
Expand Down
File renamed without changes.
13 changes: 13 additions & 0 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default defineNuxtConfig({
telemetry:true,
rootDir: './ui/',
compatibilityDate: '2025-09-13',
modules:['@nuxt/fonts', '@vueuse/nuxt'],
// css:['~/assets/styles/style.scss'],
runtimeConfig:{
public:{
websiteUrl:'',
searchIndexingIsAllowed: process.env.ALLOW_SEARCH_INDEXING === `allowed`,
}
}
})
21,905 changes: 10,560 additions & 11,345 deletions package-lock.json

Large diffs are not rendered by default.

17 changes: 6 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@
},
"type": "module",
"scripts": {
"dev": "nuxt dev",
"dev": "nuxt dev --no-qr",
"start": "nuxt start",

"build": "npm run build-without-nuxt && npm run build:nuxt",
"build-without-nuxt": "npm run build:register && npm run build:plugin-data && npm run build:test-fixtures && npm run build:model-docs && npm run build:api-docs",
"build:nuxt": "nuxt build",
Expand All @@ -23,15 +22,12 @@
"build:test-fixtures": "node cli/build-test-fixtures.js",
"build:model-docs": "jsdoc2md --private --files lib/model/*.js > docs/model-api.md",
"build:api-docs": "widdershins --code true --language_tabs --omitBody true --omitHeader true --resolve true --outfile docs/rest-api.md ui/api/openapi.json",

"clean": "rm -rf .nuxt tmp",
"update-package-lock": "rm package-lock.json && npm install && npm audit fix && npx update-browserslist-db@latest",

"lint": "npm run lint:eslint && npm run lint:stylelint",
"lint:eslint": "eslint .",
"lint:eslint-errors-only": "eslint --quiet .",
"lint:stylelint": "stylelint **/*.scss **/*.vue",

"test": "npm run lint && npm run test:fixtures-valid",
"test:dmx-value-scaling": "node tests/dmx-value-scaling.js",
"test:fixtures-valid": "node tests/fixtures-valid.js -a",
Expand All @@ -40,33 +36,31 @@
},
"dependencies": {
"@heise/embetty-server": "^2.0.3",
"@nuxtjs/axios": "^5.13.6",
"@nuxt/fonts": "^0.11.4",
"@nuxtjs/robots": "^2.5.0",
"@nuxtjs/sitemap": "^2.4.0",
"@octokit/rest": "~22.0.0",
"@vueuse/nuxt": "^14.0.0",
"a11y-dialog": "^8.1.4",
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"chalk": "^5.5.0",
"color-hash": "^2.0.2",
"color-name-list": "^10.28.1",
"cookie-universal-nuxt": "^2.2.2",
"cors": "^2.8.5",
"embetty-vue": "^2.0.1",
"express": "^4.21.2",
"jszip": "^3.10.1",
"node-env-file": "^0.1.8",
"nuxt": "~2.18.1",
"nuxt": "~4.1.2",
"nuxt-helmet": "^3.0.0",
"openapi-backend": "^5.14.0",
"sanitize-filename": "^1.6.3",
"sass": "^1.90.0",
"sass-loader": "^10.5.2",
"scroll-into-view": "^1.16.2",
"svg-inline-loader": "^0.8.2",
"typescript": "^5.9.2",
"uuid": "^10.0.0",
"vue-form": "^4.10.3",
"vue-ts-types": "^1.8.1",
"vuedraggable": "^2.24.3",
"xml2js": "^0.6.2",
"xmlbuilder": "^15.1.1"
Expand Down Expand Up @@ -100,6 +94,7 @@
"stylelint-config-standard-scss": "~15.0.1",
"stylelint-order": "^7.0.0",
"terminate": "^2.6.1",
"vue": "^3.5.21",
"vue-eslint-parser": "~10.2.0",
"widdershins": "^4.0.1"
},
Expand Down
2 changes: 1 addition & 1 deletion server/ecosystem.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const environmentVariablesOfl = {
'ALLOW_SEARCH_INDEXING': `allowed`,
'GITHUB_USER_TOKEN': secrets.OFL_GITHUB_USER_TOKEN,
'NODE_ENV': `production`,
'PORT': `5000`,
'NITRO_PORT': `5000`,
'WEBSITE_URL': `https://open-fixture-library.org/`,
};

Expand Down
3 changes: 3 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "./ui/.nuxt/tsconfig.json"
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
203 changes: 203 additions & 0 deletions ui/app/components/A11yDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
<template>
<!-- eslint-disable-next-line vuejs-accessibility/no-aria-hidden-on-focusable -- aria-hidden is dynamically toggled -->
<div
:id="id"
class="dialog-container"
:aria-hidden="shown ? `false` : `true`"
:aria-labelledby="`${id}-dialog-title`"
:role="isAlertDialog ? `alertdialog` : undefined"
@click.self="overlayClick"
>
<div
ref="dialogRef"
role="document"
class="dialog card"
:class="{ wide }"
>
<div>
<button
v-if="!isAlertDialog"
type="button"
class="icon-button close"
title="Close"
@click.prevent="hide"
>
Close
<OflSvg name="close" />
</button>

<h2 :id="`${id}-dialog-title`" tabindex="-1">
<slot name="title">{{ title }}</slot>
</h2>

<slot />
</div>
</div>
</div>
</template>

<style lang="scss" scoped>
@keyframes fade-in {
from {
opacity: 0;
}
}

@keyframes scale-up {
from {
transform: scale(0.9);
}
}

$container-fade-duration: 200ms;

.dialog-container {
position: fixed;
inset: 0;
z-index: 1000;
display: flex;
background-color: rgba(0, 0, 0, 66%);
animation: fade-in $container-fade-duration both;
}

.dialog-container[aria-hidden="true"] {
display: none;
}

.dialog {
box-sizing: border-box;
min-width: 20rem;
max-width: 90%;
max-height: 90%;
margin: auto;
overflow: auto;
overscroll-behavior: contain;
color: theme-color(text-primary);
background-color: theme-color(dialog-background);
animation:
fade-in 200ms $container-fade-duration both,
scale-up 200ms $container-fade-duration both;

&.wide {
width: 1000px;
}

h2:focus {
outline: none;
}

// fixes padding not being visible when scrollbar is present
&.card {
padding: 0;

& > div {
padding: 1rem;
}
}
}

@media (max-width: $phone) {
// make dialogs cover the whole screen
.dialog,
.dialog.wide {
width: 100%;
min-width: none;
max-width: none;
height: 100%;
max-height: none;
}
}
</style>

<script setup lang="ts">
import A11yDialog from 'a11y-dialog'

// Props
const props = defineProps<{
id: string;
isAlertDialog?: boolean;
shown?: boolean;
title: string;
wide?: boolean;
}>();

// Defaults for optional props (matching original withDefault)
const isAlertDialog = props.isAlertDialog ?? false;
const shown = ref(props.shown ?? true);
const wide = props.wide ?? false;
const { id, title } = props;

// Emits
const emit = defineEmits<{
(e: 'show'): void;
(e: 'hide'): void;
}>();

// Refs
const dialogRef = ref<HTMLDivElement | null>(null);
const a11yInstance = shallowRef<{
show: () => void;
hide: () => void;
on: (event: 'show' | 'hide', cb: () => void) => void;
off?: (event: 'show' | 'hide', cb: () => void) => void;
destroy: () => void;
} | null>(null);

// API equivalents
const show = () => a11yInstance.value?.show();
const hide = () => a11yInstance.value?.hide();

// Keep local shown in sync with incoming prop changes
watch(
() => props.shown,
val => {
shown.value = val ?? true;
update();
},
{ immediate: true }
);

// Update helper (mirrors original update method)
function update() {
if (shown.value) {
show();
} else {
hide();
}
}

// Overlay click (close only if not alert dialog)
function overlayClick() {
if (!isAlertDialog) {
hide();
}
}

onMounted(() => {
// Create dialog instance on the root element (component's root)
// In <script setup>, get current root via closest('.dialog-container') from inner ref
const container = dialogRef.value?.closest('.dialog-container') as HTMLElement | null;
if (!container) return;

const instance = new A11yDialog(container);
a11yInstance.value = instance;

instance.on('show', () => {
// Reset scroll position like original this.$refs.dialog.scrollTop = 0
if (dialogRef.value) {
dialogRef.value.scrollTop = 0;
}
emit('show');
});

instance.on('hide', () => {
emit('hide');
});

update();
});

onBeforeUnmount(() => {
a11yInstance.value?.destroy();
});
</script>
Loading