mirror of
https://github.com/smogon/pokemon-showdown-client.git
synced 2026-03-21 17:50:29 -05:00
Partial drag/drop panel tab rearranging
Not full support, but basic rearranging is now possible!
This commit is contained in:
parent
868244d49f
commit
f951b6f113
|
|
@ -26,10 +26,10 @@ Dex.includeData();
|
|||
console.log("DONE");
|
||||
|
||||
function es3stringify(obj) {
|
||||
let buf = JSON.stringify(obj);
|
||||
buf = buf.replace(/\"([A-Za-z][A-Za-z0-9]*)\"\:/g, '$1:');
|
||||
buf = buf.replace(/return\:/g, '"return":').replace(/new\:/g, '"new":').replace(/delete\:/g, '"delete":');
|
||||
return buf;
|
||||
const buf = JSON.stringify(obj);
|
||||
return buf.replace(/\"([A-Za-z][A-Za-z0-9]*)\"\:/g, (fullMatch, key) => (
|
||||
['return', 'new', 'delete'].includes(key) ? fullMatch : `${key}:`
|
||||
));
|
||||
}
|
||||
|
||||
function requireNoCache(pathSpec) {
|
||||
|
|
|
|||
|
|
@ -636,6 +636,17 @@ const PS = new class extends PSModel {
|
|||
leftRoomWidth = 0;
|
||||
mainmenu: MainMenuRoom = null!;
|
||||
|
||||
/**
|
||||
* The drag-and-drop API is incredibly dumb and doesn't let us know
|
||||
* what's being dragged until the `drop` event, so we track it here.
|
||||
*
|
||||
* Note that `PS.dragging` will be null if the drag was initiated
|
||||
* outside PS (e.g. dragging a team from File Explorer to PS), and
|
||||
* for security reasons it's impossible to know what they are until
|
||||
* they're dropped.
|
||||
*/
|
||||
dragging: {type: 'room', roomid: RoomID} | null = null;
|
||||
|
||||
/** Tracks whether or not to display the "Use arrow keys" hint */
|
||||
arrowKeysUsed = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -283,6 +283,10 @@ class MainMenuPanel extends PSRoomPanel<MainMenuRoom> {
|
|||
submit = (e: Event) => {
|
||||
alert('todo: implement');
|
||||
};
|
||||
handleDragStart = (e: DragEvent) => {
|
||||
const roomid = (e.currentTarget as HTMLElement).getAttribute('data-roomid') as RoomID;
|
||||
PS.dragging = {type: 'room', roomid};
|
||||
};
|
||||
renderMiniRoom(room: PSRoom) {
|
||||
const roomType = PS.roomTypes[room.type];
|
||||
const Panel = roomType ? roomType.Component : PSRoomPanel;
|
||||
|
|
@ -293,7 +297,7 @@ class MainMenuPanel extends PSRoomPanel<MainMenuRoom> {
|
|||
const room = PS.rooms[roomid]!;
|
||||
return <div class="pmbox">
|
||||
<div class="mini-window">
|
||||
<h3>
|
||||
<h3 draggable onDragStart={this.handleDragStart} data-roomid={roomid}>
|
||||
<button class="closebutton" name="closeRoom" value={roomid} aria-label="Close" tabIndex={-1}><i class="fa fa-times-circle"></i></button>
|
||||
<button class="minimizebutton" tabIndex={-1}><i class="fa fa-minus-circle"></i></button>
|
||||
{room.title}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,122 @@
|
|||
*
|
||||
* Topbar view - handles the topbar and some generic popups.
|
||||
*
|
||||
* Also sets up global event listeners.
|
||||
* Also handles global drag-and-drop support.
|
||||
*
|
||||
* @author Guangcong Luo <guangcongluo@gmail.com>
|
||||
* @license AGPLv3
|
||||
*/
|
||||
|
||||
window.addEventListener('drop', e => {
|
||||
console.log('drop ' + e.dataTransfer!.dropEffect);
|
||||
const target = e.target as HTMLElement;
|
||||
if (/^text/.test((target as HTMLInputElement).type)) {
|
||||
PS.dragging = null;
|
||||
return; // Ignore text fields
|
||||
}
|
||||
|
||||
// The default team drop action for Firefox is to open the team as a
|
||||
// URL, which needs to be prevented.
|
||||
// The default file drop action for most browsers is to open the file
|
||||
// in the tab, which is generally undesirable anyway.
|
||||
e.preventDefault();
|
||||
PS.dragging = null;
|
||||
});
|
||||
window.addEventListener('dragend', e => {
|
||||
e.preventDefault();
|
||||
PS.dragging = null;
|
||||
});
|
||||
window.addEventListener('dragover', e => {
|
||||
// this prevents the bounce-back animation
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
class PSHeader extends preact.Component<{style: {}}> {
|
||||
handleDragEnter = (e: DragEvent) => {
|
||||
console.log('dragenter ' + e.dataTransfer!.dropEffect);
|
||||
e.preventDefault();
|
||||
if (!PS.dragging) return; // TODO: handle dragging other things onto roomtabs
|
||||
/** the element being passed over */
|
||||
const target = e.currentTarget as HTMLAnchorElement;
|
||||
|
||||
const draggingRoom = PS.dragging.roomid;
|
||||
if (draggingRoom === null) return;
|
||||
|
||||
const draggedOverRoom = PS.router.extractRoomID(target.href);
|
||||
if (draggedOverRoom === null) return; // should never happen
|
||||
if (draggingRoom === draggedOverRoom) return;
|
||||
|
||||
const leftIndex = PS.leftRoomList.indexOf(draggedOverRoom);
|
||||
if (leftIndex >= 0) {
|
||||
this.dragOnto(draggingRoom, 'leftRoomList', leftIndex);
|
||||
} else {
|
||||
const rightIndex = PS.rightRoomList.indexOf(draggedOverRoom);
|
||||
if (rightIndex >= 0) {
|
||||
this.dragOnto(draggingRoom, 'rightRoomList', rightIndex);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// dropEffect !== 'none' prevents bounce-back animation in
|
||||
// Chrome/Safari/Opera
|
||||
// if (e.dataTransfer) e.dataTransfer.dropEffect = 'move';
|
||||
};
|
||||
handleDragStart = (e: DragEvent) => {
|
||||
const roomid = PS.router.extractRoomID((e.currentTarget as HTMLAnchorElement).href);
|
||||
if (!roomid) return; // should never happen
|
||||
|
||||
PS.dragging = {type: 'room', roomid};
|
||||
};
|
||||
dragOnto(fromRoom: RoomID, toRoomList: 'leftRoomList' | 'rightRoomList' | 'miniRoomList', toIndex: number) {
|
||||
// one day you will be able to rearrange mainmenu and rooms, but not today
|
||||
if (fromRoom === '' || fromRoom === 'rooms') return;
|
||||
|
||||
if (fromRoom === PS[toRoomList][toIndex]) return;
|
||||
if (fromRoom === '' && toRoomList === 'miniRoomList') return;
|
||||
|
||||
const roomLists = ['leftRoomList', 'rightRoomList', 'miniRoomList'] as const;
|
||||
let fromRoomList;
|
||||
let fromIndex = -1;
|
||||
for (const roomList of roomLists) {
|
||||
fromIndex = PS[roomList].indexOf(fromRoom);
|
||||
if (fromIndex >= 0) {
|
||||
fromRoomList = roomList;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!fromRoomList) return; // shouldn't happen
|
||||
|
||||
if (toRoomList === 'leftRoomList' && toIndex === 0) toIndex = 1; // Home is always leftmost
|
||||
if (toRoomList === 'rightRoomList' && toIndex === PS.rightRoomList.length - 1) toIndex--; // Rooms is always rightmost
|
||||
|
||||
PS[fromRoomList].splice(fromIndex, 1);
|
||||
// if dragging within the same roomlist and toIndex > fromIndex,
|
||||
// toIndex is offset by 1 now. Fortunately for us, we want to
|
||||
// drag to the right of this tab in that case, so the -1 +1
|
||||
// cancel out
|
||||
PS[toRoomList].splice(toIndex, 0, fromRoom);
|
||||
|
||||
const room = PS.rooms[fromRoom]!;
|
||||
switch (toRoomList) {
|
||||
case 'leftRoomList': room.location = 'left'; break;
|
||||
case 'rightRoomList': room.location = 'right'; break;
|
||||
case 'miniRoomList': room.location = 'mini-window'; break;
|
||||
}
|
||||
if (fromRoomList !== toRoomList) {
|
||||
if (fromRoom === PS.leftRoom.id) {
|
||||
PS.leftRoom = PS.mainmenu;
|
||||
} else if (PS.rightRoom && fromRoom === PS.rightRoom.id) {
|
||||
PS.rightRoom = PS.rooms['rooms']!;
|
||||
}
|
||||
if (toRoomList === 'rightRoomList') {
|
||||
PS.rightRoom = room;
|
||||
} else if (toRoomList === 'leftRoomList') {
|
||||
PS.leftRoom = room;
|
||||
}
|
||||
}
|
||||
PS.update();
|
||||
}
|
||||
renderRoomTab(id: RoomID) {
|
||||
const room = PS.rooms[id]!;
|
||||
const closable = (id === '' || id === 'rooms' ? '' : ' closable');
|
||||
|
|
@ -82,7 +191,15 @@ class PSHeader extends preact.Component<{style: {}}> {
|
|||
<i class="fa fa-times-circle"></i>
|
||||
</button>;
|
||||
}
|
||||
return <li><a class={className} href={`/${id}`} draggable={true}>{icon} <span>{title}</span></a>{closeButton}</li>;
|
||||
return <li>
|
||||
<a
|
||||
class={className} href={`/${id}`} draggable={true}
|
||||
onDragEnter={this.handleDragEnter} onDragStart={this.handleDragStart}
|
||||
>
|
||||
{icon} <span>{title}</span>
|
||||
</a>
|
||||
{closeButton}
|
||||
</li>;
|
||||
}
|
||||
render() {
|
||||
const userColor = window.BattleLog && {color: BattleLog.usernameColor(PS.user.userid)};
|
||||
|
|
@ -97,7 +214,7 @@ class PSHeader extends preact.Component<{style: {}}> {
|
|||
<div class="maintabbarbottom"></div>
|
||||
<div class="tabbar maintabbar"><div class="inner">
|
||||
<ul>
|
||||
{this.renderRoomTab('' as RoomID)}
|
||||
{this.renderRoomTab(PS.leftRoomList[0])}
|
||||
</ul>
|
||||
<ul>
|
||||
{PS.leftRoomList.slice(1).map(roomid => this.renderRoomTab(roomid))}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* Main view - sets up the frame, and the generic panels.
|
||||
*
|
||||
* Also sets up global event listeners.
|
||||
* Also sets up most global event listeners.
|
||||
*
|
||||
* @author Guangcong Luo <guangcongluo@gmail.com>
|
||||
* @license AGPLv3
|
||||
|
|
@ -20,6 +20,34 @@ class PSRouter {
|
|||
this.subscribeHash();
|
||||
}
|
||||
}
|
||||
extractRoomID(url: string) {
|
||||
if (url.startsWith(document.location.origin)) {
|
||||
url = url.slice(document.location.origin.length);
|
||||
} else {
|
||||
if (url.startsWith('http://')) {
|
||||
url = url.slice(7);
|
||||
} else if (url.startsWith('https://')) {
|
||||
url = url.slice(8);
|
||||
}
|
||||
if (url.startsWith(document.location.host)) {
|
||||
url = url.slice(document.location.host.length);
|
||||
} else if (PS.server.id === 'showdown' && url.startsWith('play.pokemonshowdown.com')) {
|
||||
url = url.slice(24);
|
||||
} else if (PS.server.id === 'showdown' && url.startsWith('psim.us')) {
|
||||
url = url.slice(7);
|
||||
} else if (url.startsWith('replay.pokemonshowdown.com')) {
|
||||
url = url.slice(26).replace('/', '/battle-');
|
||||
}
|
||||
}
|
||||
if (url.startsWith('/')) url = url.slice(1);
|
||||
|
||||
if (!/^[a-z0-9-]*$/.test(url)) return null;
|
||||
|
||||
const redirects = /^(appeals?|rooms?suggestions?|suggestions?|adminrequests?|bugs?|bugreports?|rules?|faq|credits?|privacy|contact|dex|insecure)$/;
|
||||
if (redirects.test(url)) return null;
|
||||
|
||||
return url as RoomID;
|
||||
}
|
||||
subscribeHash() {
|
||||
if (location.hash) {
|
||||
const currentRoomid = location.hash.slice(1);
|
||||
|
|
@ -196,7 +224,9 @@ class PSMain extends preact.Component {
|
|||
return;
|
||||
}
|
||||
if (elem.tagName === 'A' || elem.getAttribute('data-href')) {
|
||||
const roomid = this.roomidFromLink(elem as HTMLAnchorElement);
|
||||
const href = elem.getAttribute('data-href') || (elem as HTMLAnchorElement).href;
|
||||
const roomid = PS.router.extractRoomID(href);
|
||||
|
||||
if (roomid !== null) {
|
||||
PS.addRoom({
|
||||
id: roomid,
|
||||
|
|
@ -294,29 +324,6 @@ class PSMain extends preact.Component {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
roomidFromLink(elem: HTMLAnchorElement) {
|
||||
let href = elem.getAttribute('data-href');
|
||||
if (href) {
|
||||
// yes that's what we needed
|
||||
} else if (PS.server.id === 'showdown') {
|
||||
if (elem.host && elem.host !== Config.routes.client && elem.host !== 'psim.us') {
|
||||
return null;
|
||||
}
|
||||
href = elem.pathname;
|
||||
} else {
|
||||
if (elem.host !== location.host) {
|
||||
return null;
|
||||
}
|
||||
href = elem.pathname;
|
||||
}
|
||||
const roomid = href.slice(1);
|
||||
if (!/^[a-z0-9-]*$/.test(roomid)) {
|
||||
return null; // not a roomid
|
||||
}
|
||||
const redirects = /^(appeals?|rooms?suggestions?|suggestions?|adminrequests?|bugs?|bugreports?|rules?|faq|credits?|news|privacy|contact|dex|insecure)$/;
|
||||
if (redirects.test(roomid)) return null;
|
||||
return roomid as RoomID;
|
||||
}
|
||||
static containingRoomid(elem: HTMLElement) {
|
||||
let curElem: HTMLElement | null = elem;
|
||||
while (curElem) {
|
||||
|
|
|
|||
|
|
@ -53,6 +53,30 @@
|
|||
</script>
|
||||
<script src="https://play.pokemonshowdown.com/config/config.js"></script>
|
||||
<script>
|
||||
if (!window.Config) {
|
||||
// offline
|
||||
window.Config = {
|
||||
version: '0',
|
||||
bannedHosts: [],
|
||||
whitelist: [],
|
||||
routes: {
|
||||
root: "pokemonshowdown.com",
|
||||
client: "play.pokemonshowdown.com",
|
||||
dex: "dex.pokemonshowdown.com",
|
||||
replays: "replay.pokemonshowdown.com",
|
||||
users: "pokemonshowdown.com/users"
|
||||
},
|
||||
defaultserver: {
|
||||
id: 'showdown',
|
||||
host: 'sim3.psim.us',
|
||||
port: 443,
|
||||
httpport: 8000,
|
||||
altport: 80,
|
||||
registered: true
|
||||
},
|
||||
customcolors: {}
|
||||
};
|
||||
}
|
||||
Config.testclient = true;
|
||||
(function() {
|
||||
if (location.search !== '') {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user