Put Duplicate on the drawings list; do not require a time under the click.
The handler never ran — no event, no POST. Right-click in empty time also hid the menu. List button uses the selected trendline.
This commit is contained in:
parent
c1d508cb4f
commit
a057341bbf
3 changed files with 32 additions and 4 deletions
|
|
@ -1112,6 +1112,23 @@ createApp({
|
||||||
return `${base} ${number}`;
|
return `${base} ${number}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectedTrendline = computed(() => {
|
||||||
|
const id = selectedDrawing.value;
|
||||||
|
if (!id) return null;
|
||||||
|
const line = levels.value.find(level => level.id === id);
|
||||||
|
if (!line || line.kind !== 'manual' || !line.slope) return null;
|
||||||
|
return line;
|
||||||
|
});
|
||||||
|
|
||||||
|
async function duplicateSelected() {
|
||||||
|
const line = selectedTrendline.value;
|
||||||
|
if (!line) {
|
||||||
|
addEvent('DUPLICATE FAILED · select one trendline', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await duplicateLine(line);
|
||||||
|
}
|
||||||
|
|
||||||
async function duplicateLine(line) {
|
async function duplicateLine(line) {
|
||||||
const fail = reason => {
|
const fail = reason => {
|
||||||
addEvent(`DUPLICATE #${line.number} FAILED · ${reason}`, 'error');
|
addEvent(`DUPLICATE #${line.number} FAILED · ${reason}`, 'error');
|
||||||
|
|
@ -1125,6 +1142,15 @@ createApp({
|
||||||
}),
|
}),
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
};
|
};
|
||||||
|
fetch('/api/debug/snap', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
kind: 'duplicate',
|
||||||
|
tf: line.tf,
|
||||||
|
note: `start #${line.number} last_t ${line.last_t} cutoff ${line.cutoff_t}`,
|
||||||
|
}),
|
||||||
|
}).catch(() => {});
|
||||||
if (line.geometry_resolved === false) {
|
if (line.geometry_resolved === false) {
|
||||||
fail('no source geometry');
|
fail('no source geometry');
|
||||||
return;
|
return;
|
||||||
|
|
@ -1656,6 +1682,6 @@ createApp({
|
||||||
window.removeEventListener('keydown', handleKeydown);
|
window.removeEventListener('keydown', handleKeydown);
|
||||||
});
|
});
|
||||||
|
|
||||||
return { status, price, sessionOpen, quoteChange, animateCurrentPrice, autoScrollLivePrice, extraDetail, hideLowerTfDrawings, confluenceAlerts, barAge, dataUpdatedAt, buildStamp, timeframe, timeframes, drawingColors, drawingColorRows, drawingColorName, colorRowLabels, symbolChoices, selectedSymbol, symbolColor, symbolScale, symbolScales, symbolPanelOpen, prefs, clusters, clustersByPrice, events, eventsMore, loadOlderEvents, diagnosticMode, captureBusy, captureCountdown, captureDiagnostic, armedTool, drawName, drawColor, drawWidth, drawSide, snap, selectedDrawing, selectedDrawings, drawingList, startDrawingListResize, manualLines, hasDrawingSelection, allShownSelected, selectedAreHidden, alertPrice, alertNote, alertEarlyPoints, levelColor, levelWidth, addPriceAlert, armTool, selectTimeframe, allEnabled, toggleGroup, maValue, maAlertOn, toggleMaAlert, deleteSelected, deleteLine, toggleDrawingSelection, activateDrawing, toggleSelectAll, toggleSelectedVisibility, renameLine, updateLineStyle, updateLevelNumber, setArmed, commentText, commentFloat, comments, drawings, filteredDrawings, drawingFilter, drawingKind, drawingTf, deleteDrawing, toggleComment, togglePinned, chooseSymbol, toggleSymbolPanel, startSymbolDrag, dropSymbol, optionExpirations, optionExpiryId, optionSide, optionMode, optionMin, optionMax, optionContracts, optionUnderlying, optionBusy, optionError, optionSearched, optionCopied, onOptionsToggle, searchOptions, copyOption, canUndo, undoTitle, undo };
|
return { status, price, sessionOpen, quoteChange, animateCurrentPrice, autoScrollLivePrice, extraDetail, hideLowerTfDrawings, confluenceAlerts, barAge, dataUpdatedAt, buildStamp, timeframe, timeframes, drawingColors, drawingColorRows, drawingColorName, colorRowLabels, symbolChoices, selectedSymbol, symbolColor, symbolScale, symbolScales, symbolPanelOpen, prefs, clusters, clustersByPrice, events, eventsMore, loadOlderEvents, diagnosticMode, captureBusy, captureCountdown, captureDiagnostic, armedTool, drawName, drawColor, drawWidth, drawSide, snap, selectedDrawing, selectedDrawings, drawingList, startDrawingListResize, manualLines, hasDrawingSelection, allShownSelected, selectedAreHidden, selectedTrendline, duplicateSelected, alertPrice, alertNote, alertEarlyPoints, levelColor, levelWidth, addPriceAlert, armTool, selectTimeframe, allEnabled, toggleGroup, maValue, maAlertOn, toggleMaAlert, deleteSelected, deleteLine, toggleDrawingSelection, activateDrawing, toggleSelectAll, toggleSelectedVisibility, renameLine, updateLineStyle, updateLevelNumber, setArmed, commentText, commentFloat, comments, drawings, filteredDrawings, drawingFilter, drawingKind, drawingTf, deleteDrawing, toggleComment, togglePinned, chooseSymbol, toggleSymbolPanel, startSymbolDrag, dropSymbol, optionExpirations, optionExpiryId, optionSide, optionMode, optionMin, optionMax, optionContracts, optionUnderlying, optionBusy, optionError, optionSearched, optionCopied, onOptionsToggle, searchOptions, copyOption, canUndo, undoTitle, undo };
|
||||||
},
|
},
|
||||||
}).mount('#app');
|
}).mount('#app');
|
||||||
|
|
|
||||||
|
|
@ -569,7 +569,8 @@ class ConfluenceChart {
|
||||||
duplicate.textContent = 'Duplicate';
|
duplicate.textContent = 'Duplicate';
|
||||||
duplicate.addEventListener('click', event => {
|
duplicate.addEventListener('click', event => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const level = this.levels.find(value => value.id === this.contextLineId);
|
const id = this.contextLineId || this.selectedLineId;
|
||||||
|
const level = this.levels.find(value => value.id === id);
|
||||||
this.hideContextMenu();
|
this.hideContextMenu();
|
||||||
if (level && this.onLineDuplicate) this.onLineDuplicate({ ...level });
|
if (level && this.onLineDuplicate) this.onLineDuplicate({ ...level });
|
||||||
});
|
});
|
||||||
|
|
@ -3167,9 +3168,9 @@ class ConfluenceChart {
|
||||||
if (!level || !this.bars.length) return;
|
if (!level || !this.bars.length) return;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const point = this.eventPoint(event);
|
const point = this.eventPoint(event);
|
||||||
if (!point || !this.withinPlot(point) || point.t == null) return;
|
if (!point || !this.withinPlot(point)) return;
|
||||||
const lastBar = this.bars[this.bars.length - 1];
|
const lastBar = this.bars[this.bars.length - 1];
|
||||||
const cutoff = point.t > lastBar.t
|
const cutoff = point.t == null ? null : point.t > lastBar.t
|
||||||
? Math.round(point.t)
|
? Math.round(point.t)
|
||||||
: this.bars.reduce((nearest, bar) =>
|
: this.bars.reduce((nearest, bar) =>
|
||||||
Math.abs(bar.t - point.t) < Math.abs(nearest.t - point.t) ? bar : nearest
|
Math.abs(bar.t - point.t) < Math.abs(nearest.t - point.t) ? bar : nearest
|
||||||
|
|
|
||||||
|
|
@ -282,6 +282,7 @@
|
||||||
<i :class="selectedAreHidden ? 'fa-solid fa-eye' : 'fa-solid fa-eye-slash'"></i>
|
<i :class="selectedAreHidden ? 'fa-solid fa-eye' : 'fa-solid fa-eye-slash'"></i>
|
||||||
{{ selectedAreHidden ? 'Show' : 'Hide' }}
|
{{ selectedAreHidden ? 'Show' : 'Hide' }}
|
||||||
</button>
|
</button>
|
||||||
|
<button @click="duplicateSelected" :disabled="!selectedTrendline" aria-label="Duplicate selected trendline">Duplicate</button>
|
||||||
<button @click="deleteSelected" :disabled="!selectedDrawings.length" aria-label="Delete selected drawings">Delete</button>
|
<button @click="deleteSelected" :disabled="!selectedDrawings.length" aria-label="Delete selected drawings">Delete</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="drawing-list-shell">
|
<div class="drawing-list-shell">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue