Merge branch 'develop' into pr/ThatOneCalculator/8764

This commit is contained in:
tamaina 2022-11-22 14:26:15 +09:00
commit 61568ad780
19 changed files with 130 additions and 77 deletions

View file

@ -20,6 +20,7 @@ You should also include the user name that made the change.
### Improvements ### Improvements
### Bugfixes ### Bugfixes
- Server: 引用内の文章がnyaizeされてしまう問題を修正 @kabo2468
- Server: Bug fix for Pinned Users lookup on instance @squidicuzz - Server: Bug fix for Pinned Users lookup on instance @squidicuzz
- Client: インスタンスティッカーのfaviconを読み込む際に偽サイト警告が出ることがあるのを修正 @syuilo - Client: インスタンスティッカーのfaviconを読み込む際に偽サイト警告が出ることがあるのを修正 @syuilo

View file

@ -15,8 +15,8 @@
"private": true, "private": true,
"scripts": { "scripts": {
"build": "yarn workspaces foreach run build && yarn run gulp", "build": "yarn workspaces foreach run build && yarn run gulp",
"start": "cd packages/backend && node --experimental-json-modules ./built/boot/index.js", "start": "cd packages/backend && node ./built/boot/index.js",
"start:test": "cd packages/backend && cross-env NODE_ENV=test node --experimental-json-modules ./built/boot/index.js", "start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/index.js",
"init": "yarn migrate", "init": "yarn migrate",
"migrate": "cd packages/backend && npx typeorm migration:run -d ormconfig.js", "migrate": "cd packages/backend && npx typeorm migration:run -d ormconfig.js",
"migrateandstart": "yarn migrate && yarn start", "migrateandstart": "yarn migrate && yarn start",
@ -51,13 +51,13 @@
"js-yaml": "4.1.0" "js-yaml": "4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@types/gulp": "4.0.9", "@types/gulp": "4.0.10",
"@types/gulp-rename": "2.0.1", "@types/gulp-rename": "2.0.1",
"@typescript-eslint/eslint-plugin": "latest", "@typescript-eslint/eslint-plugin": "latest",
"@typescript-eslint/parser": "5.42.1", "@typescript-eslint/parser": "5.43.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "11.0.1", "cypress": "11.1.0",
"start-server-and-test": "1.14.0", "start-server-and-test": "1.14.0",
"typescript": "4.8.4" "typescript": "4.9.3"
} }
} }

View file

