Skip to content

Commit eb8ace9

Browse files
authored
Merge pull request #6 from FreecellRaid/feat/mobile-editor
Feat/mobile editor
2 parents d5179ce + 860dfa2 commit eb8ace9

64 files changed

Lines changed: 2369 additions & 605 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

index.html

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,40 +5,9 @@
55
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
77
<title>Freecell Log Studio</title>
8-
9-
<style>
10-
#mobile-mask {
11-
display: none;
12-
position: fixed;
13-
width: 100%;
14-
height: 100%;
15-
background-color: #ffffff;
16-
z-index: 9999;
17-
align-items: center;
18-
justify-content: center;
19-
text-align: center;
20-
}
21-
22-
@media (max-width: 480px) {
23-
#mobile-mask {
24-
display: flex;
25-
}
26-
#app {
27-
display: none;
28-
}
29-
}
30-
.tip-text {
31-
font-size: 18px;
32-
color: #333;
33-
font-family: sans-serif;
34-
}
35-
</style>
368
</head>
379

3810
<body>
39-
<div id="mobile-mask">
40-
<div class="tip-text">为了获得最佳体验,请使用电脑网页端访问</div>
41-
</div>
4211
<div id="app"></div>
4312
<script type="module" src="/src/main.ts"></script>
4413
</body>

src/App.vue

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
<template>
2-
<Editor :class="['ide-container', { 'dark-mode': uiStore.isDarkMode }]" />
2+
<component
3+
:is="isMobile ? MobileEditor : Editor"
4+
:class="[{ 'dark-mode': uiStore.isDarkMode }]"
5+
/>
36
</template>
47

58
<script setup lang="ts">
6-
import Editor from './views/Editor.vue';
7-
import { useUiStore } from './stores/uiStore';
8-
import { useScrollbarVisibility } from '@/composables/useScrollbarVisibility';
9+
import Editor from './views/DesktopEditor.vue';
10+
import MobileEditor from './views/MobileEditor.vue';
11+
import { useUiStore } from './stores/ui/uiStore.js';
12+
import { useResponsiveMode } from '@/composables/ui/useResponsiveMode.js';
913
import { onMounted } from 'vue';
14+
1015
const uiStore = useUiStore();
16+
const { isMobile } = useResponsiveMode();
17+
1118
onMounted(() => {
1219
uiStore.initTheme();
13-
useScrollbarVisibility();
1420
});
1521
</script>

