generated from Meekdai/Gmeek-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
适配GandiIDE可导入至共创世界GandiIDE中使用(仅供参考)可导入至共创世界GandiIDE中使用(仅供参考)适配其他编辑器可导入至其他Scratch编辑器中使用(仅供参考)可导入至其他Scratch编辑器中使用(仅供参考)
Description
// 慕然科技的灵动岛 - Scratch 3.0扩展
// 扩展ID: moranldd
// 扩展名称: 慕然科技的灵动岛
// 文档: https://ccwmoran.github.io/code/
class MoranDynamicIslandExtension {
constructor() {
this.islandElement = null;
this.islandVisible = true;
this.currentState = 'compact'; // minimized, compact, expanded
this.activities = [];
this.currentActivityIndex = 0;
// 默认样式配置 - 精确匹配iPhone灵动岛
this.config = {
position: { top: '15px', left: '50%' },
colors: {
background: 'rgba(28, 28, 30, 0.95)', // iOS深色背景
text: '#FFFFFF',
accent: '#0A84FF', // iOS系统蓝色
progress: '#32D74B' // iOS绿色
},
dimensions: {
minimized: { width: '40px', height: '40px', borderRadius: '20px' },
compact: { width: '158px', height: '37px', borderRadius: '18.5px' },
expanded: { width: '320px', minHeight: '180px', borderRadius: '22px' }
},
typography: {
fontFamily: '-apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif',
fontSize: { small: '13px', medium: '15px', large: '17px' },
fontWeight: { regular: '400', semibold: '600', bold: '700' }
}
};
// 圆形进度条配置
this.circularProgress = {
value: 0,
size: 36,
strokeWidth: 3,
radius: 16
};
this.init();
}
getInfo() {
return {
id: 'moranldd',
name: '慕然科技的灵动岛',
color: '#1C1C1E',
docsURI: 'https://ccwmoran.github.io/code/',
blocks: [
// 基础控制
{
opcode: 'showIsland',
blockType: Scratch.BlockType.COMMAND,
text: '显示灵动岛'
},
{
opcode: 'hideIsland',
blockType: Scratch.BlockType.COMMAND,
text: '隐藏灵动岛'
},
{
opcode: 'setState',
blockType: Scratch.BlockType.COMMAND,
text: '设置为[STATE]状态',
arguments: {
STATE: {
type: Scratch.ArgumentType.STRING,
menu: 'stateMenu',
defaultValue: 'compact'
}
}
},
// 内容管理
{
opcode: 'setContent',
blockType: Scratch.BlockType.COMMAND,
text: '设置内容 标题:[TITLE] 副标题:[SUBTITLE]',
arguments: {
TITLE: {
type: Scratch.ArgumentType.STRING,
defaultValue: '灵动岛'
},
SUBTITLE: {
type: Scratch.ArgumentType.STRING,
defaultValue: '慕然科技'
}
}
},
{
opcode: 'setIcon',
blockType: Scratch.BlockType.COMMAND,
text: '设置图标[ICON]',
arguments: {
ICON: {
type: Scratch.ArgumentType.STRING,
defaultValue: '●'
}
}
},
{
opcode: 'setProgress',
blockType: Scratch.BlockType.COMMAND,
text: '设置圆形进度[PERCENT]%',
arguments: {
PERCENT: {
type: Scratch.ArgumentType.NUMBER,
defaultValue: 50
}
}
},
// 活动管理
{
opcode: 'addActivity',
blockType: Scratch.BlockType.COMMAND,
text: '添加活动 标题:[TITLE]',
arguments: {
TITLE: {
type: Scratch.ArgumentType.STRING,
defaultValue: '新活动'
}
}
},
{
opcode: 'nextActivity',
blockType: Scratch.BlockType.COMMAND,
text: '切换到下一个活动'
},
{
opcode: 'clearActivities',
blockType: Scratch.BlockType.COMMAND,
text: '清空所有活动'
},
// 样式定制
{
opcode: 'setPosition',
blockType: Scratch.BlockType.COMMAND,
text: '设置位置 X:[X]% Y:[Y]px',
arguments: {
X: {
type: Scratch.ArgumentType.NUMBER,
defaultValue: 50
},
Y: {
type: Scratch.ArgumentType.NUMBER,
defaultValue: 15
}
}
},
{
opcode: 'setAccentColor',
blockType: Scratch.BlockType.COMMAND,
text: '设置强调色[COLOR]',
arguments: {
COLOR: {
type: Scratch.ArgumentType.STRING,
defaultValue: '#0A84FF'
}
}
},
// 重置功能
{
opcode: 'resetAll',
blockType: Scratch.BlockType.COMMAND,
text: '重置所有设置'
},
// 事件与报告
{
opcode: 'whenClicked',
blockType: Scratch.BlockType.HAT,
text: '当灵动岛被点击时',
isEdgeActivated: false
},
{
opcode: 'whenStateChanged',
blockType: Scratch.BlockType.HAT,
text: '当状态改变时',
isEdgeActivated: true
},
{
opcode: 'getCurrentState',
blockType: Scratch.BlockType.REPORTER,
text: '当前状态'
},
{
opcode: 'getActivityCount',
blockType: Scratch.BlockType.REPORTER,
text: '活动数量'
},
{
opcode: 'isVisible',
blockType: Scratch.BlockType.BOOLEAN,
text: '是否显示?'
}
],
menus: {
stateMenu: {
acceptReporters: true,
items: [
{ text: '最小型', value: 'minimized' },
{ text: '紧凑型', value: 'compact' },
{ text: '扩展型', value: 'expanded' }
]
}
}
};
}
// 初始化灵动岛
init() {
if (typeof document === 'undefined') return;
// 移除已存在的元素
const existing = document.getElementById('moran-dynamic-island');
if (existing) existing.remove();
// 创建主容器
this.islandElement = document.createElement('div');
this.islandElement.id = 'moran-dynamic-island';
// 应用基础样式
this.applyBaseStyles();
// 添加事件监听
this.setupEventListeners();
// 添加到页面
document.body.appendChild(this.islandElement);
// 初始内容
this.activities = [{
id: 'default',
title: '灵动岛',
subtitle: '慕然科技',
icon: '●',
progress: 0
}];
this.updateDisplay();
}
// 应用基础样式
applyBaseStyles() {
this.islandElement.style.cssText = `
position: fixed;
z-index: 10000;
transform: translateX(-50%);
top: ${this.config.position.top};
left: ${this.config.position.left};
user-select: none;
transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
`;
}
// 设置事件监听
setupEventListeners() {
let touchStartTime = 0;
let touchStartX = 0;
let isDragging = false;
// 触摸事件
this.islandElement.addEventListener('touchstart', (e) => {
touchStartTime = Date.now();
touchStartX = e.touches[0].clientX;
isDragging = false;
});
this.islandElement.addEventListener('touchmove', (e) => {
const deltaX = Math.abs(e.touches[0].clientX - touchStartX);
if (deltaX > 10) isDragging = true;
});
this.islandElement.addEventListener('touchend', (e) => {
const touchDuration = Date.now() - touchStartTime;
const deltaX = e.changedTouches[0].clientX - touchStartX;
if (isDragging && Math.abs(deltaX) > 30) {
// 滑动切换活动
this.nextActivity();
} else if (touchDuration > 500) {
// 长按切换状态
this.toggleState();
} else if (!isDragging) {
// 点击事件
this.handleClick();
}
});
// 鼠标事件
this.islandElement.addEventListener('mousedown', (e) => {
touchStartTime = Date.now();
touchStartX = e.clientX;
isDragging = false;
});
this.islandElement.addEventListener('mousemove', (e) => {
if (!touchStartX) return;
const deltaX = Math.abs(e.clientX - touchStartX);
if (deltaX > 10) isDragging = true;
});
this.islandElement.addEventListener('mouseup', (e) => {
const clickDuration = Date.now() - touchStartTime;
const deltaX = e.clientX - touchStartX;
if (isDragging && Math.abs(deltaX) > 30) {
this.nextActivity();
} else if (clickDuration > 500) {
this.toggleState();
} else if (!isDragging) {
this.handleClick();
}
touchStartX = 0;
});
}
// 切换状态
toggleState() {
const states = ['minimized', 'compact', 'expanded'];
const currentIndex = states.indexOf(this.currentState);
const nextIndex = (currentIndex + 1) % states.length;
this.setState({ STATE: states[nextIndex] });
Scratch.vm.runtime.startHats('moranldd_whenStateChanged');
}
// 处理点击
handleClick() {
Scratch.vm.runtime.startHats('moranldd_whenClicked');
}
// 更新显示
updateDisplay() {
if (!this.islandElement || this.activities.length === 0) return;
const activity = this.activities[this.currentActivityIndex];
// 根据状态渲染
switch (this.currentState) {
case 'minimized':
this.renderMinimizedView(activity);
break;
case 'compact':
this.renderCompactView(activity);
break;
case 'expanded':
this.renderExpandedView(activity);
break;
}
}
// 渲染最小型视图
renderMinimizedView(activity) {
const dim = this.config.dimensions.minimized;
const progress = activity.progress || 0;
// 创建圆形进度条SVG
const circumference = 2 * Math.PI * this.circularProgress.radius;
const offset = circumference - (progress / 100) * circumference;
const progressSVG = progress > 0 ? `
<svg width="${this.circularProgress.size}" height="${this.circularProgress.size}"
style="position: absolute; top: 0; left: 0;">
<circle cx="${this.circularProgress.size/2}" cy="${this.circularProgress.size/2}"
r="${this.circularProgress.radius}"
fill="none"
stroke="rgba(255,255,255,0.2)"
stroke-width="${this.circularProgress.strokeWidth}"/>
<circle cx="${this.circularProgress.size/2}" cy="${this.circularProgress.size/2}"
r="${this.circularProgress.radius}"
fill="none"
stroke="${this.config.colors.progress}"
stroke-width="${this.circularProgress.strokeWidth}"
stroke-dasharray="${circumference}"
stroke-dashoffset="${offset}"
transform="rotate(-90 ${this.circularProgress.size/2} ${this.circularProgress.size/2})"
stroke-linecap="round"/>
</svg>
` : '';
this.islandElement.innerHTML = `
<div style="
width: ${dim.width};
height: ${dim.height};
border-radius: ${dim.borderRadius};
background: ${this.config.colors.background};
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border: 0.5px solid rgba(255, 255, 255, 0.08);
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.25);
">
${progressSVG}
<span style="
color: ${this.config.colors.text};
font-size: 16px;
font-weight: 600;
z-index: 1;
">${activity.icon || '●'}</span>
</div>
`;
}
// 渲染紧凑型视图
renderCompactView(activity) {
const dim = this.config.dimensions.compact;
this.islandElement.innerHTML = `
<div style="
width: ${dim.width};
height: ${dim.height};
border-radius: ${dim.borderRadius};
background: ${this.config.colors.background};
backdrop-filter: blur(30px) saturate(180%);
-webkit-backdrop-filter: blur(30px) saturate(180%);
border: 0.5px solid rgba(255, 255, 255, 0.1);
display: flex;
align-items: center;
padding: 0 16px;
gap: 12px;
box-shadow: 0 6px 30px rgba(0, 0, 0, 0.3);
cursor: pointer;
transition: all 0.2s ease;
">
<span style="
color: ${this.config.colors.text};
font-size: 16px;
min-width: 16px;
">${activity.icon || '●'}</span>
<div style="flex: 1; overflow: hidden;">
<div style="
color: ${this.config.colors.text};
font-size: 13px;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-family: ${this.config.typography.fontFamily};
">${activity.title}</div>
${activity.subtitle ? `
<div style="
color: rgba(255, 255, 255, 0.7);
font-size: 11px;
font-weight: 400;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-top: 2px;
font-family: ${this.config.typography.fontFamily};
">${activity.subtitle}</div>
` : ''}
</div>
${this.activities.length > 1 ? `
<div style="
display: flex;
gap: 4px;
">
${this.activities.map((_, i) => `
<div style="
width: 4px;
height: 4px;
border-radius: 2px;
background: ${i === this.currentActivityIndex ? this.config.colors.accent : 'rgba(255,255,255,0.2)'};
"></div>
`).join('')}
</div>
` : ''}
</div>
`;
// 添加悬停效果
const container = this.islandElement.firstChild;
container.onmouseenter = () => {
container.style.transform = 'scale(1.02)';
container.style.boxShadow = '0 8px 35px rgba(0, 0, 0, 0.35)';
};
container.onmouseleave = () => {
container.style.transform = 'scale(1)';
container.style.boxShadow = '0 6px 30px rgba(0, 0, 0, 0.3)';
};
}
// 渲染扩展型视图
renderExpandedView(activity) {
const dim = this.config.dimensions.expanded;
const progress = activity.progress || 0;
this.islandElement.innerHTML = `
<div style="
width: ${dim.width};
min-height: ${dim.minHeight};
border-radius: ${dim.borderRadius};
background: ${this.config.colors.background};
backdrop-filter: blur(40px) saturate(180%);
-webkit-backdrop-filter: blur(40px) saturate(180%);
border: 0.5px solid rgba(255, 255, 255, 0.15);
padding: 20px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4);
cursor: pointer;
">
<div style="
color: ${this.config.colors.text};
text-align: center;
font-family: ${this.config.typography.fontFamily};
">
<div style="font-size: 24px; margin-bottom: 12px;">
${activity.icon || '●'}
</div>
<div style="
font-size: 17px;
font-weight: 600;
margin-bottom: 6px;
">${activity.title}</div>
${activity.subtitle ? `
<div style="
font-size: 13px;
color: rgba(255, 255, 255, 0.7);
margin-bottom: 20px;
">${activity.subtitle}</div>
` : ''}
${progress > 0 ? `
<div style="margin: 25px 0;">
<div style="
font-size: 21px;
font-weight: 700;
color: ${this.config.colors.progress};
margin-bottom: 8px;
">${progress}%</div>
<div style="
width: 100px;
height: 100px;
margin: 0 auto;
position: relative;
">
<svg width="100" height="100">
<circle cx="50" cy="50" r="40"
fill="none"
stroke="rgba(255,255,255,0.1)"
stroke-width="8"/>
<circle cx="50" cy="50" r="40"
fill="none"
stroke="${this.config.colors.progress}"
stroke-width="8"
stroke-dasharray="251.2"
stroke-dashoffset="${251.2 - (progress * 2.512)}"
transform="rotate(-90 50 50)"
stroke-linecap="round"/>
</svg>
</div>
</div>
` : ''}
<div style="
font-size: 11px;
color: rgba(255, 255, 255, 0.5);
margin-top: 20px;
padding-top: 15px;
border-top: 0.5px solid rgba(255, 255, 255, 0.1);
">长按返回 • 滑动切换</div>
</div>
</div>
`;
}
// ========== 积木实现 ==========
showIsland() {
if (this.islandElement) {
this.islandElement.style.display = 'block';
this.islandVisible = true;
}
}
hideIsland() {
if (this.islandElement) {
this.islandElement.style.display = 'none';
this.islandVisible = false;
}
}
setState(args) {
this.currentState = args.STATE;
this.updateDisplay();
return Scratch.vm.runtime.startHats('moranldd_whenStateChanged');
}
setContent(args) {
if (this.activities.length > 0) {
this.activities[this.currentActivityIndex].title = args.TITLE;
this.activities[this.currentActivityIndex].subtitle = args.SUBTITLE;
this.updateDisplay();
}
}
setIcon(args) {
if (this.activities.length > 0) {
this.activities[this.currentActivityIndex].icon = args.ICON;
this.updateDisplay();
}
}
setProgress(args) {
const percent = Math.max(0, Math.min(100, args.PERCENT));
if (this.activities.length > 0) {
this.activities[this.currentActivityIndex].progress = percent;
this.circularProgress.value = percent;
this.updateDisplay();
}
}
addActivity(args) {
const newActivity = {
id: `activity_${Date.now()}`,
title: args.TITLE,
subtitle: '',
icon: '●',
progress: 0
};
this.activities.push(newActivity);
if (this.activities.length > 3) this.activities.shift();
this.updateDisplay();
}
nextActivity() {
if (this.activities.length <= 1) return;
this.currentActivityIndex = (this.currentActivityIndex + 1) % this.activities.length;
this.updateDisplay();
}
clearActivities() {
this.activities = [{
id: 'default',
title: '灵动岛',
subtitle: '慕然科技',
icon: '●',
progress: 0
}];
this.currentActivityIndex = 0;
this.updateDisplay();
}
setPosition(args) {
if (this.islandElement) {
this.config.position.left = `${args.X}%`;
this.config.position.top = `${args.Y}px`;
this.applyBaseStyles();
}
}
setAccentColor(args) {
this.config.colors.accent = args.COLOR;
this.updateDisplay();
}
resetAll() {
// 重置所有配置
this.config = {
position: { top: '15px', left: '50%' },
colors: {
background: 'rgba(28, 28, 30, 0.95)',
text: '#FFFFFF',
accent: '#0A84FF',
progress: '#32D74B'
},
dimensions: {
minimized: { width: '40px', height: '40px', borderRadius: '20px' },
compact: { width: '158px', height: '37px', borderRadius: '18.5px' },
expanded: { width: '320px', minHeight: '180px', borderRadius: '22px' }
},
typography: {
fontFamily: '-apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif',
fontSize: { small: '13px', medium: '15px', large: '17px' },
fontWeight: { regular: '400', semibold: '600', bold: '700' }
}
};
this.circularProgress = {
value: 0,
size: 36,
strokeWidth: 3,
radius: 16
};
this.currentState = 'compact';
this.clearActivities();
this.applyBaseStyles();
this.updateDisplay();
}
whenClicked() {
return false; // Hat积木,由事件触发
}
whenStateChanged() {
return false; // Hat积木,由事件触发
}
getCurrentState() {
return this.currentState;
}
getActivityCount() {
return this.activities.length;
}
isVisible() {
return this.islandVisible;
}
}
// 注册扩展
if (typeof Scratch !== 'undefined' && Scratch.extensions) {
Scratch.extensions.register(new MoranDynamicIslandExtension());
}Metadata
Metadata
Assignees
Labels
适配GandiIDE可导入至共创世界GandiIDE中使用(仅供参考)可导入至共创世界GandiIDE中使用(仅供参考)适配其他编辑器可导入至其他Scratch编辑器中使用(仅供参考)可导入至其他Scratch编辑器中使用(仅供参考)