Skip to content

Commit

Permalink
Merge pull request #1525 from cwtickle/develop
Browse files Browse the repository at this point in the history
[ver33.1.0] 結果画面のSNS投稿用画像コピー機能を実装 他
  • Loading branch information
cwtickle committed Aug 3, 2023
2 parents b865201 + ac7fffa commit 51943db
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 49 deletions.
4 changes: 2 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ v31の対応終了時期はv34リリース開始時を予定しています。

| Version | Supported | Latest Version | Logs | First Release | End of Support |
| ------- | ------------------ |----------------|------|---------------|----------------|
| v33 | :heavy_check_mark: |[v33.0.0](https://github.com/cwtickle/danoniplus/releases/tag/v33.0.0) |[:memo:](https://github.com/cwtickle/danoniplus/wiki/Changelog-latest)|2023-07-29|(At Release v36)|
| v32 | :heavy_check_mark: |[v32.7.0](https://github.com/cwtickle/danoniplus/releases/tag/v32.7.0) |[:memo:](https://github.com/cwtickle/danoniplus/wiki/Changelog-latest)|2023-05-07|(At Release v35)|
| v33 | :heavy_check_mark: |[v33.1.0](https://github.com/cwtickle/danoniplus/releases/tag/v33.1.0) |[:memo:](https://github.com/cwtickle/danoniplus/wiki/Changelog-latest)|2023-07-29|(At Release v36)|
| v32 | :heavy_check_mark: |[v32.7.0](https://github.com/cwtickle/danoniplus/releases/tag/v32.7.0) |[:memo:](https://github.com/cwtickle/danoniplus/wiki/Changelog-v32)|2023-05-07|(At Release v35)|
| v31 | :warning: |[v31.7.3](https://github.com/cwtickle/danoniplus/releases/tag/v31.7.3) |[:memo:](https://github.com/cwtickle/danoniplus/wiki/Changelog-v31)|2023-03-20|(At Release v34)|
| v30 | :x: |[v30.6.3 (final)](https://github.com/cwtickle/danoniplus/releases/tag/v30.6.3) |[:memo:](https://github.com/cwtickle/danoniplus/wiki/Changelog-v30)|2023-02-10|2023-07-29|
| v29 :anchor: | :heavy_check_mark: |[v29.4.6](https://github.com/cwtickle/danoniplus/releases/tag/v29.4.6) |[:memo:](https://github.com/cwtickle/danoniplus/wiki/Changelog-v29)|2022-11-05|(At Release v38)|
Expand Down
217 changes: 175 additions & 42 deletions js/danoni_main.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
*
* Source by tickle
* Created : 2018/10/08
* Revised : 2023/07/29
* Revised : 2023/08/03
*
* https://github.com/cwtickle/danoniplus
*/
const g_version = `Ver 33.0.0`;
const g_revisedDate = `2023/07/29`;
const g_version = `Ver 33.1.0`;
const g_revisedDate = `2023/08/03`;

// カスタム用バージョン (danoni_custom.js 等で指定可)
let g_localVersion = ``;
Expand Down Expand Up @@ -50,6 +50,8 @@ const g_rootPath = current().match(/(^.*\/)/)[0];
const g_workPath = new URL(location.href).href.match(/(^.*\/)/)[0];
const g_remoteFlg = g_rootPath.match(`^https://cwtickle.github.io/danoniplus/`) !== null;
const g_randTime = Date.now();
const g_isFile = location.href.match(/^file/);
const g_isLocal = location.href.match(/^file/) || location.href.indexOf(`localhost`) !== -1;

window.onload = async () => {
g_loadObj.main = true;
Expand Down Expand Up @@ -726,14 +728,17 @@ const loadScript2 = (_url, _requiredFlg = true, _charset = `UTF-8`) => {
* デフォルトは danoni_skin_default.css を読み込む
* @param {url} _href
*/
const importCssFile2 = _href => {
const importCssFile2 = (_href, { crossOrigin = `anonymous` } = {}) => {
const baseUrl = _href.split(`?`)[0];
g_loadObj[baseUrl] = false;

return new Promise(resolve => {
const link = document.createElement(`link`);
link.rel = `stylesheet`;
link.href = _href;
if (!g_isFile) {
link.crossOrigin = crossOrigin;
}
link.onload = _ => {
g_loadObj[baseUrl] = true;
resolve(link);
Expand Down Expand Up @@ -1485,22 +1490,26 @@ const getCssCustomProperties = _ => {
}
}
} catch (error) {
// FirefoxではcomputedStyleMapが使えないため、
// CSSの全スタイルシート定義から :root がセレクタのルールを抽出し、カスタムプロパティを抽出
const sheets = document.styleSheets;
for (const sheet of sheets) {
if (!g_isFile && sheet.cssRules) {
for (const rule of sheet.cssRules) {
if (rule.selectorText === ':root') {
for (let i = 0; i < rule.style.length; i++) {
const propertyName = rule.style.item(i);
if (/^--/.test(propertyName)) {
g_cssBkProperties[propertyName] = rule.style.getPropertyValue(propertyName);

try {
// FirefoxではcomputedStyleMapが使えないため、
// CSSの全スタイルシート定義から :root がセレクタのルールを抽出し、カスタムプロパティを抽出
const sheets = document.styleSheets;
Array.from(sheets).filter(sheet => !g_isFile && sheet.href !== null &&
sheet.href.includes(`danoni_skin_`) && sheet.cssRules).forEach(sheet => {
for (const rule of sheet.cssRules) {
if (rule.selectorText === ':root') {
for (let i = 0; i < rule.style.length; i++) {
const propertyName = rule.style.item(i);
if (/^--/.test(propertyName)) {
g_cssBkProperties[propertyName] = rule.style.getPropertyValue(propertyName);
}
}
}
}
}
}
});
} catch (error) {
// 上記でもNGの場合は何もしない
}
}
}
Expand Down Expand Up @@ -10603,12 +10612,14 @@ const resultInit = _ => {
const withOptions = (_flg, _defaultSet, _displayText = _flg) =>
(_flg !== _defaultSet ? getStgDetailName(_displayText) : ``);

let difData = [
const difDatas = [
`${getKeyName(g_headerObj.keyLabels[g_stateObj.scoreId])}${transKeyData} ${getStgDetailName('key')} / ${g_headerObj.difLabels[g_stateObj.scoreId]}`,
`${withOptions(g_autoPlaysBase.includes(g_stateObj.autoPlay), true, `-${getStgDetailName(g_stateObj.autoPlay)}${getStgDetailName('less')}`)}`,
`${withOptions(g_headerObj.makerView, false, `(${g_headerObj.creatorNames[g_stateObj.scoreId]})`)}`,
`${withOptions(g_stateObj.shuffle, C_FLG_OFF, `[${getStgDetailName(g_stateObj.shuffle)}]`)}`
].filter(value => value !== ``).join(` `);
];
let difData = difDatas.filter(value => value !== ``).join(` `);
const difDataForImage = difDatas.filter((value, j) => value !== `` && j !== 2).join(` `);

let playStyleData = [
`${g_stateObj.speed}${g_lblNameObj.multi}`,
Expand Down Expand Up @@ -10661,16 +10672,16 @@ const resultInit = _ => {

// キャラクタ、スコア描画のID共通部、色CSS名、スコア変数名
const jdgScoreObj = {
ii: { pos: 0, id: `Ii`, color: `ii`, label: g_lblNameObj.j_ii, },
shakin: { pos: 1, id: `Shakin`, color: `shakin`, label: g_lblNameObj.j_shakin, },
matari: { pos: 2, id: `Matari`, color: `matari`, label: g_lblNameObj.j_matari, },
shobon: { pos: 3, id: `Shobon`, color: `shobon`, label: g_lblNameObj.j_shobon, },
uwan: { pos: 4, id: `Uwan`, color: `uwan`, label: g_lblNameObj.j_uwan, },
kita: { pos: 5, id: `Kita`, color: `kita`, label: g_lblNameObj.j_kita, },
iknai: { pos: 6, id: `Iknai`, color: `iknai`, label: g_lblNameObj.j_iknai, },
maxCombo: { pos: 7, id: `MCombo`, color: `combo`, label: g_lblNameObj.j_maxCombo, },
fmaxCombo: { pos: 8, id: `FCombo`, color: `combo`, label: g_lblNameObj.j_fmaxCombo, },
score: { pos: 10, id: `Score`, color: `score`, label: g_lblNameObj.j_score, },
ii: { pos: 0, id: `Ii`, color: `ii`, label: g_lblNameObj.j_ii, dfColor: `#66ffff`, },
shakin: { pos: 1, id: `Shakin`, color: `shakin`, label: g_lblNameObj.j_shakin, dfColor: `#99ff99`, },
matari: { pos: 2, id: `Matari`, color: `matari`, label: g_lblNameObj.j_matari, dfColor: `#ff9966`, },
shobon: { pos: 3, id: `Shobon`, color: `shobon`, label: g_lblNameObj.j_shobon, dfColor: `#ccccff`, },
uwan: { pos: 4, id: `Uwan`, color: `uwan`, label: g_lblNameObj.j_uwan, dfColor: `#ff9999`, },
kita: { pos: 5, id: `Kita`, color: `kita`, label: g_lblNameObj.j_kita, dfColor: `#ffff99`, },
iknai: { pos: 6, id: `Iknai`, color: `iknai`, label: g_lblNameObj.j_iknai, dfColor: `#99ff66`, },
maxCombo: { pos: 7, id: `MCombo`, color: `combo`, label: g_lblNameObj.j_maxCombo, dfColor: `#ffffff`, },
fmaxCombo: { pos: 8, id: `FCombo`, color: `combo`, label: g_lblNameObj.j_fmaxCombo, dfColor: `#ffffff`, },
score: { pos: 10, id: `Score`, color: `score`, label: g_lblNameObj.j_score, dfColor: `#ffffff`, },
};

// キャラクタ、スコア描画
Expand Down Expand Up @@ -10823,6 +10834,7 @@ const resultInit = _ => {
}
const twiturl = new URL(g_localStorageUrl);
twiturl.searchParams.append(`scoreId`, g_stateObj.scoreId);
const baseTwitUrl = g_isLocal ? `` : `${twiturl.toString()}`.replace(/[\t\n]/g, ``);

const tweetExcessive = (g_stateObj.excessive === C_FLG_ON) ? `(+${g_resultObj.excessive})` : ``;

Expand All @@ -10844,14 +10856,119 @@ const resultInit = _ => {
[`[arrowJdg]`, `${g_resultObj.ii}-${g_resultObj.shakin}-${g_resultObj.matari}-${g_resultObj.shobon}-${g_resultObj.uwan}${tweetExcessive}`],
[`[frzJdg]`, tweetFrzJdg],
[`[maxCombo]`, tweetMaxCombo],
[`[url]`, g_isLocal ? `` : `${twiturl.toString()}`.replace(/[\t\n]/g, ``)]
[`[url]`, baseTwitUrl]
]);
if (g_presetObj.resultVals !== undefined) {
Object.keys(g_presetObj.resultVals).forEach(key =>
tweetResultTmp = tweetResultTmp.split(`[${key}]`).join(g_resultObj[g_presetObj.resultVals[key]]));
}
const resultText = `${unEscapeHtml(tweetResultTmp)}`;
const tweetResult = `https://twitter.com/intent/tweet?text=${encodeURIComponent(resultText)}`;
const currentDateTime = new Date().toLocaleString();

/**
* リザルト画像をCanvasで作成しクリップボードへコピー
* @param {string} _msg
*/
const copyResultImageData = _msg => {
const tmpDiv = createEmptySprite(divRoot, `tmpDiv`, { x: 0, y: 0, w: g_sWidth, h: g_sHeight });
tmpDiv.style.background = `#000000cc`;
const canvas = document.createElement(`canvas`);
const artistName = g_headerObj.artistNames[g_headerObj.musicNos[g_stateObj.scoreId]] || g_headerObj.artistName;

canvas.id = `resultImage`;
canvas.width = 400;
canvas.height = 410;
canvas.style.left = `${(g_sWidth - canvas.width) / 2}px`;
canvas.style.top = `20px`;
canvas.style.position = `absolute`;

const context = canvas.getContext(`2d`);
const drawText = (_text, { x = 30, dy = 0, hy, siz = 15, color = `#cccccc`, align = C_ALIGN_LEFT, font } = {}) => {
context.font = `${siz}px ${getBasicFont(font)}`;
context.fillStyle = color;
context.textAlign = align;
context.fillText(_text, x, 35 + hy * 18 + dy);
};
const grd = context.createLinearGradient(0, 0, 0, canvas.height);
grd.addColorStop(0, `#000000`);
grd.addColorStop(1, `#222222`);
context.fillStyle = grd;
context.fillRect(0, 0, g_sWidth, g_sHeight);

drawText(`R`, { dy: -5, hy: 0, siz: 40, color: `#9999ff` });
drawText(`ESULT`, { x: 57, dy: -5, hy: 0, siz: 25 });
drawText(`${g_lblNameObj.dancing}${g_lblNameObj.star}${g_lblNameObj.onigiri}`,
{ x: 280, dy: -15, hy: 0, siz: 20, color: `#999999`, align: C_ALIGN_CENTER });
drawText(mTitleForView[0], { hy: 1 });
drawText(mTitleForView[1], { hy: 2 });
drawText(`📝 ${g_headerObj.tuning} / 🎵 ${artistName}`, { hy: mTitleForView[1] !== `` ? 3 : 2, siz: 12 });
drawText(difDataForImage, { hy: 4 });
drawText(playStyleData, { hy: 5 });

Object.keys(jdgScoreObj).forEach(score => {
drawText(g_lblNameObj[`j_${score}`], { hy: 7 + jdgScoreObj[score].pos, color: jdgScoreObj[score].dfColor });
drawText(g_resultObj[score], { x: 200, hy: 7 + jdgScoreObj[score].pos, align: C_ALIGN_RIGHT });
});

if (highscoreCondition) {
drawText(`(${highscoreDfObj.score >= 0 ? '+' : '-'} ${Math.abs(highscoreDfObj.score)})`,
{ x: 206, hy: 18, color: highscoreDfObj.score > 0 ? `#ffff99` : `#cccccc`, align: C_ALIGN_RIGHT });
}

if (g_stateObj.autoAll === C_FLG_OFF) {
drawText(g_lblNameObj.j_fast, { x: 240, hy: 7, color: `#ff9966` });
drawText(g_resultObj.fast, { x: 360, hy: 7, align: C_ALIGN_RIGHT });
drawText(g_lblNameObj.j_slow, { x: 240, hy: 8, color: `#ccccff` });
drawText(g_resultObj.slow, { x: 360, hy: 8, align: C_ALIGN_RIGHT });
if (estimatedAdj !== ``) {
drawText(g_lblNameObj.j_adj, { x: 240, hy: 9, color: `#99ff99` });
drawText(getDiffFrame(estimatedAdj), { x: 360, hy: 9, align: C_ALIGN_RIGHT });
}
if (g_stateObj.excessive === C_FLG_ON) {
drawText(g_lblNameObj.j_excessive, { x: 240, hy: 10, color: `#ffff99` });
drawText(g_resultObj.excessive, { x: 360, hy: 10, align: C_ALIGN_RIGHT });
}
}
drawText(rankMark, { x: 240, hy: 18, siz: 50, color: rankColor, font: `"Bookman Old Style"` });
drawText(baseTwitUrl, { hy: 19, siz: 8 });
drawText(currentDateTime, { hy: 20 });

tmpDiv.appendChild(canvas);

try {
if (ClipboardItem === undefined) {
throw new Error(`error`);
}
// Canvas の内容を PNG 画像として取得
canvas.toBlob(async blob => {
await navigator.clipboard.write([
new ClipboardItem({
'image/png': blob
})
]);
});
tmpDiv.removeChild(canvas);
divRoot.removeChild(tmpDiv);
makeInfoWindow(_msg, `leftToRightFade`);

} catch (err) {
// 画像をクリップボードへコピーできないときは代替で画像保存可能な画面を表示
if (document.getElementById(`tmpClose`) === null) {
divRoot.oncontextmenu = _ => true;
makeLinkButton(tmpDiv, `Tmp`);
tmpDiv.appendChild(createCss2Button(`tmpClose`, g_lblNameObj.b_close, _ => true,
Object.assign(g_lblPosObj.btnRsCopyClose, {
resetFunc: _ => {
tmpDiv.removeChild(canvas);
divRoot.removeChild(tmpDiv);
divRoot.oncontextmenu = _ => false;
},
}), g_cssObj.button_Back));
tmpDiv.appendChild(createDescDiv(`resultImageDesc`, g_lblNameObj.resultImageDesc));
}
}
};

/** 音源、ループ処理の停止 */
const resetCommonBtn = (_id, _name, _posObj, _func, _cssClass) =>
Expand All @@ -10863,6 +10980,25 @@ const resultInit = _ => {
clearTimeout(g_timeoutEvtResultId);
}, Object.assign(_posObj, { resetFunc: _func }), _cssClass);

/**
* 外部リンクボタンを作成
* @param {object} _div
* @param {string} _param
*/
const makeLinkButton = (_div = divRoot, _param = ``) => {
multiAppend(_div,
// リザルトデータをTwitterへ転送
createCss2Button(`btnTweet${_param}`, g_lblNameObj.b_tweet, _ => true, Object.assign(g_lblPosObj.btnRsTweet, {
resetFunc: _ => openLink(tweetResult),
}), g_cssObj.button_Tweet),

// Gitterへのリンク
createCss2Button(`btnGitter${_param}`, g_lblNameObj.b_gitter, _ => true, Object.assign(g_lblPosObj.btnRsGitter, {
resetFunc: _ => openLink(`https://app.gitter.im/#/room/#danonicw_freeboard:gitter.im`),
}), g_cssObj.button_Default),
);
}

// ボタン描画
multiAppend(divRoot,

Expand All @@ -10872,19 +11008,16 @@ const resultInit = _ => {
// リザルトデータをクリップボードへコピー
createCss2Button(`btnCopy`, g_lblNameObj.b_copy, _ => copyTextToClipboard(resultText, g_msgInfoObj.I_0001),
g_lblPosObj.btnRsCopy, g_cssObj.button_Setting),

// リザルトデータをTwitterへ転送
createCss2Button(`btnTweet`, g_lblNameObj.b_tweet, _ => true, Object.assign(g_lblPosObj.btnRsTweet, {
resetFunc: _ => openLink(tweetResult),
}), g_cssObj.button_Tweet),

// Gitterへのリンク
createCss2Button(`btnGitter`, g_lblNameObj.b_gitter, _ => true, Object.assign(g_lblPosObj.btnRsGitter, {
resetFunc: _ => openLink(`https://app.gitter.im/#/room/#danonicw_freeboard:gitter.im`),
}), g_cssObj.button_Default),

);
makeLinkButton();
multiAppend(divRoot,
// リトライ
resetCommonBtn(`btnRetry`, g_lblNameObj.b_retry, g_lblPosObj.btnRsRetry, loadMusic, g_cssObj.button_Reset),

createCss2Button(`btnCopyImage`, `📷`, _ => true,
Object.assign(g_lblPosObj.btnRsCopyImage, {
resetFunc: _ => copyResultImageData(g_msgInfoObj.I_0001),
}), g_cssObj.button_Default_NoColor),
);

// マスクスプライトを作成
Expand Down Expand Up @@ -10940,7 +11073,7 @@ const resultInit = _ => {
flowResultTimeline();

// キー操作イベント(デフォルト)
setShortcutEvent(g_currentPage);
setShortcutEvent(g_currentPage, _ => true, { dfEvtFlg: true });
document.oncontextmenu = _ => true;

g_skinJsObj.result.forEach(func => func());
Expand Down
18 changes: 14 additions & 4 deletions js/lib/danoni_constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*
* Source by tickle
* Created : 2019/11/19
* Revised : 2023/07/29 (v33.0.0)
* Revised : 2023/08/03 (v33.1.0)
*
* https://github.com/cwtickle/danoniplus
*/
Expand Down Expand Up @@ -153,9 +153,6 @@ const g_userAgent = window.navigator.userAgent.toLowerCase(); // msie, edge, chr
const g_isIos = listMatching(g_userAgent, [`iphone`, `ipad`, `ipod`]);
const g_isMac = listMatching(g_userAgent, [`iphone`, `ipad`, `ipod`, `mac os`]);

const g_isFile = location.href.match(/^file/);
const g_isLocal = location.href.match(/^file/) || location.href.indexOf(`localhost`) !== -1;

// 変数型
const C_TYP_BOOLEAN = `boolean`;
const C_TYP_NUMBER = `number`;
Expand Down Expand Up @@ -431,6 +428,15 @@ const updateWindowSiz = _ => {
btnRsRetry: {
x: g_sWidth / 4 * 3, w: g_sWidth / 4, h: g_limitObj.btnHeight * 5 / 4, animationName: `smallToNormalY`,
},
btnRsCopyImage: {
x: g_sWidth - 40, y: 0, w: 40, h: 40, siz: 30,
},
btnRsCopyClose: {
x: g_sWidth - 80, y: 0, w: 80, h: 40, siz: 20,
},
resultImageDesc: {
x: 0, y: g_sHeight - 30, w: g_sWidth, h: 20, siz: g_limitObj.mainSiz,
},
});
};

Expand Down Expand Up @@ -1522,6 +1528,7 @@ const g_shortcutObj = {
KeyC: { id: `btnCopy`, reset: true },
KeyT: { id: `btnTweet`, reset: true },
KeyG: { id: `btnGitter`, reset: true },
KeyP: { id: `btnCopyImage` },
Backspace: { id: `btnRetry` },
},
};
Expand Down Expand Up @@ -2642,6 +2649,7 @@ const g_lblNameObj = {
b_tweet: `Tweet`,
b_gitter: `Gitter`,
b_retry: `Retry`,
b_close: `Close`,

Difficulty: `Difficulty`,
Speed: `Speed`,
Expand Down Expand Up @@ -2822,6 +2830,7 @@ const g_lang_lblNameObj = {
kcShortcutDesc: `プレイ中ショートカット:「{0}」タイトルバック / 「{1}」リトライ`,
transKeyDesc: `別キーモードではキーコンフィグ、ColorType等は保存されません`,
sdShortcutDesc: `Hid+/Sud+時ショートカット:「pageUp」カバーを上へ / 「pageDown」下へ`,
resultImageDesc: `画像を右クリックしてコピーできます`,

s_level: `Level`,
s_douji: `同時補正`,
Expand Down Expand Up @@ -2855,6 +2864,7 @@ const g_lang_lblNameObj = {
kcShortcutDesc: `Shortcut during play: "{0}" Return to title / "{1}" Retry the game`,
transKeyDesc: `Key config, Color type, etc. are not saved in another key mode`,
sdShortcutDesc: `When "Hidden+" or "Sudden+" select, "pageUp" cover up / "pageDown" cover down`,
resultImageDesc: `You can copy the image by right-clicking on it.`,

s_level: `Level`,
s_douji: `Chords`,
Expand Down

0 comments on commit 51943db

Please sign in to comment.