mirror of
https://github.com/wavetermdev/backup.git
synced 2026-04-22 15:26:58 -07:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 45e2db0739 | |||
| 1a9ba17a95 | |||
| fd2cb82065 | |||
| 2d890e63a7 | |||
| 9b3f9cb419 |
@@ -0,0 +1,160 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { GlobalModel } from "@/models";
|
||||
import { isBlank } from "@/util/util";
|
||||
import { WorkspaceView } from "@/app/workspace/workspaceview";
|
||||
import { PluginsView } from "@/app/pluginsview/pluginsview";
|
||||
import { BookmarksView } from "@/app/bookmarks/bookmarks";
|
||||
import { HistoryView } from "@/app/history/history";
|
||||
import { ConnectionsView } from "@/app/connections/connections";
|
||||
import { ClientSettingsView } from "@/app/clientsettings/clientsettings";
|
||||
import { MainSideBar } from "@/app/sidebar/main";
|
||||
import { RightSideBar } from "@/app/sidebar/right";
|
||||
import { DisconnectedModal, ClientStopModal } from "@/modals";
|
||||
import { ModalsProvider } from "@/modals/provider";
|
||||
import { Button, TermStyleList } from "@/elements";
|
||||
import { ErrorBoundary } from "@/common/error/errorboundary";
|
||||
|
||||
import "@/app/app.less";
|
||||
|
||||
export const App2: React.FC = mobxReact.observer(() => {
|
||||
const [dcWait, setDcWait] = React.useState(false);
|
||||
const mainContentRef = React.useRef<HTMLDivElement>(null);
|
||||
const [termThemesLoaded, setTermThemesLoaded] = React.useState(false);
|
||||
|
||||
const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => {
|
||||
let isInNonTermInput = false;
|
||||
const activeElem = document.activeElement;
|
||||
if (activeElem != null && activeElem.nodeName == "TEXTAREA") {
|
||||
if (!activeElem.classList.contains("xterm-helper-textarea")) {
|
||||
isInNonTermInput = true;
|
||||
}
|
||||
}
|
||||
if (activeElem != null && activeElem.nodeName == "INPUT" && activeElem.getAttribute("type") == "text") {
|
||||
isInNonTermInput = true;
|
||||
}
|
||||
const opts: ContextMenuOpts = {};
|
||||
if (isInNonTermInput) {
|
||||
opts.showCut = true;
|
||||
}
|
||||
const sel = window.getSelection();
|
||||
if (!isBlank(sel?.toString()) || isInNonTermInput) {
|
||||
GlobalModel.contextEditMenu(e, opts);
|
||||
}
|
||||
};
|
||||
|
||||
const openMainSidebar = mobx.action(() => {
|
||||
const mainSidebarModel = GlobalModel.mainSidebarModel;
|
||||
const width = mainSidebarModel.getWidth(true);
|
||||
mainSidebarModel.saveState(width, false);
|
||||
});
|
||||
|
||||
const openRightSidebar = mobx.action(() => {
|
||||
const rightSidebarModel = GlobalModel.rightSidebarModel;
|
||||
const width = rightSidebarModel.getWidth(true);
|
||||
rightSidebarModel.saveState(width, false);
|
||||
});
|
||||
|
||||
const remotesModel = GlobalModel.remotesModel;
|
||||
const disconnected = !GlobalModel.ws.open.get() || !GlobalModel.waveSrvRunning.get();
|
||||
const hasClientStop = GlobalModel.getHasClientStop();
|
||||
const platform = GlobalModel.getPlatform();
|
||||
const clientData = GlobalModel.clientData.get();
|
||||
|
||||
// Previously, this is done in sidebar.tsx but it causes flicker when clientData is null cos screen-view shifts around.
|
||||
// Doing it here fixes the flicker cos app is not rendered until clientData is populated.
|
||||
// wait for termThemes as well (this actually means that the "connect" packet has been received)
|
||||
if (clientData == null || GlobalModel.termThemes.get() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (disconnected || hasClientStop) {
|
||||
if (!dcWait) {
|
||||
setTimeout(() => setDcWait(true), 1500);
|
||||
}
|
||||
return (
|
||||
<div id="main" className={"platform-" + platform} onContextMenu={handleContextMenu}>
|
||||
<div ref={mainContentRef} className="main-content">
|
||||
<MainSideBar parentRef={mainContentRef} />
|
||||
<div className="session-view" />
|
||||
</div>
|
||||
<If condition={dcWait}>
|
||||
<If condition={disconnected}>
|
||||
<DisconnectedModal />
|
||||
</If>
|
||||
<If condition={!disconnected && hasClientStop}>
|
||||
<ClientStopModal />
|
||||
</If>
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (dcWait) {
|
||||
setTimeout(() => setDcWait(false), 0);
|
||||
}
|
||||
|
||||
// used to force a full reload of the application
|
||||
const renderVersion = GlobalModel.renderVersion.get();
|
||||
const mainSidebarCollapsed = GlobalModel.mainSidebarModel.getCollapsed();
|
||||
const rightSidebarCollapsed = GlobalModel.rightSidebarModel.getCollapsed();
|
||||
const activeMainView = GlobalModel.activeMainView.get();
|
||||
const lightDarkClass = GlobalModel.isDarkTheme.get() ? "is-dark" : "is-light";
|
||||
const mainClassName = clsx(
|
||||
"platform-" + platform,
|
||||
{
|
||||
"mainsidebar-collapsed": mainSidebarCollapsed,
|
||||
"rightsidebar-collapsed": rightSidebarCollapsed,
|
||||
},
|
||||
lightDarkClass
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<TermStyleList onRendered={() => setTermThemesLoaded(true)} />
|
||||
<div
|
||||
key={`version- + ${renderVersion}`}
|
||||
id="main"
|
||||
className={mainClassName}
|
||||
onContextMenu={handleContextMenu}
|
||||
>
|
||||
<If condition={termThemesLoaded}>
|
||||
<If condition={mainSidebarCollapsed}>
|
||||
<div key="logo-button" className="logo-button-container">
|
||||
<div className="logo-button-spacer" />
|
||||
<div className="logo-button" onClick={openMainSidebar}>
|
||||
<img src="public/logos/wave-logo.png" alt="logo" />
|
||||
</div>
|
||||
</div>
|
||||
</If>
|
||||
<If condition={GlobalModel.isDev && rightSidebarCollapsed && activeMainView == "session"}>
|
||||
<div className="right-sidebar-triggers">
|
||||
<Button className="secondary ghost right-sidebar-trigger" onClick={openRightSidebar}>
|
||||
<i className="fa-sharp fa-solid fa-sidebar-flip"></i>
|
||||
</Button>
|
||||
</div>
|
||||
</If>
|
||||
<div ref={mainContentRef} className="main-content">
|
||||
<MainSideBar parentRef={mainContentRef} />
|
||||
<ErrorBoundary>
|
||||
<PluginsView />
|
||||
<WorkspaceView />
|
||||
<HistoryView />
|
||||
<BookmarksView />
|
||||
<ConnectionsView model={remotesModel} />
|
||||
<ClientSettingsView model={remotesModel} />
|
||||
</ErrorBoundary>
|
||||
<RightSideBar parentRef={mainContentRef} />
|
||||
</div>
|
||||
<ModalsProvider />
|
||||
</If>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
@@ -520,6 +520,10 @@ electron.ipcMain.on("get-id", (event) => {
|
||||
event.returnValue = instanceId + ":" + event.processId;
|
||||
});
|
||||
|
||||
electron.ipcMain.on("get-window-id", (event) => {
|
||||
event.returnValue = event.sender.id;
|
||||
});
|
||||
|
||||
electron.ipcMain.on("get-platform", (event) => {
|
||||
event.returnValue = unamePlatform;
|
||||
});
|
||||
@@ -985,6 +989,7 @@ function configureAutoUpdater(enabled: boolean) {
|
||||
setTimeout(runActiveTimer, 5000); // start active timer, wait 5s just to be safe
|
||||
await app.whenReady();
|
||||
await createWindowWrap();
|
||||
await createWindowWrap();
|
||||
|
||||
app.on("activate", () => {
|
||||
if (electron.BrowserWindow.getAllWindows().length === 0) {
|
||||
|
||||
@@ -4,6 +4,7 @@ contextBridge.exposeInMainWorld("api", {
|
||||
hideWindow: () => ipc.Renderer.send("hide-window"),
|
||||
toggleDeveloperTools: () => ipcRenderer.send("toggle-developer-tools"),
|
||||
getId: () => ipcRenderer.sendSync("get-id"),
|
||||
getWindowId: () => ipcRenderer.sendSync("get-window-id"),
|
||||
getPlatform: () => ipcRenderer.sendSync("get-platform"),
|
||||
getIsDev: () => ipcRenderer.sendSync("get-isdev"),
|
||||
getAuthKey: () => ipcRenderer.sendSync("get-authkey"),
|
||||
|
||||
+8
-1
@@ -6,9 +6,11 @@ import * as React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { App } from "@/app/app";
|
||||
import { App2 } from "@/app2/app";
|
||||
import * as DOMPurify from "dompurify";
|
||||
import { loadFonts } from "@/util/fontutil";
|
||||
import * as textmeasure from "@/util/textmeasure";
|
||||
import { getApi } from "./models";
|
||||
|
||||
// @ts-ignore
|
||||
let VERSION = __WAVETERM_VERSION__;
|
||||
@@ -18,7 +20,12 @@ let BUILD = __WAVETERM_BUILD__;
|
||||
loadFonts();
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
let reactElem = React.createElement(App, null, null);
|
||||
let reactElem;
|
||||
if (getApi().getWindowId() === 1) {
|
||||
reactElem = React.createElement(App, null, null);
|
||||
} else {
|
||||
reactElem = React.createElement(App2, null, null);
|
||||
}
|
||||
let elem = document.getElementById("app");
|
||||
let root = createRoot(elem);
|
||||
document.fonts.ready.then(() => {
|
||||
|
||||
Vendored
+1
@@ -943,6 +943,7 @@ declare global {
|
||||
hideWindow: () => void;
|
||||
toggleDeveloperTools: () => void;
|
||||
getId: () => string;
|
||||
getWindowId: () => number;
|
||||
getIsDev: () => boolean;
|
||||
getPlatform: () => string;
|
||||
getAuthKey: () => string;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/app/*": ["src/app/*"], // Points to the src folder
|
||||
"@/app2/*": ["src/app2/*"], // Points to the src folder
|
||||
"@/util/*": ["src/util/*"], // Points to the src folder
|
||||
"@/models": ["src/models/index"], // Points directly to the index file
|
||||
"@/models/*": ["src/models/*"], // For everything else inside models
|
||||
|
||||
@@ -64,8 +64,6 @@ const (
|
||||
FileStatPacketStr = "filestat"
|
||||
LogPacketStr = "log" // logging packet (sent from waveshell back to server)
|
||||
ShellStatePacketStr = "shellstate"
|
||||
ListDirPacketStr = "listdir"
|
||||
SearchDirPacketStr = "searchdir"
|
||||
RpcInputPacketStr = "rpcinput" // rpc-followup
|
||||
SudoRequestPacketStr = "sudorequest"
|
||||
SudoResponsePacketStr = "sudoresponse"
|
||||
@@ -122,8 +120,6 @@ func init() {
|
||||
TypeStrToFactory[WriteFileDonePacketStr] = reflect.TypeOf(WriteFileDonePacketType{})
|
||||
TypeStrToFactory[LogPacketStr] = reflect.TypeOf(LogPacketType{})
|
||||
TypeStrToFactory[ShellStatePacketStr] = reflect.TypeOf(ShellStatePacketType{})
|
||||
TypeStrToFactory[ListDirPacketStr] = reflect.TypeOf(ListDirPacketType{})
|
||||
TypeStrToFactory[SearchDirPacketStr] = reflect.TypeOf(SearchDirPacketType{})
|
||||
TypeStrToFactory[FileStatPacketStr] = reflect.TypeOf(FileStatPacketType{})
|
||||
TypeStrToFactory[RpcInputPacketStr] = reflect.TypeOf(RpcInputPacketType{})
|
||||
TypeStrToFactory[SudoRequestPacketStr] = reflect.TypeOf(SudoRequestPacketType{})
|
||||
@@ -137,8 +133,6 @@ func init() {
|
||||
var _ RpcPacketType = (*ReInitPacketType)(nil)
|
||||
var _ RpcPacketType = (*StreamFilePacketType)(nil)
|
||||
var _ RpcPacketType = (*WriteFilePacketType)(nil)
|
||||
var _ RpcPacketType = (*ListDirPacketType)(nil)
|
||||
var _ RpcPacketType = (*SearchDirPacketType)(nil)
|
||||
|
||||
var _ RpcResponsePacketType = (*CmdStartPacketType)(nil)
|
||||
var _ RpcResponsePacketType = (*ResponsePacketType)(nil)
|
||||
@@ -455,12 +449,8 @@ func MakeFileStatPacketType() *FileStatPacketType {
|
||||
return &FileStatPacketType{Type: FileStatPacketStr}
|
||||
}
|
||||
|
||||
func MakeFileStatPacketFromFileInfo(listDirPk *ListDirPacketType, finfo fs.FileInfo, err string, done bool) *FileStatPacketType {
|
||||
func MakeFileStatPacketFromFileInfo(finfo fs.FileInfo, err string, done bool) *FileStatPacketType {
|
||||
resp := MakeFileStatPacketType()
|
||||
if listDirPk != nil {
|
||||
resp.RespId = listDirPk.ReqId
|
||||
resp.Path = listDirPk.Path
|
||||
}
|
||||
resp.Error = err
|
||||
resp.Done = done
|
||||
|
||||
@@ -474,50 +464,6 @@ func MakeFileStatPacketFromFileInfo(listDirPk *ListDirPacketType, finfo fs.FileI
|
||||
return resp
|
||||
}
|
||||
|
||||
type ListDirPacketType struct {
|
||||
Type string `json:"type"`
|
||||
ReqId string `json:"reqid"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
func (*ListDirPacketType) GetType() string {
|
||||
return ListDirPacketStr
|
||||
}
|
||||
|
||||
func (p *ListDirPacketType) GetReqId() string {
|
||||
return p.ReqId
|
||||
}
|
||||
|
||||
func MakeListDirPacket() *ListDirPacketType {
|
||||
return &ListDirPacketType{Type: ListDirPacketStr}
|
||||
}
|
||||
|
||||
type SearchDirPacketType struct {
|
||||
Type string `json:"type"`
|
||||
ReqId string `json:"reqid"`
|
||||
Path string `json:"path"`
|
||||
SearchQuery string `json:"searchquery"`
|
||||
}
|
||||
|
||||
func (*SearchDirPacketType) GetType() string {
|
||||
return SearchDirPacketStr
|
||||
}
|
||||
|
||||
func (p *SearchDirPacketType) GetReqId() string {
|
||||
return p.ReqId
|
||||
}
|
||||
|
||||
func (p *SearchDirPacketType) ConvertToListDir() *ListDirPacketType {
|
||||
rtn := MakeListDirPacket()
|
||||
rtn.ReqId = p.ReqId
|
||||
rtn.Path = p.Path
|
||||
return rtn
|
||||
}
|
||||
|
||||
func MakeSearchDirPacket() *SearchDirPacketType {
|
||||
return &SearchDirPacketType{Type: SearchDirPacketStr}
|
||||
}
|
||||
|
||||
type StreamFilePacketType struct {
|
||||
Type string `json:"type"`
|
||||
ReqId string `json:"reqid"`
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -661,97 +660,6 @@ func (m *MServer) streamFile(pk *packet.StreamFilePacketType) {
|
||||
return
|
||||
}
|
||||
|
||||
func (m *MServer) writeListDirErrPacket(err error, reqId string) {
|
||||
resp := packet.MakeFileStatPacketType()
|
||||
resp.RespId = reqId
|
||||
resp.Error = fmt.Sprintf("Error in list dir: %v", err)
|
||||
resp.Done = true
|
||||
m.Sender.SendPacket(resp)
|
||||
}
|
||||
|
||||
func (m *MServer) ListDir(listDirPk *packet.ListDirPacketType) {
|
||||
dirEntries, err := os.ReadDir(listDirPk.Path)
|
||||
var readDirError string = ""
|
||||
if err != nil {
|
||||
readDirError = fmt.Sprintf("error in list dir: %v", err)
|
||||
}
|
||||
curDirStat, err := os.Stat(listDirPk.Path)
|
||||
if err != nil {
|
||||
m.writeListDirErrPacket(err, listDirPk.ReqId)
|
||||
}
|
||||
resp := packet.MakeFileStatPacketFromFileInfo(listDirPk, curDirStat, readDirError, false)
|
||||
resp.Name = "."
|
||||
m.Sender.SendPacket(resp)
|
||||
curDirStat, err = os.Stat(filepath.Join(listDirPk.Path, ".."))
|
||||
if err != nil {
|
||||
m.writeListDirErrPacket(err, listDirPk.ReqId)
|
||||
return
|
||||
}
|
||||
resp = packet.MakeFileStatPacketFromFileInfo(listDirPk, curDirStat, readDirError, len(dirEntries) == 0)
|
||||
resp.Name = ".."
|
||||
m.Sender.SendPacket(resp)
|
||||
|
||||
for index := 0; index < len(dirEntries); index++ {
|
||||
dirEntry := dirEntries[index]
|
||||
dirEntryFileInfo, err := dirEntry.Info()
|
||||
if err != nil {
|
||||
m.writeListDirErrPacket(err, listDirPk.ReqId)
|
||||
return
|
||||
}
|
||||
done := index == len(dirEntries)-1
|
||||
resp = packet.MakeFileStatPacketFromFileInfo(listDirPk, dirEntryFileInfo, readDirError, done)
|
||||
m.Sender.SendPacket(resp)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MServer) SearchDir(searchDirPk *packet.SearchDirPacketType) {
|
||||
searchEmpty := true
|
||||
foundRoot := false
|
||||
err := filepath.WalkDir(searchDirPk.Path, func(path string, dirEntry fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
errString := fmt.Sprintf("%v", err)
|
||||
if strings.Contains(errString, "operation not permitted") {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
}
|
||||
fileName := filepath.Base(path)
|
||||
match, err := regexp.MatchString(searchDirPk.SearchQuery, fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if match {
|
||||
base.Logf("matched file: %v %v", path, searchDirPk.SearchQuery)
|
||||
// special case where walkdir includes the current path, which messes up the stat pk
|
||||
rootName := filepath.Base(searchDirPk.Path)
|
||||
if !foundRoot && fileName == rootName {
|
||||
foundRoot = true
|
||||
return nil
|
||||
}
|
||||
dirEntryFileInfo, err := dirEntry.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
searchEmpty = false
|
||||
resp := packet.MakeFileStatPacketFromFileInfo(searchDirPk.ConvertToListDir(), dirEntryFileInfo, "", false)
|
||||
m.Sender.SendPacket(resp)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
m.writeListDirErrPacket(err, searchDirPk.ReqId)
|
||||
} else {
|
||||
searchError := ""
|
||||
if searchEmpty {
|
||||
searchError = "none"
|
||||
}
|
||||
resp := packet.MakeFileStatPacketType()
|
||||
resp.Error = searchError
|
||||
resp.Done = true
|
||||
m.Sender.SendPacket(resp)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func int64Min(v1 int64, v2 int64) int64 {
|
||||
if v1 < v2 {
|
||||
return v1
|
||||
@@ -795,14 +703,6 @@ func (m *MServer) ProcessRpcPacket(pk packet.RpcPacketType) {
|
||||
go m.writeFile(writePk, wfc)
|
||||
return
|
||||
}
|
||||
if listDirPk, ok := pk.(*packet.ListDirPacketType); ok {
|
||||
go m.ListDir(listDirPk)
|
||||
return
|
||||
}
|
||||
if searchDirPk, ok := pk.(*packet.SearchDirPacketType); ok {
|
||||
go m.SearchDir(searchDirPk)
|
||||
return
|
||||
}
|
||||
m.Sender.SendErrorResponse(reqId, fmt.Errorf("invalid rpc type '%s'", pk.GetType()))
|
||||
}
|
||||
|
||||
|
||||
@@ -1033,7 +1033,7 @@ func configDirHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var files []*packet.FileStatPacketType
|
||||
for index := 0; index < len(entries); index++ {
|
||||
curEntry := entries[index]
|
||||
curFile := packet.MakeFileStatPacketFromFileInfo(nil, curEntry, "", false)
|
||||
curFile := packet.MakeFileStatPacketFromFileInfo(curEntry, "", false)
|
||||
files = append(files, curFile)
|
||||
}
|
||||
dirListJson, err := json.Marshal(files)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1522,14 +1522,6 @@ func (wsh *WaveshellProc) StreamFile(ctx context.Context, streamPk *packet.Strea
|
||||
return wsh.PacketRpcIter(ctx, streamPk)
|
||||
}
|
||||
|
||||
func (msh *WaveshellProc) ListDir(ctx context.Context, listDirPk *packet.ListDirPacketType) (*packet.RpcResponseIter, error) {
|
||||
return msh.PacketRpcIter(ctx, listDirPk)
|
||||
}
|
||||
|
||||
func (msh *WaveshellProc) SearchDir(ctx context.Context, searchDirPk *packet.SearchDirPacketType) (*packet.RpcResponseIter, error) {
|
||||
return msh.PacketRpcIter(ctx, searchDirPk)
|
||||
}
|
||||
|
||||
func addScVarsToState(state *packet.ShellState) *packet.ShellState {
|
||||
if state == nil {
|
||||
return nil
|
||||
@@ -2218,7 +2210,6 @@ func (wsh *WaveshellProc) PacketRpcIter(ctx context.Context, pk packet.RpcPacket
|
||||
if pk == nil {
|
||||
return nil, fmt.Errorf("PacketRpc passed nil packet")
|
||||
}
|
||||
log.Printf("sending packet: %v", pk)
|
||||
reqId := pk.GetReqId()
|
||||
wsh.ServerProc.Output.RegisterRpcSz(reqId, RpcIterChannelSize)
|
||||
err := wsh.ServerProc.Input.SendPacketCtx(ctx, pk)
|
||||
|
||||
@@ -60,6 +60,7 @@ var electronCommon = {
|
||||
extensions: [".ts", ".tsx", ".js"],
|
||||
alias: {
|
||||
"@/app": path.resolve(__dirname, "../src/app/"),
|
||||
"@/app2": path.resolve(__dirname, "../src/app2/"),
|
||||
"@/util": path.resolve(__dirname, "../src/util/"),
|
||||
"@/models": path.resolve(__dirname, "../src/models/"),
|
||||
"@/common": path.resolve(__dirname, "../src/app/common/"),
|
||||
|
||||
@@ -81,6 +81,7 @@ var webCommon = {
|
||||
extensions: [".ts", ".tsx", ".js", ".mjs", ".cjs", ".wasm", ".json", ".less", ".css"],
|
||||
alias: {
|
||||
"@/app": path.resolve(__dirname, "../src/app/"),
|
||||
"@/app2": path.resolve(__dirname, "../src/app2/"),
|
||||
"@/util": path.resolve(__dirname, "../src/util/"),
|
||||
"@/models": path.resolve(__dirname, "../src/models/"),
|
||||
"@/common": path.resolve(__dirname, "../src/app/common/"),
|
||||
|
||||
Reference in New Issue
Block a user