@ -35,13 +35,13 @@
"@peertube/http-signature": "1.7.0", "@peertube/http-signature": "1.7.0",
"@sinonjs/fake-timers": "10.0.0", "@sinonjs/fake-timers": "10.0.0",
"@syuilo/aiscript": "0.11.1", "@syuilo/aiscript": "0.11.1",
"ajv": "8.11.0", "ajv": "8.11.2",
"archiver": "5.3.1", "archiver": "5.3.1",
"autobind-decorator": "2.4.0", "autobind-decorator": "2.4.0",
"autwh": "0.1.0", "autwh": "0.1.0",
"aws-sdk": "2.1253.0", "aws-sdk": "2.1258.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blurhash": "1.1.5", "blurhash": "2.0.4",
"bull": "4.10.1", "bull": "4.10.1",
"cacheable-lookup": "6.1.0", "cacheable-lookup": "6.1.0",
"cbor": "8.1.0", "cbor": "8.1.0",
@ -58,10 +58,10 @@
"file-type": "18.0.0", "file-type": "18.0.0",
"fluent-ffmpeg": "2.1.2", "fluent-ffmpeg": "2.1.2",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"got": "12.5.2", "got": "12.5.3",
"hpagent": "1.2.0", "hpagent": "1.2.0",
"ioredis": "4.28.5", "ioredis": "4.28.5",
"ip-cidr": "3.0.10", "ip-cidr": "3.0.11",
"is-svg": "4.3.2", "is-svg": "4.3.2",
"jest-mock": "^29.0.3", "jest-mock": "^29.0.3",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
@ -92,7 +92,7 @@
"os-utils": "0.0.14", "os-utils": "0.0.14",
"parse5": "7.1.1", "parse5": "7.1.1",
"pg": "8.8.0", "pg": "8.8.0",
"private-ip": "2.3.4", "private-ip": "3.0.0",
"probe-image-size": "7.2.3", "probe-image-size": "7.2.3",
"promise-limit": "2.7.0", "promise-limit": "2.7.0",
"pug": "3.0.2", "pug": "3.0.2",
@ -118,7 +118,7 @@
"stringz": "2.1.0", "stringz": "2.1.0",
"summaly": "2.7.0", "summaly": "2.7.0",
"syslog-pro": "1.0.0", "syslog-pro": "1.0.0",
"systeminformation": "5.12.14", "systeminformation": "5.13.5",
"tinycolor2": "1.4.2", "tinycolor2": "1.4.2",
"tmp": "0.2.1", "tmp": "0.2.1",
"ts-loader": "9.4.1", "ts-loader": "9.4.1",
@ -136,8 +136,8 @@
"xev": "3.0.2" "xev": "3.0.2"
}, },
"devDependencies": { "devDependencies": {
"@redocly/openapi-core": "1.0.0-beta.112", "@redocly/openapi-core": "1.0.0-beta.114",
"@swc/core": "1.3.15", "@swc/core": "1.3.18",
"@swc/jest": "0.2.23", "@swc/jest": "0.2.23",
"@types/archiver": "5.3.1", "@types/archiver": "5.3.1",
"@types/bcryptjs": "2.4.2", "@types/bcryptjs": "2.4.2",
@ -145,7 +145,7 @@
"@types/cbor": "6.0.0", "@types/cbor": "6.0.0",
"@types/escape-regexp": "0.0.1", "@types/escape-regexp": "0.0.1",
"@types/fluent-ffmpeg": "2.1.20", "@types/fluent-ffmpeg": "2.1.20",
"@types/jest": "29.2.2", "@types/jest": "29.2.3",
"@types/js-yaml": "4.0.5", "@types/js-yaml": "4.0.5",
"@types/jsdom": "20.0.1", "@types/jsdom": "20.0.1",
"@types/jsonld": "1.5.7", "@types/jsonld": "1.5.7",
@ -186,13 +186,13 @@
"@types/web-push": "3.3.2", "@types/web-push": "3.3.2",
"@types/websocket": "1.0.5", "@types/websocket": "1.0.5",
"@types/ws": "8.5.3", "@types/ws": "8.5.3",
"@typescript-eslint/eslint-plugin": "5.42.1", "@typescript-eslint/eslint-plugin": "5.43.0",
"@typescript-eslint/parser": "5.42.1", "@typescript-eslint/parser": "5.43.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint": "8.27.0", "eslint": "8.28.0",
"eslint-plugin-import": "2.26.0", "eslint-plugin-import": "2.26.0",
"execa": "6.1.0", "execa": "6.1.0",
"jest": "29.3.1", "jest": "29.3.1",
"typescript": "4.8.4" "typescript": "4.9.3"
} }
} }

View file

@ -9,6 +9,7 @@ import { QueueService } from '@/core/QueueService.js';
import { CreateSystemUserService } from '@/core/CreateSystemUserService.js'; import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js'; import { ApRendererService } from '@/core/remote/activitypub/ApRendererService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { deepClone } from '@/misc/clone.js';
const ACTOR_USERNAME = 'relay.actor' as const; const ACTOR_USERNAME = 'relay.actor' as const;
@ -105,7 +106,7 @@ export class RelayService {
})); }));
if (relays.length === 0) return; if (relays.length === 0) return;
const copy = structuredClone(activity); const copy = deepClone(activity);
if (!copy.to) copy.to = ['https://www.w3.org/ns/activitystreams#Public']; if (!copy.to) copy.to = ['https://www.w3.org/ns/activitystreams#Public'];
const signed = await this.apRendererService.attachLdSignature(copy, user); const signed = await this.apRendererService.attachLdSignature(copy, user);

View file

@ -9,6 +9,7 @@ import { awaitAll } from '@/misc/prelude/await-all.js';
import type { User } from '@/models/entities/User.js'; import type { User } from '@/models/entities/User.js';
import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { DriveFile } from '@/models/entities/DriveFile.js';
import { appendQuery, query } from '@/misc/prelude/url.js'; import { appendQuery, query } from '@/misc/prelude/url.js';
import { deepClone } from '@/misc/clone.js';
import { UtilityService } from '../UtilityService.js'; import { UtilityService } from '../UtilityService.js';
import { UserEntityService } from './UserEntityService.js'; import { UserEntityService } from './UserEntityService.js';
import { DriveFolderEntityService } from './DriveFolderEntityService.js'; import { DriveFolderEntityService } from './DriveFolderEntityService.js';
@ -55,7 +56,7 @@ export class DriveFileEntityService {
public getPublicProperties(file: DriveFile): DriveFile['properties'] { public getPublicProperties(file: DriveFile): DriveFile['properties'] {
if (file.properties.orientation != null) { if (file.properties.orientation != null) {
const properties = structuredClone(file.properties); const properties = deepClone(file.properties);
if (file.properties.orientation >= 5) { if (file.properties.orientation >= 5) {
[properties.width, properties.height] = [properties.height, properties.width]; [properties.width, properties.height] = [properties.height, properties.width];
} }

View file

@ -329,12 +329,20 @@ export class NoteEntityService implements OnModuleInit {
if (packed.user.isCat && packed.text) { if (packed.user.isCat && packed.text) {
const tokens = packed.text ? mfm.parse(packed.text) : []; const tokens = packed.text ? mfm.parse(packed.text) : [];
mfm.inspect(tokens, node => { function nyaizeNode(node: mfm.MfmNode) {
if (node.type === 'quote') return;
if (node.type === 'text') { if (node.type === 'text') {
// TODO: quoteなtextはskip
node.props.text = nyaize(node.props.text); node.props.text = nyaize(node.props.text);
} }
}); if (node.children) {
for (const child of node.children) {
nyaizeNode(child);
}
}
}
for (const node of tokens) {
nyaizeNode(node);
}
packed.text = mfm.toString(tokens); packed.text = mfm.toString(tokens);
} }

View file

@ -0,0 +1,18 @@
// structredCloneが遅いため
// SEE: http://var.blog.jp/archives/86038606.html
type Cloneable = string | number | boolean | null | { [key: string]: Cloneable } | Cloneable[];
export function deepClone<T extends Cloneable>(x: T): T {
if (typeof x === 'object') {
if (x === null) return x;
if (Array.isArray(x)) return x.map(deepClone) as T;
const obj = {} as Record<string, Cloneable>;
for (const [k, v] of Object.entries(x)) {
obj[k] = deepClone(v);
}
return obj as T;
} else {
return x;
}
}

View file

@ -26,6 +26,7 @@ import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityServi
import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
import type { ChannelsRepository, ClipsRepository, GalleryPostsRepository, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import type { ChannelsRepository, ClipsRepository, GalleryPostsRepository, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
import { deepClone } from '@/misc/clone.js';
import manifest from './manifest.json' assert { type: 'json' }; import manifest from './manifest.json' assert { type: 'json' };
import { FeedService } from './FeedService.js'; import { FeedService } from './FeedService.js';
import { UrlPreviewService } from './UrlPreviewService.js'; import { UrlPreviewService } from './UrlPreviewService.js';
@ -86,7 +87,7 @@ export class ClientServerService {
} }
private async manifestHandler(ctx: Koa.Context) { private async manifestHandler(ctx: Koa.Context) {
const res = structuredClone(manifest); const res = deepClone(manifest);
const instance = await this.metaService.fetch(true); const instance = await this.metaService.fetch(true);

View file

@ -17,11 +17,11 @@
"@vue/compiler-sfc": "3.2.45", "@vue/compiler-sfc": "3.2.45",
"autobind-decorator": "2.4.0", "autobind-decorator": "2.4.0",
"autosize": "5.0.1", "autosize": "5.0.1",
"blurhash": "1.1.5", "blurhash": "2.0.4",
"broadcast-channel": "4.18.1", "broadcast-channel": "4.18.1",
"browser-image-resizer": "git+https://github.com/misskey-dev/browser-image-resizer#v2.2.1-misskey.3", "browser-image-resizer": "git+https://github.com/misskey-dev/browser-image-resizer#v2.2.1-misskey.3",
"chart.js": "3.9.1", "chart.js": "4.0.1",
"chartjs-adapter-date-fns": "2.0.0", "chartjs-adapter-date-fns": "2.0.1",
"chartjs-plugin-gradient": "0.5.1", "chartjs-plugin-gradient": "0.5.1",
"chartjs-plugin-zoom": "1.2.1", "chartjs-plugin-zoom": "1.2.1",
"compare-versions": "5.0.1", "compare-versions": "5.0.1",
@ -54,10 +54,10 @@
"tsc-alias": "1.7.1", "tsc-alias": "1.7.1",
"tsconfig-paths": "4.1.0", "tsconfig-paths": "4.1.0",
"twemoji-parser": "14.0.0", "twemoji-parser": "14.0.0",
"typescript": "4.8.4", "typescript": "4.9.3",
"uuid": "9.0.0", "uuid": "9.0.0",
"vanilla-tilt": "1.7.3", "vanilla-tilt": "1.7.3",
"vite": "3.2.3", "vite": "3.2.4",
"vue": "3.2.45", "vue": "3.2.45",
"vue-prism-editor": "2.0.0-alpha.2", "vue-prism-editor": "2.0.0-alpha.2",
"vuedraggable": "4.0.1" "vuedraggable": "4.0.1"
@ -65,7 +65,7 @@
"devDependencies": { "devDependencies": {
"@types/escape-regexp": "0.0.1", "@types/escape-regexp": "0.0.1",
"@types/glob": "8.0.0", "@types/glob": "8.0.0",
"@types/gulp": "4.0.9", "@types/gulp": "4.0.10",
"@types/gulp-rename": "2.0.1", "@types/gulp-rename": "2.0.1",
"@types/katex": "0.14.0", "@types/katex": "0.14.0",
"@types/matter-js": "0.18.2", "@types/matter-js": "0.18.2",
@ -76,11 +76,11 @@
"@types/uuid": "8.3.4", "@types/uuid": "8.3.4",
"@types/websocket": "1.0.5", "@types/websocket": "1.0.5",
"@types/ws": "8.5.3", "@types/ws": "8.5.3",
"@typescript-eslint/eslint-plugin": "5.42.1", "@typescript-eslint/eslint-plugin": "5.43.0",
"@typescript-eslint/parser": "5.42.1", "@typescript-eslint/parser": "5.43.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "11.0.1", "cypress": "11.1.0",
"eslint": "8.27.0", "eslint": "8.28.0",
"eslint-plugin-import": "2.26.0", "eslint-plugin-import": "2.26.0",
"eslint-plugin-vue": "9.7.0", "eslint-plugin-vue": "9.7.0",
"rollup": "3.3.0", "rollup": "3.3.0",

View file

@ -129,6 +129,7 @@ import { $i } from '@/account';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { getNoteMenu } from '@/scripts/get-note-menu'; import { getNoteMenu } from '@/scripts/get-note-menu';
import { useNoteCapture } from '@/scripts/use-note-capture'; import { useNoteCapture } from '@/scripts/use-note-capture';
import { deepClone } from '@/scripts/clone';
const props = defineProps<{ const props = defineProps<{
note: misskey.entities.Note; note: misskey.entities.Note;
@ -137,12 +138,12 @@ const props = defineProps<{
const inChannel = inject('inChannel', null); const inChannel = inject('inChannel', null);
let note = $ref(JSON.parse(JSON.stringify(props.note))); let note = $ref(deepClone(props.note));
// plugin // plugin
if (noteViewInterruptors.length > 0) { if (noteViewInterruptors.length > 0) {
onMounted(async () => { onMounted(async () => {
let result = JSON.parse(JSON.stringify(note)); let result = deepClone(note);
for (const interruptor of noteViewInterruptors) { for (const interruptor of noteViewInterruptors) {
result = await interruptor.handler(result); result = await interruptor.handler(result);
} }

View file

@ -139,6 +139,7 @@ import { $i } from '@/account';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { getNoteMenu } from '@/scripts/get-note-menu'; import { getNoteMenu } from '@/scripts/get-note-menu';
import { useNoteCapture } from '@/scripts/use-note-capture'; import { useNoteCapture } from '@/scripts/use-note-capture';
import { deepClone } from '@/scripts/clone';
const props = defineProps<{ const props = defineProps<{
note: misskey.entities.Note; note: misskey.entities.Note;
@ -147,12 +148,12 @@ const props = defineProps<{
const inChannel = inject('inChannel', null); const inChannel = inject('inChannel', null);
let note = $ref(JSON.parse(JSON.stringify(props.note))); let note = $ref(deepClone(props.note));
// plugin // plugin
if (noteViewInterruptors.length > 0) { if (noteViewInterruptors.length > 0) {
onMounted(async () => { onMounted(async () => {
let result = JSON.parse(JSON.stringify(note)); let result = deepClone(note);
for (const interruptor of noteViewInterruptors) { for (const interruptor of noteViewInterruptors) {
result = await interruptor.handler(result); result = await interruptor.handler(result);
} }

View file

@ -89,6 +89,7 @@ import { i18n } from '@/i18n';
import { instance } from '@/instance'; import { instance } from '@/instance';
import { $i, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account'; import { $i, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account';
import { uploadFile } from '@/scripts/upload'; import { uploadFile } from '@/scripts/upload';
import { deepClone } from '@/scripts/clone';
const modal = inject('modal'); const modal = inject('modal');
@ -575,7 +576,7 @@ async function post() {
// plugin // plugin
if (notePostInterruptors.length > 0) { if (notePostInterruptors.length > 0) {
for (const interruptor of notePostInterruptors) { for (const interruptor of notePostInterruptors) {
postData = await interruptor.handler(JSON.parse(JSON.stringify(postData))); postData = await interruptor.handler(deepClone(postData));
} }
} }

View file

@ -66,8 +66,9 @@ import * as os from '@/os';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata'; import { definePageMetadata } from '@/scripts/page-metadata';
import { deepClone } from '@/scripts/clone';
let reactions = $ref(JSON.parse(JSON.stringify(defaultStore.state.reactions))); let reactions = $ref(deepClone(defaultStore.state.reactions));
const reactionPickerSize = $computed(defaultStore.makeGetterSetter('reactionPickerSize')); const reactionPickerSize = $computed(defaultStore.makeGetterSetter('reactionPickerSize'));
const reactionPickerWidth = $computed(defaultStore.makeGetterSetter('reactionPickerWidth')); const reactionPickerWidth = $computed(defaultStore.makeGetterSetter('reactionPickerWidth'));
@ -101,7 +102,7 @@ async function setDefault() {
}); });
if (canceled) return; if (canceled) return;
reactions = JSON.parse(JSON.stringify(defaultStore.def.reactions.default)); reactions = deepClone(defaultStore.def.reactions.default);
} }
function chooseEmoji(ev: MouseEvent) { function chooseEmoji(ev: MouseEvent) {

View file

@ -91,13 +91,14 @@ import FormRange from '@/components/form/range.vue';
import * as os from '@/os'; import * as os from '@/os';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { deepClone } from '@/scripts/clone';
const props = defineProps<{ const props = defineProps<{
_id: string; _id: string;
userLists: any[] | null; userLists: any[] | null;
}>(); }>();
const statusbar = reactive(JSON.parse(JSON.stringify(defaultStore.state.statusbars.find(x => x.id === props._id)))); const statusbar = reactive(deepClone(defaultStore.state.statusbars.find(x => x.id === props._id)));
watch(() => statusbar.type, () => { watch(() => statusbar.type, () => {
if (statusbar.type === 'rss') { if (statusbar.type === 'rss') {
@ -128,8 +129,8 @@ watch(statusbar, save);
async function save() { async function save() {
const i = defaultStore.state.statusbars.findIndex(x => x.id === props._id); const i = defaultStore.state.statusbars.findIndex(x => x.id === props._id);
const statusbars = JSON.parse(JSON.stringify(defaultStore.state.statusbars)); const statusbars = deepClone(defaultStore.state.statusbars);
statusbars[i] = JSON.parse(JSON.stringify(statusbar)); statusbars[i] = deepClone(statusbar);
defaultStore.set('statusbars', statusbars); defaultStore.set('statusbars', statusbars);
} }

View file

@ -0,0 +1,18 @@
// structredCloneが遅いため
// SEE: http://var.blog.jp/archives/86038606.html
type Cloneable = string | number | boolean | null | { [key: string]: Cloneable } | Cloneable[];
export function deepClone<T extends Cloneable>(x: T): T {
if (typeof x === 'object') {
if (x === null) return x;
if (Array.isArray(x)) return x.map(deepClone) as T;
const obj = {} as Record<string, Cloneable>;
for (const [k, v] of Object.entries(x)) {
obj[k] = deepClone(v);
}
return obj as T;
} else {
return x;
}
}

View file

@ -13,6 +13,7 @@ export type Theme = {
import lightTheme from '@/themes/_light.json5'; import lightTheme from '@/themes/_light.json5';
import darkTheme from '@/themes/_dark.json5'; import darkTheme from '@/themes/_dark.json5';
import { deepClone } from './clone';
export const themeProps = Object.keys(lightTheme.props).filter(key => !key.startsWith('X')); export const themeProps = Object.keys(lightTheme.props).filter(key => !key.startsWith('X'));
@ -60,7 +61,7 @@ export function applyTheme(theme: Theme, persist = true) {
const colorSchema = theme.base === 'dark' ? 'dark' : 'light'; const colorSchema = theme.base === 'dark' ? 'dark' : 'light';
// Deep copy // Deep copy
const _theme = JSON.parse(JSON.stringify(theme)); const _theme = deepClone(theme);
if (_theme.base) { if (_theme.base) {
const base = [lightTheme, darkTheme].find(x => x.id === _theme.base); const base = [lightTheme, darkTheme].find(x => x.id === _theme.base);

View file

@ -4,6 +4,7 @@ import { notificationTypes } from 'misskey-js';
import { Storage } from '../../pizzax'; import { Storage } from '../../pizzax';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { api } from '@/os'; import { api } from '@/os';
import { deepClone } from '@/scripts/clone';
type ColumnWidget = { type ColumnWidget = {
name: string; name: string;
@ -25,10 +26,6 @@ export type Column = {
tl?: 'home' | 'local' | 'social' | 'global'; tl?: 'home' | 'local' | 'social' | 'global';
}; };
function copy<T>(x: T): T {
return JSON.parse(JSON.stringify(x));
}
export const deckStore = markRaw(new Storage('deck', { export const deckStore = markRaw(new Storage('deck', {
profile: { profile: {
where: 'deviceAccount', where: 'deviceAccount',
@ -128,7 +125,7 @@ export function swapColumn(a: Column['id'], b: Column['id']) {
const aY = deckStore.state.layout[aX].findIndex(id => id === a); const aY = deckStore.state.layout[aX].findIndex(id => id === a);
const bX = deckStore.state.layout.findIndex(ids => ids.indexOf(b) !== -1); const bX = deckStore.state.layout.findIndex(ids => ids.indexOf(b) !== -1);
const bY = deckStore.state.layout[bX].findIndex(id => id === b); const bY = deckStore.state.layout[bX].findIndex(id => id === b);
const layout = copy(deckStore.state.layout); const layout = deepClone(deckStore.state.layout);
layout[aX][aY] = b; layout[aX][aY] = b;
layout[bX][bY] = a; layout[bX][bY] = a;
deckStore.set('layout', layout); deckStore.set('layout', layout);
@ -136,7 +133,7 @@ export function swapColumn(a: Column['id'], b: Column['id']) {
} }
export function swapLeftColumn(id: Column['id']) { export function swapLeftColumn(id: Column['id']) {
const layout = copy(deckStore.state.layout); const layout = deepClone(deckStore.state.layout);
deckStore.state.layout.some((ids, i) => { deckStore.state.layout.some((ids, i) => {
if (ids.includes(id)) { if (ids.includes(id)) {
const left = deckStore.state.layout[i - 1]; const left = deckStore.state.layout[i - 1];
@ -152,7 +149,7 @@ export function swapLeftColumn(id: Column['id']) {
} }
export function swapRightColumn(id: Column['id']) { export function swapRightColumn(id: Column['id']) {
const layout = copy(deckStore.state.layout); const layout = deepClone(deckStore.state.layout);
deckStore.state.layout.some((ids, i) => { deckStore.state.layout.some((ids, i) => {
if (ids.includes(id)) { if (ids.includes(id)) {
const right = deckStore.state.layout[i + 1]; const right = deckStore.state.layout[i + 1];
@ -168,9 +165,9 @@ export function swapRightColumn(id: Column['id']) {
} }
export function swapUpColumn(id: Column['id']) { export function swapUpColumn(id: Column['id']) {
const layout = copy(deckStore.state.layout); const layout = deepClone(deckStore.state.layout);
const idsIndex = deckStore.state.layout.findIndex(ids => ids.includes(id)); const idsIndex = deckStore.state.layout.findIndex(ids => ids.includes(id));
const ids = copy(deckStore.state.layout[idsIndex]); const ids = deepClone(deckStore.state.layout[idsIndex]);
ids.some((x, i) => { ids.some((x, i) => {
if (x === id) { if (x === id) {
const up = ids[i - 1]; const up = ids[i - 1];
@ -188,9 +185,9 @@ export function swapUpColumn(id: Column['id']) {
} }
export function swapDownColumn(id: Column['id']) { export function swapDownColumn(id: Column['id']) {
const layout = copy(deckStore.state.layout); const layout = deepClone(deckStore.state.layout);
const idsIndex = deckStore.state.layout.findIndex(ids => ids.includes(id)); const idsIndex = deckStore.state.layout.findIndex(ids => ids.includes(id));
const ids = copy(deckStore.state.layout[idsIndex]); const ids = deepClone(deckStore.state.layout[idsIndex]);
ids.some((x, i) => { ids.some((x, i) => {
if (x === id) { if (x === id) {
const down = ids[i + 1]; const down = ids[i + 1];
@ -208,7 +205,7 @@ export function swapDownColumn(id: Column['id']) {
} }
export function stackLeftColumn(id: Column['id']) { export function stackLeftColumn(id: Column['id']) {
let layout = copy(deckStore.state.layout); let layout = deepClone(deckStore.state.layout);
const i = deckStore.state.layout.findIndex(ids => ids.includes(id)); const i = deckStore.state.layout.findIndex(ids => ids.includes(id));
layout = layout.map(ids => ids.filter(_id => _id !== id)); layout = layout.map(ids => ids.filter(_id => _id !== id));
layout[i - 1].push(id); layout[i - 1].push(id);
@ -218,7 +215,7 @@ export function stackLeftColumn(id: Column['id']) {
} }
export function popRightColumn(id: Column['id']) { export function popRightColumn(id: Column['id']) {
let layout = copy(deckStore.state.layout); let layout = deepClone(deckStore.state.layout);
const i = deckStore.state.layout.findIndex(ids => ids.includes(id)); const i = deckStore.state.layout.findIndex(ids => ids.includes(id));
const affected = layout[i]; const affected = layout[i];
layout = layout.map(ids => ids.filter(_id => _id !== id)); layout = layout.map(ids => ids.filter(_id => _id !== id));
@ -226,7 +223,7 @@ export function popRightColumn(id: Column['id']) {
layout = layout.filter(ids => ids.length > 0); layout = layout.filter(ids => ids.length > 0);
deckStore.set('layout', layout); deckStore.set('layout', layout);
const columns = copy(deckStore.state.columns); const columns = deepClone(deckStore.state.columns);
for (const column of columns) { for (const column of columns) {
if (affected.includes(column.id)) { if (affected.includes(column.id)) {
column.active = true; column.active = true;
@ -238,9 +235,9 @@ export function popRightColumn(id: Column['id']) {
} }
export function addColumnWidget(id: Column['id'], widget: ColumnWidget) { export function addColumnWidget(id: Column['id'], widget: ColumnWidget) {
const columns = copy(deckStore.state.columns); const columns = deepClone(deckStore.state.columns);
const columnIndex = deckStore.state.columns.findIndex(c => c.id === id); const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
const column = copy(deckStore.state.columns[columnIndex]); const column = deepClone(deckStore.state.columns[columnIndex]);
if (column == null) return; if (column == null) return;
if (column.widgets == null) column.widgets = []; if (column.widgets == null) column.widgets = [];
column.widgets.unshift(widget); column.widgets.unshift(widget);
@ -250,9 +247,9 @@ export function addColumnWidget(id: Column['id'], widget: ColumnWidget) {
} }
export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) { export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) {
const columns = copy(deckStore.state.columns); const columns = deepClone(deckStore.state.columns);
const columnIndex = deckStore.state.columns.findIndex(c => c.id === id); const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
const column = copy(deckStore.state.columns[columnIndex]); const column = deepClone(deckStore.state.columns[columnIndex]);
if (column == null) return; if (column == null) return;
column.widgets = column.widgets.filter(w => w.id !== widget.id); column.widgets = column.widgets.filter(w => w.id !== widget.id);
columns[columnIndex] = column; columns[columnIndex] = column;
@ -261,9 +258,9 @@ export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) {
} }
export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) { export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) {
const columns = copy(deckStore.state.columns); const columns = deepClone(deckStore.state.columns);
const columnIndex = deckStore.state.columns.findIndex(c => c.id === id); const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
const column = copy(deckStore.state.columns[columnIndex]); const column = deepClone(deckStore.state.columns[columnIndex]);
if (column == null) return; if (column == null) return;
column.widgets = widgets; column.widgets = widgets;
columns[columnIndex] = column; columns[columnIndex] = column;
@ -272,9 +269,9 @@ export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) {
} }
export function updateColumnWidget(id: Column['id'], widgetId: string, widgetData: any) { export function updateColumnWidget(id: Column['id'], widgetId: string, widgetData: any) {
const columns = copy(deckStore.state.columns); const columns = deepClone(deckStore.state.columns);
const columnIndex = deckStore.state.columns.findIndex(c => c.id === id); const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
const column = copy(deckStore.state.columns[columnIndex]); const column = deepClone(deckStore.state.columns[columnIndex]);
if (column == null) return; if (column == null) return;
column.widgets = column.widgets.map(w => w.id === widgetId ? { column.widgets = column.widgets.map(w => w.id === widgetId ? {
...w, ...w,
@ -286,9 +283,9 @@ export function updateColumnWidget(id: Column['id'], widgetId: string, widgetDat
} }
export function updateColumn(id: Column['id'], column: Partial<Column>) { export function updateColumn(id: Column['id'], column: Partial<Column>) {
const columns = copy(deckStore.state.columns); const columns = deepClone(deckStore.state.columns);
const columnIndex = deckStore.state.columns.findIndex(c => c.id === id); const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
const currentColumn = copy(deckStore.state.columns[columnIndex]); const currentColumn = deepClone(deckStore.state.columns[columnIndex]);
if (currentColumn == null) return; if (currentColumn == null) return;
for (const [k, v] of Object.entries(column)) { for (const [k, v] of Object.entries(column)) {
currentColumn[k] = v; currentColumn[k] = v;

View file

@ -47,12 +47,13 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onUnmounted, reactive, ref } from 'vue'; import { onMounted, onUnmounted, reactive, ref } from 'vue';
import { GetFormResultType } from '@/scripts/form';
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
import { GetFormResultType } from '@/scripts/form';
import { stream } from '@/stream'; import { stream } from '@/stream';
import number from '@/filters/number'; import number from '@/filters/number';
import * as sound from '@/scripts/sound'; import * as sound from '@/scripts/sound';
import * as os from '@/os'; import * as os from '@/os';
import { deepClone } from '@/scripts/clone';
const name = 'jobQueue'; const name = 'jobQueue';
@ -100,12 +101,12 @@ const prev = reactive({} as typeof current);
const jammedSound = sound.setVolume(sound.getAudio('syuilo/queue-jammed'), 1); const jammedSound = sound.setVolume(sound.getAudio('syuilo/queue-jammed'), 1);
for (const domain of ['inbox', 'deliver']) { for (const domain of ['inbox', 'deliver']) {
prev[domain] = JSON.parse(JSON.stringify(current[domain])); prev[domain] = deepClone(current[domain]);
} }
const onStats = (stats) => { const onStats = (stats) => {
for (const domain of ['inbox', 'deliver']) { for (const domain of ['inbox', 'deliver']) {
prev[domain] = JSON.parse(JSON.stringify(current[domain])); prev[domain] = deepClone(current[domain]);
current[domain].activeSincePrevTick = stats[domain].activeSincePrevTick; current[domain].activeSincePrevTick = stats[domain].activeSincePrevTick;
current[domain].active = stats[domain].active; current[domain].active = stats[domain].active;
current[domain].waiting = stats[domain].waiting; current[domain].waiting = stats[domain].waiting;

View file

@ -2,6 +2,7 @@ import { reactive, watch } from 'vue';
import { throttle } from 'throttle-debounce'; import { throttle } from 'throttle-debounce';
import { Form, GetFormResultType } from '@/scripts/form'; import { Form, GetFormResultType } from '@/scripts/form';
import * as os from '@/os'; import * as os from '@/os';
import { deepClone } from '@/scripts/clone';
export type Widget<P extends Record<string, unknown>> = { export type Widget<P extends Record<string, unknown>> = {
id: string; id: string;
@ -32,7 +33,7 @@ export const useWidgetPropsManager = <F extends Form & Record<string, { default:
save: () => void; save: () => void;
configure: () => void; configure: () => void;
} => { } => {
const widgetProps = reactive(props.widget ? JSON.parse(JSON.stringify(props.widget.data)) : {}); const widgetProps = reactive(props.widget ? deepClone(props.widget.data) : {});
const mergeProps = () => { const mergeProps = () => {
for (const prop of Object.keys(propsDef)) { for (const prop of Object.keys(propsDef)) {
@ -43,14 +44,14 @@ export const useWidgetPropsManager = <F extends Form & Record<string, { default:
}; };
watch(widgetProps, () => { watch(widgetProps, () => {
mergeProps(); mergeProps();
}, { deep: true, immediate: true, }); }, { deep: true, immediate: true });
const save = throttle(3000, () => { const save = throttle(3000, () => {
emit('updateProps', widgetProps); emit('updateProps', widgetProps);
}); });
const configure = async () => { const configure = async () => {
const form = JSON.parse(JSON.stringify(propsDef)); const form = deepClone(propsDef);
for (const item of Object.keys(form)) { for (const item of Object.keys(form)) {
form[item].default = widgetProps[item]; form[item].default = widgetProps[item];
} }