123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003 |
- <!DOCTYPE html>
- <html>
- <head>
- <title>BoxJs</title>
- <meta charset="utf-8" />
- <meta name="apple-mobile-web-app-capable" content="yes" />
- <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
- <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover" />
- <link rel="Bookmark" href="https://raw.githubusercontent.com/chavyleung/scripts/master/BOXJS.png" />
- <link rel="shortcut icon" href="https://raw.githubusercontent.com/chavyleung/scripts/master/BOXJS.png" />
- <link rel="apple-touch-icon" href="https://raw.githubusercontent.com/chavyleung/scripts/master/BOXJS.png" />
- <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet" />
- <link href="https://cdn.jsdelivr.net/npm/@mdi/[email protected]/css/materialdesignicons.min.css" rel="stylesheet" />
- <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.min.css" rel="stylesheet" />
- <script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
- <style>
- #BG {
- position: fixed;
- top: 0;
- left: 0;
- bottom: 0;
- right: 0;
- z-index: 0;
- background-position: center;
- background-size: cover;
- background-repeat: no-repeat;
- background-color: transparent;
- }
- @media (prefers-color-scheme: light) {
- body {
- background-color: #fff;
- }
- }
- @media (prefers-color-scheme: dark) {
- body {
- background-color: #121212;
- }
- }
- [v-cloak] {
- display: none;
- }
- .v-navigation-drawer {
- padding-top: constant(safe-area-inset-top) !important;
- padding-top: env(safe-area-inset-top) !important;
- }
- .v-bottom-sheet.v-dialog--fullscreen {
- padding-top: constant(safe-area-inset-top) !important;
- padding-top: env(safe-area-inset-top) !important;
- }
- .v-app-bar.safe {
- height: auto !important;
- padding-top: constant(safe-area-inset-top) !important;
- padding-top: env(safe-area-inset-top) !important;
- }
- .v-toolbar.safe {
- height: auto !important;
- padding-top: constant(safe-area-inset-top) !important;
- padding-top: env(safe-area-inset-top) !important;
- }
- .v-toolbar__content {
- padding-left: 12px !important;
- padding-right: 12px !important;
- }
- .v-main {
- margin-top: constant(safe-area-inset-top) !important;
- margin-top: env(safe-area-inset-top) !important;
- margin-bottom: constant(safe-area-inset-bottom) !important;
- margin-bottom: env(safe-area-inset-bottom) !important;
- }
- .v-main .container {
- height: 100%;
- }
- .v-bottom-navigation,
- .v-bottom-sheet {
- padding-bottom: constant(safe-area-inset-bottom);
- padding-bottom: env(safe-area-inset-bottom);
- }
- .v-bottom-navigation {
- box-sizing: content-box;
- }
- .v-bottom-navigation button {
- box-sizing: border-box;
- }
- .v-bottom-navigation button.v-btn:before {
- background-color: transparent;
- }
- .v-speed-dial {
- margin-bottom: calc(48px + constant(safe-area-inset-bottom));
- margin-bottom: calc(48px + env(safe-area-inset-bottom));
- }
- .container.container--fluid {
- padding-bottom: 68px;
- }
- .appicon {
- user-select: none;
- -webkit-user-select: none;
- }
- </style>
- </head>
- <body>
- <div id="BG"></div>
- <div id="app" v-cloak>
- <v-app v-if="box" :style="appViewStyle">
- <v-app-bar
- ref="appBar"
- v-bind="appBarBind"
- :class="!$refs.appBar || $refs.appBar.isActive ? 'safe' : undefined"
- :value="!isHidedSearchBar"
- v-touch="{ up: () => isHidedSearchBar = true }"
- >
- <!-- 搜索条 -->
- <v-autocomplete v-bind="ui.searchBar" :label="title" @click="ui.searchDialog.show = true" hide-no-data hide-details solo>
- <template #prepend-inner>
- <!-- 容器切换 Surge、QuanX、Loon -->
- <v-menu bottom left v-if="!isLoading && isMainView">
- <template #activator="{ on }">
- <v-btn v-on="on" icon class="ml-n3">
- <v-avatar size="26"><img :src="env.icons[iconEnvThemeIdx]" /></v-avatar>
- </v-btn>
- </template>
- <v-list>
- <v-list-item dense v-for="(env, envIdx) in envs" :key="env.id" @click="switchEnv(env.id)">
- <v-list-item-avatar size="26"><v-img :src="env.icon" /></v-list-item-avatar>
- <v-list-item-title>{{env.id}}</v-list-item-title>
- </v-list-item>
- </v-list>
- </v-menu>
- <!-- 返回按钮 -->
- <v-btn icon class="ml-n3" @click="back" v-else-if="!isLoading && !isMainView">
- <v-icon>mdi-chevron-left</v-icon>
- </v-btn>
- <v-btn icon class="ml-n3" v-show="isLoading" :loading="isLoading" color="primary"></v-btn>
- </template>
- <template #append>
- <v-btn icon class="mr-n3" @click="ui.naviDrawer.show = true">
- <v-avatar size="26"><v-icon>mdi-menu</v-icon></v-avatar>
- </v-btn>
- </template>
- </v-autocomplete>
- </v-app-bar>
- <v-dialog v-model="ui.searchDialog.show" fullscreen scrollable>
- <v-card class="align-self-start">
- <v-card-subtitle class="pa-0">
- <v-toolbar v-bind="searchBarBind" class="safe">
- <v-btn icon dark @click="ui.searchDialog.show = false">
- <v-icon>mdi-chevron-left</v-icon>
- </v-btn>
- <v-text-field ref="search" v-model="ui.searchBar.input" :label="title" autofocus hide-details solo></v-text-field>
- <v-btn icon @click="open(box.syscfgs.orz3.repo)">
- <v-avatar size="26"><img :src="box.syscfgs.orz3.icon" /></v-avatar>
- </v-btn>
- </v-toolbar>
- </v-card-subtitle>
- <v-card-text class="px-0">
- <v-list nav>
- <v-list-item
- v-for="(app, appIdx) in searchApps"
- :key="appIdx"
- @click="ui.searchDialog.show = false, switchAppView(app.id)"
- dense
- >
- <v-list-item-avatar class="elevation-3"><img :src="app.icon" /></v-list-item-avatar>
- <v-list-item-content>
- <v-list-item-title>{{`${app.name} (${app.id})`}}</v-list-item-title>
- <v-list-item-subtitle>{{app.repo}}</v-list-item-subtitle>
- <v-list-item-subtitle>{{app.author}}</v-list-item-subtitle>
- </v-list-item-content>
- <v-list-item-action>
- <v-btn icon @click.stop="ui.searchDialog.show = false, favApp(app.id)">
- <v-icon :color="app.favIconColor" v-text="app.favIcon" />
- </v-btn>
- </v-list-item-action>
- </v-list-item>
- </v-list>
- </v-card-text>
- </v-card>
- </v-dialog>
- <!-- 侧栏 -->
- <v-navigation-drawer app v-model="ui.naviDrawer.show" height="100%" temporary right disable-route-watcher>
- <v-list dense nav>
- <v-list-item dense>
- <v-list-item-avatar @click="open(box.syscfgs.boxjs.repo)" class="elevation-3">
- <v-img :src="box.syscfgs.boxjs.icon"></v-img>
- </v-list-item-avatar>
- <v-row justify="start" no-gutters>
- <v-col v-for="(c, cIdx) in ui.collaborators" cols="4" :key="c.id">
- <a>
- <v-avatar size="40" @click="open(c.repo)" class="elevation-3">
- <img :src="c.icon" />
- </v-avatar>
- </a>
- </v-col>
- </v-row>
- </v-list-item>
- <v-divider></v-divider>
- <v-list-item class="pt-1">
- <v-progress-linear :active="isLoading" height="1" absolute top indeterminate></v-progress-linear>
- <v-row justify="start" no-gutters>
- <v-col v-for="(c, cIdx) in ui.contributors" cols="2" :key="c.id">
- <v-tooltip bottom>
- <template v-slot:activator="{ on, attrs }">
- <a>
- <v-avatar v-on="on" class="ma-1 elevation-3" size="26" @click="open(c.repo)">
- <v-img :src="c.icon"></v-img>
- </v-avatar>
- </a>
- </template>
- <span>{{c.login}}</span>
- </v-tooltip>
- </v-col>
- </v-row>
- </v-list-item>
- <v-divider></v-divider>
- <v-list-item v-if="box.syscfgs.env === 'Surge'">
- <v-list-item-content>
- <v-select
- v-if="box.usercfgs.httpapis"
- hide-details
- v-model="box.usercfgs.httpapi"
- :items="box.usercfgs.httpapis.split(',')"
- @change="saveUserCfgs"
- label="HTTP-API (Surge)"
- >
- </v-select>
- <v-text-field
- v-else
- label="HTTP-API (Surge)"
- v-model="box.usercfgs.httpapi"
- hint="Surge http-api 地址."
- placeholder="[email protected]:6166"
- persistent-hint
- @change="saveUserCfgs"
- :rules="[(val)=> /.*?@.*?:[0-9]+/.test(val) || '格式错误: [email protected]:6166']"
- >
- </v-text-field>
- </v-list-item-content>
- </v-list-item>
- <v-list-item>
- <v-list-item-content>
- <v-select
- :items="[{text: 'English', value: 'en-US'}, {text: '简体中文', value: 'zh-CN'}]"
- hide-details
- label="Language"
- v-model="box.usercfgs.lang"
- >
- </v-select>
- </v-list-item-content>
- </v-list-item>
- <v-list-item>
- <v-list-item-content>
- <v-select
- :items="[{text: $t('prefs.appearances.auto'), value: 'auto'}, {text: $t('prefs.appearances.dark'), value: 'dark'}, {text: $t('prefs.appearances.light'), value: 'light'}]"
- :label="$t('prefs.appearance')"
- hide-details
- v-model="box.usercfgs.theme"
- >
- </v-select>
- </v-list-item-content>
- </v-list-item>
- <v-list-item class="mt-4" v-show="box.usercfgs.bgimgs">
- <v-list-item-content>
- <v-select
- :items="bgimgs"
- :label="$t('prefs.background')"
- @change="saveUserCfgs"
- hide-details
- item-text="name"
- item-value="url"
- v-model="box.usercfgs.bgimg"
- >
- </v-select>
- </v-list-item-content>
- </v-list-item>
- <v-list-item class="mt-4">
- <v-switch
- :hide-details="isDarkMode"
- :hint="$t('prefs.iconDesc')"
- :label="$t('prefs.icon')"
- :persistent-hint="true"
- @change="saveUserCfgs"
- class="mt-0"
- dense
- v-model="box.usercfgs.isTransparentIcons"
- >
- </v-switch>
- <v-spacer></v-spacer>
- <v-btn fab small text @click="open(box.syscfgs.orz3.repo)">
- <v-avatar size="32"><img :src="box.syscfgs.orz3.icon" /></v-avatar>
- </v-btn>
- </v-list-item>
- <v-list-item class="mt-4">
- <v-switch
- :hint="$t('prefs.bgModeDesc')"
- :label="$t('prefs.bgMode')"
- :persistent-hint="true"
- @change="saveUserCfgs"
- class="mt-0"
- dense
- v-model="isWallpaperMode"
- >
- </v-switch>
- <v-spacer></v-spacer>
- <v-btn fab small text>
- <v-avatar size="32"><v-icon>mdi-image</v-icon></v-avatar>
- </v-btn>
- </v-list-item>
- <v-list-item class="mt-4">
- <v-switch
- :hint="$t('prefs.hideTopBarDesc')"
- :label="$t('prefs.hideTopBar')"
- :persistent-hint="true"
- @change="saveUserCfgs"
- class="mt-0"
- dense
- v-model="box.usercfgs.isHidedSearchBar"
- >
- </v-switch>
- <v-spacer></v-spacer>
- <v-btn fab small text>
- <v-avatar size="32"><v-icon>mdi-dock-top</v-icon></v-avatar>
- </v-btn>
- </v-list-item>
- <v-list-item class="mt-4">
- <v-switch
- :hint="$t('prefs.autoTopBarDesc')"
- :label="$t('prefs.autoTopBar')"
- :persistent-hint="true"
- @change="saveUserCfgs"
- class="mt-0"
- dense
- v-model="isAutoSearchBar"
- >
- </v-switch>
- <v-spacer></v-spacer>
- <v-btn fab small text>
- <v-avatar size="32"><v-icon>mdi-format-align-top</v-icon></v-avatar>
- </v-btn>
- </v-list-item>
- <v-list-item class="mt-4">
- <v-switch
- :hint="$t('prefs.hideBottomBarDesc')"
- :label="$t('prefs.hideBottomBar')"
- :persistent-hint="true"
- @change="saveUserCfgs"
- class="mt-0"
- dense
- v-model="box.usercfgs.isHidedNaviBottom"
- >
- </v-switch>
- <v-spacer></v-spacer>
- <v-btn fab small text>
- <v-avatar size="32"><v-icon>mdi-dock-bottom</v-icon></v-avatar>
- </v-btn>
- </v-list-item>
- <v-list-item class="mt-4">
- <v-switch
- :hint="$t('prefs.autoBottomBarDesc')"
- :label="$t('prefs.autoBottomBar')"
- :persistent-hint="true"
- @change="saveUserCfgs"
- class="mt-0"
- dense
- v-model="isAutoNaviBottom"
- >
- </v-switch>
- <v-spacer></v-spacer>
- <v-btn fab small text>
- <v-avatar size="32"><v-icon>mdi-format-align-bottom</v-icon></v-avatar>
- </v-btn>
- </v-list-item>
- <!-- <v-list-item class="mt-4">
- <v-switch
- dense
- class="mt-0"
- label="透明主题"
- v-model="box.usercfgs.isTransparent"
- @change="saveUserCfgs"
- :persistent-hint="true"
- hint="使界面更多元素透明 (beta)"
- >
- </v-switch>
- <v-spacer></v-spacer>
- <v-btn fab small text>
- <v-avatar size="32"><v-icon>mdi-invert-colors</v-icon></v-avatar>
- </v-btn>
- </v-list-item> -->
- <v-list-item class="mt-4">
- <v-switch
- :hint="$t('prefs.muteModeDesc')"
- :label="$t('prefs.muteMode')"
- @change="saveUserCfgs"
- class="mt-0"
- dense
- persistent-hint
- v-model="box.usercfgs.isMute"
- ></v-switch>
- <v-spacer></v-spacer>
- <v-btn fab small text>
- <v-avatar size="32"><v-icon>mdi-volume-off</v-icon></v-avatar>
- </v-btn>
- </v-list-item>
- <v-list-item class="mt-4">
- <v-switch
- :hint="$t('prefs.hideHelpDesc')"
- :label="$t('prefs.hideHelp')"
- @change="saveUserCfgs"
- class="mt-0"
- dense
- persistent-hint
- v-model="box.usercfgs.isHideHelp"
- ></v-switch>
- <v-spacer></v-spacer>
- <v-btn fab small text @click="open(box.syscfgs.boxjs.repo)">
- <v-avatar size="32"><v-icon>mdi-help</v-icon></v-avatar>
- </v-btn>
- </v-list-item>
- <v-list-item class="mt-4">
- <v-switch
- :hint="$t('prefs.hideBoxJsDesc')"
- :label="$t('prefs.hideBoxJs')"
- @change="saveUserCfgs"
- class="mt-0"
- dense
- persistent-hint
- v-model="box.usercfgs.isHideBoxIcon"
- ></v-switch>
- <v-spacer></v-spacer>
- <v-btn fab small text @click="open(box.syscfgs.boxjs.repo)">
- <v-avatar size="32">
- <img :src="box.syscfgs.boxjs.icons[iconThemeIdx]" />
- </v-avatar>
- </v-btn>
- </v-list-item>
- <v-list-item class="mt-4">
- <v-switch
- :hint="$t('prefs.hideProfileTitleDesc')"
- :label="$t('prefs.hideProfileTitle')"
- @change="saveUserCfgs"
- class="mt-0"
- dense
- persistent-hint
- v-model="box.usercfgs.isHideMyTitle"
- ></v-switch>
- <v-spacer></v-spacer>
- <v-btn fab small text>
- <v-avatar v-if="box.usercfgs.icon" size="32">
- <img :src="box.usercfgs.icon" />
- </v-avatar>
- <v-icon v-else size="32">mdi-face-profile</v-icon>
- </v-btn>
- </v-list-item>
- <v-list-item class="mt-4">
- <v-switch
- :hint="$t('prefs.hideCoddingDesc')"
- :label="$t('prefs.hideCodding')"
- dense
- class="mt-0"
- persistent-hint
- v-model="box.usercfgs.isHideCoding"
- @change="saveUserCfgs"
- ></v-switch>
- <v-spacer></v-spacer>
- <v-btn fab small text>
- <v-avatar size="32"><v-icon>mdi-code-tags</v-icon></v-avatar>
- </v-btn>
- </v-list-item>
- <v-list-item class="mt-4">
- <v-switch
- :hint="$t('prefs.hideReloadDesc')"
- :label="$t('prefs.hideReload')"
- @change="saveUserCfgs"
- class="mt-0"
- dense
- persistent-hint
- v-model="box.usercfgs.isHideRefresh"
- ></v-switch>
- <v-spacer></v-spacer>
- <v-btn fab small text>
- <v-avatar size="32"><v-icon>mdi-refresh</v-icon></v-avatar>
- </v-btn>
- </v-list-item>
- <v-list-item class="mt-4">
- <v-switch
- :hint="$t('prefs.debugModeDesc')"
- :label="$t('prefs.debugMode')"
- @change="saveUserCfgs"
- class="mt-0"
- dense
- persistent-hint
- v-model="box.usercfgs.isDebugWeb"
- ></v-switch>
- <v-spacer></v-spacer>
- <v-btn fab small text>
- <v-avatar size="32"><v-icon>mdi-language-html5</v-icon></v-avatar>
- </v-btn>
- </v-list-item>
- <v-list-item v-if="box.usercfgs.isDebugWeb">
- <v-list-item-content>
- <v-text-field
- :hint="$t('prefs.debugPageDesc')"
- :label="$t('prefs.debugPage')"
- @change="saveUserCfgs"
- clearable
- persistent-hint
- placeholder="http://ip:port/boxjs.html"
- v-model="box.usercfgs.debugger_web"
- >
- </v-text-field>
- </v-list-item-content>
- </v-list-item>
- <v-list-item class="mt-4"></v-list-item>
- </v-list>
- </v-navigation-drawer>
- <!-- 主页 -->
- <v-main class="appBarBind.app ? 'safe' : ''" v-scroll="onScroll">
- <v-snackbar top app v-model="ui.snackbar.show" v-bind="ui.snackbar">{{ui.snackbar.msg}}</v-snackbar>
- <!-- 主页 -->
- <v-container
- fluid
- v-show="view === ''"
- v-touch="{
- up: () => {
- if (isWallpaperMode) {
- clearWallpaper()
- setWallpaper()
- }
- },
- down: () => {
- if (isWallpaperMode) {
- isWallpaperMode = !isWallpaperMode
- changeWallpaper()
- }
- }
- }"
- >
- <v-row no-gutters v-show="!isHidedAppIcons" class="align-self-start" id="appList">
- <v-col cols="3" md="2" v-for="(app, appIdx) in favApps" :key="app.id" class="d-flex justify-space-around">
- <div class="ma-2 appicon" @click="switchAppView(app.id)">
- <v-card v-if="isDarkMode" style="border-radius: 12px">
- <v-img style="border-radius: 12px" :aspect-ratio="1" width="54" height="54" contain v-ripple :src="app.icon"></v-img>
- </v-card>
- <v-img
- v-else
- style="border-radius: 12px"
- :aspect-ratio="1"
- width="54"
- height="54"
- contain
- v-ripple
- class="elevation-3"
- :src="app.icon"
- ></v-img>
- <p class="text-center ma-0">
- <span class="d-inline-block text-truncate font-weight-bold" :style="appIconFontStyle"> {{app.name}} </span>
- </p>
- </div>
- </v-col>
- </v-row>
- </v-container>
- <!-- 应用列表 -->
- <v-container fluid v-show="view === 'app' && !curapp">
- <!-- 收藏应用 -->
- <v-expansion-panels multiple class="mb-4" v-if="favApps.length > 0" v-model="box.usercfgs.favapppanel">
- <v-expansion-panel>
- <v-expansion-panel-header> {{ $t('apps.fav') }} ({{favApps.length}}) </v-expansion-panel-header>
- <v-expansion-panel-content>
- <v-list dense nav class="ma-n4">
- <template v-for="(app, appIdx) in favApps">
- <v-list-item dense @click="switchAppView(app.id)" :key="app.id">
- <v-list-item-avatar class="elevation-3"><v-img :src="app.icon" /></v-list-item-avatar>
- <v-list-item-content>
- <v-list-item-title>{{app.name}} ({{app.id}})</v-list-item-title>
- <v-list-item-subtitle>{{app.repo}}</v-list-item-subtitle>
- <v-list-item-subtitle>{{app.author}}</v-list-item-subtitle>
- </v-list-item-content>
- <v-list-item-action>
- <v-menu bottom left>
- <template #activator="{ on }">
- <v-btn icon v-on="on"><v-icon>mdi-dots-vertical</v-icon></v-btn>
- </template>
- <v-list>
- <v-list-item dense v-if="appIdx > 0" @click="moveFav(appIdx, -1)">
- <v-list-item-title>{{ $t('base.sort.up') }}</v-list-item-title>
- </v-list-item>
- <v-list-item dense v-if="appIdx + 1 < favApps.length" @click="moveFav(appIdx, 1)">
- <v-list-item-title>{{ $t('base.sort.dn') }}</v-list-item-title>
- </v-list-item>
- <v-divider v-if="favApps.length > 1"></v-divider>
- <v-list-item dense @click="favApp(app.id)">
- <v-list-item-title>{{ $t('apps.unStar') }}</v-list-item-title>
- </v-list-item>
- </v-list>
- </v-menu>
- </v-list-item-action>
- </v-list-item>
- <!-- <v-divider inset v-if="favApps.length !== appIdx + 1"></v-divider> -->
- </template>
- </v-list>
- </v-expansion-panel-content>
- </v-expansion-panel>
- </v-expansion-panels>
- <!-- 订阅应用 -->
- <v-expansion-panels multiple class="mb-4" v-if="appSubs.length > 0" v-model="box.usercfgs.subapppanel">
- <v-expansion-panel v-for="(sub, subIdx) in appSubs" :key="sub.id" v-if="!sub.isErr">
- <v-expansion-panel-header> {{sub.name}} ({{sub.apps.length}}) </v-expansion-panel-header>
- <v-expansion-panel-content>
- <v-list dense nav class="ma-n4">
- <template v-for="(app, appIdx) in sub.apps">
- <v-list-item dense @click="switchAppView(app.id)" :key="app.id">
- <v-list-item-avatar class="elevation-3"><v-img :src="app.icon" /></v-list-item-avatar>
- <v-list-item-content>
- <v-list-item-title>{{app.name}} ({{app.id}})</v-list-item-title>
- <v-list-item-subtitle>{{app.repo}}</v-list-item-subtitle>
- <v-list-item-subtitle>{{app.author}}</v-list-item-subtitle>
- </v-list-item-content>
- <v-list-item-action>
- <v-btn icon @click.stop="favApp(app.id)">
- <v-icon :color="app.favIconColor" v-text="app.favIcon" />
- </v-btn>
- </v-list-item-action>
- </v-list-item>
- <!-- <v-divider inset v-if="sub.apps.length !== appIdx + 1"></v-divider> -->
- </template>
- </v-list>
- </v-expansion-panel-content>
- </v-expansion-panel>
- </v-expansion-panels>
- <!-- 内置应用 -->
- <v-expansion-panels multiplev-if="sysApps.length > 0" v-model="box.usercfgs.sysapppanel">
- <v-expansion-panel>
- <v-expansion-panel-header> {{ $t('apps.sysApps') }} ({{sysApps.length}}) </v-expansion-panel-header>
- <v-expansion-panel-content>
- <v-list dense nav class="ma-n4">
- <template v-for="(app, appIdx) in sysApps">
- <v-list-item dense @click="switchAppView(app.id)" :key="app.id">
- <v-list-item-avatar class="elevation-3"><v-img :src="app.icon" /></v-list-item-avatar>
- <v-list-item-content>
- <v-list-item-title>{{app.name}} ({{app.id}})</v-list-item-title>
- <v-list-item-subtitle>{{app.repo}}</v-list-item-subtitle>
- <v-list-item-subtitle>{{app.author}}</v-list-item-subtitle>
- </v-list-item-content>
- <v-list-item-action>
- <v-btn icon @click.stop="favApp(app.id)">
- <v-icon :color="app.favIconColor" v-text="app.favIcon" />
- </v-btn>
- </v-list-item-action>
- </v-list-item>
- <!-- <v-divider inset v-if="sysApps.length !== appIdx + 1"></v-divider> -->
- </template>
- </v-list>
- </v-expansion-panel-content>
- </v-expansion-panel>
- </v-expansion-panels>
- </v-container>
- <!-- 订阅列表 -->
- <v-container fluid v-show="view === 'sub'">
- <template v-if="appSubs.length === 0">
- <v-btn block class="primary" @click="addAppSubDialog = true">{{ $t('subs.add') }}</v-btn>
- <v-btn block class="primary" @click="open('https://chavyleung.gitbook.io/boxjs/awesome/subscriptions')">
- <v-icon class="mr-2">mdi-cloud</v-icon>{{ $t('subs.moreSubs') }}
- </v-btn>
- </template>
- <v-card v-else>
- <v-list dense nav>
- <v-subheader inset dense>
- {{ $t('subs.appSubs') }} ({{appSubs.length}})
- <v-spacer></v-spacer>
- <v-btn icon @click="open('https://chavyleung.gitbook.io/boxjs/awesome/subscriptions')">
- <v-icon>mdi-cloud-circle</v-icon>
- </v-btn>
- <v-btn icon @click="reloadAppSub()">
- <v-icon>mdi-refresh-circle</v-icon>
- </v-btn>
- <v-btn icon>
- <v-icon color="primary" @click="addAppSubDialog = true">mdi-plus-circle</v-icon>
- </v-btn>
- </v-subheader>
- <template v-for="(sub, subIdx) in appSubs">
- <v-list-item dense two-line @click="reloadAppSub(sub)" :key="sub.id">
- <v-list-item-avatar v-if="sub.icon"><v-img :src="sub.icon" /></v-list-item-avatar>
- <v-list-item-avatar v-else color="primary"><v-icon dark>mdi-account</v-icon></v-list-item-avatar>
- <v-list-item-content>
- <v-list-item-title>
- {{sub.name}} ({{sub.apps.length}})
- <v-chip v-if="sub.isErr" color="pink" dark x-small class="ml-1 mb-1">{{ $t('subs.errData') }}</v-chip>
- </v-list-item-title>
- <v-list-item-subtitle>{{sub.repo ? sub.repo : sub.url}}</v-list-item-subtitle>
- <v-list-item-subtitle>{{sub.author ? sub.author : '@anonymous'}}</v-list-item-subtitle>
- <v-list-item-subtitle>
- {{ $t('subs.updated') }}: {{ timeago.format(sub.updateTime, timeagoLang.replace('-', '_')) }}
- </v-list-item-subtitle>
- </v-list-item-content>
- <v-list-item-action>
- <v-menu bottom left>
- <template #activator="{ on }">
- <v-btn icon v-on="on"><v-icon>mdi-dots-vertical</v-icon></v-btn>
- </template>
- <v-list dense>
- <template v-if="sub.onInstall">
- <v-list-item @click="openInstall(sub.raw.id)">
- <v-list-item-title>{{ $t('subs.install') }}</v-list-item-title>
- </v-list-item>
- <v-divider></v-divider>
- </template>
- <v-list-item @click="open(sub.repo)">
- <v-list-item-title>{{ $t('subs.repo') }}</v-list-item-title>
- </v-list-item>
- <v-list-item @click="copy(sub.url)">
- <v-list-item-title>{{ $t('base.cmd.cp') }}</v-list-item-title>
- </v-list-item>
- <v-list-item @click="share(sub.url)">
- <v-list-item-title>{{ $t('base.cmd.share') }}</v-list-item-title>
- </v-list-item>
- <v-divider></v-divider>
- <v-list-item v-if="subIdx > 0" @click="moveSub(subIdx, -1)">
- <v-list-item-title>{{ $t('base.sort.up') }}</v-list-item-title>
- </v-list-item>
- <v-list-item v-if="subIdx + 1 < appSubs.length" @click="moveSub(subIdx, 1)">
- <v-list-item-title>{{ $t('base.sort.dn') }}</v-list-item-title>
- </v-list-item>
- <v-divider></v-divider>
- <v-list-item @click="delSub(subIdx)">
- <v-list-item-title class="text-uppercase red--text">{{ $t('base.cmd.del') }}</v-list-item-title>
- </v-list-item>
- </v-list>
- </v-menu>
- </v-list-item-action>
- </v-list-item>
- <!-- <v-divider inset v-if="appSubs.length !== subIdx + 1"></v-divider> -->
- </template>
- </v-list>
- </v-card>
- <v-dialog v-model="addAppSubDialog" scrollable>
- <v-card>
- <v-card-title>{{ $t('subs.addDialog.title') }}</v-card-title>
- <v-divider></v-divider>
- <v-card-text>
- <v-textarea
- :label="$t('subs.addDialog.url')"
- :hint="$t('subs.addDialog.urlDesc')"
- autofocus
- clearable
- persistent-hint
- rows="3"
- v-model="ui.addAppSubDialog.url"
- ></v-textarea>
- </v-card-text>
- <v-divider></v-divider>
- <v-card-actions>
- <v-spacer></v-spacer>
- <v-btn text small color="grey" @click="addAppSubDialog = false"> {{$t('base.dialog.close')}} </v-btn>
- <v-btn text small color="primary" @click="addAppSub(ui.addAppSubDialog.url)" :loading="isLoading">
- {{$t('base.dialog.save')}}
- </v-btn>
- </v-card-actions>
- </v-card>
- </v-dialog>
- <v-dialog persistent v-model="ui.installConfirmDialog.show">
- <v-card>
- <v-card-title>{{ ui.installConfirmDialog.title }}</v-card-title>
- <v-card-text> {{ ui.installConfirmDialog.message }}</v-card-text>
- <v-card-actions>
- <v-spacer></v-spacer>
- <v-btn text small color="grey" @click="ui.installConfirmDialog.show = false"> {{$t('base.dialog.close')}} </v-btn>
- <v-btn text small color="primary" @click="install(ui.installConfirmDialog.url)" :loading="isLoading">
- {{$t('base.dialog.ok')}}
- </v-btn>
- </v-card-actions>
- </v-card>
- </v-dialog>
- </v-container>
- <!-- 我的 -->
- <v-container fluid v-show="view === 'my'">
- <v-card class="mx-auto">
- <v-card-title class="headline">
- {{box.usercfgs.name ? box.usercfgs.name : $t('profile.leaveName')}}
- <v-spacer></v-spacer>
- <v-dialog v-model="ui.editProfileDialog.show">
- <template #activator="{ on }">
- <v-btn icon v-on="on"><v-icon>mdi-cog-outline</v-icon></v-btn>
- </template>
- <v-card>
- <v-card-title>{{ $t('profile.editor.title') }}</v-card-title>
- <v-divider></v-divider>
- <v-card-text>
- <v-text-field
- :hint="$t('profile.editor.nameDesc')"
- :label="$t('profile.editor.name')"
- v-model="box.usercfgs.name"
- ></v-text-field>
- <v-text-field
- :hint="$t('profile.editor.avatarDesc')"
- :label="$t('profile.editor.avatar')"
- v-model="box.usercfgs.icon"
- ></v-text-field>
- </v-card-text>
- <v-divider></v-divider>
- <v-card-actions>
- <v-spacer></v-spacer>
- <v-btn text small color="grey" @click="ui.editProfileDialog.show = false">{{ $t('base.dialog.close') }}</v-btn>
- <v-btn text small color="primary" @click="ui.editProfileDialog.show = false" :loading="isLoading">
- {{ $t('base.dialog.save') }}
- </v-btn>
- </v-card-actions>
- </v-card>
- </v-dialog>
- </v-card-title>
- <v-divider class="mx-4"></v-divider>
- <v-card-text>
- <span class="subheading">{{ $t('profile.datas') }}</span>
- <v-chip-group>
- <v-chip small>{{ $t('profile.apps') }}: {{this.apps.length}}</v-chip>
- <v-chip small>{{ $t('profile.subs') }}: {{this.appSubs.length}}</v-chip>
- <v-chip small>{{ $t('profile.sessions') }}: {{this.sessions.length}}</v-chip>
- </v-chip-group>
- </v-card-text>
- <v-card-actions>
- <v-spacer></v-spacer>
- <v-btn small class="mr-2" @click="switchView('viewer')"> {{ $t('profile.dataviewer')}} </v-btn>
- <v-dialog v-model="ui.impGlobalBakDialog.show">
- <template #activator="{ on }">
- <v-btn small v-on="on">{{ $t('profile.imp') }}</v-btn>
- </template>
- <v-card>
- <v-card-title> {{ $t('profile.impDialog.title') }} </v-card-title>
- <v-divider></v-divider>
- <v-card-text>
- <v-textarea
- :hint="$t('profile.impDialog.impDataDesc')"
- :label="$t('profile.impDialog.impData')"
- autofocus
- clearable
- rows="3"
- v-model="ui.impGlobalBakDialog.impval"
- ></v-textarea>
- </v-card-text>
- <v-divider></v-divider>
- <v-card-actions>
- <v-spacer></v-spacer>
- <v-btn text small color="grey" text @click="ui.impGlobalBakDialog.show = false">{{ $t('base.dialog.close') }}</v-btn>
- <v-btn text small color="primary" text @click="impGlobalBak" :loading="isLoading">{{ $t('profile.imp') }}</v-btn>
- </v-card-actions>
- </v-card>
- </v-dialog>
- <v-btn small @click="saveGlobalBak">{{ $t('profile.bak') }}</v-btn>
- </v-card-actions>
- </v-card>
- <v-card class="mt-4" v-if="box.globalbaks">
- <template v-for="(bak, bakIdx) in box.globalbaks">
- <v-divider v-if="bakIdx>0"></v-divider>
- <v-list-item dense @click="switchBakView(bak.id)">
- <v-list-item-content>
- <v-list-item-title>{{bak.name}}</v-list-item-title>
- <v-list-item-subtitle>{{dayjs(bak.createTime).format('YYYY-MM-DD HH:mm:ss')}}</v-list-item-subtitle>
- <v-list-item-subtitle>
- <v-chip x-small class="mr-2" v-for="(tag, tagIdx) in bak.tags" :key="tagIdx">{{tag}}</v-chip>
- </v-list-item-subtitle>
- </v-list-item-content>
- <v-list-item-action>
- <v-btn icon><v-icon>mdi-chevron-right</v-icon></v-btn>
- </v-list-item-action>
- </v-list-item>
- </template>
- </v-card>
- </v-container>
- <!-- 数据查看 -->
- <v-container fluid v-show="view === 'viewer'">
- <v-card class="mb-4">
- <v-subheader>
- {{ $t('viewer.dataViewer') }}
- <v-spacer></v-spacer>
- <v-btn color="primary" small @click="copy(ui.viewer.key)"> {{ $t('base.cmd.cp') }} </v-btn>
- </v-subheader>
- <v-card-text>
- <v-text-field
- :hint="$t('viewer.dataKeyDesc')"
- :label="$t('viewer.dataKey')"
- persistent-hint
- placeholder="boxjs_host"
- v-model="ui.viewer.key"
- >
- </v-text-field>
- </v-card-text>
- <v-divider></v-divider>
- <v-card-actions>
- <!-- TODO 列出最近查询过的 key -->
- <v-spacer></v-spacer>
- <v-btn small text color="primary" @click="queryData">{{ $t('base.dialog.view') }}</v-btn>
- </v-card-actions>
- </v-card>
- <v-card class="mb-4">
- <v-subheader>
- {{ $t('viewer.dataEditor') }}
- <v-spacer></v-spacer>
- <v-btn color="primary" small @click="copy(ui.viewer.val)"> {{ $t('base.cmd.cp') }} </v-btn>
- </v-subheader>
- <v-card-text>
- <v-textarea v-model="ui.viewer.val" :row="3" :label="$t('viewer.dataVal')"> </v-textarea>
- </v-card-text>
- <v-divider></v-divider>
- <v-card-actions>
- <v-spacer></v-spacer>
- <v-btn small text color="primary" @click="saveData">{{ $t('base.dialog.save') }}</v-btn>
- </v-card-actions>
- </v-card>
- </v-container>
- <!-- 代码编辑 -->
- <v-container fluid v-show="view === 'coding'">
- <v-card rounded="0" flat style="width: inherit">
- <v-subheader>
- <h2>{{ $t('codding.title') }}</h2>
- <v-spacer></v-spacer>
- <v-btn icon @click="runTxtScript" :loading="isLoading">
- <v-icon color="primary">mdi-play-circle</v-icon>
- </v-btn>
- </v-subheader>
- </v-card>
- <div class="pa-0" id="container" style="width: inherit; height: inherit"></div>
- </v-container>
- <!-- 应用详情 -->
- <v-container fluid v-if="view === 'app' && !!curapp">
- <v-subheader>
- <h2 :style="appTitleStyle">{{curapp.name}}</h2>
- <v-spacer></v-spacer>
- <v-btn v-if="curapp.script" icon :loading="isLoading" @click="runRemoteScript(curapp.script, curapp.script_timeout)">
- <v-icon color="primary">mdi-play-circle</v-icon>
- </v-btn>
- </v-subheader>
- <v-card class="mb-4" v-if="curapp.desc || curapp.descs || curapp.desc_html || curapp.descs_html">
- <v-card-subtitle>
- <p v-if="curapp.desc" v-text="curapp.desc" class="text-pre-wrap"></p>
- <p
- v-for="(desc, descIdx) in curapp.descs"
- v-text="desc"
- :class="curapp.descs.length === descIdx + 1 ? 'text-pre-wrap' : 'mb-0 text-pre-wrap'"
- ></p>
- <p v-if="curapp.desc_html" v-html="curapp.desc_html"></p>
- <div v-for="(desc_html, desc_htmlIdx) in curapp.descs_html" v-html="desc_html"></div>
- </v-card-subtitle>
- </v-card>
- <v-card class="mb-4">
- <template v-if="curapp.scripts">
- <v-subheader> {{ $t('appDetail.scripts') }} ({{curapp.scripts.length}}) </v-subheader>
- <v-list dense>
- <v-list-item v-for="(script, scriptIdx) in curapp.scripts" :key="scriptIdx">
- <v-list-item-title> {{scriptIdx + 1}}. {{script.name}} </v-list-item-title>
- <v-btn icon :loading="isLoading" @click.stop="runRemoteScript(script.script, script.script_timeout)">
- <v-icon>mdi-play-circle</v-icon>
- </v-btn>
- </v-list-item>
- </v-list>
- </template>
- </v-card>
- <v-card class="mb-4">
- <template v-if="curapp.settings">
- <v-subheader> {{ $t('appDetail.settings') }} ({{curapp.settings.length}}) </v-subheader>
- <v-form class="pl-4 pr-4 pb-4">
- <template v-for="(setting, settingIdx) in curapp.settings">
- <v-slider
- v-model="setting.val"
- v-bind="setting"
- class="mt-4"
- dense
- persistent-hint
- :label="setting.name"
- :hint="setting.desc"
- thumb-label="always"
- v-if="setting.type === 'slider'"
- ></v-slider>
- <v-switch
- v-model="setting.val"
- class="mt-2"
- persistent-hint
- dense
- :label="setting.name"
- :hint="setting.desc"
- v-else-if="setting.type === 'boolean'"
- ></v-switch>
- <v-textarea
- v-model="setting.val"
- v-bind="setting"
- class="mt-4"
- :row="3"
- :label="setting.name"
- :hint="setting.desc"
- v-else-if="setting.type === 'textarea'"
- ></v-textarea>
- <v-radio-group
- v-model="setting.val"
- v-bind="setting"
- persistent-hint
- class="mt-0"
- :hint="setting.desc"
- v-else-if="setting.type === 'radios'"
- >
- <v-subheader class="mb-n4 pa-0">{{setting.name}}</v-subheader>
- <v-radio
- :class="itemIdx === 0 ? 'mt-2' : ''"
- v-for="(item, itemIdx) in setting.items"
- :label="item.label"
- :value="item.key"
- :key="item.key"
- ></v-radio>
- </v-radio-group>
- <template v-else-if="setting.type === 'checkboxes'">
- <v-subheader class="mb-n8 pa-0">{{setting.name}}</v-subheader>
- <v-item-group class="mt-4 pt-1">
- <v-checkbox
- v-model="setting.val"
- class="mt-0"
- persistent-hint
- :hide-details="itemIdx + 1 !== setting.items.length"
- :hint="setting.desc"
- :label="item.label"
- :value="item.key"
- v-for="(item, itemIdx) in setting.items"
- :key="item.key"
- multiple
- ></v-checkbox>
- </v-item-group>
- </template>
- <template v-else-if="setting.type === 'colorpicker'">
- <v-subheader class="mb-n2 pa-0">{{setting.name}}</v-subheader>
- <v-color-picker
- v-model="setting.val"
- v-bind="setting"
- class="mt-2 mb-4"
- persistent-hint
- :hint="setting.desc"
- :hide-canvas="!setting.canvas"
- :dot-size="30"
- mode="hexa"
- light
- ></v-color-picker>
- </template>
- <div class="mt-4" v-else-if="setting.type === 'number'">
- <v-text-field
- v-model="setting.val"
- v-bind="setting"
- type="number"
- :label="setting.name"
- :hint="setting.desc"
- ></v-text-field>
- </div>
- <div class="mt-4" v-else-if="setting.type === 'selects'">
- <v-select
- v-model="setting.val"
- v-bind="setting"
- persistent-hint
- type="number"
- item-text="label"
- item-value="key"
- :items="setting.items"
- :label="setting.name"
- :hint="setting.desc"
- ></v-select>
- </div>
- <div class="mt-4" v-else>
- <v-text-field v-model="setting.val" v-bind="setting" :label="setting.name" :hint="setting.desc"></v-text-field>
- </div>
- </template>
- </v-form>
- <v-divider></v-divider>
- <v-card-actions>
- <v-spacer></v-spacer>
- <v-btn small text color="primary" @click="saveAppSettings">{{ $t('base.dialog.save') }}</v-btn>
- </v-card-actions>
- </template>
- </v-card>
- <v-card class="mx-auto" v-if="curapp.datas && curapp.datas.length > 0">
- <v-subheader>
- {{ $t('appDetail.curSession') }}
- <a class="ml-2">{{curapp.curSession ? curapp.curSession.name : ''}}</a>
- <v-spacer></v-spacer>
- <v-menu bottom left>
- <template #activator="{ on }">
- <v-btn icon v-on="on"><v-icon>mdi-dots-vertical</v-icon></v-btn>
- </template>
- <v-list dense>
- <v-list-item @click="copy(JSON.stringify(curapp))">
- <v-list-item-title>{{ $t('base.cmd.cp') }}</v-list-item-title>
- </v-list-item>
- <v-dialog v-model="ui.impAppDatasDialog.show">
- <template #activator="{ on }">
- <v-list-item v-on="on">
- <v-list-item-title>{{ $t('base.cmd.imp') }}</v-list-item-title>
- </v-list-item>
- </template>
- <v-card>
- <v-card-title> {{ $t('appDetail.impDialog.title') }} </v-card-title>
- <v-divider></v-divider>
- <v-card-text>
- <v-textarea
- v-model="ui.impAppDatasDialog.impval"
- rows="3"
- clearable
- autofocus
- :label="$t('appDetail.impDialog.data')"
- :hint="$t('appDetail.impDialog.dataDesc')"
- ></v-textarea>
- </v-card-text>
- <v-divider></v-divider>
- <v-card-actions>
- <v-spacer></v-spacer>
- <v-btn text small color="grey" text @click="ui.impAppDatasDialog.show = false">
- {{ $t('base.dialog.close') }}
- </v-btn>
- <v-btn text small color="primary" text @click="impAppDatas()" :loading="isLoading">
- {{ $t('base.cmd.imp') }}
- </v-btn>
- </v-card-actions>
- </v-card>
- </v-dialog>
- <v-list-item @click="copyData(curapp)">
- <v-list-item-title>{{ $t('appDetail.copyDatas') }}</v-list-item-title>
- </v-list-item>
- <v-divider></v-divider>
- <v-list-item @click="clearAppDatas()">
- <v-list-item-title class="text-uppercase red--text">{{ $t('appDetail.clearDatas') }}</v-list-item-title>
- </v-list-item>
- </v-list>
- </v-menu>
- </v-subheader>
- <v-list-item two-line dense v-for="(data, dataIdx) in curapp.datas" :key="dataIdx">
- <v-list-item-content>
- <v-list-item-title>{{data.key}}</v-list-item-title>
- <v-list-item-subtitle>{{data.val ? data.val : $t('appDetail.noDatas')}}</v-list-item-subtitle>
- </v-list-item-content>
- <v-list-item-action>
- <v-btn icon @click.stop="clearAppDatas(data.key)">
- <v-icon color="grey">mdi-close</v-icon>
- </v-btn>
- </v-list-item-action>
- </v-list-item>
- <v-divider></v-divider>
- <v-card-actions>
- <v-spacer></v-spacer>
- <v-btn small text color="primary" @click="saveAppSession">{{ $t('base.cmd.duplicate') }}</v-btn>
- </v-card-actions>
- </v-card>
- <v-card :id="session.id" class="ml-10 mt-4" v-for="(session, sessionIdx) in curapp.sessions" :key="session.id">
- <v-subheader>
- <a v-if="curapp.curSession && curapp.curSession.id === session.id">#{{sessionIdx + 1}} {{session.name}}</a>
- <template v-else>#{{sessionIdx + 1}} {{session.name}}</template>
- <v-spacer></v-spacer>
- <v-menu bottom left>
- <template #activator="{ on }">
- <v-btn icon v-on="on"><v-icon>mdi-dots-vertical</v-icon></v-btn>
- </template>
- <v-list dense>
- <v-dialog v-model="ui.modSessionDialog.show">
- <template #activator="{ on }">
- <v-list-item v-on="on">
- <v-list-item-title>{{ $t('base.cmd.mod') }}</v-list-item-title>
- </v-list-item>
- </template>
- <v-card>
- <v-card-title>{{ $t('appDetail.sessionEditor.title') }}</v-card-title>
- <v-divider></v-divider>
- <v-card-text>
- <v-text-field class="mt-4" :label="$t('appDetail.sessionEditor.name')" v-model="session.name"></v-text-field>
- <v-text-field
- v-for="(data, dataIdx) in session.datas"
- :key="dataIdx"
- v-model="data.val"
- :label="data.key"
- ></v-text-field>
- </v-card-text>
- <v-divider></v-divider>
- <v-card-actions>
- <v-spacer></v-spacer>
- <v-btn text small color="grey" text @click="ui.modSessionDialog.show = false"
- >{{ $t('base.dialog.close') }}</v-btn
- >
- <v-btn text small color="primary" text @click="updateAppSession(session)" :loading="isLoading"
- >{{ $t('base.dialog.save') }}</v-btn
- >
- </v-card-actions>
- </v-card>
- </v-dialog>
- <v-divider></v-divider>
- <v-list-item @click="delAppSession(session.id)">
- <v-list-item-title>{{ $t('base.cmd.del') }}</v-list-item-title>
- </v-list-item>
- </v-list>
- </v-menu>
- </v-subheader>
- <v-list-item two-line dense v-for="(data, dataIdx) in session.datas" :key="dataIdx">
- <v-list-item-content>
- <v-list-item-title>{{data.key}}</v-list-item-title>
- <v-list-item-subtitle>{{data.val ? data.val : $t('appDetail.noDatas')}}</v-list-item-subtitle>
- </v-list-item-content>
- </v-list-item>
- <v-divider></v-divider>
- <v-card-actions>
- <v-btn small text color="grey">{{dayjs(session.createTime).format('YYYY-MM-DD HH:mm:ss')}}</v-btn>
- <v-spacer></v-spacer>
- <v-btn small text color="primary" @click="useAppSession(session.id)">{{ $t('base.dialog.apply') }}</v-btn>
- </v-card-actions>
- </v-card>
- </v-container>
- <!-- 备份详情 -->
- <v-container fluid v-else-if="view === 'bak' && !!curbak">
- <v-subheader>
- <h2 :style="appTitleStyle">{{curbak.name}}</h2>
- <v-spacer></v-spacer>
- <v-btn color="primary" small @click="revertGlobalBak"> {{ $t('base.cmd.recovery') }} </v-btn>
- </v-subheader>
- <v-card class="mb-4">
- <v-subheader> {{ $t('bakDetail.title') }} </v-subheader>
- <v-card-text>
- <v-text-field :label="$t('bakDetail.id')" v-model="curbak.id" readonly> </v-text-field>
- <v-text-field :label="$t('bakDetail.name')" v-model="curbak.name" @change="updateGlobalBak"> </v-text-field>
- </v-card-text>
- <v-divider></v-divider>
- <v-card-actions>
- <v-spacer></v-spacer>
- <v-btn small text color="error" @click="delGlobalBak">{{ $t('base.cmd.del') }}</v-btn>
- </v-card-actions>
- </v-card>
- <v-card class="mb-4">
- <v-subheader>
- {{ $t('bakDetail.dataTitle') }}
- <v-spacer></v-spacer>
- <v-btn color="primary" small @click="copy(JSON.stringify(curbak.bak))"> {{ $t('base.cmd.cp') }} </v-btn>
- </v-subheader>
- </v-card>
- </v-container>
- <!-- 计算器 -->
- <v-container fluid v-else-if="view === 'calculator'">
- <h3>Surge 费用计算器(open AI 编写)</h3>
- <div class="text-body-2 grey--text text--darken-2">仅供参考,最终价格以实际为准</div>
- <div class="mt-4">
- <v-text-field v-model="purchaseDate" label="购买日期" type="date"></v-text-field>
- <v-select v-model="licenseType" :items="licenseTypes" label="设备授权数"></v-select>
- <v-btn @click="calculateCost">计算费用</v-btn>
- <p class="mt-2">费用:{{ cost.toFixed(2) }}</p>
- </div>
- </v-container>
- </v-main>
- <!-- 底部 -->
- <v-bottom-navigation ref="naviBar" v-bind="naviBarBind" v-touch="{ down: () => isHidedNaviBottom = true }">
- <v-progress-linear :active="isLoading" height="2" absolute top indeterminate></v-progress-linear>
- <v-btn @click="switchView('')" value="">{{ $t('menus.home') }}<v-icon>mdi-home</v-icon></v-btn>
- <v-btn @click="switchView('app')" value="app">{{ $t('menus.apps') }}<v-icon>mdi-application</v-icon></v-btn>
- <v-btn @click="switchView('sub')" value="sub">{{ $t('menus.subs') }}<v-icon>mdi-database</v-icon></v-btn>
- <v-btn @click="switchView('my')" value="my">
- <template v-if="myIcon">
- <span v-if="!isHideMyTitle">{{ $t('menus.profile') }}</span>
- <v-avatar :size="isHideMyTitle ? 36 : 24"><v-img :src="myIcon" /></v-avatar>
- </template>
- <template v-else>
- <span v-if="!isHideMyTitle">{{ $t('menus.profile') }}</span>
- <v-icon :size="isHideMyTitle ? 36 : 24">mdi-face-profile</v-icon>
- </template>
- </v-btn>
- </v-bottom-navigation>
- <v-fab-transition>
- <v-speed-dial
- v-show="!box.usercfgs.isHideBoxIcon && !isWallpaperMode"
- direction="top"
- fixed
- fab
- bottom
- :left="box.usercfgs.isLeftBoxIcon"
- :right="!box.usercfgs.isLeftBoxIcon === true"
- >
- <template #activator>
- <v-btn
- fab
- text
- @dblclick="reload()"
- v-touch="{
- left: () => box.usercfgs.isLeftBoxIcon = true,
- right: () => box.usercfgs.isLeftBoxIcon = false,
- up: () => {
- clearWallpaper()
- setWallpaper()
- },
- down: () => {
- isWallpaperMode = !!!isWallpaperMode
- changeWallpaper()
- }
- }"
- >
- <v-avatar><img :src="box.syscfgs.boxjs.icons[iconThemeIdx]" /></v-avatar>
- </v-btn>
- </template>
- <v-btn dark v-if="!box.usercfgs.isHideHelp" fab small color="grey" @click="open('https://chavyleung.gitbook.io/boxjs')">
- <v-icon>mdi-help</v-icon>
- </v-btn>
- <v-btn dark v-if="!box.usercfgs.isHideHelp" fab small color="purple" @click="ui.versionSheet.show = true">
- <v-icon>mdi-new-box</v-icon>
- </v-btn>
- <v-btn dark v-if="!box.usercfgs.isCalculator" fab small color="yellow" @click="switchView('calculator')">
- <v-icon>mdi-calculator-variant-outline</v-icon>
- </v-btn>
- <v-btn dark fab small color="pink" @click="box.usercfgs.isLeftBoxIcon = !box.usercfgs.isLeftBoxIcon">
- <v-icon> {{box.usercfgs.isLeftBoxIcon ? 'mdi-format-horizontal-align-right' : 'mdi-format-horizontal-align-left'}} </v-icon>
- </v-btn>
- <v-btn dark v-if="!box.usercfgs.isHideRefresh" fab small color="orange" @click="reload()">
- <v-icon>mdi-refresh</v-icon>
- </v-btn>
- <v-btn dark v-if="!box.usercfgs.isHideCoding" fab small @click="switchView('coding')">
- <v-icon>mdi-code-tags</v-icon>
- </v-btn>
- <v-btn dark v-if="!box.usercfgs.isHidedSearch" fab small color="green" @click="ui.searchDialog.show = true">
- <v-icon>mdi-magnify</v-icon>
- </v-btn>
- </v-speed-dial>
- </v-fab-transition>
- <v-bottom-sheet v-model="ui.versionSheet.show" scrollable fullscreen>
- <v-card v-if="box.versions">
- <v-subheader v-touch="{ down: () => ui.versionSheet.show = false }">
- <v-btn icon small @click="open('https://chavyleung.gitbook.io/boxjs/base/upgrade')">
- <v-icon>mdi-help-circle</v-icon>
- </v-btn>
- <v-spacer></v-spacer>
- <template v-if="hasNewVersion">
- <v-btn text small v-if="env.id === 'Loon'" @click="update('loon://update?sub=all')"
- >{{ $t('versionSheet.updateButton') }}</v-btn
- >
- <v-btn
- text
- small
- v-else-if="env.id === 'QuanX'"
- @click="update('quantumult-x:///add-resource?remote-resource=%7B%22rewrite_remote%22%3A%5B%22https%3A%2F%2Fgithub.com%2Fchavyleung%2Fscripts%2Fraw%2Fmaster%2Fbox%2Frewrite%2Fboxjs.rewrite.quanx.conf%2Ctag%3Dboxjs%22%5D%7D')"
- >{{ $t('versionSheet.updateButton') }}</v-btn
- >
- </template>
- <template v-else>
- <v-btn text small>{{ $t('versionSheet.versionButton') }}</v-btn>
- </template>
- <v-spacer></v-spacer>
- <v-btn icon small @click="ui.versionSheet.show = false">
- <v-icon>mdi-chevron-double-down</v-icon>
- </v-btn>
- </v-subheader>
- <v-divider></v-divider>
- <v-card-text style="height: 80%">
- <div class="mt-6" v-for="(ver, verIdx) in box.versions">
- <h2 :class="version === ver.version ? 'primary--text' : undefined">v{{ver.version}}</h2>
- <div class="pl-4 pt-2" v-for="(note, noteIdx) in ver.notes">
- <strong>{{note.name}}</strong>
- <ul>
- <li v-for="(desc, descIdx) in note.descs">{{desc}}</li>
- </ul>
- </div>
- </div>
- </v-card-text>
- <v-dialog persistent v-model="ui.reloadDialog.show">
- <v-card>
- <v-card-title>{{ $t('reloadDialog.title') }}</v-card-title>
- <v-card-text>{{ $t('reloadDialog.text') }}</v-card-text>
- <v-card-actions>
- <v-spacer></v-spacer>
- <v-btn text small color="grey" @click="ui.reloadDialog.show = false">{{ $t('base.dialog.close') }}</v-btn>
- <v-btn text small color="primary" @click="reload()" :loading="isLoading">{{ $t('reloadDialog.reload') }}</v-btn>
- </v-card-actions>
- </v-card>
- </v-dialog>
- </v-card>
- </v-bottom-sheet>
- <v-bottom-sheet v-model="ui.exeScriptSheet.show" scrollable fullscreen>
- <v-card>
- <v-card-title v-touch="{ down: () => ui.exeScriptSheet.show = false }">
- 执行结果
- <v-spacer></v-spacer>
- <v-btn icon @click="ui.exeScriptSheet.show = false">
- <v-icon>mdi-chevron-double-down</v-icon>
- </v-btn>
- </v-card-title>
- <v-divider></v-divider>
- <v-card-text style="height: 80%">
- <div class="mt-4" v-if="ui.exeScriptSheet.resp">
- <p class="text-pre-wrap" v-if="ui.exeScriptSheet.resp.exception" v-text="ui.exeScriptSheet.resp.exception"></p>
- <p class="text-pre-wrap" v-else-if="ui.exeScriptSheet.resp.output" v-text="ui.exeScriptSheet.resp.output"></p>
- <p v-else v-text="JSON.stringify(ui.exeScriptSheet.resp)"></p>
- </div>
- </v-card-text>
- </v-card>
- </v-bottom-sheet>
- </v-app>
- </div>
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js"></script>
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-i18n.min.js"></script>
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.min.js"></script>
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js"></script>
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/dayjs.min.js"></script>
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/timeago.min.js"></script>
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/uuidv4.min.js"></script>
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-clipboard.min.js"></script>
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/min/vs/loader.js"></script>
- <script>
- Vue.prototype.timeago = timeago
- Vue.prototype.dayjs = dayjs
- new Vue({
- el: '#app',
- vuetify: new Vuetify(),
- i18n: new VueI18n({
- locale: 'zh-CN',
- messages: {
- 'en-US': {
- base: {
- dialog: {
- apply: 'Apply',
- save: 'Save',
- view: 'View',
- close: 'Close',
- ok: 'OK'
- },
- sort: {
- up: 'Up',
- dn: 'Down'
- },
- cmd: {
- cp: 'Copy',
- del: 'Delete',
- imp: 'Import',
- exp: 'Export',
- mod: 'Modify',
- share: 'Share',
- recovery: 'Recovery',
- duplicate: 'Duplicate'
- }
- },
- menus: {
- home: 'Home',
- apps: 'Applications',
- subs: 'Subscriptions',
- profile: 'Profile'
- },
- apps: {
- fav: 'Favorites',
- unStar: 'Unstar',
- sysApps: 'System Applications'
- },
- appDetail: {
- scripts: 'Scripts',
- settings: 'Settings',
- curSession: 'Current Session',
- copyDatas: 'Copy Datas',
- clearDatas: 'Clear Datas',
- noDatas: 'No datas',
- impDialog: {
- title: 'Import Session',
- data: 'Session Data (JSON)',
- dataDesc: 'You can get session data via `Current Session` more > Copy'
- }
- },
- subs: {
- addDialog: {
- title: 'Add Subscription',
- name: 'Session Name',
- url: 'Subscription url',
- urlDesc: 'https://raw.githubusercontent.com/chavyleung/scripts/master/box/chavy.boxjs.json'
- },
- sessionEditor: {
- title: 'Modify Session',
- name: 'Name',
- nameDesc: 'Leave your name',
- avatar: 'Avatar (Optional)',
- avatarDesc: 'Your avatar link'
- },
- install: 'Install',
- add: 'Add Subscription',
- subs: 'More Subscriptions',
- appSubs: 'App Subscriptions',
- errData: 'Error Data',
- updated: 'Updated',
- repo: 'Repository'
- },
- prefs: {
- appearance: 'Appearance',
- appearances: {
- auto: 'Auto',
- dark: 'Dark',
- light: 'Light'
- },
- background: 'Background',
- icon: 'Dark icon',
- iconDesc: 'Available in dark mode',
- bgMode: 'Backgroud Mode',
- bgModeDesc: 'Hides bars & icons',
- hideTopBar: 'Hide TopBar',
- hideTopBarDesc: 'Restore via sidebar',
- autoTopBar: 'Auto TopBar',
- autoTopBarDesc: 'Hides when scrolling',
- hideBottomBar: 'Hide BottomBar',
- hideBottomBarDesc: 'Restore via sidebar',
- autoBottomBar: 'Auto BottomBar',
- autoBottomBarDesc: 'Hides when scrolling',
- muteMode: 'Do Not Disturb',
- muteModeDesc: 'Disable notifications',
- hideHelp: 'Hide Help',
- hideHelpDesc: 'Hides help button',
- hideBoxJs: 'Hide BoxJs',
- hideBoxJsDesc: 'Hides BoxJs button',
- hideProfileTitle: 'Hide Profile Title',
- hideProfileTitleDesc: 'Show avatar only',
- hideCodding: 'Hide Codding',
- hideCoddingDesc: 'Hides codding button',
- hideReload: 'Hide Reload',
- hideReloadDesc: 'Reload by double tap BoxJs',
- debugMode: 'Debug Mode',
- debugModeDesc: 'No page caches',
- debugPage: 'Debug Page Addr',
- debugPageDesc: 'Load page from...'
- },
- profile: {
- leaveName: 'Leave a name',
- dataviewer: 'Data Viewer',
- editor: {
- title: 'Profile',
- name: 'Name',
- nameDesc: 'Leave your name',
- avatar: 'Avatar (Optional)',
- avatarDesc: 'Your avatar link'
- },
- impDialog: {
- title: 'Import Backup',
- impData: 'Backup Data',
- impDataDesc: ''
- },
- datas: 'Datas',
- apps: 'Apps',
- subs: 'Subs',
- sessions: 'Sessions',
- imp: 'Import',
- bak: 'Backup',
- bakName: 'Global Backup'
- },
- bakDetail: {
- note: 'Note: ',
- id: 'Backup Index',
- name: 'Backup Name',
- title: 'Backup Informations',
- dataTitle: 'Backup Datas'
- },
- codding: {
- title: 'Script Editor'
- },
- viewer: {
- dataViewer: 'Data Viewer',
- dataKey: 'Data Key',
- dataKeyDesc: 'Input the data key',
- dataEditor: 'Data Editor',
- dataVal: 'Data Value'
- },
- reloadDialog: {
- title: 'Next',
- text: 'Reload page?',
- reload: 'GO'
- },
- versionSheet: {
- updateButton: 'UPDATE NOW',
- versionButton: 'NEW VERSION'
- }
- },
- 'zh-CN': {
- base: {
- dialog: {
- apply: '应用',
- save: '保存',
- close: '关闭',
- ok: '好'
- },
- sort: {
- up: '上移',
- dn: '下移'
- },
- cmd: {
- cp: '复制',
- del: '删除',
- imp: '导入',
- mod: '修改',
- share: '分享',
- recovery: '恢复',
- duplicate: '克隆'
- }
- },
- menus: {
- home: '主页',
- apps: '应用',
- subs: '订阅',
- profile: '我的'
- },
- apps: {
- fav: '收藏应用',
- unStar: '取消收藏',
- sysApps: '内置应用'
- },
- appDetail: {
- scripts: '应用脚本',
- settings: '应用设置',
- curSession: '当前会话',
- copyDatas: '复制数据',
- clearDatas: '清除数据',
- noDatas: '无数据',
- impDialog: {
- title: '导入会话',
- data: '会话数据 (JSON)',
- dataDesc: '你可通过 `当前会话` 更多 > 复制 来获得会话数据'
- }
- },
- subs: {
- addDialog: {
- title: '添加订阅',
- name: 'Session Name',
- url: '订阅地址',
- urlDesc: 'https://raw.githubusercontent.com/chavyleung/scripts/master/box/chavy.boxjs.json'
- },
- sessionEditor: {
- title: '修改会话',
- name: '会话名称'
- },
- install: '安装',
- add: '添加订阅',
- moreSubs: '更多订阅',
- appSubs: '应用订阅',
- errData: '格式错误',
- updated: '更新于',
- repo: '仓库'
- },
- prefs: {
- appearance: '外观',
- appearances: {
- auto: '自动',
- dark: '暗黑',
- light: '明亮'
- },
- background: '背景图标',
- icon: '透明图标',
- iconDesc: '明亮主题下强制使用彩色图标',
- bgMode: '壁纸模式',
- bgModeDesc: '同时隐藏顶栏、底栏、图标',
- hideTopBar: '隐藏顶栏',
- hideTopBarDesc: '通过侧栏恢复',
- autoTopBar: '自动顶栏',
- autoTopBarDesc: '滚动时自动隐藏',
- hideBottomBar: '隐藏底栏',
- hideBottomBarDesc: '通过侧栏恢复',
- autoBottomBar: '自动底栏',
- autoBottomBarDesc: '滚动时自动隐藏',
- muteMode: '勿扰模式',
- muteModeDesc: '不发出通知 (仍记日志)',
- hideHelp: '隐藏帮助按钮',
- hideHelpDesc: '隐藏帮助按钮',
- hideBoxJs: '隐藏悬浮按钮',
- hideBoxJsDesc: '隐藏右下角 BoxJs 悬浮按钮',
- hideProfileTitle: '隐藏我的标题',
- hideProfileTitleDesc: '只显示头像',
- hideCodding: '隐藏编码按钮',
- hideCoddingDesc: 'Hides script editor entrance',
- hideReload: '隐藏刷新按钮',
- hideReloadDesc: '仍可双击悬浮按钮刷新页面',
- debugMode: '调试模式',
- debugModeDesc: '每次请求都获取最新的页面',
- debugPage: '调试页面地址',
- debugPageDesc: '页面源码的获取地址'
- },
- profile: {
- leaveName: '大侠, 请留名!',
- dataviewer: '数据查看器',
- editor: {
- title: '个人资源',
- name: '昵称',
- nameDesc: '大侠, 请留名!',
- avatar: '头像 (可选)',
- avatarDesc: '头像链接, 建议从 Github 获取'
- },
- impDialog: {
- title: 'Import Backup',
- impData: 'Backup Data',
- impDataDesc: ''
- },
- datas: '我的数据',
- apps: '应用',
- subs: '订阅',
- sessions: '会话',
- imp: '导入',
- bak: '备份',
- bakName: '全局备份'
- },
- bakDetail: {
- id: '备份索引',
- name: '备份名称',
- title: '备份信息',
- dataTitle: '备份数据'
- },
- codding: {
- title: '脚本编辑器'
- },
- viewer: {
- dataViewer: '数据查看器',
- dataKey: '数据键 (Key)',
- dataKeyDesc: '输入要查询的数据键, 如: boxjs_host',
- dataEditor: '数据编辑器',
- dataVal: '数据内容'
- },
- reloadDialog: {
- title: '接下来',
- text: '更新完成, 需要刷新页面吗?',
- reload: '马上刷新'
- },
- versionSheet: {
- updateButton: ' 立即更新',
- versionButton: '新版本'
- }
- }
- }
- }),
- data() {
- return {
- ui: {
- // 请求类
- isCors: false, // 是否需要发起跨域请求
- // 路径类
- path: null,
- bfpath: null,
- view: null,
- bfview: null,
- subview: null,
- bfsubview: null,
- // 数据类
- collaborators: [],
- contributors: [],
- isSaveUserCfgs: false,
- // 界面类
- scrollY: {}, //记录每个界面的滚动值
- overlay: { show: false, val: 60 },
- snackbar: { show: false, color: 'primary', msg: '' },
- searchBar: {
- isActive: true,
- color: 'primary',
- class: 'rounded-xl',
- readonly: true,
- input: '',
- hideNoData: true,
- hideDetails: true,
- solo: true
- },
- viewer: {
- key: '',
- val: ''
- },
- searchDialog: { show: false },
- versionSheet: { show: false },
- updatesheet: { show: false },
- exeScriptSheet: { show: false, resp: null },
- naviDrawer: { show: false },
- reloadDialog: { show: false },
- modSessionDialog: { show: false },
- editProfileDialog: { show: false },
- impGlobalBakDialog: { show: false, impval: '' },
- impAppDatasDialog: { show: false, impval: '' },
- addAppSubDialog: { show: false, url: '' },
- installConfirmDialog: { show: false, title: '安装确认', message: '是否自动安装外部资源?' },
- defaultIcons: [
- 'https://raw.githubusercontent.com/Orz-3/mini/master/appstore.png',
- 'https://raw.githubusercontent.com/Orz-3/task/master/appstore.png'
- ]
- },
- boxServerData: null,
- box: null,
- purchaseDate: null,
- cost: 0,
- licenseTypes: ["1 Device License", "3 Devices License", "5 Devices License"],
- licenseType: ''
- }
- },
- computed: {
- // 获取当前版本
- version() {
- return this.box.syscfgs.version
- },
- // 标题
- title() {
- const isDebugWeb = this.box.usercfgs.isDebugWeb
- const debugger_web = this.box.usercfgs.debugger_web
- const isDebugMode = this.box.syscfgs.isDebugMode
- return `BoxJs - v${this.version}${isDebugMode ? ` - ${debugger_web}` : ''}`
- },
- // 判断是否有新版本
- hasNewVersion() {
- const curver = this.box.syscfgs.version
- const vers = this.box.versions
- if (curver && vers && vers.length > 0) {
- const lastestVer = vers[0].version
- return this.compareVersion(lastestVer, curver) > 0
- }
- },
- timeagoLang() {
- const lang = this.box.usercfgs.lang
- return lang ? lang.replace('-', '_') : 'zh_CN'
- },
- // 判断是否需要跨域请求
- isCors() {
- return this.ui.isCors
- },
- // 是否加载中
- isLoading: {
- set(val) {
- this.ui.overlay.show = val
- },
- get() {
- return this.ui.overlay.show
- }
- },
- // 判断当前是否`WebApp`
- isWebApp() {
- return window.navigator.standalone
- },
- // 是否壁纸模式
- isWallpaperMode: {
- get() {
- return this.box.usercfgs.isWallpaperMode
- },
- set(val) {
- this.box.usercfgs.isWallpaperMode = val === true
- }
- },
- // 切换壁纸
- changeWallpaper() {
- if (this.isWallpaperMode) {
- if (this.box.usercfgs.changeBgImgEnterDefault) {
- const bgUrl = this.bgimgs.find((bgimg) => bgimg.name === this.box.usercfgs.changeBgImgEnterDefault).url
- if (bgUrl) {
- this.box.usercfgs.bgimg = bgUrl
- this.saveUserCfgs(false)
- }
- }
- } else {
- if (this.box.usercfgs.changeBgImgOutDefault) {
- const bgUrl = this.bgimgs.find((bgimg) => bgimg.name === this.box.usercfgs.changeBgImgOutDefault).url
- if (bgUrl || bgUrl === '') {
- this.box.usercfgs.bgimg = bgUrl
- this.saveUserCfgs(false)
- }
- }
- }
- },
- // 当前环境: Surge、QuanX、Loon、NodeJs
- env: {
- // 获取当前容器环境
- get() {
- return this.envs.find((env) => env.id === this.box.syscfgs.env)
- },
- // 设置当前容器环境
- set(val) {
- this.box.syscfgs.env = val
- }
- },
- // 获取容器列表
- envs() {
- const envs = this.box.syscfgs.envs
- envs.forEach((env) => (env.icon = env.icons[this.iconThemeIdx]))
- return envs
- },
- // 获取当前路径
- path: {
- get() {
- return this.ui.path
- },
- set(path) {
- this.ui.path = path
- }
- },
- // 获取上一个路径
- bfpath() {
- return this.ui.bfpath || ''
- },
- // 获取当前页面: http://boxjs.com/app/baidu => `app`
- view() {
- return this.ui.view || ''
- },
- // 获取当前页面: http://boxjs.com/app/baidu => `baidu`
- subview() {
- return this.ui.subview ? this.ui.subview : ''
- },
- // 判断当前是否`主页面` (非二级页面)
- isMainView() {
- return !this.subview
- },
- // 判断当前是否`暗黑模式`
- isDarkMode() {
- let isDark = true
- const theme = this.box.usercfgs.theme
- if (theme === 'auto') {
- isDark = this.isSystemDarkMode
- } else if (theme === 'light') {
- isDark = false
- }
- return isDark
- },
- // 判断系统是否`暗黑模式`
- isSystemDarkMode() {
- return window.matchMedia('(prefers-color-scheme: dark)').matches
- },
- // 是否透明图标
- isTransparentIcons() {
- return this.box.usercfgs.isTransparentIcons
- },
- // 获取图标下标, 透明: 0, 彩色: 1 (默认)
- iconThemeIdx() {
- if (this.isDarkMode) {
- return this.isTransparentIcons ? 0 : 1
- }
- return 1
- },
- // 获取环境图标下标, 透明: 0, 彩色: 1 (默认)
- iconEnvThemeIdx() {
- return this.isDarkMode ? 0 : 1
- },
- isHidedSearchBar: {
- get() {
- return this.box.usercfgs.isHidedSearchBar || this.isWallpaperMode
- },
- set(val) {
- this.box.usercfgs.isHidedSearchBar = val === true
- }
- },
- isAutoSearchBar: {
- get() {
- return this.box.usercfgs.isAutoSearchBar
- },
- set(val) {
- this.box.usercfgs.isAutoSearchBar = val === true
- if (val === false && !this.isHidedSearchBar) {
- this.$refs.appBar.isActive = true
- }
- }
- },
- isHidedAppIcons: {
- get() {
- return this.box.usercfgs.isHidedAppIcons || this.isWallpaperMode
- },
- set(val) {
- this.box.usercfgs.isHidedAppIcons = val === true
- }
- },
- isHidedNaviBottom: {
- get() {
- return this.box.usercfgs.isHidedNaviBottom || this.isWallpaperMode
- },
- set(val) {
- this.box.usercfgs.isHidedNaviBottom = val === true
- }
- },
- isAutoNaviBottom: {
- get() {
- return this.box.usercfgs.isAutoNaviBottom
- },
- set(val) {
- this.box.usercfgs.isAutoNaviBottom = val === true
- if (val === false && !this.isHidedNaviBottom) {
- this.$refs.naviBar.isActive = true
- }
- }
- },
- // 判断是否有壁纸
- isWallpaper() {
- return !!this.box.usercfgs.bgimg
- },
- // 是否存在多张壁纸
- isMutiWallpaper() {
- return this.bgimgs && this.bgimgs.length > 2
- },
- // 背景图片列表
- bgimgs() {
- const items = []
- const bgimgs = this.box.usercfgs.bgimgs
- if (bgimgs) {
- bgimgs.split('\n').forEach((img) => {
- const [name, url] = img.split(',')
- items.push({ name, url })
- })
- }
- return items
- },
- // 样式
- appViewStyle() {
- // 主题发生变化时给 <body> 设置背景色
- if (this.isWallpaper) {
- this.setWallpaper()
- } else {
- this.clearWallpaper()
- const darkBg = `background: #121212;`
- const lightWebappBg = `background-image: linear-gradient(to bottom,rgba(0,0,0,.2) 0,transparent 76px);`
- const lightBg = `${this.isWebApp ? lightWebappBg : 'background: #fff;'}`
- document.querySelector('#BG').setAttribute('style', this.isDarkMode ? darkBg : lightBg)
- }
- if (this.isWebApp) {
- return { background: 'none' }
- } else if (this.isWallpaper && !this.isTransparent) {
- return { background: 'transparent' }
- } else if (!this.isWallpaper && this.isTransparent) {
- return { background: 'none' }
- } else {
- return
- }
- },
- appTitleStyle() {
- const style = {}
- if (this.isWallpaper) {
- style['color'] = '#fff'
- style['text-shadow'] = 'black 0.1em 0.1em 0.2em'
- }
- return style
- },
- appBarBind() {
- const app = true
- const isEmptyLight = this.isWebApp && !this.isDarkMode && !this.isWallpaper
- const color = isEmptyLight ? 'primary' : 'transparent'
- const flat = color === 'transparent'
- const hideOnScroll = !this.isHidedSearchBar && this.isAutoSearchBar
- const collapseOnScroll = false
- const scrollThreshold = 20
- return { app, color, flat, hideOnScroll, collapseOnScroll, scrollThreshold }
- },
- searchBarBind() {
- const color = this.isDarkMode ? null : 'primary'
- return { color }
- },
- naviBarBind() {
- const app = true
- const grow = true
- const color = 'primary'
- const value = this.view
- const inputValue = !this.isHidedNaviBottom
- const hideOnScroll = !this.isHidedNaviBottom && this.isAutoNaviBottom
- const scrollThreshold = 160
- return { app, grow, color, value, inputValue, hideOnScroll, scrollThreshold }
- },
- appIconFontStyle() {
- const style = {
- 'font-size': '10px',
- 'max-width': '54px'
- }
- if (this.isWallpaper) {
- style['color'] = '#fff'
- style['text-shadow'] = 'black 0.1em 0.1em 0.2em'
- }
- return style
- },
- // 是否保存用户偏好
- isSaveUserCfgs: {
- set(val) {
- this.ui.isSaveUserCfgs = val
- },
- get() {
- return this.ui.isSaveUserCfgs
- }
- },
- // 我的图标
- myIcon() {
- return this.box.usercfgs.icon
- },
- // 是否隐藏`我的`标题
- isHideMyTitle() {
- return this.box.usercfgs.isHideMyTitle
- },
- // 添加`应用订阅`对话框
- addAppSubDialog: {
- get() {
- return this.ui.addAppSubDialog.show
- },
- set(show) {
- this.ui.addAppSubDialog.show = show
- }
- },
- // 获取持久化数据
- datas() {
- return this.box.datas
- },
- // 应用会话数据
- sessions() {
- return this.box.sessions
- },
- // 获取`收藏`应用
- favApps() {
- const favapps = []
- const favAppIds = this.box.usercfgs.favapps || []
- if (favAppIds) {
- favAppIds.forEach((favAppId) => {
- const app = this.apps.find((app) => app.id === favAppId)
- if (app) {
- favapps.push(app)
- }
- })
- }
- return favapps
- },
- // 获取`内置`应用
- sysApps() {
- const sysapps = this.box.sysapps || []
- sysapps.forEach((app) => this.loadAppBaseInfo(app))
- sysapps.sort((a, b) => a.name.localeCompare(b.name))
- return sysapps
- },
- // 获取`订阅`应用 (注意: 这个接口是获取`应用`)
- subApps() {
- const apps = []
- this.appSubs.forEach((appsub) => {
- const sub = this.appSubCaches[appsub.url]
- if (sub && sub.apps && Array.isArray(sub.apps) && !appsub.isErr) {
- sub.apps.forEach((app) => {
- this.loadAppBaseInfo(app)
- apps.push(app)
- })
- }
- })
- return apps
- },
- // 获取`应用`订阅 (注意: 这个接口是获取`订阅`)
- appSubs() {
- // 深拷贝一份数据, 避免污染`usercfgs`
- const subs = JSON.parse(JSON.stringify(this.box.usercfgs.appsubs))
- subs.forEach((sub) => {
- const raw = JSON.parse(JSON.stringify(sub))
- const cacheSub = this.appSubCaches[sub.url]
- const isValidSub = cacheSub && Array.isArray(cacheSub.apps) && cacheSub.apps.length > 0
- const isValidSubApps = isValidSub && !cacheSub.apps.find((app) => !app.id)
- if (cacheSub && isValidSub && isValidSubApps) {
- Object.assign(sub, cacheSub)
- } else {
- sub.isErr = true
- sub.apps = []
- }
- sub.name = sub.name ? sub.name : '匿名订阅'
- sub.author = sub.author ? sub.author : '@anonymous'
- sub.repo = sub.repo ? sub.repo : sub.url
- sub.raw = raw
- })
- return subs
- },
- // 获取`订阅`缓存
- appSubCaches() {
- return this.box.appSubCaches
- },
- // 获取所有应用`内置应用`+`订阅应用`
- apps() {
- const apps = []
- apps.push(...this.subApps)
- apps.push(...this.sysApps)
- return apps
- },
- searchApps() {
- return this.apps.filter((app) => app.id.includes(this.ui.searchBar.input) || app.name.includes(this.ui.searchBar.input))
- },
- // 获取全局备份
- baks() {
- return this.box.globalbaks
- },
- // 当前应用
- curapp() {
- if (this.view === 'app' && !!this.subview) {
- const appId = decodeURIComponent(decodeURIComponent(this.subview))
- const app = this.apps.find((app) => app.id === appId)
- this.loadAppDataInfo(app)
- return app
- }
- },
- // 当前备份
- curbak() {
- if (this.view === 'bak' && !!this.subview) {
- const bakId = decodeURIComponent(decodeURIComponent(this.subview))
- const bak = this.baks.find((bak) => bak.id === bakId)
- return bak
- }
- }
- },
- watch: {
- 'ui.path': {
- handler(newval, oldval) {
- if (/^\/#/.test(newval)) {
- newval = newval.replace('/#', '')
- }
- const [, view, subview] = newval.split('/')
- this.ui.view = view
- this.ui.subview = subview
- if (oldval) {
- const [, bfview, bfsubview] = oldval.split('/')
- this.ui.bfpath = oldval
- this.ui.bfview = bfview
- this.ui.bfsubview = bfsubview
- }
- if (newval === '/coding') {
- require.config({ paths: { vs: 'https://cdn.jsdelivr.net/npm/[email protected]/min/vs' } })
- require(['vs/editor/editor.main'], () => {
- const envjs_demo = [
- '/** ',
- ' * 注意: ',
- ' * 在这里你可以使用完整的 EnvJs 环境',
- ' * ',
- ' * 同时: ',
- ' * 你`必须`手动调用 $done()',
- ' * ',
- ' * 因为: ',
- ' * BoxJs 不为主动执行的脚本调用 $done()',
- ' * 而把 $done 的时机完全交由脚本控制',
- ' * ',
- ' * 最后: ',
- ' * 这段脚本是可以直接运行的!',
- ' */ ',
- 'const host = $.getdata("boxjs_host")',
- 'console.log("输出的内容是返回给浏览器的!")',
- '$.msg($.name, host)',
- '$.done()',
- '// $done() 或 $.done() 都可以'
- ]
- const surgejs_demo = [
- '/** ',
- ' * 注意: ',
- ' * 你正在使用 Surge HTTP-API 环境',
- ' * ',
- ' * 在这里:你不可以使用 EnvJs',
- ' * 请确保:你的脚本能被 Surge 独立运行',
- ' * ',
- ' * 最后: ',
- ' * 这段脚本是可以直接运行的!',
- ' */ ',
- 'const host = $persistentStore.read("boxjs_host")',
- 'const msgs = [""]',
- '',
- 'msgs.push("这是日志的内容")',
- 'msgs.push("BoxJs host: " + host)',
- '',
- 'console.log(msgs.join("\\n"))',
- '$done()'
- ]
- this.ui.editor = monaco.editor.create(document.getElementById('container'), {
- fontSize: 12,
- tabSize: 2,
- value: this.env.id === 'Surge' && this.box.usercfgs.httpapi ? surgejs_demo.join('\n') : envjs_demo.join('\n'),
- language: 'javascript',
- minimap: { enabled: false },
- theme: this.isDarkMode ? 'vs-dark' : 'vs'
- })
- })
- }
- window.onresize = () => {
- if (this.ui.editor) {
- this.ui.editor.layout()
- }
- }
- // 还原视图当时的滚动值
- const scrollY = this.ui.scrollY[this.path] || 0
- const offsetTop = -this.$vuetify.application.top
- this.$vuetify.goTo(scrollY, { duration: 0, offset: offsetTop })
- }
- },
- 'ui.searchDialog.show': {
- handler(newval) {
- if (newval === false) {
- this.ui.searchBar.input = ''
- } else {
- if (this.$refs.search) {
- this.$nextTick(() => {
- setTimeout(() => this.$refs.search.$refs.input.focus(), 0)
- })
- }
- }
- }
- },
- 'ui.naviDrawer.show': {
- handler(newval, oldval) {
- // 获取贡献者列表
- if (_.isEmpty(this.ui.contributors)) {
- this.getContributors()
- }
- }
- },
- 'box.usercfgs': {
- deep: true,
- handler(newval, oldval) {
- if (oldval && this.isSaveUserCfgs) {
- this.saveUserCfgs()
- }
- this.isSaveUserCfgs = true
- }
- },
- 'box.usercfgs.lang': {
- handler(newval) {
- this.$i18n.locale = newval
- }
- },
- 'box.usercfgs.theme': {
- handler() {
- if (this.ui.editor) {
- this.ui.editor._themeService.setTheme(this.isDarkMode ? 'vs-dark' : 'vs')
- }
- }
- },
- 'box.usercfgs.color_dark_primary': {
- handler() {
- this.loadTheme()
- }
- },
- 'box.usercfgs.color_light_primary': {
- handler() {
- this.loadTheme()
- }
- }
- },
- beforeCreate() {
- // 请求&响应拦截器, 显示&隐藏加载条
- axios.interceptors.request.use((cfg) => {
- this.isLoading = true
- return cfg
- })
- axios.interceptors.response.use(
- (resp) => {
- this.isLoading = false
- return resp
- },
- (error) => (this.isLoading = false)
- )
- },
- created() {
- // 如果 url 参数中指定的 baseURL, 则后续的请求都请求到指定的域
- if (window.location.search) {
- const [, baseURL] = /baseURL=(.*?)(&|$)/.exec(window.location.search)
- axios.defaults.baseURL = baseURL || ''
- this.ui.isCors = true
- }
- // 根据路径跳转视图
- const defview = '/'
- if (!this.isCors) {
- let path = location.pathname + location.hash
- this.path = path === '/' ? defview : path
- } else {
- this.path = defview
- }
- // 监听浏览器后退事件
- window.addEventListener('popstate', (e) => (this.path = e.state ? e.state.url : '/'), false)
- // 如果后端没有渲染数据, 则发出请求获取数据
- if (this.boxServerData) {
- this.box = this.boxServerData
- this.setHttpBackend()
- } else {
- axios.get('/query/boxdata').then((resp) => {
- this.box = resp.data
- this.setHttpBackend()
- })
- }
- // 延时执行, 避免多个请求抢占资源
- this.getVersions()
- this.loadTheme()
- },
- mounted() {
- const el = document.getElementById('appList')
- const _this = this
- const sortable = Sortable.create(el, {
- animation: 600,
- delay: 200,
- onEnd(evt) {
- const favApps = _this.box.usercfgs.favapps
- const oldIdx = evt.oldIndex
- const newIdx = evt.newIndex
- const moveItem = favApps[oldIdx]
- favApps.splice(oldIdx, 1)
- favApps.splice(newIdx, 0, moveItem)
- }
- })
- if (!this.box.usercfgs.lang) {
- const locale = this.$i18n.locale
- this.box.usercfgs.lang = locale === 'zh-CN' ? locale : 'en-US'
- this.saveUserCfgs()
- } else {
- this.$i18n.locale = this.box.usercfgs.lang
- }
- if (this.path.includes('/#/bak/')) {
- const [, backupId] = this.path.split('/#/bak/')
- this.loadGlobalBak(backupId)
- } else if (this.path.includes('/#/sub/add/')) {
- const [, url] = this.path.split('/#/sub/add/')
- this.addAppSub(decodeURIComponent(url), true)
- }
- },
- methods: {
- reload() {
- this.isLoading = true
- window.location.reload()
- },
- open(url) {
- window.open(url)
- },
- update(url) {
- this.open(url)
- this.ui.versionSheet.show = false
- this.ui.reloadDialog.show = true
- },
- openInstall(subId) {
- const newsub = this.appSubs.find((s) => s.raw.id === subId)
- const event = newsub.onInstall
- if (event) {
- const install = event.install
- const redirect = install[this.env.id]
- if (!redirect) return
- this.ui.installConfirmDialog.show = true
- this.ui.installConfirmDialog.title = event.title
- this.ui.installConfirmDialog.message = event.message
- this.ui.installConfirmDialog.url = redirect
- }
- },
- install(url) {
- this.open(url)
- this.ui.installConfirmDialog.show = false
- },
- // 记录每个页面的滚动值
- onScroll(event) {
- const currentY = event.currentTarget.scrollY
- const historyY = this.ui.scrollY[this.path]
- this.ui.scrollY[this.path] = event.currentTarget.scrollY
- // 下拉显示/隐藏顶栏
- // 回弹时才触发显示与隐藏, 1 秒内不重复触发
- if (currentY < historyY && currentY < -80 && !this.ui.isWaitToggleSearchBar) {
- // 壁纸模式: 取消模式模式
- if (this.isWallpaperMode) {
- this.isWallpaperMode = false
- }
- // 非壁纸模式: 显示&隐藏顶栏
- else {
- this.ui.isWaitToggleSearchBar = true
- this.isHidedSearchBar = !this.isHidedSearchBar
- this.toggleWaitSearchBar()
- }
- }
- },
- setWallpaper() {
- let bgimg = ''
- if (this.box.usercfgs.bgimg === '跟随系统') {
- const hasdark = this.bgimgs.find((bgimg) => bgimg.name == '暗黑' || bgimg.name == 'dark')
- const haslight = this.bgimgs.find((bgimg) => bgimg.name == '明亮' || bgimg.name == 'light')
- const darkbgimg = hasdark ? hasdark.url : ``
- const lightbgimg = haslight ? haslight.url : ``
- this.isDarkMode ? (bgimg = darkbgimg) : (bgimg = lightbgimg)
- const bgStyle = [
- `background-image: linear-gradient(to bottom,rgba(0,0,0,.2) 0,transparent 76px), url(${bgimg}?_=${Math.random()})`
- ]
- document.querySelector('#BG').setAttribute('style', bgStyle.join('; '))
- } else {
- const bgStyle = [
- `background-image: linear-gradient(to bottom,rgba(0,0,0,.2) 0,transparent 76px), url(${
- this.box.usercfgs.bgimg
- }?_=${Math.random()})`
- ]
- document.querySelector('#BG').setAttribute('style', bgStyle.join('; '))
- }
- },
- clearWallpaper() {
- document.querySelector('#BG').removeAttribute('style')
- },
- toggleWaitSearchBar: _.debounce(function () {
- this.ui.isWaitToggleSearchBar = false
- }, 1000),
- handleHistory(path) {
- const { hash } = window.location.href
- const state = { title: 'BoxJs', url: '/' + (hash ? hash : '#/') }
- if (!history.state) {
- history.replaceState(state, '')
- }
- state.url = path
- history.pushState(state, '', path)
- },
- // 页面返回
- back() {
- history.back()
- },
- // 切换当前容器环境
- switchEnv(env) {
- this.env = env
- },
- // 切换当前视图
- switchView(path) {
- path = `/#/${path}`
- if (this.path !== path) {
- this.path = path
- this.handleHistory(this.path)
- } else {
- const scrollY = this.ui.scrollY[this.path]
- const isTopY = _.isNil(scrollY) || scrollY === 0
- if (path === '/#/' && isTopY) {
- this.clearWallpaper()
- this.setWallpaper()
- } else if (path === '/#/app' && isTopY) {
- Object.assign(this.box.usercfgs, { favapppanel: [], subapppanel: [], sysapppanel: [] })
- } else if (path === '/#/sub' && isTopY) {
- this.reloadAppSub()
- }
- if (this.ui.scrollY[path] !== 0) {
- this.$vuetify.goTo(0, { duration: 200, offset: 0 })
- }
- }
- },
- // 切换应用视图
- switchAppView(appId) {
- const path = `/#/app/${appId}`
- this.path = path
- this.handleHistory(this.path)
- },
- // 切换备份视图
- switchBakView(backupId) {
- const path = `/#/bak/${backupId}`
- this.loadGlobalBak(backupId).then((resp) => {
- this.path = path
- this.handleHistory(path)
- })
- },
- // 重载主题
- loadTheme() {
- this.$vuetify.theme.dark = this.isDarkMode
- this.$vuetify.theme.themes.light.primary = this.box.usercfgs.color_light_primary || '#F7BB0E'
- this.$vuetify.theme.themes.dark.primary = this.box.usercfgs.color_dark_primary || '#2196F3'
- },
- // 复制文本
- copy(str) {
- this.$copyText(str).then(
- (e) => {
- this.ui.snackbar.show = true
- this.ui.snackbar.msg = '复制成功!'
- this.ui.snackbar.color = 'primary'
- },
- (e) => {
- this.ui.snackbar.show = true
- this.ui.snackbar.msg = '复制失败!'
- this.ui.snackbar.color = 'error'
- }
- )
- },
- // 复制文本
- share(str) {
- const url = `http://boxjs.com/#/sub/add/${encodeURIComponent(str)}`
- this.copy(url)
- },
- // 移动收藏
- moveFav(favIdx, moveCnt) {
- const favapps = this.box.usercfgs.favapps
- const fromIdx = favIdx
- const toIdx = favIdx + moveCnt
- favapps.splice(fromIdx, 1, ...favapps.splice(toIdx, 1, favapps[fromIdx]))
- },
- // 移动订阅
- moveSub(subIdx, moveCnt) {
- const appsubs = this.box.usercfgs.appsubs
- const fromIdx = subIdx
- const toIdx = subIdx + moveCnt
- appsubs.splice(fromIdx, 1, ...appsubs.splice(toIdx, 1, appsubs[fromIdx]))
- },
- // 删除订阅
- delSub(subIdx) {
- this.box.usercfgs.appsubs.splice(subIdx, 1)
- },
- // 收藏应用
- favApp(appId) {
- const favAppIdx = this.box.usercfgs.favapps.findIndex((favAppId) => favAppId === appId)
- if (favAppIdx === -1) {
- this.box.usercfgs.favapps.push(appId)
- } else {
- this.box.usercfgs.favapps.splice(favAppIdx, 1)
- }
- },
- // 加载应用信息
- loadAppBaseInfo(app) {
- // 应用图标
- app.icons = Array.isArray(app.icons) ? app.icons : this.ui.defaultIcons
- const isBrokenIcons = app.icons.find((i) => i.includes('/Orz-3/task/master/'))
- if (isBrokenIcons) {
- app.icons[0] = app.icons[0].replace('/Orz-3/mini/master/', '/Orz-3/mini/master/Alpha/')
- app.icons[1] = app.icons[1].replace('/Orz-3/task/master/', '/Orz-3/mini/master/Color/')
- }
- app.icon = app.icons[this.iconThemeIdx]
- // 是否收藏
- const isFav = this.box.usercfgs.favapps.includes(app.id)
- app.favIcon = isFav ? 'mdi-star' : 'mdi-star-outline'
- app.favIconColor = isFav ? 'primary' : 'grey'
- },
- // 加载应用数据
- loadAppDataInfo(app) {
- if (app.isLoaded) return
- // 加载应用设置
- if (app.settings) {
- app.settings.forEach((setting) => {
- const key = setting.id
- const datval = this.datas[key]
- if (setting.type === 'boolean') {
- setting.val = _.isEmpty(datval) ? setting.val : (datval === 'true' || datval === true)
- } else if (setting.type === 'int') {
- setting.val = datval * 1 || setting.val
- } else if (setting.type === 'checkboxes') {
- if (!_.isEmpty(datval)) {
- setting.val = datval ? datval.split(',') : []
- } else {
- setting.val = Array.isArray(setting.val) ? setting.val : setting.val.split(',')
- }
- } else {
- setting.val = datval || setting.val
- }
- })
- }
- // 加载当前会话数据
- if (app.keys) {
- app.datas = []
- app.keys.forEach((key) => {
- const val = this.datas[key] || ''
- app.datas.push({ key, val })
- })
- }
- // 加载会话列表
- const sessions = this.sessions.filter((session) => session.appId === app.id)
- app.sessions = sessions || []
- // 加载当前切换会话
- const curSessionId = this.box.curSessions[app.id]
- if (curSessionId) {
- const curSession = this.sessions.find((session) => session.id === curSessionId)
- app.curSession = curSession
- }
- app.isLoaded = true
- },
- // 运行远程脚本
- runRemoteScript(url, timeout) {
- const opts = { url, timeout, isRemote: true }
- this.runScript(opts)
- },
- // 运行文本脚本
- runTxtScript() {
- const script = this.ui.editor.getValue()
- const opts = { script }
- this.runScript(opts)
- },
- runScript(opts) {
- axios.post('/api/runScript', opts).then((resp) => {
- if (!this.box.usercfgs.isMute) {
- this.ui.exeScriptSheet.resp = resp.data
- this.ui.exeScriptSheet.show = true
- }
- })
- },
- // 保存用户偏好
- saveUserCfgs() {
- const key = 'chavy_boxjs_userCfgs'
- const val = JSON.stringify(this.box.usercfgs)
- axios.post('/api/save', [{ key, val }]).then((resp) => {
- this.loadTheme()
- })
- },
- // 保存应用设置
- saveAppSettings() {
- const datas = []
- this.curapp.settings.forEach((setting) => {
- const isNilVal = _.isNil(setting.val)
- const key = setting.id
- const val = !isNilVal ? _.toString(setting.val) : ''
- datas.push({ key, val })
- })
- axios.post('/api/save', datas).then((resp) => {
- if (this.curapp.id === 'BoxSetting') {
- this.isSaveUserCfgs = false
- } else {
- delete resp.data.usercfgs
- }
- Object.assign(this.box, resp.data)
- if (this.curapp.id === 'BoxSetting') {
- this.setHttpBackend()
- }
- })
- },
- // 保存应用会话
- saveAppSession() {
- const session = {
- id: uuidv4(),
- name: '会话 ' + (this.curapp.sessions.length + 1),
- appId: this.curapp.id,
- appName: this.curapp.name,
- enable: true,
- createTime: new Date(),
- datas: this.curapp.datas
- }
- this.box.sessions.push(session)
- const key = 'chavy_boxjs_sessions'
- const val = JSON.stringify(this.box.sessions)
- axios.post('/api/save', [{ key, val }]).then((resp) => {
- delete resp.data.usercfgs
- Object.assign(this.box, resp.data)
- })
- },
- // 修改应用会话
- updateAppSession(session) {
- session.datas.forEach((dat) => {
- // 如果属性值是 undefined 或 null, 则修改为 ``, 否则转为字符串
- dat.val = !_.isNil(dat.val) ? _.toString(dat.val) : ''
- })
- const key = 'chavy_boxjs_sessions'
- const val = JSON.stringify(this.box.sessions)
- axios
- .post('/api/save', [{ key, val }])
- .then((resp) => {
- delete resp.data.usercfgs
- Object.assign(this.box, resp.data)
- })
- .finally(() => (this.ui.modSessionDialog.show = false))
- },
- // 删除应用会话
- delAppSession(sessionId) {
- const sessions = this.box.sessions
- const sessionIdx = sessions.findIndex((session) => session.id === sessionId)
- sessions.splice(sessionIdx, 1)
- const key = 'chavy_boxjs_sessions'
- const val = JSON.stringify(this.box.sessions)
- axios.post('/api/save', [{ key, val }]).then((resp) => {
- delete resp.data.usercfgs
- Object.assign(this.box, resp.data)
- })
- },
- // 使用应用会话
- useAppSession(sessionId) {
- const sessions = this.box.sessions
- const session = sessions.find((session) => session.id === sessionId)
- this.box.curSessions[session.appId] = sessionId
- const key = 'chavy_boxjs_cur_sessions'
- const val = JSON.stringify(this.box.curSessions)
- const curSessions = [{ key, val }]
- const datas = [...session.datas, ...curSessions]
- this.clearAppDatas()
- axios.post('/api/save', datas).then((resp) => {
- delete resp.data.usercfgs
- Object.assign(this.box, resp.data)
- })
- },
- // 复制应用数据为对象数据
- copyData(curdata) {
- const datas = curdata.datas
- let result = {}
- datas.forEach(({ key, val }) => {
- result[key] = val
- })
- this.copy(JSON.stringify(result))
- },
- // 保存应用数据
- clearAppDatas(key) {
- const datas = this.curapp.datas
- if (key) {
- const data = datas.find((data) => data.key === key)
- data.val = ''
- } else {
- datas.forEach((data) => (data.val = ''))
- }
- axios.post('/api/save', datas).then((resp) => {
- if (this.curapp.id === 'BoxSetting') {
- this.isSaveUserCfgs = false
- } else {
- delete resp.data.usercfgs
- }
- Object.assign(this.box, resp.data)
- })
- },
- // 导入应用数据
- impAppDatas() {
- const impval = this.ui.impAppDatasDialog.impval
- const impapp = JSON.parse(impval)
- const datas = impapp.datas || []
- const settings = impapp.settings || []
- settings.forEach((setting) => {
- const { id: key, val } = setting
- datas.push({ key, val })
- })
- axios
- .post('/api/save', datas)
- .then((resp) => {
- delete resp.data.usercfgs
- Object.assign(this.box, resp.data)
- })
- .finally(() => {
- this.ui.impAppDatasDialog.show = false
- this.ui.impAppDatasDialog.impval = ''
- })
- },
- // 添加应用订阅
- addAppSub(url, isRedirect) {
- const sub = { id: uuidv4(), url, enable: true }
- axios
- .post('/api/addAppSub', sub)
- .then((resp) => {
- this.isSaveUserCfgs = false
- Object.assign(this.box, resp.data)
- if (isRedirect) {
- this.switchView('sub')
- this.ui.snackbar.show = true
- this.ui.snackbar.msg = '一键订阅: 成功!'
- this.ui.snackbar.color = 'success'
- }
- this.openInstall(sub.id)
- })
- .finally(() => (this.addAppSubDialog = false))
- },
- // 重载应用订阅
- reloadAppSub(sub) {
- axios.post('/api/reloadAppSub', sub).then((resp) => {
- delete resp.data.usercfgs
- Object.assign(this.box, resp.data)
- })
- },
- // 加载完整的全局备份 (页面渲染时加载只是备份列表)
- loadGlobalBak(backupId) {
- return axios.get(`/query/baks/${backupId}`).then((resp) => {
- const backup = this.box.globalbaks.find((backup) => backup.id === backupId)
- backup.bak = resp.data
- })
- },
- // 删除全局备份
- delGlobalBak() {
- const { id } = this.curbak
- axios.post('/api/delGlobalBak', { id }).then((resp) => {
- this.back()
- delete resp.data.usercfgs
- Object.assign(this.box, resp.data)
- })
- },
- // 保存当前备份
- updateGlobalBak() {
- const { id, name } = this.curbak
- axios.post('/api/updateGlobalBak', { id, name }).then((resp) => {
- delete resp.data.usercfgs
- Object.assign(this.box, resp.data)
- })
- },
- // 导入全局备份
- impGlobalBak() {
- const impval = this.ui.impGlobalBakDialog.impval
- const bak = {
- id: uuidv4(),
- name: `${this.$t('profile.bakName')} ` + (this.box.globalbaks.length + 1),
- env: this.box.syscfgs.env,
- version: this.box.syscfgs.version,
- versionType: this.box.syscfgs.versionType,
- createTime: new Date(),
- bak: JSON.parse(impval)
- }
- bak.tags = [bak.env, bak.version, bak.versionType]
- axios
- .post('/api/impGlobalBak', bak)
- .then((resp) => {
- delete resp.data.usercfgs
- Object.assign(this.box, resp.data)
- })
- .finally(() => {
- this.ui.impGlobalBakDialog.impval = ''
- this.ui.impGlobalBakDialog.show = false
- })
- },
- // 保存备份
- saveGlobalBak() {
- const bak = {
- id: uuidv4(),
- name: `${this.$t('profile.bakName')} ` + (this.box.globalbaks.length + 1),
- env: this.box.syscfgs.env,
- version: this.box.syscfgs.version,
- versionType: this.box.syscfgs.versionType,
- createTime: new Date()
- }
- bak.tags = [bak.env, bak.version, bak.versionType]
- axios.post('/api/saveGlobalBak', bak).then((resp) => {
- delete resp.data.usercfgs
- Object.assign(this.box, resp.data)
- })
- },
- // 还原备份
- revertGlobalBak() {
- const { id } = this.curbak
- axios
- .post('/api/revertGlobalBak', { id })
- .then((resp) => {
- this.isSaveUserCfgs = false
- Object.assign(this.box, resp.data)
- })
- .finally(() => this.loadTheme())
- },
- // 获取仓库贡献者
- getContributors() {
- const url = 'https://api.github.com/repos/chavyleung/scripts/contributors'
- axios.get(url).then((resp) => {
- if (!resp) return
- resp.data.forEach((contributor) => {
- const { login: id, login, html_url: repo, avatar_url: icon } = contributor
- if ([29748519, 39037656, 9592236].includes(contributor.id)) {
- this.ui.collaborators.push({ id, login, repo, icon })
- } else {
- this.ui.contributors.push({ id, login, repo, icon })
- }
- })
- })
- },
- // 获取版本清单
- getVersions() {
- axios.get('/query/versions').then((resp) => {
- if (resp.data && resp.data.releases) {
- Object.assign(this.box, { versions: resp.data.releases })
- if (this.hasNewVersion) {
- this.ui.versionSheet.show = true
- }
- }
- })
- },
- // 查询数据
- queryData() {
- const key = this.ui.viewer.key
- this.ui.viewer.key = key ? key : 'boxjs_host'
- axios.get(`/query/data/${this.ui.viewer.key}`).then((resp) => {
- this.ui.viewer.val = resp.data.val
- this.box.usercfgs.viewkeys.unshift(this.ui.viewer.key)
- })
- },
- saveData() {
- const key = this.ui.viewer.key
- const val = this.ui.viewer.val
- if (key) {
- axios.post('/api/saveData/', { key, val }).then((resp) => {
- this.ui.viewer.val = resp.data.val
- })
- }
- },
- // 对比版本号
- compareVersion(v1, v2) {
- var _v1 = v1.split('.'),
- _v2 = v2.split('.'),
- _r = _v1[0] - _v2[0]
- return _r == 0 && v1 != v2 ? this.compareVersion(_v1.splice(1).join('.'), _v2.splice(1).join('.')) : _r
- },
- // 设置HTTP Backend
- setHttpBackend() {
- // 目前HTTP Backend不能修改端口号
- var regex = /^http:\/\/(.*):9999$/
- if (this.box.syscfgs.env === 'QuanX') {
- if (regex.test(window.location.origin)) {
- axios.defaults.baseURL = ''
- return
- }
- // 如果是Quantumult X环境并且配置了正确格式的HTTP Backend,将axios请求指向到HTTP Backend
- if (this.box.usercfgs.http_backend && regex.test(this.box.usercfgs.http_backend)) {
- axios.defaults.baseURL = this.box.usercfgs.http_backend
- this.ui.isCors = true
- } else {
- axios.defaults.baseURL = ''
- }
- }
- },
- calculateUpgradePrice(purchaseDate, licenseType) {
- const licensePrices = {
- '1 Device License': 34.99,
- '3 Devices License': 48.99,
- '5 Devices License': 69.99,
- }
- const upgradePrice = licensePrices[licenseType]
- if (!upgradePrice) {
- throw new Error(`Invalid license type: ${licenseType}`)
- }
- const discountEndDate = dayjs('2022-04-15')
- const freeDate = dayjs('2022-10-15')
- if (dayjs(purchaseDate).isBefore(discountEndDate)) {
- return licensePrices[licenseType]
- }
- const equivalentPurchaseDate = dayjs(purchaseDate)
- const diffDays = freeDate.diff(discountEndDate, 'day')
- const daysFromDiscountEndDate = equivalentPurchaseDate.diff(discountEndDate, 'day')
- if (daysFromDiscountEndDate >= diffDays) {
- // After free upgrade date
- return 0
- } else {
- const ratio = 1 - daysFromDiscountEndDate / diffDays
- const price = Math.ceil(ratio * upgradePrice * 100) / 100 - 0.01
- return price < 1.99 ? 1.99 : price
- }
- },
- calculateCost() {
- this.cost = this.calculateUpgradePrice(this.purchaseDate, this.licenseType)
- },
- }
- })
- </script>
- </body>
- </html>
|