이전 util 이전
This commit is contained in:
@@ -42,6 +42,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rushstack/eslint-patch": "^1.3.2",
|
"@rushstack/eslint-patch": "^1.3.2",
|
||||||
"@tsconfig/node18": "^18.2.0",
|
"@tsconfig/node18": "^18.2.0",
|
||||||
|
"@types/async-lock": "^1.4.0",
|
||||||
"@types/bootstrap": "^5.2.6",
|
"@types/bootstrap": "^5.2.6",
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
"@types/express-session": "^1.17.7",
|
"@types/express-session": "^1.17.7",
|
||||||
|
|||||||
Generated
+7
@@ -73,6 +73,9 @@ devDependencies:
|
|||||||
'@tsconfig/node18':
|
'@tsconfig/node18':
|
||||||
specifier: ^18.2.0
|
specifier: ^18.2.0
|
||||||
version: 18.2.0
|
version: 18.2.0
|
||||||
|
'@types/async-lock':
|
||||||
|
specifier: ^1.4.0
|
||||||
|
version: 1.4.0
|
||||||
'@types/bootstrap':
|
'@types/bootstrap':
|
||||||
specifier: ^5.2.6
|
specifier: ^5.2.6
|
||||||
version: 5.2.6
|
version: 5.2.6
|
||||||
@@ -528,6 +531,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-yhxwIlFVSVcMym3O31HoMnRXpoenmpIxcj4Yoes2DUpe+xCJnA7ECQP1Vw889V0jTt/2nzvpLQ/UuMYCd3JPIg==}
|
resolution: {integrity: sha512-yhxwIlFVSVcMym3O31HoMnRXpoenmpIxcj4Yoes2DUpe+xCJnA7ECQP1Vw889V0jTt/2nzvpLQ/UuMYCd3JPIg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/async-lock@1.4.0:
|
||||||
|
resolution: {integrity: sha512-2+rYSaWrpdbQG3SA0LmMT6YxWLrI81AqpMlSkw3QtFc2HGDufkweQSn30Eiev7x9LL0oyFrBqk1PXOnB9IEgKg==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/body-parser@1.19.2:
|
/@types/body-parser@1.19.2:
|
||||||
resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==}
|
resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import "dotenv/config";
|
|||||||
import { DataSource } from "typeorm";
|
import { DataSource } from "typeorm";
|
||||||
|
|
||||||
export const AppDataSource = new DataSource({
|
export const AppDataSource = new DataSource({
|
||||||
type: "mysql",
|
type: "mariadb",
|
||||||
host: process.env.DB_HOST,
|
host: process.env.GAME_DB_HOST,
|
||||||
database: "db/database.sqlite",
|
port: Number(process.env.GAME_DB_PORT),
|
||||||
synchronize: true,
|
username: process.env.GAME_DB_USER,
|
||||||
logging: false,
|
password: process.env.GAME_DB_PASSWORD,
|
||||||
|
charset: "utf8mb4",
|
||||||
entities: [],
|
entities: [],
|
||||||
migrations: [],
|
migrations: [],
|
||||||
subscribers: [],
|
subscribers: [],
|
||||||
|
|||||||
Vendored
+4
-1
@@ -1,9 +1,12 @@
|
|||||||
declare namespace NodeJS {
|
declare namespace NodeJS {
|
||||||
interface ProcessEnv {
|
interface ProcessEnv {
|
||||||
NODE_ENV: 'development' | 'production';
|
NODE_ENV: 'development' | 'production';
|
||||||
GAME_DB_SERVER: string;
|
GAME_DB_HOST: string;
|
||||||
GAME_DB_DATABASE: string;
|
GAME_DB_DATABASE: string;
|
||||||
|
GAME_DB_PORT: string;
|
||||||
GAME_DB_USER: string;
|
GAME_DB_USER: string;
|
||||||
GAME_DB_PASSWORD: string;
|
GAME_DB_PASSWORD: string;
|
||||||
|
SERVER_PORT: string; //숫자
|
||||||
|
SESSION_SECRET: string; //길게
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+6
-2
@@ -2,7 +2,7 @@ import 'dotenv/config';
|
|||||||
import 'reflect-metadata';
|
import 'reflect-metadata';
|
||||||
import { BasicQueryDTO } from './api/index.js';
|
import { BasicQueryDTO } from './api/index.js';
|
||||||
import { validate } from 'class-validator';
|
import { validate } from 'class-validator';
|
||||||
import express, { Request, Response } from "express"
|
import express, { type Request, type Response } from "express"
|
||||||
import { AppDataSource } from "./data_source"
|
import { AppDataSource } from "./data_source"
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
import session from "express-session";
|
import session from "express-session";
|
||||||
@@ -23,6 +23,10 @@ export async function test() {
|
|||||||
|
|
||||||
await test();
|
await test();
|
||||||
|
|
||||||
|
const ownConfig = {
|
||||||
|
sessionSecret: process.env.SESSION_SECRET,
|
||||||
|
port: Number(process.env.SERVER_PORT),
|
||||||
|
}
|
||||||
|
|
||||||
AppDataSource.initialize().then(async () => {
|
AppDataSource.initialize().then(async () => {
|
||||||
|
|
||||||
@@ -35,7 +39,7 @@ AppDataSource.initialize().then(async () => {
|
|||||||
}))
|
}))
|
||||||
app.set('etag', false);
|
app.set('etag', false);
|
||||||
|
|
||||||
app.use('/', rootRouter);
|
//app.use('/', rootRouter);
|
||||||
|
|
||||||
// start express server
|
// start express server
|
||||||
app.listen(ownConfig.port)
|
app.listen(ownConfig.port)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
|
|
||||||
export class NotNullExpected extends TypeError {
|
export class NotNullExpected extends TypeError {
|
||||||
public name = 'NotNullExpected';
|
public override name = 'NotNullExpected';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,173 +0,0 @@
|
|||||||
import type { TurnObj } from "@/defs";
|
|
||||||
import { clone, isString, range } from "lodash-es";
|
|
||||||
import { ref, type Ref } from "vue";
|
|
||||||
import { unwrap } from "./unwrap";
|
|
||||||
|
|
||||||
export type TurnObjWithTime = TurnObj & {
|
|
||||||
time: string;
|
|
||||||
year?: number;
|
|
||||||
month?: number;
|
|
||||||
tooltip?: string;
|
|
||||||
style?: Record<string, string>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getEmptyTurn = (maxTurn: number): TurnObjWithTime[] => Array.from<TurnObjWithTime>({
|
|
||||||
length: maxTurn,
|
|
||||||
}).fill({
|
|
||||||
arg: {},
|
|
||||||
brief: "",
|
|
||||||
action: "",
|
|
||||||
year: undefined,
|
|
||||||
month: undefined,
|
|
||||||
time: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
export class QueryActionHelper {
|
|
||||||
public readonly reservedCommandList: Ref<TurnObjWithTime[]>;
|
|
||||||
public readonly selectedTurnList: Ref<Set<number>>;
|
|
||||||
public readonly prevSelectedTurnList: Ref<Set<number>>;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
protected maxTurn: number,
|
|
||||||
) {
|
|
||||||
this.reservedCommandList = ref(getEmptyTurn(maxTurn));
|
|
||||||
this.selectedTurnList = ref(new Set());
|
|
||||||
this.prevSelectedTurnList = ref(new Set([0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleTurn(...reqTurnList: number[] | string[]) {
|
|
||||||
for (let turnIdx of reqTurnList) {
|
|
||||||
if (isString(turnIdx)) {
|
|
||||||
turnIdx = parseInt(turnIdx);
|
|
||||||
}
|
|
||||||
if (this.selectedTurnList.value.has(turnIdx)) {
|
|
||||||
this.selectedTurnList.value.delete(turnIdx);
|
|
||||||
} else {
|
|
||||||
this.selectedTurnList.value.add(turnIdx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectTurn(...reqTurnList: number[] | string[]) {
|
|
||||||
this.selectedTurnList.value.clear();
|
|
||||||
for (const turnIdx of reqTurnList) {
|
|
||||||
if (isString(turnIdx)) {
|
|
||||||
this.selectedTurnList.value.add(parseInt(turnIdx));
|
|
||||||
} else {
|
|
||||||
this.selectedTurnList.value.add(turnIdx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectStep(begin: number, step: number) {
|
|
||||||
this.selectedTurnList.value.clear();
|
|
||||||
for (const idx of range(0, this.maxTurn)) {
|
|
||||||
if ((idx - begin) % step == 0) {
|
|
||||||
this.selectedTurnList.value.add(idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectAll() {
|
|
||||||
for (let i = 0; i < this.maxTurn; i++) {
|
|
||||||
this.selectedTurnList.value.add(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getSelectedTurnList(useSort = true): number[] {
|
|
||||||
let result: number[];
|
|
||||||
if (this.selectedTurnList.value.size) {
|
|
||||||
result = Array.from(this.selectedTurnList.value);
|
|
||||||
}
|
|
||||||
else if (this.prevSelectedTurnList.value.size) {
|
|
||||||
result = Array.from(this.prevSelectedTurnList.value);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return [0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (useSort) {
|
|
||||||
return result.sort((a, b) => a - b);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
releaseSelectedTurnList() {
|
|
||||||
if (this.selectedTurnList.value.size > 0) {
|
|
||||||
this.prevSelectedTurnList.value.clear();
|
|
||||||
for (const v of this.selectedTurnList.value) {
|
|
||||||
this.prevSelectedTurnList.value.add(v);
|
|
||||||
}
|
|
||||||
this.selectedTurnList.value.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extractQueryActions(): [number[], TurnObj][] {
|
|
||||||
const reqTurnList = this.getSelectedTurnList();
|
|
||||||
const selectedMinTurnIdx = unwrap(Math.min(...reqTurnList));
|
|
||||||
|
|
||||||
const buffer = new Map<string, [number[], TurnObj]>();
|
|
||||||
for (const rawTurnIdx of reqTurnList) {
|
|
||||||
const turnIdx = rawTurnIdx - selectedMinTurnIdx;
|
|
||||||
const rawAction = this.reservedCommandList.value[rawTurnIdx]
|
|
||||||
const actionStr = JSON.stringify([rawAction.action, rawAction.arg]);
|
|
||||||
if (buffer.has(actionStr)) {
|
|
||||||
const items = unwrap(buffer.get(actionStr));
|
|
||||||
items[0].push(turnIdx);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
buffer.set(actionStr, [[turnIdx], {
|
|
||||||
action: rawAction.action,
|
|
||||||
arg: clone(rawAction.arg),
|
|
||||||
brief: rawAction.brief
|
|
||||||
}])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Array.from(buffer.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
amplifyQueryActions(rawActions: [number[], TurnObj][], reqTurnList: number[]): [number[], TurnObj][] {
|
|
||||||
if (reqTurnList.length < 1) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
let minQueryIdx = this.maxTurn;
|
|
||||||
let maxQueryIdx = 0;
|
|
||||||
for (const [turnList] of rawActions) {
|
|
||||||
for (const turnIdx of turnList) {
|
|
||||||
minQueryIdx = Math.min(minQueryIdx, turnIdx);
|
|
||||||
maxQueryIdx = Math.max(maxQueryIdx, turnIdx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const queryLength = maxQueryIdx - minQueryIdx + 1;
|
|
||||||
|
|
||||||
const queryTurnList: number[] = [reqTurnList[0]];
|
|
||||||
for (const reqTurnIdx of reqTurnList) {
|
|
||||||
const last = queryTurnList[queryTurnList.length - 1];
|
|
||||||
if (reqTurnIdx < last + queryLength) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
queryTurnList.push(reqTurnIdx);
|
|
||||||
}
|
|
||||||
|
|
||||||
const actions: [number[], TurnObj][] = [];
|
|
||||||
for (const [baseTurnList, action] of rawActions) {
|
|
||||||
const subTurnList: number[] = [];
|
|
||||||
for (const baseTurnIdx of baseTurnList) {
|
|
||||||
for (const queryTurnIdx of queryTurnList) {
|
|
||||||
const targetTurn = baseTurnIdx + queryTurnIdx;
|
|
||||||
if (targetTurn >= this.maxTurn) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
subTurnList.push(baseTurnIdx + queryTurnIdx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (subTurnList.length == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
actions.push([subTurnList, action]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return actions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+4
-4
@@ -5,9 +5,9 @@ export interface RNG {
|
|||||||
*/
|
*/
|
||||||
getMaxInt(): number;
|
getMaxInt(): number;
|
||||||
|
|
||||||
nextBytes(bytes: number): Uint8Array;
|
nextBytes(bytes: number): Promise<Uint8Array>;
|
||||||
nextBits(bits: number): Uint8Array;
|
nextBits(bits: number): Promise<Uint8Array>;
|
||||||
|
|
||||||
nextInt(max?: number): number;
|
nextInt(max?: number): Promise<number>;
|
||||||
nextFloat1(): number;
|
nextFloat1(): Promise<number>;
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
export class RuntimeError extends Error {
|
export class RuntimeError extends Error {
|
||||||
public name = 'RuntimeError';
|
public override name = 'RuntimeError';
|
||||||
constructor(public message: string = '') {
|
constructor(public override message: string = '') {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
toString(): string {
|
override toString(): string {
|
||||||
if (this.message) {
|
if (this.message) {
|
||||||
return this.name + ': ' + this.message;
|
return this.name + ': ' + this.message;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,115 +0,0 @@
|
|||||||
import type { TurnObj } from '@/defs';
|
|
||||||
import { ref, watch, type Ref } from 'vue';
|
|
||||||
|
|
||||||
|
|
||||||
export class StoredActionsHelper {
|
|
||||||
public readonly recentActions: Ref<Map<string, TurnObj>>;
|
|
||||||
public readonly storedActions = ref(new Map<string, [number[], TurnObj][]>());
|
|
||||||
public readonly clipboard = ref<[number[], TurnObj][] | undefined>(undefined);
|
|
||||||
public readonly activatedCategory = ref<string>("");
|
|
||||||
public readonly isEditMode = ref(false);
|
|
||||||
|
|
||||||
public readonly recentActionsKey: string;
|
|
||||||
public readonly storedActionsKey: string;
|
|
||||||
public readonly clipboardKey: string;
|
|
||||||
public readonly activatedCategoryKey: string;
|
|
||||||
public readonly editModeKey: string;
|
|
||||||
|
|
||||||
constructor(protected serverNick: string, protected type: 'general' | 'nation', protected mapName: string, protected unitSet: string, protected maxRecent = 10) {
|
|
||||||
this.recentActions = ref(new Map());
|
|
||||||
const typeKey = `${serverNick}_${mapName}_${unitSet}_${type}`;
|
|
||||||
this.recentActionsKey = `${typeKey}RecentActions`;
|
|
||||||
this.storedActionsKey = `${typeKey}StoredActions`;
|
|
||||||
this.clipboardKey = `${typeKey}Clipboard`;
|
|
||||||
this.activatedCategoryKey = `${typeKey}ActivatedCategory`;
|
|
||||||
this.editModeKey = `${serverNick}_${type}_isEditMode`;
|
|
||||||
|
|
||||||
this.loadRecentActions();
|
|
||||||
this.loadStoredActions();
|
|
||||||
|
|
||||||
const rawClipboard = localStorage.getItem(this.clipboardKey);
|
|
||||||
if (rawClipboard !== null) {
|
|
||||||
this.clipboard.value = JSON.parse(rawClipboard);
|
|
||||||
}
|
|
||||||
watch(this.clipboard, (newValue) => {
|
|
||||||
localStorage.setItem(this.clipboardKey, JSON.stringify(newValue));
|
|
||||||
});
|
|
||||||
|
|
||||||
const rawActivatedCategory = localStorage.getItem(this.activatedCategoryKey);
|
|
||||||
if (rawActivatedCategory !== null) {
|
|
||||||
this.activatedCategory.value = JSON.parse(rawActivatedCategory);
|
|
||||||
}
|
|
||||||
watch(this.activatedCategory, (newValue) => {
|
|
||||||
localStorage.setItem(this.activatedCategoryKey, JSON.stringify(newValue));
|
|
||||||
});
|
|
||||||
|
|
||||||
this.isEditMode.value = localStorage.getItem(this.editModeKey) === '1';
|
|
||||||
watch(this.isEditMode, (newValue) => {
|
|
||||||
localStorage.setItem(this.editModeKey, newValue ? '1' : '0')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
loadRecentActions() {
|
|
||||||
try {
|
|
||||||
const rawRecentActions = JSON.parse(localStorage.getItem(this.recentActionsKey) ?? '[]') as TurnObj[];
|
|
||||||
const recentActions = new Map<string, TurnObj>();
|
|
||||||
for (const action of rawRecentActions) {
|
|
||||||
const actionKey = JSON.stringify([action.action, action.arg]);
|
|
||||||
recentActions.set(actionKey, action);
|
|
||||||
}
|
|
||||||
this.recentActions.value = recentActions;
|
|
||||||
}
|
|
||||||
catch(e){
|
|
||||||
console.log(`loadRecentActions error ${e}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pushRecentActions(action: TurnObj) {
|
|
||||||
const actionKey = JSON.stringify([action.action, action.arg]);
|
|
||||||
if (this.recentActions.value.has(actionKey)) {
|
|
||||||
this.recentActions.value.delete(actionKey);
|
|
||||||
}
|
|
||||||
else if (this.recentActions.value.size > this.maxRecent) {
|
|
||||||
const firstKey = this.recentActions.value.keys().next().value;
|
|
||||||
this.recentActions.value.delete(firstKey);
|
|
||||||
}
|
|
||||||
this.recentActions.value.set(actionKey, action);
|
|
||||||
this.saveRecentActions();
|
|
||||||
}
|
|
||||||
|
|
||||||
saveRecentActions() {
|
|
||||||
localStorage.setItem(this.recentActionsKey, JSON.stringify(Array.from(this.recentActions.value.values())));
|
|
||||||
}
|
|
||||||
|
|
||||||
loadStoredActions() {
|
|
||||||
try {
|
|
||||||
const rawValue: [string, [number[], TurnObj][]][] = JSON.parse(
|
|
||||||
localStorage.getItem(this.storedActionsKey) ?? '[]'
|
|
||||||
);
|
|
||||||
this.storedActions.value = new Map(rawValue);
|
|
||||||
}
|
|
||||||
catch(e){
|
|
||||||
console.log(`loadStoredActions error ${e}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setStoredActions(actionKey: string, actions: [number[], TurnObj][]) {
|
|
||||||
this.storedActions.value.set(actionKey, actions);
|
|
||||||
console.log(this.storedActions.value);
|
|
||||||
this.saveStoredActions();
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteStoredActions(actionKey: string) {
|
|
||||||
if (this.storedActions.value.delete(actionKey)) {
|
|
||||||
this.saveStoredActions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
saveStoredActions() {
|
|
||||||
localStorage.setItem(
|
|
||||||
this.storedActionsKey,
|
|
||||||
JSON.stringify(Array.from(this.storedActions.value.entries()))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import { escapeHtml } from '@/legacy/escapeHtml';
|
|
||||||
import linkifyStr from 'linkify-string';
|
|
||||||
/**
|
|
||||||
* 단순한 Template 함수. <%변수명%>으로 template 가능
|
|
||||||
* @see https://github.com/krasimir/absurd/blob/master/lib/processors/html/helpers/TemplateEngine.js
|
|
||||||
* @param {string} html
|
|
||||||
* @param {object} options
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function TemplateEngine(html: string, options: Record<string | number, unknown> = {}): string {
|
|
||||||
const re = /<%(.+?)%>/g;
|
|
||||||
const reExp = /(^( )?(var|if|for|else|switch|case|break|{|}|;))(.*)?/g;
|
|
||||||
const code = ['with(obj) { var r=[];\n'];
|
|
||||||
let cursor = 0;
|
|
||||||
const add = function (line: string, js?: boolean) {
|
|
||||||
js ? code.push(line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n') :
|
|
||||||
code.push(line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");\n' : '');
|
|
||||||
return add;
|
|
||||||
};
|
|
||||||
options.e = escapeHtml;
|
|
||||||
options.linkifyStr = linkifyStr;
|
|
||||||
for (; ;) {
|
|
||||||
const match = re.exec(html);
|
|
||||||
if (!match) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
add(html.slice(cursor, match.index))(match[1], true);
|
|
||||||
cursor = match.index + match[0].length;
|
|
||||||
}
|
|
||||||
add(html.substr(cursor, html.length - cursor));
|
|
||||||
|
|
||||||
code.push('return r.join(""); }');
|
|
||||||
const compiledCode = code.join('').replace(/[\r\t\n]/g, ' ');
|
|
||||||
try {
|
|
||||||
return new Function('obj', compiledCode).apply(options, [options]);
|
|
||||||
} catch (err: unknown) {
|
|
||||||
console.error(err, " in \n\nCode:\n", code, "\n");
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
import { htmlReady } from "@util/htmlReady";
|
|
||||||
import { unwrap } from "@util/unwrap";
|
|
||||||
import { keyScreenMode, type ScreenModeType } from "@/defs";
|
|
||||||
|
|
||||||
export function auto500px(targetHeight = 700): void {
|
|
||||||
let deviceWidth = -1;
|
|
||||||
let viewportMeta!: HTMLMetaElement;
|
|
||||||
let oldMode: ScreenModeType = 'auto';
|
|
||||||
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
let _viewPortMeta = document.querySelector<HTMLMetaElement>("meta[name=viewport]");
|
|
||||||
if (_viewPortMeta) {
|
|
||||||
viewportMeta = _viewPortMeta;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const htmlTag = unwrap(document.querySelector("head"));
|
|
||||||
_viewPortMeta = document.createElement("meta");
|
|
||||||
_viewPortMeta.name = 'viewport';
|
|
||||||
_viewPortMeta.content = 'width=500';
|
|
||||||
htmlTag.appendChild(_viewPortMeta);
|
|
||||||
viewportMeta = _viewPortMeta;
|
|
||||||
}
|
|
||||||
|
|
||||||
function adjustViewportWidth() {
|
|
||||||
const screenMode = (localStorage.getItem(keyScreenMode) as ScreenModeType)??'auto';
|
|
||||||
if(screenMode != oldMode){
|
|
||||||
oldMode = screenMode;
|
|
||||||
deviceWidth = window.screen.availWidth;
|
|
||||||
|
|
||||||
if(screenMode == '500px'){
|
|
||||||
viewportMeta.content = 'width=500';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(screenMode == '1000px'){
|
|
||||||
viewportMeta.content = 'width=1000';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(screenMode == 'auto'){
|
|
||||||
deviceWidth = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deviceWidth == window.screen.availWidth) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(oldMode != 'auto'){
|
|
||||||
oldMode = 'auto';
|
|
||||||
adjustViewportWidth();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
deviceWidth = window.screen.availWidth;
|
|
||||||
const innerHeight = window.innerHeight;
|
|
||||||
const selectorHeight = targetHeight;
|
|
||||||
|
|
||||||
if (deviceWidth < 500) {
|
|
||||||
viewportMeta.content = 'width=500';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (innerHeight < selectorHeight) {
|
|
||||||
const maybeNextWidth = deviceWidth / innerHeight * selectorHeight;
|
|
||||||
if (maybeNextWidth >= 700) {
|
|
||||||
viewportMeta.content = 'width=1000';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
viewportMeta.content = `height=${Math.ceil(selectorHeight)}`;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if(deviceWidth >= 700){
|
|
||||||
viewportMeta.content = 'width=1000';
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
viewportMeta.content = 'width=device-width, initial-scale=1';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
htmlReady(() => {
|
|
||||||
init();
|
|
||||||
adjustViewportWidth();
|
|
||||||
window.addEventListener('scroll', adjustViewportWidth, true);
|
|
||||||
window.addEventListener('orientationchange', adjustViewportWidth, true);
|
|
||||||
document.addEventListener('tryChangeScreenMode', adjustViewportWidth, false);
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import { unwrap } from "@util/unwrap";
|
|
||||||
|
|
||||||
export function autoResizeTextarea(e: Event): void {
|
|
||||||
const el = unwrap(e.target) as HTMLInputElement;
|
|
||||||
el.style.height = 'auto';
|
|
||||||
el.style.height = `${el.scrollHeight + 1}px`;
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { combineObject } from "../common_legacy";
|
import { combineObject } from "./combineObject";
|
||||||
|
|
||||||
|
|
||||||
export function combineArray<K extends string, V>(array: V[][], columnList: K[]): Record<K, V>[] {
|
export function combineArray<K extends string, V>(array: V[][], columnList: K[]): Record<K, V>[] {
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export function combineObject<K extends string, V>(item: V[], columnList: K[]): Record<K, V> {
|
||||||
|
const newItem: Record<string, V> = {};
|
||||||
|
for (const columnIdx in columnList) {
|
||||||
|
const columnName = columnList[columnIdx];
|
||||||
|
newItem[columnName] = item[columnIdx];
|
||||||
|
}
|
||||||
|
return newItem;
|
||||||
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import { isArray, isString, isNumber, isBoolean } from "lodash-es";
|
|
||||||
|
|
||||||
export function convertFormData(values: Record<string, null | number[] | string[] | boolean[] | number | string | boolean>): FormData {
|
|
||||||
const formData = new FormData();
|
|
||||||
|
|
||||||
const simpleConv = (v: unknown, key: string): string => {
|
|
||||||
if (isString(v)) {
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
if (isNumber(v)) {
|
|
||||||
return v.toString();
|
|
||||||
}
|
|
||||||
if (isBoolean(v)) {
|
|
||||||
return v ? 'true' : 'false';
|
|
||||||
}
|
|
||||||
if (v === null) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
throw new TypeError(`지원하지 않는 formData Type: ${key}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(values)) {
|
|
||||||
if (isArray(value)) {
|
|
||||||
const arrKey = `${key}[]`;
|
|
||||||
for (const subValue of value) {
|
|
||||||
formData.append(arrKey, simpleConv(subValue, key));
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
formData.append(key, simpleConv(value, key));
|
|
||||||
}
|
|
||||||
|
|
||||||
return formData;
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { IDItem } from '@/defs';
|
import type { IDItem } from './defs';
|
||||||
|
|
||||||
export function convertIDArray<T>(array: Iterable<T>): IDItem<T>[] {
|
export function convertIDArray<T>(array: Iterable<T>): IDItem<T>[] {
|
||||||
const result: IDItem<T>[] = [];
|
const result: IDItem<T>[] = [];
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
export function insertCustomCSS(key = 'sam_customCSS'){
|
|
||||||
const customCSS = localStorage.getItem(key);
|
|
||||||
if (customCSS) {
|
|
||||||
const css = document.createElement('style');
|
|
||||||
css.innerHTML = customCSS;
|
|
||||||
console.log(css);
|
|
||||||
document.getElementsByTagName('head')[0].appendChild(css);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export declare type ValuesOf<T> = T[keyof T];
|
||||||
|
|
||||||
|
export type IDItem<T> = {
|
||||||
|
id: T;
|
||||||
|
};
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export function exportWindow(obj:unknown, objName: string, targetWindow?: unknown):void{
|
|
||||||
const target:unknown = targetWindow ?? window;
|
|
||||||
(target as {[v: string]: unknown})[objName] = obj;
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { unwrap } from "./unwrap";
|
|
||||||
|
|
||||||
//https://stackoverflow.com/questions/36280818/how-to-convert-file-to-base64-in-javascript
|
|
||||||
export function getBase64FromFileObject(file: File): Promise<string> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
reader.onload = () => {
|
|
||||||
let encoded = unwrap(reader.result).toString().replace(/^data:(.*,)?/, '');
|
|
||||||
if ((encoded.length % 4) > 0) {
|
|
||||||
encoded += '='.repeat(4 - (encoded.length % 4));
|
|
||||||
}
|
|
||||||
resolve(encoded);
|
|
||||||
};
|
|
||||||
reader.onerror = error => reject(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { formatTime } from '@util/formatTime';
|
import { formatTime } from './/formatTime';
|
||||||
export function getDateTimeNow(withFraction = false): string {
|
export function getDateTimeNow(withFraction = false): string {
|
||||||
return formatTime(new Date(), withFraction);
|
return formatTime(new Date(), withFraction);
|
||||||
}
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
export function getIconPath(imgsvr: boolean | 1 | 0, picture: string): string {
|
|
||||||
// ../d_shared/common_path.js 필요
|
|
||||||
if (!imgsvr) {
|
|
||||||
return `${window.pathConfig.sharedIcon}/${picture}`;
|
|
||||||
} else {
|
|
||||||
return `${window.pathConfig.root}/d_pic/${picture}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
export function htmlReady(fn: () => unknown): void {
|
|
||||||
if (document.readyState != 'loading') {
|
|
||||||
fn();
|
|
||||||
} else {
|
|
||||||
document.addEventListener('DOMContentLoaded', fn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { BootstrapVueNext, BToastPlugin } from "bootstrap-vue-next";
|
|
||||||
import { Directives } from "bootstrap-vue-next";
|
|
||||||
import type { App } from "vue";
|
|
||||||
import Multiselect from "vue-multiselect";
|
|
||||||
|
|
||||||
export function installVue3Components<T>(app: App<T>): App<T> {
|
|
||||||
|
|
||||||
app.use(BootstrapVueNext).use(BToastPlugin).component('v-multiselect', Multiselect);
|
|
||||||
for (const [name, directive] of Object.entries(Directives)) {
|
|
||||||
//BVN 0.7.3 directive 이름 hack
|
|
||||||
if (!name.startsWith('v')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
app.directive(name.substring(1), directive);
|
|
||||||
}
|
|
||||||
return app;
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { unwrap } from "@util/unwrap";
|
import { unwrap } from ".//unwrap";
|
||||||
import { hexToRgb } from "@util/hexToRgb";
|
import { hexToRgb } from ".//hexToRgb";
|
||||||
|
|
||||||
export function isBrightColor(color: string): boolean {
|
export function isBrightColor(color: string): boolean {
|
||||||
const cv = unwrap(hexToRgb(color));
|
const cv = unwrap(hexToRgb(color));
|
||||||
|
|||||||
@@ -1,118 +0,0 @@
|
|||||||
import Schema, { type Rule, type Values } from "async-validator";
|
|
||||||
import { isArray } from "lodash-es";
|
|
||||||
import { mergeKVArray } from "@util/mergeKVArray";
|
|
||||||
import $ from 'jquery';
|
|
||||||
|
|
||||||
type Option = {
|
|
||||||
preParse?: ($target?: JQuery<HTMLElement>)=>void,
|
|
||||||
postParse?: (values:Record<string, string | string[]>, $target?: JQuery<HTMLElement>)=>[Record<string, string | string[]>, Map<string, string>]
|
|
||||||
}
|
|
||||||
|
|
||||||
type DefaultFormDataType = Record<string, null|number[]|string[]|boolean[]|number|string|boolean>;
|
|
||||||
|
|
||||||
export type NamedRules<T extends Record<string, unknown>> = {
|
|
||||||
[v in keyof T]: Rule
|
|
||||||
}
|
|
||||||
|
|
||||||
export class JQValidateForm<TypedValue extends Values = DefaultFormDataType> {
|
|
||||||
|
|
||||||
public readonly validator: Schema;
|
|
||||||
public readonly inputs: JQuery<HTMLElement>;
|
|
||||||
constructor(public readonly target: JQuery<HTMLElement>, public readonly rule: NamedRules<TypedValue>, public readonly option?:Option) {
|
|
||||||
this.validator = new Schema(rule);
|
|
||||||
this.inputs = target.find(':input');
|
|
||||||
}
|
|
||||||
|
|
||||||
public clearErrMsg():void {
|
|
||||||
this.inputs.removeClass('is-invalid');
|
|
||||||
this.inputs.removeClass('is-valid');
|
|
||||||
this.target.find('.invalid-feedback').detach();
|
|
||||||
}
|
|
||||||
|
|
||||||
public installChangeHandler():this{
|
|
||||||
this.inputs.on('change', ()=>{
|
|
||||||
void this.validate();
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async validate(): Promise<undefined | TypedValue> {
|
|
||||||
if(this.option?.preParse !== undefined){
|
|
||||||
this.option.preParse(this.target);
|
|
||||||
}
|
|
||||||
let rawValues = mergeKVArray(this.inputs.serializeArray());
|
|
||||||
let optMap: Map<string, string>;
|
|
||||||
if(this.option?.postParse !== undefined){
|
|
||||||
[rawValues, optMap] = this.option.postParse(rawValues, this.target);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
optMap = new Map();
|
|
||||||
}
|
|
||||||
console.log(rawValues);
|
|
||||||
|
|
||||||
const validateResult = await this.validator.validate(rawValues).catch(({fields }) => {
|
|
||||||
if(fields === undefined){
|
|
||||||
console.error('validator 에러, 조건 검사 구문을 확인하세요.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.clearErrMsg();
|
|
||||||
for(const rawKey of Object.keys(fields)){
|
|
||||||
let $item: JQuery<HTMLElement>;
|
|
||||||
const key = rawKey.split('.')[0];
|
|
||||||
console.log(`ErrorType: ${key}:${rawValues[key]}`);
|
|
||||||
const errMsg = fields[rawKey][0].message;
|
|
||||||
|
|
||||||
if(optMap.has(key)){
|
|
||||||
$item = this.target.find(optMap.get(key) as string);
|
|
||||||
}
|
|
||||||
else if(isArray(rawValues[key])){
|
|
||||||
$item = this.target.find(`:input[name='${key}[]']`);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
$item = this.target.find(`:input[name='${key}']`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if($item.length == 0){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const $error = $(`<span>${errMsg}</span>`);
|
|
||||||
|
|
||||||
$error.addClass( "invalid-feedback" );
|
|
||||||
|
|
||||||
if ("radio" == $item.prop( "type" )) {
|
|
||||||
const $target = $item.parents( ".btn-group" );
|
|
||||||
$error.insertAfter( $target );
|
|
||||||
$target.addClass('is-invalid');
|
|
||||||
}
|
|
||||||
else if ("checkbox" == $item.prop( "type" )) {
|
|
||||||
let $target = $item.parent( "label" );
|
|
||||||
if($target.parent(".btn-group").length){
|
|
||||||
$target = $target.parent('.btn-group');
|
|
||||||
}
|
|
||||||
$error.insertAfter( $target );
|
|
||||||
$target.addClass('is-invalid');
|
|
||||||
} else {
|
|
||||||
$error.insertAfter( $item );
|
|
||||||
$item.addClass('is-invalid');
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.inputs.each(function(){
|
|
||||||
const name = (this as HTMLInputElement).name;
|
|
||||||
if(name in fields){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.classList.add('is-valid');
|
|
||||||
});
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
if(validateResult === undefined){
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
this.clearErrMsg();
|
|
||||||
this.inputs.addClass('is-valid');
|
|
||||||
return validateResult as TypedValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { mb_strwidth } from "@util/mb_strwidth";
|
import { mb_strwidth } from ".//mb_strwidth";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mb_strimwidth
|
* mb_strimwidth
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
import type { ValuesOf } from "@/defs";
|
import type { ValuesOf } from "./defs";
|
||||||
import { zip } from "lodash-es";
|
import { zip } from "lodash-es";
|
||||||
|
|
||||||
export function merge2DArrToObjectArr<T extends Record<string, unknown>>(column: (keyof T)[], list: ValuesOf<T>[][]): T[]{
|
export function merge2DArrToObjectArr<T extends Record<string, unknown>>(column: (keyof T)[], list: ValuesOf<T>[][]): T[]{
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
/** @deprecated */
|
|
||||||
export function scrollHardTo(elementId: string): void {
|
|
||||||
const element = document.getElementById(elementId);
|
|
||||||
if(!element){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
element.scrollIntoView({
|
|
||||||
behavior: 'auto',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
export function scrollToSelector(selector: string): void {
|
|
||||||
const element = document.querySelector(selector);
|
|
||||||
if(!element){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
element.scrollIntoView({
|
|
||||||
behavior: 'auto',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { webcrypto } from "node:crypto";
|
||||||
|
|
||||||
|
const subtle = webcrypto.subtle;
|
||||||
|
|
||||||
|
export async function sha256(msg: ArrayBuffer): Promise<ArrayBuffer>{
|
||||||
|
return await subtle.digest('SHA-256', msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sha512(msg: ArrayBuffer): Promise<ArrayBuffer>{
|
||||||
|
return await subtle.digest('SHA-512', msg);
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Nullable } from "@util/Nullable";
|
import type { Nullable } from './Nullable';
|
||||||
import { NotNullExpected } from "@util/NotNullExpected";
|
import { NotNullExpected } from "./NotNullExpected";
|
||||||
|
|
||||||
export function unwrap<T>(result: Nullable<T>): T {
|
export function unwrap<T>(result: Nullable<T>): T {
|
||||||
if (result === null || result === undefined) {
|
if (result === null || result === undefined) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Nullable } from "@util/Nullable";
|
import type { Nullable } from ".//Nullable";
|
||||||
import { NotNullExpected } from "@util/NotNullExpected";
|
import { NotNullExpected } from ".//NotNullExpected";
|
||||||
|
|
||||||
|
|
||||||
export function unwrap_any<T>(result: Nullable<unknown>): T {
|
export function unwrap_any<T>(result: Nullable<unknown>): T {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Nullable } from "@util/Nullable";
|
import type { Nullable } from ".//Nullable";
|
||||||
|
|
||||||
type ErrType<T> = { new(msg?: string): T }
|
type ErrType<T> = { new(msg?: string): T }
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -24,8 +24,8 @@
|
|||||||
"strict": true,
|
"strict": true,
|
||||||
"importHelpers": true,
|
"importHelpers": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@util/*": ["server/util/*"],
|
"@util/*": ["./server/util/*"],
|
||||||
"@server/*": ["server/*"],
|
"@server/*": ["./server/*"],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,8 +100,8 @@
|
|||||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
"skipLibCheck": true, /* Skip type checking all .d.ts files. */
|
"skipLibCheck": true, /* Skip type checking all .d.ts files. */
|
||||||
"paths": {
|
"paths": {
|
||||||
"@util/*": ["./server/util/*"],
|
"@util/*": ["./util/*"],
|
||||||
"@server/*": ["./server/*"],
|
"@server/*": ["./*"],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
|
|||||||
Reference in New Issue
Block a user