diff --git a/config/webpack.config.dev.js b/config/webpack.config.dev.js index 58c749db..0625763d 100644 --- a/config/webpack.config.dev.js +++ b/config/webpack.config.dev.js @@ -224,8 +224,6 @@ module.exports = { inject: true, template: paths.appHtml, }), - // Add module names to factory functions so they appear in browser profiler. - new webpack.NamedModulesPlugin(), // Makes some environment variables available to the JS code, for example: // if (process.env.NODE_ENV === 'development') { ... }. See `./env.js`. new webpack.DefinePlugin(env.stringified), @@ -244,8 +242,10 @@ module.exports = { // by default due to how Webpack interprets its code. This is a practical // solution that requires the user to opt into importing specific locales. // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack - // You can remove this if you don't use Moment.js: - new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), + new webpack.IgnorePlugin({ + resourceRegExp: /^\.\/locale$/, + contextRegExp: /moment$/, + }), ], // Some libraries import Node modules but don't use them in the browser. // Tell Webpack to provide empty mocks for them so importing them works. diff --git a/package.json b/package.json index baf8ada1..7ca6280e 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "build": "npm run build:prod&&npm run build:esm", "start": "node scripts/start.js", "dist": "npm run build", - "dev": "cross-env NODE_ENV=watch webpack --mode development", + "dev": "cross-env NODE_ENV=watch webpack --mode development --watch", "test": "echo \"Error: no test specified\" && exit 0", "storybook": "start-storybook -p 9009", "build-storybook": "build-storybook" diff --git a/src/assets/entry/images/IcoTableLoadWeb.svg b/src/assets/entry/images/IcoTableLoadWeb.svg new file mode 100644 index 00000000..df239310 --- /dev/null +++ b/src/assets/entry/images/IcoTableLoadWeb.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/entry/scss/components/_TableAdd.scss b/src/assets/entry/scss/components/_TableAdd.scss index de0f98a8..575a58d5 100644 --- a/src/assets/entry/scss/components/_TableAdd.scss +++ b/src/assets/entry/scss/components/_TableAdd.scss @@ -6,6 +6,7 @@ -webkit-box-direction: normal; flex-direction: column; .file_add, + .load_web, .new_add_box { position: relative; padding: 46px 45px; @@ -57,6 +58,10 @@ line-height: 48px; } } + .load_web .dsc { + background-image: url('#{$imageDir}/IcoTableLoadWeb.svg'); + background-size: 64px auto; + } .new_add_box .dsc { background-image: url('#{$imageDir}/IcoNewTableAdd.svg'); background-size: 64px auto; @@ -143,5 +148,76 @@ } } } + .load_web { + .add_label { + top: 80px; + } + + .sheet_group { + display: flex; + gap: 16px; + margin-top: 40px; + + .sheet_doc_wrapper { + flex: 2; + } + + .sheet_name_wrapper { + flex: 1; + } + + .input_wrapper { + margin-top: 8px; + } + + .label { + em { + color: #4f80ff; + } + } + label { + color: #929292; + } + + input[type='text'] { + width: 100%; + height: 40px; + box-sizing: border-box; + padding: 0 16px; + border: 0; + border: 1px solid #e2e2e2; + border-radius: 4px; + line-height: 38px; + outline: 0; + letter-spacing: -0.3px; + background: #ffffff; + + &.error_input { + border-color: #ff0000; + } + } + + input[type='text']:focus, + input[type='text']:active { + border-color: #4f80ff; + } + + input::placeholder { + font-size: 14px; + font-weight: bold; + color: #979797; + line-height: 40px; + letter-spacing: -0.3px; + font-family: NanumGothicOTF, '나눔고딕', NanumGothic, '맑은 고딕', Malgun Gothic, + 'Apple SD Gothic Neo', '돋움', dotum, Helvetica, arial, sans-serif; + } + + .sheet_valid_error_msg { + margin-top: 8px; + color: #ff0000; + font-size: 14px; + } + } + } } diff --git a/src/assets/entry/scss/popup.scss b/src/assets/entry/scss/popup.scss index 02a5a42d..a0944cda 100644 --- a/src/assets/entry/scss/popup.scss +++ b/src/assets/entry/scss/popup.scss @@ -422,6 +422,8 @@ margin-top: 24px; border-radius: 6px; background-color: rgba(255, 255, 255, 0.6); + white-space: pre-line; + .caution { position: relative; padding-left: 24px; diff --git a/src/components/ledPicker/ledPicker.jsx b/src/components/ledPicker/ledPicker.jsx index 1936687d..6ad20d85 100644 --- a/src/components/ledPicker/ledPicker.jsx +++ b/src/components/ledPicker/ledPicker.jsx @@ -5,7 +5,9 @@ import { debounce } from 'lodash'; import root from 'window-or-global'; import produce from 'immer'; import Theme from '@utils/Theme'; -import Dropdown from '@components/widget/dropdown'; + +let timer = null; +let islongPress = false; class LedPicker extends Component { get PICKER_WIDTH() { @@ -166,7 +168,7 @@ class LedPicker extends Component { }, }; } - _handleLedStatusChange = ({ x, y, isReset, value }) => { + _handleLedStatusChange = ({ x, y, isReset, resetOne }) => { const { onChangeLedPicker, withLevel, maxBrightness } = this.props; const status = this.state.ledStatus; const targetBrightness = maxBrightness || 1; @@ -178,9 +180,13 @@ class LedPicker extends Component { } } else { if (withLevel) { - status[x][y] += 1; - if (status[x][y] > 9) { + if (resetOne) { status[x][y] = 0; + } else { + status[x][y] -= 1; + if (status[x][y] < 0) { + status[x][y] = 9; + } } } else { if (status[x][y] == targetBrightness) { @@ -201,6 +207,25 @@ class LedPicker extends Component { ); }; + _handleMouseDown = (x, y) => (e) => { + timer = setTimeout(() => { + this._handleLedStatusChange({ x, y, isReset: false, resetOne: true }); + islongPress = true; + }, 500); + }; + + _handleMouseUp = (x, y) => (e) => { + if (timer) { + clearTimeout(timer); + timer = null; + } + if (islongPress) { + islongPress = false; + } else { + this._handleLedStatusChange({ x, y, isReset: false }); + } + }; + render() { const { className, @@ -243,23 +268,18 @@ class LedPicker extends Component { {ledStatus.map((leds, x) => leds.map((led, y) => { const brightness = ledStatus[x][y]; - const targetStyle = this.theme[ - `led_item_selected_${brightness}` - ]; + const targetStyle = + this.theme[`led_item_selected_${brightness}`]; const key = `led${x}${y}`; return (
0 && - this.theme.led_item_selected} ${targetStyle}`} + className={`${this.theme.led_item} ${ + brightness > 0 && this.theme.led_item_selected + } ${targetStyle}`} key={key} - onClick={() => { - this._handleLedStatusChange({ - x, - y, - isReset: false, - }); - }} + onMouseDown={this._handleMouseDown(x, y)} + onMouseUp={this._handleMouseUp(x, y)} >
{brightness} @@ -276,8 +296,9 @@ class LedPicker extends Component { const brightness = ledStatus[x][y]; return (
0 && - this.theme.led_item_selected}`} + className={`${this.theme.led_item} ${ + brightness > 0 && this.theme.led_item_selected + }`} key={`led${x}${y}`} onClick={() => this._handleLedStatusChange({ diff --git a/src/components/ledPicker/ledPickerContainer.jsx b/src/components/ledPicker/ledPickerContainer.jsx index f8ef3d26..0b72f94d 100644 --- a/src/components/ledPicker/ledPickerContainer.jsx +++ b/src/components/ledPicker/ledPickerContainer.jsx @@ -20,11 +20,6 @@ const mapDispatchToProps = (dispatch) => ({ export default withWrapper({ type: 'microBitLedPicker', -})( - connect( - undefined, - mapDispatchToProps - )(LedPickerContainer) -); +})(connect(undefined, mapDispatchToProps)(LedPickerContainer)); // mapStateToProps, // mapDispatchToProps diff --git a/src/components/popup/Contents/LoadWeb/index.jsx b/src/components/popup/Contents/LoadWeb/index.jsx new file mode 100644 index 00000000..6b2829c1 --- /dev/null +++ b/src/components/popup/Contents/LoadWeb/index.jsx @@ -0,0 +1,161 @@ +import { useState } from 'react'; +import { connect } from 'react-redux'; +import classname from 'classnames'; +import { triggerEvent } from '@actions'; +import { closePopup } from '@actions/popup'; +import { EMIT_TYPES } from '@constants'; +import Theme from '@utils/Theme'; +import { setTimeout } from 'window-or-global'; +import { CommonUtils } from '@utils/Common'; + +const copyRightLink = + 'https://copyright.or.kr/education/educlass/learning/infringement-case/index.do'; + +const SheetIdValidMsg = [ + 'Menus.file_load_web_error_sheet_id_1', + 'Menus.file_load_web_error_sheet_id_2', +]; + +const Index = ({ type, loadWeb, HeaderButtonPortal }) => { + const theme = Theme.getStyle('popup'); + const [sheetId, setSheetId] = useState(''); + const [sheetName, setSheetName] = useState(''); + const [sheetIdValid, setSheetIdValid] = useState(null); + + const onChangeSheetId = (e) => { + setSheetIdValid(null); + setSheetId(e.target.value); + }; + + const onChangeSheetName = (e) => { + setSheetName(e.target.value); + }; + + const getLoadGoogleSheet = async () => { + if (!sheetId) { + setSheetIdValid(SheetIdValidMsg[0]); + return; + } + + setSheetIdValid(null); + + try { + const response = await fetch( + // eslint-disable-next-line max-len + `https://docs.google.com/spreadsheets/d/${sheetId}/gviz/tq?tqx=out:csv&sheet=${sheetName}` + ); + if (response.status !== 200) { + setSheetIdValid(SheetIdValidMsg[1]); + return; + } + const csv = await response.text(); + loadWeb(csv); + } catch (e) { + setSheetIdValid(SheetIdValidMsg[1]); + } + }; + + return ( +
+ <> +
+

+ {CommonUtils.getLang('Menus.file_load_web_table_title')} + {CommonUtils.getLang('Menus.file_load_web_table_sub_title')} +

+
+ {CommonUtils.getLang('Workspace.load_web')} +
+
+
+ +
+ +
+ {sheetIdValid && ( +
+ {CommonUtils.getLang(sheetIdValid)} +
+ )} +
+
+ +
+ +
+
+
+
+
+

+ {CommonUtils.getLang('Menus.file_load_web_warn_title_1')} +

+

+ {CommonUtils.getLang('Menus.file_load_web_warn_title_2')} +
+ + {CommonUtils.getLang('Menus.file_load_web_warn_desc_2_1')}{' '} + + + [{CommonUtils.getLang('Menus.file_upload_warn_link')}] + +

+
+ + + {CommonUtils.getLang('Buttons.add2')} + + + +
+ ); +}; + +const mapDispatchToProps = (dispatch) => ({ + loadWeb: (csv) => { + dispatch(closePopup()); + setTimeout(() => { + dispatch(triggerEvent(EMIT_TYPES.loadWeb, csv, false)); + }); + }, +}); + +export default connect(null, mapDispatchToProps)(Index); diff --git a/src/components/popup/index.jsx b/src/components/popup/index.jsx index bbcf3ed4..f0a9c619 100644 --- a/src/components/popup/index.jsx +++ b/src/components/popup/index.jsx @@ -8,6 +8,7 @@ import Select from './Contents/Select/index'; import FileUpload from './Contents/FileUpload/index'; import FileDragUpload from './Contents/FileDragUpload/index'; import WriteBox from './Contents/WriteBox'; +import LoadWeb from './Contents/LoadWeb/index'; import Draw from './Contents/Draw'; import Projects from './Contents/Projects/index'; import { DEFAULT_OPTIONS } from '../../constants'; @@ -63,7 +64,14 @@ class Popup extends Component { } setContent() { - const { opt = {}, writeBoxOption, data: dataObj, uploads: uploaded, type, popupAlertMessage } = this.property; + const { + opt = {}, + writeBoxOption, + data: dataObj, + uploads: uploaded, + type, + popupAlertMessage, + } = this.property; const { imageBaseUrl: expsnsionIconBaseUrl } = this.property; const { isDrawVector, multiSelect, showSelected, search, searchByType } = opt; const { navigation: selected } = this.state; @@ -120,6 +128,10 @@ class Popup extends Component { ); navigation = ; break; + case 'loadWeb': + view = ; + navigation = ; + break; case 'draw': view = ; navigation = ; diff --git a/src/constants/index.js b/src/constants/index.js index 705c7a7b..d4de6cb3 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -11,6 +11,7 @@ export const EMIT_TYPES = { fetchMore: 'fetchMore', close: 'close', write: 'write', + loadWeb: 'loadWeb', draw: 'draw', makeProject: 'makeproject', select: 'select', @@ -417,6 +418,9 @@ export const DEFAULT_OPTIONS = { dragUpload: { name: 'Workspace.upload', }, + loadWeb: { + name: 'Workspace.load_web', + }, draw: { name: 'Workspace.draw_new_table', },