diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30bc162 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/node_modules \ No newline at end of file diff --git a/data.js b/data.js new file mode 100644 index 0000000..8a43e5d --- /dev/null +++ b/data.js @@ -0,0 +1,32 @@ +module.exports = [ + { + name: '자바스크립트 공부하기', + tags: ['programming', 'javascript'], + status: 'todo', + id: 12123123 + }, + { + name: '그림 그리기', + tags: ['picture', 'favorite'], + status: 'doing', + id: 35435345 + }, + { + name: '꽃구경하기', + tags: ['flower', 'favorite'], + status: 'done', + id: 7657 + }, + { + name: '저녁식사', + tags: ['dinner', 'food'], + status: 'todo', + id: 097989 + }, + { + name: '커피마시기', + tags: ['coffee', 'favorite'], + status: 'doing', + id: 65464 + } +]; diff --git a/design/pic1.jpg b/design/pic1.jpg new file mode 100644 index 0000000..ee14737 Binary files /dev/null and b/design/pic1.jpg differ diff --git a/design/pic2.jpg b/design/pic2.jpg new file mode 100644 index 0000000..dd2796a Binary files /dev/null and b/design/pic2.jpg differ diff --git a/oop/app.js b/oop/app.js new file mode 100644 index 0000000..69b5db5 --- /dev/null +++ b/oop/app.js @@ -0,0 +1,25 @@ +const readline = require('readline'); +const ValidCheck = module.require('./validCheck'); +const Todo = module.require('./todo.js'); +const todoList = module.require('./data.js'); +const Log = module.require('./log.js'); + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}); +rl.setPrompt('명령하세요 : '); + +rl.prompt(); + +const myLog = new Log(); +const myTodo = new Todo(todoList, rl); + +rl.on('line', input => { + sendCommand(input); +}); + +const sendCommand = input => { + const myValidCheck = new ValidCheck(myTodo, myLog); + myValidCheck.parseCommand(input); +}; diff --git a/oop/data.js b/oop/data.js new file mode 100644 index 0000000..cf6697b --- /dev/null +++ b/oop/data.js @@ -0,0 +1,32 @@ +module.exports = [ + { + name: '자바스크립트 공부하기', + tags: ['programming', 'javascript'], + status: 'todo', + id: 12123123 + }, + { + name: '그림 그리기', + tags: ['picture', 'favorite'], + status: 'doing', + id: 312323 + }, + { + name: '고양이 모래 주문하기', + tags: ['cat', 'shopping'], + status: 'todo', + id: 78956 + }, + { + name: '밤코하기', + tags: ['study', 'favorite'], + status: 'done', + id: 46588 + }, + { + name: '워홀가기', + tags: ['travel', 'favorite'], + status: 'todo', + id: 19846 + } +]; diff --git a/oop/errorMessage.js b/oop/errorMessage.js new file mode 100644 index 0000000..acc5c68 --- /dev/null +++ b/oop/errorMessage.js @@ -0,0 +1,7 @@ +module.exports = { + COMMAND_ERROR: '명령어가 올바르지 않습니다.', + ID_ERROR: 'ID가 존재하지 않습니다.', + STATUS_ERROR: '같은 상태로 변경할 수 없습니다.', + UNDO_ERROR: '더 이상 되돌릴 수 없습니다.', + REDO_ERROR: '더 이상 되돌리기를 취소할 수 없습니다.' +}; diff --git a/oop/log.js b/oop/log.js new file mode 100644 index 0000000..d52f142 --- /dev/null +++ b/oop/log.js @@ -0,0 +1,87 @@ +module.exports = Log = function(undoLimit = 3, defaultIndex = -1) { + (this.queue = []), (this.index = defaultIndex), (this.undoLimit = undoLimit); +}; + +Log.prototype.getIndex = function() { + return this.index; +}; + +Log.prototype.getLength = function() { + return this.queue.length; +}; + +Log.prototype.addLog = function(logDataObject) { + if (this.queue.length > this.undoLimit + 1) { + this.queue.shift(); + } + + while (this.queue.length > this.index + 1) { + this.queue.pop(); + } + + this.queue[++this.index] = { + action: logDataObject.action, + prevData: logDataObject.prevData, + nextData: logDataObject.nextData, + todoListIndex: logDataObject.todoListIndex + }; +}; + +Log.prototype.undo = function() { + const action = this.queue[this.index].action; + const prevData = this.queue[this.index].prevData; + const nextData = this.queue[this.index].nextData; + const todoListIndex = this.queue[this.index].todoListIndex; + console.log( + `"${nextData.id}"번 항목 '${nextData.name}'이(가) ${nextData.status} 에서 ${prevData.status}로 변경되었습니다.` + ); + this.index--; + if (action === 'add') { + return { + todoListIndex, + deleteCount: 1 + }; + } else if (action === 'delete') { + return { + todoListIndex, + deleteCount: 0, + data: prevData + }; + } else if (action === 'update') { + return { + todoListIndex, + deleteCount: 1, + data: prevData + }; + } +}; + +Log.prototype.redo = function() { + this.index++; + const action = this.queue[this.index].action; + const prevData = this.queue[this.index].prevData; + const nextData = this.queue[this.index].nextData; + const todoListIndex = this.queue[this.index].todoListIndex; + + console.log( + `"${nextData.id}"번 항목 '${nextData.name}'이(가) ${prevData.status} 에서 ${nextData.status}로 변경되었습니다.` + ); + if (action === 'add') { + return { + todoListIndex, + deleteCount: 0, + data: nextData + }; + } else if (action === 'delete') { + return { + todoListIndex, + deleteCount: 1 + }; + } else if (action === 'update') { + return { + todoListIndex, + deleteCount: 1, + data: nextData + }; + } +}; diff --git a/oop/todo.js b/oop/todo.js new file mode 100644 index 0000000..08002aa --- /dev/null +++ b/oop/todo.js @@ -0,0 +1,122 @@ +require('date-utils'); + +const commonDelaySecond = 1000; +const updateDelaySecond = 3000; + +module.exports = Todo = function(todoList, readline) { + this.todoList = todoList; + this.readline = readline; +}; + +Todo.prototype.show = function(command) { + if (command === 'all') { + this.printAll(); + } else { + this.printList(command); + } +}; + +Todo.prototype.getStatusList = function() { + const listOfStatus = this.todoList.reduce((acc, cur) => { + if (acc[cur.status] === undefined) { + acc[cur.status] = [cur.name]; + } else { + acc[cur.status].push(cur.name); + } + return acc; + }, {}); + return listOfStatus; +}; + +Todo.prototype.printAll = function() { + const listOfStatus = this.getStatusList(); + let printResult = []; + for (let status in listOfStatus) { + printResult.push(`${status} : ${listOfStatus[status].length}개`); + } + console.log(`현재상태 : ${printResult.join(', ')}`); + this.readline.prompt(); +}; + +Todo.prototype.printList = function(status) { + const listOfStatus = this.getStatusList(); + const statusList = listOfStatus[status]; + console.log(`${status}리스트 : 총 ${statusList.length} 건 : ${statusList.join(', ')}`); + this.readline.prompt(); +}; + +Todo.prototype.add = function(name, tag) { + const id = this.getId(); + const newData = { + name: name, + tag, + status: 'todo', + id: id + }; + this.todoList.push(newData); + const prevData = Object.assign({}, newData); + prevData.status = '삭제'; + console.log(`${newData.name} 1개가 추가되었습니다. (id : ${newData.id})`); + + setTimeout(() => { + this.printAll(); + }, commonDelaySecond); + return { action: 'add', prevData, nextData: newData, todoListIndex: this.todoList.length - 1 }; +}; + +Todo.prototype.getId = function() { + const id = new Date(); + return Number(id.toFormat('YYMMDDHH24MISS')); +}; + +Todo.prototype.checkValidId = function(id) { + let index; + const targetData = this.todoList.filter((element, innerIndex) => { + if (Number(id) === element.id) { + index = innerIndex; + return Number(id) === element.id; + } + }); + + if (targetData[0] === undefined) { + return false; + } + return index; +}; + +Todo.prototype.getStatus = function(index) { + return this.todoList[index].status; +}; + +Todo.prototype.delete = function(index) { + const deletingData = this.todoList[index]; + console.log(`${deletingData.name}가 ${deletingData.status}에서 삭제됐습니다.`); + const nextData = Object.assign({}, this.todoList[index]); + nextData.status = '삭제'; + this.todoList.splice(index, 1); + setTimeout(() => { + this.printAll(); + }, commonDelaySecond); + return { action: 'delete', prevData: deletingData, nextData, todoListIndex: index }; +}; + +Todo.prototype.update = function(index, status) { + const prevData = Object.assign({}, this.todoList[index]); + this.todoList[index].status = status; + + setTimeout(() => { + console.log(`"${this.todoList[index].name}"가(이) ${status}로 변경되었습니다.`); + setTimeout(() => { + this.printAll(); + }, commonDelaySecond); + }, updateDelaySecond); + return { action: 'update', prevData, nextData: this.todoList[index], todoListIndex: index }; +}; + +Todo.prototype.undoAndRedo = function(obj) { + if (obj.data == undefined) { + this.todoList.splice(obj.todoListIndex, obj.deleteCount); + } else { + this.todoList.splice(obj.todoListIndex, obj.deleteCount, obj.data); + } +}; diff --git a/oop/validCheck.js b/oop/validCheck.js new file mode 100644 index 0000000..1d5217b --- /dev/null +++ b/oop/validCheck.js @@ -0,0 +1,107 @@ +const errorMessage = module.require('./errorMessage.js'); + +module.exports = class ValidCheck { + constructor(todo, log) { + (this.todo = todo), (this.log = log); + } + parseCommand(input) { + try { + const inputArray = input.split('$'); + const action = inputArray.splice(0, 1)[0]; + const condition = inputArray; + const regExp = /^show$|^add$|^delete$|^update$|^undo$|^redo$/; + const matchRegExp = action.match(regExp); + + if (matchRegExp === null) { + throw new Error('COMMAND_ERROR'); + } else { + this[`${action}Check`](...condition); + } + } catch (error) { + console.log(errorMessage[error.message]); + this.todo.readline.prompt(); + } + } + + excuteTodo(action, ...condition) { + if (action === 'show') { + this.todo[action](...condition); + return; + } + this.log.addLog(this.todo[action](...condition)); + } + + excuteLog(action) { + this.todo.undoAndRedo(this.log[action]()); + this.todo.readline.prompt(); + } + + showCheck(element) { + const regExp = /^all$|^todo$|^doing$|^done$/; + const command = element.match(regExp); + if (command === null) { + throw new Error('COMMAND_ERROR'); + } + this.excuteTodo('show', command[0]); + } + + addCheck(name, tag) { + if (name === undefined || tag === undefined || name === '' || tag === '') { + throw new Error('COMMAND_ERROR'); + } + const tagResult = tag.replace(/\[|\'|\"|\]/g, '').split(','); + + this.excuteTodo('add', name, tagResult); + } + + deleteCheck(id) { + const validIndex = this.todo.checkValidId(id); + + if (validIndex === false) { + throw new Error('ID_ERROR'); + } + + this.excuteTodo('delete', validIndex); + } + + updateCheck(id, status) { + if (status === undefined) { + throw new Error('COMMAND_ERROR'); + } + + const validIndex = this.todo.checkValidId(id); + if (validIndex === false) { + throw new Error('ID_ERROR'); + } + + const regExp = /^todo$|^doing$|^done$/; + const matchRegExp = status.match(regExp); + + if (matchRegExp === null) { + throw new Error('COMMAND_ERROR'); + } else if (this.todo.getStatus(validIndex) === status) { + throw new Error('STATUS_ERROR'); + } + + this.excuteTodo('update', validIndex, status); + } + + undoCheck() { + const index = this.log.getIndex(); + if (index < 0) { + throw new Error('UNDO_ERROR'); + } + + this.excuteLog('undo'); + } + + redoCheck() { + const index = this.log.getIndex(); + const length = this.log.getLength(); + if (index >= length - 1) { + throw new Error('REDO_ERROR'); + } + + this.excuteLog('redo'); + } +}; diff --git a/todos.js b/todos.js new file mode 100644 index 0000000..47e02aa --- /dev/null +++ b/todos.js @@ -0,0 +1,54 @@ +const todos = require('./data'); + +class Todo { + constructor(todos) { + this.todos = todos; + this.todoCount = this.setTodoCount(todos); + } + + setTodoCount(todos) { + return todos.reduce((acc, cur) => { + acc[cur.status] = acc[cur.status] === undefined ? [cur.name] : acc[cur.status].concat(cur.name); + return acc; + }, {}); + } + + show(type, condition) { + let result; + if (type === 'status') { + if (condition === 'all') { + result = this.printAll(); + } else { + result = this.printStatus(condition); + } + } else { + result = this.printTags(condition); + } + console.log(result); + } + + printAll() { + return Object.entries(this.todoCount).reduce((acc, cur) => { + return (acc += `${cur[0]}: ${cur[1].length}개 `); + }, '현재상태 : '); + } + + printStatus(status) { + return `${status}리스트 총 ${this.todoCount[status].length}건 : ${this.todoCount[status].join(', ')}`; + } + + printTags(tag) { + const tagArr = this.todos.filter(todo => todo.tags.includes(tag)).map(obj => obj.name); + return `${tag} 키워드 검색 결과 : ${tagArr.join(', ')}`; + } +} + +const todo = new Todo(todos); + +todo.show('status', 'all'); +todo.show('status', 'todo'); +todo.show('status', 'doing'); +todo.show('status', 'done'); +todo.show('tag', 'favorite'); +todo.show('tag', 'food'); +todo.show('tag', 'javascript');