
/*=================================================================================================
 * MARI HOME   ---------------"--<@
 *-------------------------------------------------------------------------------------------------
 * @코드 명칭 : 마리 멀티 업로더 ( 가제 ) 
 * @요약 정보 : 업로더에 관련된 중요 코드를 등록 호출
 * @파일 이름 : multi_uploader_code.js
 * @웹용 주소 : 글쓰기등 업로드가 필요한 곳, 파일 탐색기와 같이 파일 미리보기가 필요한 곳에서 외부 파일(js파일)로 호출됨 
 * @호출 파일 : multi_uploader.js 에 의해 init() 호출 
 * @서버 위치 : 마리홈 ( ),  그누보드 ( plugin/multi_uploader/ )
 * @소스 제작 : repter ( 예뜨락 ), 홈페이지 ( https://marihome.net ) 
 * @제작 날짜 : 2023.08.01 ( 최초 제작 시작일 )
 * @소스 설명 : 업로더에 관련된 파일들의 코드들을 스프레드 시트 연산자를 통해 등록후 실행한다
 *           코드의 중심 역할을 한다( mime_types.js 외 나머지 파일들은 이곳에서 파생되어 파일화 되었다 )
 * @기타 사항 : 당분간 필요한 업그레이드를 통해서 소스 변경이 자주 일어날 수 있습니다 
 *           자바스크립트 형태 : 객체 리터럴 ( Object Literal )
 *           변수 => 프로퍼티 (Property) : 프로퍼티 값
 *           함수 => 메서드 (Method)
 * @최종 수정 : 2025.03.26
 * @버전 명시 : 0.9
 *-------------------------------------------------------------------------------------------------
 * Copyright 2023. repter All Rights Reserved.
**===============================================================================================*/ 

// 글로벌 변수 지정( 공간 확보 )
(function(global) {
    global.MARI = global.MARI || {};
    global.MARI.file_uploder = global.MARI.file_uploder || {};
})(window || global);

