Document package formats, release workflows, deployment planning and UI concepts. Closes #1
178 lines
30 KiB
HTML
178 lines
30 KiB
HTML
<div id="msix-builder-concept">
|
|
<style>
|
|
#msix-builder-concept { font-family: "Segoe UI", "Malgun Gothic", sans-serif; color-scheme: light dark; }
|
|
#msix-builder-concept * { box-sizing: border-box; }
|
|
#msix-builder-concept [hidden] { display: none !important; }
|
|
#msix-builder-concept .ms-app { --ms-bg: light-dark(#f6f7fa,#171b23); --ms-paper: light-dark(#ffffff,#202630); --ms-text: light-dark(#202a39,#edf1f8); --ms-muted: light-dark(#59677b,#aebacf); --ms-line: light-dark(#dce2eb,#3a4353); --ms-accent: light-dark(#245bd7,#96b7ff); --ms-soft: light-dark(#edf3ff,#273752); --ms-button: light-dark(#245bd7,#a9c3ff); --ms-onbutton: light-dark(#ffffff,#152445); --ms-good: light-dark(#17654c,#8bd7b6); --ms-warn: light-dark(#805011,#edc27e); --ms-error: light-dark(#a52c33,#ffabb0); color:var(--ms-text); background:var(--ms-bg); border:1px solid var(--ms-line); border-radius:12px; overflow:hidden; min-height:940px; display:flex; flex-direction:column; font-size:14px; line-height:1.55; }
|
|
#msix-builder-concept button, #msix-builder-concept input, #msix-builder-concept select { font:inherit; }
|
|
#msix-builder-concept button { color:inherit; cursor:inherit; }
|
|
#msix-builder-concept .ms-top { display:flex; align-items:center; gap:12px; padding:16px 22px; padding-right:104px; border-bottom:1px solid var(--ms-line); background:var(--ms-paper); flex-wrap:wrap; }
|
|
#msix-builder-concept .ms-brand { font-size:16px; font-weight:600; display:flex; gap:10px; align-items:center; }
|
|
#msix-builder-concept .ms-mark { display:grid; place-items:center; width:32px; height:32px; border-radius:9px; background:var(--ms-button); color:var(--ms-onbutton); }
|
|
#msix-builder-concept .ms-pill { padding:3px 9px; border-radius:5px; background:var(--ms-soft); color:var(--ms-accent); font-size:12px; }
|
|
#msix-builder-concept .ms-caption { color:var(--ms-muted); font-size:12px; }
|
|
#msix-builder-concept .ms-spacer { flex:1; }
|
|
#msix-builder-concept .ms-layout { flex:1; display:grid; grid-template-columns:190px minmax(0,1fr); }
|
|
#msix-builder-concept .ms-nav { padding:23px 12px; border-right:1px solid var(--ms-line); }
|
|
#msix-builder-concept .ms-project { padding:0 12px 23px; overflow-wrap:anywhere; }
|
|
#msix-builder-concept .ms-project strong { display:block; font-weight:600; margin:4px 0; }
|
|
#msix-builder-concept .ms-nav button { display:flex; align-items:center; gap:11px; width:100%; border:0; background:transparent; padding:11px 12px; text-align:left; border-radius:7px; margin-bottom:5px; }
|
|
#msix-builder-concept .ms-nav button[aria-current="step"] { background:var(--ms-soft); color:var(--ms-accent); font-weight:600; }
|
|
#msix-builder-concept .ms-nav svg { flex:none; }
|
|
#msix-builder-concept .ms-nav .ms-nav-end { margin:30px 12px 0; padding-top:15px; border-top:1px solid var(--ms-line); }
|
|
#msix-builder-concept .ms-content { min-width:0; padding:27px 28px 20px; }
|
|
#msix-builder-concept h2 { margin:5px 0 7px; font-size:25px; line-height:1.35; font-weight:600; letter-spacing:-.6px; }
|
|
#msix-builder-concept h3 { margin:0 0 17px; font-size:15px; font-weight:600; }
|
|
#msix-builder-concept p { margin:0; }
|
|
#msix-builder-concept .ms-eyebrow { font-size:11px; letter-spacing:1.4px; font-weight:600; color:var(--ms-accent); }
|
|
#msix-builder-concept .ms-lead { color:var(--ms-muted); margin-bottom:25px; }
|
|
#msix-builder-concept .ms-cols { display:grid; grid-template-columns:minmax(0,1.65fr) minmax(190px,1fr); gap:20px; align-items:start; }
|
|
#msix-builder-concept .ms-panel { background:var(--ms-paper); border:1px solid var(--ms-line); border-radius:10px; padding:20px; min-width:0; }
|
|
#msix-builder-concept .ms-fields { display:grid; grid-template-columns:1fr 1fr; gap:17px; }
|
|
#msix-builder-concept .ms-full { grid-column:1/-1; }
|
|
#msix-builder-concept label.ms-field { display:flex; flex-direction:column; gap:6px; font-size:12px; font-weight:600; }
|
|
#msix-builder-concept input:not([type="checkbox"]), #msix-builder-concept select { width:100%; min-width:0; padding:9px 10px; border:1px solid var(--ms-line); border-radius:6px; color:var(--ms-text); background:var(--ms-paper); font-weight:400; }
|
|
#msix-builder-concept input:focus-visible, #msix-builder-concept select:focus-visible, #msix-builder-concept button:focus-visible { outline:2px solid var(--ms-accent); outline-offset:3px; }
|
|
#msix-builder-concept .ms-field small { color:var(--ms-muted); font-size:11px; font-weight:400; }
|
|
#msix-builder-concept .ms-line { height:1px; background:var(--ms-line); margin:20px 0; }
|
|
#msix-builder-concept .ms-note { margin-top:16px; padding:12px 14px; background:var(--ms-soft); border-radius:6px; color:var(--ms-muted); font-size:12px; }
|
|
#msix-builder-concept .ms-appicon { display:grid; place-items:center; width:56px; height:56px; border-radius:13px; background:var(--ms-button); color:var(--ms-onbutton); margin-bottom:17px; }
|
|
#msix-builder-concept .ms-appicon svg { width:27px; height:27px; }
|
|
#msix-builder-concept .ms-preview-name { font-size:19px; font-weight:600; overflow-wrap:anywhere; margin-bottom:4px; }
|
|
#msix-builder-concept .ms-preview-description { font-size:12px; color:var(--ms-muted); min-height:38px; overflow-wrap:anywhere; }
|
|
#msix-builder-concept .ms-kv { display:flex; align-items:flex-start; gap:14px; justify-content:space-between; font-size:12px; padding:8px 0; }
|
|
#msix-builder-concept .ms-kv span:first-child { color:var(--ms-muted); flex-shrink:0; }
|
|
#msix-builder-concept .ms-kv span:last-child { overflow-wrap:anywhere; text-align:right; min-width:0; }
|
|
#msix-builder-concept .ms-button { display:inline-flex; justify-content:center; align-items:center; gap:7px; background:var(--ms-paper); border:1px solid var(--ms-line); border-radius:6px; padding:9px 14px; font-weight:500; min-height:39px; }
|
|
#msix-builder-concept .ms-button.primary { background:var(--ms-button); border-color:transparent; color:var(--ms-onbutton); }
|
|
#msix-builder-concept .ms-button:disabled { opacity:.5; cursor:not-allowed; }
|
|
#msix-builder-concept .ms-button.full { width:100%; margin-top:14px; }
|
|
#msix-builder-concept .ms-footer { display:flex; gap:12px; justify-content:space-between; align-items:center; margin-top:24px; flex-wrap:wrap; }
|
|
#msix-builder-concept .ms-actions { display:flex; gap:8px; flex-wrap:wrap; }
|
|
#msix-builder-concept .ms-status { font-size:12px; color:var(--ms-muted); }
|
|
#msix-builder-concept .ms-check { display:flex; gap:10px; align-items:flex-start; margin:13px 0; }
|
|
#msix-builder-concept input[type="checkbox"] { width:17px; height:17px; accent-color:var(--ms-accent); margin:3px 0 0; flex:none; }
|
|
#msix-builder-concept .ms-check small { display:block; font-size:12px; color:var(--ms-muted); margin-top:3px; }
|
|
#msix-builder-concept .ms-toolbar { display:flex; justify-content:space-between; gap:10px; align-items:center; margin-bottom:16px; flex-wrap:wrap; }
|
|
#msix-builder-concept .ms-toolbar h3 { margin:0; }
|
|
#msix-builder-concept .ms-file { display:flex; align-items:center; gap:11px; padding:13px 0; border-bottom:1px solid var(--ms-line); min-width:0; }
|
|
#msix-builder-concept .ms-file:last-child { border-bottom:0; }
|
|
#msix-builder-concept .ms-file .ms-filename { flex:1; min-width:0; overflow-wrap:anywhere; }
|
|
#msix-builder-concept .ms-file small { display:block; color:var(--ms-muted); font-size:11px; }
|
|
#msix-builder-concept .ms-file > span:last-child { font-size:12px; color:var(--ms-muted); white-space:nowrap; }
|
|
#msix-builder-concept .ms-file svg { color:var(--ms-muted); flex:none; }
|
|
#msix-builder-concept .ms-choice { display:flex; align-items:center; gap:10px; padding:14px; border:1px solid var(--ms-line); border-radius:7px; margin-bottom:10px; }
|
|
#msix-builder-concept .ms-choice.active { border-color:var(--ms-accent); background:var(--ms-soft); }
|
|
#msix-builder-concept .ms-choice strong { display:block; font-size:13px; font-weight:600; }
|
|
#msix-builder-concept .ms-choice small { color:var(--ms-muted); font-size:12px; }
|
|
#msix-builder-concept .ms-good { color:var(--ms-good); }
|
|
#msix-builder-concept .ms-warn { color:var(--ms-warn); }
|
|
#msix-builder-concept .ms-error { color:var(--ms-error); }
|
|
#msix-builder-concept .ms-alert { padding:13px 15px; border:1px solid var(--ms-line); border-left:3px solid var(--ms-error); background:var(--ms-paper); border-radius:6px; margin-bottom:17px; font-size:12px; }
|
|
#msix-builder-concept .ms-alert button { margin-top:10px; }
|
|
#msix-builder-concept .ms-stage { display:flex; align-items:flex-start; gap:12px; padding:14px 0; border-bottom:1px solid var(--ms-line); }
|
|
#msix-builder-concept .ms-stage:last-child { border:0; }
|
|
#msix-builder-concept .ms-stage .ms-stage-title { flex:1; min-width:0; }
|
|
#msix-builder-concept .ms-stage small { display:block; color:var(--ms-muted); font-size:12px; }
|
|
#msix-builder-concept .ms-stage > span:last-child { font-size:12px; }
|
|
#msix-builder-concept .ms-meter { height:6px; border-radius:4px; background:var(--ms-line); overflow:hidden; margin:12px 0; }
|
|
#msix-builder-concept .ms-meter div { height:100%; background:var(--ms-accent); }
|
|
#msix-builder-concept .ms-code { font:12px/1.8 Consolas,monospace; white-space:pre-wrap; overflow-wrap:anywhere; color:var(--ms-muted); margin:0; }
|
|
#msix-builder-concept .ms-bottom { display:flex; gap:10px; justify-content:space-between; padding:11px 22px; border-top:1px solid var(--ms-line); background:var(--ms-paper); flex-wrap:wrap; font-size:11px; color:var(--ms-muted); }
|
|
#msix-builder-concept .ms-steps { display:grid; grid-template-columns:repeat(6,1fr); gap:8px; padding:22px 26px 0; }
|
|
#msix-builder-concept .ms-steps button { border:0; background:transparent; padding:8px 0; display:flex; flex-direction:column; align-items:center; gap:7px; color:var(--ms-muted); font-size:12px; }
|
|
#msix-builder-concept .ms-step-dot { width:27px; height:27px; border:1px solid var(--ms-line); border-radius:50%; display:grid; place-items:center; background:var(--ms-paper); font-size:12px; }
|
|
#msix-builder-concept .ms-steps button[aria-current="step"] { color:var(--ms-accent); }
|
|
#msix-builder-concept .ms-steps button[aria-current="step"] .ms-step-dot { background:var(--ms-button); color:var(--ms-onbutton); border-color:transparent; }
|
|
#msix-builder-concept .ms-guided .ms-content { flex:1; padding:24px 40px; }
|
|
#msix-builder-concept .ms-guided .ms-cols { grid-template-columns:minmax(0,1.6fr) minmax(210px,1fr); }
|
|
#msix-builder-concept .ms-guided { --ms-accent:light-dark(#146d60,#82d7c6); --ms-soft:light-dark(#eaf5f2,#263e3a); --ms-button:light-dark(#146d60,#9ae1d2); --ms-onbutton:light-dark(#ffffff,#14332e); }
|
|
@media(max-width:800px) { #msix-builder-concept .ms-layout { grid-template-columns:154px minmax(0,1fr); } #msix-builder-concept .ms-content { padding:22px 18px; } #msix-builder-concept .ms-cols, #msix-builder-concept .ms-guided .ms-cols { grid-template-columns:1fr; } #msix-builder-concept .ms-side { display:grid; grid-template-columns:1fr 1fr; gap:16px; } #msix-builder-concept .ms-side > .ms-caption { grid-column:1/-1; } #msix-builder-concept .ms-guided .ms-content { padding:22px; } }
|
|
@media(max-width:520px) { #msix-builder-concept .ms-app { min-height:1900px; } #msix-builder-concept .ms-layout { grid-template-columns:1fr; } #msix-builder-concept .ms-nav { border-right:0; border-bottom:1px solid var(--ms-line); padding:12px; display:grid; grid-template-columns:1fr 1fr; gap:3px; } #msix-builder-concept .ms-project, #msix-builder-concept .ms-nav-end { display:none; } #msix-builder-concept .ms-nav button { padding:8px; margin:0; font-size:12px; } #msix-builder-concept .ms-fields, #msix-builder-concept .ms-side { grid-template-columns:1fr; } #msix-builder-concept .ms-top { padding:14px 16px; padding-right:96px; } #msix-builder-concept .ms-top .ms-caption { display:none; } #msix-builder-concept .ms-content, #msix-builder-concept .ms-guided .ms-content { padding:20px 14px; } #msix-builder-concept .ms-panel { padding:16px; } #msix-builder-concept .ms-steps { padding:12px; grid-template-columns:repeat(3,1fr); } #msix-builder-concept .ms-footer { align-items:stretch; } #msix-builder-concept .ms-actions { flex:1; justify-content:flex-end; } #msix-builder-concept .ms-bottom { padding:10px 14px; } }
|
|
@media(pointer:coarse) { #msix-builder-concept .ms-button, #msix-builder-concept .ms-nav button, #msix-builder-concept .ms-steps button { min-height:44px; } #msix-builder-concept input:not([type="checkbox"]), #msix-builder-concept select { font-size:16px; } }
|
|
</style>
|
|
<div class="viz-carousel" aria-label="MSIX 제작기 디자인" data-previous-label="이전 시안" data-next-label="다음 시안">
|
|
<section data-variant="프로젝트 작업실" aria-label="프로젝트 작업실">
|
|
<div class="ms-app" data-app="studio"></div>
|
|
</section>
|
|
<section data-variant="단계별 제작" aria-label="단계별 제작" hidden>
|
|
<div class="ms-app ms-guided" data-app="guided"></div>
|
|
</section>
|
|
</div>
|
|
<script>
|
|
(() => {
|
|
const root = document.getElementById('msix-builder-concept');
|
|
const pages = [['app','앱 정보','panels-top-left'],['files','파일 구성','folder-open'],['manifest','패키지 설정','package'],['sign','서명','shield-check'],['update','업데이트','refresh-cw'],['build','검증 · 빌드','hammer']];
|
|
const defaults = () => ({page:'app',name:'Orbit Notes',description:'팀의 아이디어를 한곳에',publisherName:'Example Studio',identity:'ExampleStudio.OrbitNotes',publisher:'CN=Example Studio',version:'1.2.0.0',arch:'x64',exe:'OrbitNotes.exe',minOs:'10.0.19041.0',sign:'development',certSubject:'CN=Example Studio',autoUpdate:false,url:'https://downloads.example.com/orbit/stable/OrbitNotes.appinstaller',interval:'24',prompt:true,extraFile:false,revision:1,validated:false,build:'idle',log:false,message:'',error:''});
|
|
const state = {studio:defaults(),guided:defaults()};
|
|
const sessionId = Date.now().toString(36) + Math.random().toString(36).slice(2);
|
|
const esc = v => String(v).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
const icon = name => `<i data-lucide="${name}" aria-hidden="true"></i>`;
|
|
const field = (s,key,label,full=false,hint='') => `<label class="ms-field ${full?'ms-full':''}">${label}<input data-field="${key}" value="${esc(s[key])}" ${key==='url'?'type="url"':'type="text"'}>${hint?`<small>${hint}</small>`:''}</label>`;
|
|
const select = (s,key,label,opts) => `<label class="ms-field">${label}<select data-field="${key}">${opts.map(([v,l])=>`<option value="${v}" ${s[key]===v?'selected':''}>${l}</option>`).join('')}</select></label>`;
|
|
const check = (s,key,label,hint='') => `<label class="ms-check"><input type="checkbox" data-field="${key}" ${s[key]?'checked':''}><span>${label}${hint?`<small>${hint}</small>`:''}</span></label>`;
|
|
const kv=(a,b)=>`<div class="ms-kv"><span>${a}</span><span>${b}</span></div>`;
|
|
function validate(s) {
|
|
if(!s.name.trim()||!s.identity.trim()||!s.publisher.trim()) return ['app','제품 이름과 패키지 식별 정보를 입력해 주세요.'];
|
|
if(!/^\d+\.\d+\.\d+\.\d+$/.test(s.version)||s.version.split('.').some(n=>Number(n)>65535)||Number(s.version.split('.')[0])===0) return ['app','버전을 1.2.0.0처럼 네 부분 숫자로 입력해 주세요.'];
|
|
if(!['OrbitNotes.exe'].includes(s.exe)) return ['files','실행 파일이 패키지 목록에 없습니다. OrbitNotes.exe를 지정해 주세요.'];
|
|
if(s.sign!=='none' && s.publisher!==s.certSubject) return ['sign','Publisher와 인증서 Subject가 일치하지 않습니다.'];
|
|
if(s.autoUpdate) { try { const u=new URL(s.url); if(u.protocol!=='https:'||!u.pathname.endsWith('.appinstaller')) throw Error(); } catch { return ['update','HTTPS 주소와 .appinstaller 파일 이름을 확인해 주세요.']; } }
|
|
return null;
|
|
}
|
|
function restore(saved) {
|
|
if(!saved||saved.schema!==1)return;
|
|
['studio','guided'].forEach(k=>{
|
|
const d=defaults(), v=saved[k];if(!v||typeof v!=='object')return;
|
|
Object.keys(d).forEach(f=>{if(typeof v[f]===typeof d[f]&&(!['string'].includes(typeof v[f])||v[f].length<1000))d[f]=v[f];});
|
|
if(!pages.some(p=>p[0]===d.page))d.page='app';
|
|
if(!['development','production','none'].includes(d.sign))d.sign='development';
|
|
d.validated=false;d.build='idle';d.error='';d.message='';state[k]=d;
|
|
});
|
|
}
|
|
restore(window.openai?.widgetState?.privateContent);
|
|
function persist(){ const host=window.openai;if(host?.setWidgetState)host.setWidgetState({modelContent:{design:'MSIX-first builder',studioPage:state.studio.page,guidedPage:state.guided.page},privateContent:{schema:1,sessionId,...state}}).catch(()=>{}); }
|
|
function preview(s) {
|
|
return `<aside class="ms-side"><div class="ms-panel"><h3>앱 표시 미리보기</h3><div class="ms-appicon">${icon('notebook-pen')}</div><div class="ms-preview-name">${esc(s.name||'앱 이름')}</div><p class="ms-preview-description">${esc(s.description)}</p><div class="ms-line"></div>${kv('게시자',esc(s.publisherName))}${kv('버전',esc(s.version))}${kv('아키텍처',esc(s.arch))}${kv('설치 위치','Windows에서 관리')}<div class="ms-note">설치 폴더 선택 없이 패키지로 배포합니다.</div></div><p class="ms-caption" style="margin-top:12px">표시 정보 예시입니다. 실제 App Installer 화면은 Windows 환경에 따라 달라집니다.</p></aside>`;
|
|
}
|
|
function side(s,title,body){return `<aside class="ms-side"><div class="ms-panel"><h3>${title}</h3>${body}</div></aside>`;}
|
|
function pane(s) {
|
|
if(s.page==='app')return `<div class="ms-cols"><div class="ms-panel"><h3>사용자에게 표시할 정보</h3><div class="ms-fields">${field(s,'name','앱 이름',true)}${field(s,'description','설명',true)}${field(s,'publisherName','게시자 표시명',true)}${field(s,'version','패키지 버전',false,'네 부분 숫자 버전')}${select(s,'arch','아키텍처',[['x64','x64'],['arm64','ARM64']])}</div><div class="ms-line"></div><div class="ms-toolbar"><span>앱 아이콘</span><span class="ms-caption">Assets / Logo.png · 예시</span></div><p class="ms-caption">실제 제품에서는 로고 파일과 크기를 검사합니다.</p></div>${preview(s)}</div>`;
|
|
if(s.page==='files')return `<div class="ms-cols"><div class="ms-panel"><div class="ms-toolbar"><h3>패키지에 포함할 파일</h3><button class="ms-button cursor-interaction" data-action="add-file">${icon(s.extraFile?'minus':'plus')}${s.extraFile?'예제 파일 제거':'예제 파일 추가'}</button></div><p class="ms-caption">소스 폴더 · D:\\releases\\orbit-notes</p>${[['OrbitNotes.exe','앱 실행 파일','18.4 MB'],['Orbit.Core.dll','애플리케이션 라이브러리','6.2 MB'],['Assets/Logo.png','앱 이미지 · 예시','24 KB'],['Resources/ko-KR.json','한국어 리소스','12 KB'],...(s.extraFile?[['Resources/en-US.json','추가한 예제 파일','10 KB']]:[])].map(([n,d,z])=>`<div class="ms-file">${icon(n.endsWith('exe')?'file-code':'file')}<div class="ms-filename">${esc(n)}<small>${d}</small></div><span>${z}</span></div>`).join('')}<div class="ms-line"></div>${field(s,'exe','실행 파일',true,'패키지 내부 상대 경로')}</div>${side(s,'파일 구성 원칙',`${kv('포함 파일',`${s.extraFile?5:4}개 · 예시`)}${kv('배치 위치','패키지 내부')}${kv('원본 파일','변경하지 않음')}<div class="ms-note">문서·설정·로그는 앱 데이터 위치를 사용합니다. 설치 폴더 쓰기에 의존하는 앱은 호환성을 확인해야 합니다.</div>`)}</div>`;
|
|
if(s.page==='manifest')return `<div class="ms-cols"><div class="ms-panel"><h3>패키지 식별 정보</h3><div class="ms-fields">${field(s,'identity','패키지 이름 · Identity Name',true)}${field(s,'publisher','Publisher',true,'서명 인증서의 Subject와 일치해야 합니다.')}${select(s,'minOs','최소 Windows 버전',[['10.0.19041.0','Windows 10 · 2004 이상'],['10.0.22000.0','Windows 11 이상']])}</div><div class="ms-line"></div><h3>앱 실행 방식</h3><div class="ms-choice active">${icon('monitor')}<div><strong>일반 데스크톱 앱</strong><small>Windows.FullTrustApplication</small></div></div><p class="ms-caption">runFullTrust 선언을 포함합니다. 서비스·드라이버·임의 설치 작업은 초기 지원 범위에 포함하지 않습니다.</p></div>${side(s,'생성할 manifest',`<pre class="ms-code"><Identity\n Name="${esc(s.identity)}"\n Publisher="${esc(s.publisher)}"\n Version="${esc(s.version)}"\n ProcessorArchitecture="${esc(s.arch)}" /></pre><div class="ms-note">미리보기는 요약입니다. 실제 생성 시 SDK 스키마·자산·의존성을 함께 검사합니다.</div>`)}</div>`;
|
|
if(s.page==='sign')return `<div class="ms-cols"><div class="ms-panel"><h3>서명 프로필</h3>${select(s,'sign','사용할 프로필',[['development','개발용 인증서 · 예시'],['production','운영 서명 프로필 · 예시'],['none','미서명 개발 빌드']])}<div class="ms-line"></div>${s.sign==='none'?'<p class="ms-warn">미서명 패키지는 직접 배포 준비 상태가 될 수 없습니다.</p>':`${field(s,'certSubject','인증서 Subject · 예시',true)}<div class="ms-line"></div>${kv('패키지 Publisher',esc(s.publisher))}${kv('이름 일치',s.publisher===s.certSubject?'<span class="ms-good">일치</span>':'<span class="ms-error">불일치</span>')}`}<div class="ms-note">개인 키나 인증서 암호를 프로젝트에 저장하지 않습니다. 실제 제품에서는 OS 또는 서명 서비스 프로필을 참조합니다.</div></div>${side(s,'배포 전 확인',`<div class="ms-stage">${icon('shield-check')}<div class="ms-stage-title">패키지 서명<small>서명자·체인·타임스탬프 검사</small></div></div><div class="ms-stage">${icon('monitor-check')}<div class="ms-stage-title">대상 PC 신뢰<small>개발용 인증서 성공과 구분</small></div></div><div class="ms-note">이 시안은 인증서 저장소에 접근하거나 서명하지 않습니다.</div>`)}</div>`;
|
|
if(s.page==='update')return `<div class="ms-cols"><div class="ms-panel"><h3>직접 배포</h3><div class="ms-choice active">${icon('globe')}<div><strong>내 서버에서 배포</strong><small>Store 계정 없이 MSIX를 전달합니다.</small></div></div>${check(s,'autoUpdate','App Installer 업데이트 사용','MSIX와 함께 .appinstaller 파일을 생성합니다.')}${s.autoUpdate?`<div class="ms-line"></div><div class="ms-fields">${field(s,'url','업데이트 안내 파일 주소',true,'패키지 파일은 버전별 고정 URL을 사용합니다.')}${select(s,'interval','실행 시 확인 간격',[['0','앱 실행마다'],['24','24시간'],['48','48시간']])}</div>${check(s,'prompt','업데이트 안내 표시')}`:'<div class="ms-note">MSIX 파일을 직접 전달합니다. 자동 업데이트 경로는 연결되지 않습니다.</div>'}</div>${side(s,'생성할 결과',`${kv('설치 패키지','.msix')}${kv('업데이트 안내',s.autoUpdate?'.appinstaller':'사용 안 함')}${kv('업데이트 적용','Windows 배포 기능')}<div class="ms-note">새 버전 전체 패키지를 생성합니다. Windows의 차등 업데이트를 이용하며 별도 MSP 파일을 만들지 않습니다.</div><p class="ms-caption" style="margin-top:14px">Store 등록·자동 업로드·그룹 배포 서버는 후속 단계입니다.</p>`)}</div>`;
|
|
const completed=s.build==='done',failed=s.build==='failed',ready=s.validated;
|
|
const stages=[['설정과 파일 검사','manifest · 입력 · 경로','list-checks'],['MSIX 패키징','Windows SDK · MakeAppx','package'],['서명과 무결성',s.sign==='none'?'미서명 개발 결과':'서명 프로필 · 해시','shield-check'],['결과 보관','불변 아티팩트 · 검증 보고서','archive']];
|
|
return `<div class="ms-cols"><div class="ms-panel"><div class="ms-toolbar"><h3>빌드 계획</h3><span class="ms-pill">${completed?'모의 빌드 완료':failed?'검증 오류':ready?'모의 검사 통과':'검사 대기'}</span></div>${stages.map(([t,d,i],n)=>`<div class="ms-stage">${icon(completed?'circle-check':i)}<div class="ms-stage-title">${t}<small>${d}</small></div><span class="${completed?'ms-good':'ms-caption'}">${completed?(s.sign==='none'&&n===2?'서명 생략':'모의 완료'):'대기'}</span></div>`).join('')}<div class="ms-meter" role="progressbar" aria-label="모의 빌드 진행" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${completed?100:0}"><div style="width:${completed?100:0}%"></div></div><div class="ms-actions"><button class="ms-button cursor-interaction" data-action="validate">설정 검사</button><button class="ms-button primary cursor-interaction" data-action="build">${icon('play')}모의 빌드</button></div>${completed?`<div class="ms-line"></div><h3>결과 예시</h3><pre class="ms-code">OrbitNotes_${esc(s.version)}_${esc(s.arch)}.msix\n${s.autoUpdate?'OrbitNotes.appinstaller\n':''}release.json\nvalidation-report.json</pre><button class="ms-button cursor-interaction" data-action="log" style="margin-top:14px">${s.log?'기록 닫기':'모의 기록 보기'}</button>${s.log?'<pre class="ms-code" style="margin-top:12px">[예시] 입력 스냅샷 고정\n[예시] MakeAppx 단계 완료\n[예시] 서명 정책 적용\n[예시] 결과 목록 생성\n실제 명령 실행 및 파일 생성 없음</pre>':''}`:''}</div>${side(s,'릴리스 준비 상태',`${kv('패키지 생성',completed?'모의 완료':'대기')}${kv('서명',s.sign==='none'?'생략':completed?'모의 완료':'대기')}${kv('설치·실행 시험','미실행')}${kv('업데이트·제거 시험','미실행')}<div class="ms-note">${s.sign==='development'?'개발용 인증서로 설정되어 있습니다. 실제 사용자 배포에는 대상 PC 신뢰와 설치 시험이 필요합니다.':'빌드와 배포 준비는 별도 상태입니다. 실제 VM 검증 증빙이 필요합니다.'}</div>`)}</div>`;
|
|
}
|
|
const descriptions={app:'앱 이름과 버전을 입력하면 패키지 표시 정보를 미리 확인할 수 있습니다.',files:'배포할 파일을 모으고 앱의 실행 파일을 지정합니다.',manifest:'Windows가 앱을 식별할 이름과 실행 조건을 설정합니다.',sign:'패키지 게시자와 서명 프로필을 연결합니다.',update:'MSIX만 전달하거나, 내 서버를 통한 업데이트 경로를 연결합니다.',build:'생성 전 설정을 검사하고 패키지와 검증 결과를 확인합니다.'};
|
|
function render(k) {
|
|
const app=root.querySelector(`[data-app="${k}"]`),s=state[k],idx=pages.findIndex(p=>p[0]===s.page);
|
|
const nav=pages.map(([id,label,ic],n)=>`<button class="cursor-interaction" data-page="${id}" ${s.page===id?'aria-current="step"':''}>${k==='guided'?`<span class="ms-step-dot">${n+1}</span>`:icon(ic)}<span>${label}</span></button>`).join('');
|
|
const error=s.error?`<div class="ms-alert" role="alert">${esc(s.error)}${s.errorPage?`<br><button class="ms-button cursor-interaction" data-page="${esc(s.errorPage)}">해당 설정으로 이동</button>`:''}</div>`:'';
|
|
app.innerHTML=`<header class="ms-top"><div class="ms-brand"><span class="ms-mark">${icon('package')}</span>MSIX Studio</div><span class="ms-pill">디자인 시안</span><span class="ms-spacer"></span><span class="ms-caption">직접 배포용 패키지 제작</span></header>${k==='guided'?`<nav class="ms-steps" aria-label="제작 단계">${nav}</nav>`:'<div class="ms-layout">'+`<nav class="ms-nav" aria-label="프로젝트 설정"><div class="ms-project"><span class="ms-caption">현재 프로젝트</span><strong>${esc(s.name)}</strong><span class="ms-pill">MSIX · ${esc(s.arch)}</span></div>${nav}<div class="ms-nav-end ms-caption">Windows 패키지 제작<br>단일 제품 · 단일 아키텍처</div></nav>`}<main class="ms-content"><div class="ms-eyebrow">${k==='guided'?`STEP ${idx+1} OF 6`:'PACKAGE WORKSPACE'}</div><h2>${pages[idx][1]}</h2><p class="ms-lead">${descriptions[s.page]}</p>${error}${pane(s)}<footer class="ms-footer"><div class="ms-status" role="status" aria-live="polite">${esc(s.message||'예제 데이터 · 실제 파일 접근 없음')}</div><div class="ms-actions">${idx>0?'<button class="ms-button cursor-interaction" data-action="prev">이전</button>':''}${idx<5?'<button class="ms-button primary cursor-interaction" data-action="next">다음 단계 '+icon('arrow-right')+'</button>':''}</div></footer></main>${k==='studio'?'</div>':''}<div class="ms-bottom"><span>프로젝트 변경 ${s.revision} · ${esc(s.version)}</span><span>실제 패키지 생성·서명·설치 없음</span></div>`;
|
|
if(globalThis.lucide)globalThis.lucide.createIcons({attrs:{width:16,height:16}});
|
|
}
|
|
function runValidation(s){const err=validate(s);s.error=err?err[1]:'';s.errorPage=err?err[0]:'';s.validated=!err;s.message=err?'설정을 수정한 후 다시 검사해 주세요.':'모의 설정 검사 통과 · SDK와 실제 파일 검사는 미실행';return !err;}
|
|
root.addEventListener('click',e=>{
|
|
const b=e.target.closest('button'),app=e.target.closest('[data-app]');if(!b||!app)return;const k=app.dataset.app,s=state[k];
|
|
if(b.dataset.page){s.page=b.dataset.page;s.error='';s.errorPage='';}
|
|
const a=b.dataset.action,idx=pages.findIndex(p=>p[0]===s.page);
|
|
if(a==='next'||a==='prev'){s.page=pages[Math.max(0,Math.min(5,idx+(a==='next'?1:-1)))][0];s.error='';s.errorPage='';}
|
|
if(a==='add-file'){s.extraFile=!s.extraFile;s.revision++;s.validated=false;s.build='idle';s.message=s.extraFile?'예제 파일을 추가했습니다.':'추가한 예제 파일을 제거했습니다.';}
|
|
if(a==='validate')runValidation(s);
|
|
if(a==='build'){s.build=runValidation(s)?'done':'failed';if(s.build==='done')s.message='모의 빌드 완료 · 실제 산출물과 설치 증빙은 없습니다.';}
|
|
if(a==='log')s.log=!s.log;
|
|
render(k);persist();
|
|
});
|
|
root.addEventListener('change',e=>{
|
|
const f=e.target.dataset.field,app=e.target.closest('[data-app]');if(!f||!app)return;const k=app.dataset.app,s=state[k];s[f]=e.target.type==='checkbox'?e.target.checked:e.target.value;s.revision++;s.validated=false;s.build='idle';s.error='';s.errorPage='';s.message='설정이 변경되었습니다. 빌드 전 다시 검사합니다.';render(k);persist();
|
|
});
|
|
window.addEventListener('openai:set_globals',e=>{const saved=e.detail?.globals?.widgetState?.privateContent;if(!saved||saved.schema!==1||saved.sessionId===sessionId)return;restore(saved);render('studio');render('guided');});
|
|
render('studio');render('guided');
|
|
if(globalThis.Tweak){['studio','guided'].forEach(k=>{const app=root.querySelector(`[data-app="${k}"]`),design={corners:12};const tweak=new Tweak({container:app,onChange:()=>{app.style.borderRadius=design.corners+'px';}});tweak.addSlider(design,'corners',{label:'창 모서리',min:0,max:20,step:2,unit:'px'});});}
|
|
})();
|
|
</script>
|
|
</div>
|