feat: Vue3로 작성된 지도 렌더러 (#216)
- 기존의 jQuery 기반 지도 렌더러를 Vue3로 재작성. - ajax로 호출하는 부분은 외부에서 처리하고, 다시 그리는 부분만 생성 - 기능상으론 거의 동일. - 일반 렌더러의 700px에, 500px모드용 모드 추가 - 아이콘의 크기는 작아지지만, 글자 크기는 작아지지 않도록 조정 dep: vueuse, detect-it 추가 Reviewed-on: https://storage.hided.net/gitea/devsam/core/pulls/216
This commit is contained in:
+4
-6
@@ -1,5 +1,3 @@
|
|||||||
@charset "UTF-8";
|
|
||||||
|
|
||||||
.world_map {
|
.world_map {
|
||||||
width: 700px;
|
width: 700px;
|
||||||
background: black;
|
background: black;
|
||||||
@@ -69,11 +67,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.map_body .map_toggle_cityname::after {
|
.map_body .map_toggle_cityname::after {
|
||||||
content: "끄기"
|
content: " 끄기"
|
||||||
}
|
}
|
||||||
|
|
||||||
.map_body .map_toggle_cityname.active::after {
|
.map_body .map_toggle_cityname.active::after {
|
||||||
content: "켜기"
|
content: " 켜기"
|
||||||
}
|
}
|
||||||
|
|
||||||
.map_body .map_toggle_single_tap {
|
.map_body .map_toggle_single_tap {
|
||||||
@@ -81,11 +79,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.map_body .map_toggle_single_tap::after {
|
.map_body .map_toggle_single_tap::after {
|
||||||
content: "끄기"
|
content: " 끄기"
|
||||||
}
|
}
|
||||||
|
|
||||||
.map_body .map_toggle_single_tap.active::after {
|
.map_body .map_toggle_single_tap.active::after {
|
||||||
content: "켜기"
|
content: " 켜기"
|
||||||
}
|
}
|
||||||
|
|
||||||
.world_map .city_tooltip {
|
.world_map .city_tooltip {
|
||||||
|
|||||||
@@ -0,0 +1,665 @@
|
|||||||
|
.map_title_tooltiptext .tooltip-inner {
|
||||||
|
max-width: 220px;
|
||||||
|
width: 220px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map_title_text {
|
||||||
|
margin: auto;
|
||||||
|
text-align: center;
|
||||||
|
width: 160px;
|
||||||
|
display: block;
|
||||||
|
line-height: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map_body .map_bglayer1 {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map_body .map_bglayer2 {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map_body .map_bgroad {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map_body .map_button_stack {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map_body .map_toggle_cityname::after {
|
||||||
|
content: " 끄기";
|
||||||
|
}
|
||||||
|
|
||||||
|
.map_body .map_toggle_cityname.active::after {
|
||||||
|
content: " 켜기";
|
||||||
|
}
|
||||||
|
|
||||||
|
.map_body .map_toggle_single_tap::after {
|
||||||
|
content: " 끄기";
|
||||||
|
}
|
||||||
|
|
||||||
|
.map_body .map_toggle_single_tap.active::after {
|
||||||
|
content: " 켜기";
|
||||||
|
}
|
||||||
|
|
||||||
|
.world_map .city_tooltip {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 16;
|
||||||
|
display: none;
|
||||||
|
min-width: 120px;
|
||||||
|
border: 0.02em gray solid;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.world_map .city_name {
|
||||||
|
background-color: rgb(30, 164, 255);
|
||||||
|
z-index: 6;
|
||||||
|
line-height: 15px;
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.world_map .nation_name {
|
||||||
|
background-color: rgb(30, 164, 255);
|
||||||
|
z-index: 6;
|
||||||
|
line-height: 15px;
|
||||||
|
height: 15px;
|
||||||
|
border-top: 0.02em gray solid;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink-my-city-d1 {
|
||||||
|
0% {
|
||||||
|
outline: solid 4px transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
outline: solid 4px rgba(255, 0, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
outline: solid 4px transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink-my-city-d2 {
|
||||||
|
0% {
|
||||||
|
outline: double 4px transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
outline: double 4px rgba(192, 192, 192, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
outline: double 4px transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Basic */
|
||||||
|
|
||||||
|
@keyframes blink-my-city {
|
||||||
|
0% {
|
||||||
|
outline: dashed 5px transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
outline: dashed 5px white;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
outline: dashed 5px transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink-my-city2 {
|
||||||
|
0% {
|
||||||
|
outline: dashed 4px transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
outline: dashed 4px red;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
outline: dashed 4px transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.btn-minimum {
|
||||||
|
padding-top: 1px;
|
||||||
|
padding-bottom: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.world_map.hide_cityname .city_detail_name {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_base .city_bg {
|
||||||
|
z-index: 1;
|
||||||
|
position: absolute;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_base div {
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cityBaseWidth: 40px;
|
||||||
|
$cityBaseHeight: 30px;
|
||||||
|
|
||||||
|
$detailMapCitySizes: (
|
||||||
|
(1, 48px, 45px, 16px, 15px, -8px, -4px),
|
||||||
|
(2, 60px, 42px, 20px, 14px, -8px, -4px),
|
||||||
|
(3, 42px, 42px, 14px, 14px, -8px, -4px),
|
||||||
|
(4, 60px, 45px, 20px, 15px, -6px, -3px),
|
||||||
|
(5, 72px, 48px, 24px, 16px, -6px, -4px),
|
||||||
|
(6, 78px, 54px, 26px, 18px, -6px, -4px),
|
||||||
|
(7, 84px, 60px, 28px, 20px, -6px, -4px),
|
||||||
|
(8, 96px, 72px, 32px, 24px, -6px, -3px)
|
||||||
|
);
|
||||||
|
|
||||||
|
$basicMapCitySize: (
|
||||||
|
(1, 12px, 12px),
|
||||||
|
(2, 12px, 12px),
|
||||||
|
(3, 14px, 14px),
|
||||||
|
(4, 16px, 14px),
|
||||||
|
(5, 18px, 16px),
|
||||||
|
(6, 20px, 16px),
|
||||||
|
(7, 22px, 18px),
|
||||||
|
(8, 24px, 18px)
|
||||||
|
);
|
||||||
|
.city_img {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.world_map.full_width_map {
|
||||||
|
width: 700px;
|
||||||
|
background: black;
|
||||||
|
font-size: 14px;
|
||||||
|
color: white;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.map_title {
|
||||||
|
width: 700px;
|
||||||
|
height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map_body {
|
||||||
|
width: 700px;
|
||||||
|
height: 500px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_base {
|
||||||
|
position: absolute;
|
||||||
|
width: $cityBaseWidth;
|
||||||
|
height: $cityBaseHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_detail_name {
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
position: absolute;
|
||||||
|
white-space: nowrap;
|
||||||
|
left: 70%;
|
||||||
|
font-size: 10px;
|
||||||
|
bottom: -10px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.map_detail {
|
||||||
|
@each $cityIdx, $cityAreaWidth, $cityAreaHeight, $cityIconWidth, $cityIconHeight, $flagRight,
|
||||||
|
$flagTop in $detailMapCitySizes
|
||||||
|
{
|
||||||
|
.my_city {
|
||||||
|
border-radius: 33%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my_city:before {
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: -2px;
|
||||||
|
right: -2px;
|
||||||
|
bottom: -2px;
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 33%;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
animation-duration: 3.9s;
|
||||||
|
animation-name: blink-my-city-d1;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my_city:after {
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: -2px;
|
||||||
|
right: -2px;
|
||||||
|
bottom: -2px;
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 33%;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
animation-duration: 3.9s;
|
||||||
|
animation-name: blink-my-city-d2;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_filler {
|
||||||
|
position: absolute;
|
||||||
|
width: calc(100% + 2px);
|
||||||
|
height: calc(100% + 2px);
|
||||||
|
left: -1px;
|
||||||
|
top: -1px;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_base.city_level_#{$cityIdx} {
|
||||||
|
.city_bg {
|
||||||
|
width: $cityAreaWidth;
|
||||||
|
height: $cityAreaHeight;
|
||||||
|
background-size: $cityAreaWidth $cityAreaHeight;
|
||||||
|
left: calc(($cityBaseWidth - $cityAreaWidth) / 2);
|
||||||
|
top: calc(($cityBaseHeight - $cityAreaHeight) / 2);
|
||||||
|
}
|
||||||
|
.city_img {
|
||||||
|
width: $cityIconWidth;
|
||||||
|
height: $cityIconHeight;
|
||||||
|
background-size: $cityIconWidth $cityIconHeight;
|
||||||
|
left: calc(($cityBaseWidth - $cityIconWidth) / 2);
|
||||||
|
top: calc(($cityBaseHeight - $cityIconHeight) / 2);
|
||||||
|
|
||||||
|
> img {
|
||||||
|
width: $cityIconWidth;
|
||||||
|
height: $cityIconHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.city_flag {
|
||||||
|
right: $flagRight;
|
||||||
|
top: $flagTop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_base {
|
||||||
|
.city_state {
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
/*TODO:도시 레벨 별로 다르게 위치 설정해볼 것*/
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_flag {
|
||||||
|
position: absolute;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
|
||||||
|
.city_capital {
|
||||||
|
position: absolute;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
top: 0;
|
||||||
|
right: -1px;
|
||||||
|
|
||||||
|
> img {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_flag > img {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.map_basic {
|
||||||
|
.city_filler {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my_city:before {
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: -2px;
|
||||||
|
right: -2px;
|
||||||
|
bottom: -2px;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
animation-duration: 2s;
|
||||||
|
animation-name: blink-my-city;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my_city:after {
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: -2px;
|
||||||
|
right: -2px;
|
||||||
|
bottom: -2px;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
animation-duration: 2s;
|
||||||
|
animation-name: blink-my-city2;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_img {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
@each $cityIdx, $cityIconWidth, $cityIconHeight in $basicMapCitySize {
|
||||||
|
.city_base.city_level_#{$cityIdx} {
|
||||||
|
.city_img {
|
||||||
|
width: $cityIconWidth;
|
||||||
|
height: $cityIconHeight;
|
||||||
|
left: calc(($cityBaseWidth - $cityIconWidth) / 2);
|
||||||
|
top: calc(($cityBaseHeight - $cityIconHeight) / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_base {
|
||||||
|
.city_capital {
|
||||||
|
position: absolute;
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
top: -2px;
|
||||||
|
right: -2px;
|
||||||
|
background-color: yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_state {
|
||||||
|
position: absolute;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
top: -2px;
|
||||||
|
left: -4px;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_state.city_state_war {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_state.city_state_bad {
|
||||||
|
background-color: orange;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_state.city_state_good {
|
||||||
|
background-color: blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$smV: calc(500 / 700);
|
||||||
|
|
||||||
|
.world_map.small_width_map {
|
||||||
|
width: 500px;
|
||||||
|
background: black;
|
||||||
|
font-size: 12px;
|
||||||
|
color: white;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.map_body {
|
||||||
|
.map_bglayer1,
|
||||||
|
.map_bglayer2,
|
||||||
|
.map_bgroad {
|
||||||
|
background-size: calc(700px * $smV) calc(500px * $smV);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.map_title {
|
||||||
|
width: 700px * $smV;
|
||||||
|
height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map_body {
|
||||||
|
width: 700px * $smV;
|
||||||
|
height: 500px * $smV;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_base {
|
||||||
|
position: absolute;
|
||||||
|
width: $cityBaseWidth;
|
||||||
|
height: $cityBaseHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_detail_name {
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
position: absolute;
|
||||||
|
white-space: nowrap;
|
||||||
|
left: 70%;
|
||||||
|
font-size: 10px;
|
||||||
|
bottom: -12px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.map_detail {
|
||||||
|
@each $cityIdx, $cityAreaWidth, $cityAreaHeight, $cityIconWidth, $cityIconHeight, $flagRight,
|
||||||
|
$flagTop in $detailMapCitySizes
|
||||||
|
{
|
||||||
|
.my_city {
|
||||||
|
border-radius: 33%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my_city:before {
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: -2px;
|
||||||
|
right: -2px;
|
||||||
|
bottom: -2px;
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 33%;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
animation-duration: 3.9s;
|
||||||
|
animation-name: blink-my-city-d1;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my_city:after {
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: -2px;
|
||||||
|
right: -2px;
|
||||||
|
bottom: -2px;
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 33%;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
animation-duration: 3.9s;
|
||||||
|
animation-name: blink-my-city-d2;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_filler {
|
||||||
|
position: absolute;
|
||||||
|
width: calc(100% + 2px);
|
||||||
|
height: calc(100% + 2px);
|
||||||
|
left: -1px;
|
||||||
|
top: -1px;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_base.city_level_#{$cityIdx} {
|
||||||
|
.city_bg {
|
||||||
|
width: $cityAreaWidth * $smV;
|
||||||
|
height: $cityAreaHeight * $smV;
|
||||||
|
background-size: calc($cityAreaWidth * $smV) calc($cityAreaHeight * $smV);
|
||||||
|
left: calc(($cityBaseWidth - $cityAreaWidth * $smV) / 2);
|
||||||
|
top: calc(($cityBaseHeight - $cityAreaHeight * $smV) / 2);
|
||||||
|
}
|
||||||
|
.city_img {
|
||||||
|
width: $cityIconWidth * $smV;
|
||||||
|
height: $cityIconHeight * $smV;
|
||||||
|
background-size: calc($cityIconWidth * $smV) calc($cityIconHeight * $smV);
|
||||||
|
left: calc(($cityBaseWidth - $cityIconWidth * $smV) / 2);
|
||||||
|
top: calc(($cityBaseHeight - $cityIconHeight * $smV) / 2);
|
||||||
|
|
||||||
|
> img {
|
||||||
|
width: $cityIconWidth * $smV;
|
||||||
|
height: $cityIconHeight * $smV;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.city_flag {
|
||||||
|
right: $flagRight;
|
||||||
|
top: $flagTop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_base {
|
||||||
|
.city_state {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
/*TODO:도시 레벨 별로 다르게 위치 설정해볼 것*/
|
||||||
|
left: 5px;
|
||||||
|
> img {
|
||||||
|
width: 10px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_flag {
|
||||||
|
position: absolute;
|
||||||
|
width: 12px * $smV;
|
||||||
|
height: 12px * $smV;
|
||||||
|
|
||||||
|
> img {
|
||||||
|
width: 12px * $smV;
|
||||||
|
height: 12px * $smV;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_capital {
|
||||||
|
position: absolute;
|
||||||
|
width: 10px * $smV;
|
||||||
|
height: 10px * $smV;
|
||||||
|
top: 0;
|
||||||
|
right: -1px;
|
||||||
|
|
||||||
|
> img {
|
||||||
|
width: 10px * $smV;
|
||||||
|
height: 10px * $smV;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.map_basic {
|
||||||
|
.city_filler {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my_city:before {
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: -2px;
|
||||||
|
right: -2px;
|
||||||
|
bottom: -2px;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
animation-duration: 2s;
|
||||||
|
animation-name: blink-my-city;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my_city:after {
|
||||||
|
content: "";
|
||||||
|
top: -2px;
|
||||||
|
left: -2px;
|
||||||
|
right: -2px;
|
||||||
|
bottom: -2px;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
animation-duration: 2s;
|
||||||
|
animation-name: blink-my-city2;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_img {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
@each $cityIdx, $cityIconWidth, $cityIconHeight in $basicMapCitySize {
|
||||||
|
.city_base.city_level_#{$cityIdx} {
|
||||||
|
.city_img {
|
||||||
|
width: $cityIconWidth * $smV;
|
||||||
|
height: $cityIconHeight * $smV;
|
||||||
|
left: calc(($cityBaseWidth - $cityIconWidth * $smV) / 2);
|
||||||
|
top: calc(($cityBaseHeight - $cityIconHeight * $smV) / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_base {
|
||||||
|
.city_capital {
|
||||||
|
position: absolute;
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
top: -2px;
|
||||||
|
right: -2px;
|
||||||
|
background-color: yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_state {
|
||||||
|
position: absolute;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
top: -3px;
|
||||||
|
left: -5px;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_state.city_state_war {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_state.city_state_bad {
|
||||||
|
background-color: orange;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city_state.city_state_good {
|
||||||
|
background-color: blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="['city_base', `city_base_${city.id}`, `city_level_${city.level}`]"
|
||||||
|
:style="cityPos"
|
||||||
|
@mouseenter="silent"
|
||||||
|
@mouseleave="silent"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="city_link"
|
||||||
|
:data-text="city.text"
|
||||||
|
:data-nation="city.nation"
|
||||||
|
:data-id="city.id"
|
||||||
|
:href="props.href"
|
||||||
|
:style="{
|
||||||
|
cursor: city.clickable ? 'pointer' : 'default',
|
||||||
|
}"
|
||||||
|
@click="clicked"
|
||||||
|
@touchend="touchend"
|
||||||
|
@mouseenter="mouseenter"
|
||||||
|
@mouseleave="mouseleave"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="city_img"
|
||||||
|
:style="{
|
||||||
|
backgroundColor: city.color,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div :class="['city_filler', props.isMyCity ? 'my_city' : '']"></div>
|
||||||
|
<div v-if="city.state > 0" :class="['city_state', `city_state_${getCityState()}`]"></div>
|
||||||
|
<div v-if="city.nationID && city.nationID > 0" class="city_flag">
|
||||||
|
<div v-if="city.isCapital" class="city_capital"></div>
|
||||||
|
</div>
|
||||||
|
<span class="city_detail_name">{{ city.name }}</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { MapCityParsed } from "@/map";
|
||||||
|
import { ref, toRef, watch, type PropType } from "vue";
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: "click", evnet: MouseEvent | TouchEvent): void;
|
||||||
|
(event: "mouseenter", e: MouseEvent): void;
|
||||||
|
(event: "mouseleave", e: MouseEvent): void;
|
||||||
|
}>();
|
||||||
|
const props = defineProps({
|
||||||
|
city: {
|
||||||
|
type: Object as PropType<MapCityParsed>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
href: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
isMyCity: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
defeault: false,
|
||||||
|
},
|
||||||
|
isFullWidth: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const city = toRef(props, "city");
|
||||||
|
const cityPos = ref({
|
||||||
|
left: "0px",
|
||||||
|
top: "0px",
|
||||||
|
});
|
||||||
|
watch(
|
||||||
|
() => props.isFullWidth,
|
||||||
|
(isFullWidth) => {
|
||||||
|
const { x, y } = city.value;
|
||||||
|
if (isFullWidth) {
|
||||||
|
cityPos.value = {
|
||||||
|
left: `${x - 20}px`,
|
||||||
|
top: `${y - 15}px`,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
cityPos.value = {
|
||||||
|
left: `${(x * 5) / 7 - 20}px`,
|
||||||
|
top: `${(y * 5) / 7 - 18}px`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
function getCityState(): string {
|
||||||
|
const state = city.value.state;
|
||||||
|
if (state < 10) {
|
||||||
|
return "good";
|
||||||
|
}
|
||||||
|
if (state < 40) {
|
||||||
|
return "bad";
|
||||||
|
}
|
||||||
|
if (state < 50) {
|
||||||
|
return "war";
|
||||||
|
}
|
||||||
|
return "wrong";
|
||||||
|
}
|
||||||
|
|
||||||
|
function clicked(event: MouseEvent) {
|
||||||
|
emit("click", event);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mouseenter(event: MouseEvent) {
|
||||||
|
event.stopPropagation();
|
||||||
|
emit("mouseenter", event);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mouseleave(event: MouseEvent) {
|
||||||
|
event.stopPropagation();
|
||||||
|
emit("mouseleave", event);
|
||||||
|
}
|
||||||
|
|
||||||
|
function touchend(event: TouchEvent) {
|
||||||
|
event.stopPropagation();
|
||||||
|
emit("click", event);
|
||||||
|
}
|
||||||
|
|
||||||
|
function silent(event: MouseEvent) {
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
a,
|
||||||
|
div,
|
||||||
|
span {
|
||||||
|
line-height: 1.3;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="['city_base', `city_base_${city.id}`, `city_level_${city.level}`]"
|
||||||
|
:style="cityPos"
|
||||||
|
@mouseenter="silent"
|
||||||
|
@mouseleave="silent"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="city.color"
|
||||||
|
class="city_bg"
|
||||||
|
:style="{
|
||||||
|
backgroundImage: `url(${imagePath}/b${city.color.substring(1).toUpperCase()}.png)`,
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="city_link"
|
||||||
|
:data-text="city.text"
|
||||||
|
:data-nation="city.nation"
|
||||||
|
:data-id="city.id"
|
||||||
|
:href="props.href"
|
||||||
|
:style="{
|
||||||
|
cursor: city.clickable ? 'pointer' : 'default',
|
||||||
|
}"
|
||||||
|
@click="clicked"
|
||||||
|
@touchend="touchend"
|
||||||
|
@mouseenter="mouseenter"
|
||||||
|
@mouseleave="mouseleave"
|
||||||
|
>
|
||||||
|
<div class="city_img">
|
||||||
|
<img :src="`${imagePath}/cast_${city.level}.gif`" />
|
||||||
|
<div :class="['city_filler', props.isMyCity ? 'my_city' : '']"></div>
|
||||||
|
|
||||||
|
<div v-if="city.nationID && city.nationID > 0" class="city_flag">
|
||||||
|
<img :src="`${imagePath}/${city.supply ? 'f' : 'd'}${city.color.substring(1).toUpperCase()}.gif`" />
|
||||||
|
<div v-if="city.isCapital" class="city_capital">
|
||||||
|
<img :src="`${imagePath}/event51.gif`" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="city_detail_name">{{ city.name }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="city.state > 0" class="city_state">
|
||||||
|
<img :src="`${imagePath}/event${city.state}.gif`" />
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { MapCityParsed } from "@/map";
|
||||||
|
import { ref, toRef, watch, type PropType } from "vue";
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: "click", e: MouseEvent | TouchEvent): void;
|
||||||
|
(event: "mouseenter", e: MouseEvent): void;
|
||||||
|
(event: "mouseleave", e: MouseEvent): void;
|
||||||
|
}>();
|
||||||
|
const props = defineProps({
|
||||||
|
city: {
|
||||||
|
type: Object as PropType<MapCityParsed>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
href: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
isMyCity: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
defeault: false,
|
||||||
|
},
|
||||||
|
imagePath: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
isFullWidth: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const city = toRef(props, "city");
|
||||||
|
const cityPos = ref({
|
||||||
|
left: "0px",
|
||||||
|
top: "0px",
|
||||||
|
});
|
||||||
|
watch(
|
||||||
|
() => props.isFullWidth,
|
||||||
|
(isFullWidth) => {
|
||||||
|
const { x, y } = city.value;
|
||||||
|
if (isFullWidth) {
|
||||||
|
cityPos.value = {
|
||||||
|
left: `${x - 20}px`,
|
||||||
|
top: `${y - 15}px`,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
cityPos.value = {
|
||||||
|
left: `${(x * 5) / 7 - 20}px`,
|
||||||
|
top: `${(y * 5) / 7 - 18}px`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const imagePath = toRef(props, "imagePath");
|
||||||
|
|
||||||
|
function clicked(event: MouseEvent) {
|
||||||
|
emit("click", event);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mouseenter(event: MouseEvent) {
|
||||||
|
event.stopPropagation();
|
||||||
|
emit("mouseenter", event);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mouseleave(event: MouseEvent) {
|
||||||
|
event.stopPropagation();
|
||||||
|
emit("mouseleave", event);
|
||||||
|
}
|
||||||
|
|
||||||
|
function touchend(event: TouchEvent) {
|
||||||
|
event.stopPropagation();
|
||||||
|
emit("click", event);
|
||||||
|
}
|
||||||
|
|
||||||
|
function silent(event: MouseEvent) {
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.full_width_map{
|
||||||
|
a,
|
||||||
|
div,
|
||||||
|
img,
|
||||||
|
span {
|
||||||
|
line-height: 1.3;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.small_width_map {
|
||||||
|
a,
|
||||||
|
div,
|
||||||
|
img {
|
||||||
|
line-height: 1.3;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
line-height: 1.0;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,465 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:id="uuid"
|
||||||
|
:class="[
|
||||||
|
'world_map',
|
||||||
|
`map_theme_${mapTheme}`,
|
||||||
|
drawableMap ? '' : 'draw_required',
|
||||||
|
props.isDetailMap ? 'map_detail' : 'map_basic',
|
||||||
|
hideMapCityName ? 'hide_cityname' : '',
|
||||||
|
isFullWidth ? 'full_width_map' : 'small_width_map',
|
||||||
|
getMapSeasonClassName(),
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-my-tooltip.hover.top="{
|
||||||
|
class: 'map_title_tooltiptext',
|
||||||
|
}"
|
||||||
|
class="map_title"
|
||||||
|
:title="getTitleTooltip()"
|
||||||
|
>
|
||||||
|
<span class="map_title_text" :style="{ color: getTitleColor() }"
|
||||||
|
>{{ mapResult?.year }}年 {{ mapResult?.month }}月</span
|
||||||
|
>
|
||||||
|
<span class="tooltiptext" />
|
||||||
|
</div>
|
||||||
|
<div ref="map_area" class="map_body" @click="clickOutside">
|
||||||
|
<div class="map_bglayer1" />
|
||||||
|
<div class="map_bglayer2" />
|
||||||
|
<div class="map_bgroad" />
|
||||||
|
<div class="map_button_stack">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
:class="['btn btn-primary map_toggle_cityname btn-sm btn-minimum', hideMapCityName ? 'active' : '']"
|
||||||
|
data-bs-toggle="button"
|
||||||
|
:aria-pressed="hideMapCityName"
|
||||||
|
autocomplete="off"
|
||||||
|
@click="hideMapCityName = !hideMapCityName"
|
||||||
|
>
|
||||||
|
도시명 표기</button
|
||||||
|
><br />
|
||||||
|
<button
|
||||||
|
:style="{
|
||||||
|
display: deviceType != 'mouseOnly' ? 'block' : 'none',
|
||||||
|
}"
|
||||||
|
type="button"
|
||||||
|
:class="['btn btn-secondary map_toggle_single_tap btn-sm btn-minimum', toggleSingleTap ? 'active' : '']"
|
||||||
|
data-bs-toggle="button"
|
||||||
|
:aria-pressed="toggleSingleTap"
|
||||||
|
autocomplete="off"
|
||||||
|
@click="toggleSingleTap = !toggleSingleTap"
|
||||||
|
>
|
||||||
|
두번 탭 해 도시 이동
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<template v-if="drawableMap === undefined"><!--로딩중?--></template>
|
||||||
|
<template v-else-if="props.isDetailMap">
|
||||||
|
<MapCityDetail
|
||||||
|
v-for="city of drawableMap.cityList"
|
||||||
|
:key="city.id"
|
||||||
|
:city="city"
|
||||||
|
:image-path="imagePath"
|
||||||
|
:is-my-city="city.id === drawableMap.myCity"
|
||||||
|
:isFullWidth="isFullWidth"
|
||||||
|
:href="props.genHref?.call(city, city.id)"
|
||||||
|
@click="cityClick(city, $event)"
|
||||||
|
@mouseenter="mouseenter(city, $event)"
|
||||||
|
@mouseleave="mouseleave(city, $event)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else
|
||||||
|
><MapCityBasic
|
||||||
|
v-for="city of drawableMap.cityList"
|
||||||
|
:key="city.id"
|
||||||
|
:city="city"
|
||||||
|
:is-my-city="city.id === drawableMap.myCity"
|
||||||
|
:isFullWidth="isFullWidth"
|
||||||
|
:href="props.genHref?.call(city, city.id)"
|
||||||
|
@click="cityClick(city, $event)"
|
||||||
|
@mouseenter="mouseenter(city, $event)"
|
||||||
|
@mouseleave="mouseleave(city, $event)"
|
||||||
|
/></template>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ref="tooltipDom"
|
||||||
|
class="city_tooltip"
|
||||||
|
:style="{
|
||||||
|
display: isOutside || !activatedCity ? 'none' : 'block',
|
||||||
|
position: 'absolute',
|
||||||
|
left: `${(() => {
|
||||||
|
if (cursorX + tooltipWidth + 10 > (isFullWidth ? 700 : 500)) {
|
||||||
|
return cursorX - tooltipWidth - 5;
|
||||||
|
}
|
||||||
|
return cursorX + 10;
|
||||||
|
})()}px`,
|
||||||
|
top: `${cursorY + 30}px`,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="city_name">{{ activatedCity?.text }}</div>
|
||||||
|
<div class="nation_name">{{ activatedCity?.nation }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export type MapCityParsedRaw = {
|
||||||
|
id: number;
|
||||||
|
level: number;
|
||||||
|
state: number;
|
||||||
|
nationID?: number;
|
||||||
|
region: number;
|
||||||
|
supply: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MapCityParsedName = MapCityParsedRaw & {
|
||||||
|
name: string;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MapCityParsedNation = MapCityParsedName & {
|
||||||
|
nationID?: number;
|
||||||
|
nation?: string;
|
||||||
|
color?: string;
|
||||||
|
isCapital: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MapCityParsedClickable = MapCityParsedNation & {
|
||||||
|
clickable: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MapCityParsedRegionLevelText = MapCityParsedClickable & {
|
||||||
|
region_str: string;
|
||||||
|
level_str: string;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MapCityParsed = MapCityParsedRegionLevelText;
|
||||||
|
|
||||||
|
type MapCityDrawable = {
|
||||||
|
cityList: MapCityParsed[];
|
||||||
|
myCity?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MapNationParsed = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
color: string;
|
||||||
|
capital: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CityPositionMap = {
|
||||||
|
[cityID: number]: [string, number, number];
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import "@/../scss/map.scss";
|
||||||
|
import { type PropType, toRef, inject, type Ref, ref, watch, type ComponentPublicInstance } from "vue";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
import type { MapResult } from "@/defs";
|
||||||
|
import { joinYearMonth } from "@/util/joinYearMonth";
|
||||||
|
import { parseYearMonth } from "@/util/parseYearMonth";
|
||||||
|
import vMyTooltip from "@/directives/vMyTooltip";
|
||||||
|
import type { GameConstStore } from "@/GameConstStore";
|
||||||
|
import { unwrap_err } from "@/util/unwrap_err";
|
||||||
|
import { getMaxRelativeTechLevel, TECH_LEVEL_YEAR_GAP } from "@/utilGame/techLevel";
|
||||||
|
import { deviceType } from "detect-it";
|
||||||
|
import MapCityBasic from "./MapCityBasic.vue";
|
||||||
|
import MapCityDetail from "./MapCityDetail.vue";
|
||||||
|
import { convertDictById } from "@/common_legacy";
|
||||||
|
import { useElementSize, useMouse, useMouseInElement } from "@vueuse/core";
|
||||||
|
import { hideMapCityName, toggleSingleTap } from "@/state/mapViewer";
|
||||||
|
import { is1000pxMode } from "@/state/is1000pxMode";
|
||||||
|
const uuid = uuidv4();
|
||||||
|
const gameConstStore = unwrap_err(
|
||||||
|
inject<Ref<GameConstStore>>("gameConstStore"),
|
||||||
|
Error,
|
||||||
|
"gameConstStore가 주입되지 않았습니다."
|
||||||
|
);
|
||||||
|
|
||||||
|
const tooltipDom = ref<ComponentPublicInstance<HTMLDivElement>>();
|
||||||
|
const map_area = ref<ComponentPublicInstance<HTMLDivElement>>();
|
||||||
|
const { elementX: cursorX, elementY: cursorY, isOutside } = useMouseInElement(map_area);
|
||||||
|
const { width: tooltipWidth } = useElementSize(tooltipDom);
|
||||||
|
const { sourceType: cursorType } = useMouse();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: "city-click", city: MapCityParsed, e: MouseEvent | TouchEvent): void;
|
||||||
|
(event: "parsed", drawable: MapCityDrawable): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const isFullWidth = ref(true);
|
||||||
|
|
||||||
|
function setWidthMode([widthMode, is1000pxMode]: ["auto" | "full" | "small" | undefined, boolean]): void {
|
||||||
|
if (widthMode == "full") {
|
||||||
|
isFullWidth.value = true;
|
||||||
|
}
|
||||||
|
if (widthMode == "small") {
|
||||||
|
isFullWidth.value = false;
|
||||||
|
}
|
||||||
|
isFullWidth.value = is1000pxMode;
|
||||||
|
}
|
||||||
|
watch([() => props.width, is1000pxMode], setWidthMode, { immediate: true });
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
width: {
|
||||||
|
type: String as PropType<"full" | "small" | "auto" | undefined>,
|
||||||
|
default: undefined,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
imagePath: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
mapName: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
isDetailMap: { type: Boolean, default: undefined, required: false },
|
||||||
|
disallowClick: { type: Boolean, default: undefined, required: false },
|
||||||
|
genHref: {
|
||||||
|
type: Function as PropType<(cityID: number) => string>,
|
||||||
|
default: undefined,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
cityPosition: {
|
||||||
|
type: Object as PropType<CityPositionMap>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
formatCityInfo: {
|
||||||
|
type: Function as PropType<(city: MapCityParsed) => MapCityParsed>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
modelValue: {
|
||||||
|
type: Object as PropType<MapResult>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapResult = toRef(props, "modelValue");
|
||||||
|
|
||||||
|
const mapTheme = toRef(props, "mapName");
|
||||||
|
function getTitleColor(): string | undefined {
|
||||||
|
const { startYear, year } = mapResult.value;
|
||||||
|
|
||||||
|
if (year < startYear + 1) {
|
||||||
|
return "magenta";
|
||||||
|
}
|
||||||
|
if (year < startYear + 2) {
|
||||||
|
return "orange";
|
||||||
|
}
|
||||||
|
if (year < startYear + 3) {
|
||||||
|
return "yellow";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const drawableMap = ref<MapCityDrawable>(convertCityObjs(props.modelValue));
|
||||||
|
const activatedCity = ref<MapCityParsed>();
|
||||||
|
|
||||||
|
function getBeginGameLimitTooltip(): string | undefined {
|
||||||
|
const { startYear, year, month } = mapResult.value;
|
||||||
|
if (year > startYear + 3) return undefined;
|
||||||
|
|
||||||
|
const [remainYear, remainMonth] = parseYearMonth(joinYearMonth(startYear + 3, 0) - joinYearMonth(year, month));
|
||||||
|
|
||||||
|
return `초반제한 기간 : ${remainYear}년${remainMonth > 0 ? ` ${remainMonth}개월` : ""} (${startYear + 3}년)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTitleTooltip(): string {
|
||||||
|
const result: string[] = [];
|
||||||
|
const beginLimit = getBeginGameLimitTooltip();
|
||||||
|
if (beginLimit) {
|
||||||
|
result.push(beginLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { startYear, year } = mapResult.value;
|
||||||
|
|
||||||
|
const maxTechLevel = gameConstStore.value.gameConst.maxTechLevel;
|
||||||
|
const currentTechLimit = getMaxRelativeTechLevel(startYear, year, maxTechLevel);
|
||||||
|
|
||||||
|
if (currentTechLimit == maxTechLevel) {
|
||||||
|
result.push(`기술등급 제한 : ${currentTechLimit}등급 (최종)`);
|
||||||
|
} else {
|
||||||
|
const nextTechLimitYear = currentTechLimit * TECH_LEVEL_YEAR_GAP + startYear;
|
||||||
|
result.push(`기술등급 제한 : ${currentTechLimit}등급 (${nextTechLimitYear}년 해제)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.join("<br>");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMapSeasonClassName(): string {
|
||||||
|
const { month } = mapResult.value;
|
||||||
|
|
||||||
|
if (month <= 3) {
|
||||||
|
return "map_spring";
|
||||||
|
}
|
||||||
|
if (month <= 6) {
|
||||||
|
return "map_summer";
|
||||||
|
}
|
||||||
|
if (month <= 9) {
|
||||||
|
return "map_fall";
|
||||||
|
}
|
||||||
|
return "map_winter";
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertCityObjs(obj: MapResult): MapCityDrawable {
|
||||||
|
//원본 Obj는 굉장히 간소하게 온다, Object 형태로 변환해서 사용한다.
|
||||||
|
|
||||||
|
function toCityObj([id, level, state, nationID, region, supply]: MapResult["cityList"][0]): MapCityParsedRaw {
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
level: level,
|
||||||
|
state: state,
|
||||||
|
nationID: nationID > 0 ? nationID : undefined,
|
||||||
|
region: region,
|
||||||
|
supply: supply != 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function toNationObj([id, name, color, capital]: MapResult["nationList"][0]): MapNationParsed {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
color,
|
||||||
|
capital,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const nationList = convertDictById(obj.nationList.map(toNationObj)); //array of object -> dict
|
||||||
|
|
||||||
|
const spyList = obj.spyList;
|
||||||
|
const shownByGeneralList = new Set(obj.shownByGeneralList);
|
||||||
|
|
||||||
|
const myCity = obj.myCity;
|
||||||
|
const myNation = obj.myNation;
|
||||||
|
|
||||||
|
function mergePositionInfo(city: MapCityParsedRaw): MapCityParsedName {
|
||||||
|
const id = city.id;
|
||||||
|
if (!(id in props.cityPosition)) {
|
||||||
|
throw TypeError(`알수 없는 cityID: ${id}`);
|
||||||
|
}
|
||||||
|
const [name, x, y] = props.cityPosition[id];
|
||||||
|
|
||||||
|
return {
|
||||||
|
...city,
|
||||||
|
name,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeNationInfo(city: MapCityParsedName): MapCityParsedNation {
|
||||||
|
//nationID 값으로 isCapital, color, nation을 통합
|
||||||
|
|
||||||
|
const nationID = city.nationID;
|
||||||
|
if (nationID === undefined || !(nationID in nationList)) {
|
||||||
|
return {
|
||||||
|
...city,
|
||||||
|
isCapital: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const nationObj = nationList[nationID];
|
||||||
|
return {
|
||||||
|
...city,
|
||||||
|
nation: nationObj.name,
|
||||||
|
color: nationObj.color,
|
||||||
|
isCapital: nationObj.capital == city.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeClickable(city: MapCityParsedNation): MapCityParsedClickable {
|
||||||
|
//clickable = (defaultCity << 4 ) | (remainSpy << 3) | (ourCity << 2) | (shownByGeneral << 1)
|
||||||
|
const id = city.id;
|
||||||
|
const nationID = city.nationID;
|
||||||
|
|
||||||
|
if (props.disallowClick) {
|
||||||
|
return { ...city, clickable: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
let clickable = 16;
|
||||||
|
if (id in spyList) {
|
||||||
|
clickable |= spyList[id] << 3;
|
||||||
|
}
|
||||||
|
if (myNation !== null && nationID == myNation) {
|
||||||
|
clickable |= 4;
|
||||||
|
}
|
||||||
|
if (shownByGeneralList.has(id)) {
|
||||||
|
clickable |= 2;
|
||||||
|
}
|
||||||
|
if (myCity !== null && id == myCity) {
|
||||||
|
clickable |= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...city,
|
||||||
|
clickable,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const cityList = obj.cityList
|
||||||
|
.map(toCityObj)
|
||||||
|
.map(mergePositionInfo)
|
||||||
|
.map(mergeNationInfo)
|
||||||
|
.map(mergeClickable)
|
||||||
|
.map(window.formatCityInfo);
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
cityList: cityList,
|
||||||
|
myCity: myCity,
|
||||||
|
};
|
||||||
|
emit("parsed", result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(mapInfo) => {
|
||||||
|
activatedCity.value = undefined;
|
||||||
|
drawableMap.value = convertCityObjs(mapInfo);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const touchState = ref(0);
|
||||||
|
function cityClick(city: MapCityParsed, $event: MouseEvent | TouchEvent): void {
|
||||||
|
if (cursorType.value == "touch") {
|
||||||
|
if (touchState.value == 1 && activatedCity.value?.id !== city.id) {
|
||||||
|
touchState.value = 0;
|
||||||
|
activatedCity.value = undefined;
|
||||||
|
}
|
||||||
|
if (touchState.value == 0) {
|
||||||
|
touchState.value = 1;
|
||||||
|
activatedCity.value = city;
|
||||||
|
$event.preventDefault();
|
||||||
|
if (!toggleSingleTap.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit("city-click", city, $event);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clickOutside($event: MouseEvent): void {
|
||||||
|
$event.preventDefault();
|
||||||
|
$event.stopPropagation();
|
||||||
|
if (touchState.value == 1) {
|
||||||
|
touchState.value = 0;
|
||||||
|
activatedCity.value = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mouseenter(city: MapCityParsed, $event: MouseEvent): void {
|
||||||
|
if (cursorType.value == "mouse") {
|
||||||
|
activatedCity.value = city;
|
||||||
|
touchState.value = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mouseleave(city: MapCityParsed, $event: MouseEvent): void {
|
||||||
|
if (cursorType.value == "mouse") {
|
||||||
|
activatedCity.value = undefined;
|
||||||
|
touchState.value = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { useMediaQuery } from "@vueuse/core";
|
||||||
|
|
||||||
|
export const is1000pxMode = useMediaQuery('(min-width:900px');
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { watch, ref } from "vue";
|
||||||
|
|
||||||
|
export const hideMapCityName = ref<boolean>(localStorage.getItem('sam.hideMapCityName') == 'yes');
|
||||||
|
watch(hideMapCityName, (value) => {
|
||||||
|
localStorage.setItem('sam.hideMapCityName', value ? 'yes' : 'no');
|
||||||
|
});
|
||||||
|
|
||||||
|
export const toggleSingleTap = ref<boolean>(localStorage.getItem('sam.toggleSingleTap') == 'yes');
|
||||||
|
watch(toggleSingleTap, (value) => {
|
||||||
|
localStorage.setItem('sam.toggleSingleTap', value ? 'yes' : 'no');
|
||||||
|
});
|
||||||
Generated
+124
@@ -37,6 +37,7 @@
|
|||||||
"@typescript-eslint/parser": "^5.14.0",
|
"@typescript-eslint/parser": "^5.14.0",
|
||||||
"@vue/compiler-sfc": "^3.2.31",
|
"@vue/compiler-sfc": "^3.2.31",
|
||||||
"@vue/eslint-config-typescript": "^10.0.0",
|
"@vue/eslint-config-typescript": "^10.0.0",
|
||||||
|
"@vueuse/core": "^8.2.6",
|
||||||
"ag-grid-community": "^27.1.0",
|
"ag-grid-community": "^27.1.0",
|
||||||
"ag-grid-vue3": "^27.1.0",
|
"ag-grid-vue3": "^27.1.0",
|
||||||
"async-validator": "^4.0.7",
|
"async-validator": "^4.0.7",
|
||||||
@@ -56,6 +57,7 @@
|
|||||||
"css-loader": "^6.7.1",
|
"css-loader": "^6.7.1",
|
||||||
"cssnano": "^5.1.3",
|
"cssnano": "^5.1.3",
|
||||||
"date-fns": "^2.28.0",
|
"date-fns": "^2.28.0",
|
||||||
|
"detect-it": "^4.0.1",
|
||||||
"downloadjs": "^1.4.7",
|
"downloadjs": "^1.4.7",
|
||||||
"esbuild-loader": "^2.18.0",
|
"esbuild-loader": "^2.18.0",
|
||||||
"eslint": "^8.11.0",
|
"eslint": "^8.11.0",
|
||||||
@@ -2860,6 +2862,87 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.31.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.31.tgz",
|
||||||
"integrity": "sha512-ymN2pj6zEjiKJZbrf98UM2pfDd6F2H7ksKw7NDt/ZZ1fh5Ei39X5tABugtT03ZRlWd9imccoK0hE8hpjpU7irQ=="
|
"integrity": "sha512-ymN2pj6zEjiKJZbrf98UM2pfDd6F2H7ksKw7NDt/ZZ1fh5Ei39X5tABugtT03ZRlWd9imccoK0hE8hpjpU7irQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@vueuse/core": {
|
||||||
|
"version": "8.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-8.2.6.tgz",
|
||||||
|
"integrity": "sha512-fzlpM3B5oVe+UhCT1mXlhG1Zxdq2lq1Z2AvddSB8+RxrsSFzII7DKfsQEz8Vop7Lzc++4m8drTNbhPovYoFqHw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@vueuse/metadata": "8.2.6",
|
||||||
|
"@vueuse/shared": "8.2.6",
|
||||||
|
"vue-demi": "*"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@vue/composition-api": "^1.1.0",
|
||||||
|
"vue": "^2.6.0 || ^3.2.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@vue/composition-api": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"vue": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vueuse/core/node_modules/@vueuse/shared": {
|
||||||
|
"version": "8.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-8.2.6.tgz",
|
||||||
|
"integrity": "sha512-J/W4CMfdL8TahELuSOgtfVO4eQXTjhigp7dVWIBsLUVFCeY9d49gvHUcQN3y5xYLZ6iNP57TjTQjMMT/zhklkw==",
|
||||||
|
"dependencies": {
|
||||||
|
"vue-demi": "*"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@vue/composition-api": "^1.1.0",
|
||||||
|
"vue": "^2.6.0 || ^3.2.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@vue/composition-api": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"vue": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vueuse/core/node_modules/vue-demi": {
|
||||||
|
"version": "0.12.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.12.5.tgz",
|
||||||
|
"integrity": "sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"bin": {
|
||||||
|
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||||
|
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@vue/composition-api": "^1.0.0-rc.1",
|
||||||
|
"vue": "^3.0.0-0 || ^2.6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@vue/composition-api": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vueuse/metadata": {
|
||||||
|
"version": "8.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-8.2.6.tgz",
|
||||||
|
"integrity": "sha512-OBKtafCt+4RcEJlYDCjp1vl65pBCL2g4TmipEtdZ8/qphKlW6nakJbkY7XRN5grPmjqU99/ahJGtyGk5NHS2hw==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@webassemblyjs/ast": {
|
"node_modules/@webassemblyjs/ast": {
|
||||||
"version": "1.11.1",
|
"version": "1.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
|
||||||
@@ -4248,6 +4331,11 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/detect-it": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/detect-it/-/detect-it-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-dg5YBTJYvogK1+dA2mBUDKzOWfYZtHVba89SyZUhc4+e3i2tzgjANFg5lDRCd3UOtRcw00vUTMK8LELcMdicug=="
|
||||||
|
},
|
||||||
"node_modules/diff": {
|
"node_modules/diff": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||||
@@ -12635,6 +12723,37 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.31.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.31.tgz",
|
||||||
"integrity": "sha512-ymN2pj6zEjiKJZbrf98UM2pfDd6F2H7ksKw7NDt/ZZ1fh5Ei39X5tABugtT03ZRlWd9imccoK0hE8hpjpU7irQ=="
|
"integrity": "sha512-ymN2pj6zEjiKJZbrf98UM2pfDd6F2H7ksKw7NDt/ZZ1fh5Ei39X5tABugtT03ZRlWd9imccoK0hE8hpjpU7irQ=="
|
||||||
},
|
},
|
||||||
|
"@vueuse/core": {
|
||||||
|
"version": "8.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-8.2.6.tgz",
|
||||||
|
"integrity": "sha512-fzlpM3B5oVe+UhCT1mXlhG1Zxdq2lq1Z2AvddSB8+RxrsSFzII7DKfsQEz8Vop7Lzc++4m8drTNbhPovYoFqHw==",
|
||||||
|
"requires": {
|
||||||
|
"@vueuse/metadata": "8.2.6",
|
||||||
|
"@vueuse/shared": "8.2.6",
|
||||||
|
"vue-demi": "*"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@vueuse/shared": {
|
||||||
|
"version": "8.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-8.2.6.tgz",
|
||||||
|
"integrity": "sha512-J/W4CMfdL8TahELuSOgtfVO4eQXTjhigp7dVWIBsLUVFCeY9d49gvHUcQN3y5xYLZ6iNP57TjTQjMMT/zhklkw==",
|
||||||
|
"requires": {
|
||||||
|
"vue-demi": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vue-demi": {
|
||||||
|
"version": "0.12.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.12.5.tgz",
|
||||||
|
"integrity": "sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==",
|
||||||
|
"requires": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@vueuse/metadata": {
|
||||||
|
"version": "8.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-8.2.6.tgz",
|
||||||
|
"integrity": "sha512-OBKtafCt+4RcEJlYDCjp1vl65pBCL2g4TmipEtdZ8/qphKlW6nakJbkY7XRN5grPmjqU99/ahJGtyGk5NHS2hw=="
|
||||||
|
},
|
||||||
"@webassemblyjs/ast": {
|
"@webassemblyjs/ast": {
|
||||||
"version": "1.11.1",
|
"version": "1.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
|
||||||
@@ -13664,6 +13783,11 @@
|
|||||||
"object-keys": "^1.0.12"
|
"object-keys": "^1.0.12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"detect-it": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/detect-it/-/detect-it-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-dg5YBTJYvogK1+dA2mBUDKzOWfYZtHVba89SyZUhc4+e3i2tzgjANFg5lDRCd3UOtRcw00vUTMK8LELcMdicug=="
|
||||||
|
},
|
||||||
"diff": {
|
"diff": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||||
|
|||||||
@@ -50,6 +50,7 @@
|
|||||||
"@typescript-eslint/parser": "^5.14.0",
|
"@typescript-eslint/parser": "^5.14.0",
|
||||||
"@vue/compiler-sfc": "^3.2.31",
|
"@vue/compiler-sfc": "^3.2.31",
|
||||||
"@vue/eslint-config-typescript": "^10.0.0",
|
"@vue/eslint-config-typescript": "^10.0.0",
|
||||||
|
"@vueuse/core": "^8.2.6",
|
||||||
"ag-grid-community": "^27.1.0",
|
"ag-grid-community": "^27.1.0",
|
||||||
"ag-grid-vue3": "^27.1.0",
|
"ag-grid-vue3": "^27.1.0",
|
||||||
"async-validator": "^4.0.7",
|
"async-validator": "^4.0.7",
|
||||||
@@ -69,6 +70,7 @@
|
|||||||
"css-loader": "^6.7.1",
|
"css-loader": "^6.7.1",
|
||||||
"cssnano": "^5.1.3",
|
"cssnano": "^5.1.3",
|
||||||
"date-fns": "^2.28.0",
|
"date-fns": "^2.28.0",
|
||||||
|
"detect-it": "^4.0.1",
|
||||||
"downloadjs": "^1.4.7",
|
"downloadjs": "^1.4.7",
|
||||||
"esbuild-loader": "^2.18.0",
|
"esbuild-loader": "^2.18.0",
|
||||||
"eslint": "^8.11.0",
|
"eslint": "^8.11.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user