Initial commit: 교육 프로젝트 배포
This commit is contained in:
@@ -0,0 +1,414 @@
|
||||
/**
|
||||
* 설정 관리 모듈
|
||||
* @module ConfigManager
|
||||
*/
|
||||
class ConfigManager {
|
||||
constructor(defaults = {}) {
|
||||
this.defaults = defaults;
|
||||
this.config = { ...defaults };
|
||||
}
|
||||
|
||||
/**
|
||||
* 설정 값 가져오기
|
||||
* @param {string} key - 키 (점 표기법 지원)
|
||||
* @param {*} defaultValue - 기본값
|
||||
* @returns {*}
|
||||
*/
|
||||
get(key, defaultValue = null) {
|
||||
return this._getNestedValue(this.config, key, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 설정 값 설정
|
||||
* @param {string} key - 키 (점 표기법 지원)
|
||||
* @param {*} value - 값
|
||||
*/
|
||||
set(key, value) {
|
||||
this._setNestedValue(this.config, key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 여러 설정 값 설정
|
||||
* @param {Object} config - 설정 객체
|
||||
*/
|
||||
setMultiple(config) {
|
||||
this.config = this._deepMerge(this.config, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 설정 값 삭제
|
||||
* @param {string} key - 키
|
||||
*/
|
||||
remove(key) {
|
||||
this._deleteNestedValue(this.config, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 설정 값 존재 확인
|
||||
* @param {string} key - 키
|
||||
* @returns {boolean}
|
||||
*/
|
||||
has(key) {
|
||||
return this._getNestedValue(this.config, key) !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 모든 설정 가져오기
|
||||
* @returns {Object}
|
||||
*/
|
||||
getAll() {
|
||||
return { ...this.config };
|
||||
}
|
||||
|
||||
/**
|
||||
* 설정 초기화
|
||||
*/
|
||||
reset() {
|
||||
this.config = { ...this.defaults };
|
||||
}
|
||||
|
||||
/**
|
||||
* 기본값으로 병합
|
||||
* @param {Object} config - 설정 객체
|
||||
* @returns {Object}
|
||||
*/
|
||||
mergeWithDefaults(config) {
|
||||
return this._deepMerge({ ...this.defaults }, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON 문자열로 변환
|
||||
* @returns {string}
|
||||
*/
|
||||
toJSON() {
|
||||
return JSON.stringify(this.config, null, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON 문자열에서 로드
|
||||
* @param {string} json - JSON 문자열
|
||||
*/
|
||||
fromJSON(json) {
|
||||
try {
|
||||
const parsed = JSON.parse(json);
|
||||
this.config = this._deepMerge({ ...this.defaults }, parsed);
|
||||
} catch (error) {
|
||||
console.error("Failed to parse JSON:", error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 로컬 스토리지에 저장
|
||||
* @param {string} key - 저장 키
|
||||
*/
|
||||
saveToStorage(key = "app_config") {
|
||||
try {
|
||||
localStorage.setItem(key, JSON.stringify(this.config));
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Failed to save to storage:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 로컬 스토리지에서 로드
|
||||
* @param {string} key - 저장 키
|
||||
*/
|
||||
loadFromStorage(key = "app_config") {
|
||||
try {
|
||||
const stored = localStorage.getItem(key);
|
||||
if (stored) {
|
||||
const parsed = JSON.parse(stored);
|
||||
this.config = this._deepMerge({ ...this.defaults }, parsed);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error("Failed to load from storage:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 중첩된 객체 값 가져오기
|
||||
* @private
|
||||
*/
|
||||
_getNestedValue(obj, key, defaultValue = null) {
|
||||
const keys = key.split(".");
|
||||
let value = obj;
|
||||
|
||||
for (const k of keys) {
|
||||
if (value && typeof value === "object" && k in value) {
|
||||
value = value[k];
|
||||
} else {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
return value !== undefined ? value : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 중첩된 객체 값 설정
|
||||
* @private
|
||||
*/
|
||||
_setNestedValue(obj, key, value) {
|
||||
const keys = key.split(".");
|
||||
const lastKey = keys.pop();
|
||||
let target = obj;
|
||||
|
||||
for (const k of keys) {
|
||||
if (!(k in target) || typeof target[k] !== "object") {
|
||||
target[k] = {};
|
||||
}
|
||||
target = target[k];
|
||||
}
|
||||
|
||||
target[lastKey] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 중첩된 객체 값 삭제
|
||||
* @private
|
||||
*/
|
||||
_deleteNestedValue(obj, key) {
|
||||
const keys = key.split(".");
|
||||
const lastKey = keys.pop();
|
||||
let target = obj;
|
||||
|
||||
for (const k of keys) {
|
||||
if (!(k in target) || typeof target[k] !== "object") {
|
||||
return;
|
||||
}
|
||||
target = target[k];
|
||||
}
|
||||
|
||||
delete target[lastKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* 깊은 병합
|
||||
* @private
|
||||
*/
|
||||
_deepMerge(target, source) {
|
||||
const output = { ...target };
|
||||
|
||||
if (this._isObject(target) && this._isObject(source)) {
|
||||
Object.keys(source).forEach((key) => {
|
||||
if (this._isObject(source[key])) {
|
||||
if (!(key in target)) {
|
||||
output[key] = source[key];
|
||||
} else {
|
||||
output[key] = this._deepMerge(target[key], source[key]);
|
||||
}
|
||||
} else {
|
||||
output[key] = source[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* 객체 확인
|
||||
* @private
|
||||
*/
|
||||
_isObject(item) {
|
||||
return item && typeof item === "object" && !Array.isArray(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 앱 설정 관리 (싱글톤)
|
||||
*/
|
||||
class AppConfig extends ConfigManager {
|
||||
static instance = null;
|
||||
|
||||
constructor(defaults = {}) {
|
||||
if (AppConfig.instance) {
|
||||
return AppConfig.instance;
|
||||
}
|
||||
|
||||
super({
|
||||
app: {
|
||||
name: "My App",
|
||||
version: "1.0.0",
|
||||
debug: false,
|
||||
},
|
||||
ui: {
|
||||
theme: "light",
|
||||
language: "ko",
|
||||
animations: true,
|
||||
},
|
||||
features: {
|
||||
search: true,
|
||||
notifications: true,
|
||||
autoSave: true,
|
||||
},
|
||||
...defaults,
|
||||
});
|
||||
|
||||
AppConfig.instance = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 싱글톤 인스턴스 가져오기
|
||||
* @returns {AppConfig}
|
||||
*/
|
||||
static getInstance() {
|
||||
if (!AppConfig.instance) {
|
||||
AppConfig.instance = new AppConfig();
|
||||
}
|
||||
return AppConfig.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 앱 이름 가져오기
|
||||
* @returns {string}
|
||||
*/
|
||||
getAppName() {
|
||||
return this.get("app.name");
|
||||
}
|
||||
|
||||
/**
|
||||
* 앱 버전 가져오기
|
||||
* @returns {string}
|
||||
*/
|
||||
getAppVersion() {
|
||||
return this.get("app.version");
|
||||
}
|
||||
|
||||
/**
|
||||
* 디버그 모드 확인
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isDebugMode() {
|
||||
return this.get("app.debug", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 테마 가져오기
|
||||
* @returns {string}
|
||||
*/
|
||||
getTheme() {
|
||||
return this.get("ui.theme", "light");
|
||||
}
|
||||
|
||||
/**
|
||||
* 테마 설정
|
||||
* @param {string} theme - 테마
|
||||
*/
|
||||
setTheme(theme) {
|
||||
this.set("ui.theme", theme);
|
||||
this.saveToStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* 언어 가져오기
|
||||
* @returns {string}
|
||||
*/
|
||||
getLanguage() {
|
||||
return this.get("ui.language", "ko");
|
||||
}
|
||||
|
||||
/**
|
||||
* 언어 설정
|
||||
* @param {string} language - 언어
|
||||
*/
|
||||
setLanguage(language) {
|
||||
this.set("ui.language", language);
|
||||
this.saveToStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* 애니메이션 사용 여부
|
||||
* @returns {boolean}
|
||||
*/
|
||||
useAnimations() {
|
||||
return this.get("ui.animations", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 기능 활성화 여부
|
||||
* @param {string} feature - 기능 이름
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isFeatureEnabled(feature) {
|
||||
return this.get(`features.${feature}`, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 기능 토글
|
||||
* @param {string} feature - 기능 이름
|
||||
*/
|
||||
toggleFeature(feature) {
|
||||
const current = this.isFeatureEnabled(feature);
|
||||
this.set(`features.${feature}`, !current);
|
||||
this.saveToStorage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML data 속성에서 설정 로드
|
||||
*/
|
||||
class DataAttributeConfig {
|
||||
/**
|
||||
* 요소에서 설정 로드
|
||||
* @param {Element} element - 대상 요소
|
||||
* @param {string} prefix - 속성 접두사
|
||||
* @returns {Object}
|
||||
*/
|
||||
static loadFromElement(element, prefix = "data-") {
|
||||
if (!element) return {};
|
||||
|
||||
const config = {};
|
||||
const attributes = element.attributes;
|
||||
|
||||
for (let i = 0; i < attributes.length; i++) {
|
||||
const attr = attributes[i];
|
||||
if (attr.name.startsWith(prefix)) {
|
||||
const key = attr.name
|
||||
.substring(prefix.length)
|
||||
.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
||||
config[key] = this._parseValue(attr.value);
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 값 파싱
|
||||
* @private
|
||||
*/
|
||||
static _parseValue(value) {
|
||||
// boolean
|
||||
if (value === "true") return true;
|
||||
if (value === "false") return false;
|
||||
|
||||
// number
|
||||
if (!isNaN(value) && value !== "") {
|
||||
return parseFloat(value);
|
||||
}
|
||||
|
||||
// JSON
|
||||
if ((value.startsWith("{") || value.startsWith("[")) &&
|
||||
(value.endsWith("}") || value.endsWith("]"))) {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (e) {
|
||||
// JSON 파싱 실패 시 문자열 반환
|
||||
}
|
||||
}
|
||||
|
||||
// string
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
// ES6 모듈 내보내기
|
||||
if (typeof module !== "undefined" && module.exports) {
|
||||
module.exports = { ConfigManager, AppConfig, DataAttributeConfig };
|
||||
}
|
||||
Reference in New Issue
Block a user