Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| be4c98b543 | |||
| dca2d212b1 | |||
| 53986f8d95 | |||
| a4a9f6f77f | |||
| 9fcfdb8570 |
@@ -12,8 +12,11 @@ jobs:
|
||||
outputs:
|
||||
release_id: ${{ steps.create.outputs.release_id }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
- name: Fetch RELEASE_NOTES.md only
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: RELEASE_NOTES.md
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Create Gitea release
|
||||
id: create
|
||||
@@ -33,11 +36,9 @@ jobs:
|
||||
REPO=$(echo "${{ gitea.repository }}" | tr '[:upper:]' '[:lower:]')
|
||||
DOCKER_IMAGE="${SERVER_HOST}/${REPO}"
|
||||
|
||||
# Scan for RELEASE_NOTES.md (check repo root first, then recursively)
|
||||
NOTES_FILE=$(find . -maxdepth 3 -name "RELEASE_NOTES.md" -type f | head -1)
|
||||
if [ -n "$NOTES_FILE" ]; then
|
||||
export RELEASE_NOTES=$(cat "$NOTES_FILE")
|
||||
echo "Found release notes: $NOTES_FILE"
|
||||
if [ -f RELEASE_NOTES.md ]; then
|
||||
export RELEASE_NOTES=$(cat RELEASE_NOTES.md)
|
||||
echo "Found RELEASE_NOTES.md"
|
||||
else
|
||||
export RELEASE_NOTES=""
|
||||
echo "No RELEASE_NOTES.md found"
|
||||
|
||||
@@ -138,6 +138,10 @@ export class FilterListManager {
|
||||
if (filterDef && !isExpanded) {
|
||||
summary = filterDef.options_schema.map(opt => {
|
||||
const val = fi.options[opt.key] !== undefined ? fi.options[opt.key] : opt.default;
|
||||
if (opt.type === 'select' && Array.isArray(opt.choices)) {
|
||||
const choice = opt.choices.find(c => c.value === val);
|
||||
if (choice) return choice.label;
|
||||
}
|
||||
return val;
|
||||
}).join(', ');
|
||||
}
|
||||
|
||||
@@ -336,10 +336,17 @@ function renderNode(node: GraphNode, callbacks: NodeCallbacks): SVGElement {
|
||||
g.appendChild(dot);
|
||||
}
|
||||
|
||||
// Clip path for title/subtitle text (prevent overflow past icon area)
|
||||
const clipId = `clip-text-${id.replace(/[^a-zA-Z0-9_-]/g, '_')}`;
|
||||
const clipPath = svgEl('clipPath', { id: clipId });
|
||||
clipPath.appendChild(svgEl('rect', { x: 14, y: 0, width: width - 48, height }));
|
||||
g.appendChild(clipPath);
|
||||
|
||||
// Title (shift left edge for icon to have room)
|
||||
const title = svgEl('text', {
|
||||
class: 'graph-node-title',
|
||||
x: 16, y: 24,
|
||||
'clip-path': `url(#${clipId})`,
|
||||
});
|
||||
title.textContent = name;
|
||||
g.appendChild(title);
|
||||
@@ -349,6 +356,7 @@ function renderNode(node: GraphNode, callbacks: NodeCallbacks): SVGElement {
|
||||
const sub = svgEl('text', {
|
||||
class: 'graph-node-subtitle',
|
||||
x: 16, y: 42,
|
||||
'clip-path': `url(#${clipId})`,
|
||||
});
|
||||
sub.textContent = subtype.replace(/_/g, ' ');
|
||||
g.appendChild(sub);
|
||||
|
||||
@@ -82,4 +82,5 @@ export const trash2 = '<path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c
|
||||
export const listChecks = '<path d="m3 17 2 2 4-4"/><path d="m3 7 2 2 4-4"/><path d="M13 6h8"/><path d="M13 12h8"/><path d="M13 18h8"/>';
|
||||
export const circleOff = '<path d="m2 2 20 20"/><path d="M8.35 2.69A10 10 0 0 1 21.3 15.65"/><path d="M19.08 19.08A10 10 0 1 1 4.92 4.92"/>';
|
||||
export const externalLink = '<path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>';
|
||||
export const thermometer = '<path d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z"/>';
|
||||
export const xIcon = '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>';
|
||||
|
||||
@@ -176,6 +176,7 @@ export const ICON_UNDO = _svg(P.undo2);
|
||||
export const ICON_SCENE = _svg(P.sparkles);
|
||||
export const ICON_CAPTURE = _svg(P.camera);
|
||||
export const ICON_BELL = _svg(P.bellRing);
|
||||
export const ICON_THERMOMETER = _svg(P.thermometer);
|
||||
export const ICON_CPU = _svg(P.cpu);
|
||||
export const ICON_KEYBOARD = _svg(P.keyboard);
|
||||
export const ICON_MOUSE = _svg(P.mouse);
|
||||
|
||||
@@ -38,9 +38,8 @@ function _collectPreviewConfig() {
|
||||
if (colors.length < 2) return null;
|
||||
config = { source_type: 'color_cycle', colors };
|
||||
} else if (sourceType === 'effect') {
|
||||
config = { source_type: 'effect', effect_type: (document.getElementById('css-editor-effect-type') as HTMLInputElement).value, palette: (document.getElementById('css-editor-effect-palette') as HTMLInputElement).value, intensity: parseFloat((document.getElementById('css-editor-effect-intensity') as HTMLInputElement).value), scale: parseFloat((document.getElementById('css-editor-effect-scale') as HTMLInputElement).value), mirror: (document.getElementById('css-editor-effect-mirror') as HTMLInputElement).checked };
|
||||
config = { source_type: 'effect', effect_type: (document.getElementById('css-editor-effect-type') as HTMLInputElement).value, gradient_id: (document.getElementById('css-editor-effect-palette') as HTMLInputElement).value, intensity: parseFloat((document.getElementById('css-editor-effect-intensity') as HTMLInputElement).value), scale: parseFloat((document.getElementById('css-editor-effect-scale') as HTMLInputElement).value), mirror: (document.getElementById('css-editor-effect-mirror') as HTMLInputElement).checked };
|
||||
if (['meteor', 'comet', 'bouncing_ball'].includes(config.effect_type)) { const hex = (document.getElementById('css-editor-effect-color') as HTMLInputElement).value; config.color = [parseInt(hex.slice(1, 3), 16), parseInt(hex.slice(3, 5), 16), parseInt(hex.slice(5, 7), 16)]; }
|
||||
if (config.palette === 'custom') { const cpText = (document.getElementById('css-editor-effect-custom-palette') as HTMLTextAreaElement)?.value?.trim(); if (cpText) { try { config.custom_palette = JSON.parse(cpText); } catch {} } }
|
||||
} else if (sourceType === 'daylight') {
|
||||
config = { source_type: 'daylight', speed: parseFloat((document.getElementById('css-editor-daylight-speed') as HTMLInputElement).value), use_real_time: (document.getElementById('css-editor-daylight-real-time') as HTMLInputElement).checked, latitude: parseFloat((document.getElementById('css-editor-daylight-latitude') as HTMLInputElement).value), longitude: parseFloat((document.getElementById('css-editor-daylight-longitude') as HTMLInputElement).value) };
|
||||
} else if (sourceType === 'candlelight') {
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
ICON_LED, ICON_PALETTE, ICON_FPS, ICON_MAP_PIN, ICON_MUSIC,
|
||||
ICON_AUDIO_LOOPBACK, ICON_TIMER, ICON_LINK_SOURCE, ICON_FILM,
|
||||
ICON_LINK, ICON_SPARKLES, ICON_ACTIVITY, ICON_CLOCK, ICON_BELL, ICON_TEST,
|
||||
ICON_AUTOMATION,
|
||||
ICON_AUTOMATION, ICON_FAST_FORWARD, ICON_THERMOMETER,
|
||||
} from '../core/icons.ts';
|
||||
import * as P from '../core/icon-paths.ts';
|
||||
import { wrapCard } from '../core/card-colors.ts';
|
||||
@@ -1067,7 +1067,7 @@ const CSS_CARD_RENDERERS: Record<string, CardPropsRenderer> = {
|
||||
const useRealTime = source.use_real_time;
|
||||
const speedVal = (source.speed ?? 1.0).toFixed(1);
|
||||
return `
|
||||
<span class="stream-card-prop">${useRealTime ? '🕐 ' + t('color_strip.daylight.real_time') : '⏩ ' + speedVal + 'x'}</span>
|
||||
<span class="stream-card-prop">${useRealTime ? ICON_CLOCK + ' ' + t('color_strip.daylight.real_time') : ICON_FAST_FORWARD + ' ' + speedVal + 'x'}</span>
|
||||
${clockBadge}
|
||||
`;
|
||||
},
|
||||
@@ -1092,8 +1092,8 @@ const CSS_CARD_RENDERERS: Record<string, CardPropsRenderer> = {
|
||||
: '';
|
||||
return `
|
||||
<span class="stream-card-prop${wsLink}" title="${t('color_strip.weather.source')}">${ICON_LINK_SOURCE} ${escapeHtml(wsName)}</span>
|
||||
<span class="stream-card-prop">⏩ ${speedVal}x</span>
|
||||
<span class="stream-card-prop">🌡 ${tempInfl}</span>
|
||||
<span class="stream-card-prop">${ICON_FAST_FORWARD} ${speedVal}x</span>
|
||||
<span class="stream-card-prop">${ICON_THERMOMETER} ${tempInfl}</span>
|
||||
${clockBadge}
|
||||
`;
|
||||
},
|
||||
|
||||
@@ -409,7 +409,14 @@ function renderPictureSourcesList(streams: any) {
|
||||
const renderPPTemplateCard = (tmpl: any) => {
|
||||
let filterChainHtml = '';
|
||||
if (tmpl.filters && tmpl.filters.length > 0) {
|
||||
const filterNames = tmpl.filters.map(fi => `<span class="filter-chain-item">${escapeHtml(_getFilterName(fi.filter_id))}</span>`);
|
||||
const filterNames = tmpl.filters.map(fi => {
|
||||
let label = _getFilterName(fi.filter_id);
|
||||
if (fi.filter_id === 'filter_template' && fi.options?.template_id) {
|
||||
const ref = _cachedPPTemplates.find(p => p.id === fi.options.template_id);
|
||||
if (ref) label += `: ${ref.name}`;
|
||||
}
|
||||
return `<span class="filter-chain-item">${escapeHtml(label)}</span>`;
|
||||
});
|
||||
filterChainHtml = `<div class="filter-chain">${filterNames.join('<span class="filter-chain-arrow">→</span>')}</div>`;
|
||||
}
|
||||
return wrapCard({
|
||||
@@ -435,7 +442,14 @@ function renderPictureSourcesList(streams: any) {
|
||||
const renderCSPTCard = (tmpl: any) => {
|
||||
let filterChainHtml = '';
|
||||
if (tmpl.filters && tmpl.filters.length > 0) {
|
||||
const filterNames = tmpl.filters.map(fi => `<span class="filter-chain-item">${escapeHtml(_getStripFilterName(fi.filter_id))}</span>`);
|
||||
const filterNames = tmpl.filters.map(fi => {
|
||||
let label = _getStripFilterName(fi.filter_id);
|
||||
if (fi.filter_id === 'css_filter_template' && fi.options?.template_id) {
|
||||
const ref = _cachedCSPTemplates.find(p => p.id === fi.options.template_id);
|
||||
if (ref) label += `: ${ref.name}`;
|
||||
}
|
||||
return `<span class="filter-chain-item">${escapeHtml(label)}</span>`;
|
||||
});
|
||||
filterChainHtml = `<div class="filter-chain">${filterNames.join('<span class="filter-chain-arrow">\u2192</span>')}</div>`;
|
||||
}
|
||||
return wrapCard({
|
||||
|
||||
Reference in New Issue
Block a user