src/components/common/HelpDocument.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ import {
8080
CodeXml,
8181
Upload,
8282
} from '@lucide/vue';
83-
import { useWindowStore } from '@/stores/windowStore';
83+
import { useWindowStore } from '@/stores/ui/windowStore';
8484
import helpDocumentJson from '@/data/helpDocument.json';
8585
8686
type HelpIconName =
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
<template>
2+
<div class="message-detail-editor">
3+
<div v-if="showMeta" class="prop-item-time">
4+
<label>时间:</label>
5+
<div>{{ formatDate(message.time) }}</div>
6+
</div>
7+
8+
<div class="prop-item">
9+
<label>玩家名</label>
10+
<input
11+
type="text"
12+
:value="values.playerName"
13+
@input="emitTextInput('playerName', $event)"
14+
@blur="emitCommit('playerName')"
15+
@keydown.enter.exact.prevent="emitCommit('playerName')"
16+
/>
17+
</div>
18+
19+
<div class="prop-item">
20+
<label>账号</label>
21+
<input
22+
type="text"
23+
:value="values.account"
24+
@input="emitTextInput('account', $event)"
25+
@blur="emitCommit('account')"
26+
@keydown.enter.exact.prevent="emitCommit('account')"
27+
/>
28+
</div>
29+
30+
<div class="prop-item">
31+
<label>身份</label>
32+
<select
33+
:value="values.role"
34+
@change="emitFieldChange('role', getRoleValue($event))"
35+
>
36+
<option value="pl">玩家</option>
37+
<option value="gm">主持人</option>
38+
<option value="npc">NPC</option>
39+
<option value="ob">观众</option>
40+
<option value="bot">骰子</option>
41+
<option value="unknown">未知</option>
42+
</select>
43+
</div>
44+
45+
<div class="prop-row">
46+
<ToggleButton
47+
:model-value="values.isOoc"
48+
@update:model-value="
49+
emitFieldChange('isOoc', Boolean($event))
50+
"
51+
>
52+
场外消息
53+
</ToggleButton>
54+
<ToggleButton
55+
:model-value="values.isCommand"
56+
@update:model-value="
57+
emitFieldChange('isCommand', Boolean($event))
58+
"
59+
>
60+
指令消息
61+
</ToggleButton>
62+
</div>
63+
64+
<div class="prop-item full-width">
65+
<label>消息内容</label>
66+
<textarea
67+
:value="values.content"
68+
:rows="contentRows"
69+
@input="emitTextInput('content', $event)"
70+
@blur="emitCommit('content')"
71+
@keydown.enter.exact.prevent="emitCommit('content')"
72+
></textarea>
73+
</div>
74+
75+
<div class="prop-item full-width">
76+
<label>备注</label>
77+
<input
78+
type="text"
79+
:value="values.note"
80+
placeholder="备注信息..."
81+
@input="emitTextInput('note', $event)"
82+
@blur="emitCommit('note')"
83+
@keydown.enter.exact.prevent="emitCommit('note')"
84+
/>
85+
</div>
86+
</div>
87+
</template>
88+
89+
<script setup lang="ts">
90+
import ToggleButton from '@/components/common/ToggleButton.vue';
91+
import type { Message, RoleType } from '@/types/log';
92+
import { formatDate } from '@/utils/date';
93+
94+
export type MessageDetailTextField =
95+
| 'playerName'
96+
| 'account'
97+
| 'content'
98+
| 'note';
99+
100+
export interface MessageDetailValues {
101+
playerName: string;
102+
account: string;
103+
role: RoleType;
104+
content: string;
105+
note: string;
106+
isOoc: boolean;
107+
isCommand: boolean;
108+
}
109+
110+
const props = withDefaults(
111+
defineProps<{
112+
message: Message;
113+
values: MessageDetailValues;
114+
showMeta?: boolean;
115+
contentRows?: number;
116+
}>(),
117+
{
118+
showMeta: true,
119+
contentRows: 5,
120+
},
121+
);
122+
123+
const emit = defineEmits<{
124+
(e: 'textInput', field: MessageDetailTextField, value: string): void;
125+
(e: 'commitText', field: MessageDetailTextField): void;
126+
<K extends keyof MessageDetailValues>(
127+
e: 'fieldChange',
128+
field: K,
129+
value: MessageDetailValues[K],
130+
): void;
131+
}>();
132+
133+
function emitTextInput(field: MessageDetailTextField, event: Event) {
134+
emit('textInput', field, getEventValue(event));
135+
}
136+
137+
function emitCommit(field: MessageDetailTextField) {
138+
emit('commitText', field);
139+
}
140+
141+
function emitFieldChange<K extends keyof MessageDetailValues>(
142+
field: K,
143+
value: MessageDetailValues[K],
144+
) {
145+
emit('fieldChange', field, value);
146+
}
147+
148+
function getEventValue(event: Event) {
149+
return (event.target as HTMLInputElement | HTMLTextAreaElement).value;
150+
}
151+
152+
function getRoleValue(event: Event): RoleType {
153+
return (event.target as HTMLSelectElement).value as RoleType;
154+
}
155+
156+
void props;
157+
</script>
158+
159+
<style scoped>
160+
.message-detail-editor {
161+
padding: 10px;
162+
display: grid;
163+
grid-template-columns: repeat(2, minmax(0, 1fr));
164+
gap: 8px;
165+
}
166+
167+
.prop-item-time {
168+
grid-column: span 2;
169+
display: flex;
170+
gap: 4px;
171+
font-size: 12px;
172+
}
173+
174+
.prop-item {
175+
display: flex;
176+
flex-direction: column;
177+
gap: 4px;
178+
min-width: 0;
179+
}
180+
181+
.prop-item.full-width {
182+
grid-column: span 2;
183+
}
184+
185+
.prop-item label {
186+
font-size: 11px;
187+
color: var(--text-muted);
188+
}
189+
190+
.prop-item input[type='text'],
191+
.prop-item select,
192+
.prop-item textarea {
193+
width: 100%;
194+
box-sizing: border-box;
195+
background-color: var(--bg-primary);
196+
border: 1px solid var(--border-color);
197+
color: var(--text-primary);
198+
padding: 6px 8px;
199+
border-radius: 4px;
200+
font-size: 13px;
201+
outline: none;
202+
}
203+
204+
.prop-item input:focus,
205+
.prop-item select:focus,
206+
.prop-item textarea:focus {
207+
border-color: var(--active-accent);
208+
}
209+
210+
.prop-row {
211+
grid-column: span 2;
212+
display: flex;
213+
gap: 20px;
214+
padding-top: 5px;
215+
}
216+
217+
.prop-row :deep(.toggle-button) {
218+
font-size: 12px;
219+
}
220+
</style>

