터치로 전략컨트롤 동작
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
handlePalettePointerDown,
|
handlePalettePointerDown,
|
||||||
|
handlePaletteTouchStart,
|
||||||
needsPointerPaletteDrag,
|
needsPointerPaletteDrag,
|
||||||
shouldSuppressPaletteClick,
|
shouldSuppressPaletteClick,
|
||||||
writePaletteHtmlDragData,
|
writePaletteHtmlDragData,
|
||||||
@@ -55,6 +56,10 @@ export default function PaletteChip({
|
|||||||
handlePalettePointerDown(e, buildPayload(), composite ? `${label} + ${label}` : label);
|
handlePalettePointerDown(e, buildPayload(), composite ? `${label} + ${label}` : label);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onTouchStart = (e: React.TouchEvent) => {
|
||||||
|
handlePaletteTouchStart(e, buildPayload(), composite ? `${label} + ${label}` : label);
|
||||||
|
};
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
if (shouldSuppressPaletteClick()) return;
|
if (shouldSuppressPaletteClick()) return;
|
||||||
if (onSelect) onSelect();
|
if (onSelect) onSelect();
|
||||||
@@ -83,6 +88,7 @@ export default function PaletteChip({
|
|||||||
className={`se-palette-card ${color ? `se-palette-card--${color}` : 'se-palette-card--ind'}${composite ? ' se-palette-card--composite' : ''}${selected ? ' se-palette-card--selected' : ''}`}
|
className={`se-palette-card ${color ? `se-palette-card--${color}` : 'se-palette-card--ind'}${composite ? ' se-palette-card--composite' : ''}${selected ? ' se-palette-card--selected' : ''}`}
|
||||||
onDragStart={onDragStart}
|
onDragStart={onDragStart}
|
||||||
onPointerDown={onPointerDown}
|
onPointerDown={onPointerDown}
|
||||||
|
onTouchStart={onTouchStart}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
onDoubleClick={handleDoubleClick}
|
onDoubleClick={handleDoubleClick}
|
||||||
role="button"
|
role="button"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import {
|
import {
|
||||||
bindPaletteDragOverlayElement,
|
bindPaletteDragOverlayElement,
|
||||||
@@ -6,53 +6,67 @@ import {
|
|||||||
notifyPaletteDragEnd,
|
notifyPaletteDragEnd,
|
||||||
notifyPaletteDragMove,
|
notifyPaletteDragMove,
|
||||||
registerPaletteDragOverlay,
|
registerPaletteDragOverlay,
|
||||||
|
subscribePaletteDrag,
|
||||||
type PaletteDragOverlaySession,
|
type PaletteDragOverlaySession,
|
||||||
} from '../../utils/paletteDragSession';
|
} from '../../utils/paletteDragSession';
|
||||||
|
import { clientXYFromReact } from '../../utils/pointerDrag';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tauri WebView — 드래그 overlay (라벨 위치는 ref 갱신, move 마다 React re-render 없음)
|
* pointer/touch 드래그 overlay (sessionRef 로 stale closure 방지, window touch 백업과 연동)
|
||||||
*/
|
*/
|
||||||
export default function PaletteDragOverlay() {
|
export default function PaletteDragOverlay() {
|
||||||
const [session, setSession] = useState<PaletteDragOverlaySession | null>(null);
|
const [session, setSession] = useState<PaletteDragOverlaySession | null>(null);
|
||||||
const overlayRef = useRef<HTMLDivElement | null>(null);
|
const sessionRef = useRef<PaletteDragOverlaySession | null>(null);
|
||||||
const labelRef = useRef<HTMLDivElement | null>(null);
|
const labelRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
sessionRef.current = session;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
registerPaletteDragOverlay({
|
registerPaletteDragOverlay({
|
||||||
mount: next => {
|
mount: next => {
|
||||||
|
sessionRef.current = next;
|
||||||
setSession(next);
|
setSession(next);
|
||||||
requestAnimationFrame(() => {
|
|
||||||
const label = labelRef.current;
|
|
||||||
if (label) {
|
|
||||||
label.style.left = `${next.x}px`;
|
|
||||||
label.style.top = `${next.y}px`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
unmount: () => setSession(null),
|
unmount: () => {
|
||||||
bindElement: el => {
|
sessionRef.current = null;
|
||||||
overlayRef.current = el as HTMLDivElement | null;
|
setSession(null);
|
||||||
|
bindPaletteDragOverlayElement(null);
|
||||||
},
|
},
|
||||||
|
bindElement: () => { /* ref callback 에서 처리 */ },
|
||||||
});
|
});
|
||||||
return () => registerPaletteDragOverlay(null);
|
return () => registerPaletteDragOverlay(null);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useEffect(() => {
|
||||||
bindPaletteDragOverlayElement(overlayRef.current);
|
return subscribePaletteDrag(ev => {
|
||||||
return () => bindPaletteDragOverlayElement(null);
|
if (ev.phase !== 'move' && ev.phase !== 'start') return;
|
||||||
}, [session]);
|
const label = labelRef.current;
|
||||||
|
if (!label) return;
|
||||||
|
label.style.left = `${ev.x}px`;
|
||||||
|
label.style.top = `${ev.y}px`;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
const overlayRefCallback = useCallback((el: HTMLDivElement | null) => {
|
||||||
if (!session || !overlayRef.current) return;
|
bindPaletteDragOverlayElement(el);
|
||||||
|
const s = sessionRef.current;
|
||||||
|
if (el && s) {
|
||||||
try {
|
try {
|
||||||
overlayRef.current.setPointerCapture(session.pointerId);
|
el.setPointerCapture(s.pointerId);
|
||||||
} catch {
|
} catch {
|
||||||
/* ignore */
|
/* window backup in paletteDragSession */
|
||||||
}
|
}
|
||||||
}, [session]);
|
const label = labelRef.current;
|
||||||
|
if (label) {
|
||||||
|
label.style.left = `${s.x}px`;
|
||||||
|
label.style.top = `${s.y}px`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const onPointerMove = useCallback((e: React.PointerEvent) => {
|
const onPointerMove = useCallback((e: React.PointerEvent) => {
|
||||||
if (!session || e.pointerId !== session.pointerId) return;
|
const s = sessionRef.current;
|
||||||
|
if (!s || e.pointerId !== s.pointerId) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const label = labelRef.current;
|
const label = labelRef.current;
|
||||||
@@ -61,31 +75,60 @@ export default function PaletteDragOverlay() {
|
|||||||
label.style.top = `${e.clientY}px`;
|
label.style.top = `${e.clientY}px`;
|
||||||
}
|
}
|
||||||
notifyPaletteDragMove(e.clientX, e.clientY);
|
notifyPaletteDragMove(e.clientX, e.clientY);
|
||||||
}, [session]);
|
}, []);
|
||||||
|
|
||||||
const onPointerUp = useCallback((e: React.PointerEvent) => {
|
const onPointerUp = useCallback((e: React.PointerEvent) => {
|
||||||
if (!session || e.pointerId !== session.pointerId) return;
|
const s = sessionRef.current;
|
||||||
|
if (!s || e.pointerId !== s.pointerId) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
notifyPaletteDragEnd(e.clientX, e.clientY);
|
notifyPaletteDragEnd(e.clientX, e.clientY);
|
||||||
}, [session]);
|
}, []);
|
||||||
|
|
||||||
const onPointerCancel = useCallback((e: React.PointerEvent) => {
|
const onPointerCancel = useCallback((e: React.PointerEvent) => {
|
||||||
if (!session || e.pointerId !== session.pointerId) return;
|
const s = sessionRef.current;
|
||||||
|
if (!s || e.pointerId !== s.pointerId) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
notifyPaletteDragCancel(e.clientX, e.clientY);
|
notifyPaletteDragCancel(e.clientX, e.clientY);
|
||||||
}, [session]);
|
}, []);
|
||||||
|
|
||||||
|
const onTouchMove = useCallback((e: React.TouchEvent) => {
|
||||||
|
const s = sessionRef.current;
|
||||||
|
if (!s || e.touches.length !== 1) return;
|
||||||
|
e.preventDefault();
|
||||||
|
const { x, y } = clientXYFromReact(e);
|
||||||
|
notifyPaletteDragMove(x, y);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onTouchEnd = useCallback((e: React.TouchEvent) => {
|
||||||
|
const s = sessionRef.current;
|
||||||
|
if (!s) return;
|
||||||
|
const { x, y } = clientXYFromReact(e);
|
||||||
|
e.preventDefault();
|
||||||
|
notifyPaletteDragEnd(x, y);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onTouchCancel = useCallback((e: React.TouchEvent) => {
|
||||||
|
const s = sessionRef.current;
|
||||||
|
if (!s) return;
|
||||||
|
const { x, y } = clientXYFromReact(e);
|
||||||
|
e.preventDefault();
|
||||||
|
notifyPaletteDragCancel(x, y);
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (!session) return null;
|
if (!session) return null;
|
||||||
|
|
||||||
return createPortal(
|
return createPortal(
|
||||||
<div
|
<div
|
||||||
ref={overlayRef}
|
ref={overlayRefCallback}
|
||||||
className="se-palette-drag-overlay"
|
className="se-palette-drag-overlay"
|
||||||
onPointerMove={onPointerMove}
|
onPointerMove={onPointerMove}
|
||||||
onPointerUp={onPointerUp}
|
onPointerUp={onPointerUp}
|
||||||
onPointerCancel={onPointerCancel}
|
onPointerCancel={onPointerCancel}
|
||||||
|
onTouchMove={onTouchMove}
|
||||||
|
onTouchEnd={onTouchEnd}
|
||||||
|
onTouchCancel={onTouchCancel}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
ref={labelRef}
|
ref={labelRef}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { createPortal } from 'react-dom';
|
|||||||
import type { DefType } from '../../utils/strategyEditorShared';
|
import type { DefType } from '../../utils/strategyEditorShared';
|
||||||
import {
|
import {
|
||||||
handlePalettePointerDown,
|
handlePalettePointerDown,
|
||||||
|
handlePaletteTouchStart,
|
||||||
needsPointerPaletteDrag,
|
needsPointerPaletteDrag,
|
||||||
writePaletteHtmlDragData,
|
writePaletteHtmlDragData,
|
||||||
type PaletteDragPayload,
|
type PaletteDragPayload,
|
||||||
@@ -94,6 +95,16 @@ function SidewaysFilterChip({
|
|||||||
handlePalettePointerDown(e, payload, item.label);
|
handlePalettePointerDown(e, payload, item.label);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onTouchStart = (e: React.TouchEvent) => {
|
||||||
|
hideHint();
|
||||||
|
const payload: PaletteDragPayload = {
|
||||||
|
type: 'sidewaysFilter',
|
||||||
|
value: item.id,
|
||||||
|
label: item.label,
|
||||||
|
};
|
||||||
|
handlePaletteTouchStart(e, payload, item.label);
|
||||||
|
};
|
||||||
|
|
||||||
const handleClick = () => onSelect();
|
const handleClick = () => onSelect();
|
||||||
const handleDoubleClick = (e: React.MouseEvent) => {
|
const handleDoubleClick = (e: React.MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -113,6 +124,7 @@ function SidewaysFilterChip({
|
|||||||
className={`se-palette-card se-palette-card--range se-palette-card--range-${item.mode}${selected ? ' se-palette-card--selected' : ''}`}
|
className={`se-palette-card se-palette-card--range se-palette-card--range-${item.mode}${selected ? ' se-palette-card--selected' : ''}`}
|
||||||
onDragStart={onDragStart}
|
onDragStart={onDragStart}
|
||||||
onPointerDown={onPointerDown}
|
onPointerDown={onPointerDown}
|
||||||
|
onTouchStart={onTouchStart}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
onDoubleClick={handleDoubleClick}
|
onDoubleClick={handleDoubleClick}
|
||||||
onMouseEnter={e => showHint(e.currentTarget)}
|
onMouseEnter={e => showHint(e.currentTarget)}
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* Desktop(Tauri) — HTML5 DnD 대신 pointer + 전체화면 overlay 드래그
|
* Desktop(Tauri) 및 터치 기기 — HTML5 DnD 대신 pointer/touch + 전체화면 overlay 드래그
|
||||||
*/
|
*/
|
||||||
import { elementsUnderDragPoint } from './flowScreenProjection';
|
import { elementsUnderDragPoint } from './flowScreenProjection';
|
||||||
|
import {
|
||||||
|
bindWindowDrag,
|
||||||
|
isPrimaryPointerButton,
|
||||||
|
} from './pointerDrag';
|
||||||
import { isDesktop } from './platform';
|
import { isDesktop } from './platform';
|
||||||
|
|
||||||
export type PaletteDragPayload = Record<string, unknown> & {
|
export type PaletteDragPayload = Record<string, unknown> & {
|
||||||
@@ -40,15 +44,28 @@ const DRAG_THRESHOLD_PX = 6;
|
|||||||
|
|
||||||
let activePayload: PaletteDragPayload | null = null;
|
let activePayload: PaletteDragPayload | null = null;
|
||||||
let activePointerId: number | null = null;
|
let activePointerId: number | null = null;
|
||||||
|
let activeTouchId: number | null = null;
|
||||||
let overlayController: OverlayController | null = null;
|
let overlayController: OverlayController | null = null;
|
||||||
let overlayElement: HTMLElement | null = null;
|
let overlayElement: HTMLElement | null = null;
|
||||||
let listeners = new Set<PaletteDragListener>();
|
let listeners = new Set<PaletteDragListener>();
|
||||||
let dropHandler: PaletteDropHandler | null = null;
|
let dropHandler: PaletteDropHandler | null = null;
|
||||||
let moveRafId: number | null = null;
|
let moveRafId: number | null = null;
|
||||||
let pendingMove: { x: number; y: number } | null = null;
|
let pendingMove: { x: number; y: number } | null = null;
|
||||||
|
let unbindActiveDrag: (() => void) | null = null;
|
||||||
|
let pendingPointerListeners: (() => void) | null = null;
|
||||||
|
|
||||||
|
function hasCoarsePointer(): boolean {
|
||||||
|
if (typeof window === 'undefined') return false;
|
||||||
|
try {
|
||||||
|
return window.matchMedia('(pointer: coarse)').matches;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** HTML5 DnD 대신 pointer/touch overlay 드래그 사용 */
|
||||||
export function needsPointerPaletteDrag(): boolean {
|
export function needsPointerPaletteDrag(): boolean {
|
||||||
return isDesktop();
|
return isDesktop() || hasCoarsePointer();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function subscribePaletteDrag(listener: PaletteDragListener): () => void {
|
export function subscribePaletteDrag(listener: PaletteDragListener): () => void {
|
||||||
@@ -88,9 +105,36 @@ function dispatchEnd(xy: { x: number; y: number }, payload: PaletteDragPayload)
|
|||||||
emit(event);
|
emit(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function releasePointerCapture(pointerId: number | null) {
|
||||||
|
if (pointerId == null) return;
|
||||||
|
try {
|
||||||
|
overlayElement?.releasePointerCapture(pointerId);
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (document.body.hasPointerCapture(pointerId)) {
|
||||||
|
document.body.releasePointerCapture(pointerId);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearPendingPointerListeners() {
|
||||||
|
pendingPointerListeners?.();
|
||||||
|
pendingPointerListeners = null;
|
||||||
|
}
|
||||||
|
|
||||||
function cleanupDrag() {
|
function cleanupDrag() {
|
||||||
|
const pointerId = activePointerId;
|
||||||
|
unbindActiveDrag?.();
|
||||||
|
unbindActiveDrag = null;
|
||||||
|
clearPendingPointerListeners();
|
||||||
|
releasePointerCapture(pointerId);
|
||||||
activePayload = null;
|
activePayload = null;
|
||||||
activePointerId = null;
|
activePointerId = null;
|
||||||
|
activeTouchId = null;
|
||||||
pendingMove = null;
|
pendingMove = null;
|
||||||
if (moveRafId != null) {
|
if (moveRafId != null) {
|
||||||
cancelAnimationFrame(moveRafId);
|
cancelAnimationFrame(moveRafId);
|
||||||
@@ -98,6 +142,7 @@ function cleanupDrag() {
|
|||||||
}
|
}
|
||||||
document.body.classList.remove('se-palette-drag-active');
|
document.body.classList.remove('se-palette-drag-active');
|
||||||
overlayController?.unmount();
|
overlayController?.unmount();
|
||||||
|
overlayElement = null;
|
||||||
document.querySelectorAll('.se-palette-drag-ghost').forEach(el => el.remove());
|
document.querySelectorAll('.se-palette-drag-ghost').forEach(el => el.remove());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,6 +185,37 @@ export function completePaletteDragAt(clientX: number, clientY: number): boolean
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function eventMatchesActiveInput(ev: Event): boolean {
|
||||||
|
if (!activePayload || activePointerId == null) return false;
|
||||||
|
if (ev instanceof PointerEvent) return ev.pointerId === activePointerId;
|
||||||
|
if (ev instanceof TouchEvent) {
|
||||||
|
if (activeTouchId != null) {
|
||||||
|
const touches = ev.touches.length > 0 ? ev.touches : ev.changedTouches;
|
||||||
|
return Array.from(touches).some(t => t.identifier === activeTouchId);
|
||||||
|
}
|
||||||
|
return ev.touches.length <= 1;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindActiveDragWindowListeners(pointerId: number) {
|
||||||
|
unbindActiveDrag?.();
|
||||||
|
|
||||||
|
unbindActiveDrag = bindWindowDrag(
|
||||||
|
(xy, ev) => {
|
||||||
|
if (!activePayload || activePointerId !== pointerId) return;
|
||||||
|
if (!eventMatchesActiveInput(ev)) return;
|
||||||
|
notifyPaletteDragMove(xy.x, xy.y);
|
||||||
|
},
|
||||||
|
(xy, ev) => {
|
||||||
|
if (!activePayload || activePointerId !== pointerId) return;
|
||||||
|
if (!eventMatchesActiveInput(ev)) return;
|
||||||
|
finishDrag('end', xy);
|
||||||
|
},
|
||||||
|
{ preventTouchScroll: true },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function captureOnOverlay(pointerId: number) {
|
function captureOnOverlay(pointerId: number) {
|
||||||
try {
|
try {
|
||||||
overlayElement?.setPointerCapture(pointerId);
|
overlayElement?.setPointerCapture(pointerId);
|
||||||
@@ -159,12 +235,20 @@ function beginDrag(
|
|||||||
payload: PaletteDragPayload,
|
payload: PaletteDragPayload,
|
||||||
xy: { x: number; y: number },
|
xy: { x: number; y: number },
|
||||||
pointerId: number,
|
pointerId: number,
|
||||||
|
touchId: number | null = null,
|
||||||
) {
|
) {
|
||||||
|
if (activePayload) {
|
||||||
|
cleanupDrag();
|
||||||
|
}
|
||||||
|
|
||||||
activePayload = payload;
|
activePayload = payload;
|
||||||
activePointerId = pointerId;
|
activePointerId = pointerId;
|
||||||
|
activeTouchId = touchId;
|
||||||
document.body.classList.add('se-palette-drag-active');
|
document.body.classList.add('se-palette-drag-active');
|
||||||
|
|
||||||
|
bindActiveDragWindowListeners(pointerId);
|
||||||
overlayController?.mount({ payload, pointerId, x: xy.x, y: xy.y });
|
overlayController?.mount({ payload, pointerId, x: xy.x, y: xy.y });
|
||||||
|
|
||||||
requestAnimationFrame(() => captureOnOverlay(pointerId));
|
requestAnimationFrame(() => captureOnOverlay(pointerId));
|
||||||
|
|
||||||
emit({ phase: 'start', x: xy.x, y: xy.y, payload });
|
emit({ phase: 'start', x: xy.x, y: xy.y, payload });
|
||||||
@@ -178,47 +262,83 @@ export function bindPaletteDragOverlayElement(el: HTMLElement | null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function startPaletteDragGesture(
|
||||||
|
startX: number,
|
||||||
|
startY: number,
|
||||||
|
pointerId: number,
|
||||||
|
touchId: number | null,
|
||||||
|
payload: PaletteDragPayload,
|
||||||
|
): void {
|
||||||
|
if (activePayload) {
|
||||||
|
cleanupDrag();
|
||||||
|
}
|
||||||
|
|
||||||
|
clearPendingPointerListeners();
|
||||||
|
|
||||||
|
let dragStarted = false;
|
||||||
|
|
||||||
|
const unbind = bindWindowDrag(
|
||||||
|
(xy, ev) => {
|
||||||
|
if (dragStarted) return;
|
||||||
|
const dx = xy.x - startX;
|
||||||
|
const dy = xy.y - startY;
|
||||||
|
if (Math.hypot(dx, dy) < DRAG_THRESHOLD_PX) return;
|
||||||
|
dragStarted = true;
|
||||||
|
clearPendingPointerListeners();
|
||||||
|
if (ev.cancelable) ev.preventDefault();
|
||||||
|
beginDrag(payload, xy, pointerId, touchId);
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
if (dragStarted) return;
|
||||||
|
clearPendingPointerListeners();
|
||||||
|
},
|
||||||
|
{ preventTouchScroll: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
pendingPointerListeners = unbind;
|
||||||
|
}
|
||||||
|
|
||||||
export function handlePalettePointerDown(
|
export function handlePalettePointerDown(
|
||||||
e: React.PointerEvent,
|
e: React.PointerEvent,
|
||||||
payload: PaletteDragPayload,
|
payload: PaletteDragPayload,
|
||||||
_label: string,
|
_label: string,
|
||||||
): void {
|
): void {
|
||||||
if (!needsPointerPaletteDrag()) return;
|
if (!needsPointerPaletteDrag()) return;
|
||||||
if (e.button !== 0) return;
|
if (!isPrimaryPointerButton(e)) return;
|
||||||
if ((e.target as HTMLElement).closest('input, textarea, select, button')) return;
|
if ((e.target as HTMLElement).closest('input, textarea, select, button')) return;
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const startX = e.clientX;
|
startPaletteDragGesture(
|
||||||
const startY = e.clientY;
|
e.clientX,
|
||||||
const pointerId = e.pointerId;
|
e.clientY,
|
||||||
|
e.pointerId,
|
||||||
|
null,
|
||||||
|
payload,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const onPointerMove = (ev: PointerEvent) => {
|
/** PointerEvent 미지원 환경 — touchstart 전용 진입 */
|
||||||
if (ev.pointerId !== pointerId) return;
|
export function handlePaletteTouchStart(
|
||||||
const dx = ev.clientX - startX;
|
e: React.TouchEvent,
|
||||||
const dy = ev.clientY - startY;
|
payload: PaletteDragPayload,
|
||||||
if (Math.hypot(dx, dy) < DRAG_THRESHOLD_PX) return;
|
_label: string,
|
||||||
|
): void {
|
||||||
|
if (!needsPointerPaletteDrag()) return;
|
||||||
|
if (typeof window !== 'undefined' && 'PointerEvent' in window) return;
|
||||||
|
if (e.touches.length !== 1) return;
|
||||||
|
if ((e.target as HTMLElement).closest('input, textarea, select, button')) return;
|
||||||
|
|
||||||
window.removeEventListener('pointermove', onPointerMove);
|
e.preventDefault();
|
||||||
window.removeEventListener('pointerup', onPointerUp);
|
|
||||||
window.removeEventListener('pointercancel', onPointerCancel);
|
|
||||||
|
|
||||||
ev.preventDefault();
|
const touch = e.touches[0];
|
||||||
beginDrag(payload, { x: ev.clientX, y: ev.clientY }, pointerId);
|
startPaletteDragGesture(
|
||||||
};
|
touch.clientX,
|
||||||
|
touch.clientY,
|
||||||
const onPointerUp = (ev: PointerEvent) => {
|
touch.identifier,
|
||||||
if (ev.pointerId !== pointerId) return;
|
touch.identifier,
|
||||||
window.removeEventListener('pointermove', onPointerMove);
|
payload,
|
||||||
window.removeEventListener('pointerup', onPointerUp);
|
);
|
||||||
window.removeEventListener('pointercancel', onPointerCancel);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onPointerCancel = onPointerUp;
|
|
||||||
|
|
||||||
window.addEventListener('pointermove', onPointerMove, { passive: false });
|
|
||||||
window.addEventListener('pointerup', onPointerUp);
|
|
||||||
window.addEventListener('pointercancel', onPointerCancel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function writePaletteHtmlDragData(e: React.DragEvent, payload: PaletteDragPayload): void {
|
export function writePaletteHtmlDragData(e: React.DragEvent, payload: PaletteDragPayload): void {
|
||||||
|
|||||||
+15
-14
@@ -50,18 +50,9 @@ fi
|
|||||||
|
|
||||||
CONF="$WORK_TREE/desktop/src-tauri/tauri.conf.json"
|
CONF="$WORK_TREE/desktop/src-tauri/tauri.conf.json"
|
||||||
|
|
||||||
# CI patch bump (jenkins-desktop-pipeline 에서 선행 bump 시 DESKTOP_SKIP_VERSION_BUMP=1)
|
# ── 빌드 전 semver bump (기본: 항상 실행) ─────────────────────────────────────
|
||||||
if [[ "${DESKTOP_SKIP_VERSION_BUMP:-}" != "1" ]] \
|
# DESKTOP_NO_BUMP=1 로만 생략 (로컬 개발용)
|
||||||
&& [[ -z "${BUMP_VERSION:-}" ]] \
|
if [[ -n "${BUMP_VERSION:-}" ]]; then
|
||||||
&& [[ "${DESKTOP_BUMP_ON_CI:-1}" == "1" ]] \
|
|
||||||
&& [[ -n "${BUILD_NUMBER:-}${DESKTOP_AUTO_BUMP:-}" ]]; then
|
|
||||||
chmod +x "$WORK_TREE/scripts/bump-desktop-version-from-release.sh"
|
|
||||||
export WORK_TREE
|
|
||||||
BUMPED="$("$WORK_TREE/scripts/bump-desktop-version-from-release.sh")"
|
|
||||||
echo "Desktop CI version bump → $BUMPED"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "$BUMP_VERSION" ]]; then
|
|
||||||
node -e "
|
node -e "
|
||||||
const fs=require('fs'); const p=process.argv[1]; const b=process.argv[2];
|
const fs=require('fs'); const p=process.argv[1]; const b=process.argv[2];
|
||||||
const c=JSON.parse(fs.readFileSync(p,'utf8'));
|
const c=JSON.parse(fs.readFileSync(p,'utf8'));
|
||||||
@@ -72,12 +63,22 @@ else if(b==='minor') nv=[a,x+1,0].join('.');
|
|||||||
else if(b==='major') nv=[a+1,0,0].join('.');
|
else if(b==='major') nv=[a+1,0,0].join('.');
|
||||||
else nv=b;
|
else nv=b;
|
||||||
c.version=nv; fs.writeFileSync(p, JSON.stringify(c,null,2)+'\n');
|
c.version=nv; fs.writeFileSync(p, JSON.stringify(c,null,2)+'\n');
|
||||||
console.log('version', nv);
|
const pkgPath=p.replace('src-tauri/tauri.conf.json','package.json');
|
||||||
|
try{const pkg=JSON.parse(fs.readFileSync(pkgPath,'utf8'));pkg.version=nv;fs.writeFileSync(pkgPath,JSON.stringify(pkg,null,2)+'\n');}catch{}
|
||||||
|
console.log(nv);
|
||||||
" "$CONF" "$BUMP_VERSION"
|
" "$CONF" "$BUMP_VERSION"
|
||||||
|
echo "=== Desktop version (explicit): v$(node -p "require('$CONF').version") ==="
|
||||||
|
elif [[ "${DESKTOP_NO_BUMP:-}" != "1" ]]; then
|
||||||
|
chmod +x "$WORK_TREE/scripts/bump-desktop-version-before-build.sh"
|
||||||
|
BEFORE_VER="$("$WORK_TREE/scripts/read-desktop-version.sh")"
|
||||||
|
BUMPED_VER="$("$WORK_TREE/scripts/bump-desktop-version-before-build.sh")"
|
||||||
|
echo "=== Desktop version bump: v${BEFORE_VER} → v${BUMPED_VER} ==="
|
||||||
|
else
|
||||||
|
echo "=== Desktop version bump SKIPPED (DESKTOP_NO_BUMP=1) — v$(node -p "require('$CONF').version") ==="
|
||||||
fi
|
fi
|
||||||
|
|
||||||
VERSION="$(node -p "require('$CONF').version")"
|
VERSION="$(node -p "require('$CONF').version")"
|
||||||
echo "Version: $VERSION"
|
echo "=== Building GoldenChart Desktop v${VERSION} ==="
|
||||||
|
|
||||||
node -e "
|
node -e "
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|||||||
Executable
+50
@@ -0,0 +1,50 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# 빌드 직전 semver patch bump (필수) — 실패 시 exit 1
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
WORK_TREE="${WORK_TREE:-$ROOT}"
|
||||||
|
export WORK_TREE
|
||||||
|
export DESKTOP_UPDATE_BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://exdev.co.kr/desktop/updates}"
|
||||||
|
|
||||||
|
CONF="$WORK_TREE/desktop/src-tauri/tauri.conf.json"
|
||||||
|
CARGO="$WORK_TREE/desktop/src-tauri/Cargo.toml"
|
||||||
|
PKG="$WORK_TREE/desktop/package.json"
|
||||||
|
|
||||||
|
BEFORE="$("$ROOT/scripts/read-desktop-version.sh")"
|
||||||
|
echo "[bump-before-build] current v${BEFORE}" >&2
|
||||||
|
|
||||||
|
chmod +x "$ROOT/scripts/bump-desktop-version-from-release.sh"
|
||||||
|
NEW="$("$ROOT/scripts/bump-desktop-version-from-release.sh")"
|
||||||
|
|
||||||
|
if [[ -z "$NEW" || "$NEW" == "$BEFORE" ]]; then
|
||||||
|
echo "[bump-before-build] ERROR: bump failed — still v${BEFORE}" >&2
|
||||||
|
echo " remote latest: ${DESKTOP_UPDATE_BASE_URL}/latest.json 확인" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# semver strictly increased
|
||||||
|
if ! node -e "
|
||||||
|
const parse = v => String(v||'0').replace(/^v/i,'').split('.').map(n=>parseInt(n,10)||0);
|
||||||
|
const a=parse(process.argv[1]), b=parse(process.argv[2]);
|
||||||
|
for(let i=0;i<3;i++){const d=(a[i]||0)-(b[i]||0);if(d){process.exit(d>0?0:1)}}
|
||||||
|
process.exit(1);
|
||||||
|
" "$NEW" "$BEFORE"; then
|
||||||
|
echo "[bump-before-build] ERROR: v${NEW} is not greater than v${BEFORE}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Cargo.toml (Tauri 일부 경로에서 참조)
|
||||||
|
if [[ -f "$CARGO" ]]; then
|
||||||
|
node -e "
|
||||||
|
const fs=require('fs');
|
||||||
|
const p=process.argv[1], v=process.argv[2];
|
||||||
|
let t=fs.readFileSync(p,'utf8');
|
||||||
|
if(!/^version\\s*=\\s*\"/m.test(t)){process.exit(0)}
|
||||||
|
t=t.replace(/^version\\s*=\\s*\"[^\"]+\"/m, 'version = \"'+v+'\"');
|
||||||
|
fs.writeFileSync(p,t);
|
||||||
|
" "$CARGO" "$NEW"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[bump-before-build] v${BEFORE} → v${NEW}" >&2
|
||||||
|
echo "$NEW"
|
||||||
@@ -18,7 +18,6 @@ log "WORK_TREE=$WORK_TREE"
|
|||||||
|
|
||||||
cd "$WORK_TREE"
|
cd "$WORK_TREE"
|
||||||
|
|
||||||
# Git 동기화 (checkout → git 에 기록된 semver 그대로 빌드)
|
|
||||||
GIT_DIR="${GIT_DIR:-/Volumes/ADATA/git/goldenChart.git}"
|
GIT_DIR="${GIT_DIR:-/Volumes/ADATA/git/goldenChart.git}"
|
||||||
if [[ -d "$GIT_DIR" ]]; then
|
if [[ -d "$GIT_DIR" ]]; then
|
||||||
if git --git-dir="$GIT_DIR" remote get-url origin >/dev/null 2>&1; then
|
if git --git-dir="$GIT_DIR" remote get-url origin >/dev/null 2>&1; then
|
||||||
@@ -27,15 +26,20 @@ if [[ -d "$GIT_DIR" ]]; then
|
|||||||
git --git-dir="$GIT_DIR" --work-tree="$WORK_TREE" checkout -f main 2>/dev/null || true
|
git --git-dir="$GIT_DIR" --work-tree="$WORK_TREE" checkout -f main 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Jenkins SCM workspace 가 있으면 scripts 를 WORK_TREE 로 동기 (구 스크립트 방지)
|
||||||
|
if [[ -n "${WORKSPACE:-}" && -d "${WORKSPACE}/scripts" && "${WORKSPACE}" != "$WORK_TREE" ]]; then
|
||||||
|
log "Sync scripts: ${WORKSPACE}/scripts → ${WORK_TREE}/scripts"
|
||||||
|
rsync -a "${WORKSPACE}/scripts/" "${WORK_TREE}/scripts/" 2>/dev/null \
|
||||||
|
|| cp -f "${WORKSPACE}/scripts/"*.sh "${WORK_TREE}/scripts/" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
chmod +x "$WORK_TREE/scripts/"*.sh 2>/dev/null || true
|
chmod +x "$WORK_TREE/scripts/"*.sh 2>/dev/null || true
|
||||||
|
|
||||||
# 의존성 (최초 1회 — 이미 있으면 빠르게 통과)
|
|
||||||
if ! command -v cargo-xwin >/dev/null 2>&1 || ! command -v rustc >/dev/null 2>&1; then
|
if ! command -v cargo-xwin >/dev/null 2>&1 || ! command -v rustc >/dev/null 2>&1; then
|
||||||
log "Desktop build deps 설치..."
|
log "Desktop build deps 설치..."
|
||||||
"$WORK_TREE/scripts/install-desktop-build-deps.sh"
|
"$WORK_TREE/scripts/install-desktop-build-deps.sh"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# updater pubkey (repo에 desktop/updater.pub 있으면 patch만)
|
|
||||||
if [[ -f "$WORK_TREE/desktop/updater.pub" ]]; then
|
if [[ -f "$WORK_TREE/desktop/updater.pub" ]]; then
|
||||||
"$WORK_TREE/scripts/patch-tauri-updater-pubkey.sh"
|
"$WORK_TREE/scripts/patch-tauri-updater-pubkey.sh"
|
||||||
elif [[ -f "${TAURI_SIGNER_KEY:-$HOME/.tauri/goldenchart.key}" ]]; then
|
elif [[ -f "${TAURI_SIGNER_KEY:-$HOME/.tauri/goldenchart.key}" ]]; then
|
||||||
@@ -54,47 +58,33 @@ export BUILD_NUMBER="${BUILD_NUMBER:-}"
|
|||||||
export DESKTOP_UPDATE_BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://exdev.co.kr/desktop/updates}"
|
export DESKTOP_UPDATE_BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://exdev.co.kr/desktop/updates}"
|
||||||
|
|
||||||
GIT_VER="$(node -p "require('$WORK_TREE/desktop/src-tauri/tauri.conf.json').version" 2>/dev/null || echo '?')"
|
GIT_VER="$(node -p "require('$WORK_TREE/desktop/src-tauri/tauri.conf.json').version" 2>/dev/null || echo '?')"
|
||||||
log "Desktop git version: v${GIT_VER}"
|
log "Desktop git tauri.conf: v${GIT_VER}"
|
||||||
|
log "semver bump → build-desktop.sh 시작 시 자동 실행 (bump-desktop-version-before-build.sh)"
|
||||||
# 매 Jenkins 빌드마다 published latest + git 중 max → +1 (동일 버전 재빌드 방지)
|
|
||||||
if [[ -z "${BUMP_VERSION:-}" ]] && [[ "${DESKTOP_BUMP_ON_CI:-1}" == "1" ]]; then
|
|
||||||
log "CI Desktop version bump (published latest + git → +1 patch)..."
|
|
||||||
BUMPED="$("$WORK_TREE/scripts/bump-desktop-version-from-release.sh")"
|
|
||||||
export DESKTOP_SKIP_VERSION_BUMP=1
|
|
||||||
log "Desktop build version → v${BUMPED}"
|
|
||||||
else
|
|
||||||
export DESKTOP_SKIP_VERSION_BUMP=1
|
|
||||||
BUMPED="$GIT_VER"
|
|
||||||
log "Desktop version bump skipped — using git v${BUMPED}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
"$WORK_TREE/scripts/build-desktop.sh" "${BUILD_ARGS[@]}"
|
"$WORK_TREE/scripts/build-desktop.sh" "${BUILD_ARGS[@]}"
|
||||||
|
|
||||||
|
BUILT_VER="$(node -p "require('$WORK_TREE/dist-desktop/build-info.json').version" 2>/dev/null || echo '?')"
|
||||||
|
log "Built version: v${BUILT_VER}"
|
||||||
|
|
||||||
WORK_TREE="$WORK_TREE" \
|
WORK_TREE="$WORK_TREE" \
|
||||||
ARTIFACT_DIR="$WORK_TREE/dist-desktop" \
|
ARTIFACT_DIR="$WORK_TREE/dist-desktop" \
|
||||||
DESKTOP_UPDATE_BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://exdev.co.kr/desktop/updates}" \
|
DESKTOP_UPDATE_BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://exdev.co.kr/desktop/updates}" \
|
||||||
TAURI_SIGNER_KEY="${TAURI_SIGNER_KEY:-$HOME/.tauri/goldenchart.key}" \
|
TAURI_SIGNER_KEY="${TAURI_SIGNER_KEY:-$HOME/.tauri/goldenchart.key}" \
|
||||||
DESKTOP_RELEASE_NOTES="${DESKTOP_RELEASE_NOTES:-Build ${BUILD_NUMBER:-local}}" \
|
DESKTOP_RELEASE_NOTES="${DESKTOP_RELEASE_NOTES:-Build ${BUILD_NUMBER:-local} v${BUILT_VER}}" \
|
||||||
"$WORK_TREE/scripts/publish-desktop-update.sh"
|
"$WORK_TREE/scripts/publish-desktop-update.sh"
|
||||||
|
|
||||||
# latest.json·설치 파일 nginx 즉시 반영 (볼륨 마운트)
|
|
||||||
if [[ -x "$WORK_TREE/scripts/reload-frontend-desktop-static.sh" ]]; then
|
if [[ -x "$WORK_TREE/scripts/reload-frontend-desktop-static.sh" ]]; then
|
||||||
WORK_TREE="$WORK_TREE" "$WORK_TREE/scripts/reload-frontend-desktop-static.sh" || true
|
WORK_TREE="$WORK_TREE" "$WORK_TREE/scripts/reload-frontend-desktop-static.sh" || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Jenkins artifact archive 경로
|
|
||||||
if [[ -d "$WORK_TREE/dist-desktop" ]]; then
|
if [[ -d "$WORK_TREE/dist-desktop" ]]; then
|
||||||
log "Artifacts ready: $WORK_TREE/dist-desktop"
|
log "Artifacts ready: $WORK_TREE/dist-desktop"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 웹 frontend Docker 재배포 (latest.json static 포함) — 선택
|
|
||||||
if [[ "${DESKTOP_DEPLOY_WEB:-0}" == "1" ]] && [[ -x "${DEPLOY_SCRIPT:-/Volumes/ADATA/git/deploy.sh}" ]]; then
|
if [[ "${DESKTOP_DEPLOY_WEB:-0}" == "1" ]] && [[ -x "${DEPLOY_SCRIPT:-/Volumes/ADATA/git/deploy.sh}" ]]; then
|
||||||
log "Trigger web deploy (frontend only)..."
|
log "Trigger web deploy (frontend only)..."
|
||||||
SERVICE_NAME=frontend "${DEPLOY_SCRIPT:-/Volumes/ADATA/git/deploy.sh}"
|
SERVICE_NAME=frontend "${DEPLOY_SCRIPT:-/Volumes/ADATA/git/deploy.sh}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "Pipeline complete."
|
log "Pipeline complete — v${BUILT_VER}"
|
||||||
log "Published target: ${DESKTOP_UPDATE_BASE_URL:-https://exdev.co.kr/desktop/updates}/latest.json"
|
log "Published: ${DESKTOP_UPDATE_BASE_URL}/latest.json"
|
||||||
if [[ -f "$WORK_TREE/dist-desktop/build-info.json" ]]; then
|
|
||||||
log "build-info: $(cat "$WORK_TREE/dist-desktop/build-info.json" 2>/dev/null | tr -d '\n' | head -c 200)"
|
|
||||||
fi
|
|
||||||
|
|||||||
@@ -32,6 +32,29 @@ if [[ -f "$BUILD_INFO" ]]; then
|
|||||||
fi
|
fi
|
||||||
NOTES="${DESKTOP_RELEASE_NOTES:-GoldenChart Desktop $VERSION}"
|
NOTES="${DESKTOP_RELEASE_NOTES:-GoldenChart Desktop $VERSION}"
|
||||||
|
|
||||||
|
# 동일 버전 재배포 방지 (updater 가 '이미 최신' 으로 보이는 원인)
|
||||||
|
PUBLISHED_LATEST_URL="${DESKTOP_PUBLISHED_LATEST_URL:-${BASE_URL}/latest.json}"
|
||||||
|
REMOTE_PUB_VER=""
|
||||||
|
if REMOTE_JSON="$(curl -sf --connect-timeout 5 --max-time 12 "$PUBLISHED_LATEST_URL" 2>/dev/null)"; then
|
||||||
|
REMOTE_PUB_VER="$(printf '%s' "$REMOTE_JSON" | node -e "
|
||||||
|
let d=''; process.stdin.on('data',c=>d+=c);
|
||||||
|
process.stdin.on('end',()=>{ try { console.log(JSON.parse(d).version||''); } catch { console.log(''); } });
|
||||||
|
" 2>/dev/null || true)"
|
||||||
|
fi
|
||||||
|
if [[ -n "$REMOTE_PUB_VER" ]]; then
|
||||||
|
if ! node -e "
|
||||||
|
const parse = v => String(v||'0').replace(/^v/i,'').split('.').map(n=>parseInt(n,10)||0);
|
||||||
|
const a=parse(process.argv[1]), b=parse(process.argv[2]);
|
||||||
|
for(let i=0;i<3;i++){const d=(a[i]||0)-(b[i]||0);if(d){process.exit(d>0?0:1)}}
|
||||||
|
process.exit(1);
|
||||||
|
" "$VERSION" "$REMOTE_PUB_VER"; then
|
||||||
|
log "ERROR: publish v${VERSION} <= remote v${REMOTE_PUB_VER} — bump 가 실행되지 않았습니다." >&2
|
||||||
|
log " build-desktop.sh 가 bump-desktop-version-before-build.sh 를 호출하는지 확인" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
log "remote v${REMOTE_PUB_VER} → publish v${VERSION} OK"
|
||||||
|
fi
|
||||||
|
|
||||||
log "=== Publish Desktop v$VERSION ==="
|
log "=== Publish Desktop v$VERSION ==="
|
||||||
log "Work tree: $WORK_TREE"
|
log "Work tree: $WORK_TREE"
|
||||||
log "Updates src: $UPDATES_SRC"
|
log "Updates src: $UPDATES_SRC"
|
||||||
|
|||||||
@@ -54,12 +54,9 @@ export PATH="/opt/homebrew/opt/llvm/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/us
|
|||||||
export WORK_TREE="${WORK_TREE}"
|
export WORK_TREE="${WORK_TREE}"
|
||||||
export GIT_DIR="${GIT_DIR}"
|
export GIT_DIR="${GIT_DIR}"
|
||||||
export BUILD_NUMBER="\${BUILD_NUMBER}"
|
export BUILD_NUMBER="\${BUILD_NUMBER}"
|
||||||
export DESKTOP_AUTO_BUMP=1
|
|
||||||
export DESKTOP_BUMP_ON_CI=1
|
|
||||||
export DESKTOP_UPDATE_BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://exdev.co.kr/desktop/updates}"
|
export DESKTOP_UPDATE_BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://exdev.co.kr/desktop/updates}"
|
||||||
export TAURI_SIGNER_KEY="\${TAURI_SIGNER_KEY:-\$HOME/.tauri/goldenchart.key}"
|
export TAURI_SIGNER_KEY="\${TAURI_SIGNER_KEY:-\$HOME/.tauri/goldenchart.key}"
|
||||||
export DESKTOP_DEPLOY_WEB=0
|
export DESKTOP_DEPLOY_WEB=0
|
||||||
export DESKTOP_UPDATE_BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://exdev.co.kr/desktop/updates}"
|
|
||||||
"${WORK_TREE}/scripts/jenkins-desktop-pipeline.sh"
|
"${WORK_TREE}/scripts/jenkins-desktop-pipeline.sh"
|
||||||
</command>
|
</command>
|
||||||
</hudson.tasks.Shell>
|
</hudson.tasks.Shell>
|
||||||
|
|||||||
Reference in New Issue
Block a user