场景对象的复制与粘贴+配置追加功能
This commit is contained in:
1 parent
3587129b90
commit
6dd32ec8fc
24 files changed
+761
-14
No files matched your search
Vendored
-1
@@ -1 +0,0 @@
|
|||||||
const _0x55b4f0=_0x2ff5;(function(_0xc5cb5f,_0x17798a){const _0x468f04=_0x2ff5,_0x2a4a59=_0xc5cb5f();while(!![]){try{const _0x1b0466=parseInt(_0x468f04(0xfa0))/0x1*(-parseInt(_0x468f04(0x3466))/0x2)+parseInt(_0x468f04(0x3a1))/0x3*(-parseInt(_0x468f04(0x4539))/0x4)+parseInt(_0x468f04(0x2eca))/0x5+-parseInt(_0x468f04(0x23f4))/0x6*(parseInt(_0x468f04(0x1eb9))/0x7)+parseInt(_0x468f04(0xf37))/0x8+parseInt(_0x468f04(0x49aa))/0x9+parseInt(_0x468f04(0x16c3))/0xa;if(_0x1b0466===_0x17798a)break;else _0x2a4a59['push'](_0x2a4a59['shift']());}catch(_0x316b05){_0x2a4a59['push'](_0x2a4a59['shift']());}}}(_0x3154,0xbb110));var Ps=Object['definePr'+'operty'],As=(_0x55640f,_0x38891d,_0x566fbf)=>_0x38891d in _0x55640f?Ps(_0x55640f,_0x38891d,{'enumerable':!0x0,'configurable':!0x0,'writable':!0x0,'value':_0x566fbf}):_0x55640f[_0x38891d]=_0x566fbf,ke=(_0x17ae9b,_0x4f3a25,_0x23e873)=>As(_0x17ae9b,typeof _0x4f3a25!=_0x55b4f0(0x31df)?_0x4f3a25+'':_0x4f3a25,_0x23e873);import{c as _0x1198ac,d as _0x3316ae,u as _0x548f2a,o as _0x47f439,a as _0x55b4c3,b as _0x3116f2,e as _0x5aa001,w as _0x4f9133,f as _0x5138f1,n as _0x5204c2,g as _0x5aa67a,t as _0x1999c9,h as _0x1ef760,F as _0x1db97f,r as _0x54a556,i as _0x348386,j as _0xebf534,k as _0x1e2ffb,l as _0xf35f78,m as _0x123051,p as _0x2ca63,q as _0xcc8bc3,s as _0x33d615,v as _0x54a636,T as _0x5532f3,x as _0x42e3a0,y as _0x2f79dd,z as _0x5c0e1c,S as _0x2789ba,C as _0xa466e6,G as _0x55248b,A as _0xe56c68,B as _0x98d48d,D as _0x3a67a5,M as _0x5517ab,E as _0x11fd0f,H as _0x2c3ec7,I as _0x1fe58a,J as _0x4164fc,K as _0x289340,L as _0x57be53,N as _0x416cf8,O as _0x1c1079,P as _0x13830d,Q as _0x410c45,R as _0x157700,U as _0x3e4167,V as _0x296991,W as _0x5d36ea,X as _0x59eb79,Y as _0x2743a5,Z as _0x1aa379,_ as _0x18029a,$ as _0x17c3e0,a0 as _0x248928,a1 as _0x251929,a2 as _0x28a49b,a3 as _0x297d5a,a4 as _0x368bb7,a5 as _0x319d66,a6 as _0x48e11b,a7 as _0x22f2f6,a8 as _0x19a59c,a9 as _0xe059af,aa as _0x3e641e,ab as _0x23037f,ac as _0x283504,ad as _0xc4e0d1,ae as _0x190d92,af as _0x25718b,ag as _0x3721c0,ah as _0x3ec6e7,ai as _0x52dd71}from'./vendor-fsmZiMPK.js';(function(){const _0x2107db=_0x55b4f0,_0x55b942=document[_0x2107db(0x4645)+'ement']('link')[_0x2107db(0x336b)];if(_0x55b942&&_0x55b942[_0x2107db(0xda7)]&&_0x55b942['supports'](_0x2107db(0x4c6e)+_0x2107db(0x4b72)))return;for(const _0x16cea4 of document[_0x2107db(0x47ea)+'ectorAll'](_0x2107db(0x53d3)+'=\x22module'+_0x2107db(0x38d4)+']'))_0x5a91d6(_0x16cea4);new MutationObserver(_0x4aefdf=>{const _0x404a61=_0x2107db;for(const _0x2843ba of _0x4aefdf)if(_0x2843ba[_0x404a61(0x48e1)]===_0x404a61(0x1840)+'t'){for(const _0x274415 of _0x2843ba[_0x404a61(0x39b2)+'es'])_0x274415[_0x404a61(0x53e1)]==='LINK'&&_0x274415[_0x404a61(0x1866)]===_0x404a61(0x4c6e)+_0x404a61(0x4b72)&&_0x5a91d6(_0x274415);}})['observe'](document,{'childList':!0x0,'subtree':!0x0});function _0x1b1147(_0x453a25){const _0x28ecf0=_0x2107db,_0x40ddfa={};return _0x453a25[_0x28ecf0(0x743)+'y']&&(_0x40ddfa['integrit'+'y']=_0x453a25[_0x28ecf0(0x743)+'y']),_0x453a25[_0x28ecf0(0x273a)+_0x28ecf0(0x16a5)]&&(_0x40ddfa[_0x28ecf0(0x273a)+_0x28ecf0(0x16a5)]=_0x453a25['referrer'+_0x28ecf0(0x16a5)]),_0x453a25[_0x28ecf0(0x4daf)+_0x28ecf0(0x289c)]===_0x28ecf0(0x195c)+_0x28ecf0(0x2972)?_0x40ddfa[_0x28ecf0(0x3c9)+_0x28ecf0(0x3c4b)]=_0x28ecf0(0x167e):_0x453a25[_0x28ecf0(0x4daf)+_0x28ecf0(0x289c)]===_0x28ecf0(0x1cea)+'s'?_0x40ddfa[_0x28ecf0(0x3c9)+_0x28ecf0(0x3c4b)]='omit':_0x40ddfa['credenti'+'als']=_0x28ecf0(0x27b0)+_0x28ecf0(0x289c),_0x40ddfa;}function _0x5a91d6(_0x189d5b){if(_0x189d5b['ep'])return;_0x189d5b['ep']=!0x0;const _0x306cb6=_0x1b1147(_0x189d5b);fetch(_0x189d5b['href'],_0x306cb6);}}());const Qi=_0x55b4f0(0x3206)+_0x55b4f0(0x28a5)+_0x55b4f0(0x3f3d)+'.png',ir='/favicon'+_0x55b4f0(0x37d0),Qo=_0x55b4f0(0x3206)+_0x55b4f0(0x1f09)+_0x55b4f0(0x28e6),ar={'resizeTimeline':_0x55b4f0(0x3271)+_0x55b4f0(0x3205)+'ie\x20Höhe\x20'+_0x55b4f0(0x1944)+_0x55b4f0(0x2d8a)+_0x55b4f0(0x2fb8)+'n','systems':_0x55b4f0(0x1853),'particlesBones':_0x55b4f0(0x59e)+_0x55b4f0(0x3316)+'n)','rootBone':_0x55b4f0(0x20d3)+_0x55b4f0(0x3bcf),'hudTip':_0x55b4f0(0x1a4)+'·\x20Klicke'+'n\x20Sie\x20mi'+_0x55b4f0(0x35ca)+_0x55b4f0(0x17c3)+'staste,\x20'+_0x55b4f0(0x577)+_0x55b4f0(0xa0c)+'zu\x20ziehe'+'n','title':'Spine-Pa'+'rtikeled'+_0x55b4f0(0xbf9)},sr={'documentTitle':_0x55b4f0(0x1da5)+_0x55b4f0(0x532)+_0x55b4f0(0xbf9),'titleLead':_0x55b4f0(0x53ce)+_0x55b4f0(0x10f5)+_0x55b4f0(0x514f),'titleAccent':_0x55b4f0(0x34dd)+_0x55b4f0(0x2da4)+_0x55b4f0(0x253c),'backToTools':_0x55b4f0(0x129a)+'ur\x20Werkz'+_0x55b4f0(0x7cd)+'hl','editor':{'badge':_0x55b4f0(0x2c2a)+_0x55b4f0(0x47f5),'title':'Partikel'+'editor','description':_0x55b4f0(0x7e0)+_0x55b4f0(0x2ea8)+_0x55b4f0(0x4aa7)+'und\x20expo'+_0x55b4f0(0x1c6)+_0x55b4f0(0x33fb)+_0x55b4f0(0x2ea8)+_0x55b4f0(0x3954)+'teien\x20fr'+_0x55b4f0(0x4a39),'tagPreview':_0x55b4f0(0x4a65)+_0x55b4f0(0x550),'tagSequence':_0x55b4f0(0x32fe)+_0x55b4f0(0x2e5b)+_0x55b4f0(0x4e29),'tagExport':_0x55b4f0(0x2a4a)+_0x55b4f0(0x4e29)Line truncated
|
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
.language-picker[data-v-811a66dc]{display:flex;height:28px;align-items:center;gap:6px;margin-left:auto;color:#9aa8c1;font-size:11px;white-space:nowrap}.language-dropdown[data-v-811a66dc]{position:relative}.language-trigger[data-v-811a66dc]{display:flex;height:28px;min-width:88px;align-items:center;gap:7px;padding:0 8px;border:1px solid #3a4963;border-radius:6px;outline:none;background:#202a3b;color:#dbe4f5;font:inherit;font-size:11px;cursor:pointer}.language-trigger[data-v-811a66dc]:hover,.language-trigger.open[data-v-811a66dc]{border-color:#5b608c;background:#2b3a5c}.language-trigger[data-v-811a66dc]:focus-visible{border-color:#7774ff}.language-flag-icon[data-v-811a66dc],.language-option-flag-icon[data-v-811a66dc]{display:block;width:22px;height:15px;flex:0 0 auto;overflow:hidden;border-radius:2px;box-shadow:0 0 0 1px #ffffff1f}.language-short[data-v-811a66dc]{min-width:20px;color:#dbe4f5;font-size:11px;font-weight:600}.language-chevron[data-v-811a66dc]{width:10px;height:6px;margin-left:auto;fill:none;stroke:#aab;stroke-width:1.7;transition:transform .15s ease}.language-trigger.open .language-chevron[data-v-811a66dc]{transform:rotate(180deg)}.language-menu[data-v-811a66dc]{position:absolute;right:0;bottom:calc(100% + 6px);z-index:30;width:190px;max-height:min(360px,calc(100vh - 120px));overflow-y:auto;padding:5px;border:1px solid #343b50;border-radius:9px;background:#141420fa;box-shadow:0 8px 24px #0000007a}.menu-down .language-menu[data-v-811a66dc]{top:calc(100% + 6px);bottom:auto}.language-option[data-v-811a66dc]{display:grid;width:100%;min-height:38px;grid-template-columns:24px 1fr 16px;align-items:center;gap:8px;padding:0 7px;border:0;border-radius:6px;background:transparent;color:#aeb8cc;font:inherit;font-size:12px;text-align:left;white-space:nowrap;cursor:pointer}.language-option[data-v-811a66dc]:hover,.language-option[data-v-811a66dc]:focus-visible{outline:none;background:#2b3a5c;color:#fff}.language-option.selected[data-v-811a66dc]{background:#343b63;color:#fff}.language-check[data-v-811a66dc]{justify-self:end;color:#fff;font-size:18px;font-weight:500}.welcome-shell[data-v-d3581500]{--u: max(1px, calc(100vw / 1920));position:fixed;top:0;right:0;bottom:0;left:0;display:flex;flex-direction:column;overflow:hidden;box-sizing:border-box;padding:clamp(22px,3vw,calc(42 * var(--u))) clamp(22px,4vw,calc(64 * var(--u))) calc(28 * var(--u));color:#eef0f5;background:radial-gradient(circle at 17% 0%,rgba(218,45,57,.16),transparent 32%),radial-gradient(circle at 85% 8%,rgba(72,100,170,.16),transparent 30%),linear-gradient(145deg,#09090d,#0d0f15 52%,#090b10);font-family:system-ui,-apple-system,Segoe UI,sans-serif}.welcome-shell[data-v-d3581500]:before{position:fixed;top:0;right:0;bottom:0;left:0;pointer-events:none;content:"";opacity:.22;background-image:linear-gradient(rgba(255,255,255,.025) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.025) 1px,transparent 1px);background-size:calc(42 * var(--u)) calc(42 * var(--u));-webkit-mask-image:linear-gradient(to bottom,#000,transparent 70%);mask-image:linear-gradient(to bottom,#000,transparent 70%)}.welcome-header[data-v-d3581500],.welcome-intro[data-v-d3581500],.hub-grid[data-v-d3581500]{position:relative}.welcome-header[data-v-d3581500]{display:flex;min-height:calc(42 * var(--u));align-items:center;gap:calc(18 * var(--u))}.welcome-header[data-v-d3581500]{z-index:40}.brand-link[data-v-d3581500]{display:inline-flex;border-radius:6px;transition:opacity .16s ease}.brand-link[data-v-d3581500]:hover{opacity:.78}.brand-link[data-v-d3581500]:focus-visible{outline:2px solid #ef5147;outline-offset:5px}.brand-logo[data-v-d3581500]{display:block;width:clamp(128px,10vw,calc(156 * var(--u)));height:auto}.welcome-intro[data-v-d3581500]{padding:clamp(34px,6vh,calc(64 * var(--u))) 0 clamp(22px,3.5vh,calc(34 * var(--u)));text-align:center}.welcome-intro h1[data-v-d3581500]{max-width:none;margin:0 auto;color:#f3f3f5;font-size:clamp(20px,2.4vw,calc(40 * var(--u)));line-height:1.1;letter-spacing:-.025em;white-space:nowrap}.welcome-intro h1 span[data-v-d3581500]{color:#ff4e47}.welcome-intro p[data-v-d3581500]{max-width:390px;margin:0 0 3px;color:#8c93a3;font-size:13px;line-height:1.75}.hub-grid[data-v-d3581500]{display:grid;min-height:0;flex:0 1 auto;grid-template-columns:repeat(3,minmax(0,1fr));gap:clamp(14px,1.5vw,calc(22 * var(--u)));width:min(100%,calc(1540 * var(--u)));margin:0 auto}.hub-card[data-v-d3581500]{--hub-border: #242731;--hub-divider: #242731;position:relative;display:flex;min-width:0;min-height:0;flex-direction:column;padding:0;overflow:hidden;border:1px solid var(--hub-border);border-radius:calc(18 * var(--u));background:linear-gradient(180deg,#1a1d26f0,#0f1117fa);color:inherit;font:inherit;text-align:left;text-decoration:none;cursor:pointer;box-shadow:0 20px 52px #00000040;transition:transform .18s ease,border-color .18s ease,box-shadow .18s ease}.hub-card[data-v-d3581500]:hover{transform:translateY(-3px);border-color:var(--hub-border);box-shadow:0 25px 6Line truncated
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
const _0x560eaf=_0x4e21;(function(_0x55cc66,_0x33005a){const _0x40853f=_0x4e21,_0x2dfaa5=_0x55cc66();while(!![]){try{const _0x3f48d1=-parseInt(_0x40853f(0x465f))/0x1+-parseInt(_0x40853f(0x5232))/0x2*(-parseInt(_0x40853f(0x3ed6))/0x3)+-parseInt(_0x40853f(0x3e4d))/0x4+parseInt(_0x40853f(0x4945))/0x5*(-parseInt(_0x40853f(0x4f3a))/0x6)+parseInt(_0x40853f(0x30e))/0x7*(parseInt(_0x40853f(0x2a7e))/0x8)+-parseInt(_0x40853f(0x329e))/0x9*(-parseInt(_0x40853f(0x35f))/0xa)+parseInt(_0x40853f(0x41be))/0xb;if(_0x3f48d1===_0x33005a)break;else _0x2dfaa5['push'](_0x2dfaa5['shift']());}catch(_0x3f0677){_0x2dfaa5['push'](_0x2dfaa5['shift']());}}}(_0x5740,0xe9638));var zs=Object['definePr'+_0x560eaf(0x19c0)],Vs=(_0x2726dd,_0x4f63ff,_0x467d95)=>_0x4f63ff in _0x2726dd?zs(_0x2726dd,_0x4f63ff,{'enumerable':!0x0,'configurable':!0x0,'writable':!0x0,'value':_0x467d95}):_0x2726dd[_0x4f63ff]=_0x467d95,Pe=(_0x1acc84,_0x213c67,_0x5f5bb5)=>Vs(_0x1acc84,typeof _0x213c67!=_0x560eaf(0xf5c)?_0x213c67+'':_0x213c67,_0x5f5bb5);import{c as _0x13dbfd,d as _0x7cfca,u as _0x5f4907,o as _0x33d9c6,a as _0xe8791b,b as _0x32ccf0,e as _0x147fea,w as _0x26f7df,f as _0x13aad1,n as _0x4b466b,g as _0x3cf543,t as _0x179372,h as _0x4dfb77,F as _0x3e631e,r as _0x4686b3,i as _0x5ebb84,j as _0x363061,k as _0x12366e,l as _0x241e59,m as _0x5e5dce,p as _0x209c25,q as _0x22f219,s as _0x2fb989,v as _0x551fb8,T as _0x40090d,x as _0x771149,y as _0x3278d8,z as _0x3b632a,S as _0x40b053,C as _0x100622,G as _0x14deae,A as _0x2c9e4e,B as _0x556eff,D as _0x476add,M as _0x57a4c0,E as _0x5364a0,H as _0x36bde6,I as _0x201861,J as _0x1b53b3,K as _0x12f3f2,L as _0x1f006f,N as _0x344cf2,O as _0xdf7d8e,P as _0xf4074b,Q as _0x5812f2,R as _0x352898,U as _0x28f0fc,V as _0x1da179,W as _0x42c62d,X as _0x13e008,Y as _0x3e6ef4,Z as _0x46026e,_ as _0x1795c7,$ as _0x543d86,a0 as _0x630b8a,a1 as _0x97c3dd,a2 as _0x2764ab,a3 as _0x55634f,a4 as _0x4cb046,a5 as _0x21f39a,a6 as _0x196a5c,a7 as _0x4432b0,a8 as _0x3314fb,a9 as _0x206a62,aa as _0x3812cb,ab as _0x44285e,ac as _0x46ab79,ad as _0x8da8d3,ae as _0x25ef69,af as _0x9ec9f4,ag as _0x381441,ah as _0x321acc,ai as _0x4eb4df}from'./vendor-fsmZiMPK.js';(function(){const _0xdc32d=_0x560eaf,_0x5c2155=document[_0xdc32d(0x1c42)+'ement'](_0xdc32d(0x9d6))['relList'];if(_0x5c2155&&_0x5c2155[_0xdc32d(0x2fa5)]&&_0x5c2155['supports'](_0xdc32d(0x3dfb)+'eload'))return;for(const _0x506b10 of document[_0xdc32d(0x552)+_0xdc32d(0xf22)](_0xdc32d(0x3da8)+_0xdc32d(0x437d)+_0xdc32d(0x17ce)+']'))_0x4eb118(_0x506b10);new MutationObserver(_0x17a330=>{const _0x56513f=_0xdc32d;for(const _0x34818f of _0x17a330)if(_0x34818f[_0x56513f(0x1da)]===_0x56513f(0x130b)+'t'){for(const _0x21f2e4 of _0x34818f[_0x56513f(0x3f76)+'es'])_0x21f2e4['tagName']===_0x56513f(0x2eab)&&_0x21f2e4[_0x56513f(0x1471)]===_0x56513f(0x3dfb)+_0x56513f(0x164e)&&_0x4eb118(_0x21f2e4);}})['observe'](document,{'childList':!0x0,'subtree':!0x0});function _0x3c409a(_0x4829d3){const _0x40f2eb=_0xdc32d,_0x23163a={};return _0x4829d3['integrit'+'y']&&(_0x23163a[_0x40f2eb(0x3dd)+'y']=_0x4829d3[_0x40f2eb(0x3dd)+'y']),_0x4829d3[_0x40f2eb(0x2f7a)+'Policy']&&(_0x23163a[_0x40f2eb(0x2f7a)+'Policy']=_0x4829d3[_0x40f2eb(0x2f7a)+'Policy']),_0x4829d3[_0x40f2eb(0x1466)+'gin']===_0x40f2eb(0x11a2)+'entials'?_0x23163a[_0x40f2eb(0x31c3)+_0x40f2eb(0xd62)]=_0x40f2eb(0x7a7):_0x4829d3[_0x40f2eb(0x1466)+_0x40f2eb(0x1fdb)]===_0x40f2eb(0x60c)+'s'?_0x23163a['credenti'+_0x40f2eb(0xd62)]=_0x40f2eb(0x5618):_0x23163a[_0x40f2eb(0x31c3)+'als']='same-ori'+_0x40f2eb(0x1fdb),_0x23163a;}function _0x4eb118(_0x4707b7){const _0x350c9b=_0xdc32d;if(_0x4707b7['ep'])return;_0x4707b7['ep']=!0x0;const _0x38f4be=_0x3c409a(_0x4707b7);fetch(_0x4707b7[_0x350c9b(0x341)],_0x38f4be);}}());const ca=_0x560eaf(0x98a)+_0x560eaf(0x396)+_0x560eaf(0x1a03)+_0x560eaf(0x4342),hr=_0x560eaf(0xbac)+_0x560eaf(0x4342),ri=_0x560eaf(0x98a)+'eagle-lo'+_0x560eaf(0x2f7f),gr={'resizeTimeline':_0x560eaf(0x156b)+_0x560eaf(0x4cee)+'ie\x20Höhe\x20'+_0x560eaf(0x4c5b)+_0x560eaf(0x3a9a)+_0x560eaf(0xb23)+'n','systems':'System','particlesBones':_0x560eaf(0x1aae)+_0x560eaf(0x4a83)+'n)','rootBone':'Wurzelkn'+_0x560eaf(0x1e17),'hudTip':'Radzoom\x20'+_0x560eaf(0x2bd5)+_0x560eaf(0x34e2)+_0x560eaf(0x304)+_0x560eaf(0x5479)+_0x560eaf(0x392)+'um\x20die\x20L'+_0x560eaf(0xc4c)+_0x560eaf(0x4d34)+'n','title':_0x560eaf(0x3219)+'rtikeled'+_0x560eaf(0x10a)},vr={'documentTitle':_0x560eaf(0x3219)+_0x560eaf(0x16d8)+_0x560eaf(0x10a),'titleLead':_0x560eaf(0x1c6)+_0x560eaf(0x2d6c)+_0x560eaf(0x27c7),'titleAccent':_0x560eaf(0x2358)+_0x560eaf(0x4af7)+'olchain','backToTools':_0x560eaf(0x1de8)+_0x560eaf(0x4ff)+_0x560eaf(0x2e74)+'hl','editor':{'badge':_0x560eaf(0x4216)+_0x560eaf(0x671),'title':_0x560eaf(0x1aae)+_0x560eaf(0x42aa),'description':_0x560eaf(0x36dc)+_0x560eaf(0x3115)+_0x560eaf(0x4e49)+_0x560eaf(0x455d)+_0x560eaf(0x203f)+_0x560eaf(0x402a)+_0x560eaf(0x3115)+_0x560eaf(0x292)+_0x560eaf(0x293)+_0x560eaf(0x8be),'tagPreview':_0x560eaf(0x930)+_0x560eaf(0x4c54),'tagSequence':'Spine-Pr'+_0x560eaf(0x2a7d)+_0x560eaf(0x7Line truncated
|
||||||
Vendored
-1
@@ -1 +0,0 @@
|
|||||||
.language-picker[data-v-811a66dc]{display:flex;height:28px;align-items:center;gap:6px;margin-left:auto;color:#9aa8c1;font-size:11px;white-space:nowrap}.language-dropdown[data-v-811a66dc]{position:relative}.language-trigger[data-v-811a66dc]{display:flex;height:28px;min-width:88px;align-items:center;gap:7px;padding:0 8px;border:1px solid #3a4963;border-radius:6px;outline:none;background:#202a3b;color:#dbe4f5;font:inherit;font-size:11px;cursor:pointer}.language-trigger[data-v-811a66dc]:hover,.language-trigger.open[data-v-811a66dc]{border-color:#5b608c;background:#2b3a5c}.language-trigger[data-v-811a66dc]:focus-visible{border-color:#7774ff}.language-flag-icon[data-v-811a66dc],.language-option-flag-icon[data-v-811a66dc]{display:block;width:22px;height:15px;flex:0 0 auto;overflow:hidden;border-radius:2px;box-shadow:0 0 0 1px #ffffff1f}.language-short[data-v-811a66dc]{min-width:20px;color:#dbe4f5;font-size:11px;font-weight:600}.language-chevron[data-v-811a66dc]{width:10px;height:6px;margin-left:auto;fill:none;stroke:#aab;stroke-width:1.7;transition:transform .15s ease}.language-trigger.open .language-chevron[data-v-811a66dc]{transform:rotate(180deg)}.language-menu[data-v-811a66dc]{position:absolute;right:0;bottom:calc(100% + 6px);z-index:30;width:190px;max-height:min(360px,calc(100vh - 120px));overflow-y:auto;padding:5px;border:1px solid #343b50;border-radius:9px;background:#141420fa;box-shadow:0 8px 24px #0000007a}.menu-down .language-menu[data-v-811a66dc]{top:calc(100% + 6px);bottom:auto}.language-option[data-v-811a66dc]{display:grid;width:100%;min-height:38px;grid-template-columns:24px 1fr 16px;align-items:center;gap:8px;padding:0 7px;border:0;border-radius:6px;background:transparent;color:#aeb8cc;font:inherit;font-size:12px;text-align:left;white-space:nowrap;cursor:pointer}.language-option[data-v-811a66dc]:hover,.language-option[data-v-811a66dc]:focus-visible{outline:none;background:#2b3a5c;color:#fff}.language-option.selected[data-v-811a66dc]{background:#343b63;color:#fff}.language-check[data-v-811a66dc]{justify-self:end;color:#fff;font-size:18px;font-weight:500}.welcome-shell[data-v-d3581500]{--u: max(1px, calc(100vw / 1920));position:fixed;top:0;right:0;bottom:0;left:0;display:flex;flex-direction:column;overflow:hidden;box-sizing:border-box;padding:clamp(22px,3vw,calc(42 * var(--u))) clamp(22px,4vw,calc(64 * var(--u))) calc(28 * var(--u));color:#eef0f5;background:radial-gradient(circle at 17% 0%,rgba(218,45,57,.16),transparent 32%),radial-gradient(circle at 85% 8%,rgba(72,100,170,.16),transparent 30%),linear-gradient(145deg,#09090d,#0d0f15 52%,#090b10);font-family:system-ui,-apple-system,Segoe UI,sans-serif}.welcome-shell[data-v-d3581500]:before{position:fixed;top:0;right:0;bottom:0;left:0;pointer-events:none;content:"";opacity:.22;background-image:linear-gradient(rgba(255,255,255,.025) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.025) 1px,transparent 1px);background-size:calc(42 * var(--u)) calc(42 * var(--u));-webkit-mask-image:linear-gradient(to bottom,#000,transparent 70%);mask-image:linear-gradient(to bottom,#000,transparent 70%)}.welcome-header[data-v-d3581500],.welcome-intro[data-v-d3581500],.hub-grid[data-v-d3581500]{position:relative}.welcome-header[data-v-d3581500]{display:flex;min-height:calc(42 * var(--u));align-items:center;gap:calc(18 * var(--u))}.welcome-header[data-v-d3581500]{z-index:40}.brand-link[data-v-d3581500]{display:inline-flex;border-radius:6px;transition:opacity .16s ease}.brand-link[data-v-d3581500]:hover{opacity:.78}.brand-link[data-v-d3581500]:focus-visible{outline:2px solid #ef5147;outline-offset:5px}.brand-logo[data-v-d3581500]{display:block;width:clamp(128px,10vw,calc(156 * var(--u)));height:auto}.welcome-intro[data-v-d3581500]{padding:clamp(34px,6vh,calc(64 * var(--u))) 0 clamp(22px,3.5vh,calc(34 * var(--u)));text-align:center}.welcome-intro h1[data-v-d3581500]{max-width:none;margin:0 auto;color:#f3f3f5;font-size:clamp(20px,2.4vw,calc(40 * var(--u)));line-height:1.1;letter-spacing:-.025em;white-space:nowrap}.welcome-intro h1 span[data-v-d3581500]{color:#ff4e47}.welcome-intro p[data-v-d3581500]{max-width:390px;margin:0 0 3px;color:#8c93a3;font-size:13px;line-height:1.75}.hub-grid[data-v-d3581500]{display:grid;min-height:0;flex:0 1 auto;grid-template-columns:repeat(3,minmax(0,1fr));gap:clamp(14px,1.5vw,calc(22 * var(--u)));width:min(100%,calc(1540 * var(--u)));margin:0 auto}.hub-card[data-v-d3581500]{--hub-border: #242731;--hub-divider: #242731;position:relative;display:flex;min-width:0;min-height:0;flex-direction:column;padding:0;overflow:hidden;border:1px solid var(--hub-border);border-radius:calc(18 * var(--u));background:linear-gradient(180deg,#1a1d26f0,#0f1117fa);color:inherit;font:inherit;text-align:left;text-decoration:none;cursor:pointer;box-shadow:0 20px 52px #00000040;transition:transform .18s ease,border-color .18s ease,box-shadow .18s ease}.hub-card[data-v-d3581500]:hover{transform:translateY(-3px);border-color:var(--hub-border);box-shadow:0 25px 6Line truncated
|
|
||||||
Vendored
+2
-2
@@ -9,9 +9,9 @@
|
|||||||
html, body { margin: 0; padding: 0; background: #111; color: #eee; font-family: system-ui, -apple-system, sans-serif; }
|
html, body { margin: 0; padding: 0; background: #111; color: #eee; font-family: system-ui, -apple-system, sans-serif; }
|
||||||
#app { display: flex; flex-direction: column; height: 100vh; }
|
#app { display: flex; flex-direction: column; height: 100vh; }
|
||||||
</style>
|
</style>
|
||||||
<script type="module" crossorigin src="/assets/index-6a1Sdk_L.js"></script>
|
<script type="module" crossorigin src="/assets/index-LPRIE0BV.js"></script>
|
||||||
<link rel="modulepreload" crossorigin href="/assets/vendor-fsmZiMPK.js">
|
<link rel="modulepreload" crossorigin href="/assets/vendor-fsmZiMPK.js">
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-uyvk6dvP.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-BOPqyKhf.css">
|
||||||
<script>
|
<script>
|
||||||
var _paq = window._paq = window._paq || [];
|
var _paq = window._paq = window._paq || [];
|
||||||
_paq.push(['enableLinkTracking']);
|
_paq.push(['enableLinkTracking']);
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
import type { CollisionBody, ParticleSystem, ScenePath, useParticleStore } from '../store/particleStore'
|
||||||
|
import type { SpineSceneObject } from '../spine/spineTypes'
|
||||||
|
import { ensureEmitterConfig } from '../core/particleEmitter'
|
||||||
|
import { registerSpineAsset } from '../spine/spineAssetRegistry'
|
||||||
|
import { translate } from '../i18n'
|
||||||
|
import { cloneEditorValue } from './editorClone'
|
||||||
|
import {
|
||||||
|
hydrateParticleResources,
|
||||||
|
parseEditorConfig,
|
||||||
|
parseEmbeddedSpines,
|
||||||
|
withoutLegacyColliderTags,
|
||||||
|
} from './editorConfig'
|
||||||
|
|
||||||
|
type EditorStore = ReturnType<typeof useParticleStore>
|
||||||
|
|
||||||
|
function nextId(items: readonly { id: number }[]) {
|
||||||
|
return Math.max(0, ...items.map((item) => Number(item.id) || 0)) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function remapId(map: ReadonlyMap<number, number>, id: number) {
|
||||||
|
return map.get(id) || 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function appendEditorConfigText(store: EditorStore, text: string) {
|
||||||
|
const config = parseEditorConfig(text)
|
||||||
|
const systems = cloneEditorValue(config.scene.systems) as unknown as ParticleSystem[]
|
||||||
|
const colliders = config.scene.colliders.map(withoutLegacyColliderTags)
|
||||||
|
const paths = cloneEditorValue(config.scene.paths) as unknown as ScenePath[]
|
||||||
|
const spines = cloneEditorValue(config.scene.spines) as unknown as SpineSceneObject[]
|
||||||
|
|
||||||
|
let systemId = nextId(store.systems)
|
||||||
|
let colliderId = nextId(store.colliders)
|
||||||
|
let pathId = nextId(store.paths)
|
||||||
|
let spineId = nextId(store.spines)
|
||||||
|
const systemIds = new Map<number, number>()
|
||||||
|
const colliderIds = new Map<number, number>()
|
||||||
|
const pathIds = new Map<number, number>()
|
||||||
|
const spineIds = new Map<number, number>()
|
||||||
|
|
||||||
|
for (const system of systems) {
|
||||||
|
systemIds.set(system.id, systemId)
|
||||||
|
system.id = systemId++
|
||||||
|
}
|
||||||
|
for (const collider of colliders) {
|
||||||
|
colliderIds.set(collider.id, colliderId)
|
||||||
|
collider.id = colliderId++
|
||||||
|
}
|
||||||
|
for (const path of paths) {
|
||||||
|
pathIds.set(path.id, pathId)
|
||||||
|
path.id = pathId++
|
||||||
|
}
|
||||||
|
for (const spine of spines) {
|
||||||
|
if (spine.sourceBundle == null) spine.sourceBundle = null
|
||||||
|
if (spine.dataVersion == null) spine.dataVersion = ''
|
||||||
|
if (!Array.isArray(spine.slots)) spine.slots = []
|
||||||
|
if (typeof spine.selectedBone !== 'string') spine.selectedBone = ''
|
||||||
|
if (typeof spine.selectedSlot !== 'string') spine.selectedSlot = ''
|
||||||
|
spineIds.set(spine.id, spineId)
|
||||||
|
spine.id = spineId++
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const system of systems) {
|
||||||
|
const config = ensureEmitterConfig(system.config as any)
|
||||||
|
system.frames = undefined
|
||||||
|
config.collisionColliderIds = config.collisionColliderIds
|
||||||
|
.map((id) => remapId(colliderIds, id))
|
||||||
|
.filter((id) => id > 0)
|
||||||
|
config.spawnPathId = remapId(pathIds, config.spawnPathId)
|
||||||
|
if (!config.spawnPathId && (config.emissionAngleMode === 'path-normal' || config.emissionAngleMode === 'path-tangent')) {
|
||||||
|
config.emissionAngleMode = 'fixed'
|
||||||
|
}
|
||||||
|
config.emitterFollowPathId = remapId(pathIds, config.emitterFollowPathId)
|
||||||
|
config.emitterFollowSpineId = remapId(spineIds, config.emitterFollowSpineId)
|
||||||
|
config.renderLayerSpineId = remapId(spineIds, config.renderLayerSpineId)
|
||||||
|
for (const rule of config.events || []) {
|
||||||
|
rule.targetSystemId = remapId(systemIds, rule.targetSystemId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const collider of colliders) {
|
||||||
|
collider.followPathId = remapId(pathIds, collider.followPathId)
|
||||||
|
if (!collider.followPathId) collider.followPath = false
|
||||||
|
}
|
||||||
|
|
||||||
|
await hydrateParticleResources(systems)
|
||||||
|
const parsedSpines = await parseEmbeddedSpines(spines)
|
||||||
|
|
||||||
|
const animations = [...store.timeline.animations]
|
||||||
|
for (const animation of config.timeline?.animations || []) {
|
||||||
|
const name = typeof animation === 'string' ? animation.trim() : ''
|
||||||
|
if (name && !animations.includes(name)) animations.push(name)
|
||||||
|
}
|
||||||
|
for (const system of systems) {
|
||||||
|
if (!animations.includes(system.animation)) {
|
||||||
|
system.animation = store.timeline.animation || store.timeline.animations[0] || 'animation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const spine of spines) {
|
||||||
|
const parsed = parsedSpines.get(spine.id)
|
||||||
|
spine.assetVersion = Math.max(1, Number(spine.assetVersion) || 0) + 1
|
||||||
|
if (!parsed) {
|
||||||
|
spine.status = 'empty'
|
||||||
|
spine.error = spine.skeletonFileName ? translate('errors.missingEmbeddedSpine') : ''
|
||||||
|
spine.animations = []
|
||||||
|
spine.bones = []
|
||||||
|
spine.slots = []
|
||||||
|
spine.selectedAnimation = ''
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
registerSpineAsset(spine.id, parsed)
|
||||||
|
spine.dataVersion = parsed.dataVersion
|
||||||
|
spine.animations = parsed.animations
|
||||||
|
spine.bones = parsed.bones
|
||||||
|
spine.slots = parsed.slots
|
||||||
|
if (!parsed.animations.some((animation) => animation.name === spine.selectedAnimation)) {
|
||||||
|
spine.selectedAnimation = parsed.animations[0]?.name || ''
|
||||||
|
}
|
||||||
|
spine.status = 'ready'
|
||||||
|
spine.error = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
store.systems.push(...systems)
|
||||||
|
store.colliders.push(...colliders)
|
||||||
|
store.paths.push(...paths)
|
||||||
|
store.spines.push(...spines)
|
||||||
|
store.timeline.animations = animations
|
||||||
|
store.syncSceneSequences()
|
||||||
|
|
||||||
|
if (systems[0]) store.selectSceneObject('particle', systems[0].id)
|
||||||
|
else if (colliders[0]) store.selectSceneObject('collision', colliders[0].id)
|
||||||
|
else if (paths[0]) store.selectSceneObject('path', paths[0].id)
|
||||||
|
else if (spines[0]) store.selectSceneObject('spine', spines[0].id)
|
||||||
|
store.recalcTotalFrames()
|
||||||
|
}
|
||||||
@@ -83,7 +83,7 @@ function jsonClone<T>(value: T): T {
|
|||||||
})) as T
|
})) as T
|
||||||
}
|
}
|
||||||
|
|
||||||
function withoutLegacyColliderTags(collider: CollisionBody): CollisionBody {
|
export function withoutLegacyColliderTags(collider: CollisionBody): CollisionBody {
|
||||||
const copy = cloneEditorValue(collider) as CollisionBody & { tags?: unknown; selectedTag?: unknown }
|
const copy = cloneEditorValue(collider) as CollisionBody & { tags?: unknown; selectedTag?: unknown }
|
||||||
delete copy.tags
|
delete copy.tags
|
||||||
delete copy.selectedTag
|
delete copy.selectedTag
|
||||||
@@ -192,7 +192,7 @@ export function parseEditorConfig(text: string): EditorConfigFile {
|
|||||||
return candidate as EditorConfigFile
|
return candidate as EditorConfigFile
|
||||||
}
|
}
|
||||||
|
|
||||||
async function hydrateParticleResources(systems: ParticleSystem[]) {
|
export async function hydrateParticleResources(systems: ParticleSystem[]) {
|
||||||
const textureCache = new Map<string, Promise<Texture>>()
|
const textureCache = new Map<string, Promise<Texture>>()
|
||||||
const loadTexture = (url: string) => {
|
const loadTexture = (url: string) => {
|
||||||
const source = url === '/particles/star.png'
|
const source = url === '/particles/star.png'
|
||||||
@@ -225,7 +225,7 @@ async function hydrateParticleResources(systems: ParticleSystem[]) {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function parseEmbeddedSpines(spines: SpineSceneObject[]) {
|
export async function parseEmbeddedSpines(spines: SpineSceneObject[]) {
|
||||||
const parsed = new Map<number, ParsedSpineBundle>()
|
const parsed = new Map<number, ParsedSpineBundle>()
|
||||||
try {
|
try {
|
||||||
for (const spine of spines) {
|
for (const spine of spines) {
|
||||||
|
|||||||
@@ -0,0 +1,275 @@
|
|||||||
|
import type {
|
||||||
|
CollisionBody,
|
||||||
|
ParticleSystem,
|
||||||
|
SceneObjectType,
|
||||||
|
ScenePath,
|
||||||
|
useParticleStore,
|
||||||
|
} from '../store/particleStore'
|
||||||
|
import type { SpineSceneObject } from '../spine/spineTypes'
|
||||||
|
import { registerSpineAsset, releaseSpineAsset } from '../spine/spineAssetRegistry'
|
||||||
|
import { ensureEmitterConfig } from '../core/particleEmitter'
|
||||||
|
import { collectEmitterAncestors } from '../core/subEmitterIds'
|
||||||
|
import { translate } from '../i18n'
|
||||||
|
import { cloneEditorValue } from './editorClone'
|
||||||
|
import { hydrateParticleResources, parseEmbeddedSpines, withoutLegacyColliderTags } from './editorConfig'
|
||||||
|
|
||||||
|
type EditorStore = ReturnType<typeof useParticleStore>
|
||||||
|
|
||||||
|
const CLIPBOARD_FORMAT = 'SpineParticle.SceneObject'
|
||||||
|
const CLIPBOARD_VERSION = 1
|
||||||
|
type ClipboardObject = ParticleSystem | CollisionBody | ScenePath | SpineSceneObject
|
||||||
|
|
||||||
|
interface SceneObjectClipboardPayload {
|
||||||
|
format: typeof CLIPBOARD_FORMAT
|
||||||
|
version: typeof CLIPBOARD_VERSION
|
||||||
|
type: SceneObjectType
|
||||||
|
object: ClipboardObject
|
||||||
|
}
|
||||||
|
|
||||||
|
let pasteBusy = false
|
||||||
|
|
||||||
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
|
return !!value && typeof value === 'object' && !Array.isArray(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function jsonClone<T>(value: T): T {
|
||||||
|
return JSON.parse(JSON.stringify(value, (key, item) => {
|
||||||
|
if (key === 'frames' || key === 'texture' || key === 'trailTexture') return undefined
|
||||||
|
return item
|
||||||
|
})) as T
|
||||||
|
}
|
||||||
|
|
||||||
|
function objectForCopy(store: EditorStore, type: SceneObjectType, id: number): ClipboardObject | null {
|
||||||
|
if (type === 'particle') {
|
||||||
|
const system = store.systems.find((item) => item.id === id)
|
||||||
|
return system ? jsonClone(system) as unknown as ParticleSystem : null
|
||||||
|
}
|
||||||
|
if (type === 'collision') {
|
||||||
|
const collider = store.colliders.find((item) => item.id === id)
|
||||||
|
return collider ? withoutLegacyColliderTags(collider) : null
|
||||||
|
}
|
||||||
|
if (type === 'path') {
|
||||||
|
const path = store.paths.find((item) => item.id === id)
|
||||||
|
return path ? cloneEditorValue(path) : null
|
||||||
|
}
|
||||||
|
const spine = store.spines.find((item) => item.id === id)
|
||||||
|
if (!spine) return null
|
||||||
|
const copy = jsonClone(spine)
|
||||||
|
copy.selectedBone = ''
|
||||||
|
copy.selectedSlot = ''
|
||||||
|
copy.status = copy.sourceBundle ? 'ready' : 'empty'
|
||||||
|
copy.error = ''
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
|
||||||
|
function parsePayload(text: string): SceneObjectClipboardPayload {
|
||||||
|
let value: unknown
|
||||||
|
try {
|
||||||
|
value = JSON.parse(text, (key, item) => (
|
||||||
|
key === '__proto__' || key === 'prototype' || key === 'constructor' ? undefined : item
|
||||||
|
))
|
||||||
|
} catch {
|
||||||
|
throw new Error(translate('scene.clipboardInvalid'))
|
||||||
|
}
|
||||||
|
if (!isRecord(value)
|
||||||
|
|| value.format !== CLIPBOARD_FORMAT
|
||||||
|
|| value.version !== CLIPBOARD_VERSION
|
||||||
|
|| !['particle', 'collision', 'path', 'spine'].includes(String(value.type))
|
||||||
|
|| !isRecord(value.object)) {
|
||||||
|
throw new Error(translate('scene.clipboardInvalid'))
|
||||||
|
}
|
||||||
|
return value as unknown as SceneObjectClipboardPayload
|
||||||
|
}
|
||||||
|
|
||||||
|
function validObjectForType(payload: SceneObjectClipboardPayload) {
|
||||||
|
const object = payload.object as unknown as Record<string, unknown>
|
||||||
|
if (payload.type === 'particle') return isRecord(object.config)
|
||||||
|
if (payload.type === 'collision') return typeof object.name === 'string'
|
||||||
|
if (payload.type === 'path') return typeof object.name === 'string' && Array.isArray(object.points)
|
||||||
|
return typeof object.name === 'string'
|
||||||
|
&& (object.sourceBundle == null || isRecord(object.sourceBundle))
|
||||||
|
}
|
||||||
|
|
||||||
|
function typeLabel(type: SceneObjectType) {
|
||||||
|
return type === 'spine' ? 'Spine' : translate(`scene.${type}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetParticleReferences(store: EditorStore, system: ParticleSystem) {
|
||||||
|
const config = ensureEmitterConfig(system.config as any)
|
||||||
|
const systems = store.systems.map((item) => (item.id === system.id ? system : item))
|
||||||
|
const systemIds = new Set(systems.map((item) => item.id))
|
||||||
|
const colliderIds = new Set(store.colliders.map((item) => item.id))
|
||||||
|
const pathIds = new Set(store.paths.map((item) => item.id))
|
||||||
|
const spineIds = new Set(store.spines.map((item) => item.id))
|
||||||
|
const ancestors = collectEmitterAncestors(systems, system.id)
|
||||||
|
|
||||||
|
config.collisionColliderIds = config.collisionColliderIds.filter((id) => colliderIds.has(id))
|
||||||
|
if (!pathIds.has(config.spawnPathId)) {
|
||||||
|
config.spawnPathId = 0
|
||||||
|
if (config.emissionAngleMode === 'path-normal' || config.emissionAngleMode === 'path-tangent') {
|
||||||
|
config.emissionAngleMode = 'fixed'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const rule of config.events || []) {
|
||||||
|
if (!systemIds.has(rule.targetSystemId) || rule.targetSystemId === system.id || ancestors.has(rule.targetSystemId)) {
|
||||||
|
rule.targetSystemId = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (config.emitterFollowMode === 'path' && !pathIds.has(config.emitterFollowPathId)) {
|
||||||
|
config.emitterFollow = false
|
||||||
|
config.emitterFollowMode = 'none'
|
||||||
|
config.emitterFollowPathId = 0
|
||||||
|
}
|
||||||
|
if (!spineIds.has(config.emitterFollowSpineId)) {
|
||||||
|
config.emitterFollowSpineId = 0
|
||||||
|
config.emitterFollowBoneName = ''
|
||||||
|
if (config.emitterFollowMode === 'bone') {
|
||||||
|
config.emitterFollow = false
|
||||||
|
config.emitterFollowMode = 'none'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (config.emitterFollowMode === 'bone') {
|
||||||
|
const spine = store.spines.find((item) => item.id === config.emitterFollowSpineId)
|
||||||
|
if (!spine?.bones.some((bone) => bone.name === config.emitterFollowBoneName)) {
|
||||||
|
config.emitterFollow = false
|
||||||
|
config.emitterFollowMode = 'none'
|
||||||
|
config.emitterFollowSpineId = 0
|
||||||
|
config.emitterFollowBoneName = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!spineIds.has(config.renderLayerSpineId)) {
|
||||||
|
config.renderLayerSpineId = 0
|
||||||
|
config.renderLayerSlotName = ''
|
||||||
|
if (config.renderLayerMode === 'slot') config.renderLayerMode = 'top'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function copySceneObjectToClipboard(store: EditorStore, type: SceneObjectType, id: number) {
|
||||||
|
const object = objectForCopy(store, type, id)
|
||||||
|
if (!object) throw new Error(translate('scene.clipboardInvalid'))
|
||||||
|
if (!navigator.clipboard?.writeText) throw new Error(translate('scene.clipboardUnavailable'))
|
||||||
|
const payload: SceneObjectClipboardPayload = {
|
||||||
|
format: CLIPBOARD_FORMAT,
|
||||||
|
version: CLIPBOARD_VERSION,
|
||||||
|
type,
|
||||||
|
object,
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(JSON.stringify(payload))
|
||||||
|
} catch {
|
||||||
|
throw new Error(translate('scene.clipboardCopyFailed'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function pasteSceneObjectFromClipboard(store: EditorStore, type: SceneObjectType, id: number) {
|
||||||
|
if (pasteBusy) return
|
||||||
|
pasteBusy = true
|
||||||
|
try {
|
||||||
|
if (!navigator.clipboard?.readText) throw new Error(translate('scene.clipboardUnavailable'))
|
||||||
|
let text: string
|
||||||
|
try {
|
||||||
|
text = await navigator.clipboard.readText()
|
||||||
|
} catch {
|
||||||
|
throw new Error(translate('scene.clipboardReadFailed'))
|
||||||
|
}
|
||||||
|
const payload = parsePayload(text)
|
||||||
|
if (!validObjectForType(payload)) throw new Error(translate('scene.clipboardInvalid'))
|
||||||
|
if (payload.type !== type) {
|
||||||
|
throw new Error(translate('scene.clipboardTypeMismatch', {
|
||||||
|
source: typeLabel(payload.type),
|
||||||
|
target: typeLabel(type),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'particle') {
|
||||||
|
const index = store.systems.findIndex((item) => item.id === id)
|
||||||
|
if (index < 0) throw new Error(translate('scene.clipboardInvalid'))
|
||||||
|
const target = store.systems[index]
|
||||||
|
const source = payload.object as ParticleSystem
|
||||||
|
if (!isRecord(source.config)) throw new Error(translate('scene.clipboardInvalid'))
|
||||||
|
const system = cloneEditorValue(source)
|
||||||
|
system.id = target.id
|
||||||
|
system.config = ensureEmitterConfig(system.config as any)
|
||||||
|
system.config.name = target.config.name
|
||||||
|
system.frames = undefined
|
||||||
|
system.animation = store.timeline.animations.includes(system.animation)
|
||||||
|
? system.animation
|
||||||
|
: store.timeline.animation
|
||||||
|
resetParticleReferences(store, system)
|
||||||
|
await hydrateParticleResources([system])
|
||||||
|
store.systems.splice(index, 1, system)
|
||||||
|
store.selectSceneObject('particle', system.id)
|
||||||
|
store.recalcTotalFrames()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'collision') {
|
||||||
|
const index = store.colliders.findIndex((item) => item.id === id)
|
||||||
|
if (index < 0) throw new Error(translate('scene.clipboardInvalid'))
|
||||||
|
const target = store.colliders[index]
|
||||||
|
const collider = withoutLegacyColliderTags(payload.object as CollisionBody)
|
||||||
|
collider.id = target.id
|
||||||
|
collider.name = target.name
|
||||||
|
if (!store.paths.some((item) => item.id === collider.followPathId)) {
|
||||||
|
collider.followPath = false
|
||||||
|
collider.followPathId = 0
|
||||||
|
}
|
||||||
|
store.colliders.splice(index, 1, collider)
|
||||||
|
store.selectSceneObject('collision', collider.id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'path') {
|
||||||
|
const index = store.paths.findIndex((item) => item.id === id)
|
||||||
|
if (index < 0) throw new Error(translate('scene.clipboardInvalid'))
|
||||||
|
const target = store.paths[index]
|
||||||
|
const path = cloneEditorValue(payload.object as ScenePath)
|
||||||
|
path.id = target.id
|
||||||
|
path.name = target.name
|
||||||
|
store.paths.splice(index, 1, path)
|
||||||
|
store.selectSceneObject('path', path.id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = store.spines.findIndex((item) => item.id === id)
|
||||||
|
if (index < 0) throw new Error(translate('scene.clipboardInvalid'))
|
||||||
|
const target = store.spines[index]
|
||||||
|
const spine = cloneEditorValue(payload.object as SpineSceneObject)
|
||||||
|
spine.id = target.id
|
||||||
|
spine.name = target.name
|
||||||
|
spine.selectedBone = ''
|
||||||
|
spine.selectedSlot = ''
|
||||||
|
spine.assetVersion = target.assetVersion + 1
|
||||||
|
spine.status = 'loading'
|
||||||
|
spine.error = ''
|
||||||
|
const parsed = (await parseEmbeddedSpines([spine])).get(spine.id)
|
||||||
|
if (parsed) {
|
||||||
|
releaseSpineAsset(target.id)
|
||||||
|
registerSpineAsset(spine.id, parsed)
|
||||||
|
spine.dataVersion = parsed.dataVersion
|
||||||
|
spine.animations = parsed.animations
|
||||||
|
spine.bones = parsed.bones
|
||||||
|
spine.slots = parsed.slots
|
||||||
|
if (!parsed.animations.some((animation) => animation.name === spine.selectedAnimation)) {
|
||||||
|
spine.selectedAnimation = parsed.animations[0]?.name || ''
|
||||||
|
}
|
||||||
|
spine.status = 'ready'
|
||||||
|
} else {
|
||||||
|
releaseSpineAsset(target.id)
|
||||||
|
spine.dataVersion = ''
|
||||||
|
spine.animations = []
|
||||||
|
spine.bones = []
|
||||||
|
spine.slots = []
|
||||||
|
spine.selectedAnimation = ''
|
||||||
|
spine.status = 'empty'
|
||||||
|
}
|
||||||
|
store.spines.splice(index, 1, spine)
|
||||||
|
store.selectSceneObject('spine', spine.id)
|
||||||
|
store.recalcTotalFrames()
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error && error.message) throw error
|
||||||
|
throw new Error(translate('scene.clipboardPasteFailed'))
|
||||||
|
} finally {
|
||||||
|
pasteBusy = false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -164,6 +164,9 @@
|
|||||||
"storageUnavailable": "Der lokale Browserspeicher ist nicht verfügbar",
|
"storageUnavailable": "Der lokale Browserspeicher ist nicht verfügbar",
|
||||||
"saved": "Im Browser gespeichert: {name}",
|
"saved": "Im Browser gespeichert: {name}",
|
||||||
"loaded": "Aus dem Browser geladen: {name}",
|
"loaded": "Aus dem Browser geladen: {name}",
|
||||||
|
"appendToScene": "Diese Konfiguration an die Szene anhängen",
|
||||||
|
"appending": "Konfiguration wird angehängt: {name}",
|
||||||
|
"appended": "An die Szene angehängt: {name}",
|
||||||
"deleted": "Browserkonfiguration gelöscht: {name}",
|
"deleted": "Browserkonfiguration gelöscht: {name}",
|
||||||
"downloadEmpty": "Keine Konfigurationen zum Herunterladen",
|
"downloadEmpty": "Keine Konfigurationen zum Herunterladen",
|
||||||
"downloaded": "Heruntergeladen: {name}"
|
"downloaded": "Heruntergeladen: {name}"
|
||||||
@@ -501,6 +504,14 @@
|
|||||||
"addPathShort": "+ Pfad",
|
"addPathShort": "+ Pfad",
|
||||||
"addSpine": "Fügen Sie Spine hinzu",
|
"addSpine": "Fügen Sie Spine hinzu",
|
||||||
"copyTitle": "Kopieren Sie das aktuell ausgewählte Szenenobjekt",
|
"copyTitle": "Kopieren Sie das aktuell ausgewählte Szenenobjekt",
|
||||||
|
"contextCopy": "In die Zwischenablage kopieren",
|
||||||
|
"contextPaste": "Einfügen und überschreiben",
|
||||||
|
"clipboardUnavailable": "Dieser Browser erlaubt keinen Zugriff auf die Zwischenablage",
|
||||||
|
"clipboardInvalid": "Die Zwischenablage enthält kein gültiges Szenenobjekt",
|
||||||
|
"clipboardCopyFailed": "Das Szenenobjekt konnte nicht in die Zwischenablage kopiert werden",
|
||||||
|
"clipboardReadFailed": "Die Zwischenablage konnte nicht gelesen werden. Prüfen Sie die Browserberechtigungen.",
|
||||||
|
"clipboardTypeMismatch": "Nur Objekte desselben Typs können überschrieben werden (Zwischenablage: {source}, aktuell: {target})",
|
||||||
|
"clipboardPasteFailed": "Das Szenenobjekt konnte nicht wiederhergestellt werden",
|
||||||
"showParticle": "Partikelsystem anzeigen",
|
"showParticle": "Partikelsystem anzeigen",
|
||||||
"hideParticle": "Partikelsystem ausblenden",
|
"hideParticle": "Partikelsystem ausblenden",
|
||||||
"showCollider": "Collider anzeigen",
|
"showCollider": "Collider anzeigen",
|
||||||
@@ -802,6 +813,8 @@
|
|||||||
"eye": "kleine Augen",
|
"eye": "kleine Augen",
|
||||||
"eyeDesc": "Objekte einzeln ein-/ausblenden; Verborgene Partikelsysteme werden nicht exportiert.",
|
"eyeDesc": "Objekte einzeln ein-/ausblenden; Verborgene Partikelsysteme werden nicht exportiert.",
|
||||||
"copyDesc": "Kopiert das aktuell ausgewählte Objekt vollständig, um den Vergleich verschiedener Parameter zu erleichtern.",
|
"copyDesc": "Kopiert das aktuell ausgewählte Objekt vollständig, um den Vergleich verschiedener Parameter zu erleichtern.",
|
||||||
|
"sceneClipboard": "Szenen-Zwischenablage",
|
||||||
|
"sceneClipboardDesc": "Rechtsklick auf ein Szenenobjekt, um es zu kopieren oder durch Einfügen zu ersetzen.",
|
||||||
"backgroundDesc": "Laden Sie das Referenzbild hoch und passen Sie Position und Transparenz nur für die Leinwandvorschau an.",
|
"backgroundDesc": "Laden Sie das Referenzbild hoch und passen Sie Position und Transparenz nur für die Leinwandvorschau an.",
|
||||||
"settingsDesc": "Das Layout der Verwaltungsoberfläche, die Zusatzanzeige, der Maßstab usw. werden automatisch im Browser gespeichert.",
|
"settingsDesc": "Das Layout der Verwaltungsoberfläche, die Zusatzanzeige, der Maßstab usw. werden automatisch im Browser gespeichert.",
|
||||||
"undoDesc": "Ctrl+Z fährt einen Schritt zurück; Die maximale Schrittanzahl kann in den Systemeinstellungen angepasst werden.",
|
"undoDesc": "Ctrl+Z fährt einen Schritt zurück; Die maximale Schrittanzahl kann in den Systemeinstellungen angepasst werden.",
|
||||||
|
|||||||
@@ -176,6 +176,9 @@
|
|||||||
"storageUnavailable": "Browser local storage is unavailable",
|
"storageUnavailable": "Browser local storage is unavailable",
|
||||||
"saved": "Saved to browser: {name}",
|
"saved": "Saved to browser: {name}",
|
||||||
"loaded": "Loaded from browser: {name}",
|
"loaded": "Loaded from browser: {name}",
|
||||||
|
"appendToScene": "Append this configuration to the scene",
|
||||||
|
"appending": "Appending configuration: {name}",
|
||||||
|
"appended": "Appended to scene: {name}",
|
||||||
"deleted": "Deleted browser configuration: {name}",
|
"deleted": "Deleted browser configuration: {name}",
|
||||||
"downloadEmpty": "No configurations to download",
|
"downloadEmpty": "No configurations to download",
|
||||||
"downloaded": "Downloaded: {name}"
|
"downloaded": "Downloaded: {name}"
|
||||||
@@ -513,6 +516,14 @@
|
|||||||
"addPathShort": "+ Path",
|
"addPathShort": "+ Path",
|
||||||
"addSpine": "Add Spine",
|
"addSpine": "Add Spine",
|
||||||
"copyTitle": "Duplicate selected scene object",
|
"copyTitle": "Duplicate selected scene object",
|
||||||
|
"contextCopy": "Copy to clipboard",
|
||||||
|
"contextPaste": "Paste and overwrite",
|
||||||
|
"clipboardUnavailable": "This browser does not allow clipboard access",
|
||||||
|
"clipboardInvalid": "The clipboard does not contain a valid scene object",
|
||||||
|
"clipboardCopyFailed": "Could not copy the scene object to the clipboard",
|
||||||
|
"clipboardReadFailed": "Could not read the clipboard. Check browser permissions.",
|
||||||
|
"clipboardTypeMismatch": "Only objects of the same type can be overwritten (clipboard: {source}, current: {target})",
|
||||||
|
"clipboardPasteFailed": "Could not restore the scene object",
|
||||||
"showParticle": "Show particle system",
|
"showParticle": "Show particle system",
|
||||||
"hideParticle": "Hide particle system",
|
"hideParticle": "Hide particle system",
|
||||||
"showCollider": "Show collider",
|
"showCollider": "Show collider",
|
||||||
@@ -825,6 +836,8 @@
|
|||||||
"eye": "Visibility",
|
"eye": "Visibility",
|
||||||
"eyeDesc": "Show or hide an object independently. Hidden particle systems are not exported.",
|
"eyeDesc": "Show or hide an object independently. Hidden particle systems are not exported.",
|
||||||
"copyDesc": "Duplicate the selected object for parameter comparisons.",
|
"copyDesc": "Duplicate the selected object for parameter comparisons.",
|
||||||
|
"sceneClipboard": "Scene clipboard",
|
||||||
|
"sceneClipboardDesc": "Right-click a scene object to copy it to the clipboard or overwrite it by pasting.",
|
||||||
"backgroundDesc": "Upload a reference image and adjust its position and opacity for canvas preview only.",
|
"backgroundDesc": "Upload a reference image and adjust its position and opacity for canvas preview only.",
|
||||||
"settingsDesc": "Manage layout, canvas guides, tick display, and other editor settings.",
|
"settingsDesc": "Manage layout, canvas guides, tick display, and other editor settings.",
|
||||||
"undoDesc": "Ctrl+Z undoes one step. The maximum history size is configurable.",
|
"undoDesc": "Ctrl+Z undoes one step. The maximum history size is configurable.",
|
||||||
|
|||||||
@@ -164,6 +164,9 @@
|
|||||||
"storageUnavailable": "El almacenamiento local del navegador no está disponible",
|
"storageUnavailable": "El almacenamiento local del navegador no está disponible",
|
||||||
"saved": "Guardado en el navegador: {name}",
|
"saved": "Guardado en el navegador: {name}",
|
||||||
"loaded": "Cargado desde el navegador: {name}",
|
"loaded": "Cargado desde el navegador: {name}",
|
||||||
|
"appendToScene": "Añadir esta configuración a la escena",
|
||||||
|
"appending": "Añadiendo configuración: {name}",
|
||||||
|
"appended": "Añadido a la escena: {name}",
|
||||||
"deleted": "Configuración eliminada del navegador: {name}",
|
"deleted": "Configuración eliminada del navegador: {name}",
|
||||||
"downloadEmpty": "No hay configuraciones para descargar",
|
"downloadEmpty": "No hay configuraciones para descargar",
|
||||||
"downloaded": "Descargado: {name}"
|
"downloaded": "Descargado: {name}"
|
||||||
@@ -501,6 +504,14 @@
|
|||||||
"addPathShort": "+ camino",
|
"addPathShort": "+ camino",
|
||||||
"addSpine": "Añadir Spine",
|
"addSpine": "Añadir Spine",
|
||||||
"copyTitle": "Copiar el objeto de escena seleccionado actualmente",
|
"copyTitle": "Copiar el objeto de escena seleccionado actualmente",
|
||||||
|
"contextCopy": "Copiar al portapapeles",
|
||||||
|
"contextPaste": "Pegar y sobrescribir",
|
||||||
|
"clipboardUnavailable": "Este navegador no permite acceder al portapapeles",
|
||||||
|
"clipboardInvalid": "El portapapeles no contiene un objeto de escena válido",
|
||||||
|
"clipboardCopyFailed": "No se pudo copiar el objeto de escena al portapapeles",
|
||||||
|
"clipboardReadFailed": "No se pudo leer el portapapeles. Comprueba los permisos del navegador.",
|
||||||
|
"clipboardTypeMismatch": "Solo se pueden sobrescribir objetos del mismo tipo (portapapeles: {source}, actual: {target})",
|
||||||
|
"clipboardPasteFailed": "No se pudo restaurar el objeto de escena",
|
||||||
"showParticle": "Mostrar sistema de partículas",
|
"showParticle": "Mostrar sistema de partículas",
|
||||||
"hideParticle": "Ocultar sistema de partículas",
|
"hideParticle": "Ocultar sistema de partículas",
|
||||||
"showCollider": "mostrar colisionadores",
|
"showCollider": "mostrar colisionadores",
|
||||||
@@ -802,6 +813,8 @@
|
|||||||
"eye": "ojos pequeños",
|
"eye": "ojos pequeños",
|
||||||
"eyeDesc": "Mostrar/ocultar objetos individualmente; Los sistemas de partículas ocultas no se exportan.",
|
"eyeDesc": "Mostrar/ocultar objetos individualmente; Los sistemas de partículas ocultas no se exportan.",
|
||||||
"copyDesc": "Copia completamente el objeto seleccionado actualmente para facilitar la comparación de diferentes parámetros.",
|
"copyDesc": "Copia completamente el objeto seleccionado actualmente para facilitar la comparación de diferentes parámetros.",
|
||||||
|
"sceneClipboard": "Portapapeles de escena",
|
||||||
|
"sceneClipboardDesc": "Haz clic derecho en un objeto de escena para copiarlo o sustituirlo pegando.",
|
||||||
"backgroundDesc": "Cargue la imagen de referencia y ajuste la posición y la transparencia solo para la vista previa del lienzo.",
|
"backgroundDesc": "Cargue la imagen de referencia y ajuste la posición y la transparencia solo para la vista previa del lienzo.",
|
||||||
"settingsDesc": "El diseño de la interfaz de administración, la pantalla auxiliar, la escala, etc. se guardan automáticamente en el navegador.",
|
"settingsDesc": "El diseño de la interfaz de administración, la pantalla auxiliar, la escala, etc. se guardan automáticamente en el navegador.",
|
||||||
"undoDesc": "Ctrl+Z retrocede un paso; el número máximo de pasos se puede ajustar en la configuración del sistema.",
|
"undoDesc": "Ctrl+Z retrocede un paso; el número máximo de pasos se puede ajustar en la configuración del sistema.",
|
||||||
|
|||||||
@@ -164,6 +164,9 @@
|
|||||||
"storageUnavailable": "Le stockage local du navigateur est indisponible",
|
"storageUnavailable": "Le stockage local du navigateur est indisponible",
|
||||||
"saved": "Enregistré dans le navigateur : {name}",
|
"saved": "Enregistré dans le navigateur : {name}",
|
||||||
"loaded": "Chargé depuis le navigateur : {name}",
|
"loaded": "Chargé depuis le navigateur : {name}",
|
||||||
|
"appendToScene": "Ajouter cette configuration à la scène",
|
||||||
|
"appending": "Ajout de la configuration : {name}",
|
||||||
|
"appended": "Ajouté à la scène : {name}",
|
||||||
"deleted": "Configuration supprimée du navigateur : {name}",
|
"deleted": "Configuration supprimée du navigateur : {name}",
|
||||||
"downloadEmpty": "Aucune configuration à télécharger",
|
"downloadEmpty": "Aucune configuration à télécharger",
|
||||||
"downloaded": "Téléchargé : {name}"
|
"downloaded": "Téléchargé : {name}"
|
||||||
@@ -501,6 +504,14 @@
|
|||||||
"addPathShort": "+ chemin",
|
"addPathShort": "+ chemin",
|
||||||
"addSpine": "Ajouter Spine",
|
"addSpine": "Ajouter Spine",
|
||||||
"copyTitle": "Copier l'objet de scène actuellement sélectionné",
|
"copyTitle": "Copier l'objet de scène actuellement sélectionné",
|
||||||
|
"contextCopy": "Copier dans le presse-papiers",
|
||||||
|
"contextPaste": "Coller et remplacer",
|
||||||
|
"clipboardUnavailable": "Ce navigateur n’autorise pas l’accès au presse-papiers",
|
||||||
|
"clipboardInvalid": "Le presse-papiers ne contient aucun objet de scène valide",
|
||||||
|
"clipboardCopyFailed": "Impossible de copier l’objet de scène dans le presse-papiers",
|
||||||
|
"clipboardReadFailed": "Impossible de lire le presse-papiers. Vérifiez les autorisations du navigateur.",
|
||||||
|
"clipboardTypeMismatch": "Seul un objet de même type peut être remplacé (presse-papiers : {source}, actuel : {target})",
|
||||||
|
"clipboardPasteFailed": "Impossible de restaurer l’objet de scène",
|
||||||
"showParticle": "Afficher le système de particules",
|
"showParticle": "Afficher le système de particules",
|
||||||
"hideParticle": "Masquer le système de particules",
|
"hideParticle": "Masquer le système de particules",
|
||||||
"showCollider": "montrer les collisionneurs",
|
"showCollider": "montrer les collisionneurs",
|
||||||
@@ -802,6 +813,8 @@
|
|||||||
"eye": "petits yeux",
|
"eye": "petits yeux",
|
||||||
"eyeDesc": "Afficher/masquer les objets individuellement ; les systèmes de particules cachées ne sont pas exportés.",
|
"eyeDesc": "Afficher/masquer les objets individuellement ; les systèmes de particules cachées ne sont pas exportés.",
|
||||||
"copyDesc": "Copie entièrement l'objet actuellement sélectionné pour faciliter la comparaison des différents paramètres.",
|
"copyDesc": "Copie entièrement l'objet actuellement sélectionné pour faciliter la comparaison des différents paramètres.",
|
||||||
|
"sceneClipboard": "Presse-papiers de scène",
|
||||||
|
"sceneClipboardDesc": "Faites un clic droit sur un objet de scène pour le copier ou le remplacer par collage.",
|
||||||
"backgroundDesc": "Téléchargez l'image de référence et ajustez la position et la transparence pour l'aperçu du canevas uniquement.",
|
"backgroundDesc": "Téléchargez l'image de référence et ajustez la position et la transparence pour l'aperçu du canevas uniquement.",
|
||||||
"settingsDesc": "La disposition de l'interface de gestion, l'affichage auxiliaire, la balance, etc. sont automatiquement enregistrés dans le navigateur.",
|
"settingsDesc": "La disposition de l'interface de gestion, l'affichage auxiliaire, la balance, etc. sont automatiquement enregistrés dans le navigateur.",
|
||||||
"undoDesc": "Ctrl+Z se rétracte d'un pas ; le nombre maximum d'étapes peut être ajusté dans les paramètres du système.",
|
"undoDesc": "Ctrl+Z se rétracte d'un pas ; le nombre maximum d'étapes peut être ajusté dans les paramètres du système.",
|
||||||
|
|||||||
@@ -164,6 +164,9 @@
|
|||||||
"storageUnavailable": "ブラウザのローカルストレージを利用できません",
|
"storageUnavailable": "ブラウザのローカルストレージを利用できません",
|
||||||
"saved": "ブラウザに保存しました:{name}",
|
"saved": "ブラウザに保存しました:{name}",
|
||||||
"loaded": "ブラウザから読み込みました:{name}",
|
"loaded": "ブラウザから読み込みました:{name}",
|
||||||
|
"appendToScene": "この設定をシーンに追加",
|
||||||
|
"appending": "設定を追加しています:{name}",
|
||||||
|
"appended": "シーンに追加しました:{name}",
|
||||||
"deleted": "ブラウザの設定を削除しました:{name}",
|
"deleted": "ブラウザの設定を削除しました:{name}",
|
||||||
"downloadEmpty": "ダウンロードできる設定がありません",
|
"downloadEmpty": "ダウンロードできる設定がありません",
|
||||||
"downloaded": "ダウンロードしました:{name}"
|
"downloaded": "ダウンロードしました:{name}"
|
||||||
@@ -501,6 +504,14 @@
|
|||||||
"addPathShort": "+ パス",
|
"addPathShort": "+ パス",
|
||||||
"addSpine": "Spineを追加",
|
"addSpine": "Spineを追加",
|
||||||
"copyTitle": "現在選択されているシーン オブジェクトをコピーします",
|
"copyTitle": "現在選択されているシーン オブジェクトをコピーします",
|
||||||
|
"contextCopy": "クリップボードへコピー",
|
||||||
|
"contextPaste": "貼り付けて上書き",
|
||||||
|
"clipboardUnavailable": "このブラウザではクリップボードにアクセスできません",
|
||||||
|
"clipboardInvalid": "クリップボードに有効なシーンオブジェクトがありません",
|
||||||
|
"clipboardCopyFailed": "シーンオブジェクトをクリップボードへコピーできませんでした",
|
||||||
|
"clipboardReadFailed": "クリップボードを読み取れません。ブラウザの権限を確認してください。",
|
||||||
|
"clipboardTypeMismatch": "同じ種類のシーンオブジェクトだけを上書きできます(クリップボード: {source}、現在: {target})",
|
||||||
|
"clipboardPasteFailed": "シーンオブジェクトを復元できませんでした",
|
||||||
"showParticle": "パーティクル システムを表示する",
|
"showParticle": "パーティクル システムを表示する",
|
||||||
"hideParticle": "パーティクル システムを非表示にする",
|
"hideParticle": "パーティクル システムを非表示にする",
|
||||||
"showCollider": "コライダーを表示",
|
"showCollider": "コライダーを表示",
|
||||||
@@ -802,6 +813,8 @@
|
|||||||
"eye": "小さな目",
|
"eye": "小さな目",
|
||||||
"eyeDesc": "オブジェクトを個別に表示/非表示にします。非表示のパーティクル システムはエクスポートされません。",
|
"eyeDesc": "オブジェクトを個別に表示/非表示にします。非表示のパーティクル システムはエクスポートされません。",
|
||||||
"copyDesc": "現在選択されているオブジェクトを完全にコピーして、さまざまなパラメータの比較を容易にします。",
|
"copyDesc": "現在選択されているオブジェクトを完全にコピーして、さまざまなパラメータの比較を容易にします。",
|
||||||
|
"sceneClipboard": "シーンクリップボード",
|
||||||
|
"sceneClipboardDesc": "シーンオブジェクトを右クリックすると、クリップボードへのコピーや貼り付けによる上書きができます。",
|
||||||
"backgroundDesc": "参照画像をアップロードし、キャンバスプレビューのみ位置と透明度を調整します。",
|
"backgroundDesc": "参照画像をアップロードし、キャンバスプレビューのみ位置と透明度を調整します。",
|
||||||
"settingsDesc": "管理インターフェイスのレイアウト、補助表示、スケールなどはブラウザに自動的に保存されます。",
|
"settingsDesc": "管理インターフェイスのレイアウト、補助表示、スケールなどはブラウザに自動的に保存されます。",
|
||||||
"undoDesc": "Ctrl+Z は 1 ステップ後退します。最大ステップ数はシステム設定で調整できます。",
|
"undoDesc": "Ctrl+Z は 1 ステップ後退します。最大ステップ数はシステム設定で調整できます。",
|
||||||
|
|||||||
@@ -164,6 +164,9 @@
|
|||||||
"storageUnavailable": "브라우저 로컬 저장소를 사용할 수 없습니다",
|
"storageUnavailable": "브라우저 로컬 저장소를 사용할 수 없습니다",
|
||||||
"saved": "브라우저에 저장했습니다: {name}",
|
"saved": "브라우저에 저장했습니다: {name}",
|
||||||
"loaded": "브라우저에서 불러왔습니다: {name}",
|
"loaded": "브라우저에서 불러왔습니다: {name}",
|
||||||
|
"appendToScene": "이 구성을 장면에 추가",
|
||||||
|
"appending": "구성을 추가하는 중: {name}",
|
||||||
|
"appended": "장면에 추가했습니다: {name}",
|
||||||
"deleted": "브라우저 설정을 삭제했습니다: {name}",
|
"deleted": "브라우저 설정을 삭제했습니다: {name}",
|
||||||
"downloadEmpty": "다운로드할 설정이 없습니다",
|
"downloadEmpty": "다운로드할 설정이 없습니다",
|
||||||
"downloaded": "다운로드했습니다: {name}"
|
"downloaded": "다운로드했습니다: {name}"
|
||||||
@@ -501,6 +504,14 @@
|
|||||||
"addPathShort": "+ 경로",
|
"addPathShort": "+ 경로",
|
||||||
"addSpine": "Spine 추가",
|
"addSpine": "Spine 추가",
|
||||||
"copyTitle": "현재 선택된 장면 객체를 복사합니다.",
|
"copyTitle": "현재 선택된 장면 객체를 복사합니다.",
|
||||||
|
"contextCopy": "클립보드로 복사",
|
||||||
|
"contextPaste": "붙여넣어 덮어쓰기",
|
||||||
|
"clipboardUnavailable": "이 브라우저에서는 클립보드에 접근할 수 없습니다",
|
||||||
|
"clipboardInvalid": "클립보드에 사용할 수 있는 장면 객체가 없습니다",
|
||||||
|
"clipboardCopyFailed": "장면 객체를 클립보드로 복사하지 못했습니다",
|
||||||
|
"clipboardReadFailed": "클립보드를 읽을 수 없습니다. 브라우저 권한을 확인하세요.",
|
||||||
|
"clipboardTypeMismatch": "같은 유형의 장면 객체만 덮어쓸 수 있습니다(클립보드: {source}, 현재: {target})",
|
||||||
|
"clipboardPasteFailed": "장면 객체를 복원하지 못했습니다",
|
||||||
"showParticle": "입자 시스템 표시",
|
"showParticle": "입자 시스템 표시",
|
||||||
"hideParticle": "입자 시스템 숨기기",
|
"hideParticle": "입자 시스템 숨기기",
|
||||||
"showCollider": "충돌체 표시",
|
"showCollider": "충돌체 표시",
|
||||||
@@ -802,6 +813,8 @@
|
|||||||
"eye": "작은 눈",
|
"eye": "작은 눈",
|
||||||
"eyeDesc": "개체를 개별적으로 표시하거나 숨깁니다. 숨겨진 입자 시스템은 내보내지지 않습니다.",
|
"eyeDesc": "개체를 개별적으로 표시하거나 숨깁니다. 숨겨진 입자 시스템은 내보내지지 않습니다.",
|
||||||
"copyDesc": "다양한 매개변수의 비교를 용이하게 하기 위해 현재 선택된 개체를 완전히 복사합니다.",
|
"copyDesc": "다양한 매개변수의 비교를 용이하게 하기 위해 현재 선택된 개체를 완전히 복사합니다.",
|
||||||
|
"sceneClipboard": "장면 클립보드",
|
||||||
|
"sceneClipboardDesc": "장면 객체를 마우스 오른쪽 버튼으로 클릭하여 클립보드로 복사하거나 붙여넣어 덮어쓸 수 있습니다.",
|
||||||
"backgroundDesc": "참조 이미지를 업로드하고 캔버스 미리보기 전용 위치와 투명도를 조정하세요.",
|
"backgroundDesc": "참조 이미지를 업로드하고 캔버스 미리보기 전용 위치와 투명도를 조정하세요.",
|
||||||
"settingsDesc": "관리 인터페이스 레이아웃, 보조 디스플레이, 규모 등은 브라우저에 자동으로 저장됩니다.",
|
"settingsDesc": "관리 인터페이스 레이아웃, 보조 디스플레이, 규모 등은 브라우저에 자동으로 저장됩니다.",
|
||||||
"undoDesc": "Ctrl+Z는 한 단계 후퇴합니다. 최대 단계 수는 시스템 설정에서 조정할 수 있습니다.",
|
"undoDesc": "Ctrl+Z는 한 단계 후퇴합니다. 최대 단계 수는 시스템 설정에서 조정할 수 있습니다.",
|
||||||
|
|||||||
@@ -164,6 +164,9 @@
|
|||||||
"storageUnavailable": "O armazenamento local do navegador não está disponível",
|
"storageUnavailable": "O armazenamento local do navegador não está disponível",
|
||||||
"saved": "Salvo no navegador: {name}",
|
"saved": "Salvo no navegador: {name}",
|
||||||
"loaded": "Carregado do navegador: {name}",
|
"loaded": "Carregado do navegador: {name}",
|
||||||
|
"appendToScene": "Adicionar esta configuração à cena",
|
||||||
|
"appending": "Adicionando configuração: {name}",
|
||||||
|
"appended": "Adicionado à cena: {name}",
|
||||||
"deleted": "Configuração excluída do navegador: {name}",
|
"deleted": "Configuração excluída do navegador: {name}",
|
||||||
"downloadEmpty": "Nenhuma configuração para baixar",
|
"downloadEmpty": "Nenhuma configuração para baixar",
|
||||||
"downloaded": "Baixado: {name}"
|
"downloaded": "Baixado: {name}"
|
||||||
@@ -501,6 +504,14 @@
|
|||||||
"addPathShort": "+ caminho",
|
"addPathShort": "+ caminho",
|
||||||
"addSpine": "Adicionar Spine",
|
"addSpine": "Adicionar Spine",
|
||||||
"copyTitle": "Copie o objeto de cena atualmente selecionado",
|
"copyTitle": "Copie o objeto de cena atualmente selecionado",
|
||||||
|
"contextCopy": "Copiar para a área de transferência",
|
||||||
|
"contextPaste": "Colar e substituir",
|
||||||
|
"clipboardUnavailable": "Este navegador não permite acessar a área de transferência",
|
||||||
|
"clipboardInvalid": "A área de transferência não contém um objeto de cena válido",
|
||||||
|
"clipboardCopyFailed": "Não foi possível copiar o objeto de cena para a área de transferência",
|
||||||
|
"clipboardReadFailed": "Não foi possível ler a área de transferência. Verifique as permissões do navegador.",
|
||||||
|
"clipboardTypeMismatch": "Somente objetos do mesmo tipo podem ser substituídos (área de transferência: {source}, atual: {target})",
|
||||||
|
"clipboardPasteFailed": "Não foi possível restaurar o objeto de cena",
|
||||||
"showParticle": "Mostrar sistema de partículas",
|
"showParticle": "Mostrar sistema de partículas",
|
||||||
"hideParticle": "Ocultar sistema de partículas",
|
"hideParticle": "Ocultar sistema de partículas",
|
||||||
"showCollider": "mostrar colisores",
|
"showCollider": "mostrar colisores",
|
||||||
@@ -802,6 +813,8 @@
|
|||||||
"eye": "olhos pequenos",
|
"eye": "olhos pequenos",
|
||||||
"eyeDesc": "Mostrar/ocultar objetos individualmente; sistemas de partículas ocultas não são exportados.",
|
"eyeDesc": "Mostrar/ocultar objetos individualmente; sistemas de partículas ocultas não são exportados.",
|
||||||
"copyDesc": "Copia completamente o objeto atualmente selecionado para facilitar a comparação de diferentes parâmetros.",
|
"copyDesc": "Copia completamente o objeto atualmente selecionado para facilitar a comparação de diferentes parâmetros.",
|
||||||
|
"sceneClipboard": "Área de transferência da cena",
|
||||||
|
"sceneClipboardDesc": "Clique com o botão direito em um objeto de cena para copiá-lo ou substituí-lo colando.",
|
||||||
"backgroundDesc": "Carregue a imagem de referência e ajuste a posição e a transparência apenas para visualização da tela.",
|
"backgroundDesc": "Carregue a imagem de referência e ajuste a posição e a transparência apenas para visualização da tela.",
|
||||||
"settingsDesc": "Layout da interface de gerenciamento, display auxiliar, balança, etc. são salvos automaticamente no navegador.",
|
"settingsDesc": "Layout da interface de gerenciamento, display auxiliar, balança, etc. são salvos automaticamente no navegador.",
|
||||||
"undoDesc": "Ctrl+Z retrai um passo; o número máximo de etapas pode ser ajustado nas configurações do sistema.",
|
"undoDesc": "Ctrl+Z retrai um passo; o número máximo de etapas pode ser ajustado nas configurações do sistema.",
|
||||||
|
|||||||
@@ -176,6 +176,9 @@
|
|||||||
"storageUnavailable": "Локальное хранилище браузера недоступно",
|
"storageUnavailable": "Локальное хранилище браузера недоступно",
|
||||||
"saved": "Сохранено в браузере: {name}",
|
"saved": "Сохранено в браузере: {name}",
|
||||||
"loaded": "Загружено из браузера: {name}",
|
"loaded": "Загружено из браузера: {name}",
|
||||||
|
"appendToScene": "Добавить эту конфигурацию в сцену",
|
||||||
|
"appending": "Добавление конфигурации: {name}",
|
||||||
|
"appended": "Добавлено в сцену: {name}",
|
||||||
"deleted": "Удалено из кэша браузера: {name}",
|
"deleted": "Удалено из кэша браузера: {name}",
|
||||||
"downloadEmpty": "Нет конфигураций для скачивания",
|
"downloadEmpty": "Нет конфигураций для скачивания",
|
||||||
"downloaded": "Скачано: {name}"
|
"downloaded": "Скачано: {name}"
|
||||||
@@ -513,6 +516,14 @@
|
|||||||
"addPathShort": "+Путь",
|
"addPathShort": "+Путь",
|
||||||
"addSpine": "Добавить Spine",
|
"addSpine": "Добавить Spine",
|
||||||
"copyTitle": "Дублировать выбранный объект сцены",
|
"copyTitle": "Дублировать выбранный объект сцены",
|
||||||
|
"contextCopy": "Копировать в буфер обмена",
|
||||||
|
"contextPaste": "Вставить с заменой",
|
||||||
|
"clipboardUnavailable": "Браузер не разрешает доступ к буферу обмена",
|
||||||
|
"clipboardInvalid": "В буфере обмена нет допустимого объекта сцены",
|
||||||
|
"clipboardCopyFailed": "Не удалось скопировать объект сцены в буфер обмена",
|
||||||
|
"clipboardReadFailed": "Не удалось прочитать буфер обмена. Проверьте разрешения браузера.",
|
||||||
|
"clipboardTypeMismatch": "Можно заменять только объект того же типа (в буфере: {source}, текущий: {target})",
|
||||||
|
"clipboardPasteFailed": "Не удалось восстановить объект сцены",
|
||||||
"showParticle": "Показать систему частиц",
|
"showParticle": "Показать систему частиц",
|
||||||
"hideParticle": "Скрыть систему частиц",
|
"hideParticle": "Скрыть систему частиц",
|
||||||
"showCollider": "Показать коллайдер",
|
"showCollider": "Показать коллайдер",
|
||||||
@@ -825,6 +836,8 @@
|
|||||||
"eye": "Видимость",
|
"eye": "Видимость",
|
||||||
"eyeDesc": "Показывает или скрывает отдельный объект. Скрытые системы частиц не экспортируются.",
|
"eyeDesc": "Показывает или скрывает отдельный объект. Скрытые системы частиц не экспортируются.",
|
||||||
"copyDesc": "Полностью дублирует выбранный объект для сравнения параметров.",
|
"copyDesc": "Полностью дублирует выбранный объект для сравнения параметров.",
|
||||||
|
"sceneClipboard": "Буфер объектов сцены",
|
||||||
|
"sceneClipboardDesc": "Щёлкните объект сцены правой кнопкой, чтобы скопировать его в буфер или заменить вставкой.",
|
||||||
"backgroundDesc": "Загружает опорное изображение и позволяет настроить его положение и непрозрачность только для предпросмотра на холсте.",
|
"backgroundDesc": "Загружает опорное изображение и позволяет настроить его положение и непрозрачность только для предпросмотра на холсте.",
|
||||||
"settingsDesc": "Управление расположением интерфейса, вспомогательными элементами, делениями и другими настройками; значения автоматически сохраняются в браузере.",
|
"settingsDesc": "Управление расположением интерфейса, вспомогательными элементами, делениями и другими настройками; значения автоматически сохраняются в браузере.",
|
||||||
"undoDesc": "Ctrl+Z отменяет одно действие. Максимальное число шагов задаётся в системных настройках.",
|
"undoDesc": "Ctrl+Z отменяет одно действие. Максимальное число шагов задаётся в системных настройках.",
|
||||||
|
|||||||
@@ -176,6 +176,9 @@
|
|||||||
"storageUnavailable": "浏览器本地存储不可用",
|
"storageUnavailable": "浏览器本地存储不可用",
|
||||||
"saved": "已保存到浏览器:{name}",
|
"saved": "已保存到浏览器:{name}",
|
||||||
"loaded": "已从浏览器加载:{name}",
|
"loaded": "已从浏览器加载:{name}",
|
||||||
|
"appendToScene": "将当前配置追加到场景中",
|
||||||
|
"appending": "正在追加配置:{name}",
|
||||||
|
"appended": "已追加到场景:{name}",
|
||||||
"deleted": "已删除缓存配置:{name}",
|
"deleted": "已删除缓存配置:{name}",
|
||||||
"downloadEmpty": "没有可下载的配置",
|
"downloadEmpty": "没有可下载的配置",
|
||||||
"downloaded": "已下载:{name}"
|
"downloaded": "已下载:{name}"
|
||||||
@@ -513,6 +516,14 @@
|
|||||||
"addPathShort": "+路径",
|
"addPathShort": "+路径",
|
||||||
"addSpine": "添加 Spine",
|
"addSpine": "添加 Spine",
|
||||||
"copyTitle": "复制当前选中的场景对象",
|
"copyTitle": "复制当前选中的场景对象",
|
||||||
|
"contextCopy": "复制到剪贴板",
|
||||||
|
"contextPaste": "粘贴覆盖",
|
||||||
|
"clipboardUnavailable": "当前浏览器不允许访问剪贴板",
|
||||||
|
"clipboardInvalid": "剪贴板中没有可用的场景对象",
|
||||||
|
"clipboardCopyFailed": "复制场景对象到剪贴板失败",
|
||||||
|
"clipboardReadFailed": "无法读取剪贴板,请检查浏览器权限",
|
||||||
|
"clipboardTypeMismatch": "只能覆盖同类型场景对象(剪贴板为 {source},当前为 {target})",
|
||||||
|
"clipboardPasteFailed": "场景对象恢复失败",
|
||||||
"showParticle": "显示粒子系统",
|
"showParticle": "显示粒子系统",
|
||||||
"hideParticle": "隐藏粒子系统",
|
"hideParticle": "隐藏粒子系统",
|
||||||
"showCollider": "显示碰撞体",
|
"showCollider": "显示碰撞体",
|
||||||
@@ -825,6 +836,8 @@
|
|||||||
"eye": "小眼睛",
|
"eye": "小眼睛",
|
||||||
"eyeDesc": "单独显示/隐藏对象;隐藏的粒子系统不会被导出。",
|
"eyeDesc": "单独显示/隐藏对象;隐藏的粒子系统不会被导出。",
|
||||||
"copyDesc": "完整复制当前选中对象,方便对比不同参数。",
|
"copyDesc": "完整复制当前选中对象,方便对比不同参数。",
|
||||||
|
"sceneClipboard": "场景剪贴板",
|
||||||
|
"sceneClipboardDesc": "右键场景对象可复制到剪贴板或覆盖粘贴。",
|
||||||
"backgroundDesc": "上传参考图并调整位置和透明度,只用于画布预览。",
|
"backgroundDesc": "上传参考图并调整位置和透明度,只用于画布预览。",
|
||||||
"settingsDesc": "管理界面布局、辅助显示、刻度等,自动存入浏览器。",
|
"settingsDesc": "管理界面布局、辅助显示、刻度等,自动存入浏览器。",
|
||||||
"undoDesc": "Ctrl+Z 撤回一步;最大步数可在系统设置中调整。",
|
"undoDesc": "Ctrl+Z 撤回一步;最大步数可在系统设置中调整。",
|
||||||
|
|||||||
@@ -164,6 +164,9 @@
|
|||||||
"storageUnavailable": "瀏覽器本機儲存空間無法使用",
|
"storageUnavailable": "瀏覽器本機儲存空間無法使用",
|
||||||
"saved": "已儲存到瀏覽器:{name}",
|
"saved": "已儲存到瀏覽器:{name}",
|
||||||
"loaded": "已從瀏覽器載入:{name}",
|
"loaded": "已從瀏覽器載入:{name}",
|
||||||
|
"appendToScene": "將目前設定追加到場景中",
|
||||||
|
"appending": "正在追加設定:{name}",
|
||||||
|
"appended": "已追加到場景:{name}",
|
||||||
"deleted": "已刪除瀏覽器設定:{name}",
|
"deleted": "已刪除瀏覽器設定:{name}",
|
||||||
"downloadEmpty": "沒有可下載的設定",
|
"downloadEmpty": "沒有可下載的設定",
|
||||||
"downloaded": "已下載:{name}"
|
"downloaded": "已下載:{name}"
|
||||||
@@ -501,6 +504,14 @@
|
|||||||
"addPathShort": "+路徑",
|
"addPathShort": "+路徑",
|
||||||
"addSpine": "加入 Spine",
|
"addSpine": "加入 Spine",
|
||||||
"copyTitle": "複製目前選取的場景對象",
|
"copyTitle": "複製目前選取的場景對象",
|
||||||
|
"contextCopy": "複製到剪貼簿",
|
||||||
|
"contextPaste": "貼上覆蓋",
|
||||||
|
"clipboardUnavailable": "目前瀏覽器不允許存取剪貼簿",
|
||||||
|
"clipboardInvalid": "剪貼簿中沒有可用的場景物件",
|
||||||
|
"clipboardCopyFailed": "無法複製場景物件到剪貼簿",
|
||||||
|
"clipboardReadFailed": "無法讀取剪貼簿,請檢查瀏覽器權限",
|
||||||
|
"clipboardTypeMismatch": "只能覆蓋同類型場景物件(剪貼簿為 {source},目前為 {target})",
|
||||||
|
"clipboardPasteFailed": "場景物件還原失敗",
|
||||||
"showParticle": "顯示粒子系統",
|
"showParticle": "顯示粒子系統",
|
||||||
"hideParticle": "隱藏粒子系統",
|
"hideParticle": "隱藏粒子系統",
|
||||||
"showCollider": "顯示碰撞體",
|
"showCollider": "顯示碰撞體",
|
||||||
@@ -802,6 +813,8 @@
|
|||||||
"eye": "小眼睛",
|
"eye": "小眼睛",
|
||||||
"eyeDesc": "單獨顯示/隱藏物件;隱藏的粒子系統不會被匯出。",
|
"eyeDesc": "單獨顯示/隱藏物件;隱藏的粒子系統不會被匯出。",
|
||||||
"copyDesc": "完整複製目前選取對象,方便比較不同參數。",
|
"copyDesc": "完整複製目前選取對象,方便比較不同參數。",
|
||||||
|
"sceneClipboard": "場景剪貼簿",
|
||||||
|
"sceneClipboardDesc": "在場景物件上按右鍵,即可複製到剪貼簿或貼上覆蓋。",
|
||||||
"backgroundDesc": "上傳參考圖並調整位置和透明度,只用於畫布預覽。",
|
"backgroundDesc": "上傳參考圖並調整位置和透明度,只用於畫布預覽。",
|
||||||
"settingsDesc": "管理介面佈局、輔助顯示、刻度等,自動存入瀏覽器。",
|
"settingsDesc": "管理介面佈局、輔助顯示、刻度等,自動存入瀏覽器。",
|
||||||
"undoDesc": "Ctrl+Z 撤回一步;最大步數可在系統設定中調整。",
|
"undoDesc": "Ctrl+Z 撤回一步;最大步數可在系統設定中調整。",
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
<dl class="help-list">
|
<dl class="help-list">
|
||||||
<div><dt>{{ $t('help.eye') }}</dt><dd>{{ $t('help.eyeDesc') }}</dd></div>
|
<div><dt>{{ $t('help.eye') }}</dt><dd>{{ $t('help.eyeDesc') }}</dd></div>
|
||||||
<div><dt>+Copy</dt><dd>{{ $t('help.copyDesc') }}</dd></div>
|
<div><dt>+Copy</dt><dd>{{ $t('help.copyDesc') }}</dd></div>
|
||||||
|
<div><dt>{{ $t('help.sceneClipboard') }}</dt><dd>{{ $t('help.sceneClipboardDesc') }}</dd></div>
|
||||||
<div><dt>{{ $t('toolbar.background') }}</dt><dd>{{ $t('help.backgroundDesc') }}</dd></div>
|
<div><dt>{{ $t('toolbar.background') }}</dt><dd>{{ $t('help.backgroundDesc') }}</dd></div>
|
||||||
<div><dt>{{ $t('toolbar.settings') }}</dt><dd>{{ $t('help.settingsDesc') }}</dd></div>
|
<div><dt>{{ $t('toolbar.settings') }}</dt><dd>{{ $t('help.settingsDesc') }}</dd></div>
|
||||||
<div><dt>{{ $t('help.undoShortcut') }}</dt><dd>{{ $t('help.undoDesc') }}</dd></div>
|
<div><dt>{{ $t('help.undoShortcut') }}</dt><dd>{{ $t('help.undoDesc') }}</dd></div>
|
||||||
|
|||||||
+16
-2
@@ -93,7 +93,7 @@
|
|||||||
<div class="tl-playhead" :style="{ left: playPx + 'px' }"></div>
|
<div class="tl-playhead" :style="{ left: playPx + 'px' }"></div>
|
||||||
|
|
||||||
<!-- 每系统一条轨道(粒子条) -->
|
<!-- 每系统一条轨道(粒子条) -->
|
||||||
<div class="tl-lanes">
|
<div class="tl-lanes" @wheel.stop="onLaneWheel">
|
||||||
<div v-for="(sys, i) in store.systems" :key="sys.id" class="tl-lane">
|
<div v-for="(sys, i) in store.systems" :key="sys.id" class="tl-lane">
|
||||||
<span class="lane-ico" :class="{ 'sub-lane-ico': isSubEmitter(sys) }" :style="{ left: startMarkerPx(sys) + 'px' }">●</span>
|
<span class="lane-ico" :class="{ 'sub-lane-ico': isSubEmitter(sys) }" :style="{ left: startMarkerPx(sys) + 'px' }">●</span>
|
||||||
<!-- 粒子条:left=delay;红色底条=系统存在粒子的总时长;绿色覆盖层=持续发射时长 -->
|
<!-- 粒子条:left=delay;红色底条=系统存在粒子的总时长;绿色覆盖层=持续发射时长 -->
|
||||||
@@ -405,6 +405,13 @@ function onWheel(e: WheelEvent) {
|
|||||||
viewFrames.value = Math.round(nv)
|
viewFrames.value = Math.round(nv)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onLaneWheel(e: WheelEvent) {
|
||||||
|
const lanes = e.currentTarget as HTMLElement
|
||||||
|
if (lanes.scrollHeight > lanes.clientHeight) return
|
||||||
|
e.preventDefault()
|
||||||
|
onWheel(e)
|
||||||
|
}
|
||||||
|
|
||||||
// 拖动时间轴滑动(scrub):只允许进入实际播放范围。
|
// 拖动时间轴滑动(scrub):只允许进入实际播放范围。
|
||||||
function onScrubDown(e: PointerEvent) {
|
function onScrubDown(e: PointerEvent) {
|
||||||
const vp = vpRef.value!
|
const vp = vpRef.value!
|
||||||
@@ -580,7 +587,14 @@ function onSpineBarDown(e: PointerEvent, spine: SpineSceneObject) {
|
|||||||
.tl-tickline { position: absolute; top: 0; bottom: 0; width: 1px; background: #263040; }
|
.tl-tickline { position: absolute; top: 0; bottom: 0; width: 1px; background: #263040; }
|
||||||
.tl-ticklabel { position: absolute; top: 2px; transform: translateX(-50%); font-size: 9px; color: #667; }
|
.tl-ticklabel { position: absolute; top: 2px; transform: translateX(-50%); font-size: 9px; color: #667; }
|
||||||
.tl-playhead { position: absolute; top: 0; bottom: 0; width: 2px; background: #ffcc33; z-index: 5; pointer-events: none; }
|
.tl-playhead { position: absolute; top: 0; bottom: 0; width: 2px; background: #ffcc33; z-index: 5; pointer-events: none; }
|
||||||
.tl-lanes { position: absolute; top: 22px; left: 0; right: 0; bottom: 0; }
|
.tl-lanes {
|
||||||
|
position: absolute; top: 22px; left: 0; right: 0; bottom: 0;
|
||||||
|
overflow-x: hidden; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #46516b #0d0d14;
|
||||||
|
}
|
||||||
|
.tl-lanes::-webkit-scrollbar { width: 8px; }
|
||||||
|
.tl-lanes::-webkit-scrollbar-track { background: #0d0d14; }
|
||||||
|
.tl-lanes::-webkit-scrollbar-thumb { border-radius: 4px; background: #46516b; }
|
||||||
|
.tl-lanes::-webkit-scrollbar-thumb:hover { background: #5d6a88; }
|
||||||
.tl-lane { position: relative; height: 26px; margin-bottom: 4px; }
|
.tl-lane { position: relative; height: 26px; margin-bottom: 4px; }
|
||||||
.lane-ico { position: absolute; left: 4px; top: 6px; font-size: 11px; color: #dde; z-index: 2; pointer-events: none; }
|
.lane-ico { position: absolute; left: 4px; top: 6px; font-size: 11px; color: #dde; z-index: 2; pointer-events: none; }
|
||||||
.lane-ico.sub-lane-ico { color: #7f8999; }
|
.lane-ico.sub-lane-ico { color: #7f8999; }
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
@dblclick="loadLocalProject(project)"
|
@dblclick="loadLocalProject(project)"
|
||||||
@keydown.enter.prevent="loadLocalProject(project)"
|
@keydown.enter.prevent="loadLocalProject(project)"
|
||||||
@keydown.space.prevent="selectLocalProject(project)"
|
@keydown.space.prevent="selectLocalProject(project)"
|
||||||
|
@contextmenu.prevent.stop="openProjectContextMenu($event, project)"
|
||||||
>
|
>
|
||||||
<span class="project-name">{{ project.name }}</span>
|
<span class="project-name">{{ project.name }}</span>
|
||||||
<span v-if="store.settings.showProjectSize" class="project-size">{{ formatProjectSize(project.size) }}</span>
|
<span v-if="store.settings.showProjectSize" class="project-size">{{ formatProjectSize(project.size) }}</span>
|
||||||
@@ -95,10 +96,25 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
|
<Teleport to="body">
|
||||||
|
<div
|
||||||
|
v-if="projectContextMenu.open"
|
||||||
|
ref="projectContextMenuElement"
|
||||||
|
class="project-context-menu"
|
||||||
|
role="menu"
|
||||||
|
:style="{ left: projectContextMenu.x + 'px', top: projectContextMenu.y + 'px' }"
|
||||||
|
@pointerdown.stop
|
||||||
|
@contextmenu.prevent
|
||||||
|
>
|
||||||
|
<button type="button" role="menuitem" @click="appendProjectFromContextMenu">{{ $t('config.local.appendToScene') }}</button>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { nextTick, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import type { LocalProject } from '../../editor/projectLibrary'
|
||||||
import NumSlider from '../NumSlider.vue'
|
import NumSlider from '../NumSlider.vue'
|
||||||
import CurveEditor from '../CurveEditor.vue'
|
import CurveEditor from '../CurveEditor.vue'
|
||||||
import ParticleAttributeControl from '../ParticleAttributeControl.vue'
|
import ParticleAttributeControl from '../ParticleAttributeControl.vue'
|
||||||
@@ -111,12 +127,65 @@ const {
|
|||||||
store,
|
store,
|
||||||
configFileInput, selectedPreset, presetOptions, presetLoading, configMessage, configError,
|
configFileInput, selectedPreset, presetOptions, presetLoading, configMessage, configError,
|
||||||
localProjects, projectSaveOpen, projectNameDraft, projectSaveError, projectStorageBusy,
|
localProjects, projectSaveOpen, projectNameDraft, projectSaveError, projectStorageBusy,
|
||||||
saveConfig, closeProjectSaveDialog, confirmSaveConfig, loadLocalProject, downloadLocalProject,
|
saveConfig, closeProjectSaveDialog, confirmSaveConfig, loadLocalProject, appendLocalProject, downloadLocalProject,
|
||||||
downloadCurrentProject, removeLocalProject, downloadAllLocalProjects, projectFilesInput, loadAllProjectFiles,
|
downloadCurrentProject, removeLocalProject, downloadAllLocalProjects, projectFilesInput, loadAllProjectFiles,
|
||||||
selectedLocalProjectName, selectLocalProject, onConfigFileChange, refreshPresets,
|
selectedLocalProjectName, selectLocalProject, onConfigFileChange, refreshPresets,
|
||||||
loadSelectedPreset, collapsed, toggle, sectionOpen, select,
|
loadSelectedPreset, collapsed, toggle, sectionOpen, select,
|
||||||
} = useParticlePanelContext()
|
} = useParticlePanelContext()
|
||||||
const { locale } = useI18n()
|
const { locale } = useI18n()
|
||||||
|
const projectContextMenuElement = ref<HTMLElement | null>(null)
|
||||||
|
const projectContextMenu = reactive({
|
||||||
|
open: false,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
project: null as LocalProject | null,
|
||||||
|
})
|
||||||
|
|
||||||
|
function closeProjectContextMenu() {
|
||||||
|
projectContextMenu.open = false
|
||||||
|
projectContextMenu.project = null
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openProjectContextMenu(event: MouseEvent, project: LocalProject) {
|
||||||
|
selectLocalProject(project)
|
||||||
|
Object.assign(projectContextMenu, { open: true, x: event.clientX, y: event.clientY, project })
|
||||||
|
await nextTick()
|
||||||
|
const menu = projectContextMenuElement.value
|
||||||
|
if (!menu) return
|
||||||
|
const bounds = menu.getBoundingClientRect()
|
||||||
|
projectContextMenu.x = Math.max(8, Math.min(event.clientX, window.innerWidth - bounds.width - 8))
|
||||||
|
projectContextMenu.y = Math.max(8, Math.min(event.clientY, window.innerHeight - bounds.height - 8))
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendProjectFromContextMenu() {
|
||||||
|
const project = projectContextMenu.project
|
||||||
|
closeProjectContextMenu()
|
||||||
|
if (project) void appendLocalProject(project)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onProjectMenuPointerDown(event: PointerEvent) {
|
||||||
|
if (projectContextMenu.open && !projectContextMenuElement.value?.contains(event.target as Node)) {
|
||||||
|
closeProjectContextMenu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onProjectMenuKeyDown(event: KeyboardEvent) {
|
||||||
|
if (event.key === 'Escape') closeProjectContextMenu()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('pointerdown', onProjectMenuPointerDown, true)
|
||||||
|
window.addEventListener('keydown', onProjectMenuKeyDown, true)
|
||||||
|
window.addEventListener('scroll', closeProjectContextMenu, true)
|
||||||
|
window.addEventListener('blur', closeProjectContextMenu)
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('pointerdown', onProjectMenuPointerDown, true)
|
||||||
|
window.removeEventListener('keydown', onProjectMenuKeyDown, true)
|
||||||
|
window.removeEventListener('scroll', closeProjectContextMenu, true)
|
||||||
|
window.removeEventListener('blur', closeProjectContextMenu)
|
||||||
|
})
|
||||||
function formatProjectSize(bytes: number) {
|
function formatProjectSize(bytes: number) {
|
||||||
const kb = Math.max(1, bytes / 1024)
|
const kb = Math.max(1, bytes / 1024)
|
||||||
if (kb < 1024) return `${kb < 10 ? kb.toFixed(1) : Math.round(kb)} KB`
|
if (kb < 1024) return `${kb < 10 ? kb.toFixed(1) : Math.round(kb)} KB`
|
||||||
@@ -129,6 +198,18 @@ function presetDisplayName(preset: PresetOption) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.project-context-menu {
|
||||||
|
position: fixed; z-index: 10000; min-width: 180px; padding: 4px;
|
||||||
|
border: 1px solid #3d4962; border-radius: 7px; background: #171d2a;
|
||||||
|
box-shadow: 0 10px 28px rgba(0, 0, 0, .48);
|
||||||
|
}
|
||||||
|
.project-context-menu button {
|
||||||
|
display: block; width: 100%; height: 30px; padding: 0 10px; border: 0; border-radius: 5px;
|
||||||
|
background: transparent; color: #d5dced; font: inherit; font-size: 12px; text-align: left; white-space: nowrap; cursor: pointer;
|
||||||
|
}
|
||||||
|
.project-context-menu button:hover, .project-context-menu button:focus-visible {
|
||||||
|
outline: none; background: #2d3953; color: #fff;
|
||||||
|
}
|
||||||
.config-management-body { padding-bottom: 2px; }
|
.config-management-body { padding-bottom: 2px; }
|
||||||
.group-download {
|
.group-download {
|
||||||
max-width: 50%; min-height: 24px; margin-left: auto; padding: 3px 8px; border: 1px solid #485673; border-radius: 5px;
|
max-width: 50%; min-height: 24px; margin-left: auto; padding: 3px 8px; border: 1px solid #485673; border-radius: 5px;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
:class="{ on: activeObjectType === 'particle' && entry.system.id === store.activeObjectId, 'scene-object-child': entry.depth > 0 }"
|
:class="{ on: activeObjectType === 'particle' && entry.system.id === store.activeObjectId, 'scene-object-child': entry.depth > 0 }"
|
||||||
:style="entry.depth > 0 ? { paddingLeft: `${entry.depth * 18}px` } : undefined"
|
:style="entry.depth > 0 ? { paddingLeft: `${entry.depth * 18}px` } : undefined"
|
||||||
@click="store.selectSystem(entry.system.id)"
|
@click="store.selectSystem(entry.system.id)"
|
||||||
|
@contextmenu.prevent.stop="openSceneObjectMenu($event, 'particle', entry.system.id)"
|
||||||
>
|
>
|
||||||
<span v-if="entry.depth > 0" class="sub-branch">└</span>
|
<span v-if="entry.depth > 0" class="sub-branch">└</span>
|
||||||
<span v-else class="tdot">●</span>
|
<span v-else class="tdot">●</span>
|
||||||
@@ -36,6 +37,7 @@
|
|||||||
class="tree-item scene-object-item"
|
class="tree-item scene-object-item"
|
||||||
:class="{ on: activeObjectType === 'collision' && collider.id === store.activeObjectId }"
|
:class="{ on: activeObjectType === 'collision' && collider.id === store.activeObjectId }"
|
||||||
@click="store.selectSceneObject('collision', collider.id)"
|
@click="store.selectSceneObject('collision', collider.id)"
|
||||||
|
@contextmenu.prevent.stop="openSceneObjectMenu($event, 'collision', collider.id)"
|
||||||
>
|
>
|
||||||
<span class="tdot collision-dot">■</span>
|
<span class="tdot collision-dot">■</span>
|
||||||
<span class="tlabel">{{ collider.name }}</span>
|
<span class="tlabel">{{ collider.name }}</span>
|
||||||
@@ -51,6 +53,7 @@
|
|||||||
class="tree-item scene-object-item"
|
class="tree-item scene-object-item"
|
||||||
:class="{ on: activeObjectType === 'path' && path.id === store.activeObjectId }"
|
:class="{ on: activeObjectType === 'path' && path.id === store.activeObjectId }"
|
||||||
@click="store.selectSceneObject('path', path.id)"
|
@click="store.selectSceneObject('path', path.id)"
|
||||||
|
@contextmenu.prevent.stop="openSceneObjectMenu($event, 'path', path.id)"
|
||||||
>
|
>
|
||||||
<span class="tdot path-dot">◇</span>
|
<span class="tdot path-dot">◇</span>
|
||||||
<span class="tlabel">{{ path.name }}</span>
|
<span class="tlabel">{{ path.name }}</span>
|
||||||
@@ -66,6 +69,7 @@
|
|||||||
class="tree-item scene-object-item"
|
class="tree-item scene-object-item"
|
||||||
:class="{ on: activeObjectType === 'spine' && spine.id === store.activeObjectId }"
|
:class="{ on: activeObjectType === 'spine' && spine.id === store.activeObjectId }"
|
||||||
@click="store.selectSceneObject('spine', spine.id)"
|
@click="store.selectSceneObject('spine', spine.id)"
|
||||||
|
@contextmenu.prevent.stop="openSceneObjectMenu($event, 'spine', spine.id)"
|
||||||
>
|
>
|
||||||
<span class="tdot spine-dot">◆</span>
|
<span class="tdot spine-dot">◆</span>
|
||||||
<span class="tlabel">{{ spine.name }}</span>
|
<span class="tlabel">{{ spine.name }}</span>
|
||||||
@@ -199,10 +203,24 @@
|
|||||||
<div v-else class="panel-hint">{{ $t('scene.panelHint') }}</div>
|
<div v-else class="panel-hint">{{ $t('scene.panelHint') }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Teleport to="body">
|
||||||
|
<div
|
||||||
|
v-if="sceneContextMenu.open"
|
||||||
|
ref="sceneContextMenuElement"
|
||||||
|
class="scene-context-menu"
|
||||||
|
role="menu"
|
||||||
|
:style="{ left: sceneContextMenu.x + 'px', top: sceneContextMenu.y + 'px' }"
|
||||||
|
@pointerdown.stop
|
||||||
|
@contextmenu.prevent
|
||||||
|
>
|
||||||
|
<button type="button" role="menuitem" @click="copyFromSceneContextMenu">{{ $t('scene.contextCopy') }}</button>
|
||||||
|
<button type="button" role="menuitem" @click="pasteFromSceneContextMenu">{{ $t('scene.contextPaste') }}</button>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
|
||||||
import NumSlider from '../NumSlider.vue'
|
import NumSlider from '../NumSlider.vue'
|
||||||
import CurveEditor from '../CurveEditor.vue'
|
import CurveEditor from '../CurveEditor.vue'
|
||||||
import ParticleAttributeControl from '../ParticleAttributeControl.vue'
|
import ParticleAttributeControl from '../ParticleAttributeControl.vue'
|
||||||
@@ -210,11 +228,80 @@ import ColorGradientEditor from '../ColorGradientEditor.vue'
|
|||||||
import ImageResourceCard from '../ImageResourceCard.vue'
|
import ImageResourceCard from '../ImageResourceCard.vue'
|
||||||
import SpineObjectPanel from '../../spine/SpineObjectPanel.vue'
|
import SpineObjectPanel from '../../spine/SpineObjectPanel.vue'
|
||||||
import { collectSubEmitterIds } from '../../core/subEmitterIds'
|
import { collectSubEmitterIds } from '../../core/subEmitterIds'
|
||||||
|
import { copySceneObjectToClipboard, pasteSceneObjectFromClipboard } from '../../editor/sceneObjectClipboard'
|
||||||
|
import type { SceneObjectType } from '../../store/particleStore'
|
||||||
import { useParticlePanelContext } from './particlePanelContext'
|
import { useParticlePanelContext } from './particlePanelContext'
|
||||||
const { store, resolvedCurveEditorMode, sys, activeObjectType, activeCollider, activePath, activeSpine, canCopyObject, collapsed, toggle, sectionOpen, addSys, addCollider, addPath, addSpine, copySelectedObject, applyPathPreset, clearPathAnchors, randomizeSeed, LINEAR_CURVE } = useParticlePanelContext()
|
const { store, resolvedCurveEditorMode, sys, activeObjectType, activeCollider, activePath, activeSpine, canCopyObject, collapsed, toggle, sectionOpen, addSys, addCollider, addPath, addSpine, copySelectedObject, applyPathPreset, clearPathAnchors, randomizeSeed, LINEAR_CURVE } = useParticlePanelContext()
|
||||||
|
|
||||||
type DisplaySystem = { system: (typeof store.systems)[number]; depth: number }
|
type DisplaySystem = { system: (typeof store.systems)[number]; depth: number }
|
||||||
|
|
||||||
|
const sceneContextMenuElement = ref<HTMLElement | null>(null)
|
||||||
|
const sceneContextMenu = reactive({
|
||||||
|
open: false,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
type: 'particle' as SceneObjectType,
|
||||||
|
id: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
function closeSceneContextMenu() {
|
||||||
|
sceneContextMenu.open = false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openSceneObjectMenu(event: MouseEvent, type: SceneObjectType, id: number) {
|
||||||
|
store.selectSceneObject(type, id)
|
||||||
|
Object.assign(sceneContextMenu, { open: true, x: event.clientX, y: event.clientY, type, id })
|
||||||
|
await nextTick()
|
||||||
|
const menu = sceneContextMenuElement.value
|
||||||
|
if (!menu) return
|
||||||
|
const bounds = menu.getBoundingClientRect()
|
||||||
|
sceneContextMenu.x = Math.max(8, Math.min(event.clientX, window.innerWidth - bounds.width - 8))
|
||||||
|
sceneContextMenu.y = Math.max(8, Math.min(event.clientY, window.innerHeight - bounds.height - 8))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runSceneContextAction(action: 'copy' | 'paste') {
|
||||||
|
const { type, id } = sceneContextMenu
|
||||||
|
closeSceneContextMenu()
|
||||||
|
try {
|
||||||
|
if (action === 'copy') await copySceneObjectToClipboard(store, type, id)
|
||||||
|
else await pasteSceneObjectFromClipboard(store, type, id)
|
||||||
|
} catch (error) {
|
||||||
|
window.alert(error instanceof Error ? error.message : String(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyFromSceneContextMenu() {
|
||||||
|
void runSceneContextAction('copy')
|
||||||
|
}
|
||||||
|
|
||||||
|
function pasteFromSceneContextMenu() {
|
||||||
|
void runSceneContextAction('paste')
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSceneMenuPointerDown(event: PointerEvent) {
|
||||||
|
if (sceneContextMenu.open && !sceneContextMenuElement.value?.contains(event.target as Node)) {
|
||||||
|
closeSceneContextMenu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSceneMenuKeyDown(event: KeyboardEvent) {
|
||||||
|
if (event.key === 'Escape') closeSceneContextMenu()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('pointerdown', onSceneMenuPointerDown, true)
|
||||||
|
window.addEventListener('keydown', onSceneMenuKeyDown, true)
|
||||||
|
window.addEventListener('scroll', closeSceneContextMenu, true)
|
||||||
|
window.addEventListener('blur', closeSceneContextMenu)
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('pointerdown', onSceneMenuPointerDown, true)
|
||||||
|
window.removeEventListener('keydown', onSceneMenuKeyDown, true)
|
||||||
|
window.removeEventListener('scroll', closeSceneContextMenu, true)
|
||||||
|
window.removeEventListener('blur', closeSceneContextMenu)
|
||||||
|
})
|
||||||
|
|
||||||
const warmupDisabled = computed(() => !!sys.value && (
|
const warmupDisabled = computed(() => !!sys.value && (
|
||||||
collectSubEmitterIds(store.systems).has(sys.value.id)
|
collectSubEmitterIds(store.systems).has(sys.value.id)
|
||||||
|| (sys.value.config.mode === 'stream' && sys.value.config.streamBehavior === 'loop')
|
|| (sys.value.config.mode === 'stream' && sys.value.config.streamBehavior === 'loop')
|
||||||
@@ -270,6 +357,18 @@ const displaySystems = computed<DisplaySystem[]>(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.scene-context-menu {
|
||||||
|
position: fixed; z-index: 10000; min-width: 142px; padding: 4px;
|
||||||
|
border: 1px solid #3d4962; border-radius: 7px; background: #171d2a;
|
||||||
|
box-shadow: 0 10px 28px rgba(0, 0, 0, .48);
|
||||||
|
}
|
||||||
|
.scene-context-menu button {
|
||||||
|
display: block; width: 100%; height: 30px; padding: 0 10px; border: 0; border-radius: 5px;
|
||||||
|
background: transparent; color: #d5dced; font: inherit; font-size: 12px; text-align: left; cursor: pointer;
|
||||||
|
}
|
||||||
|
.scene-context-menu button:hover, .scene-context-menu button:focus-visible {
|
||||||
|
outline: none; background: #2d3953; color: #fff;
|
||||||
|
}
|
||||||
.panel { display: flex; width: 100%; height: 100%; flex-direction: column; overflow: hidden; background: #14141f; border-left: 1px solid #2a2a3c; padding: 12px; box-sizing: border-box; }
|
.panel { display: flex; width: 100%; height: 100%; flex-direction: column; overflow: hidden; background: #14141f; border-left: 1px solid #2a2a3c; padding: 12px; box-sizing: border-box; }
|
||||||
.empty, .panel-hint { color: #667; font-size: 12px; padding: 8px 0; }
|
.empty, .panel-hint { color: #667; font-size: 12px; padding: 8px 0; }
|
||||||
.group { border: 1px solid #2a2a3c; border-radius: 8px; margin-bottom: 10px; overflow: hidden; }
|
.group { border: 1px solid #2a2a3c; border-radius: 8px; margin-bottom: 10px; overflow: hidden; }
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const { exportSettings, exportBusy, exportTarget, exportMessage, exportError, co
|
|||||||
const {
|
const {
|
||||||
configFileInput, selectedPreset, presetOptions, presetLoading, configMessage, configError,
|
configFileInput, selectedPreset, presetOptions, presetLoading, configMessage, configError,
|
||||||
localProjects, projectSaveOpen, projectNameDraft, projectSaveError, projectStorageBusy,
|
localProjects, projectSaveOpen, projectNameDraft, projectSaveError, projectStorageBusy,
|
||||||
saveConfig, closeProjectSaveDialog, confirmSaveConfig, loadLocalProject, downloadLocalProject, downloadCurrentProject,
|
saveConfig, closeProjectSaveDialog, confirmSaveConfig, loadLocalProject, appendLocalProject, downloadLocalProject, downloadCurrentProject,
|
||||||
removeLocalProject, downloadAllLocalProjects, projectFilesInput, loadAllProjectFiles,
|
removeLocalProject, downloadAllLocalProjects, projectFilesInput, loadAllProjectFiles,
|
||||||
selectedLocalProjectName, selectLocalProject, onConfigFileChange, refreshPresets, loadSelectedPreset,
|
selectedLocalProjectName, selectLocalProject, onConfigFileChange, refreshPresets, loadSelectedPreset,
|
||||||
} = useProjectPresets(store)
|
} = useProjectPresets(store)
|
||||||
@@ -657,5 +657,5 @@ function onSpawnPathChange() {
|
|||||||
// 重置/删除图片 → 回到默认 star.png
|
// 重置/删除图片 → 回到默认 star.png
|
||||||
|
|
||||||
|
|
||||||
return { store, exportSettings, exportBusy, exportTarget, exportMessage, exportError, connectorPairUrl, connectorStatusText, connectorStatusClass, connectorReady, connectorAuthorizationUrl, connectorDisabledReason, connectorButtonTitle, refreshConnectorHealth, exportSpineJson, exportSpineProject, exportSpineImages, configFileInput, selectedPreset, presetOptions, presetLoading, configMessage, configError, localProjects, selectedLocalProjectName, selectLocalProject, projectSaveOpen, projectNameDraft, projectSaveError, projectStorageBusy, projectFilesInput, saveConfig, closeProjectSaveDialog, confirmSaveConfig, loadLocalProject, downloadLocalProject, downloadCurrentProject, removeLocalProject, downloadAllLocalProjects, loadAllProjectFiles, onConfigFileChange, refreshPresets, loadSelectedPreset, resolvedCurveEditorMode, sys, textureFileInput, sequenceFileInput, trailTextureFileInput, addTrailResource, openTrailTexturePicker, onTrailRigChange, onTrailResourceBoneCountChange, onTrailResourceRotationChange, onTrailTextureFile, resetTrailTexture, removeTrailResource, setTrailResourceWeight, toggleTrailResourceLock, setTrailColor, commitTrailColor, addImageResource, openTexturePicker, openSequencePicker, onTextureFile, onSequenceFiles, setImageMode, resetTexture, setResourceFixedColor, commitResourceFixedColor, removeImageResource, setResourceWeight, toggleResourceLock, automaticLoopDuration, loopDurationFrames, activeObjectType, activeSection, canUseSection, activateSection, sectionVisible, sectionOpen, activeCollider, activePath, activeSpine, canCopyObject, selectedSpineBone, boundSpineBone, renderLayerSlotPickerOpen, renderLayerSlotSearch, renderLayerReadySpines, renderLayerSpineReady, renderLayerSlots, renderLayerFilteredSlots, renderLayerSlotLabel, renderLayerResolvedLabel, renderLayerWarning, setRenderLayerSlotMode, selectRenderLayerSpine, pickRenderLayerSlot, clearRenderLayerSlot, collisionPickerOpen, selectedCollisionBodies, availableCollisionBodies, addCollisionBody, removeCollisionBody, collapsed, toggle, setEmitMode, setLoopDurationFrames, setStreamBehavior, setLoopSection, setLoopSectionDuration, setLoopCustomDuration, onEmitterFollowModeChange, bindSelectedBone, resetModule, normalizeExportFps, select, addSys, addCollider, addPath, addSpine, copySelectedObject, applyPathPreset, clearPathAnchors, randomizeSeed, setSpeedOverLifeMode, prepareScaleOverLifeCurve, setScaleOverLifeMode, onShapeChange, onSpawnPathChange, LINEAR_CURVE, FLAT_CURVE, ALPHA_CURVE, RESOURCE_ALPHA_CURVE, TRAIL_ALPHA_CURVE, TRAIL_SHAPE_CURVE }
|
return { store, exportSettings, exportBusy, exportTarget, exportMessage, exportError, connectorPairUrl, connectorStatusText, connectorStatusClass, connectorReady, connectorAuthorizationUrl, connectorDisabledReason, connectorButtonTitle, refreshConnectorHealth, exportSpineJson, exportSpineProject, exportSpineImages, configFileInput, selectedPreset, presetOptions, presetLoading, configMessage, configError, localProjects, selectedLocalProjectName, selectLocalProject, projectSaveOpen, projectNameDraft, projectSaveError, projectStorageBusy, projectFilesInput, saveConfig, closeProjectSaveDialog, confirmSaveConfig, loadLocalProject, appendLocalProject, downloadLocalProject, downloadCurrentProject, removeLocalProject, downloadAllLocalProjects, loadAllProjectFiles, onConfigFileChange, refreshPresets, loadSelectedPreset, resolvedCurveEditorMode, sys, textureFileInput, sequenceFileInput, trailTextureFileInput, addTrailResource, openTrailTexturePicker, onTrailRigChange, onTrailResourceBoneCountChange, onTrailResourceRotationChange, onTrailTextureFile, resetTrailTexture, removeTrailResource, setTrailResourceWeight, toggleTrailResourceLock, setTrailColor, commitTrailColor, addImageResource, openTexturePicker, openSequencePicker, onTextureFile, onSequenceFiles, setImageMode, resetTexture, setResourceFixedColor, commitResourceFixedColor, removeImageResource, setResourceWeight, toggleResourceLock, automaticLoopDuration, loopDurationFrames, activeObjectType, activeSection, canUseSection, activateSection, sectionVisible, sectionOpen, activeCollider, activePath, activeSpine, canCopyObject, selectedSpineBone, boundSpineBone, renderLayerSlotPickerOpen, renderLayerSlotSearch, renderLayerReadySpines, renderLayerSpineReady, renderLayerSlots, renderLayerFilteredSlots, renderLayerSlotLabel, renderLayerResolvedLabel, renderLayerWarning, setRenderLayerSlotMode, selectRenderLayerSpine, pickRenderLayerSlot, clearRenderLayerSlot, collisionPickerOpen, selectedCollisionBodies, availableCollisionBodies, addCollisionBody, removeCollisionBody, collapsed, toggle, setEmitMode, setLoopDurationFrames, setStreamBehavior, setLoopSection, setLoopSectionDuration, setLoopCustomDuration, onEmitterFollowModeChange, bindSelectedBone, resetModule, normalizeExportFps, select, addSys, addCollider, addPath, addSpine, copySelectedObject, applyPathPreset, clearPathAnchors, randomizeSeed, setSpeedOverLifeMode, prepareScaleOverLifeCurve, setScaleOverLifeMode, onShapeChange, onSpawnPathChange, LINEAR_CURVE, FLAT_CURVE, ALPHA_CURVE, RESOURCE_ALPHA_CURVE, TRAIL_ALPHA_CURVE, TRAIL_SHAPE_CURVE }
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,7 @@ import ColorGradientEditor from '../ColorGradientEditor.vue'
|
|||||||
import ImageResourceCard from '../ImageResourceCard.vue'
|
import ImageResourceCard from '../ImageResourceCard.vue'
|
||||||
import SpineObjectPanel from '../../spine/SpineObjectPanel.vue'
|
import SpineObjectPanel from '../../spine/SpineObjectPanel.vue'
|
||||||
import { createEditorConfig, loadEditorConfigFile, loadEditorConfigText, parseEditorConfig } from '../../editor/editorConfig'
|
import { createEditorConfig, loadEditorConfigFile, loadEditorConfigText, parseEditorConfig } from '../../editor/editorConfig'
|
||||||
|
import { appendEditorConfigText } from '../../editor/appendSceneConfig'
|
||||||
import { beginTransfer, readResponseTextWithProgress } from '../../editor/transferProgress'
|
import { beginTransfer, readResponseTextWithProgress } from '../../editor/transferProgress'
|
||||||
import { defaultSpineExportSettings, ensureSpineExportSettings } from '../../export/spineExportSettings'
|
import { defaultSpineExportSettings, ensureSpineExportSettings } from '../../export/spineExportSettings'
|
||||||
import { buildSpineJson, downloadSpineJson } from '../../export/spineJsonExporter'
|
import { buildSpineJson, downloadSpineJson } from '../../export/spineJsonExporter'
|
||||||
@@ -128,6 +129,18 @@ async function loadLocalProject(project: LocalProject) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function appendLocalProject(project: LocalProject) {
|
||||||
|
configError.value = false
|
||||||
|
configMessage.value = translate('config.local.appending', { name: project.name })
|
||||||
|
try {
|
||||||
|
await appendEditorConfigText(store, project.text)
|
||||||
|
configMessage.value = translate('config.local.appended', { name: project.name })
|
||||||
|
} catch (error) {
|
||||||
|
configError.value = true
|
||||||
|
configMessage.value = error instanceof Error ? error.message : String(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function downloadLocalProject(project: LocalProject) {
|
function downloadLocalProject(project: LocalProject) {
|
||||||
const fileName = projectJsonFileName(project.name)
|
const fileName = projectJsonFileName(project.name)
|
||||||
downloadTextFile(project.text, fileName)
|
downloadTextFile(project.text, fileName)
|
||||||
@@ -280,7 +293,7 @@ onBeforeUnmount(() => window.removeEventListener(PROJECT_SAVE_EVENT, saveConfig)
|
|||||||
return {
|
return {
|
||||||
configFileInput, selectedPreset, presetOptions, presetLoading, configMessage, configError,
|
configFileInput, selectedPreset, presetOptions, presetLoading, configMessage, configError,
|
||||||
localProjects, selectedLocalProjectName, projectSaveOpen, projectNameDraft, projectSaveError, projectStorageBusy,
|
localProjects, selectedLocalProjectName, projectSaveOpen, projectNameDraft, projectSaveError, projectStorageBusy,
|
||||||
saveConfig, closeProjectSaveDialog, confirmSaveConfig, loadLocalProject, downloadLocalProject,
|
saveConfig, closeProjectSaveDialog, confirmSaveConfig, loadLocalProject, appendLocalProject, downloadLocalProject,
|
||||||
downloadCurrentProject, removeLocalProject, downloadAllLocalProjects, projectFilesInput, loadAllProjectFiles,
|
downloadCurrentProject, removeLocalProject, downloadAllLocalProjects, projectFilesInput, loadAllProjectFiles,
|
||||||
selectLocalProject, onConfigFileChange, refreshPresets, loadSelectedPreset,
|
selectLocalProject, onConfigFileChange, refreshPresets, loadSelectedPreset,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user