src/components/common/MessageItem.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@
9191
<script setup lang="ts">
9292
import { computed, ref, nextTick, watch } from 'vue';
9393
import type { Message } from '@/types/log';
94-
import { useStyleStore } from '@/stores/styleStore';
95-
import { useUiStore } from '@/stores/uiStore';
94+
import { useStyleStore } from '@/stores/project/styleStore';
95+
import { useUiStore } from '@/stores/ui/uiStore';
9696
import { formatDate } from '@/utils/date';
9797
import { computeStyleForMessage } from '@/editor/styleEngine';
9898
import { Trash2, Plus, Scissors, ChevronsDown } from '@lucide/vue';
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
<script setup lang="ts">
2222
import { ref } from 'vue';
2323
import { FolderOpen } from '@lucide/vue';
24-
import { useFileImport } from '@/composables/useImporter';
24+
import { useFileImport } from '@/composables/application/useImporter';
2525
2626
const { importAndApply } = useFileImport();
2727
const isFileDragging = ref(false);

src/components/workspace/MainWorkspace.vue renamed to src/components/desktop/MainWorkspace.vue

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,17 @@
4040

4141
<script setup lang="ts">
4242
import { computed, defineAsyncComponent } from 'vue';
43-
import { useWindowStore } from '@/stores/windowStore';
43+
import { useWindowStore } from '@/stores/ui/windowStore';
4444
import type { WindowInstance, WindowName } from '@/types/window';
45-
import FileImporter from '@/components/common/FileImporter.vue';
46-
import DefaultView from './DefaultView.vue';
47-
48-
const ChunkView = defineAsyncComponent(() => import('./ChunkView.vue'));
49-
const ExportPreview = defineAsyncComponent(() => import('./ExportPreview.vue'));
45+
import FileImporter from '@/components/desktop/FileImporter.vue';
46+
import DefaultView from '@/components/workspace/DefaultView.vue';
47+
48+
const ChunkView = defineAsyncComponent(
49+
() => import('@/components/workspace/ChunkView.vue'),
50+
);
51+
const ExportPreview = defineAsyncComponent(
52+
() => import('@/components/workspace/ExportPreview.vue'),
53+
);
5054
5155
const windowStore = useWindowStore();
5256
const workspacePanes = computed(() => windowStore.workspacePanes);
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,10 @@ import {
151151
UserRound,
152152
} from '@lucide/vue';
153153
import { defineAsyncComponent, ref, onMounted, onUnmounted, watch } from 'vue';
154-
import { useStyleStore } from '@/stores/styleStore';
155-
import { usePanelResize } from '@/composables/usePanelResize';
156-
import { useUiStore } from '@/stores/uiStore';
157-
import { useWindowStore } from '@/stores/windowStore';
154+
import { useStyleStore } from '@/stores/project/styleStore';
155+
import { usePanelResize } from '@/composables/ui/usePanelResize';
156+
import { useUiStore } from '@/stores/ui/uiStore';
157+
import { useWindowStore } from '@/stores/ui/windowStore';
158158
import ChunkListPanel from '@/components/panels/ChunkListPanel.vue';
159159
160160
const IdentityPanel = defineAsyncComponent(

0 commit comments

Comments
 (0)