if(typeof(window.MARI.file_uploder.code) === "undefined") {
    MARI.file_uploder.code  = {

        /*=========================================================================================
         * 0.  객체의 모든 속성을 새로운 객체로 복사한 뒤( 스프레드 연산자를 사용 ), 
         *     그 객체를 MARI.file_uploder.code에 할당해서 code에 있는것 처럼 사용한다
        **---------------------------------------------------------------------------------------*/ 
        ...MARI.file_uploder.langObj,     // helper를 할당  ( lang/language.js )
        ...MARI.file_uploder.helper,      // helper를 할당  ( multi_uploader_helper.js )
        ...MARI.file_uploder.setData,     // setData를 할당 ( multi_uploader_setDataAttribute.js )
        ...MARI.file_uploder.layout,      // 레이아웃  할당   ( multi_uploader_layout.js )
        ...MARI.file_uploder.dragAction,  // 드래그 관련 할당  ( multi_uploader_dragAction.js ) 


        /*=========================================================================================
         * 사용자 정의 변수
        **---------------------------------------------------------------------------------------*/
        moduleUrl            : '/g5/plugin/multi_uploader', // 자신의 그누보드 환경에 맞게 
        input_obj            : `
                               input[name='bf_multi_file[]'], input[name='cnt_multi_file[]'],
                               input[name='cnt_multi_file'], input[name='cmt_multi_file']
                               `,                     // 템플릿 리터럴: 백틱으로 감싸면 여러 줄을 작성할 수 있습니다( 코드 가독성을 위해... )
        limitTotalSize       : 80,                    // 전체 업로드 리미트 사이즈 ( 메가 바이트 )       :: gnu_multi_uploader_write_skin.php에서 지정
		limitOneSize         : 40,                    // 파일 하나당 업로드시 리미트 사이즈 ( 메가 바이트 ) :: gnu_multi_uploader_write_skin.php에서 지정 
        limit_upload_num     : 100,                   // 100개 :: 파일 전체 업로드 리미트 갯수         :: gnu_multi_uploader_write_skin.php에서 지정
        PAD                  : 26,                    // 26 미리보기 이미지 위치 패딩값 20인데 그림자 번지는 값까지 어느 정도 더해서 26으로 
        maxFileNameLength    : 255,                   // 파일명 최대 길이
		use_selection        : 'Yes',                 // 드래그 선택 사용 유무         ( Yes =>사용, No =>사용 안함 ) 
        fileIconUse          : 'Yes',                 // 파일명 앞에 파일 아이콘 사용 유무 ( Yes =>사용, No =>사용 안함 )
		file_info_layer_use  : 'Yes',                 // 파일 정보 레이어 사용 여부      ( Yes =>사용, No =>사용 안함 ) 
		use_full_screen      : 'No',                  // 풀스크린을 사용 종류 ( Yes => 윈도우 풀스크린을 사용, No => 문서 최대 크기 사용 )
		exit_screen          : 'full_window',         // 풀스크린을 닫을시   (use_full_screen => Yes여야 작동   normal => 문서 노멀로 바로 변경, full_window => 윈도우 풀스크린 -> 문서 최대 -> 문서 노멀로 단계별 변경 )
        viewrDefaultWidth    : '277px',               // 277px 미리보기, 상세보기 창의 처음 로드시 기본 넓이 ( min_preview_wd ~  (100% - max_preview_wd) )
        min_preview_wd       : '277px',               // 277px 파일 미리보기 창 최소 넓이 ( 포토샵으로 넓이 재었음 )
        max_preview_wd       : '115px',               // 미리보기 창 최대한 늘렸을 경우의 넓이 ( 전체 넓이에서 지정한(115px) 픽셀 뺀 나머지가 미리보기 창 크기가 됨 )
        defaultExtType       : 'type2',               // 되도록 type2 추천 ( type1 => defaultExtensions1, type2 => defaultExtensions2 )
        defaultExtensions1   : ['*/*'],               // 기본 업로드 파일 필터 :: 모든 파일을 받는다   ( type1 되도록 사용 금지 )
        defaultExtensions2   : [                      // 기본 업로드 파일 필터 :: 지정한 파일만 받는다  ( type2 사용 추천 ) 
            /*
            // 1.모든 이미지 파일 ( image )
            'image/*',
            // 2.특정 이미지 파일 ( image )
            '.jpg, .jpeg, .png, .gif, .bmp, .webp, .svg, .tiff, .ico',
            // 3.모든 비디오 파일 ( video )
            'video/*',
            // 4.특정 비디오 파일 ( video ) 
            '.mp4, .avi, .mov, .mkv, .webm, .flv, .wmv, .ogv',
            // 5.모든 오디오 파일 ( audios )  
            'audio/*',
            // 6.특정 오디오 파일 ( audios )  
            '.mp3, .wav, .ogg, .aac, .flac, .m4a',
            // 7.모든 문서 파일 ( document )
            '.pdf, .doc, .docx, .xls, .xlsx, .ppt, .pptx, .txt, .csv, .rtf, .odt',
            // 8.특정 문서 파일  ( document )
            '.pdf, .txt',
            // 9.모든 압축 파일 ( archive )    
            '.zip, .rar, .7z, .tar, .gz, .bz2',
            // 10.특정 압축 파일 ( archive )    
            '.zip, .rar',
            // 11.모든 코드 파일 ( code )
            '.html, .css, .js, .json, .xml, .py, .java, .c, .cpp, .php, .rb, .ts',
            // 12.특정 코드 파일 ( code )
            '.html, .css, .js',
            // 13.모든 실행 파일 ( executable )
            '.exe, .bat, .sh, .app',
            // 14.특정 실행 파일 ( executable )
            '.exe, .sh',
            // 15.여러 개 조합 (multiple)
            'image/*, video/*, .pdf, .zip',
            */
            /*=====================================================================================
             * 위에 설명된 사항을 적절히 조합하거나 복사해서 아래에 넣어 사용하세요
             * 
            **-----------------------------------------------------------------------------------*/
            // 'image/*, video/*, .pdf, .zip','.txt, .html',                           // 여러 개 조합
            '.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg', '.tiff', '.ico', // 특정 이미지 파일
        ],
        defaultShape         : 'shape3',   // 기본 모양        
        debergView           : 'No',       // ( Yes, No ) 개발자 창에 디버깅 표시 디버깅용 레이어서 관리( 로컬 스토리지 ) 
        imgMinZoomValue      : 1,          // 미리보기에서 이미지 최소 축소 퍼센테이지
        imgMaxZoomValue      : 100,        // 미리보기에서 이미지 최대 확대 퍼센테이지
        reName               : true,       // 중복된 파일이 있을 경우 새로운 이름으로 업로드 false일 경우 업로드 금지
        resizeWdHtAct        : true,       // 우 하단 모서리 높이 넓이 리사이즈 핸들바로 넓이 늘리기 true, false 
		resizeWidthAct       : true,       // 우측 리사이즈 핸들바로 넓이 늘리기 true, false
		resizeHeightAct      : true,       // 하단 리사이즈 핸들바로 높이 늘리기 true, false
        focusPanelHideAct    : true,       // 포커스 판넬 감춤을 사용함 :: 디버깅등이 필요해서 계속 보여져야 할 때 사용
        loaderType           : [           // 로딩중 모양  
                                 'type1',  // shape1 로딩중 모양  css loader type1
                                 'type1',  // shape2 로딩중 모양  
                                 'type1',  // shape3 로딩중 모양 
                                 'type2',  // shape4 로딩중 모양 
                                 'type2',  // shape5 로딩중 모양  css loader type2
                                 'type2',  // shape6 로딩중 모양 
                                 'type1',  // shape7 로딩중 모양 
                                 'type1',  // shape8 로딩중 모양 
        ],
        upWheelNum : [ // 미리보기 휠업시 배율 :: 좀 더 가깝게 만들기 위해서 적당한 수열 공식이 없어서 배열로 만들어 대입
            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 
            21, 24, 26, 29, 31, 35, 38, 42, 46, 51, 56, 61, 67, 74, 81, 
            90, 100, 110, 121, 133, 146, 161, 177, 195, 214, 236, 259, 
            285, 314, 345, 380, 418, 459, 505, 556, 612, 673, 740, 800
        ],
        downWheelNum : [ // 미리보기 휠다운시 배율 :: 
            800, 727, 661, 601, 546, 497, 452, 411, 373, 339,
            308, 280, 255, 232, 211, 192, 174, 158, 144, 131,
            119, 108, 100, 91, 83, 75, 68, 62, 56, 51, 47, 42,
            39, 35, 32, 29, 26, 24, 22, 20, 18, 16, 15, 14, 12, 11,
            10, 9, 8, 7, 6, 5, 4, 3, 2, 1
        ],
        moduleBgUse          : 'N',        // 모듈 백그라운드 이미지 사용 여부 Y => 사용 N => 사용 안함
        allChoice            : 'N',        // 파일이 있을 경우 로드후 리스트 모두 선택 :  Y => 사용 N => 사용 안함
        openViewDIv          : 'Y',        // 파일이 있을 경우 로드후 미리보기 열기 :  Y => 사용 N => 사용 안함
        
        
        




        /*=========================================================================================
         * 이후 수정 금지
        **---------------------------------------------------------------------------------------*/
        country              : null,       // 사용자가 사용하는 언어
 	    eq_idx               : null,       // 사용중인 업로더의 인덱스 :: mousemove로 현재의 마우스 위치에 따라 기록
        search_chk1          : null,
        search_chk2          : null,
        search_chk3          : null,
        search_chk4          : null,
        search_off1          : null,
        search_off2          : null,
        search_off3          : null,
        search_off4          : null, 
        helperDomeId         : null,       // 드래그, 리사이즈등에 사용되는 커버 판넬 아이디
		drag_bar_flag        : false,      // 미리보기 드래그바 상태 플래그
        resize_bar_flag      : false,      // 넓이, 높이 리사이즈 상태 플래그
		resizeWd_bar_flag    : false,      // 넓이 리사이즈 상태 플래그
		resizeHt_bar_flag    : false,      // 높이 리사이즈 상태 플래그
		drag_flag            : false,      // 드래그 상태 플래그
		shift_key_flag       : false,
		ctrl_key_flag        : false,
		mouse_down_flag      : false,      // 마우스 오른쪽 버튼이 클릭되어 진행중인지 ( 드래그등에 관련 )
		drag_ready_flag      : false,      // 드래그 상태 플래그
        fileNameEdit_flag    : false,      // 파일명 바꾸기중인지

        // 1차 배열  [value1, value2, value3, ...]
        dataCodeNum          : [],         // 데이터 코드값 증가에 쓰임
		resize_flag          : [],         // 업로더 공간 리사이즈에 관한 드래그 플래그 ( 1차 배열 )
        resize_DomWidth      : [],         // 업로도 리사이즈 하고자 하는 돔의 클릭시 넓이
        resize_DomHeight     : [],         // 업로도 리사이즈 하고자 하는 돔의 클릭시 높이
        resize_viewWidth     : [],         // 리사이즈 하고자 하는 미리보기 공간 클릭시 넓이
        resize_viewHeight    : [],         // 업로도 리사이즈 하는 미리보기 공간 클릭시 높이
        resize_imgWidth      : [],         // 리사이즈 하고자 하는 미리보기 이미지 클릭시 넓이
        resize_imgHeight     : [],         // 업로도 리사이즈 하는 미리보기 이미지 클릭시 높이 
        resize_positionX     : [],         // 업로더 리사이즈를 위해 사용 마우스 다운시 현재 위치를 기록하고 마우스 무빙된 거리를 빼서 높이에 가감
        resize_positionY     : [],         // 업로더 리사이즈를 위해 사용 마우스 다운시 현재 위치를 기록하고 마우스 무빙된 거리를 빼서 높이에 가감
		dataCodeAry          : [],         // 코드를 만들 때 생성되어져 있는 코드인지 분별하는 용도로 쓴다
        click_idx            : [],         // 클릭으로 선택된 현재의 클릭 인덱스
        nowImgNaturalHeight  : [],         // 클릭으로 선택된 이미지의 원래 높이 값
        nowImgNaturalWidth   : [],         // 클릭으로 선택된 이미지의 원래 넓이 값    
        before_click_idx     : [],         // 이전 클릭 인덱스 ( 마우스 다운에서 입력 )
		view_img_idx         : [],         // 클릭으로 선택된 미리보기 이미지 인덱스
        module_width         : [],         // 모듈의 넓이
        view_dp_wd           : [],         // 미리보기, 상세보기 창 넓이
        view_dp_wd2          : null,       // 미리보기, 상세보기 창 넓이
        view_dp_ht2          : null,       // 미리보기, 상세보기 창 높이
        screen_mode          : [],         // 현재 창의 형태 ( normal => 일반, full => 전체문서, window_full => 윈도우 풀스크린 )
        viewTypeFlag         : [],         // 미리보기창  preview( 미리보기 ),detail( 상세보기 ) 
        preview_mode         : [],         // 파일 미리 보기 창 열려 있는지에 대한 플래그 
        detail_mode          : [],         // 파일 상세 보기 창 열려 있는지에 대한 플래그 
        totalBiteSize        : [],         // add_atribute에서 파일 사이즈 합산시 사용
        list_shape           : [],         // 현재 업로더가 선택한 리스트의 모양 리스트 형태 변경 버튼을 눌렀을때 변수를 받아둔다
        use_focus_pannel_act : [],         // 하이라이트 판넬 사용 유무 ( 일시적으로 보이지 않아야 할 때 사용 )
        multiSelect          : [],         // 여러개의 파일 선택 사용 유무( 드래그 행위 )
        shape8TextPositions  : [],         // shape8 에만 사용 각각의 정보가 들어가는 html 공간의 넓이를 기록해뒀다가 리사이즈가 있을 경우 보이고 안보이고를 하는데 사용
        fileInfoClass        : [           // shape8에서 영역 리사이즈시 숨기거나 보여지는 영역의 클래스들
                                'file_type_in', 'file_pic_size_in', // 'file_name_in',
                                'modified_date_in', 'file_size_in'
        ],               
        validClasses         : [ // 아래의 클래스를 가진 개체에 마우스를 놓고 드래그하면 고스트 무브 ( 개체를 이동할 수 있게 한다 )  
                                'contentDiv', 'file_type_in', 'choice_div', 'file_size_in',
                                'upload_thumb', 'one_file', 'one_file thumb_chk1', 'one_file thumb_off1',
                                'one_file thumb_chk3', 'file_name_in',
        ],

        // 2차 배열 [[value1-1, value1-2, ...], [value2-1, value2-2, ...]]
        extensions           : [],         // 업로드 가능한 확장자( html 선상에서 use-ext 속성으로 지정한 업로드 확장자에 따른 처리 ) 
 		drag_position_ary    : [],         // 각 개체의 높이 넓이 위치정보등을 담는다
        history_ary          : [],         // 히스토리
        modify_file_ary      : [],         // 수정용 파일을 받는 용도로 사용한다
		shift_start_end_ary  : [],         // SHIFT 사용시 클릭한 시작점과 끝점만 들어간 배열 
		temp_Ary             : [],         // 
		drag_select_ary      : [],         // 마우스 드래그로 선택된 최종 개체 리스트 배열 
		drag_shift_ary       : [],         // SHIFT 사용시 드래그한 최종 그룹
        rowsData             : [],         // 각 줄별 데이터가 => 첫줄, 두번째줄... 마지막줄, [인덱스, 파일이름, 썸네일 크기], 현재 라인에서 가장 높은 이미지의 높이에 맞추는데 사용
        fileNameAry          : [],         // 파일명을 받아두고 변경시 대조군으로 사용한다
        getMakeTypeAry       : [],         // 마우스 다운시 전체 리스트의 MakeType을 가져와 사용하기 좋게 배열에 넣는다

        // 배열 안에 객체 [{attr1:val1},{attr2:val2},{attr3:val3},...] == 1차 배열
		dTa_Ary              : [],         // 파일 정보의 객체가 들어감
        thumb_Ary            : [],         // 파일 썸네일 정보의 객체가 들어감 
        delete_Ary           : [],         // 삭제된 데이터 ( 서버에서 가져온 데이터만 )

        // 숫자값
        total_size           : 0,          // 업로드된 파일들의 전체 사이즈 ( 표기 파일 크기 1000이 넘어서면 메가바이트로 작으면 킬로바이트로 표기 ) 
		obj_info_top         : 0,          // 파일 정보 레이어 위치
		obj_info_left        : 0,          // 파일 정보 레이어 위치
		startX               : 0,          // 처음 클릭한 위치 
		startY               : 0,          // 처음 클릭한 위치
        startX5              : 0,          // 처음 클릭한 위치 
        startY5              : 0,          // 처음 클릭한 위치 
        domAddCnt            : 0,

		// 드래그, 개체 선택 관련
 		before_scrollTop     : 0,          // 
		pos_top              : 0,          // 
		viewBarPos_left      : 0,          // 미리보기 좌우 무빙바의 위치  
		drag_moveX           : 0,          // 마우스 무빙중인 clientX 위치 
		drag_moveY           : 0,          // 마우스 무빙중인 clientY 위치

        // 특정값
        click_eq             : null,       // 마우스 다운시 선택한 업로더 eq
 	    drag_idx             : null,       // 드래그중 선택되고 있는 현재의 인덱스
        click_target         : null,       // 클릭한 곳이 파일인지 배경인지
        click_extention      : null,       // 클릭한 파일의 확장자
		drag_eq_idx          : null,       // eq에 쓰일 인덱스 
		insert_idx           : null,       // 위치 변경으로 선택되어 넣어질 인덱스
		insert_pos           : null,       // 위치 변경으로 선택되어 넣어질 앞 뒤
		shift_start_num      : null,       // SHIFT키를 사용할 때 시작 개체
		shift_end_num        : null,       // SHIFT키를 사용할 때 끝 개체
		ctrl_mouseup_flag    : null,       // ctrl 드래그후 다른 개체를 잡기 위해 마우스 업이 이뤄졌다면
 		set_time_var         : null,       // 
	    set_time_var2        : null,       // 
		set_time_var3        : null,       // 미리보기 버튼 클릭 리사이징시 
		set_time_var4        : null,       // 미리보기 버튼 클릭 리사이징시 
		move_x_flag          : null,       // 좌우 마우스 움직임 
		move_y_flag          : null,       // 상하 마우스 움직임

        version              : '0.9',      // 버전

        /*=========================================================================================
         * 1.  업로더 관련 중요 클래스명 모음
        **---------------------------------------------------------------------------------------*/ 
        uploader_module_cls  : '.uploader_module',    // 
        module_bg_cls        : '.module_bg',          //
		loader_cls           : '.file_loader',        // 파일 업로더 최상위 개체 클래스명 
		loader_main_cls      : '.loader_main',        // 파일 업로더 메인 개체 클래스명
 	    file_zone_cls        : '.file_zone',          //
	    drop_zone_cls        : '.drop_zone',          // 
	    file_main_cls        : '.file_main',          // 
	    file_list_cls        : '.file_list',          // 
		resize_handle_cls    : '.resize_handle',      // 
        resizeHt_handle_cls  : '.resizeHt_handle',    // 
        resizeWd_handle_cls  : '.resizeWd_handle',    //  
		one_file_cls         : '.one_file',           // 
		focus_pannel_cls 	 : '.focus_heighlight',   // 
        front_panel_cls	     : '.front_panel',        //  
	    view_preview_cls     : '.view_preview',       // 
	    view_detail_cls      : '.view_detail',        // 
        file_up_msg_cls      : '.file_up_msg',        //
	    file_info_cls        : '.file_info',          // 
		info_div_cls         : '.file_info_div',      // 
		file_btn_cls         : '.upload_button',      //    
		thumb_bt_cls         : '.thumb_bt',           // 
        choice_div_cls       : '.choice_div',         // 
		del_bt_cls           : '.del_bt',             // 
		n_del_bt_cls         : '.n_del_bt',           // 
        drag_bar_cls         : '.drag_bar',           //   
        file_view_dp_cls     : '.file_view_dp',       //       
		bt0_cls              : '.file_view_type_bt0', //  
        file_editor          : '.file_editor',        //  
        file_name_in_cls     : '.file_name_in',       // 파일명
        file_name_cls        : '.file_name',          // 파일명

		/*=========================================================================================
		 * 2. 로드시 바로 연동되어야 할 기능 연결
		**---------------------------------------------------------------------------------------*/
		init : function(){
            const obj_lang         = MARI.file_uploder.langObj; 
 			const obj_util         = MARI.file_uploder.util;
            const obj_this         = MARI.file_uploder.code; 
            const view_preview_cls = obj_this.view_preview_cls;
            const view_detail_cls  = obj_this.view_detail_cls;
            const drop_zone_cls    = obj_this.drop_zone_cls;
            const input_obj        = obj_this.input_obj;
            const fileLoader       = document.querySelectorAll(obj_this.loader_cls); // loader_main_cls 로 접근하지 않는 이유는 돔 생성 전이라서 
            const inputElems       = document.querySelectorAll(obj_this.input_obj);

            // 언어 설정
            const userLang         = (navigator.languages || [navigator.language])[0].split('-')[0];// 사용자의 언어 설정 감지
            const languageCodes    = Object.keys(file_ext_list);// 객체에서 모든 키를 뽑아오기
            obj_this.country       = languageCodes.includes(userLang) ? userLang : 'en';// userLang가 languageCodes에 포함되면 그 값을, 아니면 'en'을 사용
            // 언어 객체에서 선택된 언어 가져오기
            let objLang            = MARI.file_uploder.langObj[obj_this.country]; 

            if(document.querySelector('.programInfo .version')){
               document.querySelector('.programInfo .version').innerHTML = obj_this.version;
            }

            let isUpdate           = '';
            let num = 0; // obj_eq
            fileLoader.forEach((loader, index) => { 
                if (loader.getAttribute('use_module') !== 'Y') return; // 사용하지 않는것은 제외 시킨다

                // 배열 및 기본 설정 초기화
                obj_this.drag_select_ary[num] = { objType: [], name: [], idx: [], code: [] };
                obj_this.viewTypeFlag[num]    = 'preview'; 
                obj_this.click_idx[num]       = obj_this.view_img_idx[num] = null; 
                obj_this.preview_mode[num]    = obj_this.detail_mode[num]  = 'close';
                obj_this.view_dp_wd[num]      = parseInt(obj_this.viewrDefaultWidth, 10);

                let shapeType = loader.getAttribute('list_shape');
                let isShape   = obj_util.isShapeTypeValid(shapeType);
                if(!isShape && shapeType){
                  //alert((index+1)+'번째 업로더의 리스트 shape 타입이 올바르지 않습니다\n올바른 타입명을 사용하세요\n사용중인 shape명 : '+shapeType);
                    alert(objLang.txt1(index+1, shapeType));
                    isUpdate = 'No';
                    return false;
                }

                obj_this.list_shape[num]      = shapeType || 'shape3';  // 현재 업로더의 뷰타입을 넣는다 

                /*=========================================================================================
                 * 3. 초기화가 필요한 배열들 사용 가능한 배열로 초기화
                **---------------------------------------------------------------------------------------*/
                function initializeArray(obj, num, property, defaultValue) { 
                    if (!Array.isArray(obj[property][num])) {
                        obj[property][num] = defaultValue;
                    }
                }

                //  배열 초기화후 필요한 값을 넣어줌
                initializeArray(obj_this, num, 'extensions', []);
                initializeArray(obj_this, num, 'temp_Ary', []); 
                initializeArray(obj_this, num, 'history_ary', []);
                initializeArray(obj_this, num, 'drag_position_ary', []);
                initializeArray(obj_this, num, 'dataCodeAry', []);
                initializeArray(obj_this, num, 'shift_start_end_ary', []);
                initializeArray(obj_this, num, 'multiSelect', []);
                initializeArray(obj_this, num, 'rowsData', []);
                initializeArray(obj_this, num, 'fileNameAry', []);
                initializeArray(obj_this, num, 'dataCodeNum', [1]);
                initializeArray(obj_this, num, 'resize_flag', false);
                initializeArray(obj_this, num, 'nowImgPercent', []);
                initializeArray(obj_this, num, 'upWheelCurrentIndex', 0);
                initializeArray(obj_this, num, 'downWheelCurrentIndex', 0);
                initializeArray(obj_this, num, 'delete_Ary', []);

                // 콘솔 디버깅 유무 :: 로컬스토리지 이용
                let getLocal = obj_util.getFromLocalStorage('uploaderData'); 
                if(getLocal){  
                    let resultVal = (getLocal.debergView === 'yes') ? 'Yes' : 'No';
                    if(MARI.file_uploder.code.isAdmin === true){
                        obj_this.debergView  = resultVal;
                    }
                }

                // 히스토리 입력
                obj_this.historyEvent(num,  'init => 기본 설정 초기화및 준비')

                isUpdate = 'Yes';
                num++; // obj_eq

            });

            if(isUpdate === 'No'){ // 초기화 설정이 올바르지 않다면
                return false;
            }

            if( document.querySelector(obj_this.loader_cls) ) {
                if (typeof modifyFile_Ary === 'undefined') {
                    //alert('파일 업로드를 사용하기 위해서는 수정용 파일을 담을 배열인 \'modifyFile_Ary\'가 정의되어야 합니다');
                    alert(objLang.txt2(index+1,shapeType));
                    return false;
                }

                obj_this.makeLayoutDom(); // 레이아웃 dom 생성
                obj_this.btn_event();
                obj_this.drag_selection();
                obj_util.full_window();    //btn_event 안으로 들어갈 수 있음 

                ['dragenter', 'dragover', 'dragleave'].forEach(event => 
                    document.addEventListener(event, obj_util.preventDefaultActions)
                );

                // 미리보기 공간등에 드랍했을 때
                obj_util.addEvent(view_preview_cls + ',' + view_detail_cls, 'drop', event => {
                    obj_util.preventDefaultActions(event);
                    //alert('파일 업로드는 업로드 버튼을 사용하거나 파일 드랍 공간에 드랍하세요');
                    alert(objLang.txt3);
                    return false;
                });

                obj_util.addEvent(drop_zone_cls, 'drop', function(event) {
                    obj_util.preventDefaultActions(event);
                    obj_this.changeEvent(event);
                });
                
                obj_util.addEvent(input_obj, 'change', function(event) { 
                    obj_this.changeEvent(event); 
                });

                // 페이지가 이미 로드된 경우를 처리
                if (document.readyState === 'complete' || document.readyState === 'interactive') {  // 문서가 로드 완료 되었다면  
                    if (modifyFile_Ary.length > 0) obj_this.modifyFileAdd();
                }
            }

        }, // end init


		/*=========================================================================================
		 * 3. 레이아웃을 만든다
		**---------------------------------------------------------------------------------------*/
        makeLayoutDom : function(){

			const obj_util      = MARI.file_uploder.util; 
            const obj_this      = MARI.file_uploder.code; 
            const objLang       = MARI.file_uploder.langObj[obj_this.country];  // 언어 객체에서 선택된 언어 가져오기
            const fileLoader    = document.querySelectorAll(obj_this.loader_cls); 
            const inputElems    = document.querySelectorAll(obj_this.input_obj);
            const viewTypeBt    = document.querySelectorAll(obj_this.bt0_cls);
            const dropZoneElems = document.querySelectorAll(obj_this.drop_zone_cls);
            let main_dom        = obj_this.mainLayout(); // 메인 레이아웃
            let num = 0;
            fileLoader.forEach((loader, i) => {
                const useModule      = loader.getAttribute('use_module');
                const list_shape     = loader.getAttribute('list_shape')    || obj_this.defaultShape;
                const uploadType     = loader.getAttribute('use-multiple')  || 'Y'; // 멀티 업로더, 단일 업로더 ( 업로드 행위 )
                const useMultiSelect = loader.getAttribute('multi_select')  || 'Y'; // 여러개의 파일 선택 사용 유무( 드래그 행위 )
                const useHeighLight  = loader.getAttribute('use_heiglight') || 'Y'; // 하이라이트 판넬 사용 유무 ( 일시적으로 보이지 않아야 할 때 사용 )
                const viewTypeBt     = document.querySelectorAll(obj_this.bt0_cls);
                const dropZoneElems  = document.querySelectorAll(obj_this.drop_zone_cls);
                const obj_id         = obj_util.getCurrentTimeInMicroseconds() + '_' + i;

                if (useModule !== 'Y') {  return false; }    

                // ※ 업로드 가능한 사용자 지정 확장자 설정
                obj_this.extensions[num] = obj_this.extensions[num] || []; 

                // ※ 업로드를 멀티플 업로드로 할 것인지 단일 업로드로 할 것인지 설정
                let useMutipleOpt = (uploadType && uploadType === 'Y') ? true : false;

                // ※ 업로더 레이아웃 돔 생성
                loader.insertAdjacentHTML('beforeend', main_dom);

                // 돔 생성 이후
                const loaderMain  = document.querySelectorAll(obj_this.loader_main_cls)[num]; 
                const inputElem   = loaderMain.querySelector('input.frm_file');
                const viewBt0     = loaderMain.querySelector(obj_this.bt0_cls);       // 파일 로더를 기준으로 접근함
                const drop_zone   = loaderMain.querySelector(obj_this.drop_zone_cls); // 파일 로더를 기준으로 접근함
                const moduleBgObj = obj_this.module_bg_obj(num);

				/*===================================
                 * ※ 업로더 백그라운드 이미지
                 *   이미지 주소 때문에 css 클래스 추가로 처리
				**---------------------------------*/
                if(obj_this.moduleBgUse === 'Y'){
                    moduleBgObj.classList.add('use_bg'); // 이미지 주소 포함된 클래스 추가
                }
                
				/*===================================
                 * ※ 업로드 가능한 확장자 처리
				**---------------------------------*/
                // 해당 돔의 html 속성중 use-ext의 값이 있는지 확인한다
                let useExt = loader.getAttribute('use-ext');        // 업로더에서 사용가능한 파일의 값을 가져온다 
                obj_this.extensions[num] = useExt?.split(',') ?? [];// 콤마 기준으로 배열을 만든다 값이 없으면 빈배열

                // html에서 지정한 업로드 파일 타입 또는 시스템이 정한 타입을 구분한다
                let allowedExtensions = null;
                if( obj_this.extensions[num].length > 0 ){ // 사용자 html 지정 확장자가 있다면
                    allowedExtensions = obj_this.extensions[num]; // 사용자 지정 확장자 사용::여러개의 모듈이 한페이지에 있을 경우 선택적으로 모듈마다 달리 사용할 수 있다
                } else { // js상 지정한 확장자
                    if( obj_this.defaultExtType == 'type1' ){// 모든 확장자를 사용
                        allowedExtensions = obj_this.defaultExtensions1;
                    }
                    else
                    if( obj_this.defaultExtType == 'type2' ){// 지정한 디펄트 확장자만 사용::모든 모듈이 공통되게 사용된다
                        allowedExtensions = obj_this.defaultExtensions2; 
                    }
                }

                // 확장자를 accept 속성에 맞는 문자열로 변환 
                let acceptAllowedExt = allowedExtensions.join(',');
                
                // 업로더의 아이디와 dTa_Ary 누적을 위한 배열을 만듦
                if (inputElems[i]) {
                    inputElems[i].setAttribute("upload_id", obj_id); // 업로더 아이디 설정
                }

                loader.setAttribute('obj_id', obj_id);
                loader.setAttribute('obj_eq', num);                  // 업로더 영역에 업로더 인덱스 삽입
                inputElem.setAttribute('obj_eq', num);               // 업로더 인풋에 업로더 인덱스 삽입
                inputElem.setAttribute('accept', acceptAllowedExt);  // 업로더 인풋에 사용 가능한 파일 지정
                loader.removeAttribute('use-ext');                   // 업로더에서 속성 삭제해서 보이지 않도록 
                inputElem.multiple = useMutipleOpt;                  // 업로더 인풋에 멀티 업로드 옵션 추가 여부

                // 업로더의 넓이를 기록한다 ( 윈도우 최대 사이즈로 늘려서 미리보기, 상세보기창을 열 때 비율을 계산하기 위해 필요 ) 
                MARI.file_uploder.code.multiSelect[num] = Array('Y'); 

                // 여러개의 파일 선택 사용 유무( 드래그 행위 ) 
                MARI.file_uploder.code.multiSelect[num] = (useMultiSelect === 'N') ? 'N' : 'Y';

                // 하이라이트 판넬 사용 유무 ( 일시적으로 보이지 않아야 할 때 사용 )
                MARI.file_uploder.code.use_focus_pannel_act[num] = (useHeighLight === 'N') ? 'N' : 'Y';
 
                // ※ dTa_Ary 누적 시키기
                const dtAry = {
                    loader_index : i,
                    obj_id       : parseInt(obj_id),
                    modify_files : [],                 // modifyFileAdd에서 이곳으로 옮김 2024-09-02(월)
                    new_files    : new DataTransfer(), // DataTransfer 객체를 files 항목으로 초기화
                };
                MARI.file_uploder.code.dTa_Ary.push(dtAry);
                
                // ※ thumb_Ary 누적 시키기( 썸네일 관련 :: 2025-02-28 )
                const thumb_dtAry = {
                    loader_index : i,
                    obj_id       : parseInt(obj_id),
                    thumb_modify_files : [],                 
                    thumb_new_files    : new DataTransfer(),
                };
                MARI.file_uploder.code.thumb_Ary.push(thumb_dtAry);

                // 선택된 파일 보기 형태로 CSS 설정 셀렉트 박스 안 아이콘 선택 ( 돔생성 이후여야 하기에 이 곳에서 정의함 ) 
                if(viewBt0)
                viewBt0.classList.add(list_shape);   // 타입 변환 버튼

                // 선택된 파일 보기 열린 창에 사용자가 초기 지정한 리스트 모양을 클래스 선택해 준다
                if (useModule === 'Y') { 
                    // 현재 업로더의 shape를 가져와서 숫자만 떼어 낸다 
                    let shapeNum  = parseInt(list_shape.replace("shape", "")) - 1; 
                    let openDiv   = loaderMain.querySelector('.open_div'); // 첫 번째 .open_div 요소 선택
                    if(openDiv){
                    let secondDiv = openDiv.querySelectorAll('div')[shapeNum]; // 첫 번째 .open_div 요소 내의 두 번째 <div> 요소 선택
                    if (secondDiv) { secondDiv.classList.add('on'); }
                    }
                }

                // drop_zone에 클래스 추가
                drop_zone.classList.add(list_shape);

                // 히스토리 입력
                obj_this.historyEvent(num, ( parseInt(num) + 1 ) + '번 업로더 관련 레이아웃 생성 및 기본값 설정 완료'); 

                num++; // obj_eq
            });

            // 메모리 해제 
            main_dom = null;
 
			/* 파일 정보 레이어 만들기 및 오픈 관련 */
			if(obj_this.file_info_layer_use === 'Yes'){
			    obj_this.file_info_layer_process(event); 
            }

        }, //end makeLayoutDom


		/*=========================================================================================
		 * 4. 버튼 관련 이벤트 등록
		**---------------------------------------------------------------------------------------*/
        btn_event : function(){
			const obj_util      = MARI.file_uploder.util;
            const obj_this      = MARI.file_uploder.code;
            const objLang       = MARI.file_uploder.langObj[obj_this.country];  // 언어 객체에서 선택된 언어 가져오기
            const drop_zone_str = obj_this.drop_zone_cls.substr(1);
            const fileLoader    = document.querySelectorAll(obj_this.loader_cls);  

            // 파일명 바꾸기
            let tempNameEq   = null;
            let tempNameIdx  = null; 
            let imgElement   = null;
            let spanElement  = null;
            let tempFileName = null; // 현재 파일명을 수정 하는 파일 이름을 받아둔다
            let tempFileExt  = null; // 현재 파일명을 수정 하는 파일 확장자를 받아둔다  
            let oldName      = null; // 이전 이름
            let newName      = null; // 고친후 이름
            let heighlight   = null; // 하일 라이트 판넬 유무 설정을 받아둔다 
            let main_dom     = obj_this.mainLayout(); // 메인 레이아웃
            let num = 0;
            fileLoader.forEach((loader, i) => { 
                num++; // obj_eq
                // 히스토리 입력
                // obj_this.historyEvent(num, ( parseInt(num) + 1 ) + '번 업로더 관련 레이아웃 생성 및 기본값 설정 완료');  
            });

			// 클릭 이벤트에 관련된 동작 :: 시작
            document.addEventListener("click", function(event) {

                // 업로더 안에서 클릭이나 마우스 다운등의 이벤트등이 일어났다면
                if (event.target.closest(obj_this.uploader_module_cls)) {
                    let eq_idx = obj_this.event_eq(event); // 사용 가능하게 설정된 업로더에서의 인덱스 
                    obj_this.eq_idx =  eq_idx;
                }

			    // 업로드 버튼 클릭시 파일 업로더 열기( 클릭 방법 1 )
                if (event.target.matches(obj_this.file_btn_cls)) {  
                    let eq_idx   = obj_this.event_eq(event); //사용 가능하게 설정된 업로더에서의 인덱스   
                    let loaderMain = obj_this.loader_main_obj(eq_idx);
                    loaderMain.querySelector(obj_this.input_obj).click();
                    event.preventDefault();
                    // 히스토리 입력
                    obj_this.historyEvent(eq_idx,  '파일 인풋 업로드 버튼 클릭', 'blue'); 
					return false;
                }

			    // 선택한 n개 파일 삭제 ( eq_idx를 뽑는 다른 방법 포함 )
				if (event.target.matches(obj_this.n_del_bt_cls) || event.target.closest(obj_this.n_del_bt_cls)) {
                    let eq_idx = obj_this.event_eq(event); //사용 가능하게 설정된 업로더에서의 인덱스
					event.preventDefault(); // 기본 이벤트 취소
					// 선택한 파일 삭제 함수 호출
				 	obj_this.select_files_del(event); 

                    // 히스토리 입력
                    obj_this.historyEvent(eq_idx,  '선택한 n개 파일 삭제 ', 'red');

					return false;
				}

                // 선택 파일 삭제
                // 클릭된 요소가 del_bt_cls와 일치하거나 그 상위 요소 중에 del_bt_cls가 있는지 확인
                if (event.target.matches(obj_this.del_bt_cls) || event.target.closest(obj_this.del_bt_cls)) {
                    event.preventDefault(); // 기본 이벤트 취소
                    let eq_idx = obj_this.event_eq(event); //사용 가능하게 설정된 업로더에서의 인덱스

                    // 사용자 확인 대화상자 표시
                    let cfm = confirm('선택한 한개의 파일을 삭제 하시겠습니까?');

                    // 사용자가 확인을 클릭한 경우 파일 삭제 함수 호출
                    if (cfm) {
                        let evt_idx    = obj_this.event_index(event, obj_this.del_bt_cls); 
                        let objType    = obj_this.getMakeType(eq_idx, evt_idx); 
                        let modifyCnt  = obj_this.file_count_return(eq_idx, 'modifyCnt'); // 파일 업로더 서버에 업로드 완료 갯수  
                        let getListIdx = obj_this.getListDataAryIdx(eq_idx, evt_idx) - 1;   // 데이터 코드에는 1부터 증가이므로 빼준다  
                        let fileName   = obj_this.getFileInfo2(eq_idx, objType, getListIdx, 'dataFileOriginalName', '608 Line');

                        // 선택 파일 삭제
                        obj_this.file_delete(event, eq_idx, evt_idx);

                        // 히스토리 입력
                        obj_this.historyEvent(eq_idx,  (evt_idx+1)+ '번째 개별 선택 파일 삭제 [ ' + fileName+' ] ', 'red'); 
                    }
                    return false;
                }

                // 대표 썸네일 선택
                if (event.target.matches(obj_this.thumb_bt_cls)) {
                    obj_util.preventDefaultActions(event);// 이벤트 버블링 안된도록 
                    let eq_idx   = obj_this.event_eq(event); //사용 가능하게 설정된 업로더에서의 인덱스
                    let evt_idx    = obj_this.event_index(event, obj_this.thumb_bt_cls); 
                    let objType    = obj_this.getMakeType(eq_idx, evt_idx); 
                    let getListIdx = obj_this.getListDataAryIdx(eq_idx, evt_idx) - 1;   // 데이터 코드에는 1부터 증가이므로 빼준다  
                    let fileName   = obj_this.getFileInfo2(eq_idx, objType, getListIdx, 'dataFileOriginalName', '627 line'); 
                    
                    obj_this.thumb_choice(eq_idx, evt_idx); // 썸네일 선택

                    // 히스토리 입력
                    obj_this.historyEvent(eq_idx,  '대표 썸네일 선택 [ ' + fileName+' ]', 'orange');
                    return false;
                }

                // 에디터 창 열기
                if (event.target.matches('.edit_bt')) { 
                    let eq_idx  = obj_this.event_eq(event); //사용 가능하게 설정된 업로더에서의 인덱스
                    let fileEditor = document.querySelectorAll(obj_this.file_editor)[eq_idx];
                    if (fileEditor.style.display !== 'block') {
                        obj_util.fadeIn(fileEditor,  200); 
                    } else {
                        obj_util.fadeOut(fileEditor,  250);
                    }
                    return false;
                }
 
                // 히스토리 레이어 열기, 닫기
                if (event.target.matches('.file_historyDiv .openBt')) { 
                    let eq_idx          = obj_this.event_eq(event); //사용 가능하게 설정된 업로더에서의 인덱스
                    let loaderMain      = obj_this.loader_main_obj(eq_idx); 
			        let drop_zone_obj   = obj_this.getDropZone(eq_idx); 
                    let ul_obj          = obj_this.ul_obj(eq_idx); // .file_list 클래스의 ul  
                    let fileHistory     = loaderMain.querySelector('.file_historyDiv');
                    let fileHistoryBt   = loaderMain.querySelector('.file_historyDiv .openBt'); 
                    let bgPannel        = loaderMain.querySelector('.file_historyDiv .file_historyBgpannel'); 
                    let historyIn       = fileHistory.querySelector('.historyIn');
 				    let openFlag        = fileHistory.getAttribute('divOpen'); // 열림, 닫힘 플래그
                    let fileZoneBtm     = loaderMain.querySelector('.file_zone_bottom');
                    let fileZoneObj     = obj_this.file_zone_obj(eq_idx);  
                    let fileZoneRect    = fileZoneObj.getBoundingClientRect();  
                    let fileZoneBtmRect = fileZoneBtm.getBoundingClientRect();  

                    if (openFlag == 'no') {//닫혀 있다면 열기
                        fileHistory.style.top    = - fileZoneRect.height + fileZoneBtmRect.height + 25 + 'px'; 
                        fileHistory.style.height = fileZoneRect.height - fileZoneBtmRect.height + 'px'; 
                        fileHistory.setAttribute("divOpen", 'yes');
                        bgPannel.style.opacity = 1;
                        fileHistoryBt.classList.add('on');  
                        drop_zone_obj.classList.add('blur');

                        let timeoutId  = setTimeout(function() { 
                            fileHistoryBt.innerHTML = 'close';
                        }, 250);

                        historyIn.innerHTML = obj_this.history_ary[eq_idx]
                            .slice()   // 원본 배열을 복사
                            .reverse() // 배열을 뒤집어서 내림차순으로 변경 
                            .map((item, index, array) => `<li>${item}</li>`)
                            .join(''); // 내림차순 

                        // 리스트의 넘버링을 준다
                        const totalItems = historyIn.querySelectorAll('li').length + 1; 
                        // li 항목의 총 개수로 카운터 초기화
                        historyIn.style.counterReset = `list-counter ${totalItems}`;

                    } else {
 
                        obj_util.fadeOut(historyIn, 200, 350, 'block', 1, 10, function() {
                            fileHistory.setAttribute("divOpen", 'no'); 
                            fileHistory.style.top    = '0px';
                            fileHistory.style.height = fileZoneBtmRect.height + 'px'; 
                            drop_zone_obj.classList.remove('blur');  
                            let timeoutId  = setTimeout(function() {
                            //  fileHistoryBt.innerHTML = 'open'; 
                                historyIn.innerHTML     = ''; //내용 지우기
                                bgPannel.style.opacity  = 0;  
                                fileHistoryBt.classList.remove('on'); 
                            }, 750);
                        
                            let timeoutId2 = setTimeout(function() { 
                            fileHistoryBt.innerHTML = 'open'; 
                            }, 250);
                        });
                    }
                    return false;
                }

                // 히스토리, 파일 정보 보기
                if (event.target.matches('.colorInfo button')) { 
                    let eq_idx        = obj_this.event_eq(event); //사용 가능하게 설정된 업로더에서의 인덱스
                    let loaderMain    = obj_this.loader_main_obj(eq_idx);
                    let fileHistory   = loaderMain.querySelector('.file_historyDiv');
                    let historyIn     = fileHistory.querySelector('.historyIn');
                    if(event.target === loaderMain.querySelectorAll('.colorInfo button')[0]){
                        obj_util.preventDefaultActions(event);// 이벤트 버블링 안된도록 
                        fileHistory.querySelectorAll('.colorInfo .square')[1].classList.remove('on');
                        fileHistory.querySelectorAll('.colorInfo .square')[0].classList.add('on');

                        historyIn.innerHTML = obj_this.history_ary[eq_idx]
                            .slice() // 원본 배열을 복사
                            .reverse() // 배열을 뒤집어서 내림차순으로 변경
                            .map((item, index, array) => `<p>${array.length - index}. ${item}</p>`)
                            .join(''); // 내림차순
                    }

                    if(event.target === loaderMain.querySelectorAll('.colorInfo button')[1]){
                        obj_util.preventDefaultActions(event);// 이벤트 버블링 안된도록
                        fileHistory.querySelectorAll('.colorInfo .square')[0].classList.remove('on');
                        fileHistory.querySelectorAll('.colorInfo .square')[1].classList.add('on');
                    }
                    return false;
                }
 
                // 히스토리 레이어의 배열 보기 열기 
                if (event.target.matches('.historySpan')) {
                    // 클릭된 요소의 바로 다음 형제 요소를 찾음
                    let historyCont = event.target.nextElementSibling;
                    // historyCont가 있고 클래스가 맞는지 확인
                    if (historyCont && historyCont.classList.contains('historyCont')) {
                        // 현재 표시 상태를 토글
                        if (historyCont.style.display === 'block') {
                            historyCont.style.display = 'none'; // 닫기
                            event.target.innerHTML = '[ open ▼ ]'; // 닫힌 상태의 텍스트:: 정보 보기
                        } else {
                            historyCont.style.display = 'block'; // 열기
                            event.target.innerHTML = '[ close ▲ ]'; // 열린 상태의 텍스트:: 정보 보기
                        }
                    }

                    return false;
                }

            }); 
			// 클릭 이벤트에 관련된 동작 :: 끝 

            // 마우스 다운 이벤트에 관련된 동작 :: 시작
            document.addEventListener("mousedown", function(event) { 
			    let targetClass = event && event.target ? event.target.className : ''; 

                if (event.target.classList.contains('file_zone_help')) { 
                    //alert('준비중입니다'); 
                    alert(objLang.txt4);
                    return false;
                }

                // 업로더 안에서 클릭이나 마우스 다운등의 이벤트등이 일어났다면
                if (event.target.closest(obj_this.uploader_module_cls)) {
                    let eq_idx = obj_this.event_eq(event); // 사용 가능하게 설정된 업로더에서의 인덱스 
                    obj_this.eq_idx =  eq_idx;
                }
 
			    // 파일 리스트 모양 바꾸기 :: 레이어 열기
                if (event.target.classList.contains('select_div')) { 
                    obj_util.preventDefaultActions(event);// 이벤트 버블링 안된도록 
                    let eq_idx    = obj_this.event_eq(event); //사용 가능하게 설정된 업로더에서의 인덱스
                    let selectDiv = document.querySelectorAll('.select_div')[eq_idx];
                    let openDiv   = document.querySelectorAll('.open_div')[eq_idx];
                    let isOpen    = selectDiv.classList.toggle('on');
                    openDiv.style.display = isOpen ? 'block' : 'none';
                }
                
                if (event.target.classList.contains('moreBar')) {
                    event.preventDefault();
                    obj_util.preventDefaultActions(event);   // 이벤트 버블링 안된도록 
                    let eq_idx   = obj_this.event_eq(event); // 사용 가능하게 설정된 업로더에서의 인덱스
                    let moreBar  = document.querySelectorAll('.moreBar')[eq_idx];
                    let openDiv2 = document.querySelectorAll('.open_div2')[eq_idx];
                    let isOpen   = moreBar.classList.toggle('on');        // moreBar 객체를 클릭 했을 때 openDiv2를 열고 닫기
                    openDiv2.style.display = isOpen ? 'block' : 'none';
                }

			    // 파일 리스트 모양 바꾸기 :: 바탕화면 클릭시 레이어 닫기
                if (targetClass !== 'select_div' && targetClass !== 'select_div on') {
                    const openDivs = document.querySelectorAll('.open_div');
                    const visibleDivsCount = Array.from(openDivs).filter(div => {
                        const displayStyle = getComputedStyle(div).display;
                        return displayStyle !== 'none' || displayStyle === 'block';
                    }).length;
                    
                    if(visibleDivsCount > 0){ // 열려진게 한개라도 있다면
                        // 모든 .select_div 요소에서 'on' 클래스를 제거
                        document.querySelectorAll('.select_div').forEach(element => element.classList.remove('on'));
                        // 모든 .open_div 요소를 숨김
                        document.querySelectorAll('.open_div').forEach((element, index) => { 
                            if(element.style.display  === 'block'){
                                element.style.display = 'none'; // display 속성을 'none'으로 설정 
                                // let eq_idx  = obj_this.event_eq(event); //사용 가능하게 설정된 업로더에서의 인덱스 
                            }
                        });
                    }
                }
              
                const invalidClasses = ['moreBar', 'moreBar on', 'open_div2', 'open_on', 'opt_label', 
                'opt_check1', 'opt_check2', 'opt_check3', 'opt_check4', 'opt_check5',  'opt_check6',            
                'title'];
                if (!invalidClasses.includes(event.target.className)) {// invalidClasses 배열에 속하지 않은 것이라면
                    const openDivs = document.querySelectorAll('.open_div2');
                    const visibleDivsCount = Array.from(openDivs).filter(div => {
                        const displayStyle = getComputedStyle(div).display;
                        return displayStyle !== 'none' || displayStyle === 'block';
                    }).length;
                    
                    if(visibleDivsCount > 0){ // 열려진게 한개라도 있다면
                        // 모든 .moreBar 요소에서 'on' 클래스를 제거 
                        document.querySelectorAll('.moreBar').forEach(element => element.classList.remove('on')); 

                        // 모든 .open_div2 요소를 숨김
                        document.querySelectorAll('.open_div2').forEach((element, index) => { 
                            if(element.style.display  === 'block'){
                                element.style.display = 'none'; // display 속성을 'none'으로 설정  
                            }
                        });
                    }
                }

                // 파일 리스트 모양 바꾸기 :: 선택
                if (event.target instanceof Element) {
                    const match = event.target.className.match(/shape[1-8]/);
                    if (match) {
                        let eq_idx        = obj_this.event_eq(event); //사용 가능하게 설정된 업로더에서의 인덱스 
                        let loader_obj    = obj_this.loader_obj(eq_idx);
                        let fileLoader    = document.querySelectorAll(obj_this.loader_cls); 
                        let list_shape    = match[0]; // 매칭된 클래스명 
                        let loaderMain    = obj_this.loader_main_obj(eq_idx);
                        let drop_zone_obj = obj_this.getDropZone(eq_idx); 
                        let openDiv = document.querySelectorAll('.open_div')[eq_idx];
                        let fileViewTypeButtons = document.querySelectorAll('.file_view_type_bt0');

                        // ※ drop_zone_obj의 클래스를 변경
                        drop_zone_obj.className = `${drop_zone_str} ${list_shape}`; 
                        // ※ 속성 변경 :: 2024-11-28(목) 추가 shape변경시 좀 더 원활히 css에서 선택하게 하기 위해
                        loader_obj.setAttribute('list_shape', list_shape);
                        
                        // eq_idx에 해당하는 .file_view_type_bt0 요소의 클래스를 설정
                        fileViewTypeButtons[eq_idx].className = `file_view_type_bt0 ${list_shape}`;
                        // .open_div 내의 모든 div 요소에서 'on' 클래스를 제거
                        openDiv.querySelectorAll('div').forEach(div => div.classList.remove('on'));
                        // .open_div 내의 .shape 클래스 요소에 'on' 클래스를 추가
                        openDiv.querySelector(`.${list_shape}`).classList.add('on');
                      
                        //list_shape 배열에 선택된 shape을 저장
                        MARI.file_uploder.code.list_shape[eq_idx] = list_shape;
 
                        // 미리보기 드래그바 보이기 또는 감추기 활성 여부
                        obj_this.drag_bar_line_dp(event, eq_idx);
               
                        // 파일 아이콘 사용 유무에 따른 보이기 감추기    
                        if(obj_this.fileIconUse === 'No' && ( list_shape !== 'shape4' || list_shape !== 'shape5' || list_shape !== 'shape6' || list_shape !== 'shape7' || list_shape !== 'shape8')){   
                            loaderMain.querySelectorAll('.file_icon').forEach(function(element) {
                                element.style.display = 'none'; 
                            });
                        } else { 
                            loaderMain.querySelectorAll('.file_icon').forEach(function(element) {
                                element.removeAttribute('style');
                            });
                        }

                        // 리스트의 최대 넓이를 리스트 갯수에 맞춰서 지정한다
                        obj_this.getListMaxWidth(eq_idx);
                        
                        // 현재 라인에서 가장 높은 이미지의 높이에 맞춰 나머지 높이를 맞춘다 :: 파일 리스트에서 모양 바꾸기 
                        obj_this.equalizeImageHeights(eq_idx, 'changeShape', list_shape); 

                        /*===========================================
                         * 파일의 위치 정보
                         * 파일 미리보기 안에 들어 있는 파일들의 위치 정보를 기록한다
                        **-----------------------------------------*/
                        obj_this.position_info();

                        let element   = loaderMain.querySelector(`.${list_shape}[title]`); // title 속성을 가진 요소 선택 
                        let titleText = element.querySelector('.title').textContent; // span.title의 텍스트 가져오기 
                        
                        // 히스토리 입력
                        obj_this.historyEvent(eq_idx,  '파일 리스트 모양 바꾸기 [ '+ titleText + ' ]', 'orange'); 
                    }
                    //return false;
                }

                // 바탕등을 클릭시 파일명 바꾸기에 사용된 속성을 제거 한다 
                // 이전 변경 요소가 있다면 요소에 대해 인라인 스타일, 수정 속성 제거 
                if(MARI.file_uploder.code.fileNameEdit_flag === true){
                    // 파일명 변경을 위해 에디트 모드로 변경하거나 원래대로 한다, 사용할 수 있는 파일명인지와 중복 확인등을 한다 
                    let eq_idx  = obj_this.event_eq(event); //사용 가능하게 설정된 업로더에서의 인덱스 
                    let evt_idx = obj_this.event_index(event, obj_this.thumb_bt_cls); 
                    let objType = obj_this.getMakeType(eq_idx, obj_this.click_idx[eq_idx]);
                    if(event.target.className !='file_name'){
                    obj_this.validateAndEditFileName(event, objType, 'editEnd');
                    }
                } 

                if(obj_this.eq_idx !== null){
                    // more 판넬 안에 체크 사항들( 따로 뺄 것 )
                    let eq_idx      = obj_this.eq_idx;
                    let loaderMain  = obj_this.loader_main_obj(eq_idx);
                    let moreDiv     = loaderMain.querySelector('.open_div2');    // 프론트 판넬
                    let opt_label   = loaderMain.querySelectorAll('.opt_label'); // 프론트 판넬 
                    let rulerCont   = loaderMain.querySelector('.rulerCont');    // 미리보기 눈금자 :: 픽셀 잣대 
                    let gridLines   = loaderMain.querySelector('.gridLines');    // 눈금선 :: 격자.모눈자
                    let guidesLine  = loaderMain.querySelector('.guidesLine');   // 안내선 :: 십자선
                    let opt_check1  = loaderMain.querySelector('.opt_check1');   // 체크박스-미리보기 눈금자 :: 픽셀 잣대 
                    let opt_check2  = loaderMain.querySelector('.opt_check2');   // 체크박스-미리보기 눈금선 :: 격자.모눈자
                    let opt_check3  = loaderMain.querySelector('.opt_check3');   // 체크박스-미리보기 안내선 :: 십자선
                    let opt_check4  = loaderMain.querySelector('.opt_check4');   // 체크박스-음원 자동 재생 보이기 
                    let opt_check5  = loaderMain.querySelector('.opt_check5');   // 체크박스-콘솔창에 디버깅 표시 보이기  
                    let opt_check6  = loaderMain.querySelector('.opt_check6');   // 체크박스-미리보기 네비게이션 보이기  

                    const uploaderData = obj_util.getFromLocalStorage('uploaderData'); 
                    const allLabels    = Array.from(opt_label);
                    const clickedLabel = event.target.closest('.opt_label');

                    if (clickedLabel) {
                        const chkClickIndex = allLabels.indexOf(clickedLabel); // 클릭한 라벨의 인덱스

                        //체크박스 - 미리보기 눈금자 :: 픽셀 잣대 보이기 
                        if ( chkClickIndex == 0 && opt_check1) {
                            if (opt_check1.checked) { //체크되어 있어서 해제 동작을 한다
                                if(rulerCont)
                                rulerCont.style.display = 'none'; 
                                obj_util.saveToLocalStorage('uploaderData', { useRuler: 'no'});  
                            } else {
                                if(rulerCont)
                                rulerCont.style.display = 'block';
                                obj_util.saveToLocalStorage('uploaderData', { useRuler: 'yes'}); 
                            } 
                        } 

                        //체크박스 - 미리보기 눈금선 :: 격자.모눈자
                        if ( chkClickIndex == 1 && opt_check2) {
                            if (opt_check2.checked) { //체크되어 있어서 해제 동작을 한다
                                if(gridLines)
                                gridLines.style.display = 'none'; 
                                obj_util.saveToLocalStorage('uploaderData', { useGridLine: 'no'});  
                            } else {
                                if(gridLines)
                                gridLines.style.display = 'block';
                                obj_util.saveToLocalStorage('uploaderData', { useGridLine: 'yes'}); 
                            } 
                        }

                        //체크박스 - 미리보기 안내선 :: 십자선
                        if ( chkClickIndex == 2 && opt_check3) {
                            if (opt_check3.checked) { //체크되어 있어서 해제 동작을 한다
                                if(guidesLine)
                                guidesLine.style.display = 'none'; 
                                obj_util.saveToLocalStorage('uploaderData', { useGuidesLine: 'no'});  
                            } else {
                                if(guidesLine)
                                guidesLine.style.display = 'block';
                                obj_util.saveToLocalStorage('uploaderData', { useGuidesLine: 'yes'}); 
                            } 
                        } 

                        // 체크박스 - 자동 재생
                        if ( chkClickIndex == 3 && opt_check4) {
                            if (opt_check4.checked) { //체크되어 있어서 해제 동작을 한다 
                                obj_util.saveToLocalStorage('uploaderData', { autoPlay: 'no'});  
                            } else { 
                                obj_util.saveToLocalStorage('uploaderData', { autoPlay: 'yes'}); 
                            } 
                        }

                        // 체크박스 - 콘솔 디버그 보이기
                        if ( chkClickIndex == 4 && opt_check5) {
                            if (opt_check5.checked) { //체크되어 있어서 해제 동작을 한다 
                                obj_util.saveToLocalStorage('uploaderData', { debergView: 'no'});  
                                 // 콘솔 디버깅 유무
                                if(MARI.file_uploder.code.isAdmin === true){
                                    obj_this.debergView  = 'No';
                                }
                            } else { 
                                obj_util.saveToLocalStorage('uploaderData', { debergView: 'yes'}); 
                                 // 콘솔 디버깅 유무
                                if(MARI.file_uploder.code.isAdmin === true){
                                    obj_this.debergView  = 'Yes';
                                }
                            } 
                        }

                        // 체크박스 -미리보기 네비게이션 보이기
                        if ( chkClickIndex == 5 && opt_check6) { 
                            let imgViewNavi = loaderMain.querySelector('.imgViewNavi'); // 미리보기 이미지

                            if (opt_check6.checked) { //체크되어 있어서 해제 동작을 한다 
                                obj_util.saveToLocalStorage('uploaderData', { imgNaviView: 'no'});  
                                obj_util.fadeOut(imgViewNavi, 0, 0);

                            } else { //해제 되어 있어서 체크 동작을 한다
                                
                                obj_util.saveToLocalStorage('uploaderData', { imgNaviView: 'yes'}); 

                                // 이미지가 이미지 공간 보다 크다면
                                let previewImg = loaderMain.querySelector('.preview_upload_thumb2'); // 미리보기 이미지 
                                let previewDIv = loaderMain.querySelector('.preview_in_div');  
                                if(previewDIv){
                                let gap        = 100; // 상하 여유분
                                let objWd      = previewDIv.offsetWidth;
                                let objHt      = previewDIv.offsetHeight;
                                let imgWd      = previewImg.offsetWidth;
                                let imgHt      = previewImg.offsetHeight;
                                let isLarger   = imgWd > (objWd + gap) || imgHt > (objHt + gap);
 
                                if(isLarger)
                                    obj_util.fadeIn(imgViewNavi, 250, 100);
                                }
                            }
                        }
                    } 
                }

            });
            // 마우스 다운 이벤트에 관련된 동작 :: 끝

            // 마우스 업 이벤트에 관련된 동작 :: 시작
            document.addEventListener("mouseup", function(event) {
                let obj_util     = MARI.file_uploder.util;
                let obj_this     = MARI.file_uploder.code;
			    // 파일 리스트 모양 바꾸기 :: 레이어 열기

                // 업로더 안에서 클릭이나 마우스 다운등의 이벤트등이 일어났다면
                if (event.target.closest(obj_this.uploader_module_cls)) {
                    let eq_idx = obj_this.event_eq(event); // 사용 가능하게 설정된 업로더에서의 인덱스 
                    obj_this.eq_idx =  eq_idx;
                }

                if(obj_this.eq_idx !== null){
                    let loaderMain    = obj_this.loader_main_obj(obj_this.eq_idx);
                    let front_panel   = loaderMain.querySelector(obj_this.front_panel_cls); // 프론트 판넬
                    if (front_panel && front_panel.style.display === 'block') {
                        front_panel.style.display = 'none';
                        // 미리보기 넓이를 기록한다   
                        let loaderMain   = obj_this.loader_main_obj(obj_this.eq_idx);
                        let preview_mode = String(obj_this.preview_mode[obj_this.eq_idx]); // 파일 미리 보기 창 열려 있는지에 대한 플래그  
                        let detail_mode  = String(obj_this.detail_mode[obj_this.eq_idx]);  // 파일 상세 보기 창 열려 있는지에 대한 플래그
                        let file_view_dp = loaderMain.querySelector(obj_this.file_view_dp_cls); // 프론트 판넬  

                        /*===============================
                        * 미리보기창 넓이를 넣어준다 드롭바를 건들지
                        * 않을 수 있으므로
                        * ( 오픈 되어 있을 경우만 갱신 )
                        **-----------------------------*/ 
                        if (preview_mode === 'open' || detail_mode === 'open') {
                            obj_this.view_dp_wd[obj_this.eq_idx] = parseInt(file_view_dp.offsetWidth);
                        }
                    }
                }

                /*=========================================================================================
                 * 12. 미리보기, 상세보기창
                **---------------------------------------------------------------------------------------*/ 
                let eq_idx       = obj_this.event_eq(event);             //사용 가능하게 설정된 업로더에서의 인덱스  
                let loaderMain   = obj_this.loader_main_obj(eq_idx);
                let clickCls     = event.target.className; 
			 	let preview_mode = String(obj_this.preview_mode[eq_idx]); // 파일 미리 보기 창 열려 있는지에 대한 플래그  
			 	let detail_mode  = String(obj_this.detail_mode[eq_idx]);  // 파일 상세 보기 창 열려 있는지에 대한 플래그  
				let view_dp_wd   = parseInt(obj_this.view_dp_wd[eq_idx]); // 70보다 작으면 70 입력된 값이 있으면 값 없으면 지정된 값
                let obj_wd       = Math.max(70, view_dp_wd || parseInt(obj_this.viewrDefaultWidth));

                if (event.target.closest('.file_view_type1, .file_view_type2, .file_view_type3, .file_view_type4')) {
                    
                    if (event.target.closest('.file_view_type2, .file_view_type4')) {
                        alert('준비중입니다!!');
                        return false;
                    }    
                    
                    // 내용
                    /*===============================
                    ** 미리보기,상세보기 돔의 내용을 만들어 넣어준다 
                    **-----------------------------*/
                    let infoDom    = loaderMain.querySelector('.file_info');
                    let previewDom = loaderMain.querySelector('.view_preview'); 
                    let detailDom  = loaderMain.querySelector('.view_detail');
                    let viewBt1    = loaderMain.querySelector('.file_view_type1');
                    let viewBt2    = loaderMain.querySelector('.file_view_type2');
                    let viewBt3    = loaderMain.querySelector('.file_view_type3');
                    let viewBt4    = loaderMain.querySelector('.file_view_type4');
                    let dragBar    = loaderMain.querySelector('.drag_bar');
                    let dropMsg    = loaderMain.querySelector('.file_info .msg'); 
                    let viewMsg    = loaderMain.querySelector('.file_view_dp .msg');

                    /*===============================
                    * 현재의 모듈 넓이를 갱신한다 
                    **-----------------------------*/  
                    let moduleDom = obj_this.module_obj(eq_idx);
                    MARI.file_uploder.code.module_width[eq_idx] = moduleDom.offsetWidth; 

                    /*===============================
                    ** 미리 보기 열기 ( preview_mode )
                    **-----------------------------*/ 
                    if( clickCls === 'file_view_type1' || clickCls === 'file_view_type1 on' || 
                        clickCls === 'file_view_type3' || clickCls === 'file_view_type3 on' || 
                        clickCls === 'preview_view_bt' ){

                        MARI.file_uploder.code.viewTypeFlag[eq_idx] = 'preview'; // 미리보기창  preview( 미리보기 ),detail( 상세보기 )
                        detailDom.classList.remove('on');

                        //닫혀 있다면 열기
                        if(preview_mode === 'close' || detail_mode === 'open'){ 
                            // 1. 모드 저장
                            obj_this.preview_mode[eq_idx] = 'open';  // 모드 변경
                            obj_this.detail_mode[eq_idx]  = 'close'; // 상세보기는 닫기로
                                
                            // 2. 풀스크린, 문서 최대크기일 때 열기시 비율에 맞춰 크기를 늘리며 값을 저장한다
                            if(obj_this.screen_mode[eq_idx] === 'full_window' || obj_this.screen_mode[eq_idx] === 'full'){ 
                                let a = MARI.file_uploder.code.module_width[eq_idx];
                                let b = loaderMain.offsetWidth - 60;// 풀사이즈에서의 패딩값을 빼줌
                                let x = 0;
                                if(preview_mode === 'close'){
                                    let c = MARI.file_uploder.code.view_dp_wd[eq_idx];
                                    x = (b * c) / a;
                                    previewDom.style.width = x + 'px';
                                } 
                                MARI.file_uploder.code.view_dp_wd[eq_idx] = Math.round(x); // Math.round(x * 10) / 10
                            }
 
                            // 3. 버튼 변경
                            viewBt1.classList.add('on');
                            viewBt2.classList.remove('on');
                            viewBt3.classList.add('on');
                            viewBt4.classList.remove('on');
 
                            // 4. 상세 보기가 열린 상태에서 미리보기를 클릭했다면
                            if(preview_mode === 'close' && detail_mode === 'open'){ 
                                // 미리보기, 상세보기 공간 페이드 인
                                let wd = detailDom.style.width;  
                                detailDom.removeAttribute('style');
                                previewDom.style.display = 'block';
                                previewDom.style.width   = wd; 
                                // 미리보기, 상세보기 공간 페이드 인 
                                obj_util.fadeIn(previewDom, 200, 350, 'block', 1, 10, function() { 
                                    // 미리보기, 상세보기 메세지 보이기
                                    obj_util.fadeIn(viewMsg,  200, 0);
                                });
                                return false;
                            }

                            // 5. 동작 미리보기 공간 열기  
                            infoDom.style.display = 'block';
                            let currentWidth = previewDom.offsetWidth; // 초기 너비 설정
                            let s = 0; 
                            let t = 0;
                            let intervalVar = setInterval(function() { 
                                currentWidth += s; // 줄일 픽셀 단위 설정
                                // 미리보기, 상세보기 공간 넓이 늘리기
                                if (currentWidth > MARI.file_uploder.code.view_dp_wd[eq_idx]) {
                                    clearInterval(intervalVar);
                                    previewDom.style.width =  MARI.file_uploder.code.view_dp_wd[eq_idx] + 'px';
                 
                                    // 미리보기 드래그바 보이기 또는 감추기 활성 여부
                                    //obj_this.drag_bar_line_dp(event, eq_idx, 'fadeIn');

                                    // 미리보기, 상세보기 공간 페이드 인 
                                    obj_util.fadeIn(previewDom, 200, 350, 'block', 1, 10, function() { 
                                        // 미리보기, 상세보기 메세지 보이기
                                        obj_util.fadeIn(viewMsg,  200, 0);
                                        
                                        // 미리보기에서 사용 ( 이미지 리사이징에 대한 처리를 위해서 공간의 현재 넓이와 높이가 필요 )
                                        let viewDp = loaderMain.querySelector(obj_this.view_preview_cls);
                                        obj_this.resize_viewWidth[eq_idx]  = viewDp.offsetWidth - obj_this.PAD;  // 리사이즈 하고자 하는 미리보기 공간 클릭시 넓이
                                        obj_this.resize_viewHeight[eq_idx] = viewDp.offsetHeight - obj_this.PAD; // 업로도 리사이즈 하는 미리보기 공간 클릭시 높이
                                    }); 
                                } else { 
                                    t++;       // 점점 빨리 실행 되도록
                                    s = s + t; // 점점 빨리 실행 되도록 0,1,4,10,20,35,56,84,120,165,200  
                                    previewDom.style.display = 'block'; 
                                    previewDom.style.width   = currentWidth + 'px'; 
                                }

                            }, 30); // 10ms마다 실행
                             
                            return false;// 열고 나서 바로 닫지 않도록 소스를 여기서 끊어줌
                        }

                        // 미리보기 열려 있다면 닫기
                        if(preview_mode === 'open'){
                            // 6. 모드 저장
                            obj_this.preview_mode[eq_idx] = 'close';  // 모드 변경
                            obj_this.detail_mode[eq_idx]  = 'close'; // 상세보기는 닫기로 

                            // 7. 풀스크린, 문서 최대크기일 때 열기시 비율에 맞춰 크기를 줄여서 값을 저장한다 
                            if(obj_this.screen_mode[eq_idx] === 'full_window' || obj_this.screen_mode[eq_idx] === 'full'){
                                let a = MARI.file_uploder.code.module_width[eq_idx];
                                let b = loaderMain.offsetWidth - 60;// 풀사이즈에서의 패딩값을 빼줌
                                let x = '';  
                                if(preview_mode === 'open'){
                                    let c = MARI.file_uploder.code.view_dp_wd[eq_idx]; 
                                    x = (a * c) / b;
                                }
                                MARI.file_uploder.code.view_dp_wd[eq_idx] = Math.round(x); // Math.round(x * 10) / 10
                            }

                            // 8. 버튼 변경 
                            viewBt1.classList.remove('on');  
                            viewBt2.classList.remove('on');
                            viewBt3.classList.remove('on');  
                            viewBt4.classList.remove('on');

                            // 미리보기, 상세보기 메세지 보이기
                            obj_util.fadeOut(viewMsg, 200, 350, 'none', 1, 10, function() {
                                // 9. 미리보기 페이드 아웃, 드래그바 페이드 아웃
                                obj_util.fadeOut(previewDom,  200, 500);
                            });

                            obj_this.drag_bar_line_dp(event, eq_idx, 'fadeOut');// 미리보기 드래그바  감추기

                            // 10. 동작 미리보기 공간 닫기 
                            let currentWidth = previewDom.offsetWidth; // 초기 너비 설정 
                            let s = 0;
                            let t = 0;
                            let timeoutId  = setTimeout(function() { 
                                let interval = setInterval(function() {
                                    currentWidth -= s; // 줄일 픽셀 단위 설정
                                    if (currentWidth <= 0) {
                                        clearInterval(interval); // 너비가 0이 되면 멈춤
                                        previewDom.style.width = '0px'; 
                                        clearTimeout(timeoutId);
                                        previewDom.removeAttribute('style'); 
                                        infoDom.removeAttribute('style'); 
                                        dragBar.removeAttribute('style'); 
                                    } else {
                                        //    s = s + 30; // 점점 빨리 실행 되도록 
                                        t++;       // 점점 빨리 실행 되도록
                                        s = s + t; // 점점 빨리 실행 되도록 0,1,4,10,20,35,56,84,120,165,200 

                                        previewDom.style.width = currentWidth + 'px';
                                    //  detailDom.style.width  = currentWidth + 'px';
                                    }
                                }, 10); // 10ms마다 실행 
                            }, 750); // 페이드 아웃 시간과 기다리는 시간을 합한
 
                            return false;
                        }

                    }// end if

                    /*===============================
                    ** 상세 보기 열기 ( detail_mode )
                    **-----------------------------*/
                    if( clickCls === 'file_view_type2' || clickCls === 'file_view_type2 on' ||  
                        clickCls === 'file_view_type4' || clickCls === 'file_view_type4 on' || 
                        clickCls === 'detail_view_bt' ){ 

                        MARI.file_uploder.code.viewTypeFlag[eq_idx] = 'detail'; // 미리보기창  preview( 미리보기 ),detail( 상세보기 )
                        previewDom.classList.remove('on');

                        //닫혀 있다면 열기
                        if(detail_mode === 'close' || preview_mode === 'open'){

                            // 1. 모드 저장
                            obj_this.detail_mode[eq_idx]  = 'open';  // 상세보기는 열기
                            obj_this.preview_mode[eq_idx] = 'close'; // 미리보기는 닫기

                            // 2. 풀스크린, 문서 최대크기일 때 열기시 비율에 맞춰 크기를 늘리며 값을 저장한다 
                            if(obj_this.screen_mode[eq_idx] === 'full_window' || obj_this.screen_mode[eq_idx] === 'full'){
                                let a = MARI.file_uploder.code.module_width[eq_idx];
                                let b = loaderMain.offsetWidth - 60;// 풀사이즈에서의 패딩값을 빼줌
                                let x = '';
                                if(detail_mode === 'close'){
                                    let c = MARI.file_uploder.code.view_dp_wd[eq_idx]; 
                                    x = (b * c) / a;
                                    detailDom.style.width = x + 'px';
                                }
                                MARI.file_uploder.code.view_dp_wd[eq_idx] = Math.round(x); // Math.round(x * 10) / 10
                            }

                            // 3. 버튼 변경 
                            viewBt1.classList.remove('on');
                            viewBt2.classList.add('on');  
                            viewBt3.classList.remove('on');
                            viewBt4.classList.add('on');
 
                            // 4. 미리 보기가 열린 상태에서 상세 보기를 클릭했다면
                            if(preview_mode === 'open' && detail_mode === 'close'){ 
                                // 미리보기, 상세보기 공간 페이드 인 
                                let wd = previewDom.style.width;  
                                previewDom.removeAttribute('style');
                                detailDom.style.display = 'block';
                                detailDom.style.width   = wd; 
    
                                obj_util.fadeIn(detailDom, 200, 350, 'block', 1, 10, function() {
                                    // 미리보기, 상세보기 메세지 보이기
                                    obj_util.fadeIn(viewMsg,  200, 0);
                                });
                                return false;
                            }

                            // 5. 동작 상세보기 공간 열기   
                            infoDom.style.display = 'block';
                            let currentWidth = detailDom.offsetWidth; // 초기 너비 설정
                            let s = 0; 
                            let t = 0;
                            let interval = setInterval(function() {
                                currentWidth += s; // 줄일 픽셀 단위 설정

                                // 미리보기, 상세보기 공간 넓이 늘리기
                                if (currentWidth > MARI.file_uploder.code.view_dp_wd[eq_idx]) {
                                    clearInterval(interval); 
                                    detailDom.style.width  =  MARI.file_uploder.code.view_dp_wd[eq_idx] + 'px'; 

                                    // 미리보기 드래그바 보이기 또는 감추기 활성 여부
                                    obj_this.drag_bar_line_dp(event, eq_idx, 'fadeIn');
                                    
                                    // 미리보기, 상세보기 공간 페이드 인 
                                    obj_util.fadeIn(detailDom, 200, 350, 'block', 1, 10, function() {
                                        // 미리보기, 상세보기 메세지 보이기
                                        obj_util.fadeIn(viewMsg,  200, 0);
                                    });
                                    
                                } else { 
                                    t++;       // 점점 빨리 실행 되도록
                                    s = s + t; // 점점 빨리 실행 되도록 0,1,4,10,20,35,56,84,120,165,200 
                                    detailDom.style.display = 'block'; 
                                    detailDom.style.width  = currentWidth + 'px'; 
                                }
                            }, 10); // 10ms마다 실행
 
                            return false;// 열고 나서 바로 닫지 않도록 소스를 여기서 끊어줌
                        }
 
                        //열려 있다면 닫기
                        if(detail_mode === 'open'){ 

                            // 6. 모드 저장
                            obj_this.preview_mode[eq_idx] = 'close'; // 닫기로
                            obj_this.detail_mode[eq_idx]  = 'close'; // 닫기로

                            // 7. 풀스크린, 문서 최대크기일 때 열기시 비율에 맞춰 크기를 줄여서 값을 저장한다 
                            if(obj_this.screen_mode[eq_idx] === 'full_window' || obj_this.screen_mode[eq_idx] === 'full'){
                                let a = MARI.file_uploder.code.module_width[eq_idx];
                                let b = loaderMain.offsetWidth - 60;// 풀사이즈에서의 패딩값을 빼줌
                                let x = ''; 
                                if(detail_mode === 'open'){
                                    let c = MARI.file_uploder.code.view_dp_wd[eq_idx];
                                    x = (a * c) / b;
                                }
                                MARI.file_uploder.code.view_dp_wd[eq_idx] = Math.round(x); // Math.round(x * 10) / 10
                            }

                            // 8. 버튼 변경  
                            viewBt1.classList.remove('on');  
                            viewBt2.classList.remove('on');
                            viewBt3.classList.remove('on');  
                            viewBt4.classList.remove('on');  

                            // 미리보기, 상세보기 메세지 보이기
                            obj_util.fadeOut(viewMsg, 200, 350, 'none', 1, 10, function() {
                                // 9. 미리보기 페이드 아웃, 드래그바 페이드 아웃
                                obj_util.fadeOut(detailDom,  200, 500);
                            });
                            
                            obj_this.drag_bar_line_dp(event, eq_idx, 'fadeOut');// 상세보기 드래그바  감추기

                            // 10. 동작 상세보기 공간 닫기
                            let currentWidth = detailDom.offsetWidth; // 초기 너비 설정
                            let s = 0;
                            let t = 0;
                            let timeoutId  = setTimeout(function() { 
                                let interval = setInterval(function() {
                                    currentWidth -= s; // 줄일 픽셀 단위 설정
                                    if (currentWidth <= 0) {
                                        clearInterval(interval); // 너비가 0이 되면 멈춤 
                                        detailDom.style.width  = '0px';
                                        clearTimeout(timeoutId);
                                        detailDom.removeAttribute('style');
                                        infoDom.removeAttribute('style');
                                        dragBar.removeAttribute('style'); 
                                    } else {  
                                        //    s = s + 30; // 점점 빨리 실행 되도록 
                                        t++;       // 점점 빨리 실행 되도록
                                        s = s + t; // 점점 빨리 실행 되도록 0,1,4,10,20,35,56,84,120,165,200

                                        detailDom.style.width  = currentWidth + 'px';
                                    }
                                }, 10); // 10ms마다 실행

                            }, 750); // 페이드 아웃 시간과 기다리는 시간을 합한
 
                            return false;
                        }
                    } 
                }
            });
            // 마우스 업 이벤트에 관련된 동작 :: 끝

            // 마우스 더블 클릭 이벤트에 관련된 동작 :: 시작
            document.addEventListener("dblclick", function(event) {
 
                // 업로더 안에서 클릭이나 마우스 다운등의 이벤트등이 일어났다면
                if (event.target.closest(obj_this.uploader_module_cls)) {
                    let eq_idx = obj_this.event_eq(event); // 사용 가능하게 설정된 업로더에서의 인덱스 
                    obj_this.eq_idx =  eq_idx;
                }
                else {
                    return false;
                } 

			    // 드롭다운 드롭존 더블 클릭시 파일 업로더 열기( 클릭 방법 2 )
                if (
                  !( event.target.matches(obj_this.file_name_in_cls) || event.target.matches(obj_this.file_name_cls)) 
                  &&( event.target.matches(obj_this.drop_zone_cls) || event.target.closest(obj_this.drop_zone_cls) )  
                ) {
                    
                    let cls  = event.target.closest(obj_this.drop_zone_cls)?.className;
                    if (cls && cls.includes(drop_zone_str)) { 
                        let eq_idx     = obj_this.event_eq(event); //사용 가능하게 설정된 업로더에서의 인덱스  
                        let loaderMain = obj_this.loader_main_obj(eq_idx);
                        event.preventDefault();

                        loaderMain.querySelector(obj_this.input_obj).click();

                        // 히스토리 입력
                        obj_this.historyEvent(eq_idx,  '드롭다운 드롭존 더블 클릭시 파일 업로드 버튼 열기', 'blue');
                    }
                    return false;
                }

                // 클릭과 같은 형태로 css 변경
                let evt_idx     = obj_this.event_index(event, obj_this.one_file_cls);
                let target_type = event && event.target ? obj_this.is_thumbnail(event) : null;//썸네일 선택 혹은 주변 공간 선택 구분 
                if( obj_this.shift_key_flag === false && obj_this.ctrl_key_flag === false ){ 
                    //SHIFT,ctrl 둘다 안함
                    obj_this.aryClassSetting(event, 'no_shift_ctrl_up', target_type, evt_idx);
                }

                // 파일명 사용자가 변경하기
                if( event.target.matches(obj_this.file_name_in_cls) || 
                    event.target.matches(obj_this.file_name_cls) ) {
                    obj_util.preventDefaultActions(event);// 이벤트 버블링 안된도록 
                    let eq_idx      = obj_this.event_eq(event); //사용 가능하게 설정된 업로더에서의 인덱스
                    let loaderMain  = obj_this.loader_main_obj(eq_idx); 
			        let li_obj      = obj_this.li_obj(eq_idx);
                    let evt_idx     = obj_this.event_index(event, obj_this.one_file_cls);
                    let nameDiv     = li_obj[evt_idx].querySelector(obj_this.file_name_in_cls);  
                    let nameSpan    = li_obj[evt_idx].querySelector(obj_this.file_name_cls);
                    
                    // n개 삭제에 갯수 넣어주기
                    let select_cnt = obj_this.selectCnt(eq_idx);
                    loaderMain.querySelector('.del_file_cnt').innerHTML = select_cnt;  

                    // 수정중이면 드래그가 되지 않도록 ( 설정에 관계없이 하일 라이트 사용 금지 ) 
                    obj_this.use_focus_pannel_act[eq_idx] = 'N'; 
  
                    // 파일명 변경을 위해 에디트 모드로 변경하거나 원래대로 한다, 사용할 수 있는 파일명인지와 중복 확인등을 한다
                    let objType    = obj_this.getMakeType(eq_idx, evt_idx); 
                    obj_this.validateAndEditFileName(event, objType, 'editName'); 
                    return false;
                }
 
            });
            // 마우스 더블 클릭 이벤트에 관련된 동작 :: 끝
             
            // 윈도우 리사이즈 이벤트에 관련된 동작 :: 시작
            window.addEventListener("resize", function(event) { 
                // 윈도우 리사이즈시 스크롤바 색 관련, 포지션 재 정의
                let f_len  = document.querySelectorAll( obj_this.loader_main_cls).length;
                let num = 0;
                for (let i = 0; i < f_len; i++) {
                    if(document.querySelectorAll(obj_this.loader_cls).length > 0){
                        if (document.querySelectorAll(obj_this.loader_cls)[i].getAttribute('use_module') === 'Y') { 
                            const winWidth  = window.innerWidth;
                            const winHeight = window.innerHeight;

                            // 히스토리 입력
                            obj_this.historyEvent(num,  '윈도우 리사이즈 => '+ winWidth + 'px'+ ' * ' + winHeight+ 'px')

                            // 미리보기 드래그바 보이기 또는 감추기 활성 여부
                            obj_this.drag_bar_line_dp(event, num);
                            num++;
                        }
                    }
                }
                //obj_this.position_info(); 
                return false;
            });
            // 윈도우 리사이즈 이벤트에 관련된 동작 :: 끝

         },// end btn_event


		/*=========================================================================================
		 * 5. 인풋 파일 업로드시, 드랍존에 파일 업로드 이벤트가 발생했다면
		**---------------------------------------------------------------------------------------*/
		changeEvent : function(event){ 
			let obj_this         = MARI.file_uploder.code;   
            let objLang          = MARI.file_uploder.langObj[obj_this.country];  // 언어 객체에서 선택된 언어 가져오기
			let loader_cls       = obj_this.loader_cls;
			let loader_main_cls  = obj_this.loader_main_cls;
			let input_obj        = obj_this.input_obj;        // 파일 업로드 인풋 클래스 명    
			let limit_upload_num = obj_this.limit_upload_num; // 파일 전체 업로드 리미트 갯수  
            let eq_idx           = obj_this.event_eq(event);  // 사용 가능하게 설정된 업로더에서의 인덱스  
			let li_obj           = obj_this.li_obj(eq_idx);
			let tempFile         = null;

			/* 인풋 혹은 드랍에 의한 파일 데이터가 배열이 들어간다 */
			if(window.event.type === 'change'){ // 인풋 클릭했을 때  
                // loader_cls 요소를 모두 선택하고, eq_idx 번째 요소를 가져옴 
                let loaderMain     = obj_this.loader_main_obj(eq_idx);
                // input_obj에 해당하는 자식 요소를 찾음
                tempFile = loaderMain.querySelector(input_obj).files;  

                // 히스토리 입력
                obj_this.historyEvent(eq_idx,  '업로드 버튼 이벤트에 의한 파일 업로드', 'blue bold');
			}
			else
			if(window.event.type === 'drop'){  // 드래그 존에 드래그 앤 드랍을 했을 때 
 				tempFile = Array.from(window.event.dataTransfer.files); // 윈도우 이벤트 오브젝트를 배열로

                // 히스토리 입력
                obj_this.historyEvent(eq_idx,  '드롭다운 드롭존에 파일 드랍으로 업로드', 'blue');
 			}

			/*=================================================
             * 시스템 -> temp_Ary ( 일회성 )-> dTa_Ary ( 누적 )
			 * 현재의 파일 업로더 혹은 드랍다운의 파일을
			 * dTa_Ary에 담기 위한 일회용 임시 배열
			 * 파일 업로드 행위마다 최종 업로드 값이 들어감, 누적 배열 아님 주의
			 * 1,2,3,4,5,6,7,8,9,10의 사진파일이 있다면
			 * 인풋클릭으로 1,2,3을 담고 다시 열어서 4,5를 담았다면
			 * 배열엔 4,5가 담기고 마지막 드래그앤 드랍으로 6,7을 담았다면
			 * 최종 배열엔 6,7이 담겨져 있다
			**-----------------------------------------------*/
			MARI.file_uploder.code.temp_Ary[eq_idx] = tempFile;
 
            obj_this.eq_idx = eq_idx; //  현재 eq_idx 갱신
            
            // 히스토리 입력
            obj_this.historyEvent(eq_idx,  '최초 업로드 파일(tempFile)을 새로운 파일용 임시 배열에 누적 (temp_Ary)', 'blue');
            
            // 디버깅 
            obj_this.log('tempFile', tempFile);

			let up_cnt   = li_obj.length;     // 업로드완료한 이전 실려 있는 파일 
			let file_cnt = tempFile.length;   // 파일 업로더 전체 갯수
 		 	let totalCnt = up_cnt + file_cnt;  // 파일 업로더에 선택해서 올려진 이전과 현재의 합한 전체 갯수 

			if( totalCnt > limit_upload_num ){
				let minus_num = (totalCnt-limit_upload_num);
                /*
				    let msg1  = '';
					msg1 += '파일은 ' + limit_upload_num + '개 이하로만 업로드할 수 있습니다!!!\n\n';
					msg1 += '리스트에 업로드된 갯수 : ' + up_cnt+'개\n';
					msg1 += '현재 업로드를 위해 선택된 갯수 : ' + file_cnt+'개\n';
					msg1 += '업로드 할수 있는 '+(limit_upload_num - up_cnt)+'개를 제외한\n';
					msg1 += '마지막 '+(minus_num)+'개는 업로드에서 제외 됩니다';
                    let msg2  = '';
                    msg2 += '업로드 제한 갯수 이상 업로드가 되어 있습니다\n더 이상 업로드를 할 수 없습니다\n\n';
                    if(obj_this.boardSubject && obj_this.boardId )
                    msg2 += '업로드 게시판 : ' +  obj_this.boardSubject + ' ( ' + obj_this.boardId + ' )';
                    msg2 += '\n관리자가 설정한 업로드 제한 갯수 : ' +limit_upload_num + '개';
                */

				let msg1  = objLang.txt5(limit_upload_num, up_cnt, file_cnt, (limit_upload_num - up_cnt), (minus_num));
                let msg2  = objLang.txt6(obj_this.boardSubject, obj_this.boardId, limit_upload_num);

				if( up_cnt < limit_upload_num ){ alert(msg1); }
				else{ alert(msg2); }
                // 히스토리 입력
                obj_this.historyEvent(eq_idx,  '제한 갯수를 넘어선 업로드 방지', 'red');
			}

			// 전체 갯수가 리미트 미만이라면 그대로,  전체 갯수가 리미트 이상이라면
		 	let func_num = ( totalCnt < limit_upload_num ) ? file_cnt : (limit_upload_num - up_cnt);
 
			// 파일 추가 ( 기존 갯수 + 업로드하려는 갯수 )
	 	 	obj_this.newFileAdd(event, func_num);// func_num은 굳이 여기서 안 넣어도...

		}, // changeEvent


		/*=========================================================================================
		 * 6. 이미지일 경우 canvas이용해서 썸네일을 생성 리턴한다
         *        리스트의 아주 큰 아이콘 크기에 사용하기 위한 250픽셀을 기준점으로 사용
		**---------------------------------------------------------------------------------------*/
        createThumbnail: function (fileData, callback) {
            const obj_this = MARI.file_uploder.code; 
            const objLang  = MARI.file_uploder.langObj[obj_this.country];  // 언어 객체에서 선택된 언어 가져오기
            const file = fileData;
            if (!file || !file.type.startsWith('image/')) return;

            const reader = new FileReader();
            reader.onload = function (e) {
                const originalImageUrl = URL.createObjectURL(file);
                const img = new Image();
                img.onload = function () {
                    if (img.width <= 250 && img.height <= 250) {
                        // 원본 이미지가 250px 이하이면 리사이징 없이 그대로 사용
                        callback({
                            fileName     : file.name,
                            pictureUrl   : originalImageUrl,
                            thumbnailUrl : originalImageUrl, // 썸네일도 원본 이미지 URL 사용
                            pictureWidth : img.width,
                            pictureHeight: img.height
                        });
                    } else {
                        const canvas = document.createElement('canvas');
                        const ctx = canvas.getContext('2d');
                        const maxSize = 250;
                        let width = img.width, height = img.height;

                        if (width > height) {
                            height *= maxSize / width;
                            width = maxSize;
                        } else {
                            width *= maxSize / height;
                            height = maxSize;
                        }

                        canvas.width = width;
                        canvas.height = height;
                        ctx.drawImage(img, 0, 0, width, height);

                        canvas.toBlob(function (blob) {
                            const thumbBlobURL = URL.createObjectURL(blob);
                            callback({
                                fileName: file.name,
                                pictureUrl: originalImageUrl,
                                thumbnailUrl: thumbBlobURL,
                                pictureWidth: img.width,
                                pictureHeight: img.height
                            });

                           // 썸네일을 저장하기 위한 작업          
                            const form = document.querySelector('form[name="fwrite"]');
                          //const form             = document.getElementById('fwrite');
                            const submitButton     = document.getElementById("btn_submit");
                            const firstFrmFile     = document.querySelector(".frm_file"); // 첫 번째 `frm_file` 요소 선택 
                            const thumbFileInput   = form.querySelector('input[name="thumbnail_file[]"]');//썸네일 인풋
                            
                            // 파일 인풋이 없을 경우에만 새로운 파일 인풋을 생성
                            if (!thumbFileInput) {
                                const inputFile    = document.createElement("input"); // 새로운 input 생성
                                inputFile.type     = "file";                          // 파일 타입 지정
                                inputFile.multiple = true;                            // 여러 파일 선택 가능
                                inputFile.accept   = "image/jpeg, image/png";         // 허용되는 파일 형식
                                inputFile.name     = "thumbnail_file[]";              // 서버에 전달될 이름 (배열 형식으로 보내기)
                                inputFile.disabled = true;                            // 읽기 전용으로 설정 (disabled 속성)

                                // 폼에 추가
                                firstFrmFile.parentNode.insertBefore(inputFile, firstFrmFile.nextSibling);
                            }

              
                            // 3. Blob을 File 객체로 변환
                            const thumbFile = new File([blob], file.name, { type: 'image/jpeg' });
		                    let eq_idx = obj_this.eq_idx ?? 0;

                            if (!Array.isArray(obj_this.thumb_Ary[eq_idx].thumb_new_files)) {
                                obj_this.thumb_Ary[eq_idx].thumb_new_files = [];  // 배열이 아니면 초기화
                            }
                            // 파일 추가
                            obj_this.thumb_Ary[eq_idx].thumb_new_files.push(thumbFile);
                                                    
                           // 썸네일을 저장하기 위한 작업
                        }, 'image/jpeg', 0.7);
                    }
                };
                img.src = e.target.result;
            };
            reader.readAsDataURL(file);
        },

		/*=========================================================================================
		 * 7. 파일 추가, 삭제, 대표 썸네일 선택 관련
         *
		 *   dTa_Ary :: 클릭시 마다 템프에 올라온 파일의 데이터를 해당 배열로 옮겨서 관리한다 
		 * - data-code 생성
		 * - dTa_Ary 에 업로드된 템프 데이터 누적
		 * - 처리 과정 
		 *    innit -> changeEvent -> newFileAdd    -> newAddDom    -> listAddDom
		 *    innit ->       ->    -> modifyFileAdd -> modifyAddDom -> listAddDom
         *
         
         newFileAdd
         modifyFileAdd  두개를 통합할 것
		**---------------------------------------------------------------------------------------*/        
        newFileAdd : function(event, add_num){
			const obj_util       = MARI.file_uploder.util;
            const obj_this       = MARI.file_uploder.code; 
            const objLang        = MARI.file_uploder.langObj[obj_this.country];  // 언어 객체에서 선택된 언어 가져오기
			const limitOneSize   = obj_this.limitOneSize;                   // 파일 하나당 업로드 리미트 사이즈
			const limitTotalSize = obj_this.limitTotalSize;                 // 파일 업로드 전체 리미트 사이즈
            let eq_idx           = obj_this.event_eq(event);                //사용 가능하게 설정된 업로더에서의 인덱스 
			let temp_Ary         = MARI.file_uploder.code.temp_Ary[eq_idx]; // dTa_Ary에 담기 위한 임시 배열 ( 파일 업로드 행위마다 리셋 )
			let dTa_Ary          = MARI.file_uploder.code.dTa_Ary[eq_idx];
 			let sum_size         = MARI.file_uploder.code.totalBiteSize[eq_idx] || 0;  // 누적 사이즈( 로드되면서 저장용 데이터의 사이즈를 넣는다 )
			let date             = new Date(); 
			let data_time        = date.getTime(); 
			let get_codeAry      = obj_this.dataCodeAry;                   // 데이터 코드가 들어갈 자리를 만든다 isArrayExists 변수에서 사용한다
            let newCnt           = (dTa_Ary.new_files && dTa_Ary.new_files.files) ? parseInt(dTa_Ary.new_files.files.length) : 0;    // 새로운 파일로 담긴 파일 갯수
			let modifyCnt        = parseInt(dTa_Ary.modify_files.length);  // 수정용 파일로 담긴 파일 갯수
			let file_cnt         = parseInt(temp_Ary.length);              // 파일 업로더 전체 갯수
            let totalCnt         = modifyCnt + newCnt + file_cnt;          // 수정용 + 새로 올린거 + 새로 올려 놓은 받아 놓은 것 

			/*===================================
             * 업로드시 템프에서 temp_Ary 배열에 들어가 있고
             * 들어간 수 만큼을 이곳으로 넘겨 받아( add_num )
             * 따로 들어갈 배열에 다시 만들어 넣는다( dTa_Ary )
             *
			 * 들어온 파일을 누적 시킨다  
			 * 업로더에서 템프로 옮길수있는 전체 갯수가 add_num;
			 * 전체 올린 갯수 혹은 전체중에 일부 갯수를 뜻함
			**---------------------------------*/
            let li_obj     = obj_this.li_obj(eq_idx);
            let listAddNum = li_obj.length + add_num;

            let isUpdate = false;
		 	for(let a=0; a < add_num; a++){ 
	 			let one_size  = obj_util.bite_to_mega(temp_Ary[a].size);
                let extention = temp_Ary[a].name.substring(temp_Ary[a].name.lastIndexOf(".")+1);//파일 확장자
				let lowerExt  = extention.toLowerCase();//파일 확장자

				if( one_size > limitOneSize ){ 
					let minus_up   = one_size - limitOneSize;
					let minus_up2  = Number(minus_up).toLocaleString();
					//let msg  = '';
					//msg += '파일 크기가 큽니다 줄이거나 다른 파일을 올리세요\n';
					//msg += '파일명 : ' + temp_Ary[a].name + ' ( 약 ' + minus_up2 + 'MB 오버 )';
                    let msg = objLang.txt7(temp_Ary[a].name,  minus_up2);
					alert(msg);
                    
                    // 히스토리 입력
                    obj_this.historyEvent(eq_idx,  '하나의 파일 업로드 크기를 넘어선 업로드 방지', 'red');
                    isUpdate = false;
					break;
				}
                
                let tempSumSize = sum_size;
				sum_size += temp_Ary[a].size; // 선택된 파일의 사이즈를 누적 시킨다

				/*===================================
				 * 최대 업로드 사이즈를 넘는다면 누적을 막는다
				**---------------------------------*/
                // 기존 누적과 업로드 파일을 합한
                let total_mega_size  = obj_util.bite_to_mega(sum_size);  // 토탈 업로드 파일 소숫점 이하 세자리 까지 
                let limitBite  = obj_util.mega_to_bite( limitTotalSize ); // 리미트 토탈 메가 바이트를 바이트로 변경 

				if(sum_size > limitBite){// 합산값이 정해진 토탈값을 넘어선다면:: 기준값 메가 바이트
                    // 기존 누적과 업로드 파일을 합한
                    let total_mega_size  = obj_util.bite_to_mega(sum_size);  // 토탈 업로드 파일 소숫점 이하 세자리 까지
                    let total_killo_size = obj_util.killo_to_mega(sum_size); 
                    let total_size       = ( total_mega_size > 1 ) ? total_mega_size : total_killo_size;
                    let total_units      = ( total_mega_size > 1 ) ? 'MB' : 'KB'; // 표기 파일 크기
                    
                    // 업로드 하려는 현재의 파일
                    let now_mega_size    = obj_util.bite_to_mega( temp_Ary[a].size);
                    let now_killo_size   = obj_util.killo_to_mega( temp_Ary[a].size);
                    let now_size         = ( now_mega_size > 1 ) ? now_mega_size : now_killo_size;
                    let now_units        = ( now_mega_size > 1 ) ? 'MB' : 'KB';
                    
                    // 기존 누적 파일
                    let temp_mega_size   = obj_util.bite_to_mega( tempSumSize);
                    let temp_killo_size  = obj_util.killo_to_mega( tempSumSize);
                    let temp_size        = ( temp_mega_size > 1 ) ? temp_mega_size : temp_killo_size;
                    let temp_units       = ( temp_mega_size > 1 ) ? 'MB' : 'KB';
                    
                    // 리미트에서 넘어선 값
                    let limit_over_bite  = sum_size - obj_util.mega_to_bite( limitTotalSize ); // 리미트 토탈 메가 바이트를 바이트로 변경 
                    let over_mega_size   = obj_util.bite_to_mega(limit_over_bite)+'MB'; 
                    
				    //let msg = '';
					//msg += '전체 업로드 사이즈가 너무 큽니다\n\n';
					//msg += '합계 업로드 : ' + total_size + total_units  + '\n';
					//msg += '오버 사이즈 : ' + over_mega_size  + ' ( '+ limit_over_bite +'byte )\n';
					//msg += '다른 파일을 삭제하거나 용량을 줄이세요\n'; 
                    let msg = objLang.txt8(total_size, total_units, over_mega_size, limit_over_bite); 
					alert(msg);

                    // 히스토리 입력
                    obj_this.historyEvent(eq_idx,  '전체 업로드 파일 크기를 넘어선 업로드 방지', 'red');
                    isUpdate = false;
					break;
				}
 
				/*===================================
                 * ※ 업로드 가능한 확장자로 1차 필터링
				**---------------------------------*/
                let loaderMain       = obj_this.loader_main_obj(eq_idx); 
                let inputElem        = loaderMain.querySelector('input.frm_file'); 
                let acceptAllowedExt = inputElem.getAttribute('accept'); 

                // 허용된 확장자 또는 MIME 타입 체크 

                // 1. 들어온 acceptAllowedExt 을 콤마 기준으로 루프를 돌린다
                let loopPass = false;
                let extArray = acceptAllowedExt.split(','); // 콤마 기준으로 문자열을 배열로 분리 
                let len = extArray.length;
  
                for (let i=0; i<len; i++) {
                    let ext = extArray[i].trim(); // 대조 허용 확장자
              
                    // 2. 대조하는 확장자에 /* 이 포함되어 있으면 마임 타입으로 검사 없으면 개별 확장자로 검사하기 위한 분류
                    if (ext.includes('*/*')) { // 마임 타입으로 검사 
                        loopPass = true;
                        continue;
                    }

                    if (ext.includes('/*')) { // 마임 타입으로 검사 
                        // 파일의 마임과 대조 마임이 같다면 비교 
                        let fileType = temp_Ary[a].type;
                        let extFlag  = ext.split('/')[0];      // 비교 대상의 사용자가 지정한 확장자
                        let typeFlag = fileType.split('/')[0]; // 업로드 파일의 마임 타입 

                        if( extFlag === typeFlag){ // 지원하는 마임이 존재한다면 업로드 할수 있도록
                            loopPass = true;
                        }
                    } else { // 확장자로 검사
                        if( ext == '.'+lowerExt ){
                            loopPass = true;
                        }
                    }
                }

                if( loopPass === false ){ 
                    const acceptExtList = acceptAllowedExt.replace(/\./g, '').split(',').join(', ');
                    //alert(lowerExt + ' 파일은 사용할 수 없습니다.\n\n업로드 가능한 파일\n[ '+acceptExtList+' ]');
                    alert(objLang.txt9(lowerExt, acceptExtList));
                    isUpdate = false;
                    break;
                }


                /*===============================================
                 * 지원하는 마임 타입인지 검사로 2차 필터링
                // ( 확장자명만 바꿀 경우 거르기 위해서 )
                **----------------------------------------------*/
                const file    = temp_Ary[a]; 
                const reader  = new FileReader();
                reader.onload = function(e) {
                    const buffer = new Uint8Array(e.target.result);
                    const header = new TextDecoder().decode(buffer.slice(0, 4));
                    let mimeType = file.type; 
                    if (!mimeType && header === "8BPS") { // 포토샵psd인 경우 빈 문자열로 나오기도 함
                        mimeType = "image/vnd.adobe.photoshop";
                    }
                    const hex = buffer.slice(0, 4).map(b => b.toString(16).padStart(2, '0')).join('').toUpperCase(); 
                    // HWP (한컴 오피스 구버전)
                    if (hex === 'D0CF11E0') {
                        return callback('application/x-hwp'); // HWP 확정
                    }
                    // ZIP 기반 파일인지 확인 (HWPX 가능성)
                    if (hex === '504B0304') {
                        return callback('application/zip'); // HWPX 가능성 있음 (추가 검증 필요)
                    }
                }; 
                reader.readAsArrayBuffer(file.slice(0, 4)); // 처음 4바이트만 읽음
 
 
                /*===============================================
                 * 파일명 검사로 3차 필터링
                // ( xss 공격 가능성 있는 특수문자 거르기 위해서 )
                **----------------------------------------------*/ 
                // const fileName = file.name.replace(/[^a-zA-Z0-9가-힣.\-_]/g, ""); // 허용 문자만 남기기
 
                // 한글, 영어, 프랑스어, 독일어, 러시아어, 아랍어, 일본어, 중국어, 베트남어, 몽골어, 태국어 허용
                const sanitizedFileName = file.name.replace(/[^a-zA-Z0-9\uAC00-\uD7A3\u00C0-\u017F\u0400-\u04FF\u0600-\u06FF\u3040-\u30FF\u4E00-\u9FFF\u1E00-\u1EFF\u0E00-\u0E7F\u1820-\u18AF\u0102-\u01A0.\-_ ,;!@#$%^&*()+=<>?\/\[\]{}|~]/g, ""); 

                if (file.name !== sanitizedFileName) {
                    //alert("🚨 파일명에 허용되지 않은 문자가 포함되어 있습니다.");
                    alert(objLang.txt10);
                    event.target.value = ""; // 파일 입력 초기화
                    isUpdate = false;
                    return false;
                }

                isUpdate = true;
            } // end for

 
            if( isUpdate == true ){
                function processTask(i, callback) { 
                    let is_update  = false; 
                    let imgLoading = false;

                    //for문 안의 내용  
                    let one_size  = obj_util.bite_to_mega(temp_Ary[i].size);
                    let dta_idx   = obj_this.dataCodeNum[eq_idx];// || '0'; // N,M에 따라 1번부터 누적 처리하는 곳에서 배열 참조때는 -1을 해준다
                    let new_code  = 'N_' + eq_idx + '_' + data_time + '_' + dta_idx; // data_time을 무작위 랜덤 숫자나 글자로 만들수도 있다
                    let extention = temp_Ary[i].name.substring(temp_Ary[i].name.lastIndexOf(".")+1);//파일 확장자
                    let lowerExt  = extention.toLowerCase();//파일 확장자
                    // get_codeAry[eq_idx]에서 new_code 가 중복인지 확인하고, 중복이면 새로운 코드를 생성
                    if (get_codeAry[eq_idx].some(item => JSON.stringify(item) === JSON.stringify(new_code))) {
                        new_code += '_' + dta_idx; // 중복인 경우 new_code 재생성
                    } else {
                        get_codeAry[eq_idx][i+modifyCnt] = new_code; // 중복이 아닌 경우 코드 배열에 추가
                    }
                    obj_this.dataCodeNum[eq_idx]++; // 코드 넘버 증가

                    /*===================================
                     * ※ 추가시 중복 파일명 처리
                    **---------------------------------*/ 
                    obj_this.beforeName = temp_Ary[i].name; // 대조에 사용할수 있는 원본 파일명을 넣는다
                    let newFileName = obj_this.uniqueFileName(event, eq_idx, 'Add', i, temp_Ary[i].name);

                    // ※ 파일명 중복으로 인해 파일명 변경을 허락하지 않았다면 리턴
                    if(newFileName === false) return false;
                    
                    let dataEditCode = 'dataDefault';
                    if(newFileName !== temp_Ary[i].name){ // 파일명 변경이 있었다면
                        dataEditCode = 'dataUpNameChange';
                    }
                    /*=================================================================================
                     * ※ 1차 데이터 처리 
                     * 새로운 파일 누적 데이터 배열에 data_time, data_idx를 덧붙여 data-code를 만들고
                     * 업로드 데이터를 누적 시킨다
                     * 기본적인 데이터만 들어가고 로드 후 불려지는 정보 데이터도 있기에 나머지 데이터들은 아래에서 처리
                     * 
                     * ※ 파일 api에서 제공하는 기본값인 아래의 값 이외는 추가된 값      
                     *   lastModified, lastModifiedDate, name, size, type, webkitRelativePath
                    **-------------------------------------------------------------------------------*/
                    /*=================================================
                     * 이미지 파일일 경우 canvas 이용 썸네일 생성 :: newFileAdd 내부임
                    **-----------------------------------------------*/   
                    if (temp_Ary[i].type.startsWith('image/') /*|| lowerExt === 'mp3'*/) { 
                        imgLoading = 'ready';
                        let fileDataUrl = obj_this.createThumbnail(temp_Ary[i], function (result) { 
                            temp_Ary[i].dataThumbnail  = result.thumbnailUrl;// 
                            temp_Ary[i].dataPicture    = result.pictureUrl;  
                            temp_Ary[i].pictureWidth   = result.pictureWidth; // 원본 이미지 너비
                            temp_Ary[i].pictureHeight  = result.pictureHeight;// 원본 이미지 너비 
                            imgLoading = true;
                        });
                    } else {
                        imgLoading = false;
                        // 이미지 외의 파일에 리스트등에 쓰일 이미지 파일                       
                        temp_Ary[i].dataThumbnail  = obj_this.moduleUrl +'/images/file_type1/'+lowerExt+'.png';// 
                        temp_Ary[i].dataPicture    = obj_this.moduleUrl +'/images/file_type1/'+lowerExt+'.png'; 
                        temp_Ary[i].pictureWidth   = '161'; // 원본 이미지 너비
                        temp_Ary[i].pictureHeight  = '184'; // 원본 이미지 너비 
                        imgLoading = true;
                    }
                    //for문 안의 내용
                    // for문
                    //callback(); // 재귀호출로 다음 i 실행

                    let chk_nums  = 0;
                    let checkLoad = setInterval(() => { 
                        if(chk_nums >= 5 || imgLoading === true){// 조건을 충족하면 :: 5 => 5번이상 루프 돌지 않도록 조절
                            if(imgLoading === true){ // 이미지가 로딩되었다면
                                is_update = true;
                               
                                // 캔버스를 통한 썸네일 생성이 이뤄진 이후 템프 데이터를 업데이트 한다:::이 작업 때문에 어렵게 돌아 돌아 온 길임..... 
                                // 필요한 후속 작업 수행 
                                temp_Ary[i].dataFileOriginalName = temp_Ary[i].name; // 바뀐 파일명으로( 중복이면 바꿔서 아니면 그대로 )
                                temp_Ary[i].dataFileName   = newFileName;      // 중복시 파일명
                                temp_Ary[i].dataCode       = new_code;         // 데이터 코드를 넣어준다
                                temp_Ary[i].dataExt        = lowerExt;         // 데이터 확장자( 마임타입 확인을 위해 넣어준다 지우지 말것 )
                                temp_Ary[i].dataMakeType   = 'New';            // 데이터 타입 구분( 새로 만든 파일인지, 수정용은 불러올때 만들거나 만들어져 있다 )
                                temp_Ary[i].dataTempIdx    = i;                // 새로운 파일을 받을때 마다 0부터 시작해 템프 배열을 참조할 때 사용한다
                                temp_Ary[i].dataType       = temp_Ary[i].type;
                                temp_Ary[i].dataEdit       = dataEditCode;     // 고치지 않은::Default ( 파일명 겹침등으로 고침이 있으면 Edit로 변경 )

                                // ※ 데이터 배열에 누적 
                                MARI.file_uploder.code.dTa_Ary[eq_idx].new_files.items.add( temp_Ary[i] );

                                // 히스토리 입력
                                obj_this.historyEvent(eq_idx, '[ ' + temp_Ary[i].name + ' ] 작업용 new_files 배열 '+i+'번에 누적', 'blue');

                                // 디버깅 
                                obj_this.log(eq_idx, ':: temp_Ary => ', MARI.file_uploder.code.temp_Ary[eq_idx][i]); 

                                /*===============================================
                                 * 확장자에 따른 분류를 통해서 돔을 추가해 준다
                                **----------------------------------------------*/ 
                                obj_this.newAddDom(event, i, listAddNum);//listAddNum => 리스트 갯수와 추가된 갯수까지해서 총합(리스트상에서)

                            } else {
                                is_update = false;
                                obj_this.log( '이미지 생성이 되지 않았습니다 ' );
                            }
                            clearInterval(checkLoad); // 완료되면 반복 중지   
                        }

                        chk_nums++; //setInterval의 실행 횟수를 체크를 위해서 사용한다

                        if (is_update === true) { // 실행이 끝나면 
                            chk_nums = 0;
                            callback(); // 재귀호출로 다음 i 실행
                        }
                    }, 50); // 50ms 간격 실행하면서 캔버스에서 이미지 생성이 되어 자료가 콜백되었는지 확인한다 
                    
                }// end for

                function runSequentially(i, max) {
                    if (i >= max) return; // 종료 조건  
                    processTask(i, () => runSequentially(i + 1, max)); // i가 끝나면 다음 i 실행
                }
                runSequentially(0, add_num); // add_num 개수만큼 순차 실행 
 
            }// end if

        }, // newFileAdd 
 

		/*=========================================================================================
		 * 8. 수정용 파일 추가( 업로드된 갯수만큼 누적용 데이터 배열에 입력 )
         *
         *   돔 생성 이후에 실행되는 코드 
		 *   dTa_Ary :: 수정용으로 불러온 데이터 누적
		 * - dTa_Ary 에 업로드된 템프 데이터 누적
		 * - 처리 과정 
		 *    innit -> changeEvent -> newFileAdd    -> newAddDom    -> listAddDom
		 *    innit ->     ->      -> modifyFileAdd -> modifyAddDom -> listAddDom
         * 
		**---------------------------------------------------------------------------------------*/
		modifyFileAdd : function(){ // 업로드시 클릭등의 이벤트가 없으므로 직접 코드를 할당한다
			let obj_util     = MARI.file_uploder.util;
			let obj_this     = MARI.file_uploder.code; 
            let objLang      = MARI.file_uploder.langObj[obj_this.country];  // 언어 객체에서 선택된 언어 가져오기
            let loaderMain   = document.querySelectorAll(obj_this.loader_main_cls);
            let inputElems   = document.querySelectorAll(obj_this.input_obj);
			let loaderUseCnt = loaderMain.length;  
			/*=====================================================================================
			 * ※ 중요한 프로세스라 이곳에 히스토리를 남긴다 2024.08.25 
			 *
			 * dTa_Ary :: 클릭시 마다 템프에 올라온 파일의 데이터를 해당 배열로 옮겨서 관리한다 
			 * 차이점     :: New file => 업로드 이벤트 발생시,  Modify file => 문서 온로드시 즉시 사용가능하도록
			**-----------------------------------------------------------------------------------*/ 
            // 사용 가능하게 완료된 모듈의 갯수만큼 돌린다
			for(let i=0; i< loaderUseCnt; i++){
                let eq_idx     = i; //사용 가능하게 설정된 업로더에서의 인덱스  
                let totalCnt  = obj_this.file_count_return(eq_idx, 'totalCnt');                      // 파일 업로더 전체 갯수
 
				// 수정용 데이터 배열을 가지고 dTa_Ary 배열에 들어가는 info_ary를 만든다
				// ※ 데이터 배열에 누적
                if( modifyFile_Ary[i]){ 
 	  		  	    MARI.file_uploder.code.dTa_Ary[i].modify_files = modifyFile_Ary[i].file_ary;
                    let modifyLen = modifyFile_Ary[i].file_ary.length;
                    for(let j=0; j<modifyLen; j++){
                        MARI.file_uploder.code.dataCodeAry[i][j] = modifyFile_Ary[i].file_ary[j].dataCode; 
                    
                        /*===================================
                         * ※ 추가시 중복 파일명 처리
                        **---------------------------------*/ 
                        let savedFileName   = modifyFile_Ary[i].file_ary[j].dataFileOriginalName; // 디비에 저장된 name은 그대로 두고 dataFileOriginalName을 이용한다
                        obj_this.beforeName = savedFileName; // 대조에 사용할수 있는 원본 파일명을 넣는다 
                        let newFileName = obj_this.uniqueFileName(event, eq_idx, 'Modify', j, savedFileName);  
                        // ※ 파일명 중복으로 인해 파일명 변경을 허락하지 않았다면 리턴
                        if(newFileName === false) break;
                        let dataEditCode = 'dataDefault';
                        if(newFileName !== savedFileName){ // 파일명 변경이 있었다면
                            dataEditCode = 'dataUpNameChange';
                        } 
                             
                        modifyFile_Ary[i].file_ary[j].dataFileName =  newFileName;
                    }
                // 아작스로 수정 데이터를 불러들일 수 있도록 추후 고칠 것 
                }
  
                // 루프 안에서 돔을 생성 사용하기 위해서
                obj_this.eq_idx = i;// 현재의 eq_idx 를 자동 갱신 
                /*===============================================
                 * 확장자에 따른 분류를 통해서 돔을 추가해 준다
                **----------------------------------------------*/
                let add_num = modifyFile_Ary[i].file_ary.length;
				obj_this.modifyAddDom(i, totalCnt, add_num);// 파일 한개당 한번씩 호출됨 최적화 생각할 것  
			}

            // 루프를 돌면서 사용후 다시 클릭하기 전까지 null을 유지
            obj_this.eq_idx = null;// 현재의 eq_idx 를 자동 갱신
		}, // end modifyFileAdd


		/*=========================================================================================
		 * 9. 새로운 리스트 돔을 만든다 파일 추가 분리

        // 새로 추가시 ::: init -> changeEvent -> newFileAdd    -> newAddDom    -> listAddDom ( info_ary )
        // 수정 추가시 ::: init ->      ->     -> modifyFileAdd -> modifyAddDom -> listAddDom ( info_ary )
		**---------------------------------------------------------------------------------------*/
		newAddDom : function(event, tempNum, add_num){// 파일 한개당 한번씩 호출됨 최적화 생각할 것

			const obj_util       = MARI.file_uploder.util;
            const obj_this       = MARI.file_uploder.code;
            const objLang        = MARI.file_uploder.langObj[obj_this.country];  // 언어 객체에서 선택된 언어 가져오기
            let eq_idx           = obj_this.event_eq(event);  // 사용 가능하게 설정된 업로더에서의 인덱스 
            let temp_Ary         = obj_this.temp_Ary[eq_idx]; // dTa_Ary에 담기 위한 임시 배열 ( 파일 업로드 행위마다 리셋 )
			// temp_Ary가 1개만 받아왔을때는 0으로 그 이상일 때는 순차적으로
	 		if( temp_Ary[0] === undefined ){ //최소 한개를 검사해서
                alert(objLang.txt11);
				return false;
			}
			/*=====================================================================================
			 * 1.돔을 리스트에 추가해 준다
			 * 추가된 돔에 파일 데이터는 다음 과정에 이후에 넣는다
			 * 파일의 사이즈에 따라서 먼저 로드되고 나중에 로드되면서 순서가 바뀌게 되니 이렇게 먼저 자리를 순서에 맞게 넣어주고
			 * 그 이후에 데이터를 각각 넣어준다
			**-----------------------------------------------------------------------------------*/
            let dTaAry    = MARI.file_uploder.code.dTa_Ary[eq_idx] || null; // New,Modify가 담기는 데이터 배열
            let newFile   = dTaAry.new_files.files || null; 
            let tempFile  = temp_Ary || null;                      // dTa_Ary에 담기 위한 임시 배열 ( 파일 업로드 행위마다 리셋 )
            let modifyCnt = (modifyFile_Ary[eq_idx] && modifyFile_Ary[eq_idx].file_ary) ? modifyFile_Ary[eq_idx].file_ary.length : 0; // 수정용 파일 갯수
            let lastNum   = tempFile[tempNum].dataCode.split('_').pop();
            let info_ary = { // newFileAdd에서 추가시 지정했던 4개의 속성
                dataFileOriginalName : tempFile[tempNum].dataFileOriginalName,
                dataFileName   : tempFile[tempNum].dataFileName,
                dataCode       : tempFile[tempNum].dataCode,
                dataExt        : tempFile[tempNum].dataExt,
                dataMakeType   : tempFile[tempNum].dataMakeType,
                dataTempIdx    : tempFile[tempNum].dataTempIdx,
                dataType       : tempFile[tempNum].type, 
                dataEdit       : tempFile[tempNum].dataEdit, // 고치지 않은::Default 고침이 있으면 Edit로 변경 
                dataPicture    : tempFile[tempNum].dataPicture,
                dataThumbnail  : tempFile[tempNum].dataThumbnail,   
            }; // 객체 초기화  

            //※※※ info_ary를 처음으로 넘기게 된다 ※※※
            // 데이터의 형태는 3가지로 분류된다
            // 1. Add    :: 새로 받아 템프에 있는 파일 ( 다시 받게 되면 New에 저장된다 )
            // 2. New    :: 템프에 받아 두었던 새로 받기 위해서 옮겨 놓은 이전 파일들과 템프
            // 3. Modify :: 서버에 저장된 파일들
            
            // 데이터 추가할 때는 Add, 추가 이후는 New로 데이터를 접근하게 된다  
  		    obj_this.listAddDom(event, tempNum, 'Add', info_ary, add_num); // 기본 데이터를 넘긴다
            // 히스토리 입력
            obj_this.historyEvent(eq_idx,  '새로운 파일 입력 [ ' + lastNum + '번째 ]');

		}, // newAddDom

		/*=========================================================================================
		 * 10. 수정용 리스트 돔을 만든다 파일 추가 분리
         *    수정용 데이터엔 이미 필요한 데이터들이 들어 있으므로 속성 추가등을 할 필요가 없다
         *
         *    새로 추가시 ::: init -> changeEvent -> newFileAdd    -> newAddDom    -> listAddDom
         *    수정 추가시 ::: init ->      ->     -> modifyFileAdd -> modifyAddDom -> listAddDom 
		**---------------------------------------------------------------------------------------*/
        modifyAddDom : function(eq_idx, totalCnt, add_num){ // 파일 한개당 한번씩 호출됨 최적화 생각할 것
            const obj_this = MARI.file_uploder.code;
            const objLang  = MARI.file_uploder.langObj[obj_this.country];  // 언어 객체에서 선택된 언어 가져오기
            if( !modifyFile_Ary[eq_idx] )return false;// 데이터가 없다면
			let fileCnt    = modifyFile_Ary[eq_idx].file_ary.length||0;         // 각 업로더에 올려져야할 파일 갯수

            // 리스트 돔을 만든다 
            for(let j=0; j<fileCnt; j++){
                let info_ary = {}; // 객체 초기화 
                info_ary = modifyFile_Ary[eq_idx].file_ary[j]; // 객체 덮어 씌우기
                obj_this.listAddDom(event, j+1, 'Modify', info_ary, add_num); // 수정용 파일을 추가한다  돔 생성과정에서 info_ary가 필요함 
            }

        }, // modifyAddDom  


		/*=========================================================================================
		 * 11. 리스트 추가( 새로운 파일 ) 
		 *    event, eq_idx, callNum, 'new', info_ary
         *    Modify에서 event를 사용 않는 이유는 로드시 바로 실행이 이뤄지며 마우스 클릭등의 이벤트가 없으므로
         *
         *    새로 추가시 ::: init -> changeEvent -> newFileAdd    -> newAddDom    -> listAddDom
         *    수정 추가시 ::: init ->      ->     -> modifyFileAdd -> modifyAddDom -> listAddDom 
		**---------------------------------------------------------------------------------------*/
        listAddDom : function(event, listIdx, objType, info_Ary, add_num){

            let obj_util   = MARI.file_uploder.util; 
            let obj_this   = MARI.file_uploder.code;
            const objLang  = MARI.file_uploder.langObj[obj_this.country];  // 언어 객체에서 선택된 언어 가져오기
            let eq_idx     = obj_this.event_eq(event);
            let loaderMain = obj_this.loader_main_obj(eq_idx);
            let dataCode   = info_Ary.dataCode;
            let lastNum    = (dataCode && typeof dataCode === 'string') ? dataCode.split('_').pop() : null;  
            let modifyCnt  = (modifyFile_Ary[eq_idx] && modifyFile_Ary[eq_idx].file_ary) ? modifyFile_Ary[eq_idx].file_ary.length : 0; // 수정용 파일 갯수

            if( !dataCode ){
                //alert(listIdx  + '알 수 없는 속성의 파일입니다.\n확인 후 올려 주세요'); 
                alert(objLang.txt12(listIdx));
                return false;
            }

            // 리스트 레이아웃 호출
            let list_dom   = obj_this.listLayout(eq_idx, dataCode); 
            let fileUpMsg  = loaderMain.querySelector(obj_this.file_up_msg_cls);
            let fileList   = loaderMain.querySelector(obj_this.file_list_cls);  
            let totalCnt   = obj_this.file_count_return(eq_idx, 'totalCnt'); // 파일 업로더 전체 갯수 
            let reName     = info_Ary.dataFileName; // 변경된 파일 이름
            let fileName   = reName != 'undefined' ? reName : info_Ary.name; // 파일 이름

            let upTotalCnt = obj_this.file_count_return(eq_idx, 'upTotalCnt'); // 파일 업로더 전체 갯수 ( 올려지고 있는 시점이므로 )
            // 파일을 마우스로 끌어오세요 안내글 삭제
            if(totalCnt > 0){
                if( fileUpMsg ) fileUpMsg.remove(); 
            }
 
            // 오류 있을 경우 계속 돔 추가하는거 방지
            let liObj   = obj_this.li_obj(eq_idx); 
            //console.log(add_num,  liObj.length+1 );
            //if(add_num < liObj.length + 1){ // 추가해준 것 보다 리스트가 더 추가가 되고 있다면( 스크립등의 오류로 )
            //    alert('리스트 추가시 오류가 발생하고 있습니다\n추가하려는 파일이 등록된 유형의 파일이 아닐수 있습니다 확인하세요');
            //    console.log( '리스트 추가시 오류 발생' );
            //    return false;
            //}

            // 돔 추가 ( html 문자열을 넣는 형태로 생성한다 ) 
            fileList.insertAdjacentHTML('beforeend', list_dom); 

            // 돔 추가후에 리스트
            let li_obj   = obj_this.li_obj(eq_idx);

            // 리스트의 최대 넓이를 리스트 갯수에 맞춰서 지정한다
            obj_this.getListMaxWidth(eq_idx);

            // 파일 데이터 속성을 넣어줌   
            obj_this.add_atribute(event, eq_idx, objType, listIdx, info_Ary, add_num);
 
            // 히스토리 입력
            obj_this.historyEvent(eq_idx, ( listIdx ) + '번 파일 업로드 [ ' + objType + ' - ' + fileName + ' ]', 'blue');

            // 미리보기 드래그바 보이기 또는 감추기 활성 여부
            obj_this.drag_bar_line_dp(event, eq_idx);
            /*===============================================
             * modify 는 여러 업로드에 배분해서 올려야 하기에 이중 for 문이지만
             * new는 업로드 해야할 곳을 지정해서 그 곳만 올리기에 단일 for 문이다
            **----------------------------------------------*/
            // 업로드가 끝난 마지막에 에니메이션등 기능 호출 
            if(upTotalCnt === listIdx + 1){// 끝난 시점에 대조하기 위함이므로 수정용 데이터 더하기 받아진 템프 데이터 갯수를 기준으로 한다
                let inputThumb = document.querySelectorAll("input[name=list_thumbnail]");
                let val_idx    = inputThumb[eq_idx].value;// || 0;
                
                // OK   => html상 인풋의 list_thumbnail의 value 값이 리스트의 갯수 이내에 있어야 오류가 없음 
                // 로딩될 시간의 여유를 준다
                let timeoutId  = setTimeout(function() { 
                    obj_this.thumb_choice(eq_idx, val_idx); // 썸네일 선택
                }, 250);
            }

            /*===============================================
             * 특정시간 이후 미리보기 자동 오픈을 실행
            **----------------------------------------------*/ 
            if(obj_this.openViewDIv ==='Y' && totalCnt == listIdx){ 

                obj_this.drag_flag       = false;
                obj_this.mouse_down_flag = false;

                let timeoutId = setTimeout(function () {
                    // .file_view_type1 클래스를 가진 첫 번째 요소에 mouseup 이벤트 트리거
                    const targetElement = document.querySelector('.file_view_type1');
                    if (targetElement) {

                        //특정된 리스트 선택
                        let choiceNum = li_obj.length - 1;

                        /*======================
                         * 미리보기 버튼 클릭
                        **--------------------*/
                        // 수동으로 mouseup 이벤트 발생
                        const mouseUpEvent = new MouseEvent('mouseup', {
                            bubbles    : true,    // 이벤트가 버블링되도록 설정
                            cancelable : true, // 이벤트가 취소 가능한지 설정
                            // 특정 위치를 넣는다면 아래와 같이      
                            // clientX : 10,    // 마우스 클릭 위치 (가로)
                            // clientY : 15     // 마우스 클릭 위치 (세로)
                        });
                        
                        //이벤트 한번만 실행
                        document.addEventListener('mouseup', function (event) { 
                            // ※ 미리 보기 선택, 상세 보기 선택
                            if(obj_this.preview_mode[eq_idx]==='open'|| obj_this.detail_mode[eq_idx]==='open'){ 
                                let timeoutId2 = setTimeout(function () { //지연 시간후 선택( 레이어 열리는 시간 )   
                                       obj_this.file_preview_act(event, eq_idx, choiceNum, 'one');   
                                       MARI.file_uploder.code.view_img_idx[eq_idx] = choiceNum;  

                                       // 미리보기 이미지 콘트롤러 액션
                                       obj_this.imgControllerAct(event, eq_idx, choiceNum); 
                                }, 550);
                            }
                            
                        }, { once: true }); 
                        // mouseup 이벤트를 targetElement에 디스패치
                        targetElement.dispatchEvent(mouseUpEvent);  // mouseup 이벤트가 발생한 것처럼 처리됨 
  
                        
                        /*======================
                         * 미리보기 개체 선택 클릭
                        **--------------------*/
                        const mouseDownEvent = new MouseEvent('mousedown', {
                            bubbles    : true, // 이벤트가 버블링되도록 설정
                            cancelable : true, // 이벤트가 취소 가능한지 설정 
                        });
                        //이벤트 한번만 실행
                        document.addEventListener('mousedown', function (event) {   
                            obj_this.drag_flag       = false;
                            obj_this.mouse_down_flag = false;

                            li_obj.forEach(el => el.className = 'one_file');     // 모든 걸 초기화
                            li_obj[choiceNum].className = 'one_file thumb_chk1'; // ▣
                            obj_this.set_drag_select_ary(event, objType, 'one', eq_idx, choiceNum, 'thumbnail', 'dragAct11');  
                            obj_this.click_idx[eq_idx] = choiceNum;


                            /*===================
                             * 네비게이션 이미지
                            **-----------------*/
                            let timeoutId = setTimeout(function () {// 데이터들이 로드될 시간을 준다
                                // 미리보기 이미지 네비게이션  ::: imgControllerAct > 2140 라인즈음
                                let naviImg  = loaderMain.querySelector('.naviImg');     // 미리보기 썸네일 가이드 이미지    
                                let getDatas = obj_this.getFileInfo2(eq_idx, objType, choiceNum, 'All', '195'); // 데이터
    console.log( '=================================' );
                                if(naviImg && getDatas  && getDatas.dataThumbnail !== null){
                                    console.log( getDatas.dataThumbnail );    
                                    naviImg.src  = getDatas.dataThumbnail// 미리보기 이미지  
                                }  
                            }, 550);  
                            
                            
                        }, { once: true }); 

                        // mouseup 이벤트를 targetElement에 디스패치 
                        li_obj[choiceNum].dispatchEvent(mouseDownEvent);  // mouseup 이벤트가 발생한 것처럼 처리됨 

                    } 
                }, 550);  
            } // end if
            
        },
        
		/*=========================================================================================
		 * 12. 파일 데이터 속성을 넣어줌
         *
         * 데이터에서 현재까지는 수정용 파일은 전부 불러오고 새로운 파일들은 기본 정보만 들어간 상태이다  
         * 현재까지의 datas에 저장된 내용 ataAryIdx, dataCode, dataExt, dataFileOriginalName, dataFileName, dataMakeType  
         *
         * 새로 추가시 ::: init -> changeEvent -> newFileAdd    -> newAddDom    -> listAddDom -> add_atribute
         * 수정 추가시 ::: init ->      ->     -> modifyFileAdd -> modifyAddDom -> listAddDom -> add_atribute
         * 
         *  ※ 파일을 사용하기 위한 등록
         *  다음의 세군데에서 등록이 이뤄져야 파일 업로드가 된다
         *  obj_this.add_atribute     :: ( multi_uploader_code.js )
         *  obj_this.file_preview_act :: ( multi_uploader_dragAction.js)
         *  obj_this.setDataAttr_flac :: ( multi_uploader_setDataAttribute.js)
		**---------------------------------------------------------------------------------------*/ 
		add_atribute : function(event, eq_idx, objType, listIdx, info_Ary, totalcnt){// 파일 한개당 한번씩 호출됨 최적화 생각할 것
            
            let obj_util    = MARI.file_uploder.util; 
            let obj_this    = MARI.file_uploder.code;  
            const objLang   = MARI.file_uploder.langObj[obj_this.country];  // 언어 객체에서 선택된 언어 가져오기
            let tempAry     = MARI.file_uploder.code.temp_Ary[eq_idx] || null;                      // dTa_Ary에 담기 위한 임시 배열 ( 파일 업로드 행위마다 리셋 )
            let dTaAry      = MARI.file_uploder.code.dTa_Ary[eq_idx] || null;                       // New,Modify가 담기는 데이터 배열
            let newFile     = dTaAry.new_files.files || null;                                       // New 파일이 담기는 
            let modifyFile  = dTaAry.modify_files || null;

            // Modify 파일이 담기는 
            let aryIndex    = (objType =='Add') ? listIdx : listIdx -1;  // 들어온 데이터 코드 기준으로 배열에 들어간 인덱스를 가져 온다
            let loaderMain  = obj_this.loader_main_obj(eq_idx);
            let ul_obj      = obj_this.ul_obj(eq_idx);     // .file_list 클래스의 ul  
            let li_obj      = obj_this.li_obj(eq_idx);
            let search_chk1 = ul_obj.querySelectorAll('.thumb_chk1');
            let totalCnt    = obj_this.file_count_return(eq_idx, 'totalCnt');  // 업로드 전체 갯수
            let lastIdx     = totalCnt - 1;                                                        // 마지막 배열에 들어갈 인덱스
            let modifyCnt   = obj_this.file_count_return(eq_idx, 'modifyCnt'); // 파일 업로더 전체 갯수 
            let addAryIdx   = modifyCnt -1;
            let listIdx2    = (objType === 'Add')    ? lastIdx  : aryIndex + (objType === 'Modify' ? 0 : modifyCnt);
            let lastIdx2    = (objType === 'Modify') ? aryIndex : li_obj.length - 1; 
            let objType2    = (objType === 'Modify') ? 'Modify' : 'New';

            if(obj_this.allChoice === 'Y'){ // 모두 선택시
                if (li_obj[listIdx2]) {
                    search_chk1.forEach(el => el.className = 'one_file thumb_chk3'); // ▣ -> ■ ( .thumb_chk1으로 된 기존 선택된 것을 보더 없는 선택으로 변경 )
                    li_obj[listIdx2].classList.add(lastIdx === listIdx2 ? 'thumb_chk1' : 'thumb_chk3'); // li_obj 배열이므로 0부터
                }
                obj_this.before_click_idx[eq_idx] = aryIndex; // 클릭한 곳 ( 새로운 것이 들어오면서 갱신 된다 ) 

                // 위에서 선택된 것에 맞게 선택된 것을 배열에 넣어준다 
                obj_this.set_drag_select_ary(event, objType2, 'add', eq_idx, listIdx2, 'thumbnail', 'dragAct1');
                             
            }

            /*===============================================
             * 데이터 추가 후 추가된 데이터를 불러온다
             * 아래의 방법으로 여기서 직접 추가할 수도 있다
             * info_Ary['newKey3'] = "value3"; 
             * getFileInfo로 불러오면 초기 데이터만 불러온다
            **----------------------------------------------*/
            let datas = obj_this.setFileInfo(event, eq_idx, objType, aryIndex, info_Ary);   // 데이터 셋팅 후 데이터를 리턴

            /*===============================================
             * 파일 타입이 이미지일 경우 추가 속성
             *-----------------------------------------------
             *  ※ 파일을 사용하기 위한 등록
             *  다음의 세군데에서 등록이 이뤄져야 파일 업로드가 된다
             *  obj_this.add_atribute     :: ( multi_uploader_code.js )
             *  obj_this.file_preview_act :: ( multi_uploader_dragAction.js)
             *  obj_this.setDataAttr_flac :: ( multi_uploader_setDataAttribute.js)
            **----------------------------------------------*/ 
            if (datas){

                if ( datas.dataType.match("image.*") ){  // 이미지 계열은 하나로 처리
                
                    // 추가하면서 선택배열과 클래스를 더해준다 마지막 것은 보더 입힌 것으로 
                    obj_this.setDataAttr_image(event, eq_idx, objType, aryIndex, info_Ary, listIdx2);
                }
                else{

                    let handlers = {
                        mp3   : obj_this.setDataAttr_mp3,
                        mp4   : obj_this.setDataAttr_mp4,
                        flac  : obj_this.setDataAttr_flac,
                        txt   : obj_this.setDataAttr_txt,
                        text  : obj_this.setDataAttr_txt,
                        pdf   : obj_this.setDataAttr_pdf,
                        webm  : obj_this.setDataAttr_webm,
                        xlsx  : obj_this.setDataAttr_xlsx,
                        hwpx  : obj_this.setDataAttr_hwpx,
                        hwp   : obj_this.setDataAttr_hwp,
                        avi   : obj_this.setDataAttr_avi,
                        asx   : obj_this.setDataAttr_asx,
                        asf   : obj_this.setDataAttr_asf,
                        html  : obj_this.setDataAttr_html,
                        zip   : obj_this.setDataAttr_zip,
                        tar   : obj_this.setDataAttr_tar,
                        '7z'  : obj_this.setDataAttr_7z,
                    }; 

                    if(handlers[datas.dataExt]){
                        handlers[datas.dataExt]?.(event, eq_idx, objType, aryIndex, info_Ary, listIdx2);
                    } else {
                        //alert('[ ' + datas.dataExt + ' ] 등록되지 않은 속성의 파일입니다');
                        alert(objLang.txt13(datas.dataExt));
                        return false;
                    }
                }

            }// 데이터 속성 추가

            let totalImg         = loaderMain.querySelector('.total_in_file_img'); 
            let totalBiteSize    = obj_this.totalBiteSize[eq_idx] || 0; 
            let tempAddSize      = parseInt(totalBiteSize) + parseInt(datas.dataByteSize);
            let temp_mega_total  = obj_util.bite_to_mega(tempAddSize, 'add_size'); // 합산해서 소숫점 이하 두자리 까지
            let temp_killo_total = obj_util.killo_to_mega(tempAddSize, 'add_size'); // 합산해서 소숫점 이하 두자리 까지
            let total_size       = ( temp_mega_total > 1 ) ? temp_mega_total : temp_killo_total; // 표기 파일 크기 1000이 넘어서면 메가바이트로 작으면 킬로바이트로 표기
            let file_units       = ( temp_mega_total > 1 ) ? 'MB' : 'KB'; // 표기 파일 크기
            /*===============================================
             * 하단 파일 추가 버튼 위의 업로드 갯수 풍선 알림
            **----------------------------------------------*/
            if(totalImg.style.display !=='block'){ 
                obj_util.fadeIn(totalImg, 500, 0, 'block', 1, 10, function() { 
                     obj_util.fadeOut(totalImg,  500, 4000); //지속시간, 딜레이시간 
                }); 
            }

            //합산된 것을 넣어준다
            obj_this.totalBiteSize[eq_idx] = parseInt( tempAddSize );

            // ※ 파일 업로드 전체 합산 크기와 파일 크기 단위 표시
            document.querySelectorAll('.up_size_num')[eq_idx].innerHTML   = total_size; // 파일 사이즈 숫자만
            document.querySelectorAll('.up_size_units')[eq_idx].innerHTML = file_units; // mb, kb

            // 업로드 전체 갯수 파일 업로더 버튼 위 말풍선 안에 넣어주기
            loaderMain.querySelector('.total_in_file_cnt').innerHTML = totalCnt;  
            
            // n개 삭제에 갯수 넣어주기
            if(loaderMain.querySelector('.del_file_cnt')){
                let selectAryCnt = obj_this.drag_select_ary[eq_idx]['name'].length; 
                loaderMain.querySelector('.del_file_cnt').innerHTML = selectAryCnt;   
            }
            // 업로드 전체 갯수 에니메이션
            obj_this.totalInFileAni(eq_idx, totalCnt); 

        },

		/*=========================================================================================
		 * 13. 파일 삭제하기 배열로 바꿔서 삭제후 다시 object로 변경
         *     ※ 삭제시 해당 자리에 null을 넣은 이유
         *     리스트의 속성중 data-code(데이터 코드)를 통해 데이터에 접근하는데 삭제로 인해 배열 인덱스가 데이터 코드를 통한
         *     인덱스와 맞지 않아 에러를 일으킬 수 있으므로 인덱스를 맞추기 위해 null로 데이터를 비우고 돔만 삭제한다
		**---------------------------------------------------------------------------------------*/ 
		file_delete : function(event, eq_idx, delete_idx){

			let obj_util    = MARI.file_uploder.util; 
            let obj_this    = MARI.file_uploder.code;
            const objLang   = MARI.file_uploder.langObj[obj_this.country];  // 언어 객체에서 선택된 언어 가져오기
            let objType     = obj_this.getMakeType(eq_idx, delete_idx); 
            let loaderMain  = obj_this.loader_main_obj(eq_idx);
            let li_obj      = obj_this.li_obj(eq_idx);
            let liDataCode  = li_obj[delete_idx].getAttribute('data-code'); 
            let tempAry     = obj_this.temp_Ary[eq_idx] || null;                // dTa_Ary에 담기 위한 임시 배열 ( 파일 업로드 행위마다 리셋 )
            let modifyCnt   = obj_this.file_count_return(eq_idx, 'modifyCnt'); // 수정용 파일 갯수
            let newCnt      = obj_this.file_count_return(eq_idx, 'newCnt');    // 새로운 파일 갯수
 			let setThumbIdx = 0; 
            let inputs      = document.querySelectorAll('input[name="list_thumbnail"]')[eq_idx]; 
            let thumb_value = inputs.value;

            // 데이터 관련
            let dTs         = new DataTransfer(); 
            let dTaAry      = obj_this.dTa_Ary[eq_idx]; // New,Modify가 담기는 데이터 배열
            let newFile     = dTaAry.new_files.files;   // New 파일이 담기는 
            let modifyFile  = dTaAry.modify_files;      // Modify 파일이 담기는
            
            // delete_idx를 통해 리스트의 순번을 가져오고 getListIdx에 해당 리스트에 담긴 데이터 코드에서 배열순서를 분리해 가져와 넣는다
            let getListIdx  = obj_this.getListDataAryIdx(eq_idx, delete_idx) - 1;   // 데이터 코드에는 1부터 증가이므로 빼준다
            let datas       = obj_this.getFileInfo2(eq_idx, objType, getListIdx, 'All','2347 Line'); // 데이터
 
            // 데이터 삭제
            if( objType === 'Modify' ){ // 수정용 데이터일 때 선택된 배열 
                let delModifyIdx = modifyFile.findIndex(item => item && item.dataCode === liDataCode); // modify_files에서의 인덱스 
                if (delModifyIdx !== -1) { // 찾았다면
                 // modifyFile.splice(delModifyIdx, 1, null); // 데이터 삭제 해당 자리에 null 입력
                    modifyFile.splice(delModifyIdx, 1); // 데이터 삭제( 돔 속성의 data-code로 접근시 참조되는 인덱스 값과 다르게 되서 사용 안함 )
                }
                // 히스토리 입력 
                obj_this.historyEvent(eq_idx,  obj_this.nowDatas(modifyFile), 'red');
            }
            else 
            if( objType === 'New' ){// 새로 입력 데이터일 때 선택된 배열
                // 특성상 복사및 갱신으로 처리

                // 1.dTaAry 값을 맞춰줌
                // 파일 리스트를 배열로 바꾸고 필요한 부분을 삭제후 빈공간에 빈 의미없는 파일을 넣어 다시 파일 리스트로 만들어준다
                let fileArray = Array.from(newFile);  // FileList -> Array 변환
                let delNewIdx = fileArray.findIndex(item => item && item.dataCode === datas.dataCode);

                // 2.파일 삭제 및 새로운 파일 추가
                if (delNewIdx !== -1) { // 찾았다면
                    // fileArray.splice(delNewIdx, 1, new File([''], null, { type: 'undefined' }));  //  빈 파일 추가시
                    fileArray.splice(delNewIdx, 1); // 배열에서 파일 제거
                }

                // 3. 파일배열 갱신
                let dTs = new DataTransfer(); // DataTransfer 객체 생성
                // 유효한 파일만 DataTransfer에 추가
                fileArray.forEach(file => {
                    if (file instanceof File) {
                        dTs.items.add(file);
                    }
                });
                dTaAry.new_files = dTs; // 갱신된 내용을 파일용 배열로 적용

                // 3.파일 인풋의 값을 맞춰줌
                let searchElement   = document.querySelectorAll(obj_this.input_obj)[eq_idx];
                searchElement.files = dTs.files;

            }

            // 수정일 경우만 삭제할 대상의 인덱스 결과를 넘김
            if (objType === 'Modify') {
                let delInputElement = document.querySelectorAll("input[name=submit_delete_list]")[eq_idx];
                let del_input_val   = delInputElement.value;
                // 중복 저장 방지 (한 번만 저장)
                if (!obj_this.delete_Ary[eq_idx].includes(delete_idx)) {
                    obj_this.delete_Ary[eq_idx].push(delete_idx);
                }

                // JSON 형식으로 변환하여 input 값에 저장
                document.querySelector("input[name='submit_delete_list']").value = JSON.stringify(obj_this.delete_Ary[eq_idx]);

            }
             
			/*===================================
			** 대표 썸네일 인덱스 값을 조정한다
			** 대표 썸네일 삭제시 첫번째를 선택
			** 대표 썸네일 보다 작다면 값을 1씩 뺀다
			** 대표 썸네일 보다 크다면 그대로
			**---------------------------------*/ 
            // 대표 썸네일의 인덱스 
		 	if( delete_idx === thumb_value ) { setThumbIdx = 0; }	// 대표 썸네일 삭제시 0번을 대표로 대체
			if( delete_idx < thumb_value   ) { setThumbIdx = thumb_value - 1;} 
			if( delete_idx > thumb_value   ) { setThumbIdx = thumb_value;} 

            // 대표 썸네일의 바뀐 인덱스 번호를 넣어준다 
            document.querySelectorAll('input[name="list_thumbnail"]')[eq_idx].value = setThumbIdx;
 
			//미리보기 해제
			if( delete_idx === obj_this.view_img_idx[eq_idx] ){
				obj_this.reset_preview(event);
			}
 
			// ※ 돔삭제 
            li_obj[delete_idx].parentNode.removeChild(li_obj[delete_idx]);

            // 돔의 dataCode, 데이터의 dataCode, 인덱스등 업데이트
            // 인덱스등 필요 데이터 갱신을 위해서 전체를 한번 돌림
            obj_this.setDataCode(event, eq_idx);

            // 안내글 ( 삭제 이후므로 -1)
            let totalCnt = obj_this.file_count_return(eq_idx, 'totalCnt');  // 업로드 전체 갯수
            obj_this.infoMsg(eq_idx, totalCnt); 

            // 리스트의 최대 넓이를 리스트 갯수에 맞춰서 지정한다
            obj_this.getListMaxWidth(eq_idx);

 			// 업로드 전체 갯수 넣어주기, 대표 썸네일 선택, 선택된 갯수가 있다면 초기화, 전체 파일 사이즈 넣기, 끝나는 시점에 에니메이션
            obj_this.setTotalElem(eq_idx, totalCnt, setThumbIdx);
			obj_this.select_name_list(event);         // 선택된 배열명 리스트를 수정한다 
            obj_this.drag_bar_line_dp(event, eq_idx); // 미리보기 드래그바 보이기 또는 감추기 활성 여부

            let before_click  = obj_this.before_click_idx[eq_idx]; // 이전 클릭 했던 곳
            if(before_click === delete_idx ){
                MARI.file_uploder.code.before_click_idx[eq_idx] = null;
            }

            // position_Ary 재 설정
            obj_this.position_info();

        },

		/*=========================================================================================
		 * 14. 선택된 n개 파일 삭제하기
		**---------------------------------------------------------------------------------------*/
		select_files_del : function(event){
            
            let obj_this   = MARI.file_uploder.code;
            let objLang    = MARI.file_uploder.langObj[obj_this.country];  // 언어 객체에서 선택된 언어 가져오기
            let eq_idx     = obj_this.event_eq(event);    //사용 가능하게 설정된 업로더에서의 인덱스 
            let loaderMain = obj_this.loader_main_obj(eq_idx);
			let li_obj     = obj_this.li_obj(eq_idx);     // loaderMain.querySelectorAll(obj_this.one_file_cls); 
            let select_ary = MARI.file_uploder.code.drag_select_ary;
            let select_cnt = obj_this.selectCnt(eq_idx);  // 드래그 선택중인 갯수 리턴
  
			let cf = confirm('선택한 '+select_cnt+'개의 파일을 삭제합니다');
			if(!cf){
				return false;
			} 

			/* 
			* 드래그 선택 또는 이곳 저곳 콘트롤 선택으로 여러개를 선택시 선택 배열엔 인덱스가 일정하게 들어가는게 아니라 들쑥날쑥하게 들어간다 
			*	예) 드래그 배열 => [5,7,1,3,9,8]
			* 이를 삭제하기 위해  인덱스를 따로 삭제용 배열에 받고 받은 배열을 오름 차순으로 정렬 
			*	예 )삭제 배열  => [1,3,5,7,8,9]  
			* 정렬된 배열을 기준으로 뒤에서 부터 삭제한다 ( 인덱스를 잘못 참조하지 않도록 )
			*/ 
            let delNames = '';
			let del_ary = []; 
			for(let d = 0; d < select_cnt; d++){ // 배열이므로 전체-1
				let del_idx    = select_ary[eq_idx]['idx'][d];  //선택시 배열에 들어간 값
				let del_name   = select_ary[eq_idx]['name'][d]; //선택시 배열에 들어간 값
				let del_code   = select_ary[eq_idx]['code'][d]; //선택시 배열에 들어간 값
                if(d == 0)
                delNames += del_name;
                else
                delNames += ', '+del_name;
 
                let attr_code  = li_obj[del_idx].getAttribute('data-code'); // 루프를 돌면서 개체에서 직접 불러온 대조용 코드
				if( del_code === attr_code ){ //돔입력 값과 삭제 값이 같다면 ( 삭제시 인덱스가 맞지 않아 엉뚱한 것을 삭제하는 것을 방지 하고자 )
					del_ary[d] = del_idx;// 삭제할 idx;
				}
			}
 
			//내림 차순으로 배열 정렬, 인덱스가 맞물리지 않도록 제일 높은 인덱스인 뒤에서 부터 제거해 나가기 위해서 
            del_ary.sort((a, b) => b - a);

 			for(let d = 0; d <select_cnt; d++){ 
 			 	 obj_this.file_delete(event, eq_idx, del_ary[d]);// 삭제하기
			}
 
            // 히스토리 입력 
            obj_this.historyEvent(eq_idx,  '선택한 n개 파일 삭제 => ' + delNames, 'red');

		},



	};// end property    MARI.file_uploder.code  = {
}//if(typeof(window.MARI.file_uploder.code) === "undefined") {    

