From 052d669ffeed7ce763688054236d7c3172e2f0ba Mon Sep 17 00:00:00 2001 From: Freddie Date: Tue, 2 Jun 2020 02:28:11 +0100 Subject: [PATCH] [sdvx] v1.0 --- sdvx@asphyxia/README.md | 7 +- sdvx@asphyxia/handlers/features.ts | 6 +- sdvx@asphyxia/handlers/profiles.ts | 2 +- sdvx@asphyxia/handlers/webui.ts | 117 +++++++++++++++++++ sdvx@asphyxia/index.ts | 11 ++ sdvx@asphyxia/templates/load.pug | 6 +- sdvx@asphyxia/webui/automation_mixes.pug | 139 +++++++++++++++++++++++ sdvx@asphyxia/webui/css/profile_name.css | 3 - sdvx@asphyxia/webui/custom_page.pug | 29 ----- sdvx@asphyxia/webui/js/custom_page.js | 5 - sdvx@asphyxia/webui/profile_detail.pug | 45 ++++++++ sdvx@asphyxia/webui/profile_name.pug | 42 ------- 12 files changed, 325 insertions(+), 87 deletions(-) create mode 100644 sdvx@asphyxia/handlers/webui.ts create mode 100644 sdvx@asphyxia/webui/automation_mixes.pug delete mode 100644 sdvx@asphyxia/webui/css/profile_name.css delete mode 100644 sdvx@asphyxia/webui/custom_page.pug delete mode 100644 sdvx@asphyxia/webui/js/custom_page.js create mode 100644 sdvx@asphyxia/webui/profile_detail.pug delete mode 100644 sdvx@asphyxia/webui/profile_name.pug diff --git a/sdvx@asphyxia/README.md b/sdvx@asphyxia/README.md index 59c0d48..4c63057 100644 --- a/sdvx@asphyxia/README.md +++ b/sdvx@asphyxia/README.md @@ -1,3 +1,8 @@ # SOUND VOLTEX -(WORK IN PROGRESS) +Plugin Version: **v1.0** + +Supported Versions: + +- HEAVENLY HAVEN +- VIVID WAVE diff --git a/sdvx@asphyxia/handlers/features.ts b/sdvx@asphyxia/handlers/features.ts index 901a9ea..371f8e2 100644 --- a/sdvx@asphyxia/handlers/features.ts +++ b/sdvx@asphyxia/handlers/features.ts @@ -69,11 +69,11 @@ export const saveMix: EPR = async (info, data, send) => { const id = await GetCounter('mix'); let code = _.padStart(_.random(0, 999999999999).toString(), 12, '0'); - while (await DB.FindOne(null, { code })) { + while (await DB.FindOne({ collection: 'mix', code })) { code = _.padStart(_.random(0, 999999999999).toString(), 12, '0'); } - const doc = await DB.Insert(refid, { + const doc = await DB.Insert({ collection: 'mix', id, code, @@ -103,7 +103,7 @@ export const saveMix: EPR = async (info, data, send) => { export const loadMix: EPR = async (info, data, send) => { const code = $(data).str('mix_code'); - const mix = await DB.FindOne(null, { collection: 'mix', code }); + const mix = await DB.FindOne({ collection: 'mix', code }); if (!mix) { send.object({ result: K.ITEM('s32', 1) }); return; diff --git a/sdvx@asphyxia/handlers/profiles.ts b/sdvx@asphyxia/handlers/profiles.ts index bbcff62..0951e73 100644 --- a/sdvx@asphyxia/handlers/profiles.ts +++ b/sdvx@asphyxia/handlers/profiles.ts @@ -18,7 +18,7 @@ async function getAutomationMixes(params: Param[]) { const mixids = params .filter(p => p.id == 3) .reduce((res, p) => _.union(res, p.param), []); - return await DB.Find(null, { collection: 'mix', id: { $in: mixids } }); + return await DB.Find({ collection: 'mix', id: { $in: mixids } }); } function unlockNavigators(items: Partial[]) { diff --git a/sdvx@asphyxia/handlers/webui.ts b/sdvx@asphyxia/handlers/webui.ts new file mode 100644 index 0000000..a7008b4 --- /dev/null +++ b/sdvx@asphyxia/handlers/webui.ts @@ -0,0 +1,117 @@ +import { Profile } from '../models/profile'; +import { Mix } from '../models/mix'; +import { GetCounter } from '../utils'; + +export const updateProfile = async (data: { + refid: string; + name?: string; + appeal?: string; + akaname?: string; +}) => { + if (data.refid == null) return; + + const update: Update['$set'] = {}; + + if (data.name && data.name.length > 0) { + const validName = data.name + .toUpperCase() + .replace(/[^ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!?#$&*\-\.\ ]/g, '') + .slice(0, 8); + if (validName.length > 0) update.name = validName; + } + + if (data.appeal && data.appeal.length > 0) { + const validAppeal = parseInt(data.appeal); + if (!_.isNaN(validAppeal)) update.appeal = validAppeal; + } + + if (data.akaname && data.akaname.length > 0) { + const validAka = parseInt(data.akaname); + if (!_.isNaN(validAka)) update.akaname = validAka; + } + + await DB.Update( + data.refid, + { collection: 'profile' }, + { $set: update } + ); +}; + +export const updateMix = async (data: { + code: string; + name?: string; + creator?: string; +}) => { + const update: Update['$set'] = {}; + + if (data.name && data.name.length > 0) { + if (data.name.length > 0) update.name = data.name; + } + + if (data.creator && data.creator.length > 0) { + // const validCreator = data.creator + // .toUpperCase() + // .replace(/[^ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!?#$&*\-\.\ ]/g, '') + // .slice(0, 8); + if (data.creator.length > 0) update.creator = data.creator; + } + + await DB.Update( + { collection: 'mix', code: data.code }, + { $set: update } + ); +}; + +export const importMix = async (data: { json: string }) => { + if (data.json.startsWith('`')) { + data.json = data.json.slice(1); + } + + if (data.json.endsWith('`')) { + data.json = data.json.slice(0, data.json.length - 1); + } + + const mix: any[] = JSON.parse(data.json); + + let code = mix[0]; + while (await DB.FindOne({ collection: 'mix', code })) { + code = _.padStart(_.random(0, 999999999999).toString(), 12, '0'); + } + + const id = await GetCounter('mix'); + const musics = mix.slice(9); + + if (musics.length % 2 !== 0) return; + + const mdata = []; + + for (let i = 0; i < musics.length; i += 2) { + mdata.push({ + grade: musics[i + 1], + id: musics[i], + }); + } + + await DB.Insert({ + collection: 'mix', + code, + id, + name: mix[1], + creator: mix[2], + param: `{ "dbVer" : "${ + mix[3] + }", "gene" : { "params" : "{ \\"minorVer\\" : \\"${ + mix[4] + }\\", \\"seed\\" : ${mix[5]} }", "ver" : "${ + mix[6] + }" }, "musics" : ${JSON.stringify(mdata)}, "voxdj" : { "params" : "${ + mix[7] + }", "ver" : "${mix[8]}" } }`, + jacket: 0, + tag: 1, + }); +}; + +export const deleteMix = async (data: { code: string }) => { + await DB.Remove({ collection: 'mix', code: data.code }); +}; diff --git a/sdvx@asphyxia/index.ts b/sdvx@asphyxia/index.ts index ec1a84d..31a5dd1 100644 --- a/sdvx@asphyxia/index.ts +++ b/sdvx@asphyxia/index.ts @@ -1,5 +1,11 @@ import { common } from './handlers/common'; import { hiscore, rival, saveMix, loadMix } from './handlers/features'; +import { + updateProfile, + updateMix, + importMix, + deleteMix, +} from './handlers/webui'; import { load, create, @@ -16,6 +22,11 @@ export function register() { R.Config('unlock_all_songs', { type: 'boolean', default: false }); R.Config('unlock_all_navigators', { type: 'boolean', default: false }); + R.WebUIEvent('updateProfile', updateProfile); + R.WebUIEvent('updateMix', updateMix); + R.WebUIEvent('importMix', importMix); + R.WebUIEvent('deleteMix', deleteMix); + const MultiRoute = (method: string, handler: EPR | boolean) => { // Helper for register multiple versions. R.Route(`game.sv4_${method}`, handler); diff --git a/sdvx@asphyxia/templates/load.pug b/sdvx@asphyxia/templates/load.pug index 5de07c3..d6e9ea8 100644 --- a/sdvx@asphyxia/templates/load.pug +++ b/sdvx@asphyxia/templates/load.pug @@ -77,9 +77,9 @@ game //- Akaname each id in [0, 1, 2] info - type(__type="u8") 6 - id(__type="u32") #{id} - param(__type="u32" __count="1") #{akaname} + type(__type="s32") 6 + id(__type="s32") #{id} + param(__type="s32" __count="1") #{akaname} play_count(__type="u32") 1001 day_count(__type="u32") 301 diff --git a/sdvx@asphyxia/webui/automation_mixes.pug b/sdvx@asphyxia/webui/automation_mixes.pug new file mode 100644 index 0000000..1c77bd7 --- /dev/null +++ b/sdvx@asphyxia/webui/automation_mixes.pug @@ -0,0 +1,139 @@ +//DATA// + mixes: DB.Find({ collection: 'mix' }) + +- + const mixes_reversed = _.reverse(mixes) + const DIFF = ['NOV', 'ADV', 'EXH', 'INF', 'MXM'] + const editing = query.edit + const sharing = query.share + function formatCode(code) { + return `${code.slice(0,4)}-${code.slice(4,8)}-${code.slice(8)}` + } + + function shortenParam(param) { + const data = JSON.parse(param); + const gParam = JSON.parse(data.gene.params); + return [ + data.dbVer, + gParam.minorVer, + gParam.seed, + data.gene.ver, + data.voxdj.params, + data.voxdj.ver, + ].concat(...data.musics.map(m=>[m.id, m.grade])) + } + + +//---------------- + Editing +//---------------- +if editing + - const mix = mixes_reversed.filter(m=>m.code == editing)[0] + if mix + .card + .card-header + p.card-header-title + span.icon + i.mdi.mdi-music + | #{formatCode(mix.code)} + .card-content + form(method="post" action="/emit/updateMix") + input.input(type="hidden" name="code", value=mix.code readonly) + .field + label.label Mix Name + .control + input.input(type="text" name="name", placeholder=mix.name) + .field + label.label By + .control + input.input(type="text" name="creator", placeholder=mix.creator) + .field.buttons + a.button(href="?") + span.icon + i.mdi.mdi-chevron-left + span Back + button.button.is-primary(type="submit") + span.icon + i.mdi.mdi-check + span Submit + else + p Mix not found + a.button(href="?") + span.icon + i.mdi.mdi-chevron-left + span Back + +//---------------- + Sharing +//---------------- +else if sharing + - + const mix = mixes_reversed.filter(m=>m.code == sharing)[0] + if mix + .card + .card-header + p.card-header-title + span.icon + i.mdi.mdi-music + | #{formatCode(mix.code)} + .card-content + .notification.is-info.is-light Double click and copy the code + .field + input.input.is-small.is-size-7(type='text' onClick="this.setSelectionRange(0, this.value.length)" value=`\`${JSON.stringify([mix.code,mix.name,mix.creator].concat(shortenParam(mix.param)))}\`` readonly) + .field + a.button(href="?") + span.icon + i.mdi.mdi-chevron-left + span Back + else + p Mix not found + a.button(href="?") + span.icon + i.mdi.mdi-chevron-left + span Back + +//---------------- + Main Page +//---------------- +else + div.content + .buttons + form(method="post" action="/emit/importMix") + .field.has-addons + .control.is-expanded + input.input(placeholder="Paste code here" name='json') + .control + button.button.is-info(type="submit") + span.icon + i.mdi.mdi-import + span Import Mix + .columns.is-multiline + each mix in mixes_reversed + .column.is-half + .card + .card-header + p.card-header-title + span.icon + i.mdi.mdi-music + | #{formatCode(mix.code)} + .card-content + p #{mix.name} + p by #{mix.creator} + - let param = JSON.parse(mix.param) + - let musics = param.musics.map(m=>`${m.id}(${DIFF[m.grade]})`).join('+') + p #{param.musics.length}-SONGS + span.has-tooltip-right(data-tooltip=musics) MIX + .buttons + a.button.is-primary(href=`?share=${mix.code}`) + span.icon + i.mdi.mdi-export + span Share + a.button.is-info.is-outlined(href=`?edit=${mix.code}`) + span.icon + i.mdi.mdi-pencil + span Edit + button.button.is-danger.is-outlined(onClick=`emit('deleteMix', {code: '${mix.code}'}).then(()=>location.reload(true))`) + span.icon + i.mdi.mdi-trash-can + span Delete + diff --git a/sdvx@asphyxia/webui/css/profile_name.css b/sdvx@asphyxia/webui/css/profile_name.css deleted file mode 100644 index bb2ac1c..0000000 --- a/sdvx@asphyxia/webui/css/profile_name.css +++ /dev/null @@ -1,3 +0,0 @@ -#change-name-button { - background-color: rgb(245, 147, 0); -} diff --git a/sdvx@asphyxia/webui/custom_page.pug b/sdvx@asphyxia/webui/custom_page.pug deleted file mode 100644 index baa5d1f..0000000 --- a/sdvx@asphyxia/webui/custom_page.pug +++ /dev/null @@ -1,29 +0,0 @@ -//- - Custom web component for plugin page - The following frontend components are already avaliable: - * Bulma [https://bulma.io/] - * MaterialDesignIcons [https://materialdesignicons.com/] - * jQuery [https://jquery.com/] - * axios [https://github.com/axios/axios] - * GeoPattern [https://github.com/btmills/geopattern] - * jdenticon [https://jdenticon.com/] - You can include your own js, css or images as well. - - To request data from database, use //DATA// to indicate a comment block for capture and follow: - fieldName: expression - - Then fieldName will be avaliable for the template engine when rendering. - -//DATA// - data: DB.FindOne({ clicked: { $exists: true } }) - -.content - h3 Custom Page - p Clicked: #{data.clicked} - p - button.button.is-primary#plugin-click - | Send "click" event and refresh - -//- You can include custom javascripts and send data back to plugin using emit() - see profile_name.pug for sending data using form -script(src="static/js/custom_page.js") \ No newline at end of file diff --git a/sdvx@asphyxia/webui/js/custom_page.js b/sdvx@asphyxia/webui/js/custom_page.js deleted file mode 100644 index 86cc11a..0000000 --- a/sdvx@asphyxia/webui/js/custom_page.js +++ /dev/null @@ -1,5 +0,0 @@ -$('#plugin-click').on('click', () => { - emit('click', {}).then(() => { - location.reload(); - }); -}); diff --git a/sdvx@asphyxia/webui/profile_detail.pug b/sdvx@asphyxia/webui/profile_detail.pug new file mode 100644 index 0000000..9fb3786 --- /dev/null +++ b/sdvx@asphyxia/webui/profile_detail.pug @@ -0,0 +1,45 @@ +//DATA// + profile: DB.FindOne(refid, { collection: 'profile' }) + +- + const padded = _.padStart(profile.id.toString(), 8); + const sdvxid = `${padded.slice(0, 4)}-${padded.slice(4)}`; + +div + .card + .card-header + p.card-header-title + span.icon + i.mdi.mdi-account-edit + | Detail Change + .card-content + form(method="post" action="/emit/updateProfile") + .field + label.label ID + .control + input.input(type="text" name="refid", value=refid readonly) + .field + label.label SDVX ID + .control + input.input(type="text" name="sdvxid", value=sdvxid readonly) + .field + label.label Name + .control + input.input(type="text" name="name", placeholder=profile.name) + .field + label.label AppealCardID + span.icon.has-tooltip-right(data-tooltip="Check \"data/others/appeal_card.xml\"") + i.mdi.mdi-help-circle + .control + input.input(type="number" step=1 name="appeal", placeholder=profile.appeal) + .field + label.label AkanameID + span.icon.has-tooltip-right(data-tooltip="Check \"data/others/akaname_parts.xml\"") + i.mdi.mdi-help-circle + .control + input.input(type="number" step=1 name="akaname", placeholder=profile.akaname) + .field + button.button.is-primary(type="submit") + span.icon + i.mdi.mdi-check + span Submit diff --git a/sdvx@asphyxia/webui/profile_name.pug b/sdvx@asphyxia/webui/profile_name.pug deleted file mode 100644 index 7fd16d4..0000000 --- a/sdvx@asphyxia/webui/profile_name.pug +++ /dev/null @@ -1,42 +0,0 @@ -//- - Custom web component for individual user profile - The following frontend components are already avaliable: - * Bulma [https://bulma.io/] - * MaterialDesignIcons [https://materialdesignicons.com/] - * jQuery [https://jquery.com/] - * axios [https://github.com/axios/axios] - * GeoPattern [https://github.com/btmills/geopattern] - * jdenticon [https://jdenticon.com/] - You can include your own js/css as well - - To request data from database, use //DATA// to indicate a comment block for capture and follow: - fieldName: expression - - Then fieldName will be avaliable for the template engine when rendering. - - WebUI file with prefix "profile_" is different from other pug file: - * This page have a "refid" local variable, you can request data with it or use it during render. - * User can access these page by editing a profile. - -//DATA// - profile: DB.FindOne(refid, { login_count: { $exists: true } }) - -//- - You can include your own stylesheet -link(rel="stylesheet" href="static/css/profile_name.css") - -div - h3 Hi #{profile.name} - - section.section - //- - You can send data back to plugin using post method in form - see custom_page.pug for sending data using emit() function - form(method="post" action="/emit/change") - input(type="hidden" name="refid" value=refid) - .field - label.label Change Name - .field - input.input(type="text" name="name", value=profile.name) - .field - button.button.is-primary#change-name-button(type="submit") Submit