Skip to content

Commit e54252d

Browse files
committed
feat: support mode switching with autoToggle option
Add ability to toggle inspector activation mode via keyboard shortcuts. Users can now switch between always-on and hotkey-triggered modes without restarting the dev server. New option: - autoToggle (default: true): Enable mode switching via shortcut Changes: - Add mode toggle logic to client component - Add comprehensive tests for mode switching - Update type definitions
1 parent 6680218 commit e54252d

File tree

9 files changed

+365
-33
lines changed

9 files changed

+365
-33
lines changed

packages/core/src/client/index.ts

Lines changed: 179 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ interface ActiveNode {
6767
class?: 'tooltip-top' | 'tooltip-bottom';
6868
}
6969

70+
type InspectorAction = 'copy' | 'locate' | 'target' | 'all';
71+
type TrackAction = InspectorAction | 'default';
72+
type ResolvedAction = InspectorAction | 'none';
73+
7074
const PopperWidth = 300;
7175

7276
function nextTick() {
@@ -89,7 +93,9 @@ export class CodeInspectorComponent extends LitElement {
8993
@property()
9094
locate: boolean = true;
9195
@property()
92-
copy: boolean | string = false;
96+
copy: boolean | string = true;
97+
@property({ attribute: 'default-action' })
98+
defaultAction: InspectorAction = 'copy';
9399
@property()
94100
target: string = '';
95101
@property()
@@ -531,16 +537,41 @@ export class CodeInspectorComponent extends LitElement {
531537
};
532538

533539
// 触发功能的处理
534-
trackCode = () => {
535-
if (this.locate) {
536-
// 请求本地服务端,打开vscode
540+
trackCode = (action: TrackAction = 'default') => {
541+
let resolvedAction: ResolvedAction;
542+
if (action === 'default') {
543+
resolvedAction = this.getDefaultAction();
544+
} else if (action === 'all') {
545+
resolvedAction = this.copy || this.locate || this.target ? 'all' : 'none';
546+
} else if (this.isActionEnabled(action)) {
547+
resolvedAction = action;
548+
} else {
549+
resolvedAction = 'none';
550+
}
551+
552+
if (resolvedAction === 'none') {
553+
return;
554+
}
555+
556+
const shouldLocate =
557+
(resolvedAction === 'locate' || resolvedAction === 'all') && this.locate;
558+
const shouldCopy =
559+
(resolvedAction === 'copy' || resolvedAction === 'all') && !!this.copy;
560+
const shouldTarget =
561+
(resolvedAction === 'target' || resolvedAction === 'all') && !!this.target;
562+
563+
if (!shouldLocate && !shouldCopy && !shouldTarget) {
564+
return;
565+
}
566+
567+
if (shouldLocate) {
537568
if (this.sendType === 'xhr') {
538569
this.sendXHR();
539570
} else {
540571
this.sendImg();
541572
}
542573
}
543-
if (this.copy) {
574+
if (shouldCopy) {
544575
const path = formatOpenPath(
545576
this.element.path,
546577
String(this.element.line),
@@ -549,14 +580,133 @@ export class CodeInspectorComponent extends LitElement {
549580
);
550581
this.copyToClipboard(path[0]);
551582
}
552-
if (this.target) {
583+
if (shouldTarget) {
553584
window.open(this.buildTargetUrl(), '_blank');
554585
}
555-
window.dispatchEvent(new CustomEvent('code-inspector:trackCode', {
556-
detail: this.element,
557-
}));
586+
window.dispatchEvent(
587+
new CustomEvent('code-inspector:trackCode', {
588+
detail: this.element,
589+
})
590+
);
591+
};
592+
593+
private getDefaultAction(): ResolvedAction {
594+
const resolved = this.resolvePreferredAction(this.defaultAction);
595+
if (resolved !== 'none' && resolved !== this.defaultAction) {
596+
this.defaultAction = resolved;
597+
}
598+
return resolved;
599+
}
600+
601+
private isActionEnabled(action: Exclude<InspectorAction, 'all'>): boolean {
602+
if (action === 'copy') {
603+
return !!this.copy;
604+
}
605+
if (action === 'locate') {
606+
return !!this.locate;
607+
}
608+
return !!this.target;
609+
}
610+
611+
private resolvePreferredAction(
612+
preferred: InspectorAction
613+
): ResolvedAction {
614+
if (preferred === 'all') {
615+
return this.copy || this.locate || this.target ? 'all' : 'none';
616+
}
617+
if (this.isActionEnabled(preferred)) {
618+
return preferred;
619+
}
620+
const fallbackOrder: Array<Exclude<InspectorAction, 'all'>> = [
621+
'copy',
622+
'locate',
623+
'target',
624+
];
625+
for (const candidate of fallbackOrder) {
626+
if (candidate !== preferred && this.isActionEnabled(candidate)) {
627+
return candidate;
628+
}
629+
}
630+
return 'none';
631+
}
632+
633+
private getAvailableDefaultActions(): InspectorAction[] {
634+
const actions: InspectorAction[] = [];
635+
if (this.copy) {
636+
actions.push('copy');
637+
}
638+
if (this.locate) {
639+
actions.push('locate');
640+
}
641+
if (this.target) {
642+
actions.push('target');
643+
}
644+
if (actions.length > 1 && this.copy && this.locate) {
645+
actions.push('all');
646+
}
647+
return actions;
648+
}
649+
650+
private handleModeShortcut = (e: KeyboardEvent) => {
651+
if (!e.shiftKey || !e.altKey) {
652+
return;
653+
}
654+
const code = e.code?.toLowerCase();
655+
const key = e.key?.toLowerCase();
656+
const isCKey = code ? code === 'keyc' : key === 'c';
657+
if (!isCKey) {
658+
return;
659+
}
660+
e.preventDefault();
661+
e.stopPropagation();
662+
const actions = this.getAvailableDefaultActions();
663+
if (actions.length <= 1) {
664+
return;
665+
}
666+
const currentIndex = actions.indexOf(this.defaultAction);
667+
const nextAction =
668+
currentIndex === -1
669+
? actions[0]
670+
: actions[(currentIndex + 1) % actions.length];
671+
this.defaultAction = nextAction;
672+
this.printModeChange(nextAction);
558673
};
559674

675+
private printModeChange(action: InspectorAction) {
676+
if (this.hideConsole) {
677+
return;
678+
}
679+
const label = this.getActionLabel(action);
680+
const agent =
681+
typeof navigator !== 'undefined' ? navigator.userAgent.toLowerCase() : '';
682+
const isWindows = ['windows', 'win32', 'wow32', 'win64', 'wow64'].some(
683+
(item) => agent.toUpperCase().includes(item.toUpperCase())
684+
);
685+
const shortcut = isWindows ? 'Shift+Alt+C' : 'Shift+Opt+C';
686+
console.log(
687+
`%c[code-inspector-plugin]%c Mode switched to %c${label}%c (${shortcut})`,
688+
'color: #006aff; font-weight: bolder; font-size: 12px;',
689+
'color: #006aff; font-size: 12px;',
690+
'color: #00B42A; font-weight: bold; font-size: 12px;',
691+
'color: #006aff; font-size: 12px;'
692+
);
693+
}
694+
695+
private getActionLabel(action: ResolvedAction): string {
696+
switch (action) {
697+
case 'copy':
698+
return 'Copy Path';
699+
case 'locate':
700+
return 'Open in IDE';
701+
case 'target':
702+
return 'Open Target Link';
703+
case 'all':
704+
return 'Copy + Open';
705+
default:
706+
return 'Disabled';
707+
}
708+
}
709+
560710
copyToClipboard(text: string) {
561711
if (typeof navigator?.clipboard?.writeText === 'function') {
562712
navigator.clipboard.writeText(text);
@@ -657,8 +807,10 @@ export class CodeInspectorComponent extends LitElement {
657807
e.stopPropagation();
658808
// 阻止默认事件
659809
e.preventDefault();
660-
// 唤醒 vscode
661-
this.trackCode();
810+
const primaryAction = this.getDefaultAction();
811+
if (primaryAction !== 'none') {
812+
this.trackCode(primaryAction as InspectorAction);
813+
}
662814
// 清除遮罩层
663815
this.removeCover();
664816
if (this.autoToggle) {
@@ -756,6 +908,7 @@ export class CodeInspectorComponent extends LitElement {
756908
const isWindows = ['windows', 'win32', 'wow32', 'win64', 'wow64'].some(
757909
(item) => agent.toUpperCase().match(item.toUpperCase())
758910
);
911+
const modeShortcut = isWindows ? 'Shift+Alt+C' : 'Shift+Opt+C';
759912
const hotKeyMap = isWindows ? WindowsHotKeyMap : MacHotKeyMap;
760913
const keys = this.hotKeys
761914
.split(',')
@@ -771,10 +924,11 @@ export class CodeInspectorComponent extends LitElement {
771924
}
772925
});
773926
const replacement = '%c';
927+
const currentMode = this.getActionLabel(this.getDefaultAction());
774928
console.log(
775929
`${replacement}[code-inspector-plugin]${replacement}Press and hold ${keys.join(
776930
` ${replacement}+ `
777-
)}${replacement} to enable the feature. (click on page elements to locate the source code in the editor)`,
931+
)}${replacement} to enable the feature. (Current mode: ${currentMode}; press ${modeShortcut} to switch)`,
778932
'color: #006aff; font-weight: bolder; font-size: 12px;',
779933
...colors
780934
);
@@ -829,7 +983,7 @@ export class CodeInspectorComponent extends LitElement {
829983

830984
handleClickTreeNode = (node: TreeNode) => {
831985
this.element = node;
832-
this.trackCode();
986+
this.trackCode('locate');
833987
this.removeLayerPanel();
834988
};
835989

@@ -883,6 +1037,7 @@ export class CodeInspectorComponent extends LitElement {
8831037
window.addEventListener('click', this.handleMouseClick, true);
8841038
window.addEventListener('pointerdown', this.handlePointerDown, true);
8851039
window.addEventListener('keyup', this.handleKeyUp, true);
1040+
window.addEventListener('keydown', this.handleModeShortcut, true);
8861041
window.addEventListener('mouseleave', this.removeCover, true);
8871042
window.addEventListener('mouseup', this.handleMouseUp, true);
8881043
window.addEventListener('touchend', this.handleMouseUp, true);
@@ -897,6 +1052,7 @@ export class CodeInspectorComponent extends LitElement {
8971052
window.removeEventListener('click', this.handleMouseClick, true);
8981053
window.removeEventListener('pointerdown', this.handlePointerDown, true);
8991054
window.removeEventListener('keyup', this.handleKeyUp, true);
1055+
window.removeEventListener('keydown', this.handleModeShortcut, true);
9001056
window.removeEventListener('mouseleave', this.removeCover, true);
9011057
window.removeEventListener('mouseup', this.handleMouseUp, true);
9021058
window.removeEventListener('touchend', this.handleMouseUp, true);
@@ -967,6 +1123,13 @@ export class CodeInspectorComponent extends LitElement {
9671123
bottom: this.activeNode.bottom,
9681124
display: this.showNodeTree ? '' : 'none',
9691125
};
1126+
const resolvedDefaultAction = this.getDefaultAction();
1127+
const modeLabel = this.getActionLabel(resolvedDefaultAction);
1128+
const modeShortcut =
1129+
typeof navigator !== 'undefined' &&
1130+
/mac|iphone|ipad|ipod/i.test(navigator.userAgent)
1131+
? 'Shift+Opt+C'
1132+
: 'Shift+Alt+C';
9701133

9711134
return html`
9721135
<div
@@ -995,7 +1158,9 @@ export class CodeInspectorComponent extends LitElement {
9951158
<div class="name-line">
9961159
<div class="element-name">
9971160
<span class="element-title">&lt;${this.element.name}&gt;</span>
998-
<span class="element-tip">click to open editor</span>
1161+
<span class="element-tip">
1162+
Mode: ${modeLabel} · ${modeShortcut} to switch
1163+
</span>
9991164
</div>
10001165
</div>
10011166
<div class="path-line">

packages/core/src/server/use-client.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,12 @@ export function getWebComponentCode(options: CodeOptions, port: number) {
147147
ip = false,
148148
bundler,
149149
} = options || ({} as CodeOptions);
150-
const { locate = true, copy = false, target = '' } = behavior;
150+
const {
151+
locate = true,
152+
copy = true,
153+
target = '',
154+
defaultAction = 'copy',
155+
} = behavior;
151156
return `
152157
;(function (){
153158
if (typeof window !== 'undefined') {
@@ -164,6 +169,7 @@ export function getWebComponentCode(options: CodeOptions, port: number) {
164169
inspector.copy = ${typeof copy === 'string' ? `'${copy}'` : !!copy};
165170
inspector.target = '${target}';
166171
inspector.ip = '${getIP(ip)}';
172+
inspector.defaultAction = ${JSON.stringify(defaultAction)};
167173
document.documentElement.append(inspector);
168174
}
169175
}

packages/core/src/shared/type.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export type Behavior = {
66
locate?: boolean;
77
copy?: boolean | string;
88
target?: string;
9+
defaultAction?: 'copy' | 'locate' | 'target' | 'all';
910
};
1011
export type RecordInfo = {
1112
port: number;

packages/core/types/client/index.d.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ interface ActiveNode {
3535
visibility?: 'visible' | 'hidden';
3636
class?: 'tooltip-top' | 'tooltip-bottom';
3737
}
38+
type InspectorAction = 'copy' | 'locate' | 'target' | 'all';
39+
type TrackAction = InspectorAction | 'default';
3840
export declare class CodeInspectorComponent extends LitElement {
3941
hotKeys: string;
4042
port: number;
@@ -43,6 +45,7 @@ export declare class CodeInspectorComponent extends LitElement {
4345
hideConsole: boolean;
4446
locate: boolean;
4547
copy: boolean | string;
48+
defaultAction: InspectorAction;
4649
target: string;
4750
ip: string;
4851
position: {
@@ -133,7 +136,14 @@ export declare class CodeInspectorComponent extends LitElement {
133136
sendXHR: () => void;
134137
sendImg: () => void;
135138
buildTargetUrl: () => string;
136-
trackCode: () => void;
139+
trackCode: (action?: TrackAction) => void;
140+
private getDefaultAction;
141+
private isActionEnabled;
142+
private resolvePreferredAction;
143+
private getAvailableDefaultActions;
144+
private handleModeShortcut;
145+
private printModeChange;
146+
private getActionLabel;
137147
copyToClipboard(text: string): void;
138148
handleDrag: (e: MouseEvent | TouchEvent) => void;
139149
isSamePositionNode: (node1: HTMLElement, node2: HTMLElement) => boolean;

packages/core/types/shared/type.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export type Behavior = {
66
locate?: boolean;
77
copy?: boolean | string;
88
target?: string;
9+
defaultAction?: 'copy' | 'locate' | 'target' | 'all';
910
};
1011
export type RecordInfo = {
1112
port: number;

0 commit comments

Comments
 (0)